TCP协议

2019/04/10 10:10
阅读数 53

TCP详解

1、前言

传输控制协议(Transmission Control Protocol,TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。

2、TCP详解

2.1TCP报文头部

来源连接端口(16bit)\目的连接端口(16bit):计算机上的进程要和其他进程通信是要通过计算机端口的,而一个计算机端口某个时刻只能被一个进程占用,所以通过指定源端口和目标端口,就可以知道是哪两个进程需要通信。源端口、目标端口是用16位表示的,可推算计算机的端口个数为2^16个

序列号码(seq,32bit)

如果含有同步化旗标(SYN),则此为最初序列号;第一个数据比特的序列号为本序列加1。

如果没有同步化旗标(SYN),则此为第一个数据比特的序列码。

确认号码(ack,32bit):期望收到的数据的开始序列号,也即是接收端收到的数据的字节长度加1。

数据偏移(4bit):表示TCP报文段的首部长度,共4位,由于TCP首部包含一个长度可变的选项部分,需要指定这个TCP报文段到底有多长。它指出TCP报文段的数据起始处距离TCP报文段的起始处有多远。该字段的单位是32位(即4个字节为计算单位),4位二进制最大表示15,所以数据偏移也就是TCP首部最大60字节。

保留(3bit):置0

NS(ECN-nonce,ECN显式拥塞通知(Explicit Congestion Notification)):是对TCP的扩展,定义于RFC 3540第5节;ECN允许拥塞控制的端对端通知而避免丢包。ECN为一项可选功能,如果底层网络设备支持,则可能被启用ECN的两个端点使用。当有序数据包到达时,ECN-nonce接收器会维护随机数的和,并在每个确认中返回当前的随机数的和。在标记分组的情况下,接收机可能不知道一个或多个随机数值,在这种情况下,当计算总和时,接收端将忽略丢失的随机数值,并将ECN-Echo置位向发送端发出阻塞信号。

CWR(Congestion Window Reduced,减少拥塞窗口):当具有ECN功能的TCP发送方处于某种原因(由于重传超时、快速重传或相应ECN通知)减少其拥塞窗口时,TCP发送方会在第一个新数据的TCP头部中设置CWR标识后,减少其随后发送的报文。如果该报文在网络中被丢弃,则发送方的TCP将不得不再次减少拥塞窗口,并重新发送丢弃的数据包。(RFC 3168)

ECE(ECN-Echo):发送端在接收到ECE置位的TCP包时,发送端会将CWR进行置位,以确认接收到ECN-echo标识并对其做出反应。(RFC 3168)

(注:

在两端协商建立ECN关系时,发送方A将CWR与ECE同时置位,形成ECN SYN包并发送给接收方B;接收方B在接收到ECN SYN包时,只将ECE位置位,CWR不置位,形成ECN SYN-Ack包发送给发送方A。

在发生拥塞时,接收端B在接收到一组TCP包,计算ECN-nonce接收器中随机数总和,并与确认包中的随机数总和进行比较,如果两个数值不一样,则发送端A端判断发生拥塞,会将发送给发送端的第一个TCP包中ECE进行置位;在发送端A接收到这个ECE位被置位的数据包之后,它会将CWR进行置位,并发送给你接收端B;在接收端B接收到一个CWR置位的数据包,它会减少向发送端A的数据包发送。)

USG:标识紧急指针是否有效

ACK:标识确认序号是否有效

PSH:用来标识接收端应用程序立刻将数据从TCP缓冲区读取

RST:要求重新建立连接,我们把还有RST标识的报文称为复位报文段

SYN:发出建立连接,我们把还有SYN标识的报文称为同步报文段

FIN:通知对端,本段即将关闭,我们把含有FIN标识的报文称为结束报文段

窗口大小:占用16bit,表示从确认号开始,本报文的发送方可以接收的字数,即接收窗口大小,用于流量控制。窗口大小为滑动计算,由Window size value * Window size scaling factor(此值在三次握手阶段TCP选项Windowscale协商得到)得出此值。

校验和:占用16bit,由发送端填充,检验形式有CRC校验等,如果接收端校验不通过,则认为数据有问题,此处的校验和不光包含TCP首部,也包含TCP数据部分,以16位字进行计算所得,这是一个强制字段。

紧急指针:占用16bit,用来标识数据哪个部分有问题,只在USG位被置位时使用。

选项:最多40字节。每个选项的开始时1字节的kind字段,说明选项的类型

0:选项表结束(1字节)

1:无操作(1字节),用于选项字段之间的字边界对齐

2:最大报文长度(4字节,Maximum Segment Size,MSS)通常在创建连接而设置SYN标识的数据包中指明这个选项,指明本段所能接受的最大的报文段。通常将MSS设置为(MTU-40)字节,携带TCP报文段的IP数据包的长度就不会超过MTU(MTU最大长度为1518字节,最短位为64字节),从而避免本机发生IP分片,只能出现在同步报文段中,否则将被忽略。

MTU和MSS值的关系:MTU=MSS+IP Header+TCP Header

通信双方最终的MSS值=较小MTU-IP Header-TCP Header

3:窗口扩大因子(4字节,wscale),取值0-14.用来把TCP的窗口的值左移的位数,使窗口值乘倍。只能出现在同步报文段中,否则将被忽略。这是因为现在的TCP接收数据缓冲区(接收窗口)的长度通常大于65535字节。

4:sackOK,发送端支持并同意使用SACK选项

5:SACK实际工作的选项

6:时间戳(10字节,TCP Timestamps Option,TSopt)

发送端的时间戳(Timestamp Value field,TSval,4字节)

时间戳回显应答(Timestamp Echo Reply field,TSecr,4字节)

 

2、连接管理机制

正常情况下,TCP需要经过三次握手建立连接,四次挥手端口连接。

2.1TCP建立连接

三次握手过程,如下图:

详细经过:

刚开始,客户端与服务器都处于CLOSED状态;此时,客户端向服务器主动发出连接,服务器被动接收连接:

1)TCP服务器进程先创建传输控制模块TCB,时刻准备接收客户端进程的连接,此时服务器就进入了LISTEN(监听)状态

