TCP是TCP/IP协议族中一个最核心的协议,它向下使用网络层IP协议,向上为应用层HTTP、FTP、SMTP、POP3、SSH、Telnet等协议提供支持。本文给出TCP报文格式的详细说明,介绍网络数据包传递中如何进行地址解析、建立TCP连接的三次握手过程以及断开TCP连接的四次挥手过程。
1. 简介
传输控制协议(英语:Transmission Control Protocol,缩写:TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由国际互联网工程任务组(The Internet Engineering Task Force, IETF)的 RFC793 定义。在简化的计算机网络 OSI 模型中,它完成传输层所指定的功能。
在TCP定义中,有以下3点需要特别说明:
(1)什么是面向连接?
面向连接是相对于另一个传输层协议UDP(User Datagram Protocol, 用户数据报协议)而言的。TCP在开始传输数据前要先经历三次握手建立连接,并通过连接一对一发送消息,传输结束后通过四次挥手断开连接。
而UDP是无连接的,发送方在发送数据之前不需要与接收方建立连接,即刻可以传输数据,每个UDP数据包都是独立的,相互之间没有关联,因此UDP可以一对一、一对多或多对多发送消息。
(2)什么是可靠的通信协议?
是否可靠也是相对于UDP而言的。TCP自身有三次握手和超时重传等机制确保数据的可靠传输,发送方在发送数据包后会等待接收方发送确认(ACK)消息。如果发送方在一定时间内未收到确认消息,它将假定数据丢失,并重新发送数据。接收方收到重复的数据包时会发送冗余的ACK消息来通知发送方,以避免数据丢失。同时TCP还提供流量控制和拥塞控制,以保持网络的稳定性和性能。因此无论网络如何变化,只要不是主机宕机等原因都可以保证一个报文可以到达目标主机。
相对于TCP的可靠传输,UDP是不可靠的。UDP数据包的传输过程中不提供确认、重传、流量控制和拥塞控制等机制,因此UDP数据包可能丢失、重复、乱序或损坏。
(3)什么是面向字节流的?
TCP是面向字节流的传输,虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序看成是一连串的无结构的字节流。TCP有一个缓冲,当应用程序传送的数据块太长,TCP就可以把它划分短一些再传送。如果应用程序一次只发送一个字节,TCP也可以等待积累有足够多的字节后再构成报文段发送出去。
与面向字节流相对的是UDP的面向报文。UDP对应用层交下来的报文,既不合并也不拆分,而是保留这些报文的边界,即应用层交给UDP多长的报文,UDP就照样发送,一次发送一个报文。因此,应用程序必须选择合适大小的报文。若报文太长,则IP层需要分片,降低效率。若太短,会使IP报文太小。
2.TCP报文格式
了解报文格式是搞懂一个通信协议的必经之路。TCP报文由TCP首部(报头)和应用数据构成,其中TCP首部是TCP 协议的核心所在,应用数据部分是TCP报文的负载,如下图所示。
以下详细介绍各字段含义:
源端口(Source Port)和目的端口(Destination Port):长度各为16位,即2个字节,分别指示发送端的应用程序使用的端口号以及接收端的应用程序期望接收的端口号。它们的长度说明为什么计算机端口的范围是1-65535 (0不使用,2^16=65536,最大位65536不使用)。有了源端口和目标端口,加上IP首部里的源IP和目标IP,就可以唯一确定一个连接。
序列号(Sequence Number):长度为32位,说明序列号的范围是[0, 2^32-1],也就是[0, 4294967295]。当序号增加到4294967295后,下一个序号将回到0重新开始。在建立连接时由计算机生成的随机数作为其初始值(ISN,即Initial Sequence Number,初始序列号),通过 SYN 包传给接收端主机,每发送一次数据,就累加一次该“数据字节数”的大小。序列号用来解决网络包乱序问题,实现可靠的数据传输和流量控制。
确认号(Acknowledgment Number):长度为32位,只有在ACK标志位被设置时才有效。它指示期望接收的下一个字节的序列号(所以该字段一般都是上次接收成功的数据字节序号加1),用于确认已经成功接收的数据。在TCP连接建立后,确认号的范围通常是相对于初始序号(ISN)的相对偏移量。如果ISN的初始值为X,那么确认号的范围就是[X+1, X+1+N-1],其中N表示已经成功接收的字节数。发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。确认号的范围是[0, 2^32-1],也就是[0, 4294967295]。
数据偏移(Data Offset):长度为4位,指示TCP报文的“数据”起始处距离TCP报文起始处的距离有多远,以4字节为单位计算出的数据段开始地址的偏移值。没有选项时该值为5,即20字节;4位能表示的最大整数是15,也就说明TCP报文里数据开始的位置距离报文起点是60个字节(4*15)。这意味着TCP的首部长度是20-60个字节。
保留(Reserved):长度为3位,保留供将来使用,目前应设置为零。
控制标志(Flags):长度为9位,用于控制和管理TCP连接。各控制标志位说明如下:
NS(Nonce Sum):用于支持一种称为ECN-nonce的TCP扩展机制,该机制用于增加拥塞控制的安全性,防止拥塞控制信息被恶意篡改。
CWR(Congestion Window Reduced):用于指示发送方减小拥塞窗口(Congestion Window)的大小。CWR标志位通常与拥塞控制机制一起使用,以应对网络拥塞的情况。
ECE(ECN-Echo):ECE标志被设置表示发送方支持显式拥塞通知(Explicit Congestion Notification, ECN)机制,并请求接收方通知其关于网络拥塞的情况。接收方在收到设置了ECE标志的TCP报文段后,如果网络出现拥塞,则可以在回复的TCP报文段中设置ECN-Echo标志作为响应。通过使用ECE标志和ECN-Echo回复,TCP连接的发送方和接收方可以共同协调拥塞控制,以提高网络的性能和稳定性。
URG(Urgent):指示报文段中包含紧急数据。当URG=1时,表明开启了urgent mode,通知接收方在处理数据时要特别注意紧急数据的处理。URG标志位的设置与紧急指针字段(Urgent Pointer)一起使用。
ACK(Acknowledgment):指示确认号字段有效。仅当ACK=1时确认号字段才有效,当ACK=0时确认号无效。TCP规定,在连接建立后所有的传送的报文段都必须把ACK置为1。
PSH(Push):指示接收方应立即将数据推送给应用程序,而不是等待缓冲区填满。当两个应用进程进行交互式的通信时,有时一端的应用进程希望在键入一个命令后立即就能收到对方的响应。在这种情况下,TCP就可以使用推送(push)操作。这时,发送方TCP把PSH置为1,并立即创建一个报文段发送出去。接收方TCP收到PSH=1的报文段,就尽快地(即“推送”向前)交付接收应用进程。而不用再等到整个缓存都填满了后再向上交付。
RST(Reset):用于复位连接,中断当前的通信。当 RST=1 时,表示 TCP 连接中出现异常(如主机崩溃或其他原因)必须强制断开连接,然后再重新建立连接进行传输。RST置为1还用来拒绝一个非法的报文段或拒绝打开一个连接。
SYN(Synchronize):用于建立连接,发起连接请求。在连接建立时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使SYN=1和ACK=1,因此SYN置为1就表示这是一个连接请求或连接接受报文。
FIN(Finish):用于关闭连接,请求终止连接。当FIN=1时,表示发送方没有数据要传输了,要求释放连接。
窗口大小(Window Size):长度为16位,指示接收方的接收窗口大小,用于流量控制,最大的窗口大小为2^16-1=65535=64k。这是早期的设计,对于现在的网络应用,可能会不太够,因此可以在选项里加一个 窗口扩大选项,来传输更多的数据。窗口指的是发送本报文段一方的接受窗口(而不是自己的发送窗口)。窗口值告诉对方:从本报文段首部中的确认号算起,接收方目前允许对方发送的数据量(以字节为单位)。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。总之,窗口值作为接收方让发送方设置其发送窗口的依据。
校验和(Checksum):长度为16位,用于检测TCP报文段是否在传输过程中发生了错误。校验和计算包括报头和数据。
紧急指针(Urgent Pointer):长度为16位,只有在URG标志位被设置时才有效。它指出本报文段中的紧急数据的字节数(紧急数据结束后就是普通数据)。因此,在紧急指针指出了紧急数据的末尾在报文段中的位置。当所有紧急数据都处理完时,TCP就告诉应用程序恢复到正常操作。值得注意的是,即使窗口为0时也可以发送紧急数据。
选项(Options):可选字段,长度可变,最长可达40个字节。当没有使用“选项”时,TCP的首部长度是20字节。选项字段用于提供额外的功能和控制,每个选项的开始是 1 字节的kind字段,说明选项的类型。一些常见的选项举例如下:
最大报文段长度(Maximum Segment Size, MSS):占用4字节,通常在创建连接而设置SYN标志的数据包中指明这个选项,指明本端所能接收的最大长度的报文段。通常将MSS设置为(MTU-40)字节,携带TCP报文段的IP数据报的长度就不会超过MTU(MTU最大长度为1518字节,最短为64字节),从而避免本机发生IP分片。只能出现在同步报文段中,否则将被忽略。
窗口扩大因子(Window Scale Factor):占用3字节,取值0-14。用来把TCP的窗口的值左移的位数,使窗口值乘倍。只能出现在同步报文段中,否则将被忽略。这是因为现在的TCP接收数据缓冲区(接收窗口)的长度通常大于65535字节。
时间戳选项(TCP Timestamps Option, TSopt):占用10字节,其中最主要的字段是时间戳字段(Timestamp Value field, TSval, 4字节)和时间戳回送回答字段(Timestamp Echo Reply field, TSecr, 4字节)。时间戳选项允许通信的两端在TCP报文段中包含时间戳值,以便进行一些时间相关的操作和计算。
安全摘要选项(TCP Authentication Option, TCP Option):用于提供数据完整性和身份验证的功能。该选项用于对TCP报文段进行保护,防止数据篡改和未经授权的访问。
3.数据包传递的地址解析
类比日常工作中邮寄信件,我们装在信封里的信件相当于要传递的数据,标准的信件格式是要在信封上写“收信人地址”和“寄信人地址”,相当于IP地址,其中,“收信人地址”对应数据包里IP报头中的“目的IP地址”,“寄信人地址”对应数据包里IP报头中的“源IP地址”,写上寄信、收信两个地址就可以保证信件可以邮寄到目的地了。
但信件邮寄到目的地址后由谁来收?从上面这封信的收件人地址检索到这个地址是位于上海市浦东新区张江“A公司B部门”的,这个部门可能有成百上千人,收件人不明确,即使把信件送到这个地址,也没办法投递到具体的收信人。
因此,邮件信件需要填写“收件人姓名”、“收件人地址”和“寄件人姓名”、“寄件人地址”的组合,才能保证信件能准确投递到具体的收件人手中。这里的收信人姓名相当于TCP报头的目的端口,寄信人姓名相当于TCP报头的源端口。
对比传递信件,我们来看网络数据包传递过程的例子。位于北京的李四(电脑IP地址: 106.54.28.25)给上海的张三(电脑IP地址: 114.92.67.193)通过QQ(端口: 80)发送一条消息,如下图所示:
首先,李四电脑将消息打包成TCP数据报后,添加IP报头和以太网报头形成网络数据包,发送到计算机网络中。计算机网络通过数据包中IP报头的目的IP地址(114.92.67.193)把该数据包准确传递到张三电脑。
张三电脑收到了李四电脑发送过来的数据包后,由于张三电脑上同时运行有多个程序(例如图中的QQ、微信、Foxmail等),虽然张三电脑知道这个数据包是传输给它的,但是它不知道该把这个数据包中的数据交给哪个程序。
针对这个问题,使用数据包中TCP报头的源端口和目的端口,根据不同的程序使用不同端口号来确定应用程序并发送和接受数据,这样数据包就能像邮寄信件一样准确投递到具体电脑上指定的程序了。例如我们指定张三电脑上QQ、微信、Foxmail使用的端口分别是80、8900和110,那么当收到数据包里目的端口80就是传输给QQ的。
上述例子还可以引申出数据包结构中的其他字段的作用,例如我们收到信后可以简单地通过信封是否完整,来检查该信件是否被别人在传输途中拆开并篡改过信件内容。对于网络数据包,TCP报头的“校验和”(Checksum)可以验证收到数据包数据是否在途被别人拆开修改过。
4.TCP连接
4.1 三次握手过程详解
由于建立TCP连接的过程需要来回3次,所以将这个过程形象的叫做三次握手(Three-Way Handshake),一旦建立连接,两台主机就可以进行全双工的通信。
下面是三次握手的详细过程,包括发送的报文段内容:
(1)第一次握手
首先客户端发起连接请求,向服务器发送一个SYN(同步)报文段,段中包含了目的端口和本机端口,设置SYN 标志位为1,即SYN=1,并设置序号字段(Sequence Number)为一个随机选择的x,即seq=x,也就是初始序号(Initial Sequence Number, ISN),如果是第一个连接,很可能是0。此时服务器对应的端口要处于监听状态,客户端发起请求后进入 SYN_SENT 状态,等待服务器的确认。
(2)第二次握手
服务端收到客户端发来的 SYN 报文段,对这个SYN报文段进行确认。服务器向客户端发送一个SYN-ACK报文段作为回应,报文段中的标志位设置为SYN=1和ACK=1,表示同时作为确认和同步;序号字段设置为服务器的随机选择的初始序号y(服务端的TCP段序号),即seq=y;确认号字段(Acknowledgment Number)设置为客户端的初始序号加1,即ack=x+1。服务器端将上述所有信息放到一个TCP段(即SYN+ACK段)中,一并发送给客户端,此时服务器进入SYN_RECV状态。
(3)第三次握手
客户端接收到服务端发来的 SYN+ACK 报文段后,要向服务端发送一个ACK(确认)报文段,对连接请求的确认进行确认。报文段中的标志位设置为ACK=1,确认号字段设置为服务器的初始序号加1,即ack=y+1,序号字段设置为客户端的初始序号加1,即seq=x+1。此时客户端进入 ESTABLISHED(已连接)状态,服务端接收到此 TCP段,也将进入 ESTABLISHED 状态,也就标志着三次握手结束,连接成功建立。
三次握手完成之后,TCP连接就正式建立起来了,双方可以开始进行数据的可靠传输。三次握手的目的是确保双方的初始序号和确认号的同步,并验证双方的可达性。通过这个过程,TCP可以建立一个可靠的双向通信通道,在后续的数据传输中保证数据的可靠性和顺序性。
4.2四次挥手
四次挥手是TCP断开连接的过程。
(1)第一次挥手
客户端数据发送完成,则向服务端发送连接释放请求的FIN报文(请求连接终止:FIN=1),主动关闭TCP连接。报文中会指定一个序列号seq=u,并停止再发送数据,但依然能够接收数据。此时客户端处于 FIN_WAIT_1 状态,等待服务端确认。TCP规定,FIN报文即使不携带数据,也要消耗一个序号。
(2)第二次挥手
前两次挥手既让服务端知道了客户端想释放连接,也让客户端知道了服务端已了解自己想要释放连接的请求。
(3)第三次挥手
如果服务端也想断开连接,就向客户端发送连接释放报文。由于在CLOS_WAIT状态,服务端很可能又发送了一些数据,假定此时连接释放报文的序列号为seq=w,ack也是取第一次挥手的seq +1,即ack=u+1,这和第二次挥手时是一样的。
此时服务端就进入了LAST_ACK(最后确认)状态,等待客户端的确认,并停止向客户端发送数据,但服务端仍能够接收从客户端传输过来的数据。
(4)第四次挥手
客户端收到服务器的连接释放报文后,一样发送一个 ACK 报文作为应答(ack=w+1,seq=u+1), 此时客户端处于TIME_WAIT(时间等待)状态,并在这个状态等待 2MSL(Two Maximum Segment Lifetime, 最大报文生存时间)。
服务端收到从客户端发出的 TCP 报文之后结束 LAST-ACK 阶段,进入 CLOSED 阶段。客户端等待完 2MSL之后,结束 TIME-WAIT 阶段,进入 CLOSED 阶段,由此完成四次挥手。
为什么客户端在TIME_WAIT阶段要等 2MSL? 主要有以下两点:
一是为了保证客户端发送的最后一个ACK报文段能够到达服务器端,确保服务端能正常进入CLOSED状态。服务端在 1MSL 内没有收到客户端发出的 ACK 确认报文,就会再次向客户端发出 FIN 报文。
二是为了避免新旧连接混淆。由于网络滞留,客户端可能发送了多次请求建立连接的请求,经过时间2MSL,就可以使本链接持续时间内所产生的所有报文段都从网络中消失,这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。
end