第十三章 TCP连接

TCP是一种面向连接的单播协议。在发送数据之前,通信双方必须在彼此间建立一条连接

TCP必须检测并修补所有在IP层(或下面的层)产生的数据传输问题,比如丢包、重复以及错误。

一个TCP连接由两个IP地址和两个端口号组成。也就是一对套接字

连接部分相关报文

TCP报文头部

13.2 连接:

连接
  1. 主动连接端(通常客户端):向对端发送一个TCP报文,其中SYN被设置1
    报文中指明了对端的IP和端口,它自己的起始序列号。
    同时还会交换一些和连接相关的选项。
  2. 被动连接端收到报文以后,发送一个确认报文,其中ACK被设置1,SYN也被设置1
    同时包含自己的一个起始序列号。
    确认号为对端发来的起始序列号+1。
    同时还会交换一些和连接相关的选项。
  3. 被动连接端收到对端确认报文。同时发送一个确认对端SYN的ACK报文。
    其ACK被设置。SYS没设置。
    同时,序列号为自己上次所发包的序列号+1
    确认号为对端上次发来包的序列号+1
    这个包一般没有选项。

SYN段主要是用来交换初始序列号的,如果不是用来交换初始序列号的化,这个位就没有被设置。然后SYN段也可以用来承载信息,但是伯克利套接字的API不支持,所以很少使用。

需要注意的是主动开启端,最后发送的ACK,带有一个bit的数据,所以seq是1。但是实际中,数据是0填补,而且没有data段。这样的话,接收端应该需要确认啊?

最后一个ACK

同时主动打开

同时开启

13.2 关闭

  1. 主动关闭端,发送一个设置了FIN字段的TCP报文。
    这个序列号应该也是之前的数据的seq来的。
  2. 被动关闭端收到对端发来的关闭请求,发送一个ACK被设置的报文。
    同时确认号为对端的序列号+1

这中间如果被动关闭端仍然在发送数据,则称为半关闭。主动关闭端不能再发送数据,但是仍然需要发送ACK

  1. 在被动关闭端传输完数据以后会发送FIN
    ACK仍然为对端上次的序列号+1
  2. 主动关闭端收到该关闭请求
    发送ACK

这几个段的ACK和SEQ的号码,都继续之前的。和正常交换数据一样。

同时主动关闭

image.png

互相发送FIN,然后互相确认对方的FIN

13.2.1 TCP半关闭

当应用程序一端表明自己已经发送完数据,但是仍然希望继续接受对方数据,因此可以发送一个FIN分解。

13.2.2 初始序列号

在发送用于建立连接的SYN报文之前,通信双方会选择一个初始序列号。
初始序列号会随时间而改变,因此每一个连接都拥有不同的初始序列号。
初始序列号被视为一个32位的计数,数值每4微秒加1。

一个TCP报文段,必须同时拥有一对套接字和位于当前窗口的序列号才能被接收方所接受。(ACK不重要?)

image.png

TCP超时

主动开启端发送SYN分段以后,在3秒后会重发,之后6秒,这个过程称为指数回退

一般tcp超时可能是由于arp导致主机不可达。
如果arp缓存了对方的物理地址

连接管理

连接建立

建立连接过程的主要工作:两端交换初始序列号ISN。

题外话

三次握手问题的问题起源于“两军问题”:红1,红2要攻打蓝方,但是红方被蓝方分割,且红方要战胜蓝方只能同时发起攻击,红方之间通信又必须要经过蓝方领地,通信可能存在丢失或者被截获。因此红方需要一种有效的通信方式来约定发起攻击的时间。
百度百科
没看过原文,不知道限制是什么。
如果不考虑被截获,只考虑可能丢失的情况时:

  1. 方法一(二次握手):
    红1约定时间,通信兵送去红2(步骤一)。红二收到,同意,然后送给红1(步骤2)。红1收到后结束
    存在的问题是:步骤2丢失的情况下,红1没收到红2的信息,如果无限发送步骤一,但是永远收不到步骤2,那么红1就不知道红2同意没有。因此步骤2之后,需要考虑红1是否收到。pass
  2. 方法二(三次握手):
    红1约定时间,通信兵送去红2(步骤一)。红二收到,同意,然后送给红1(步骤2)。红1收到后,发送收到红2的消息给红2(步骤3)。红2收到,结束。
    存在的问题是:步骤3丢失,那么红2依然不知道红1是否收到步骤2的消息。同方法二。pass
  3. 方法二(四次握手):
    红1约定时间,通信兵送去红2(步骤一)。红二收到,同意,然后送给红1(步骤2)。红1收到后,发送收到红2的消息给红2(步骤3)。红2收到后,发送给红1收到确认的消息(步骤4),红1收到以后结束。
    存在的问题是:如果步骤4丢失,那么本次通信中,仍然存在红2不知道红1知不知道,红2同意的消息。