2)TCP客户端进程也是先创建传输控制模块TCB,然后向服务器发出连接报文,此时报文首部中的同步表示位SYN=1,同时选择一个初始序列号seq=x,此时,TCP客户端进程进入SYN-SENT(同步已发送)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。

3)TCP服务器收到连接报文后,如果同意连接,则发出确认报文。确认报文中的ACK=1,SYN=1,确认序号是x+1,同时选择一个初始序列号seq=x,此时,TCP服务器进程进入SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但同样要消耗一个序号。

4)在TCP客户端进程收到确认包后,还要向服务器给出确认收到SYN-ACK的确认包。确认报文的AC=1,确认序列号=y+1,自己的序列号=x+1。

5)此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。当服务器收到客户端的确认后也进去ESTABLISHED状态,此后双方就可以进行数据传输。

 

问题一:为什么不用两次握手,建立连接?

答:假想一下,如果我们去掉第三次握手?因为我们不进行第三次握手,所以在服务端对客户端进行回应(第二次握手)后,就会理所当然的认为连接已建立,而如果客户端没有收到服务端的这次回应,那么,客户端会认为连接没有建立,但是服务端会对之前的连接保存一定的系统资源,如果出现大量的情况,那么服务端就会因为系统资源耗尽,而导致会崩溃。

 

问题二:为什么不是四次?

答:因为在TCP通过三次握手后,客户端和服务端至少可以确认之前的情况,但是无法确定之后的情况,基于此理论,无论4次还是5次都是无法确定的该报是否已经收到,所以即便再多的握手包也都是徒劳的。

 

2.2TCP断开连接

四次挥手过程,如下图:

模式一:

 

详细过程:

数据传输完毕后,双方都可以释放连接,此时客户端和服务器都处于ESTABLISHED状态,然后客户端主动断开连接,服务器被动断开连接。

1)客户端进程发出释放连接报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时客户端进入FIN-WAIT-1(终止等待1)状态。TCP规定,FIN报文段计是不携带数据,也要消耗一个序号。

2)服务器收到释放连接报文,发出确认报文,ACK=1,ack=u+1,并带上自己的序号seq=v,此时,服务端进入CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送,但是服务器若发送数据,客户端依然要接收。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。

3)客户端收到服务器的确认断开连接后,此时客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需接受服务器发送的最好的数据报文)。

4)服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号位seq=w,此时,服务器就进入LAST-ACK(最后确认),等待客户端的确认。

