经过了过年的忙碌和年初的懈怠一切的日子, 我又开始重新更新了~ 这是最新的一篇~ 完整版可以去 gitbook(https://www.gitbook.com/@rogerzhu/)看到
如果对和程序员有关的计算机网络知识, 和对计算机网络方面的编程有兴趣, 虽然说现在这种看不见的东西真正能在实用中遇到的机会不多, 但是我始终觉得无论计算机的语言, 热点方向怎么变化, 作为一个程序员, 很多基本的知识都应该有所了解而当时在网上搜索资料的时候, 这方面的资料真的是少的可怜, 所以, 我有幸前两年接触了这方面的知识, 我觉得我应该把我知道的记录下来, 虽然写的不一定很好, 但是希望能给需要帮助的人多个参考我的计划是用半年时间来写完这一系列文章, 这个标题也是我对太多速成文章的一种态度, 好了, 废话不再多扯了, 下面是其中的一节内容, 更多内容可以去 gitbook 上找到
TCP 与 UDP
前面对于 UDP 已经阐述了有一些内容了, UDP 可以完成一些数据的传输, 那么为什么还要再研究出另外一种传输层协议呢? 因为在很多时候, 不可靠的传输会造成上面的应用层协议变得毫无意义, 而且面对越来越复杂的网络, 没有管理控制的传输层协议更是会导致网络拥堵不断加剧直至瘫痪可以设想一下, UDP 就像是寄信, 当你把信寄出去的时候, 你是无法知道这封信可不可以到达收信人的的, 如果说唯一你能做的就是相信邮递机构这封信在沿途是丢了还是寄到什么其他地方去了, 你完全不知道 (虽然说在现在这个快递信息极端透明的情况下看起来不太可能, 但是在快递刚刚开始的时候, 这种情况太常见了) 但是如果是打电话, 这种模式就不行了, 不能说你播出一个号码, 说一段留言, 然后还不知道对方能不能接收到这个留言, 如果是这样, 我要电话还有个什么用再换个角度, 即使我真的能有这么个留言的模式打电话, 但是由于同时有太多留言, 造成电话网络压力过大, 你的留言可能因为延迟一年后才到达对方, 很明显, 电话不是这么设计的, 就像 TCP 一样, 设计他就是为了能提供可靠的通信
TCP 里最初级也是最重要的概念之一就是连接, UDP 是没有连接的协议, 通俗点说就是 UDP 的两端在通信的时候只要任一方想发送消息给其他方, 他只要发就可以了 UDP 是一个很任性的协议, 想发就发, 想断就断, 不需要实现通知对方, 也不需要做些什么准备工作 TCP 就不同了, TCP 是一个很绅士的协议, 在发之前, 发送方和接收方会先进行协调, 结束的时候呢, 双方同样也会进行相互的沟通并积极的做好自己的清理工作, 英文中对这种行为有个很恰当的词语, 叫做 graceful
绅士的开始
前面说过, TCP 是一个绅士的协议, 在发送数据之前, 双方会进行友好的协商, 这种协商也就是在所有介绍 TCP 的文章里都会提到的三次握手现在有个普遍的现象, 现在问面试者什么是三次握手, 基本都没有答不出来但是如果再进一步, 问一下, 如果在某一个步骤的时候出现了丢失, 那么会怎么样, 基本上就只剩百分之二十的人能答的出来这就是像你只知道别人的绅士行为是什么样的, 却不知道这些行为的来源, 所以如果盲目的学习只能给人一种学到皮毛的感受不过, 这也是一篇介绍 TCP 的文章, 所以当然也绕不开 TCP 的三次握手, 我用一张 wireshark 里真实抓包得到的 TCP 流来进行图示:
左边是发送方, 右边是接收方, 在介绍三次握手之前, 首先大家得回忆下前面介绍过的 TCP 的报头中的标识符位 TCP 报头中有 6 位标识符, 在置 1 之后分别代表这一个 TCP 包有不同的含义其中第五位是 SYN 位, 当这一位置 1 时表示连接的开始或者同步序号请求, SYN 就是英文同步 synchronize 的缩写除了这一个之外, 另一个会在三次握手中出现的就是 ACK, 这个是六个标识符中的第二个标识符, 英文 acknowledgement 的缩写, 主要用来表示对于对端消息的回应, 简单粗暴的理解的话, 可以理解为, 啊, 我知道了
为什么我说 TCP 是一个绅士的协议呢? 从其三次握手的过程中就可以体会的到, 请求的发起方先发送一个编号为 0 的 SYN 包到接收方, 接收方接收到这个 SYN 包之后, 首先肯定是要通知发送方我已经接受到了你的 SYN 请求, 也就是我们上面说的 ACK 但同时按照上面描述的, 如果想建立连接, 就必须发送 SYN, 所以, 对于接收方, 就有两个需要发送的包, 亦或是说两个被置不同标识的包, 但是很明显, 这两个包是可以合并的, 所以说, 发送方就会发送一个 TCP 包, 这个包里, SYN 位和 ACK 位同时被置上回到发送方, 在接收到这个对端发送来的 SYN 包之后, 同样要回一个 ACK 包给对端此时, TCP 连接就建立好了, 后面的通信中, 两端就可以自由的发送数据和消息了
这里还可以了解到的就是贯穿整个 TCP 的确认消息, TCP 如何让对端知道自己已经收到了哪些包? 前面一篇说过, TCP 报头中是有一个序列号的字段的, 这个字段用来给每一个报文编号, 这个编号在一次通信中是不断递增的, 所以理论上接收端只要告诉发送端自己已经收到的包的序号就等于告诉发送端我已经收到比这个序号小的所有的报文回到上面的图中, 可以看到第一个 SYN 包的序号是 0, 那么当接收端告知对方的 ACK 中所使用的序列号是 1, 表示标识符比 1 小的包我都接收到了在这个特定情况下, 也就等于发送端已经知道了接收端已经良好的收到了自己的 SYN 请求当然, 这个序列号, 确认号具体在 TCP 报头的什么位置, 在上一篇文章中, 可以很容易的找出来
TCP 的三次握手其实给人和人之间的交流提供了一个很好的模型, 就拿开车并道这件事来说, 如果人人都能遵循三次握手的原则, 那么我相信所有的因为并道而产生的事故都能避免前车要并道之前先闪三次转向灯, 后车闪灯表示自己已经收到前车并道信号并且可以并道, 前车再次打转向灯, 然后开始并道简直是一个标准的三次握手过程, 简洁而有效, 可惜的就是在实际生活中, 按照这样的规则做并道的人太少
被打断的绅士
虽然说三次握手的设计是一个很绅士的设计, 但是所有的时候理想和现实都是有差距的, 由于网络的复杂性, 三次握手的每一个消息都有可能在传递的过程中面临三种情况, 丢失, 延迟到达, 重复这三种情况贯穿于整个的 TCP 通信的每一步, 而 TCP 中的很多设计也是因为解决这三个情况而应运而生
在建立连接的阶段主要是丢失的问题, 在介绍丢失问题的解决思路之前, 先要介绍的一个概念是发送计时器在 TCP 中, 发送消息的时候会启动一个计时器, 这个计时器在收到相应回复的时候会重置而重新计时, 而如果一直没有收到相应的回复, 在计时器到期的时候发送端就会重发消息, 这是 TCP 重传机制里面第一层的保障
因为 TCP 发起连接的时候只有三条消息, 所以丢失也就三种情况:
第一个 SYN 消息丢失, 也就是发起者的发起请求丢失了, 所以接收者也就不会回送 SYN-ACK 消息, 因为他没有得消息刺激他回应所以过一段时间后发起者发现自己没有收到回应消息, 于是在计时器到期后, 发起端会重发 SYN 消息如果在经过了几次重传仍然没有成功以后, 尝试连接过程就终止了
第二个 SYN-ACK 消息丢失, 发送端本质上和上一种情况相同接收者因为确实已经收到了 SYN 消息并发送了回复消息, 所以其计时器已经启动了在计时器到期之后, 接收端会重发 SYN-ACK 消息, 如果几次之后还没有成功, 那么接收端会发送 RST 终止连接, RST 的含义在后文中会详细介绍
第三个来自发送端的 ACK 丢失, 接收端本质上会上一种情况相同, 最终会发送 RST 消息终止连接
单独分别从两端看这三个错误的处理方式, 并不难理解和理清楚其中的过程, 但是如果从两端一起考虑, 那么稍微想一下就知道过程变得极端的复杂, 因为在发生丢失的情况下, 两端都有定时器在计时
在 linux 的 TCP-IP 协议的实现中, 分别使用两个不同的计时器, 在发送端启动是普通的超时计时器, 在接收端启动的是 SYN-ACK 计时器超时计时器就是在发送端发送 SYN 的时候开始计时, 默认是 1 秒, 如果过了 1 秒没有收到确认, 会再次发送 SYN, 然后将计时器设置成为 2 秒, 然后依 4 秒, 8 秒, 16 秒, 以此类推当然, 在代码中有一个重试上限, 在 linux 上的默认是设置为 5 次同样的 SYN-ACK 计时器在接收端接收到 SYN 之后发出 SYN-ACK 消息之后启动, 间隔和重试次数和普通计时器都是一致的, 当然他会做一些其他的事情所以和普通计时器是有一些区别的
我们考虑实际中的情况二, 发送端发送 SYN 后未收到 SYN-ACK 消息, 同时启动计时器 A, 过了一小段时间之后, 接收端接收到了 SYN 消息, 启动计时器 B, 发送 SYN-ACK 消息, 但是这个消息丢失了 1 秒钟后, 发送端由于 A 到期, 重发 SYN, 而几乎与此同时接收端也会由于 B 到期重发 SYN-ACK 消息那么问题来了, 假设这个时候重发的 SYN 又一次成功的到达了接收端会怎样? 答案很简单, 接收端会忽略它, 因为 seq 序号重复了接收端既不会再一次发送 SYN-ACK 消息, 也不会重置计时器于是就避免不断重复的重发, 造成网络混乱甚至崩溃
如果用一句话总结的话, 就是通过超时计时器和序号的重复检测, TCP 可以同样可以很绅士的解决这些不绅士的打断
来源: https://www.cnblogs.com/ZXYloveFR/p/8529657.html