TCP协议

1、TCP服务特点

TCP协议特点:面向连接、字节流和可靠传输。通信双方必须先建立连接,才能开始数据读写,双方必须为该连接分配必要的内核资源,以管理连接的状态和连接上数据的传输。TCP连接时全双工的,即双方的数据可以通过一个连接进行。完成数据交换之后吗,通信双必须断开连接以释放系统资源。

TCP协议连接为一对一方式,因此基于广播和多播(目标是多个主机地址)的应用程序不能使用TCP服务。而无连接协议的UDP则非常适合于广播和多播。

字节流服务和数据报服务的区别:这种区别对应到实际变成中,则体现为通信双方释放必须执行相同次数的读、写操作(注意,只是表现形式)。

当发送端应用程序连续执行多次写操作时,TCP模块先将这些数据放入TCP发送缓冲区中。当TCP模块真正开始发送数据时,发送缓冲区中的这些等待发送的数据被封装为一个或多个TCP报文发出。所以,TCP模块发送的TCP报文的个数和应用程序执行的写操作次数之间没有固定的数量关系。

接收端收到一个或多个TCP报文段后吗,TCP模块将它们携带的应用程序数据安装TCP报文端的序号依次放入TCP接收缓冲区,滨田通知应用程序读取数据。接收端应用程序可以一次性将TCP接收缓冲区的数据全部读出,也可以分多次读取(取决于拥有指定的应用程序缓冲区大小)。因此,应用程序执行的读操作次数和TCP模块接收到的TCP报文段个数之间也没有固定的数量关系。

综上所述,得出结论:

  • 相对TCP而言,发送端执行的写操作次数和接收端执行的读操作次数之间没有任何数量关系,这就是字节流的概念,即应用程序对数据的发送和接收时没有边界限制的。
  • 相对UDP而言,发送端应用程序每执行一个UDP数据报执行一次写操作,UDP模块就将其封装成一个UDP数据报并外对端发送。接收端必须及时针对每一个UDP数据报执行读操作,否则就会丢包(经常发生在较慢的服务器上)。并且,如果用户没有指定足够的应用程序缓存区来读取UDP数据,则UDP数据将被截断。这就是数据报的概念。

下图显示了TCP字节流服务和UDP数据报服务的区别:

1)TCP字节流服务

2)UDP数据报服务

TCP协议采用发送应答机制,即发送端发送的每个TCP报文段读必须得到接收方的应答,才认为这个TCP报文段传输成功。其次,TCP协议采用超时重传机制,发送端在发送出一个TCP报文段之后启动定时器,如果在规定时间内未收到应答,则重发该报文段。最后,因为TCP报文段最终还是用IP数据报发送的,而IP数据报到达接收端可能乱序、重复,所以TCP协议还想对接收的TCP报文重排、整理,再交付给应用层。从而保证TCP传输的可靠性。

UDP协议与IP协议类似,提供不可靠服务。它们都需要上层协议来处理数据确认和超时重传。

2、TCP头部结构

2.1 TCP固定头部结构

TCP头部信息出现在每个TCP报文段中,用于指定通信的源端口和目的端口,管理TCP连接等。

  • 16位端口号(port number):告知主机该报文来自哪里,以及传给哪个上层协议或应用程序(目的端口号)。进行TCP通信时,客户端通常使用系统自动选择的临时端口号,而服务器则使用指明的服务端口号。所以知名服务端口号定义在/etc/services文件中。
  • 32位序号(sequence number):一次TCP通信(从TCP连接建立到断开)过程中某个传输方向上的字节流的每个字节的编号(即该次传输报文段所携带数据的第一个字节在整个字节流中的偏移),另一个传输方向上的TCP报文段的序号值也具有相同的含义。
  • 32位确认号(acknowledgement number):用作对另一端发送来的TCP报文段的响应。其值是收到的报文段的序号值加1。
  • 4位头部长度(header length):标志TCP头部有多少个32bit字(4字节),因为4位最大能表示15,所以TCP头部最长是60字节。
  • 6位标志位:
    • URG:表示紧急指针(urgent pointer)是否有效。
    • ACK:表示确认号是否有效。我们称携带ACK标志的TCP报文段位确认报文段。
    • PSH:提示接收端应用程序应该立即从TCP接收缓冲区中取走数据,为接收后续数据腾出空间。
    • RST:表示要求对方重新建立连接。我们称携带RST标志的TCP报文为复位报文段。
    • SYN:表示请求建立一个连接。我们称携带SYN标志的TCP报文段为同步报文段。
    • FIN:表示通知对方本端要关闭连接。我们称携带FIN标志的TCP报文段为结束报文段。
  • 16为窗口大小(windows size):TCP流量控制的一个手段。这里的窗口指接收通过窗口(Receive Window,RWND)。它告知对方本端的TCP接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。
  • 16位校验和(TCP cheksum):由发送端填充,接收端对TCP报文执行CRC算法检验TCP报文段在传输过程中是否损坏。这个校验包括TCP头部和数据部分。因此,这也是TCP可靠传输的一个重要保障。
  • 16位紧急指针(urgent pointer):一个正的偏移量。它和序号段的值相加表示最后一个紧急数的的下一个字节的序号。这个字段是紧急指针相对当前序号的偏移。TCP的紧急指针是发送端向接收端发送紧急数据的方法。

