您的位置:首页 > 理论基础 > 计算机网络

linux高性能服务器编程学习笔记三:TCP协议详解

2017-11-28 22:57 211 查看
1、和IP协议相比,TCP协议处于传输层,更靠近应用层,因此在应用程序中具有更强的可操作性。
2、一般来说,TCP主要处理端到端的通信,因此头部信息主要包括源端口号,目的端口号。TCP协议是面向连接、面向字节流和可靠传输的协议。因此头部还需要一些字段来管理TCP连接以及控制两个方向的数据流。
3、TCP服务的特点:使用TCP协议通信必须先建立连接,然后才能开始数据的读写。并且通信双方都需要在内核维护必要的数据结构和资源用于管理连接的状态和保证数据的传输。TCP是全双工的,也就是通信双方只需在一个连接上传输数据即可。完成数据的交换之后,还需关闭连接以释放资源。TCP协议是一对一的,也就是广播和多播并不能使用TCP服务,UDP则非常适合于广播和多播。

4、TCP协议面向字节流的概念:使用TCP协议的时候,应用程序连续多次执行写操作时,TCP模块先将这些数据拷贝到TCP发送缓冲区中。当TCP模块真正发送数据时,可能会对处于发送缓冲区的数据进行封装(封装成一个或多个)成TCP报文段再发出。也就是应用程序执行写操作的次数和TCP模块发出的TCP报文段的个数并无固定关系。同理,接收端的TCP模块接收到TCP报文段并按照TCP报文段的序号放入接收缓冲区后,通知应用程序读取数据,应用程序读取数据的次数可能是一次,也可能是多次,取决于应用程序设置的读数据缓冲区的大小。因此TCP模块接收TCP报文段的个数和应用程序读取数据的次数并无固定关系。应用程序对数据的发送和接收是没有边界概念的,这就是面向字节流的含义。
5、UDP面向报文数据报的概念:发送端应用程序每执行一次写操作,UDP模块就必须将其封装成一个UDP数据报并发送之。接收端的UDP模块也必须及时的接收到这个UDP数据报并且通知应用程序针对每一个UDP数据报进行读操作(recvfrom系统调用),否则可能会丢包。这里要注意,假如接收端的应用程序没有足够大的读缓冲区,可能造成UDP数据被截断。



6、TCP传输可靠主要是因为发送应答机制和超时重传机制这两个机制保证。发送应答机制就是每发送一个TCP报文段就必须收到接收方的应答,才认为这个TCP报文段传输成功。超时重传是每次传输一个TCP报文段都会开启定时功能,假如在规定时间内没有收到接收方对这个TCP报文段的应答,则重新发送该报文段。同时,由于TCP报文段最终是以IP数据报发送的,因此到达接收方时是乱序、重复的,所以TCP协议还会对接收到的TCP报文段进行重排、整理,再交付给应用层。
7、TCP固定头部结构



这里重点讲解以下几个字段:
(1)6位标志位:
   URG标志:表示紧急指针是否有效
   ACK标志:表示确认号是否有效。一般称携带ACK标志的TCP报文段为确认报文段
   PSH标志:提示接收端的应用程序赶紧读取TCP接收缓冲区中的数据,以腾出空间为后续数据准备
   RST标志:表示要求对方重新建立连接。一般称携带RST标志的TCP报文段为复位报文段
   SYN标志:表示请求建立一个连接。一般称携带SYN标志的TCP报文段为同步报文段。
   FIN标志:表示通知对方本端要关闭连接了。一般称携带FIN标志的TCP报文段为结束报文段。
(2)16位窗口大小字段:由发送端填充,告诉对方本段的接收缓冲区还能接收多少字节的数据。这是一种流量控制手段。这样对方就可以控制发送数据的速度
(3)16位校验和:由发送端填充,与IP数据包不同的是,TCP报文段的检验和是检验整个报文段(包括TCP头部),这也是TCP可靠传输的一个保障。
(4)16位紧急指针字段:是一个正的偏移量,它和序号字段的值相加就是最后一个紧急数据的下一个字节的序号。因此又称为紧急偏移,用于发送紧急数据的方法
8、TCP头部选项
(1)TCP头部的选项字段最多有40字节。典型的TCP头部选项结构见:



Kind字段表明选项的类型。第二个字段length指定该选项的总长度。第三个字段info是选项的具体信息。
(2)常见的TCP选项有7种,见下图:



