0%

TCP三次握手/四次挥手的文章看过太多,但有些不太正确。还是决定自己写一篇,也算是对自己知识和碰到的问题的总结。

tcp的主要特点

  • 面向连接
  • 点到点
  • 可靠交付
  • 全双工通信
  • 面向字节流

其中核心的特点:tcp是可以可靠传输协议,它的其他所有特点都为这个可靠传输服务。

如何保证可靠传输

tcp在传输过程中都有一个ack,接收方通过ack告诉发送方收到那些包了。这样发送方能知道有没有丢包,进而确定重传。

tcp三次握手

  • 第一步:client发送连接请求报文段,将SYN位置为1,Seq(Sequence Number)为X(由操作系统动态随机选取一个32位长的序列号)。然后,客户端进入SYN_SEND状态,等待服务器的确认。

  • 第二步:server收到client的SYN报文段。需要对这个SYN报文段进行确认,设置Ack(Acknowledgment Number)设置为X(第一次握手中的Seq的值)+1。同时,自己还要发送SYN请求信息,将SYN位置为1,Seq(Sequence Number)为Y(由操作系统动态随机选取一个32位长的序列号)。服务器端将上述所有信息一并发送给客户端,此时服务器进入SYN_RECV状态。

  • 第三步:client收到sever的SYN+ACK后,回复server一个ACK。将Ack(Acknowledgment Number)设置为Y(第二次握手中的Seq的值)+1,Seq(Sequence Number)设置为X+1(第二次握手中的Ack(Acknowledgment Number)值),向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。

tcp首部简介

这里先介绍下TCP首部的相关知识。三次握手相关的是主要是ACK和SYN这两个标志比特位。

(1)源端口和目的端口:各占2字节。

(2)序号(Seq, Sequence Number):占4个字节。用来标识从TCP发端向TCP收端发送的数据字节流,它表示在这个报文段中的的第一个数据字节在数据流中的序号。主要用来解决网络报乱序的问题。序号范围是[0, 2^32 - 1],共2^32个序号。

(3)确认号(Ack, Acknowledgment Number): 占4个字节。期望收到对方下一个报文段的第一个数据字节的序号,因此,确认序号应当是上次已成功收到数据字节序号加1。不过,只有当标志位中的ACK标志(下面介绍)为1时该确认序列号的字段才有效。主要用来解决不丢包的问题。

(4)数据偏移(Offset): 占4位。TCP报文段的数据起始处距离TCP报文段的起始处有多远。实际上指出TCP报文段的首部长度。但注意,“数据偏移”的单位是32位字(即以4字节长度为单位)。最多能表示15个32bit的的字,即4*15=60个字节的首部长度。因此TCP最多有60字节的首部。然而,没有选项字段,正常的长度是20字节。

(5)保留: 占6位。保留为今后使用,目前置为0。

(6)标志比特(TCP Flags): TCP首部中有6个标志比特,它们中的多个可同时被设置为1,主要是用于操控TCP的状态机的,依次为URG,ACK,PSH,RST,SYN,FIN。每个标志位的意思如下:

  • URG(URGent): 此标志表示TCP包的紧急指针域(后面马上就要说到)有效,用来保证TCP连接不被中断,并且督促中间层设备要尽快处理这些数据。
  • ACK(ACKnowlegment): 仅当ACK=1时Ack字段有效。在连接建立后所有传送的报文段都必须把ACK置1。
  • PSH(PuSH): 这个标志位表示Push操作。所谓Push操作就是指在数据包到达接收端以后,立即传送给应用程序,而不是在缓冲区中排队。
  • RST(ReSeT): 这个标志表示连接复位请求。用来复位那些产生错误的连接,也被用来拒绝错误和非法的数据包。
  • SYN(SYNchoronization): 表示同步序号,用来建立连接。SYN标志位和ACK标志位搭配使用,当连接请求的时候,SYN=1,ACK=0。连接被响应的时候,SYN=1,ACK=1。
  • FIN(FINis): 表示发送端已经达到数据末尾,也就是说双方的数据传送完成,没有数据可以传送了,发送FIN标志位的TCP数据包后,连接将被断开。

(7)窗口: 占2个字节,[0, 2^16 - 1]。指的是发送本报文段的一方的接收窗口。明确指出了现在允许对方发送的数据量。窗口值是经常动态变化的。

(8)校验和: 占2字节。该字段检验的范围包括首部和数据这两部分。由发端计算和存储,并由收端进行验证。

(9)紧急指针: 占2个字节,紧急指针仅在URG=1时才有意义,它指出本报文段中的紧急数据的字节数。当所有紧急数据处理完毕时,TCP就告诉应用程序恢复到正常操作。值得注意的是,即使窗口为0时也可发送紧急数据。

(10)选项: 长度可变,最长可达40字节,当没有选项时,TCP的首部长度是20字节。最初只规定了一种选项,即最大报文段长度MSS(Maximum Segment Size),MSS是指每一个TCP报文段中的数据字段的最大长度。

抓包实践

来看一个pb请求的三次握手过程

