本篇继续探讨TCP连接管理的初步过程即常规的三次握手过程,从TCP报文格式入手,深入研究在原理层面上的连接建立过程,并辅以实验演示加深理解。
一、概述
TCP协议在RFC793(https://www.rfc-editor.org/rfc/rfc793.txt)中进行了初步定义,用于在网络层之上、应用层之下提供一种可靠的、点到点面向连接的、协议无关的传输层字节流服务。TCP运行于通信协议中的层次如下:
在一个TCP连接中仅有两方进行彼此通信,因此广播和组播均不能用于TCP。TCP使用如下方式提供可靠性:
1.数据传输在连接建立后开始,并在连接断开前结束。若存在单向数据传输,可使用半关闭(half-close)特性节省某一端的连接池资源,该特性将在后续文章中说明。
2.数据被分割为TCP认为最合适的数据段(segment)交给IP进行传输,后续文章将说明TCP如何确定报文段长度。
3.TCP在发送一个报文段后即启动一个定时器,若在定时器超时前未收到目的端对这个报文段的确认(Acknowledgement),将重发这个报文段;而接受端在收到一个报文段后将推迟一定时间发送确认。后续文章将说明TCP协议中自适应的超时及重传策略。
4.TCP使用一个端到端的校验和来保证数据在传输过程中没有发生任何变化,若收到报文的校验和出错,TCP将直接丢弃这个报文段,不会向发送端发起该段的任何确认;发送端定时器超时后将重传该报文段,并继续等待接受端确认。
5.若报文段失序或发生重复,TCP将对收到的数据进行重新排序或丢弃重复的报文段。
6.TCP提供传输层的流量控制机制,接收端只允许发送端发送缓冲区能容纳的数据,这将防止较快主机使较慢主机的缓冲区溢出。
TCP对字节流的内容不作任何解释,不知道传输的数据是二进制数据、ASCII字符、EBCDIC字符还是其他类型数据。对字节流的解释交由两端应用层完成。
二、首部格式
TCP数据可被封装在一个标准的IP数据报中,若不计选项字段一般为20字节。格式如下:
Source/DestinationPort:每个报文段包含源端和目的端的端口号,用于寻找发起端和接收端的应用程序。源/目的IP+源/目的端口号可唯一确定一个TCP连接,称为一个“接口”或“套接字”(Socket)。
SequenceNumber:序列号用于标识发送端向接收端发送的报文,是这个报文段中的第一个数据字节。TCP使用序列号对每个报文段进行计数,该字段是一个32位无符号数,到达后从开始重新计数。
AcknowledgementNumber:确认序列号用于对上一个发送的数据进行确认,同时包含希望收到的下一个报文段的序列号。只有ACK Flag为1时该字段才有效。TCP为应用层提供全双工服务,数据可在两个方向上独立传输,因此连接的每一端必须独立保持每个方向上的传输序列号。
DataOffset:因为TCP首部存在选项字段,首部长度是可变的(最长60字节),所以需要数据偏移字段标识数据的开始字节,也可将该字段看作“首部长度”字段。该字段表示数据字节开始前32bit(8bytes)的个数。
TCP首部中有6个标志比特(Flag bit),此处简单介绍它们的用法:
URG:紧急指针字段有效
ACK:确认序号字段有效
PSH:接收方需尽快把该报文段交给应用层
RST:重置连接
SYN:同步序号,用于发起一个连接
FIN:发送端完成发送任务
Window:TCP流量控制由连接的每一端通过声明窗口大小提供,后续文章中将详细说明该字段在流量控制中的用法。
Checksum:校验和字段包含了整个报文段(TCP首部+TCP数据),是一个强制性的校验字段,后续文章中将说明该字段对报文的验证方法。
UrgentPointer:当URG标志为1时紧急指针生效,和SequenceNumber字段值相加表示紧急数据的最后一个字节的序列号。很少有应用使用紧急指针特性。
Padding:填充字段,用于将TCP头部填充为32bit的整数倍。
三、连接建立
在unix终端中输入如下命令可建立与域名“dmmjy9.top"的80端口的TCP连接:
在Wireshark中显示报文交换过程如下:
从第一个报文段开始分析:
首先关注Flags字段,客户端发起建立连接请求时会将第一个报文段的SYN Flag置为1,表示这是第一个连接请求(SYN位置1的报文也称为SYN报文);同时设置一个SequenceNumber:
值得注意的是,wireshark中SequenceNumber默认显示相对序列号(relative sequence number),实际的序列号为一个特殊变量,该变量每0.5秒增加64000并每隔9.5小时后回到,需要对wireshark进行设置或使用tcpdump获得这个序列号:
上图可知此次连接的SequenceNumber为“3957265117”,该序列号也称为“初识序列号”(ISN,此处记为ISNa)。连接的发起端将源端口设为本地某一随机端口,目的端口设为想要建立连接的服务器端口(该端口在服务器上处于LISTEN状态),发送第一个SYN的一端将执行主动打开(active open),并进入“SYN SENT”状态;收到这个SYN报文并发送下一个SYN+ACK的一端执行被动打开(passive open)并进入“SYN RCVD”状态。此处有一种两端都执行主动打开的机制,将在后续文章中介绍。
收到SYN报文后,服务器将对该连接请求进行验证,若为合法的连接将回复SYN和ACK Flag置为1的报文,该报文在wireshark显示如下:
如图可知,该报文Flags字段中SYN和ACK Flag都置为1,同时sequencenumber和acknowledgement number字段都可用。为清楚显示各序列号的关系,使用tcpdump获取序列号值:
分析上图可知,该报文中的sequencenumber字段为服务器生成的ISN(此处记为ISNb),同时acknowledgementnumber设为ISNa+1,用于对上一个SYN报文进行确认。该报文发送后服务器TCP状态进入“SYN RCVD”,并挂起该连接线程等待确认。
客户端收到服务器发送的SYN+ACK报文,将对该报文的acknowledgementnumber、checksum进行验证,若为合法报文则回复ACK置1的报文,该报文在wireshark中显示如下:
在tcpdump中显示如下:
需要注意的是,tcpdump将此处的ack number重置为1,即tcpdump认为连接建立时的最后一个ack就是数据传输的第一个报文,实际值应为ISNb+1。该现象在wireshark中可得到印证:
上图为一个新连接的完整建立过程,可见第三个ack报文的acknowledgementnumber即为上一个SYN+ACK报文的sequencenumber + 1。客户端在收到SYN+ACK并发出ACK后将该连接状态置为ESTABLISH,服务端收到ACK后也将该连接状态置为ESTABLISH。至此三次连接建立完成,双方可以进行下一步的数据通信。
四、连接终止
既然TCP连接是全双工的,数据在两个方向上同时传递,因此每个方向必须单独地进行关闭。当一端收到一个FIN,它必须通知应用层另一端已经终止了向本端的数据传送,但只意味着在这一方向上没有数据流动,因此原则上需要使用四次交换过程来终止TCP连接。一个TCP连接在收到FIN后仍能发送数据,这对于利用半关闭的应用是可能的,尽管现实中很少有TCP应用程序使用这种特性。
下图是一个未使用半关闭特性应用发起的断开连接过程。请注意,报文交互并没有发生期望的“四次挥手”过程,造成这种现象的原因将在下文解释。
从第一个报文开始分析:
如上图,该报文的FIN和ACK置位,其中的acknowledgementnumber用于客户端对最后一个收到的报文进行确认,其值为最后一个数据报文的sequencenumber+1;该报文的sequence number与上一个发送报文的相同。
下面是第二个报文:
此报文的FIN与ACK同时置位,并不同于理论上的ACK置位确认上一个FIN报文,其原因是该应用没有使用半关闭特性,客户端发起关闭后服务器也没有继续发送数据的需求,因此服务器将应该回复的ACK报文中的FIN同时置位,用以在服务器->客户端方向同时发起终止连接请求并节约一部分连接池资源。
下面分析最后一个报文:
该报文的ACK置位,acknowledgementnumber为上一个收到报文的sequence number+1,服务器收到该报文后即关闭连接的双向数据传输,释放连接池资源。
五、总结
至此,一个连接从建立到断开的完整过程演示结束,RFC793中的TCP连接状态机如下图:
TCP连接的断开是一个相对复杂的过程,其中涉及多个等待时间、MSL、平静时间等概念,将在后续文章中进行详细说明。
邮箱:ie-evolution@ie-evolution.xin
网站:
www.i-learner.xin
www.ie-evolution.xin
知乎专栏:IE进化论
公众平台:IE进化论(ie_evolution)
领取专属 10元无门槛券
私享最新 技术干货