2.2 TCP 头部选项

TCP头部的最后一个选项字段(options)是可变长的可选信息。这部分最多包含40字节,因为TCP头部最长是60字节(其中还包含前面讨论的20字节的固定部分),典型的TCP头部选项结构如下图:

第一个字段kind说明选项类型。有的TCP选项没有后面两个字段,仅包含1字节kind字段。

第二个字段length(如果有的话)指定该选项总长度,该长度包括kind字段和length字段占据的2个字节。

第三个字段info(如果有的话)是选项的具体信息。

常见的TCP选项有7种:

  • kind=0 选项表结束选项。
  • kind=1空操作(nop)选项,没有特殊含义,一般用于将TCP选项的总长度填充位4字节的整数倍。
  • kind=2最大报文段长度选项。TCP连接初始化时,通信双方使用该选项来协商最大报文段长度(Max Segment Size,MSS)。TCP模块通常将MSS设置为(MTU-40)字节(即减掉20字节TCP头部和20字节IP头部)。此时携带TCP报文段的IP数据报长度就不会超过MTU(假设TCP头部和IP头部都不包含选项字段,并且这也是一般情况),从而避免本机发生IP分片。对以太网而言,MSS值是1460(1500-40)字节。
  • kind=3为窗口扩大因子选项。TCP连接初始化时,通信双方使用该选项来协商接收通告窗口的扩大因子。在TCP头部种,接收通告窗口大小是16位表示的,故最大为65535字节,但实际上TCP模块允许的接收通告窗口大小运不知这个数(为了提高TCP通信的吞吐量)。窗口扩大因子解决了这个问题。假设TCP头部中接收通告窗口大小是N,窗口扩大因子(移位数)是M,那么TCP报文段的实际接收通告大小是N乘2的M次方,或者说N左移M位。注意,M的取值范围是0~14。可以通过修改/proc/sys/net/ipv4/tcp_window_scaling内核变量来启用或者关闭窗口扩大因子选项。与MSS选项一样,窗口扩大因子选项只能出现在同步报文段中,否则将被忽略。但同步报文段本身不执行窗口扩大操作,即同步报文段头部的接收通告窗口是该TCP报文段的实际接收通告窗口的大小。当连接建立好之后,每个数据传输方向的窗口扩大因子就固定不变了。
  • kind=4为选择性确认(Selectvie Acknowledgment,SACK)选项。TCP通信时,如果某个IP报文段丢失,则TCP模块会重传最后被确认的TCP报文段后续所有的报文段,这样原先以及正确传输的TCP报文段也可能重复发送,从而降低TCP性能。SACK技术正好为改善这种情况而产生的,它使TCP模块值重发丢失的TCP报文段,不用发送所有未被确认的TCP报文段。选择性确认选项用在连接初始化时,表示是否支持SACK技术。可以通过修改/proc/sys/net/ipv4/tcp_sack内核变量来启动或关闭选择性确认选项。
  • kind=5为SACK实际工作的选项。该选项的参数告诉发送方本端已经收到并缓存的不连续的数据块,从而让发送端可以据此检测并充分丢失的数据库。每个块边沿(edge of block)参数包含一个4字节的序号。其中块左边沿表示不连续的垮的第一个数据的序号,而块右边沿不连续块的最后一个数据的序号的下一个序号。这样对参数之间的数据时没有收到的。因为每个垮信息占用8字节,所有TCP头部选项中实际上最多可包含4个这样的不连续数据块(考虑选项类型和长度占用的2字节)。
  • kind=8是时间戳选项。该选项提供了较为准确的计算通信双方之间的回路时间(Round Trip Time,RTT)的方法,从而为TCP流量控制提供重要信息。可以通过修改/proc/sys/net/ipv4/tcp_timestamps内核变量来启动或关闭时间戳选项。

3、TCP连接的建立与关闭

 3.1 TCP连接的建立与关闭

在192.168.6.16抓取TCP连接过程中客户端和服务器交换的TCP报文。命令如下:

 sudo tcpdump -i enp2s0 -nt '(src 192.168.6.16 and dst 192.168.6.180) or (src 192.168.6.180 and dst 192.168.6.16)'

