网路是怎样连接的(七)TCP的交互(中)

网路是怎样连接的(七)TCP的交互(中)

思考重点

  • TCP如何确认对方收到消息?
  • 讯息收发中的头部消息变化?
  • 关闭连接操作?

核心知识

网路是怎样连接的(七)TCP的交互(中)

核心知识点

封包的收发

当使用connect()完成双方的通讯连接后,整个控制流程就会回到应用程式中,这时我们可以使用write()来发送消息,read()来读取消息。不过write()操作会由应用程式决定要写入多长的数据,而这些数据会透过协议栈存放在作业系统分配好的缓冲区当中,由TCP模块决定一次要发布多长的数据。同理read()操作也是向作业系统分配的特定缓冲区拿取消息

由于TCP主打可靠性与面向连接的通讯,在封包收发途中,双方需要对消息发送方回应一个ACK,代表我收到你发送的封包了。另外假设TCP真的针对应用程式下放的资料进行分段,发送方必须藉由序号功能告知接收方封包占整个资料的具体位置,以防止消息漏掉

下放应用层数据

儘管调用write()操作会指定写入长度,但协议栈会先将指定长度的数据存入缓冲中,并不会完全依照指定长度发送封包,而每个操作系统对于发布封包的长度限制都不太相同,不过它们一致的目标是要避免太短资料长度的频繁发送,以及太长资料长度的壅塞与时延

为了达到更高的效率,协议栈也允许应用程式在执行发送操作时也可以透过设置flag告诉协议栈要如何处理缓冲区消息,例如允许缓冲区在没有全满的状态下发布消息

缓冲区有额外配置计时器,用来防止太长时间没发布的问题产生

网路是怎样连接的(七)TCP的交互(中)

控制发布数据长度

上图显示一种发送组封包的功能,藉由创建一个专门组封包长度的缓冲区,它会在单位时间或者在作业系统缓冲到某个长度时将先进入的资料抓进组封包缓冲内,待达到指定长度后就会把组装好的封包发送出去(这裡是以三个封包为例子),这种功能多用于对网路连线功能有限制的设备上

TCP分段功能

一般来说应用程式下放的资料长度并不会超过MSS(最大数据包长度),所以一般应用数据不会涉及到数据的切分,也就是分段。但有些应用像是提交一份表单或是装置发布了一串非常长的资料,就有可能超出MSS的范围,因此TCP会需要对这些数据包进行处理,把它们切成小块

网路是怎样连接的(七)TCP的交互(中)

蛋糕分段

TCP的分段功能就像将刚出炉的蛋糕切成刚好的等分,并将分出来的蛋糕包装并且挂上名牌贩售给消费者,毕竟真的会有人一次买整块没切的大蛋糕吗?恐怕没有吧

TCP也是一样,它会将超过MSS的应用数据切成等分,需要特别注意应用层协定头部也被包含在被裁切的范围内

网路是怎样连接的(七)TCP的交互(中)

TCP分段功能

那麽问题来了,传输双方到底是怎麽决定裁切的长度呢?其实在通讯双方进行连接的三次握手时双方会在TCP头部的选项部分填入自己允许的MSS长度,两相比较后选择最小的那个做为TCP分段的裁切大小,在往后资料发送中若是资料长度超过MSS(这裡假设途中的500),就以500来切分数据

网路是怎样连接的(七)TCP的交互(中)

在连接建立时就会确定分段MSS大小

ACK与序号的功用

为什麽要使用TCP协定?其中一个目的便是使双方之间的通讯具备可靠性,所以理所当然,消息封包不能发了就不理它,TCP要求接收方要回应是否正确收到消息,以判断是否启动重传机制。此外,假如数据被分段了,接收方要使用收到的序号消息去判断有没有漏包,以及接收完成后要怎麽拼装

ACK就好像是聊天中的"嗯、喔"等语助词,它告诉对方我收到你的消息了,还记得我在使用网路视讯的时候,若是对话中发生延迟,整个对话会变得很奇怪,当我说完一句话,对方会有一段时间没有反应,这时就在想我该重新说一遍吗?是对方不懂我的意思吗?不过对方通常会需要过个1、2秒才会反应过来,看来我对时间延迟的标准比电脑还高很多阿…

ACK与序号

网路是怎样连接的(七)TCP的交互(中)

TCP确认序号