kind=1是空操作选项,一般用于将TCP选项的总长度设置为4的倍数。
   Kind=2是最大报文段长度选项。TCP连接初始化时,通信双方使用该选项来协商最大报文段的数据部分长度(MSS)。TCP模块一般将其设置为(MTU-40)字节(减掉20字节的TCP头部和20字节的IP头部)。这样携带TCP报文段的IP数据报的长度就不会超过MTU而避免本机对其进行分片(假如TCP和IP都不包含选项字段)。
   Kind=3表示窗口扩大因子选项。TCP连接初始化时,通信双方使用该选项协商接收通告窗口的扩大因子。在TCP的头部当中,接收通告窗口为16位,那么最大为65535字节,但实际上TCP允许的最大接收窗口远不止这个数,这就需要窗口扩大因子来解决此问题。假设TCP接收通告窗口大小为N,窗口扩大因子为M,则TCP报文段实际的接收通告窗口大小为N*2M(左移M位)。M的取值范围为0-14。可以在/proc/sys/net/ipv4/tcp_window_scaling内核变量来启用或关闭窗口扩大因子。注意!和MSS选项一样,窗口扩大因子只能出现在同步(携带SYN标志)报文段中,否则被忽略。
   Kind=4很重要!表示是否支持选择性确认(SACK)选项。TCP通信时,假如某个TCP报文段丢失,则TCP模块会重传最后被确认的TCP报文段的后续的所有报文段。但实际上有些不连续的报文段已经在接收端的缓冲区当中,这样重传所有后续报文段会降低TCP性能。SACK技术就是为改善这种情况而产生。选择性确认同样是用在连接初始化时。可以通过修改/proc/sys/net/ipbv/tcp_sack内核变量来启用或关闭选择性确认选项
   Kind=5是sack实际工作的选项。该选项的参数告诉发送方本端已经接收到并缓存的不连续的数据块,从而让发送端以此检查并重传丢失的数据块。每个块边沿参数包含一个4字节的序号,左边沿表示不连续块第一个数据的序号,右边沿表示不连续块的最后一个数据的序号的下一个序号,这一对参数之间的数据就是没有收到的。因为选项减去前两个字节(kind和length)。最长为38个字节。而一个完整的块(包括左边沿块和右边沿块)为8个字节。那么最多可以存储4个这样的不连续的数据块。
   Kind=8是时间戳选项。该选项提供了准确的计算通信双方之间的回路时间(RTT)的方法。从而为TCP流量控制提供了重要的信息。可以通过修改/proc/sys/net/ipv4/tcp_timestamps内核变量来启用或关闭时间戳选项。
