连接的建立与终止
通过如下命令让主机svr4与bsdi发起并终止连接
下面为tcpdump输出的TCP报文段部分
每行格式为源>目的:标志
上图中的标志的解释
- 第1行,1415531521:1415531521(0)表示分组的开始序号为1415531521,结束序号也为1415531521,数据字节数为0。这是连接同步请求,没有任何数据交换
- 第2行,ack 1415531522代表确认序号,这个值只有在ACK标志位为1时才显示
- win 4096表示发送端通告的窗口大小
- < mss 1024 >表示发送端指明的最大报文长度,发端不接收超过该长度的报文段
建立连接协议
三次握手:
- 客户端发送一个SYN段作为连接请求,初始化序号为1415531521
- 服务端响应一个SYN段,初始化序号为1823083521。同时发送确认报文,确认序号为
1415531521+1=1415531522
。一个SYN占用一个序号 - 客户端响应确认报文,确认序号为
1823083521+1=1823083522
终止连接协议
一个TCP连接是全双工,数据在两个方向传递,因此如果要关闭TCP连接,必须单独关闭两个方向的连接
四次握手:
- 客户端发送FIN,请求关闭连接
- 服务端收到FIN后,响应ACK
- 服务端发送FIN,请求关闭连接
- 客户端收到FIN后,响应ACK
之所以比连接多一次握手,是因为TCP支持半关闭,允许一边关闭连接后另一边仍能发送数据,所以无法合并报文5和报文6,两者之间仍然可能存在单向数据传输的报文。所以一旦使用了半关闭,这四次握手就会分成两个阶段执行
建立连接超时
其中第1、2次请求间隔5.8秒,第2、3次请求间隔24秒,其中tos 0x10代表telnet将IP数据报服务类型字段设为最小时延
上面显示的总耗时76秒,是因为在大多数实现中,新建连接的时间最长为75秒
BSD TCP采用500ms定时器,在初始化设置时,第一个500ms内会在任意时刻滴答一下,导致第一个间隔不是6秒而是5.8秒,但第二个间隔却是准确的24秒
最大报文段长度
MSS,表示TCP传往另一端最大数据块的长度。当建立TCP连接时,双方都要通告各自的MSS。如当A告知B MSS为1460时,表示B可以发给A的最大数据块长度就是1460
通常以太网将MSS设为MTU大小减去IP首部和TCP首部长度,即1500-20-20=1460
TCP的半关闭
TCP提供了发送端在结束发送后,依然可以接收数据的能力,这就是半关闭
在应用程序调用shutdown,并且第二个参数为1时,会使用半关闭。普通的close不会有半关闭。所以很少应用程序会使用到半关闭
为什么要有半关闭?
场景:当发送远程命令到目的主机执行时,命令发送完后,client就能结束数据发送,但仍然接收目的主机发回的执行结果,这就是半关闭状态,当目的主机发完数据时,结束数据发送,完成连接关闭。没有半关闭,目的主机无法知道client数据什么时候发完,也就无法执行命令并响应结果
TCP状态变迁图
其中SYN收到就是SYN_RCVD
通过上面两个图可以观察到
- client从CLOSED到ESTABLISHED所需要发送和接收的报文
- server从LISTEN到ESTABLISHED所需要发送和接收的报文
- ESTABLISHED状态才能进行数据传输
- client从ESTABLISHED到CLOSED所需要发送和接收的报文
- server从ESTABLISHED到CLOSED再到LISTEN所需要发送和接收的报文
并不存在CLOSED这个状态,它是一个假象的状态起点和终点,所以server从CLOSED过度到LISTEN不需要特殊处理
TIME_WAIT状态
又称2MSL等待状态,即2倍的报文段最大生存周期
当TCP主动关闭一方发送最后一个ACK时,会进入TIME_WAIT状态,等待2MSL时间才能彻底关闭连接
为什么需要等待2MSL呢?
- 防止最后一个ACK丢失。如果真丢失,被动关闭一方会重发FIN(
2MSL=MSL(丢失的ACK)+MSL(重发的FIN)
) - 在2MSL期间不能复用连接(源IP、目的IP、源端口、目的端口的四元祖),避免原始连接中迷途的数据跑到新连接中
大部分TCP实现较为严格,在TIME_WAIT期间,无法复用端口
- 如果客户端主动关闭连接,进入TIME_WAIT,由于客户端是随机端口,无法复用原来端口可以很容易做到
- 如果服务端主动关闭连接,进入TIME_WAIT,由于服务端是知名端口,无法复用原来端口会导致重启服务耗时至少为2MSL。很多实现提供SO_REUSEADDR来绕过这个限制,保证服务端可以复用TIME_WAIT期间的端口
FIN_WAIT_2状态
当主动关闭一方发送FIN,并收到ACK后,会进入FIN_WAIT_2状态,直到被动关闭一方发送FIN,主动方才会响应ACK并进入TIME_WAIT状态
所以,如果被动关闭一方不关闭连接,会导致主动关闭一方永远处于FIN_WAIT_2状态,而被动关闭一方永远处于CLOSE_WAIT状态
复位报文段
当报文段发往一个socket时出现错误,TCP就会发出一个复位报文段。通常有以下三种场景:
- 到不存在的端口发起连接请求。当发送到目的端,发现端口不存在,UDP会响应一个ICMP端口不可达差错,TCP会响应复位报文段
- 异常终止一个连接。通过FIN正常关闭,但是需要处理排队的数据;复位报文段异常关闭,可以立即丢弃排队的数据
- 检测半打开连接。当一边异常关闭连接而另一边不知道,这种是半打开连接。如server异常断电重启,client不知server的状况,继续发送报文,server收到后立马响应一个复位报文段
TCP选项
如图显示了三个选项:
- 最大报文段长度
- 窗口扩大因子
- 时间戳
实际为了保证选项总长度是4字节的倍数,需要在其中填充无操作NOP选项
TCP服务器的设计
- 当服务端监听连接时,会处于LISTEN状态
- 当客户端请求到来时,会启动一个线程/进程去接收客户端连接请求
- 当客户端服务端连接建立时,处于ESTABLISHED状态
服务器可以限定本地监听的IP地址和远端请求的IP地址
连接队列
当服务器忙于处理客户端请求时,多的请求在连接建立后会放入连接队列。连接队列有以下规则:
- 监听连接请求的一端会有固定长度的连接队列,该队列存储已建立连接(完成了三次握手)的请求,稍后应用程序会从该队列中移除请求并处理
- 积压值(backlog)是这个队列的最大长度
- 当该队列已满,TCP不会将理会后续的连接请求(SYN),并且不会回复RST。RST代表这个请求是硬错误,不能重试。而不理会会让客户端请求超时,发现是一个软错误,会进行重试