在192.168.6.180 上进行远程登录,操作过程如下:

执行telnet命令并在两台通信主机之间建立TCP连接后,输入quit退出telnet客户端程序,从而结束TCP连接。整个过程中(从连接建立到结束)tcpdump输出内容如下所示:

IP 192.168.6.180.46850 > 192.168.6.16.80: Flags [S], seq 689215479, win 64240, options [mss 1460,sackOK,TS val 3022028385 ecr 0,nop,wscale 7], length 0
IP 192.168.6.16.80 > 192.168.6.180.46850: Flags [S.], seq 1208482781, ack 689215480, win 65160, options [mss 1460,sackOK,TS val 1179665075 ecr 3022028385,nop,wscale 7], length 0
IP 192.168.6.180.46850 > 192.168.6.16.80: Flags [.], ack 1, win 502, options [nop,nop,TS val 3022028386 ecr 1179665075], length 0
IP 192.168.6.180.46850 > 192.168.6.16.80: Flags [F.], seq 1, ack 1, win 502, options [nop,nop,TS val 3022040881 ecr 1179665075], length 0
IP 192.168.6.16.80 > 192.168.6.180.46850: Flags [F.], seq 1, ack 2, win 510, options [nop,nop,TS val 1179677566 ecr 3022040881], length 0
IP 192.168.6.180.46850 > 192.168.6.16.80: Flags [.], ack 2, win 502, options [nop,nop,TS val 3022040882 ecr 1179677566], length 0

因为整个过程并没有发送应用层数据的交换,所以TCP报文的数据部分长度(length)总是0。

TCP连接建立:

  • 第1个TCP报文段包含SYN标志,因此它是一个同步报文段,即180向16发起连接请求。同时该同步报文包含一个ISN值为689215479的序号。
  • 第2个TCP报文也是同步报文段,表示16同意与180建立连接,同时发送自己的ISN值为1208482781的序号,并对第1个报文段进行确认。确认值是689215480,即第1个报文段的序号值加1。前面说过,序号之是用来标志TCP数据流中每个字节的,但同步报文段比较特殊,即使它并没有携带任何应用程序数据,它要要占用1个序号值。
  • 第3个TCP报文是对180对第2个同步报文段的确认。至此,TCP连接就建立起来了。建立TCP连接的这三个步骤称为TCP三次握手。

TCP连接关闭:

  • 第4个报文段包含FIN标志,因此是一个结束报文,即180请求关闭报文段。结束报文和同步报文一样,也要占用一个序号值。
  • 第5个报文包含FIN标志,也是一个结束报文,即16发给180的结束报文段。(如果有TCP的延迟确认特性,在第5个报文发送前,16会先发一个确认结束的报文)
  • 第6个报文是180确认确认报文段。

在连接的关闭过程中,因为180先发送结束报文段(telnet客户端主动退出),故称180执行主动关闭,而称16执行被动关闭。整个过程如下图所示:

3.2 半关闭状态

TCP连接是全双工的,所以它允许两个方向的数据传输被独立关闭,即通信的一端可以发送结束报文段给对端,告诉它本端已经完成了数据的发送,但允许继续接收来自对端的数据,直到对端也发送结束报文段以关闭连接。TCP连接的这种状态称为半关闭(half close)状态。

服务器和客户端应用程序判断对方是否已经关闭连接的方法是:read系统调用返回0(收到结束报文段)。同时,Linux还提供其他检测连接是否被对方关闭的方法。

3.3 连接超时

上一节中讨论的是很快建立连接的情况,如果客户端访问一个距离很远的服务器,或者由于网络繁忙,导致服务器对于客户端发送出的同步报文没有应答,此时客户端程序必须进行重连,如果重连失败,则通知应用程序连接超时。重连次数由/proc/sys/net/ipv4/tcp_syn_retries内核变量所定义。每次重连的超时时间都增加一倍。

抓包指令:

sudo tcpdump -n -i enp2s0 port 23

登录指令:

date;telnet 192.168.6.16;date

4、 TCP状态转移

TCP连接的任意一端在任何时刻都处于某种状态,当前状态可以通过netstat命令查看。本节讨论TCP连接从建立到关闭的整个过程中通信两端状态的变化,

虚线表示典型的服务器端的连接状态转移;粗线线表示典型客户端连接状态转移。CLOSED是一个假想的起始点,并不是一个实际的状态。

 4.1 TCP状态转移总图

服务器端状态转移过程 