9、连接超时
(1)假如在连接的时候,由于网络堵塞或者是访问一个距离特别远的服务器,导致服务器对于客户端发出的同步报文段(连接请求)没有应答,会尝试多重连几次,到达一定次数会通知应用程序连接超时
(2)重连的报文段一般时间间隔是1s、2s、4s、8s、16s、32s。在5次重连均失败的情况下,TCP模块放弃连接并通知应用程序。相关次数操作可以改变/proc/sys/net/ipv4/tcp_syn_retries内核变量。
10、半关闭状态:TCP连接是全双工的,这不仅代表通信双方可以在一条连接上来回通信,也表明通信双方可以独立关闭连接。首先发送结束报文段的一方会使的整个TCP连接处于半连接状态,也就是说先关闭连接的一方是在告诉对方我不会再发送数据了,但是我可以接收数据直到你也关闭连接。
11、服务器和客户端判断对方是否已经关闭连接的方法是:read系统调用返回0(收到结束报文段)。
12、TCP状态转移:
(1)先说服务器的状态转移:首先通过listen系统调用处于监听状态,被动等待对方的连接请求(被动打开)。服务器一旦监听到连接请求就将该连接放入内核的等待队列当中,并向对端发送带SYN的确认报文段,此时该连接处于SYN_RCVD状态。然后再此收到客户端发过来的确认报文段,此时处于ESTABLISHED状态,这个状态意味着双方的连接建立完毕。当客户端关闭连接并发送带FIN的结束报文段,服务器通过返回确认报文段而进入CLOSE_WAIT状态,此状态表明等待服务器应用程序关闭连接。当服务器应用程序关闭连接之后,该连接处于LAST_ACK状态,此状态表明等待确认客户端发送的最后一次结束报文段。一旦确认完成,连接就彻底关闭了。
(2)客户端的状态转移:客户端通过系统调用connect发起连接请求,给服务端发送一个同步报文段(注意服务器会回复一个带SYN的确认报文段)此时连接处于SYN_SENT状态。这里注意一下connect系统调用可能会因为这几个原因失败:服务端的端口不存在。或者该端口仍被处于TIME_WAIT状态的连接所占用,这两种情况,服务器会给客户端发送一个复位报文段告诉客户端应该将连接断开或者重连。还有一种情况就是端口存在,但是connect在超时时间内没有收到服务器带SYN的确认报文段,则connect调用失败。Connect调用失败会使连接立即返回CLOSED状态。如果客户端收到服务端带SYN的确认报文段,则表明connect调用成功,然后再次发送确认报文段,此时连接处于ESTABLISHED状态。当客户端向服务器发送结束报文段,连接处于FIN_WAIT_1状态。若又收到了服务端对此报文的确认,则进入FIN_WAIT_2状态。(如果客户端处于FIN_WAIT_2状态,服务器处于CLOSE_WAIT状态,此时应该处于半关闭状态)。然后服务器发送结束报文段,则客户端给予确认并进入TIME_WAIT状态。这里注意一下,有些意外状态会迫使客户端长期处于FIN_WAIT_2状态:客户端执行关闭之后,假如并不等服务器关闭就立即强行退出。此时客户端连接会由内核来管理,此时的连接可以称为孤儿连接(类似于孤儿进程)。Linux为了防止孤儿连接长时间留在内核中,定义了两个内个变量/proc/sys/net/ipv4/tcp_max_orphans以及/proc/sys/net/ipv4/tcp_fin_timeout。前者规定内核接管的最大的孤儿连接数目,后者规定孤儿连接在内核中生存的时间。
13、TIME_WAIT状态存在的原因:
(1)保证TCP连接能够可靠的终止
(2)保证让迟来的报文或者还在网络流中的报文能够有足够的时间被丢弃或者在网络流中消逝。
第一个原因是,假如用于确认服务器结束报文段的TCP报文段丢失了,那么服务器会重发结束报文段,假如没有TIME_WAIT这个状态让客户端有足够的时间处理重复收到的结束报文段(也就是再次发送确认报文段),那么客户端会立刻发送一个复位报文段给服务器,服务器会认为这是一个错误。第二个原因是一个TCP端口任意时刻只能用于一个TCP连接。当存在TIME_WAIT状态时,那么可以让整个连接可靠的结束而防止端口被再次利用(因为端口在连接完整关闭以前仍然被占用着)。假如没有TIME_WAIT状态,则应用程序能够以此TCP端口建立一个新连接,假如在原先的旧连接仍然有数据或TCP报文段,将会被新的连接接收到,这显然是错误的。当然我们可以通过socket选项的SO_REUSEADDR来强制进程使用处于TIME_WAIT状态连接的端口。
让TIME_WAIT坚持2MSL(MSL:报文段最大生存时间),是为了确保双向尚未被接收到的,迟到的报文能够消失。
14、复位报文段
在以下几种情况下,TCP连接的一端会向另一端发送携带RST标志的报文段,即复位报文段。
(1)访问不存在的端口,或者向服务器的某个端口发起连接,但该端口处于TIME_WAIT状态
(2)异常终止连接,即给对方发送一个复位报文段。(一旦发送了复位报文段,发送端所有排队等待发送的数据都将被丢弃),可以利用socket选项SO_LINGER来发送复位报文段
(3)考虑这样一个情况,客户端(服务器)关闭或异常终止了连接,而对端没有接收到结束报文段,此时,服务器(客户端)还维持者原来的连接,而客户端(服务器)即使重启,也没有该连接的信息,这种情况称之为半打开状态,假如服务器(客户端)往半打开连接发送数据,对方会回应一个复位报文段。
15、TCP超时重传
(1)TCP服务必须在重传时间内未收到重复确认的TCP报文段,为此TCP模块为每个TCP报文段都维护一个重传定时器,在每一次发送TCP报文段都会启动。
(2)每一次需要重传,其时间间隔都增加一倍,一般来说最多重传5次,在5次均重传失败之后,底层的IP和ARP开始接管,直到telnet客户端放弃连接为止。
(3)linux的内核变量:/proc/sys/net/ipv4/tcp_retries1和/proc/sys/net/ipv4/tcp_retries2,前者指定最少的重传次数,默认为3,后者指定放弃连接前TCP最多可以执行的重传次数。
16、拥塞控制
(1)一般原理:发生拥塞控制的原因:资源(带宽、交换节点的缓存、处理机)的需求>可用资源。作用:拥塞控制就是为了防止过多的数据注入到网络中,这样可以使网络中的路由器或者链路不至于过载。拥塞控制要做的都有一个前提:就是网络能够承受现有的网络负荷。