接收方可以透过资料总长减去TCP头部的[[网路是怎样连接的(六)TCP的交互(上)#委託TCP进行处理#TCP头部|资料偏移]]算出下一次需要从第几个bytes开始,这种机制可以避免封包的遗失,例如下一次从序号501开始,却收到序号1000的封包,这代表在传输过程中有遗失。另外作业系统也配置了Timer(时间计时器)来判断指定时间内是否接收到返回响应,进而决定是否启动重传

对传送方而言,序号就是他告诉接收方我要发送的资料是从哪裡开始,有多长。对接收方而言,就是回应发送方我成功接收了,下次封包要从第几个bytes开始

上图具体化了双方的序号沟通以及封包遗失的处理方法

  • 若是发送方的资料封包成功被接收,接收方会计算目前为止收到的资料长度,若是封包还没有完整接收,将会把现在接收的资料范围+1并写进TCP头部
  • 若是接收方没有接收到封包,或是响应在网路中遗失了,发送方会在超过等待时间后判断这个封包发送失败,它并不会追究原因,只管把相同的封包在发送一次
  • 还有一种状况是假设发送方的封包因为不明原因在网路中发生遗失,或者这个封包格式发生错误无法被接收端解析,一样会在超过一定等待时间后进行重发

ACK号与控制位ACK号差别
确认回应编号(Acknowledgment Number): 显示的是下次要接收的资料包起始位置序号,该序号-1就是目前接收到的资料总长
控制位ACK(Acknowledgment Flag): 布林值,在TCP三次握手与封包接收时会设成1表示我有收到你的资料

关于起始序号
一般来说起始的封包序号不会是1,而是在建立连接时藉由客户端与服务端互相发送的序号所产生的一个共用随机乱数,这麽做的目的是为了防止网路攻击者预测封包序号,提高安全性,不过后续的确认回復依然都是+1就对了

逾时重传机制

当客户端发送的封包在指定时间内没有获得确认消息时会启动重传机制,不过问题来了,这个时间到底是怎麽判断的?

其实TCP会动态监测包传递的收发状况,计算每次封包发送的时间并以此推测出逾时时间的大概值,这个逾时时间会依照作业系统的不同而拥有不同的时间区间(例如UNIX系统和Window使以0.5秒为单位),但在一开始因为不知道封包的连线状况所以预设逾时时间会设定为6秒

不过重传机制也不是没有任何限制的,通常作业系统会设定一个retry上限的重传限制,当发生逾时时作业系统会将逾时时间调整为2、4倍等指数倍增,但若客户端重传太多次把retry的quota耗完时就会触发断开socket连线或者重新建立连线操作

网路是怎样连接的(七)TCP的交互(中)

两种不同的TCP逾时判断状况

TCP对逾时时间的设定要求是不会轻易触发逾时,发送多于的封包,同时取大于平均值的0.5秒倍数的最小值(以UNIX举例)。不过有时候会遇到封包收发时间的波动区间过于陡峭,例如上图的第二张图,这种状况通常是网路核心发生壅塞等状况,这个时候作业系统会设定一个比一般收发状态更高的逾时时间上限

从TCP的一些操作中有没有体会到一点trade off的味道呢?毕竟现实中鱼与熊掌很难兼得的,效率、可靠性、速率的取捨会根据实际应用场景来做调整,并没有绝对的优劣之分

断开连线

断开连线的时间点发生在资料完全传送完成后,或是某些特定的操作。当浏览器向服务器请求结束会后主动断开连线,表示沟通结束。另一种状况可能是服务器判断客户端发送的资料封包不符合格式,主动断开连线。所以发起断开连线的操作可以是客户端或服务端双方,端看当前的沟通状况决定

断开连线操作

我们以客户端完成所有请求发送后主动断开连线为例,因为不管发送方或是接收方都需要发送两次FIN消息封包以及两次ACK消息封包,所以断开连线又被称为四次握手

网路是怎样连接的(七)TCP的交互(中)

TCP断开连线操作
  1. 客户端将FIN = 1的控制位消息写入TCP头部,并发送给服务器
    • 客户端进入FIN_WAIT_1等待服务端回应ACK
    • 服务器将ACK写入TCP头部
  2. 服务器回传ACK消息给客户端
    • 客户端知道服务端开始进行断开连线操作了,把状态改为FIN_WAIT_2等待服务器返回最后的FIN消息
    • 服务器协议栈将断开连线消息传达给指定应用程式,服务器处于CLOSE_WAIT状态等待结果
  3. 应用程式处理完断开连线讯息后,调用socket API,委託协议栈将含有FIN = 1的消息返回给客户端
    • 客户端接收到FIN = 1消息后,回传一个ACK并进入TIME_WAIT倒数计时状态
    • 服务器处于LAST_ACK状态,等待客户端对它返回ACK消息
  4. 服务器成功接收ACK消息
    • 客户端进行倒数计时,经过2MSL时间后自动进入CLOSE状态
    • 服务器一接收到ACK响应后随即就进入CLOSE状态

2MSL时间后才进入CLOSE

首先要注意的是只有起初发起断开请求的一方才会进入TIME_WAIT阶段。我们先假设客户端一接收到服务器发送的FIN消息马上进入CLOSE阶段,但好死不死,网路状况不稳或任何通讯延迟导致客户端ACK响应超过逾时时间,服务器会进行FIN的重传,如果又这麽刚好,客户端又刚创建一个端口号相同的应用程式socket,这时重传回来的FIN消息封包就有可能错误的关闭连线。所以一般来说会等待一段时间后才会进入CLOSE阶段,此处的MSL是 Maximum Segment Lifetime,封包最大生存时间,一般可以解释成一组FIN消息的往返需要的处理时间,Linux默认是60秒。若确定四次握手成功后客户端就会在2MSL时间过后删除相对应的套接字

#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT state, about 60 seconds */
上一篇:TCP协议概述


下一篇:Kafka介绍