服务器通过listen系统调用,进入LISTEN状态,被动等待客户端连接。服务器一旦监听到某个连接请求(收到同步报文段),就将该连接放入内核等待队列中,并向客户端发送带SYN标志的确认报文段。此时该连接处于SYN_RCVD状态。如果服务器成功接收到客户端发送回的确认报文段,则该连接转移到ESTABLISHED状态。ESTABLISHED状态是连接双方能够进行双向数据传输的状态。

当客户端主动关闭连接时(通过close或shutdown系统调用向服务器发送结束报文段),服务器通过返回确认报文段使连接进入CLOSE_WAIT状态,表示等待服务器应用程序关闭连接。通常,服务器检测到客户端关闭连接后,也会立即给客户端发送一个结束报文段来关闭连接。这将使连接转移到LAST_ACK状态,以等待客户端对结束报文段的最后一次确认。一旦确认完成,连接就彻底关闭了。

客户端状态转移过程

客户端通过connect系统调用主动与服务器建立连接。connect系统调用首先给服务器发送一个同步报文段,使连接转移到SYN_SENT状态。此后,connect系统调用可以因为如下两个原因失败返回:

  • connect连接目标断开不存在(未被任何进程监听),或者断开仍处于TIME_WAIT状态的连接所占用,则服务器将给客户端发送一个复位报文段,connect调用失败。
  • 如果目标端口存在,但connect在超时时间内未收到服务器的确认报文段,则connect调用失败。

connect调用失败将使连接立即返回到初始的CLOSED状态。如果客户端成功收到服务器的同步报文段和确认,则connect调用成功,连接转移至ESTABLISHED状态。

客户端主动关闭时,它向服务器发送一个结束报文段,同时连接进来FIN_WAIT_1状态。若此时客户端收到服务器专门用于确认目的的确认报文段,则连接转移至FIN_WAIT_2状态。当客户端处于FIN_WAIT_2状态时,服务器处于CLOSE_WAIT状态,这一对状态是可能发生的半关闭的状态。此时如果服务器也关闭连接(发送结束报文段),则客户端将给予确认并进入TIME_WAIT状态。

另外,上图中还给出了客户端从FIN_WAIT_1状态直接进入TIME_WAIT状态的一条路线(不经过FIN_WAIT_2状态),前提是处于FIN_WAIT_1状态的客户端直接收到带确认的结束报文段(而不是先收到确认报文段,再收到结束报文段)

 处于FIN_WAIT_2状态的客户端需要等待服务器发送结束报文段,才能转移至TIME_WAIT状态,否则它将一直停留再这个状态。如果不是为了再半关闭状态下继续收数据,连接长时间地停留在FIN_WAIT_2状态并无益处。连接停留在FIN_WAIT_2状态的情况可能发生在:客户端执行半关闭后,未等服务器关闭连接就强行退出了。此时客户端连接由内核来接管,可称之为孤儿连接。Linux为了防止孤儿连接长时间停留在内核中,定义了两个内核变量:/proc/sys/net/ipv4/tcp_max_orphans和/procs/sys/net/ipv4/tcp_fin_timeout。前者指定内核能接管的孤儿连接数目,后者指定孤儿连接在内核中生存的时间。

TCP连接建立和断开过程中客户端和服务器端状态变化如下图所示:

4.2 TIME_WAIT状态

客户端连接在收到服务器的结束报文段之后,并没有直接进入CLOSED状态,而是转移到TIME_WAIT状态。在这个状态,客户端连接要等待一段长为2MSL(Maximum Segment Life,报文段最大生存时间)的时间,才能完全关闭。MSL是TCP报文段在网络中的最大生存时间,标准文档RFC1122的建议值是2min。

 TIME_WAIT状态存在的原因有两点:

  • 可靠的终止TCP连接。
  • 保持迟来的TCP报文段有足够的时间被识别并丢弃。

第一个原因:假设用于确认服务器结束报文段6的TCP报文7丢失,那么服务器将重发结束报文段。因此客户端需要停留在某个状态以处理重复收到的结束报文段(即向服务器发送确认报文段)。否则客户端将以复位报文段来回应服务器,服务器则认为这是一个错误,因为它期望的是一个向TCP报文段7那样的确认报文段。

在Linux系统上,一个TCP断开不能被同时打开多次(两次及以上)。当一个TCP连接处于TIME_WAIT状态时,我们将无法立即使用该连接占用着的端口来建立一个新连接。相反,如果不存在TIME_WAIT状态,则应用程序能够理解建立一个和刚关闭的连接类似的连接(这里说的类似,是指他们具有相同的IP地址和端口号)。这个新的和原来相似的连接被称为原来的连接的化身。新的化身可能接收到属于原来的连接的、携带应用程序数据的TCP报文段(迟到的报文段),这显然是不应该发生的。这就是TIME_WAIT存在的第二个原因。

 有时应用程序希望避免使用TIME_WAIT状态。因为当程序退出以后,我们希望立即重启它。但由于处在TIME_WAIT状态的连接还占用着端口,程序将无法启动(直到2MSL超时结束)。对于客户端程序来说,通常不用担心上面描述的重启问题,因为客户端一般使用系统自动分配的临时客户端号码来建立连接,而由于随机性,临时端口号一般和程序上一次使用的端口号不同。所以客户端程序一般可以立即重启。