对比流量控制:拥塞控制是一个全局的过程,涉及到所有的主机、路由器、以及降低网络相关的所有因素。流量控制往往指点对点通信量的控制。是端对端的问题。

(2)流量控制:设主机A向主机B发送数据。双方确定的窗口值是400.再设每一个报文段为100字节长,序号的初始值为seq=1,大写ACK表示首部中的却认为为ACK,小写ack表示确认字段的值。 

接收方的主机B进行了三次流量控制。第一次把窗口设置为rwind=300,第二次减小到rwind=100最后减到rwind=0,即不允许发送方再发送过数据了。这种使发送方暂停发送的状态将持续到主机B重新发出一个新的窗口值为止。 

假如,B向A发送了零窗口的报文段后不久,B的接收缓存又有了一些存储空间。于是B向A发送了rwind=400的报文段,然而这个报文段在传送中丢失了。A一直等待收到B发送的非零窗口的通知,而B也一直等待A发送的数据。这样就死锁了。为了解决这种死锁状态,TCP为每个连接设有一个持续计时器。只要TCP连接的一方收到对方的零窗口通知,就启动持续计时器,若持续计时器设置的时间到期,就发送一个零窗口探测报文段(仅携带1字节的数据),而对方就在确认这个探测报文段时给出了现在的窗口值。

(2)主要有四个部分:慢启动、拥塞避免、快重传、快恢复
慢开始和拥塞避免 

发送报文段速率的确定,既要根据接收端的接收能力,又要从全局考虑不要使网络发生拥塞,这由接收窗口和拥塞窗口两个状态量确定。接收端窗口(Reciver Window)又称通知窗口(Advertised Window),是接收端根据目前的接收缓存大小所许诺的最新窗口值,是来自接收端的流量控制。拥塞窗口cwnd(Congestion Window)是发送端根据自己估计的网络拥塞程度而设置的窗口值,是来自发送端的流量控制。一般来说,发送数据为拥塞窗口和通知窗口当中的较小值。

1)当主机开始发送数据时,如果立即将较大的发送窗口的全部数据字节都注入到网络中,那么由于不清楚网络的情况,有可能引其网络拥塞 

2)比较好的方法是试探一下,即从小到达逐渐增大发送端的拥塞控制窗口数值 

3)通常在刚刚开始发送报文段时可先将拥塞窗口cwnd设置为一个最大报文段的MSS的数值。在每收到一个对新报文段确认后,将拥塞窗口增加至多一个MSS的数值,当rwind足够大的时候,为了防止拥塞窗口cwind的增长引起网络拥塞,还需要另外一个变量—慢开始门限ssthresh 。

拥塞控制具体过程为:

1)TCP连接初始化,将拥塞窗口设置为1 

2)执行慢开始算法,cwind按指数规律增长,知道cwind == ssthress开始执行拥塞避免算法,cwnd按线性规律增长 

3)当网络发生拥塞,把ssthresh值更新为拥塞前ssthresh值的一半,cwnd重新设置为1,按照步骤(2)执行。 

快重传和快恢复 

一条TCP连接有时会因等待重传计时器的超时而空闲较长的时间,慢开始和拥塞避免无法很好的解决这类问题,因此提出了快重传和快恢复的拥塞控制方法。 

快重传算法并非取消了重传机制,只是在某些情况下更早的重传丢失的报文段(如果当发送端接收到三个重复的确认ACK时,则断定分组丢失,立即重传丢失的报文段,而不必等待重传计时器超时)。慢开始算法只是在TCP建立时才使用。 

快恢复算法有以下两个要点: 

1)当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把慢开始门限减半,这是为了预防网络发生拥塞。 

2)由于发送方现在认为网络很可能没有发生拥塞,因此现在不执行慢开始算法,而是把cwnd值设置为慢开始门限减半后的值,然后开始执行拥塞避免算法,使拥塞窗口的线性增大。

总结: 

没有发生拥塞之前使用前两种算法,发生拥塞之后(即三次重复确认)使用后两种算法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: