TCP报文段格式
TCP虽然是面向字节流的,但TCP传送带数据单元确是报文段。TCP报文段分为首部和数据段部分,而TCP的全部功能体现在它在首部中各字段的作用。因此,只有弄清TCP首部各字段的作用才能掌握TCP的工作原理。
TCP报文段首部的前20字节是固定的,后面4N字节是根据需要而增加的选项(N必须是整数)。因此TCP首部的最小长度是20字节 。下图是TCP报文段及首部的格式。
TCP中没有表示包长度和数据长度的字段。可由IP层获知TCP的包长由TCP的包长可知数据长度。
- 源端口号
表示发送端端口号,字段长16位(2字节)。
- 目标端口号
表示接收端端口号,字段长16位(2字节)。
- 序列号
字段长32位(4字节)。序列号(有时也叫序号)是指发送数据的位置。每发送一次数据,就会累加一次该数据字节数的大小。
序列号不会从0或1开始,而是在建立连接是由计算机生成的随机数作为其初始值,通过SYN包传给接收端主机。然后再将每转发过去的字节数累加到初始值上表示数据的位置。此外,在建立连接和断开连接时发送端SYN包和FIN包虽然不携带数据,但是也会作为一个字节增加对应的序列号。
- 确认应答号
确认应答号字段长度为32位。是指下一次应该收到的数据的序列号。实际上,它是指已收到确认应答号减一为止的数据。发送端收到这个确认应答以后可以认为在这个序号以前的数据都已被正常接收。例如,主机B接收到了主机A编号为301的数据报,数据报长度为100,于是说明主机B收到了编号为301~400的数据,那么主机B下一次就期望收到编号为401的数据,此时确认应答号就是401。
- 数据偏移
该字段表示TCP所传输的数据部分应该从TCP包的哪个位开始计算,当然也可以把它看作TCP首部的长度。该字段长4位,单位为4字节。不包括选项字段的话,上图所示的TCP的首部为20字节长,因此数据偏移字段可以设置为5,反之,如果该字段的值为5,那说明从TCP包的最一开始到数据部分的开始这20字节为止都是TCP首部,余下的部分为TCP数据。
- 保留
该字段主要是为了以后扩展时使用,其长度为4位(有些教材写的是6位)。一般设置为0,但即使收到的包在该字段不为0,此包也不会被丢弃。
- 控制位
字段长8位,每一位从左到右分别是CWR、ECE、URG、ACK、PSH、RST、SYN、FIN。当它们对应位上的值为1时,具体含义如下:
CWR:CWR标志与后面的ECE标志都用于IP首部的ECN字段。ECE标志为1时,则通知对方已将拥塞窗口缩小。
ECE:该标志表示ECN-Echo。为1会通知通信对方,从对方到这边的网络有拥塞。在收到数据包的IP首部中ECN为1时将TCP首部中的ECE设置为1。
URG:该位为1时,表示包中有需要紧急处理的数据。
ACK:该位为1时,确认应答的字段变为有效。TCP规定除了最初建立连接时的SYN包之外该位必须设置为1。
PSH:该位为1时,表示需要将收到的数据立刻传给上层协议。为0时,则不需要立即传而是先进行缓存。
RST:该位为1时表示TCP连接中出现异常必须强制断开连接。
SYN:用于建立连接。SYN为1表示希望建立连接,并在其序列号的字段进行序列化初始值的设定。
FIN:该位为1时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换FIN位置为1的TCP段。每个主机又对对方的FIN包进行确认应答以后就可以断开连接。
- 窗口大小
该字段长为16位。用于通知从相同TCP首部的确认应答号所指位置开始能够接收端数据大小(8位字节)。TCP不允许发送超过此处所示大小的数据。不过,如果窗口为0,则表示可以发送窗口探测,以了解最新的窗口大小。但是这个数据必须是1个字节。
- 校验和
TCP的校验和与UDP相似,区别在于TCP的校验和无法关闭。
TCP和UDP一样在计算校验和的时候使用TCP首部。接收端在收到TCP数据段以后,从IP首部获取IP地址信息构造TCP伪首部,再进行校验和计算。由于校验和字段里保存着除本字段以外其他部分的和的补码值,因此如果计算机校验和字段在内的所有数据的16位和以后,得出的结果是"16位全部为1"说明所收到的数据是正确的。
- 紧急指针
该字段长16位。只有在URG控制位为1时有效。该字段的数值表示本报文段中紧急数据的指针。从数据部分的首位到紧急指针所指示的位置为止为紧急数据,因此也可以说紧急指针指出了紧急数据的末尾在报文段中的位置。
例如,在Web浏览器中点击停止按钮,或使用telnet
输入ctrl+c
时都会有URG为1的包。此外,紧急指针也用作表示数据流分段的标志。
- 选项
选项字段用于提高TCP的传输性能。因为根据数据偏移(首部长度)进行控制,所以其长度最大为40字节。另外,选项字段尽量调整为32位的整数倍。
三次握手
我们都知道TCP是面向连接和可靠传输的传输协议,为了准确无误的将数据送往目标处,TCP协议采用的三次握手策略。用TCP协议把数据包送出去后,TCP不会对传送后的请求置之不理,它会向对方确认是否成功到达。握手过程中使用了TCP的标志------SYN和ACK。
什么是面向连接?
连接:在一个连接中传输的数据是有关系状态的,比如需要确定传输的对端正处在等待发送或接收到状态上。需要维护传输数据的关系,比如数据流的顺序。典型的例子就是打电话。
无连接:不用关心对端是否在线。每一个数据段的发送都是独立的一个数据个体,数据和数据之间没有关系,无需维护之间的关系。典型的例子就是发短信。
什么是可靠?
是指数据在传输过程中不会被损坏或丢失,保证数据可以正确到达。相反就是不可靠。
讲解三次握手之前,需要先了解一些专业名词。
- seq:对应首部中的序列号字段,指明希望收到的下一个数据的序号是什么。
- ack:对应首部中的确认号字段,表明多少之前的数据都已经收到了。
- LISTEN:服务端上有服务就会监听一个窗口,等待客户端来连接它。
- SYN_SENT:客户端想要连接服务器,会先打招呼,这是三次握手当中的第一次,它把请求发出去后,就变成了这个状态,表示等待服务端的确认。
- SYN_RECEIVED:服务器接收到了客户端的请求,之后需要反馈给客户端确认信息,并且同时要把自己的请求信息一起发送给客户端,此时就会变成SYN_RECEIVED状态。
- ESTABLISHD:经过三次握手后,客户端和服务器端都会变成ESTABLISHD状态,表示双方建立了连接。只有双方都是该状态,才可以顺利的传输数据。
- CLOSED:彻底关闭连接的状态(这是为方便描述假想的状态,实际不存在)
了解完TCP的特点后,再来看看三次握手:
一开始客户端和服务端都是关闭状态,相当于它们连个互相不认识,为了更好的描述三次握手,我把客户端叫做小客,把服务端叫做小服。
当小客需要小服帮忙的时候,需要先给小服发消息:"在吗?小服,我需要你帮我一个忙。"(客户度会发送SYN=1,seq=x,x是一个随机数)
小服收到消息后,会给小客发送消息:"你好小客,可以帮你,需要我怎么帮你呢?"(服务端发送SYN=1,ACK=1,同时发送seq=y,ack=x+1,这里的ack为客户端发送端seq数值加1)
小客收到小服的反馈后,给小服说了一句:"谢谢你小服,我需要你帮我......"(客户端再次跟服务端发送ACK=1,seq=x+1,ack=y+1)。
小服再次收到小客的反馈后,小服就开始帮小服做事情了。
三次握手原理总结:
- 客户端发送建立TCP连接的请求报文,其中报文中含有seq序列号,由发送端随机生成,并且将报文中的SYN字段设置为1,表示需要建立TCP连接。(SYN=1,seq=x,x为随机数)
- 服务端回复客户端发送的TCP连接请求报文,其中包含seq序列号,是由回复端随机生成的,并且将SYN设置为1ACK也设置为1,表明同意连接,并且产生ack字段,ack字段数值是在客户端发送过来的序列号seq的基础上加1进行回复,以便客户端收到消息时,知道自己的TCP建立请求已得到验证。(SYN=1,ack=x+1,seq=y,y为随机值)这里的ack+1可以理解为是确认和谁建立连接。
- 客户端收到服务端发送的TCP建立验证请求后,会使自己的序列号加1表示,并且再次回复ACK验证请求,此时SYN=0,因为这既不是请求连接 ,也不是同意连接,在服务端发过来的seq上加1进行回复。(SYN=1,ack=y+1,seq=x+1)
三次握手的原因
为什么要三次握手,不能两次、四次或者其它次数吗?这是面试中经常会被提到的问题。
相信有些人的回答是:"因为三次握手才能保证双方具有接收和发送的能力。"
这种回答是没有问题的,但这回答是片面的,并没有说出主要的原因。相信面试官也不想听这种回答。
在前面我们知道了什么是TCP连接:
- 用于保证可靠性和流量控制维护某些状态的信息,这些信息的组合,包括Socket、序列号和窗口大小 称为连接。
所以,回答方向的重点就是为什么三次握手才可以初始化Socket、序列号和窗口大小并建立TCP连接
接下来,以三个方面分析三次握手的原因: - 三次握手才可以阻止重复历史连接的初始化(主要原因)
- 三次握手才可以同步双方的初始化序列号
- 三次握手才可以避免资源浪费
原因一:避免历史连接
简单来说,三次握手的首要原因是为了防止旧的重复连接初始化造成混乱 。
我们考虑一个场景,客户端先发送了SYN(seq = 90)报文,然后客户端宕机了,而且这个SYN报文还被网络阻塞了,服务端并没有收到,接着客户端重启后,又重新向服务端建立连接,发送了SYN(seq = 100)报文(注意!不是重传SYN,重传的SYN的序列号是一样的)。
看看三次握手是如何阻止历史连接的:
客户端连续发送多次SYN(都是同一个四元组)建立连接的报文,在网络拥堵情况下:
- 一个
旧的SYN报文
比最新的SYN
报文早到达了服务端,那么此时服务端就会回一个SYN + ACK
报文给客户端,此报文中的确认号是91(90+1)。 - 客户端收到后,发现自己期望收到的确认号应该是100+1,而不是90+1,于是就会回RST报文。
- 服务端收到RST报文后,就会释放连接。
- 后续最新的SYN抵达了服务端后,客户端与服务端就可以正常的完成三次握手了。
上述中的旧SYN报文称为历史连接,TCP使用三次握手建立连接的最主要原因就是防止历史连接初始化了连接。
如果是两次握手连接,就无法阻止历史连接 ,那为什么 TCP 两次握手为什么无法阻止历史连接呢?
主要是因为在两次握手的情况下,服务端没有中间状态给客户端来阻止历史连接,导致服务端可能建立一个历史连接,造成资源浪费。
可以看到,如果采用两次握手连接建立TCP连接的场景下,服务端在向客户度发送数据前,并没有阻止掉历史连接,导致服务端连接建立了一个历史连接,又白白发送了数据,妥妥浪费了服务端的资源。
因此,要解决这种现象,最好就是在服务端发送数据前,也就是建立连接之前,要阻止掉历史连接,这样就不会造成资源浪费,而要实现这个功能,就需要三次握手。
原因二:同步双方初始序列号
TCP协议的通信双方,都必须维护一个序列号,序列号是可靠传输的一个关键因素,它的作用是:
- 接收方可以去除重复的数据。
- 接收方可以根据数据包的序列号按序接收。
- 可以标识发送出去的数据包中,哪些是已经被对方收到的(通过ACK报文中的序列号知道)。
可见,序列号在TCP连接中占据着非常重要的作用,所以当客户端发送携带初始序列号的SYN报文段时候,需要服务端返回一个ACK应答报文,表示客户端的SYN报文已经被服务端成功接收,那当服务端发送初始序列号给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。
四次握手其实也能够可靠的同步双方的初始化序列号,但由于第二步和第三步可以优化成一步 ,所以就成了三次握手。
而两次握手只保证了一方的初始化序列号能被对方成功接收,没办法保证双方的初始化序列号都能被确认接收。
原因三:避免资源浪费
如果只有两次握手,当客户端发送的SYN报文在网络中阻塞,客户端没有接收到ACK报文,就会重新发送SYN,由于没有第三次握手,服务端不清楚客户端是否收到了自己回复的ACK报文,所以服务端每收到一个SYN就只能先主动建立一个连接,这会造成什么情况呢?
如果客户端发送的SYN报文在网络中阻塞了,重复发送多次SYN报文,那么服务端在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。
小结 :
TCP建立连接时,通过三次握手能防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始化序列号。序列号能够保证数据包不重复、不丢弃和按序传输。
不使用两次握手和四次握手的原因:
- 两次握手:无法阻止历史连接的建立、会造成双方资源的浪费、也无法可靠的同步双方序列号;
- 四次握手:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。
四次挥手
还是需要先了解一些专业名词
- FIN_WAIT_1:客户端和服务端传输完数据后,总有一方需要需要主动发起关闭连接的请求,这个FIN_WAIT_1状态出现在主动关闭的一方。当它发起关闭连接的请求后,就会变成此状态。它需要等待对方的确认。
- FIN_WAIT_2:主动关闭方接收到被动关闭方的确认消息后,就会变成此状态。
- CLOSE_WAIT:被动关闭方收到主动方的关闭请求后,会发出确认消息,确认消息发出后就出现了CLOSE_WAIT。
- LAST_ACK:被动关闭方除了发送确认信息外,还需要发送关闭确认信息给对方,这个信息发送完毕后,就会成为LAST_ACK状态,此时被动关闭方只需要等待主动关闭方的一个回馈确认信息。
- TIME_WAIT:主动关闭方将最后一次的确认信息发送给被动关闭方,就会处于TIME_WAIT状态,该状态只出现在主动关闭方,它只需等待一个时间就会彻底关闭,这个等待的时间为2*MSL(Maximum Segment Lifetime,报文最长存活时间)。因为它需要一个等待时间,所以在Linux系统里,这个状态是最常见、最多的状态。
- CLOSING: 几乎看不到的状态,表示正在关闭连接,这个是瞬时完成的。
小客在小服的帮助下得到了圆满的结束,小客想对小服提出感谢。
小客对小服说:"谢谢你小服,感谢你的帮助,要是没有你,真不知道该怎么办。"(FIN=1 seq=u)
小服对小客说:"不用客气,这阵子确实有点累了,需要休息一下,以后要是有需要帮忙的地方,再叫我就是了。"(ACK=1 seq=v ack=u+1)
小服再对小客说:"要是没有其它事情的话,我就先去休息了。"(FIN=1 seq=w ack=u+1)
小客回复小服:"好的,再次感谢你,拜拜。"(ACK=1 seq=u+1 ack=w+1)
四次挥手原理总结:
- 客户端发送断开TCP连接请求的报文,其中报文中包含seq序列号,是由发送端随机生成的,并且还将报文中的FIN字段设置为1,表示需要断开TCP连接。
- 服务端会回复客户端发送的TCP断开请求报文,其中包含seq序列号,是由回复端随机生成的,而且会产生ACK字段,ACK字段数值是在客户端发过来的seq序列号基础上加1进行回复,以便客户端收到消息时,知晓自己的TCP断开请求已经得到验证。
- 服务端在回复完客户端的TCP断开请求后,不会马上进行TCP连接的断开,服务端会先确保断开前,所有传输到客户端的数据是否已经传输完毕,一旦确认传输完成,就会回复报文的FIN设置为1,并且产生随机seq序列号。
- 客户端收到服务端的TCP断开请求后,会回复服务端的断开请求,包含随机生成的seq字段和ACK字段,ACK字段会在服务端的TCP断开请求的seq基础上加1,从而完成服务端请求的验证回复。
为什么客户端在TIME_WAIT阶段要等待2MSL?
一是为了保证客户端发送的最后一个ACK报文段能够到达服务端,确保服务端能正常进入CLOSED状态。服务端在1MSL内没有收到客户端发出的ACK确认报文,就会再次向客户端发出FIN报文。
二是为了避免新旧连接混淆。由于网络滞留,客户端可能发送了多次请求连接的请求,经过时间2MSL,就可以使本连接持续时间内所产生的所有报文段都从网络中消失,这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。