TCP连接的建立和终止

TCP连接的建立和终止

本文主要分析TCP连接的建立,断开过程
以及TCP的状态机
通过抓包分析TCP连接的过程

TCP数据包格式

TCP数据包中没有标识数据大小的字段,这个字段定义在IP首部中了。
TCP首部长度最小是20字节,最大是60字节,首部长度就定了偏移量,标识了TCP首部的大小
TCP流量控制是由连接的每一端通过声明窗口大小来提供的,这个值是16比特,所以最大是65535字节。
校验和覆盖了整个TCP报文段,首部和数据,这是一个强制性的字段,一定是由发送端计算和存储,并由接收端进行验证。
只有当URG标志设置为1时紧急指针才有效。紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。
最常见的可选字段是 最长报文大小,又称为MSS(Maximun Segment Size)。每个连接方通常都在通信的第一个报文段(SYN包)中指明这个选项,它指明本端所能接收的最大长度的报文段。
比如A的MTU为296字节,也就是以太网可以发送的包的最大大小,而B的MUT是1500字节,那么当双方建立连接时,A发送的SYN包就指明了MSS为256(296-40),而B的SYN包中指明了MSS为1460(1500-40)。此时使用较小一方的MSS,也就是256字节作为数据报文长度。
MSS默认值为536字节

 

TCP中的标识比特

他们中的多个可以同时被设置为1,含义如下

[table “3” could not be loaded /]

TCP状态机

TCP的状态变迁图

TCP连接的建立,数据传输,以及关闭的过程

状态转换和连接建立关闭的对比图

分成三部分
1.连接的建立过程
2.数据的传输过程
3.连接的关闭过程

 

TCP连接

连接过程(三次握手)

这是由一个主动,一个被动的过程,客户端主动触发一个连接,服务端接收连接,最初客户端和服务端都处于CLOSED状态。
对于客户端来说,首先会发送一个SYN包,之后TCP状态就变为SYN_SENT,而当客户端收到服务端发来的ack+syn包时,对于客户端来说他已经确定了服务端收到了之前发送的SYN包了,所以客户端的TCP状态就变成了ESTABLISHED.
对于服务端来说,当调用listen()函数之后,就看以监听客户端的发来的SYN包了,当收到SYN包之后,服务端的TCP状态就变为TCP_RCVD,同时客户端还会向服务端发送一个syn+ack包,也就是服务端也要创建连接。最后当客户度收到这个syn+ack包后,最后会再发一个ack包给服务端,也就是TCP状态转换图中SYN-SEND状态指向SYN-RECEVED状态的箭头,也就是客户度最后的确认ack,当服务端收到这个ack后,连接就建立了,这样服务端的TCP状态也变为ESTABLISHED.
同时打开需要四次握手,双方都是主动方,都需要经历SYN_SENT和SYN_RCVD状态,最后变成ESTABLISHED状态。

数据传输过程
只有一种状态ESTABLISHED

 

关闭过程(四次握手)

这里分为主动关闭和被动关闭,还有双方同时关闭。
客户端发送fin包后,TCP状态就处于FIN_WAIT_1,如果收到服务端发来的ack确认后,状态就变为FIN_WAIT_2。之后客户端会等待服务端发送fin包,也就是等待服务端关闭。当收到服务端发来的fin包后,客户端会发送一个确认,此时客户端的状态就变为TIME_WAIT,等待2ML时间后,状态就变为了CLOSED。
对于服务端来说,收到客户端发来的fin包后,状态就变为CLOSE_WAIT,也就是被动关闭,同时会发送一个ack确认给客户端。之后服务端也会发送一个fin包给客户端,表示关闭连接,之后状态就变成了LAST_ACK,此时服务端会等客户端发送最后一个确认ack,当收到后服务端就会关闭连接,状态就变为CLOSED。
同时关闭是双方都是主动关闭,双方都发送了一个fin包。客户端主动关闭发送fin包后,变成FIN_WAIT_1状态,此时又收到了一个fin包,于是状态变为CLOSING,同时再发送一个确认包ack给对方。而当收到对方对fin包的确认后,状态变成了TIME_WAIT,等待2ML时间后,连接关闭,变为CLOSED状态,对于服务端来说情况是一样的就不在阐述了。
同时关闭的状态图

 

2MSL等待时间
主动关闭时收到fin包的确认ack后就处于TIME_WAIT状态,此时需要等待2倍的MSL时间(Maximum Segment Lifetime)。他是任何报文段被丢弃前在网络内的最长时间。TCP报文是以IP数据报在网络内传输的,而IP数据报限制生存时间是基于TTL字段,TTL并不是定时器而是基于跳数。
2MSL存在的原因有:
1)确认被动方已经收到了ack,如果对方没有收到ack,则又会发送一个fin,这样一个来回就是2MSL时间了
2)等待足够长的时间让这个连接的报文不会跟后面新建的连接报文混在一起

 

抓包分析

通过Wireshark抓包分析连接的建立和关闭过程
Wireshark的表达式为:

1
(ip.dst_host==61.135.169.105 and ip.src_host==192.168.0.100) or (ip.dst_host==192.168.0.100 and ip.src_host==61.135.169.105)

详细看一下连接的建立过程,首先是客户端发送一个SYN包
此时的只有一个序列号,没有确认序列号,序列号是1735627764
另外在可选项中有一个MSS,设置为1460,还有一个SACK。

服务端收到SYN包后,发送SYN+ACK包
此时服务端的序列号是1533877802,同时对客户端的序列号进行了确认,也就是序列号+1,所以ack是1735627765
服务端的MSS设置为1440,所以使用两者中较小的一个MSS,也就是1440字节。