即使有没收到信息就再排一个通信兵的情况,那么仍然不能保证最后一个消息送达。问题在于,红1和红2交替收不到消息。(如果理解不了就画个图)。
因此两军问题无解。

在TCP又需要解决两军问题,来交换信息。
TCP选用了三次握手,但其实两次握手就可以交换完信息。大不了提示连接失败嘛,有啥大不了的。
但是对于TCP又不同,因为只要只要红1收到红2的确认信息,就可以不用管红2了,在以后的交换中直接用交换的到的初始序列号就可以了。只要红2收到红1的消息,就知道红1收到了红2发送的消息。
但是,TCP仍然选用了3次握手。

由此看来四次握手也是多余的。

三次握手

折中办法:三次握手

序列号:seq 表示本分组数据的第一个字节在整个链接中数据中的偏移量。
确认号:ACK
确认分组:ACK

过程

三次握手
  1. 主动开启者。发送一个SYN报文段,知名自己想要连接的端口号和它本次连接的初始序列号(ISN(c)),通常还有多个选项。
  2. 服务收到以后,发送自己的SYN报段作为相应,ACK:服务端本次连接的初始序列号(ISN(s))。Seq:将ISN(c)+1,表示希望收到的下一个分组数据第一个字节在本次连接所有传输数据中的偏移,通常也有多个选项。
  3. 服务端收到以后,ACK:将ISN(s)+1发送给服务端。Seq:不变,因此不需要对端确认该分组。

初始序列ISN号与序列号SYN

序列号的作用

  • 序列号SYN在TCP中代表每个分组的第一个字节在整个数据流中的自己偏移。解决了分组乱序的问题。同时,分组在传送过程中大小可变。
  • 只要接收端收到分组的序列号和上一次分组的序列号有变化,接受端就需要发送ACK。序列号没有变化就不需要发送ACK

序列号为什么不能固定

  • 如果序列号固定了,那就不需要建立连接啦。两端已经知道序列号了,那还建立个啥连接啊。
  • 不选用固定序列号的原因如下:
    延时抵达:假设C,S两端,C因为重传发送了连个请求连接的数据包,并且连接建立交换数据结束以后,连接断开了。此时,之间丢失的那个数据报又到达了S段。S段又建立了一条连接。但是C端并不知道啊。因此会发生错误。
    通过保活机制是可以避免的
  • 排序混乱:同延时抵达。不同的是,数据分组重发了,连接断开。然后又重新建立了一条新的了解,这时候之前连接丢失的数据到达了,那么分组就会混乱。
    同时如果分组的SYN,超过服务端允许接受的MSS大小,也会被丢弃,但也应为此,可以破坏传输的数据
    但是,这不就是不同的套接字了嘛?同一个套接字对不是有time_wait嘛
  • 序列号欺骗:能够向服务器插入信息。假设A主机能够修改B服务器的信息,而其他主机能能请求B服务器信息,且其他主机不能截获A与B的通信(这样就不能获得B发给A的出事序列号)。Z主机通过拒绝服务或者其他手段使A不能联网,然后伪装成B发送请求连接给B(源IP设为A的),B回应自己的初始序列号。如果B回应的初始序列号固定,那么Z就会知道,然后通过这个序列号,再次伪装成A给B发送确认ACK,连接建立,然后Z就可以修改B服务器信息。
    只能修改,不能获得B发给A的数据。也很严重

因此初始序列号必须随机,且不能被预测。

序列号回绕

序列号达到最大值以后回绕(又从头开始):

static inline int before(__u32 seq1, __u32 seq2)
{
return (__s32)(seq1-seq2) < 0;
}
//__u32无符号整数,__s32有符号整数

利用的是有符号和无符号整数的技巧。

ACK作用

用来确认收到的新数据

ACK分组,用来确认收到的、连续的数据的最后一个字节的偏移量+1。
表示:ACK数值之前的数据都已经收到,而ACK数值当前的值得数据是本端需要收到的。