但如果是服务器主动关闭连接后异常终止,则因为它总是使用一个知名的服务端口号,连夜连接的TIME_WAIT状态将导致它不能立即重启。不过,我们可以通过Socket选项SQ_REUSERADDR来强制进行立即使用处于TIME_WAIT状态的连接占用的端口。

5、复位报文段

在某些特殊条件下,TCP连接的一端会向另一端发送携带RST标志的报文段,即复位报文段,以通知对方关闭连接或重新建立连接。

5.1 访问不存在的端口

当客户端程序访问一个不存在的端口是,目标主机将给它发送一个复位报文段。

比如16上执行telent命令登录180上一个不存在的端口54321,并用tcpdump抓取该过程中两台主机的TCP报文段。具体操作如下:

//180上执行
sudo tcpdump -nt -i enp1s0 port 54321
//16上执行
telnet 192.168.6.180 54321

16上的telnet程序输出显示连接被拒绝,因为这个端口不存在。

180上tcpdump抓取到的TCP报文段内容如下:

由此可见,180针对16的连接请求(同步报文段)回应了一个复位报文段(tcpdump输出R标志)。因为复位报文段的接收通告窗口大小为0,所以可以预见:收到复位报文段的一端应该关闭连接或者重新连接,而不能回应这个复位报文段。

实现上,当客户端程序向服务器的某个端口发起连接,而该端口仍被处于TIME_WAIT状态的连接所占用时,客户端程序也将收到复位报文段。

5.2 异常连接终止

前面讨论的连接终止方式都是正常终止方式:数据交换完成之后,一方给另一方发送结束报文段。而TCP提供异常终止连接的方法,即给对方发送一个复位报文段。一旦发送了复位报文段,发送端所有排队等待发送的数据都将被丢弃。

应用程序可以使用socket选项SO_LINGER来发送复位报文段,以异常终止一个连接。

5.3 处理半打开的连接

服务器(或客户端)关闭或者异常终止了连接,而对方没有收到结束报文(比如网络发生了故障),此时,客户端(或者服务器)还维持着原来的连接,而服务器(或客户端)即使重启,也已经没有该连接的任何信息了。我们称这种状态为半打开的状态,处于这种状态的连接称为半打开连接。如果客户端(服务器)往处于半打开状态的连接写入数据,则对方将回复一个复位报文段。

//109上运行服务器程序
nc -l 12345
sudo tcpdump -nt -i enp1s0 port 12345
//16上执行 telnet
telnet 192.168.0.109 12345

16远程登录后,拔掉109网线,并重启服务器。在后在16上向半连接状态的连接写入字符a。tcpdump抓取到的TCP报文段如下:

输出内容中,前3个TCP报文是正常的TCP连接的3此握手过程。第4个TCP报文段由客户端发送给服务器,它携带了3个字节应用程序数据,这3个字节依次是字母”a“,回车符”\r“,换行符”\n“。不过因为服务器程序已经被断开,所有109对客户端发送的数据库回应了一个复位报文段5。

6、TCP交互数据流

TCP报文段所携带的应用程序数据按照长度分为两种:

  • 交互数据
  • 成块数据

交互数据包含很少的字节。使用交互数据的应用程序(或协议)对实时性要求高,比如telnet、ssh等。

成块数据的长度则通常为TCP报文段允许的最大数据长度。使用成块数据的应用程序(或协议)对传输效率要求较高,比如ftp。

比如在本机执行telent登录到本机,然后再shell命令提示符后执行ls命令。同时使用tcpdump抓取这一过程中telnet客户端和telnet服务器的TCP报文段。具体操作如下:

tcpdump -nt -i lo port 23 
telnet 127.0.0.1
//登录后输入ls 
ls (回车)

tcpdump 输出如下:

TCP报文段1由客户端发给服务器,它携带1个字节的应用程序数据,即字母"l"。

TCP报文段2是服务器对报文段1的确认。同时回显字母"l"。

TCP报文段3是客户端对TCP报文段2的确认。

第4~6个报文段是针对字母"s"的上述过程。

TCP报文段7传送的2字节数据分别是:客户端键入的回车符和流结束符(EOF,本例中是0x00)。