client: 172.17.1.217 server: 172.27.247.168

握手的核心目的是告知对方seq(client的初始seq,server的初始seq),对方回复ack(收到的seq+包的大小),这样发送端就知道有没有丢包了。

握手的次要目的是告知和协商一些信息。

  • MSS–最大传输包
  • SACK_PERM–是否支持Selective ack(用户优化重传效率)
  • WS–窗口计算指数(有点复杂的话先不用管)

这就是tcp为什么要握手建立连接,就是为了解决tcp的可靠传输。

查看第一次握手的详情

查看第二次握手的详情

查看第三次握手的详情

为什么要三次握手?

  • 如果只有一次握手,Client不能确定与Server的单向连接,更加不能确定Server与Client的单向连接;

  • 如果只有两次握手,Client确定与Server的单向连接,但是Server不能确定与Client的单向连接;

  • 只有三次握手,Client与Server才能相互确认双向连接,实现双工数据传输。

  • 谢希仁版《计算机网络》中的例子是这样的,“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。

握手中断

  • 第一次握手中断: A发送给B的SYN中断,A会周期性超时重传,直到A收到B的确认响应。
  • 第二次握手中断: B发送给A的SYN、ACK中断,B会周期性超时重传,直到B收到A的确认响应。
  • 第三次握手中断: A发送给B的ACK中断,A不会重传。超时后,B会重传SYN信号(即回到第二次握手),直到B收到A的确认响应。

参考

tcp四次挥手

TCP四次挥手的过程以及握手两端状态的变化。

挥手过程

  • 第一次挥手: 主动关闭方(可以使客户端,也可以是服务器端,这里标记为:A),将FIN置为1,ACK置为1,Seq(Sequence Number)设置为X为上一次对方传送过来的Ack(Acknowledgment Number)值,Ack(Acknowledgment Number)设置为Y为上一次对方传过来的Seq(Sequence Number)值+1。设置好以上值后,将数据发送至被动关闭方(这里标记为:B)。然后A进入FIN_WAIT_1状态。

  • 第二次挥手:B收到了A发送的FIN报文段,向A回复,将ACK置为1,Ack(Acknowledgment Number)设置为X第一次挥手中的Seq(Sequence Number)值+1,Seq(Sequence Number)设置为Y第一次挥手中的Ack(Acknowledgment Number)值。然后B进入CLOSE_WAIT状态,A收到B的回复后,进入FIN_WAIT_2状态。

  • 第三次挥手:B再次向A发送报文,将FIN置为1,ACK置为1,Ack(Acknowledgment Number)设置为X+1第二次挥手中的Ack(Acknowledgment Number)值,Seq(Sequence Number)设置为Y第二次挥手中的Seq(Sequence Number)值。然后B进入LAST_ACK状态,A收到B的报文后,进入TIME_WAIT状态。

  • 第四次挥手:A收到B发送的FIN报文段,像B回复,将ACK置为1,Ack(Acknowledgment Number)设置为Y第三次挥手中的Seq(Sequence Number)值+1,Seq(Sequence Number)设置为X+1第三次挥手中的Ack(Acknowledgment Number)值。然后A进入TIME_WAIT状态,B在收到报文后进入CLOSED状态,A在发送完报文等待了2MSL时间后进入CLOSED状态。

为什么 TIME_WAIT 状态要等待 2MSL 之后才关闭连接

  • 2MSL表示两个MSL的时长,MSL全称为Maximum Segment Life,表示TCP 对TCP Segment 生存时间的限制。

  • 为了保证A发送的最后一个ACK报文段能够到达B。这个ACK报文段有可能丢失,因而使处在LAST_ACK状态的B收不到对自己已发送的FIN+ACK报文段的确认。B会超时重传这个FIN+ACK报文段。而A就能在2MSL时间内收到这个重传的FIN+ACK报文段。接着A重传一次确认,重新启动2MSL计时器。最后A和B都正常进入到CLOSED状态。如果A在TIME_WAIT状态不等待一段时间,而是在发送完ACK报文段后立即释放连接,那么就无法收到B重传的FIN+ACK报文段,因而也不会在发送一次确认报文段。这样,B就无法按照正常步骤进入CLOSED状态。

  • 防止已失效的连接请求报文段出现在本连接中。A在发送完最后一个ACK报文段后,在经过2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。

抓包实践

抓包看,挥手的过程却只有三次。这是因为数据传输中的延迟确认策略。

何谓延迟确认策略?

WIKI:TCP delayed acknowledgment is a technique used by some implementations of the Transmission Control Protocol in an effort to improve network performance. In essence, several ACK responses may be combined together into a single response, reducing protocol overhead. However, in some circumstances, the technique can reduce application performance.即接收方收到包后,如果暂时没有内容回复给发送方,则延迟一段时间再确认,假如在这个时间范围内刚好有数据需要传输,则和确认包一起回复。这种也被称为数据捎带。延迟确认只是减轻网络负担,未必可以提升网络性能,有些情况下反而会影响性能。

正是这个策略,让图中缺少了一次断开连接的包,仔细看可以发现8和9之间时间也是差了200ms左右,为什么是200ms?根据TCP/IP详解卷一里面的描述是绝不大部分平台的时延是按200ms来实现的,但是不允许超过500ms。

谈到延迟确认就必须再谈谈Nagle算法,两者的作用都是减轻网络负担,Nagle算法起源于John Nagle在RFC896的提议,所以命名为Nagle算法。Nagle在描述The small-packet problem时提到TCP在传输1字节有用信息时必须传输41字节的数据,其中20字节的TCP头,20字节的IP头,4000%的开销在低负载网络是可以容忍的,但是在重负载网络,这种开销是会导致网络拥塞,进而导致重传和数据丢失。这里暂时先不用关注拥塞、重传等,后面再讨论。重点关注下Nagle算法的实现原理,即在发送的数据在未被确认前,如果有新的小数据生成,那就把小数据收集起来,等凑足一个MSS或者收到确认后再发送。

说到这,我们就清楚了提到的延迟确认和Nagle算法的作用都是降低网络负担,提高传输效率,但是未必能提升网络性能。

参考

问题的描述

本地镜像在不同物理机之间的传输,这里是通过自研的工具rsc,包含rsc_client和rsc_server。

rsc_server对收到的数据的处理不当,对IO可能产生不理想的结果:
(1)当Bps或者iops 过高时,可能使dm-0的util过高,甚至100%,影响上面虚机的IO;
(2)当传输过程中Bps过低时,会使得传输镜像过慢,影响效率,甚至导致创建虚机失败。

dm-0是物理机上的数据盘,有通过多快盘做raid。

以下在rsc_server的IO优化过程中,对不同方案测试结果的比较和分析。

无限速方案

测试环境为:

测试镜像: Windows 2008 64位 EN

镜像文件为qcow2格式,总共40G,实际占用空间20G。

宿主机:

172.27.162.108 –> 172.27.172.226(V3)

172.28.160.151–> 172.28.161.86(V4)

rsc_server端,接收到数据时,write到cache,就立刻返回。当cache满时,由系统一次性flush到磁盘。会使磁盘util=100%,持续10~20s,导致影响到客户虚机的IO性能。

限速方案

不同方案的测试和比较:
(1)对rsc_server进程的IO限速;
(2)对flush进程的IO限速;
(3)每次write时都sync;
(4)收到一定数量的包,调用fsync
其中对进程的限速是通过cgroup。

对rsc_server进程的IO限速

设置写到磁盘的限速参数:Bps=40MB/s,iops=15000。

把rsc_server进程PID添加到限速的tasks里。

无法限制住磁盘的IO。

原因:将cache刷到磁盘时,是由flush进程执行的,并不是rsc_server本身。所以考虑到将flush进程限速。

对flush 进程的IO限速

把flush进程的PID加入tasks里,可以限制磁盘到40M/s。

带来的问题,会影响所有通过flush来刷磁盘的操作:

1 宿主机的磁盘dm-0上,其他会将数据写到cahe就返回的操作都受到影响,如jbd2服务。jbd2(journaling block device 2)是ext4文件系统的服务,可以完成数据备份和恢复。

2 虚机内部

每次write时都sync

每次write都sync到磁盘,可以绕过从cache flush到磁盘的过程,也就不会产生flush过程中产生的IO过大的问题。

但每次write都sync带来写磁盘性能过低,落盘速度5M/s左右,util为98%~100%。

每收到10个64K的包,调用一次fsync

考虑到每次write都sync,影响磁盘性能,考虑减少sync的次数。改成累积到一定数量的包后,再调用fsync的方案。

172.27.162.108 –> 172.27.172.226( V3 )

172.28.160.151–> 172.28.161.86( V4 )

可以结果:

(1)写入IO得到很大提高,在30M/s ~ 200M/s之间,受宿主机影响较大;

(2)传输过程中,IO一直很稳定,util保持在40%或70%,也没有看到flush进程的出现,说明IO落地是由fsync执行;

使标准镜像(小于40G)都能在20分钟内传完,扩容过的镜像也不影响虚机的创建。

小插曲:

前面由于代码bug, 在每第10个64K包sync了n次。n为该64K的包,在跳过0的空洞后,拆成的小包的个数。sync次数过多导致IO性能过低,测试数据为6M/s左右。

172.28.160.151–> 172.28.161.86(V4)

strace的查看结果:

strace -T -p 21712( rsc_server进程PID ) -o test_rsc_server.txt

IO性能过低。

最终方案

“每收到10个64K的包,调用一次fsync”,由于宿主机的磁盘性能不同,不排除依然可能会有IO打满的情况,因为此时cache到磁盘的过程,是由rsc_server自己控制的,所以可以通过cgroup对rsc_server限速。

172.27.162.108 –> 172.27.172.226( V3 )

限速: rsc_server写入dm-0的 Bps=60M/s。

结果:rsc_server的落盘速度稳定在60M/s。

因此最终的方案是“fsync + cgroup限速”。