为什么是连续数据

  • 因为分组乱序的原因,需要通知发送端,本端有哪些空洞的数据没有收到。从第一个空洞开始,要求发送端,传送缺失的数据。

什么时候发送ACK分组

  • 当收到的分组的Seq不是当前连续数据最后一个字节的偏移量+1的时候。表示收到了新数据,或者是收到了已经收到的数据(之前丢失有跑过来的数据)。
  • 当没有这事ACK位的时候。

除了三次握手的第三次ACK分组,其余所有ACK分组的Seq值都是上次本端Seq值。
而三次握手的第三次ACK分组的Seq值是上次本端Seq值+1

不需要发送ACK分组

四次挥手

四次挥手
  1. 主动关闭端发送一个FIN段,Seq:承接数据的Seq,ACK:也是和继续使用传送数据使用的,确认数据用。
  2. 被动关闭端将受到发送确认ACK。Seq:对端的ACK(也就是其实发送送了一个字节),ACK:对全Seq+1,表示确认。
    —————半关闭状态—————
  3. 被动关闭端开始关闭,发送FIN段,Seq:接着传送数据的,ACK:接着传送数据的。
  4. 主动关闭端发送ACK确认。

TCP选项

TCP选项

TCP固定头部为20个字节,而TCP头部长度最长为60字节,因此选项的的最大长度为40字节。
每个选项的头一个自己是种类,指明了选项的类型。种类值为0或1的选项仅占用1个字节。
其他的选项会根据种类来确定自身的字节数。
选项的从长度包括了种类与len个字节。
设置NOP选项的,目的是允许发送者在必要的时候用多个4字节组填充某个字段。因为TCP头部的长度应该是4bit的倍数。
EOF指明了选项的结尾。

不能被理解的选项,就会被简单的忽略

  1. 最大段大小选项

指明一台主机TCP协议允许从对方接收到的最大报文段(每次能够接收到的)

最大段大小是指TCP协议所允许的从对方接收到的最大报文段,因此这也是通信对方在发送数据时能够使用的最大报文段。根只记录TCP数据的字节数而不包括其他相关的TCP与IP头部。
建立一条TCP连接时,通信的每一方都要在SYN报文段的MSS选项中说明自已允许的最大段大小
任何主机都能处理至少576字节的ipv4数据报
TCP头部至少20字节,IP头部至少20字节,因此这两部分固定头部为40字节。按照这个计算,TCP协议要求每次发送时的最大段大小为536字节。
而典型的值为1500的IP报,因此数据本分为1460字节。

  1. 选择确认选项
    由于接收到的数据队列会出现空洞的情况,因此TCP接收方需要放置应用程序使用超出空洞的数据,但是这个选项主要是用于通知发送端,接收端已经接收到的数据段。(可能是多个段)

当接收到乱序的数据时,它就能提供一个SACK选项来描述这些乱序的数据,从而帮助对方有效地进行重传。最大SACK块数目为3

  1. 窗口缩放选项

该选项只能出现在一个SYN字段中,这个再看看,看不太懂

指示窗口的大小,然后是该选项的值乘一个比例字段。2^s

  1. 时间戳选项与防回绕序列号
    要求发送方在每个报文段中添加2个4字节的时间戳数值。
    接收方会在确认中反映这些数值,发送方就可以针对每一个ACK估算TTL。
  2. 用户超时选项
    指明发送者在确认对方未能成功接收数据之前愿意等待该数据ACK的确认时间。
  3. 认证选项

13.4 TCP中的路径最大传输单元发现

MTU指经过两台主机之间路径的所有网络报文中最大传输单元的最小值
了解MTU,可以避免在传出过程中被分片。

TCP中的路径最大传输单元发现的过程为:在建立连接是,TCP使用一台主机对外接口的最大传输单元的最小值,或是对方发来数据报中的最大段大小来选择发送方的最大段大小。如果对端没有指明,那么采用默认536数据报。但是少见。
TCP中的路径最大传输单元发现不允许TCP发送方有超过另一方声明的最大段大小的行为。
一条连接的两个方向的TCP中的路径最大传输单元是不同的

链接状态11中

转台转换图

1. CLOSED状态

初始状态,两端尚未建立连接。
客户端未开始连接,服务端也没有启动服务程序。

2. SYN_SENT(客户端)

客户端发送SYN段,请求建立连接。进入SYN_SENT状态。