TCP报文段8携带服务器返回的客户端查询的目录的内容(ls命令输出),包括该目录下的文件的名字及其显示控制参数。

TCP报文段9是客户端对TCP报文段8的确认。

TCP报文段10是携带的也是服务器返回给客户端的数据,包括一个回车符、一个换行符、客户端登录用户的PS1环境变量(第一级命令提示符)。

TCP报文段11是对客户 端的TCP报文段10的确认。

在上述过程中,客户端争对服务器返回的数据所发送的确认报文段(TCP报文段6、9、11)都不携带任何应用程序数据(长度为0),而服务器每次发送的确认报文段(TCP报文段2、5、8、10)都包含它需要发送的应用程序数据。服务器的这种处理方式称为延迟确认,即它不马上确认上次收到的数据,而是在一端延迟时间后查看本端是否由数据需要发送,如果有,则和确认信息一起发出。

因为服务器对客户端请求处理的很快,所有发送确认报文段的时候总是有数据一起发送。延迟确认可以减少发送TCP报文段的数量。而由于用户的输入书店明显慢于客户端程序的处理速度,所以客户端的确认报文段总是不携带任何应用数据。注意:在TCP连接的建立和断开过程中,也可能发生延迟确认。

上述例子在本端回路运行的结果,在局域网中也能得到基本相同的结果,但在广域网中就未必如此了。广域网上的交互数据流可能经受很大的延迟,并且携带交互数据的微小TCP报文段数量一般很多(一个按键输入就导致一个TCP报文段),这些因素都可能导致拥塞发生。而解决该问题的一个简单有效的方法是使用Nagle算法。

Nagle算法要求一个TCP连接的通信双方在任意时刻都最多只能发送一个未确认的TCP报文段,在该TCP报文段的确认到达之前不能发送其他TCP报文段。另一方面,发送方在等待确认的同时收集本端需要发送的微量数据,并在确认到来时以一个TCP报文段将它们全部发出。这样极大减少了网络上的微小TCP报文段的数量。该算法的另一个有点在于其自适应性:确认到达得越快,数据也就发送得越快。

7、TCP成块数据流

FTP协议传输大文件时,则采用TCP成块数据流进行传输。操作过程:

tcpdump -nt -i enp1s0 port 20
ftp 127.0.0.1
ftp>get bigfile(回车) #获取大文件

上述过程,tcpdump输出如下:

注意,客户端发送的最后两个TCP报文段17和18,它们分别是对TCP报文段2和16的确认(从序号值和确认值来判断)。由此可见,当传输大量大数据块的时候,发送方会连续发送多个TCP报文段,接收方可以一次确认所有这些报文段。那么发送方在收到上一次确认后能连续发送多个TCP报文段呢?这是由接收通告窗口(还需考虑拥塞窗口)的大小决定。TCP报文段17说明客户端还能接收30084*64字节(本例中窗口扩大因子为6)。而在TCP报文段18中,接收通告大小为27317*64字节,即客户端能接收的数据量变小了。这表明客户端的TCP接收缓冲区还有更多的数据未被应用程序读取而停留在其中,这些数据都来自TCP报文段3~16中的一部分。服务器在收到TCP报文段18后,它至少还能连续发送尾部确认的报文段数量是106个(但一般不会连续发这么多i)。其中16384是成块数据的长度(见TCP报文段1~16的lengh值),和显然它小于但解决MSS规定的16396字节。

注意,服务器没发送4个TCP报文段就传送一个PSH标志给客户端,用于通知客户端的应用程序尽快读取数据。不过,这对于服务器来说显然不是必需。

8、带外数据

有些传输层协议具有带外(Out Of Band,OOB)数据的概念,用于迅速通告对方本端发生的重要事件。因此,带外数据比普通数据(也称带内数据)有更高的优先级,它应该立即被发送,而不论发送缓冲区 中是否有排队等待发送的普通数据。带外数据的传输可以使用一条独立的传输层连接,也可以映射到传输普通数据的连接中。实际应用中,带外数据的使用很少见,已知的仅有telent、ftp等远程非活跃程序。

UDP没有实现带外数据传输,TCP也没有真正的带外数据库。不过TCP利用其头部中的紧急指针标志和紧急指针两个字段,给应用程序提供一种紧急方式。TCP紧急方式利用传输普通数据的连接来传输紧急数据。这种紧急数据的含义和带外数据类似,因此也将TCP 紧急数据称为带外数据。

 TCP发送外带数据的过程:

假设一个进程已经往某个TCP连接的发送缓冲区写入了N字节普通数据,并等待发送。在数据被发送钱,该进程又向该连接写入了3个字节的带外数据“abc”,此时,待发送的TCP报文段的头部将被设置URG标志,并且紧急指针被设置为指向最后一个带外数据的下一字节(进一步减去当前TCP报文段的序号值得到其头部中的紧急偏移值)。

