TCP协议的复杂我们在上层接口已经展现出来了,报文也是相对复杂的
TCP协议
TCP 全称为 "传输控制协议(Transmission Control Protocol"). 人如其名, 要对数据的传输进行一个详细的控制;
TCP 协议段格式
对于上图中的数据不必多说,就是应用层拷贝进去的数据。
我们先进行一下简单的介绍,再解释一下我们现在能理解的位段。
- 源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去;
- 32 位序号/32 位确认号: 后面详细讲;
- 4 位 TCP 报头长度: 表示该 TCP 头部有多少个 32 位 bit(有多少个 4 字节); 所以TCP 头部最大长度是 15 * 4 = 60
- 6 位标志位:
○ URG: 紧急指针是否有效
○ ACK: 确认号是否有效
○ PSH: 提示接收端应用程序立刻从 TCP 缓冲区把数据读走
○ RST: 对方要求重新建立连接; 我们把携带 RST 标识的称为复位报文段
○ SYN: 请求建立连接; 我们把携带 SYN 标识的称为同步报文段
○ FIN: 通知对方, 本端要关闭了, 我们称携带 FIN 标识的为结束报文段 - 16 位窗口大小: 后面再说
- 16 位校验和: 发送端填充, CRC 校验. 接收端校验不通过, 则认为数据有问题. 此
处的检验和不光包含 TCP 首部, 也包含 TCP 数据部分. - 16 位紧急指针: 标识哪部分数据是紧急数据;
- 40 字节头部选项: 暂时忽略;
我们学习每个协议时都需要面对两个问题
- 如何解包?
- 如何分用?
对于解包:
我们看到报文中有4位首部长度,同时报头部分为20字节。
4位首部长度的单位是4字节->[0, 15]->[0, 60],也就是说报头部分最多有60字节,剩下的40字节是选项的大小。
如果我们的报文不带选项,首部长度应该是多少呢?
答案就是0101
。
对于分用:我们有16位目的端口号,故可以直接找到对应的进程。
确认应答(ACK)机制
ACK就是acknowledgement(应答)。
我们先来看具体的一个例子
当A和B距离的很近时,他们之间的交谈很容易
但当距离的很远时,那么通信就不是那么容易了
因为距离的很远,所以当你问问题时不知道对方是否收到,就像A问B吃饭了吗,除非B给你回复,否则你是不清楚B是否收到的。
但只要B给你回复,那么就说明上一句一定被B收到了。
这也就说明最新的一个回复是永远无法确定是否被收到。
此时我们就可以对现实的这个例子下一个结论:
只要收到了应答,就能保证我发的数据一定被对方收到了
我们映射到网络中
我们所谓的ACK就是当其中一端发了一个消息,另一端发一个ACK进行回复,若是没有收到ACK就是代表没有收到消息!
且不用对ACK进行ACK,如果这样那就不成了先鸡还是先蛋的问题?
通信模式
我们常见的通信模式有两种
左侧的效率高,右侧的效率低
所以更常见的是左侧。
但是如果发的这么密集,如果ACK丢了一个会怎么样?
就无法知道那个是缺失的了。
所以我们需要一个唯一标识符。
更细致一点的图
也就是序号。
确认序号原则上就是收到的序号+1,其中若是收到了3001,那么就说明3001之前的报文全部都收到了。
这个定义与滑动窗口有关,我们暂时不关心。
此时我们暂时先稍微看两个特殊情况,具体的会在后边谈。
- 当1001ACK收到,2001,3001...没收到,我们不会立即重传,会超时重传或者快重传之类的。
- 当4001收到,1001,2001,3001未收到会影响吗?定义规定了4001收到就代表4001之前的都收到了所以不影响,所以我们也支持少量的弄丢。
问题
问题一: 那么为什么设置为两个序号?
发的时候用一个,ACK的时候也用一个,事实上设置一个序号的话完全可以,但是我们有一个机制叫做捎带应答:
意思就是服务器进行ACK时也正好将自己要发送的数据一起发送了,这就是捎带应答。
可以大大的提高效率,所以需要两个序号。
问题二: 客户端如何知道收到的报文是应答还是携带数据?
同样,请求也有各种各样:请求链接,请求断开...
所以我们需要一个类型进行表示
我们的ACK就在其中,属于其中一种。
捎带的话也不冲突,携带上数据即可。
问题三: 如何理解序号?
逻辑上我们将发送缓冲区当做一个字符数组
所以我们所谓的序号其实就是数组下标,发送1000,其实就意味着将1-1000数组内数据全部发送,发送2000就代表将1001到2000发送,1000的ACK就是1001,2000的ACK就是2001...以此类推。
超时重传机制
• 主机 A 发送数据给 B 之后, 可能因为网络拥堵等原因, 数据无法到达主机B;
• 如果主机 A 在一个特定时间间隔内没有收到 B 发来的确认应答, 就会进行重发;
但是, 主机 A 未收到 B 发来的确认应答, 也可能是因为 ACK 丢失了;
因此主机 B 会收到很多重复数据. 那么 TCP 协议需要能够识别出那些包是重复的包, 并且把重复的丢弃掉.
这时候我们可以利用前面提到的序列号, 就可以很容易做到去重的效果.
那么, 如果超时的时间如何确定?
• 最理想的情况下, 找到一个最小的时间, 保证 "确认应答一定能在这个时间内返回".
• 但是这个时间的长短, 随着网络环境的不同, 是有差异的.
• 如果超时时间设的太长, 会影响整体的重传效率;
• 如果超时时间设的太短, 有可能会频繁发送重复的包;
TCP 为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间.
• Linux 中(BSD Unix 和 Windows 也是如此), 超时以 500ms 为一个单位进行控制, 每次判定超时重发的超时时间都是 500ms 的整数倍.
• 如果重发一次之后, 仍然得不到应答, 等待 2*500
ms 后再进行重传.
• 如果仍然得不到应答, 等待 4*500
ms 进行重传. 依次类推, 以指数形式递增.
• 累计到一定的重传次数, TCP 认为网络或者对端主机出现异常, 强制关闭连接.
注意:网络是十分复杂的,先发的不一定先到,但是到达接收缓冲区是肯定按照顺序的,否则接收方read时岂不是错乱了,所以需要序号进行按序到达。
至此我们的序号已经有三个作用了:
- 用来ACK(报头)
- 报文去重
- 按序到达
连接管理机制
在正常情况下, TCP 要经过三次握手建立连接, 四次挥手断开连接
我们先看三次握手
其中我们又会学到新的标记位:SYN(Synchronization),这个就是建立连接时报头需要携带的标记位。
我们connect发起了三次挥手,剩下的都是由OS自主完成的。
注意:accept不参与三次挥手
我们感性的理解一下:
第一次握手:做我女朋友吧
第二次握手:好啊,什么时候开始?
第三次握手:现在!
虽然最新一条客户端无法保证服务器能收到,但是一旦发出我们就认为三次握手成功了!
所以本质就是在赌!堵可以建立成功,即最后一个ACK被对方收到了。
看似很牵强,但实际上至少已经能相互验证可以互相发消息没问题的了。
就是因为在堵,所以有特殊情况的出现,也就是最后一个ACK服务器未收到,虽然说会超时重发,但此时客户端认为已经连接完成了。
客户已经发送消息了,问题就出现了,明明连接还没完全建立却发送了消息,此时服务端应答时需要一个新的标志位RST(reset)重置。
对异常连接进行释放。
所以至此以后,可能由于各种各样复杂的网络情况导致异常链接都重置即可!
持续更新~