主机不可达

  • 当直接连接子网中不可达的主机,没有apr缓存
    直接死在arp上,此时是arp报错。没有ICMP。
    经过一段时间重发以后,connect函数返回ETIMEDOUT:Connection time out
  • 当穿过路由后在某一个转发阶段主机不可达。
    也是死在arp上,但是路由器会返回ICMP。(实测中没有收到ICMP,路由器扔了?
    经过一段时间重发以后,connect函数返回EHOSTUNREACH:No rout to host
  • 连接主机一个没有在监听的端口
    服务器直接返回RST段。(为啥我还是没有抓到啊。
    connect函数立即返回ECONNEWFUSED:Connection refused

TCP重传

客户端TCP为了建立连接频繁的发送SYN段。0,3,6,12秒后发送分节。

3. LISTEN(服务端)

没啥啊,服务端的服务程序启动监听。
可能会报错,比如,刚关闭程序,又开启。此时同一端口处于不可用的状态。

4. SYN_RCVD(服务端)(客户端同时打开)

服务端又LISTEN到SYN_RCVD

此状态为服务端被动接收到了客户端的SYN段,发送自己的SYN和ACK进入此阶段。
在等待ACK的过程中,如果接收到的是RST段,那么将返回LISTEN状态。
也就是只有被动请求时,才会返回,客户端是傻?最后不发送ACK,发送RST?进程崩溃了?嗯,是因为这样:客户端的第一个SYN重发了,建立连接以后交换数据又断开了,之前的SYN又到了,所有出现这种情况,不考虑进程崩溃

同时打开

就一对套接字,服务端请求的端口,正是客户端发起请求的端口。

客户端由SYB_SENT进入SUN_RCVD
此状态为,服务端与客户端同时打开连接,两端都处在:自己的SYN也已经发送,对端的SYN分节已经收到,确认对端的ACK已经发送,此时都在等待对端确认自己的ACK时。
同时打开不会有RST,能同时请求,两个端口肯定有在监听嘛

同时打开

同时打开的情况是:大家都在不知道对端的情况下发送的SYN的ISN,然后知道对端请求以后,又“重发”(不是真正意义上的重发)了一次自己的ISN,同时又ACK了对端的ISN。

5. ESTABLISHED

建立连接了啊,赶紧通信啊。

6. CLOSE_WAIT(服务端)

也成为被动关闭端

服务端接收到FIN段,然后发送ACK。进入到CLOSE_WAIT状态。

close()函数关闭(对应客户端状态为CLOSING)

服务端发送FIN分节,等待并接受到ACK以后关闭本次连接,返回的应该是CLOSED?嗯?本次连接确实关闭了。
对应客户端处于CLOSE_WAIT状态。

shutdown()函数关闭(对应客户端状态为FIN_WAIT_2)

该函数常用于,客户端的数据发送完了,但是还需要接受服务端发送来的消息,所以客户端使用shutdown()函数来关闭连接。连接进入半连接状态。(往往还发送一个结尾的标志,让服务端知道)
而正真关闭连接的动作交给服务端的close()函数(需要两端协调)。
对应客户端处于FIN_WAIT_2状态。

7. LAST_ACK

被动关闭端,发送完FIN以后进入该状态,等待接受ACK,接受到ACK以后进入CLOSED

8. FIN_WAIT_1

客户端由于close()或是shutdown()发送FIN段,进入该状态。

9. FIN_WAIT_2(客户端,对应服务端状态为:CLOSE_WAIT)

客户端由shutdown()发送FIN段,进入FIN_WAIT_1,在接收到客户端的ACK以后,仍然需要等待服务端的信息。
对应服务端处于CLOSE_WAIT状态。
此过程中,服务端仍然可以给客户端发送消息,发送完毕以后,close()关闭连接。

完全关闭操作进入FIN_WAIT_2状态

完全关闭状态进入FIN_WAIT_2状态也是有可能的,此时,本端启动一个计时器,如果计时器超时时,连接是空闲的,那么TCP连接直接转移到CLOSED状态。

10. CLOSING(客户端)

客户端调用close()关闭连接,服务端收到后直接发送ACK和FIN段。客户端接收到以后进入CLOSING状态

11. TIME_WAIT状态(主动关闭端)

挺重要的一个状态,避免了同一对套接字因为重传问题导致接收到错误数据。
CLOSING状态发送ACK以后进入此状态!

TIME_WAIT状态也成为2MSL等待状态。在该状态中TCP连接将会等待两倍于最大段生存期(Maximum Segment Lifetime)的时间(通常2分钟,所以等待4分钟)。

TIME_WAIT的意义

  • 为了最后一个ACK的重传
    实现TCP全双工连接的终止
  • 让老的重读分组在网络中消失
    当一个套接字对的链接终止以后,又紧接着在该套接字对上建立了链接。如果以前重传的丢失的分组,又跑过来了,那么服务端会收到错误的数据。
    为了避免这种情况,所以,主动关闭的一端需要等待2MSL时间,让丢失的分子彻底消失。

同时主动关闭端的该端口,TCP讲不给处于TIME_WAIT状态的链接发起新的连接,其实也就是再建立相同套接字对的时候,客户端会选择其他的端口。
服务端一般不会主动关闭,因为端口有限。不能浪费。

13.6 重置报文段

image.png

13.6.1 针对不存在的端口的连接请求

接收方端口上没有进程监听

UDP协议规定,当一个数据报到达一个不能使用的目的端日时就会生成一个ICMP目的地不可达(端口不可达)的消息,TCP协议则使用重置报文段来代替完成相关工作。

当端口不存不存在的时候,第一个SYN报文的ACK没有被设置,接收到这个SYN报文的主机,因为端口上没有监听的进城,因此发送重置报文,其序列号字段被设置为0,而ACK号的大小为设置接收到SYN的序列号+1+数据段长度0。
因为SYN位在逻辑上仍会占用一个字节的序列号空间。

13.6.2 终止一条连接

用户键入中断字符

使用FIN报文终止一条连接称为有序释放,因为FIN是在之前所有排队数据都发送后才被发送出去,因此通常不会出现数据丢失的情况。
对于重置报文终止一条连接,这种方式陈伟终止释放
通信一端崩溃又重启,此时如果对端发送信息,那么本端会发送重置报文。

终止一条连接可以为应用程序提供两大特性:

  • 排队数据会被丢弃,一个重置报文段发送
  • 重置报文段的接收方会说明通信另一端采用了终止的方式而不是一次正常关闭。API必须提供一种实现上述终止行为的方式来取代正常的关闭操作。

重置报文段要求重置报文段的ACK位必须有,而且ACK的值必须在正确的窗口范围内,这样可以防止被攻击。

套接字选项SO_LINGER

此选项指定函数close对面向连接的协议如何操作(如TCP)。内核缺省close操作是立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方。

参考文章1
参考文章2

套接字API通过将SO_LINGER的数值设置为0来实现上述功能。意味着,不会在终止前为了确保数据到达另一端而逗留的时间为0.

13.6.3 半开连接

已建立连接的两台主机之一电源被切断,而没有关机,(假设保护机制没有打开),然后主机又重新开机。

当重启的主机再次接收到对端的数据报的时候,那么就和第一种情况一样了。指定的端口上没有进程在监听。

13.6.4 时间等待错误

TIME_WAIT状态的目的是允许任何发送到一条关闭连接的数据报被丢弃。然而如果处于TIME_WAIT状态的连接接收到数据报,或者是重置报文段,那么它将会被破坏,这种情况称为时间等待错误

13.7 TCP服务器选项

推荐阅读更多精彩内容

  • 18.1 引言 TCP是一个面向连接的协议。无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。本章将...
    张芳涛阅读 2,928评论 0 13
  • 1、TCP状态linux查看tcp的状态命令:1)、netstat -nat 查看TCP各个状态的数量2)、lso...
    北辰青阅读 8,127评论 0 11
  • 24.1 引言 TCP已经在从1200 b/s的拨号SLIP链路到以太数据链路上运行了许多年。在80年代和90年代...
    张芳涛阅读 1,128评论 0 3
  • 个人认为,Goodboy1881先生的TCP /IP 协议详解学习博客系列博客是一部非常精彩的学习笔记,这虽然只是...
    贰零壹柒_fc10阅读 4,478评论 0 8
  • 1.这篇文章不是本人原创的,只是个人为了对这部分知识做一个整理和系统的输出而编辑成的,在此郑重地向本文所引用文章的...
    SOMCENT阅读 12,205评论 6 174
  • 止于唇齿,掩于岁月。
    DM大猫阅读 165评论 2 2
  • 江风急 浪涛花 一路几颠簸 遥望山那头 故里人 应是晚风炊烟时 两岸山 一重重 色凝重 几闻乌啼猿鸣 抬头望 满天...
    牌刀石阅读 283评论 5 2