最后客户端回一个ack包给服务端
应答的ack是服务端序列号+1,所以ACK就是1533877803

双方关闭连接的四次握手TCP包都没有数据,包头都是20字节,通过Wireshark的抓图就能看出来,这里就不再描述了。

 

重传机制

超时重传

TCP连接第一次发送SYN后经过了3秒都没有收到ACK包,又发了第二个包等了6秒还没收到又发了第三个包。
这里可以看到发送的时间是依次成倍增长的。
《TCP/IP详解》里面超时时间第一次是接近6秒,然后是若干个24秒,这里可能是不同操作系统导致的超时发送时间不同。
如果是连接一个无法ping通的ip地址,出现的结果也是一样的。

 

连接的半关闭

可以看到,客户端关闭输出流之后,发送一个fin包,服务端也响应了这个fin包做了ack回应,
但是之后连接并没有关闭,服务端可以继续发送数据,客户端也可以读取数据并响应ack。

 

连接的复位

客户端按下ctrl+c后触发了一个RST,导致服务端断开连接
异常终止对程序来说有两个优点:
1.丢弃任何代发数据并立即发送复位报文段
2.RST的接收方会区分另一端执行的是异常关闭还是正常关闭。应用程序使用的API必须提供产生异常关闭
而不是正常关闭的手段
TCP选项SO_LINGER提供了这种异常关闭的能力,如果设置为0,导致连接关闭时进行复位而不是正常的
FIN包。如果设置了这个功能,表示在关闭前会把残留的数据返回给发送方,如果等待指定的时间还没有
完成则关闭。
SO_LINGER的结构体如下:

struct linger {
    int l_onoff; /* 0 = off, nozero = on */
    int l_linger; /* linger time */
};

有下列三种情况:

  1. 设置 l_onoff为0,则该选项关闭,l_linger的值被忽略,等于内核缺省情况,close调用会立即返回给调用者,如果可能将会传输任何未发送的数据;
  2. 设置 l_onoff为非0,l_linger为0,则套接口关闭时TCP夭折连接,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST给对方,而不是通常的四分组终止序列,这避免了TIME_WAIT状态;
  3. 设置 l_onoff 为非0,l_linger为非0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。如果套接口缓冲区中仍残留数据,进程将处于睡眠状态,直 到(a)所有数据发送完且被对方确认,之后进行正常的终止序列(描述字访问计数为0)或(b)延迟时间到。此种情况下,应用程序检查close的返回值是非常重要的,如果在数据发送完并被确认前时间到,close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失。close的成功返回仅告诉我们发送的数据(和FIN)已由对方TCP确认,它并不能告诉我们对方应用进程是否已读了数据。如果套接口设为非阻塞的,它将不等待close完成。

 

TCP选项

格式如下

kind(1字节) len(1字节) 内容

kind表示选项的类型
len表示一个类型的选项占多少字节
内容就是该选项的内容
比如MSS选项如下:

kind=2 len=4 最大报文段长度(2字节)

kind一个字节,len一个字节,MSS内容2个字节,所以len=4。
各种选项类型如下:

kind 类型 含义
0 选项表结束
1 无操作(用作字节对其填充)
2 最大报文段长度
3 窗口扩大因子
4-7 SACK相关
8 时间戳

 

连接的限制

本地地址 远端地址 描述
local.port foreign.port 限制到一个客户进程
local.port *.* 限制为达到一个本地端口local的连接
*.port *.* 接受发往port的所有连接

请求连接队列

  • 正等待连接请求的一端有一个固定长度的连接队列,该队列的连接已被TCP接受(完成三次握手),但还没有被应用层接受
  • 应用层将指明该队列的最大长度,这个值通常被称为积压值(backlog)
  • 当一个连接请求(SYN)达到时,TCP使用一个算法,根据当前连接队列中的连接数来确定是否接受这个连接。  backlog说明的是TCP监控的端口已被TCP接受而等待应用层处理的最大连接数。这个值对系统所允许的最大连接数  ,或者服务器锁能处理的并发数无影响。
  • 如果对于新的连接请求,该TCP监控的端点的连接队列中海油空间,TCP模块将对SYN进行确认并完成连接的建立。另外客户端主动打开成功但服务端的应用层还不知道这个新连接时,它可能任务服务器进程已经做好准备接受数据了(如果发生这种情况,服务器的TCP仅将接受的数据放入缓冲区)
  • 如果对于新的连接请求,连接队列中已没有空间,TCP将不理会接受的SYN,也不发回任何报文(即不发回RST),如  果应用层不能及时接受已被TCP接受的连接,这些连接可能占满整个连接队列,客户端主动打开最终将超时。

 

参考

TCP的那些事儿
TCP协议中Window Scale Option问题
TCP/IP ECN分析
Congestion window
TCP选项详解
TCP/IP同时打开和关闭
TCP协议中的flag含义
setsockopt :SO_LINGER 选项设置
Stateless TCP
TCP协议疑难杂症全景解析
从TCP协议的原理来谈谈RST复位攻击
一站式学习Wireshark(一):Wireshark基本用法
一站式学习Wireshark(二):应用Wireshark观察基本网络协议
一站式学习Wireshark(三):应用Wireshark IO图形工具分析数据流
一站式学习Wireshark(四):网络性能排查之TCP重传与重复ACK
一站式学习Wireshark(五):TCP窗口与拥塞处理

3 次阅读

发表评论

电子邮件地址不会被公开。