5)客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP还没有释放,必须经过2*MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。

6)服务器只要收到了客户端发出的确认,立即进入CLOSED状态,同时,撤销进程产生的TCB,这就结束了这次TCP的连接。服务端会比客户端提早结束这次TCP连接。

 

模式二

数据传输完毕后,双方都可以释放连接,此时客户端和服务器都处于ESTABLISHED状态,然后双方都主动断开。

1)两端同时向对端传送释放连接报文,并停止发送数据。释放数据报文首部,FIN=1,ACK=1,序列号分seq别是c与s,此时两端同时进入FIN-WAIT-1(种植等待1)状态。

2)在两端分别收到了对端发送自己的释放连接报文后,并发出确认报文。确认数据报文首部,ACK=1,ack分别时s+1和c+1,并携分别带自己的自己的序列号seq是c+1和s+1,并且自己的状态转换为CLOSING(双方同时尝试关闭,等待确认)状态。

3)在两端收到对端的确认报文之后,状态从CLOSING(双方同时尝试关闭,等待确认)状态,转变为TIME-WAIT(时间等待)状态,此时需要经过2*MSL(最长报文段寿命)的使时间后,双方分别撤销了相应的TCB后,双方才会进入CLOSED状态。

 

3、有限TCP状态机

TCP协议的操作可以使用11钟状态的有限状态机。

CLOSED:关闭状态,没有链接活动或正在进行

LISTEN:监听状态,服务器正在等待连接进入

SYN_SENT:已发出链接报文,等待确认。

SYN_RCVD:收到一个链接报文,并已发出确认链接报文,尚未确认对方是否已收到。

ESTABLISHED:链接建立,正常数据传输状态。

FIN_WAIT_1:(主动关闭)已发送关闭连接报文,等待确认

FIN_WAIT_2:(主动关闭)收到对方关闭确认,等到对方关闭连接报文

TIMED_WAIT:完成双向关闭,等待2*MSL(最长报文时间)

CLOSING:在发出FIN后,又收到对方发送给的FIN后,进入等待对方对己方的连接终止(FIN)的确认(ACK)的状态。

CLOSE_WAIT:(被动关闭)收到对方关闭请求,已经确认

LAST_ACK:(被动关闭)等待最后一个关闭确认,并等待所有分组死掉

 

4、客户端得典型状态转移

1、客户端通过connect系统调用主动与服务器建立连接connect系统调用首先给服务器发送一个同步报文段,使连接转移到SYN_SENT状态

此后connect系统调用可能因为如下两个原因失败返回:

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

2)如果目标端口存在,但connect在超时时间内未收到服务器的确认报文段,则connect调用失败。

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

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

4、客户端从FIN_WAIT_1状态可能直接进入TIME_WAIT状态(不经过FIN_WAIT_2状态),前提是处于FIN_WAIT_1状态的服务器直接收到带确认信息的结束报文段(而不是先收到确认报文段,再收到结束报文段)。

5、处于FIN_WAIT_2状态的客户端需要等待服务器发送结束报文段,才能转移至TIME_WAIT状态,否则它将一直停留在这个状态。如果不是为了在半关闭状态下继续接收数据,连接长时间地停留在FIN_WAIT_2状态并无益处。连接停留在FIN_WAIT_2状态的情况可能发生在:客户端执行半关闭后,未等服务器关闭连接就强行退出了。此时客户端连接由内核来接管,可称之为孤儿连接(和孤儿进程类似)

 

5、TCP重传超时

1)异常网络状况下(开始出现超时或丢包),TCP控制数据传输以保证其承诺的可靠服务;

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

3)与TCP超时重传相关的两个内核参数:

/proc/sys/net/ipv4/tcp_retries1,指定在底层IP接管之前TCP最少执行的重传次数,默认值是3

/proc/sys/net/ipv4/tcp_retries2,指定连接放弃前TCP最多可以执行的重传次数,默认值15(一般对应13~30min)

 

6、拥塞控制

1)网络中的带宽、交换结点中的缓存和处理机等,都是网络的资源。在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可承受的能力,网络的性能就会变坏。此情况称为拥塞。

2)TCP为提高网络利用率,降低丢包率,并保证网络资源对每条数据流的公平性。即所谓的拥塞控制。