发送端一次发送的多个字节的带外数据之哟u最后一个字节被当作带外数据(字母c),而其他数据(字母a和b)被当成了普通数据。如果TCP模块以多个TCP报文段来发送上图所示TCP发送缓冲区的内容,则每个TCP报文段都将设置URG标志,并且它们的紧急指针指向同一个位置(数据流中带外数据的下一个位置),但只有一个TCP报文段真正携带带外数据。

TCP接收带外数据的过程:

TCP接收端只有在收到紧急指针标志时才检查紧急指针,然后根据紧急指针所指的位置确定带外数据的位置,并将它读入一个特殊的缓存中,这个缓存只有1字节,称为带外缓存。如果上层应用程序没有及时将带外数据从带外缓存中读取出,则后续的带外数据将覆盖它。

如果TCP连接设置了SO_OOBINLINE选项,则带外数据将和普通数据一样被TCP模块存放在TCP接收缓冲区中。此时应用程序需要像读取普通数据一样来读取带外数据。那么这种情况下如何区分带外数据和普通数据呢?显然,紧急指针可以用来指出带外数据的位置,socket编程接口也提供了系统调用来识别带外数据。

9、TCP超时重传

在异常网络状况下(开始出现超时或丢包),TCP能控制数据传输以保证可靠服务。

TCP服务必须能够重传超时时间内未收到确认的TCP报文段。为此,TCP模块未每个TCP报文段都维护一个重传定时器,该定时器在TCP报文段第一次被发送时启动。如果超时时间内未收到接收方的应答,TCP模块将重传TCP报文段并重置定时器。至于下次重传的超时时间如何选择,以及最多指向多少次重传,就是TCP的重传策略。

实验过程:

#109上执行
sudo tcpdump -n -i eth0 port 5001
iperf -s  #108上执行
#109上执行
telent 192.168.1.108 5001
Trying 192.168.1.108 ...
connected to 192.168.1.108.
Escape character is '^]'.
1234  #发送完之后断开服务器网线。
12
Connection closed by foreign host

iperf是一个测量网络状况的工具,-s 选项表示其作为服务器运行。iperf默认监听5001端口,并丢弃该端口上收到的所有数据。相当于一个discard服务器。上述操作过程tcpdump输出如下:

  • TCP报文段1~3是三次握手建立连接的过程。
  • TCP报文段4~5是客户端发送数据“1234”(应用程序数据长度为6,包括回车、换行两个字符)及服务器确认的过程。
  • TCP报文段6是客户端第一次发送数据"12"的过程。
  • TCP报文段7~11报文段是因为服务器的网线被断开,所以在客户端无法收到TCP报文段6的确认报文段后,执行了5此重传(可以从TCP报文段序号得知)。
  • 数据包12~23都是ARP模块输出内容。即从109查询108的MAC地址。

从tcpdump输出的时间戳,可以看到TCP报文段6~11被发送的时间间隔分别为0.2s、0.4s、0.8s、1.6s、3.2s。可见TCP一共执行5次重传,每次重传超时时间都增加一倍。在5次重传均失败的情况下,底层的IP和ARP开始接管,直到telent客户端放弃连接为止。

 Linux有连个重要内核参数与TCP超时重传相关:/proc/sys/net/ipv4/tcp_retries1和/pro/sys/net/ipv4/tcp_retries2。前者指定在底层IP接管之前TCP最少执行的重传次数,默认值是3.后者指定连接放弃前TCP最多可以执行重传次数,默认值是15(一般对于13~30min)。在上述实例中,超时重传发送了5次,连接坚持时间是15分钟。

10、拥塞控制

10.1拥塞控制简介

TCP模块还有一个重要任务,即提高网络利用率,降低丢包率,并保证网络资源对每条数据流的公平性。这就是所谓的拥塞控制。

TCP拥塞控制的标志文档是RFC 5681,其中详细介绍了拥塞控制的四个部分:

  • 慢启动(slow start)
  • 拥塞避免(congestion avoidance)
  • 快速重传(fast retransmit)
  • 快速回复(fast recovery)

拥塞控制算法在Linux下有多种实现,比如reno算法、vegas算法和cubic算法等。它们部分或者全部实现了上述四个部分。/proc/sys/net/ipv4/tcp_congestion_control文件指示机器当前所使用的拥塞算法。

拥塞控制的最终受控变量是发送端向网络一次连续写入(收到其中第一个数据的确认之前)的数据量,我们称为SWND(send window,发送窗口)。不过,发送端最终以TCP报文段来发送数据,所以SWND限定了发送端能连续发送的TCP报文段数量。这些TCP报文段的最大长度(仅指数据部分)称为SMSS(Sender Maximun Segment Size,发送者对打段大小),其指一般等于MSS。