3)TCP拥塞控制的标准文档是RFC 5681,其中详细介绍了拥塞控制的四个部分:慢启动(slow start)、拥塞避免(congestion avoidance)、快速重传(fast retransmit)和快速恢复(fast recovery)。拥塞控制算法在Linux下有多种实现,比如reno算法、vegas算法和cubic算法等。它们或者部分或者全部实现了上述四个部分。

 4)当前所使用的拥塞控制算法

/proc/sys/net/ipv4/tcp_congestion_control

 

7、KeepAlive包

        TCP的KeepAlive是侧重于保持客户端和服务器的连接,一方会定期发送心跳包给另一方,当一方断掉的时候,没有断掉的定时发送几个心跳包,如果间隔发送几次,对方都返回的RST,而不是ACK,那么就释放当前连接。假想一下,如果TCP层没有keepAlive的机制,当一方断开连接却没有发送FIN给另外一方,那么另外一方会认为这个连接还是存在的,,类似的连接一多,时间一长,那么这对服务器资源是一种很大的影响。

7.2 为什么要有keepAlive?

        首先明确,在TCP是没有“请求”一说。TCP是一种通信的方式,“请求”一词是事务上的概念。在TCP连接建立之后,如果应用程序或者承载在TCP上面的协议一直不发送数据,或者隔很长时间才发送一次数据,当链接很久没有数据报文传输时,如何确定对方还在线?对方到底掉线了还是没有数据传输,链接还需不需要保持?这种情况在TCP协议设计中时需要考虑的。

       TCP协议通过一种巧妙的方式去解决这个问题,当没有传输数据,超时一段时间后,TCP自动发送一个数据为全0的1字节的报文,如果对方回应了这个报文,说明对方在线,TCP链接就可以继续保持,如果对方没有报文回应,并且重试了多次之后,则本端认为链接丢失,没有必要继续保持链接。

7.3 怎么开启KeepAlive?

在Linux上,默认不开启KeepAlive,并且没有一个全局的选项去开启TCP的KeepAlive。需要开启KeepAlive的应用必须在TCP的socket中单独开启。Linux Kernel有三个选项影响到KeepAlive的行为:

在/proc/sys/net/ipv4/目录下:

tcp_keepalive_time 7200 #距离上次传送数多少时间未收到新报文判断为开始检测,单位秒,默认7200s

tcp_keepalive_intvl 75 #检测开始多长时间发送心跳包,单位秒,默认75s

tcp_keepalive_probes 9 #发送几次心跳包对方没有回应则close链接,默认9次

TCP socket也有三个选项和内核对应,通过setsockopt系统调用针对单独的socket进行设置

TCPKEEPCNT:覆盖tcp_keepalive_probes

TCPKEEPDLE:覆盖tcp_keepalive_time

TCPKEEPTVL:覆盖tcp_keepalive_intvl

举个例子,如果我的系统默认keepalive设置,如果我在应用程序中针对socket开启了keepalive,然后设置的TCP_KEEPIDLE为60,那么TCP协议栈在发现TCP链接空闲了60s没有传输数据,那么系统就会发送第一个KeepAlive探测报文。

7.4 KeepAlive的不足

KeepAlive只能检测链接是否存活,不能检测链接是否可用。例如,某一方发生了死锁,无法在链接上进行任何读写操作,但是操作系统仍然可以相应网络的KeepAlive的探测包。

TCP KeepAlive机制依赖于操作系统的实现,灵活性不足,并且默认关闭。且默认的KeepAlive心跳时间是2个小时,时间较长。并且,代理或者负载均衡,会让TCP KeepAlive失效。

 

8、基本TCP调优参数

1)Linux为了防止孤儿连接长时间存留在内核中,定义了两个内核参数:

/proc/sys/net/ipv4/tcp_max_orphans 指定内核能接管的孤儿连接数目

/proc/sys/net/ipv4/tcp_fin_timeout 指定孤儿连接在内核中生存的时间

 2)Linux为了保证tcp连接得一定数量,定义了两个内核参数:

/proc/sys/net/ipv4/tcp_max_syn_backlog 未完成连接队列大小,建议调整大小为1024以上

/proc/sys/net/core/somaxconn 完成连接队列大小,建议调整大小为1024以上

 

原文出处:https://www.cnblogs.com/ColoDu/p/12343507.html

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部