发送端需要合理地选择SWND的大小,如果SWND太小,会引起明显的网络延迟;反之,如果SWND太大,则容易导致网络拥塞。

接收方可以通过其接收通告窗口(RWND)来控制发送端的SWND。但这显然不够,所以发送端引入了一个称为拥塞窗口(Congestion Window,CWND)的状态变量。实际的SWND值是RWND和CWND中较小者。下图显示了拥塞控制的输入和输出(可见,它是一个闭环反馈控制)。

10.2 慢启动和拥塞避免

TCP连接建立好之后,CWND将被设置成初始值IW(Initial Windows),其大小为2~4个SMMS。但新的Linux内核提高了该初始值,以减小传输滞后。此时发送端最多能发送IW字节的数据。此番发送端每收到接收端的一个确认,其CWND就按照如下公式增加:

CWND +=min(N,SMMS) 

其中N是此次确认中包含的之前未被确认的字节数。这样一来,CWND按照指数形式扩大,这就是所谓的慢启动。慢启动的理由是,TCP模块刚开始发送数据时并不知道网络的实际情况,需要用一种试探的方式平滑的增加CWND的大小。

但如果不施加其他手段,慢启动必然使得CWND很快膨胀,并最终导致网络拥塞。因此TCP拥塞控制中定义了另一个重要的状态变量:慢启动门限(slow start threshold size,ssthresh)。当CWND的大小超过该值时,TCP拥塞控制将进入拥塞避免阶段。

拥塞避免算法使得CWND按照线性方式增加,从而减缓其扩大。RFC 5681中提到了如下两种实现方式:

  • 每个RTT时间内按照上述公式计算新的CWND,而不论该RTT时间内发送端收到多少个确认。
  • 每收到一个对新数据的确认报文段,按照如下公式更新CWND。
CWND +=SMSS*SMSS/CWND

下图粗略地描述了慢启动和拥塞避免发生的时机和区别。图中,为例讨论方便,以SMSS为单位来显示CWND(实际上以字节为单位),以次数为单位来显示 RTT。

以上讨论了发送端在未检测到拥塞时所采用的积极编码拥塞的方法。接下来接收拥塞发生时(可能发生在慢启动阶段或拥塞避免阶段)拥塞控制行为。

发送端判断拥塞发生的依据有如下两个:

  • 传输超时,或者说TCP重传定时器益处。
  • 接收到重复的确认报文段。

拥塞控制对这两种情况有不同的处理方式。对第一种情况仍然使用慢启动和拥塞避免。对第二种情况则使用快速重传和快速恢复(如果是真的发生拥塞的话)。注意:第二种情况如果发生在重传定时器溢出之后,则也被拥塞控制当成第一种情况来对待。

如果发送端检测到拥塞发生是由于传输超时,即上述第一种情况,那么它将指向重传并做如下调整:

ssthresh=max(FlightSize/2,2*SMSS)
CWMD <=SMSS

其中,FlightSize是已经发送但未收到确认的字节数。这样调整之后,CWMD将小于SMSS,那么也必然小于新的慢启动门限值ssthresh,它一定不小于SMSS的2倍,故而拥塞控制再次进入慢启动阶段。

10.3 快速重传和快速恢复

在很多情况下,发送端都可能接收到重复的确认报文段,比如TCP报文段丢失,或者接收端收到乱序TCP报文段并重排之等。拥塞控制算法需要判断当前收到重复的确认报文段时,网络是否真的发送了拥塞,或者说TCP报文段是否真的丢失了。具体做法是:发送端如果连续收到3个重复确认报文段,就认为是拥塞发生了。然后它启动快速重传和快速恢复算法来处理拥塞。过程如下:

  • 当收到第3个重复确认报文段时,按照如下公式计算ssthresh,然后立即重传丢失的报文段。
    ssthresh=max(FlightSize/2,2*SMSS)
    CWMD <=SMSS

    并按照如下公司设置CWND。

    CWND=ssthresh + 3*SMSS
  • 每次收到1个重复确认时,设置CWND=CWND+ssthresh。此时发送端可以发送新的TCP报文段(如果新的CWND允许的话)。
  • 当收到新的数据确认时,设置CWND=ssthresh(ssthresh是新的慢启动门限值,由第一步计算得到)

快速重传和快速恢复完成之后,拥塞控制将恢复到拥塞避免阶段。

注:笔记来源《Linux高性能服务器编程》

posted @ 2021-06-09 13:19  钟齐峰  阅读(692)  评论(0编辑  收藏  举报