linux高性能服务器tcp实现(3)

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 linux高性能服务器tcp实现(3),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

tcp重点:

TCP头部信息:TCP头部信息出现在每个TCP报文段中,用于指定通信的源端端口号、目的端端口号,管理TCP连接,控制两个方向的数据流。
TCP状态转移过程:TCP连接的任意一端都是一个状态机。在TCP连接从建立到断开的整个过程中,连接两端的状态机将经历不同的状态变迁。理解TCP状态转移对于调试网络应用程序将有很大的帮助。
TCP数据流:通过分析TCP数据流,我们就可以从网络应用程序外部来了解应用层协议和通信双方交换的应用程序数据。这一部分将讨论两种类型的TCP数据流﹔交互数据流和成块数据流。TCP数据流中有一种特殊的数据,称为紧急数据,我们也将简单讨论之。
TCP数据流的控制:为了保证可靠传输和提高网络通信质量,内核需要对TCP数据流进行控制。特别是超时重传和拥塞控制

tcp服务

使用TCP协议通信的双方必须先建立连接,然后才能开始数据的读写。双方都必须为该连接分配必要的内核资源,以管理连接的状态和连接上数据的传输。TCP连接是全双工的,即双方的数据读写可以通过一个连接进行。完成数据交换之后,通信双方都必须断开连接以释放系统资源。强调是一对一
UDP使用广播和多播(目标是多个主机地址);

tcp字节流服务
TCP传输是可靠的。首先,TCP协议采用发送应答机制,即发送端发送的每个TCP报文段都必须得到接收方的应答,才认为这个TCP报文段传输成功。其次,TCP协议采用超时重传机制,发送端在发送出一个TCP报文段之后启动定时器,如果在定时时间内未收到!答,它将重发该报文段。最后,因为TCP报文段最终是以P数据报发送的,而IP数据报达接收端可能乱序、重复,所以TCP协议还会对接收到的TCP报文段重排、整理,再交给应用层。
在这里插入图片描述
UDP模块就将其封装成一个UDP数据报并发送之。接收端必须及时针对每一个UDP数据报执行读操作(通过recvfrom系统调用),否则就会丢包(这经常发生在较慢的服务器上)。并且,如果用户没有指定足够的应用程序缓冲区来读取UDP数据,则UDP数据将被截断。

TCP的头部结构

TCP的固定结构 加粗样式在这里插入图片描述

16位端口号(port number):告知主机该报文段是来自哪里(源端口)以及传给哪个上层协议或应用程序(目的端口)的。进行TCP通信时,客户端通常使用系统自动选择的临时端口号,而服务器则使用知名服务端口号。所有知名服务使用的端口号都定义在/etclservices文件中。
32位序号( sequence number):一次TCP通信(从TCP连接建立到断开)过程中某一个传输方向上的字节流的每个字节的编号。假设主机A和主机B进行TCP通信,A发送给B的第一个TCP报文段中,序号值被系统初始化为某个随机值ISN (Initial SequenceNumber,初始序号值)。那么在该传输方向上(从A到B),后续的TCP报文段中序号值将被系统设置成ISN加上该报文段所携带数据的第一个字节在整个字节流中的偏移。例如,某个TCP报文段传送的数据是字节流中的第1025~2048字节,那么该报文段的序号值就是ISN+1025。另外一个传输方向(从B到A)的TCP报文段的序号值也具有相同的含义。
4位头部长度(header length):标识该TCP头部有多少个32bit字(4字节)。因为4位最大能表示15,所以 TCP头部最长是60字节。
6位标志位包含如下几项:

 (1)URG标志,表示紧急指针(urgent pointer)是否有效。
 (2)ACK标志,表示确认号是否有效。我们称携带ACK标志的TCP报文段为确认报文段。
 (3)PSH标志,提示接收端应用程序应该立即从TCP接收缓冲区中读走数据,为接收后续数据腾出空间(如果应用程序不将接收到的数据读走,它们就会一-直停 留在TCP接收缓冲区中)。
 (4)RST标志,表示要求对方重新建立连接。我们称携带RST标志的TCP报文段为复位报文段。
 (5)SYN标志,表示请求建立- -个连接。我们称携带SYN标志的TCP报文段为同步报文段。
 (6)FIN标志,表示通知对方本端要关闭连接了。我们称携带FIN标志的TCP报文段为结束报文段。
 
 

16位窗口大小( window size):是TCP流量控制的一个手段。这里说的窗口,指的是接收通告窗口(Receiver Window,RWND)。它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。
**16位校验和(TCP checksum)😗*由发送端填充,接收端对TCP报文段执行CRC算法以检验TCP报文段在传输过程中是否损坏。注意,这个校验不仅包括TCP头部,也包括数据部分。这也是TCP可靠传输的一个重要保障。
16位紧急指针(urgent pointer):是一个正的偏移量。它和序号字段的值相加表示最后一个紧急数据的下一字节的序号。因此,确切地说,这个字段是紧急指针相对当前序号的偏移,不妨称之为紧急偏移。TCP的紧急指针是发送端向接收端发送紧急数据的方法。我们将在后面讨论TCP紧急数据。

TCP头部选项

在这里插入图片描述

选项的第一个字段kind 说明选项的类型。有的TCP选项没有后面两个字段,仅包含1字节的kind字段。第二个字段length(如果有的话)指定该选项的总长度,该长度包括kind字段和 length字段占据的2字节。第三个字段info(如果有的话〉是选项的具体信息。
在这里插入图片描述
kind=0->结束
kind=1->nop选项
kind-2是最大报文段长度选项。TCP连接初始化时,通信双方使用该选项来协商最大报文段长度〈Max Segment Size,MSS)。TCP模块通常将MSS设置为(MTU-40)字节(减掉的这40字节包括20字节的TCP头部和20字节的IP头部)。这样携带TCP报文段的IP数据报的长度就不会超过MTU(假设TCP头部和IP头部都不包含选项字段,并且这也是一般情况),从而避免本机发生IP分片。对以太网而言,MSS值是1460 (1500-40)字节。
kind=3是窗口扩大因子选项。TCP连接初始化时,通信双方使用该选项来协商接收通告窗口的扩大因子。在TCP的头部中,接收通告窗口大小是用16位表示的,故最大为65 535字节,但实际上TCP模块允许的接收通告窗口大小远不止这个数(为了提高TCP通信的吞吐量)。窗口扩大因子解决了这个问题。假设TCP头部中的接收通告窗口大小是N,窗口扩大因子(移位数〉是M,那么TCP报文段的实际接收通告窗口大小是N乘2*m,或者说N左移M位。注意,M的取值范围是0~~14。我们可以通过proc/sys/net/ipv4/tcp_window_scaling 内核变量来启用或关闭窗口扩大因子选项。和MSS选项一样,窗口扩大因子选项只能出现在同步报文段中,否则将被忽略。但同步报文段本身不执行窗口扩大操作,即同步报文段头部的接收通告窗口大小就是该TCP报文段的实际接收通告窗口大小。当连接建立好之后,每个数据传输方向的窗口扩大因子就固定不变了。关于窗口扩大因子选项的细节,可参考标准文档RFC 1323。
kind=4是选择性确认(Selective Acknowledgment,SACK)选项。TCP通信时,如果某个TCP报文段丢失,则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字节)。

tcp状态转移

在这里插入图片描述
服务器通过listen系统调用(见第5章)进人LISTEN状态,被动等待客户端连接,因此执行的是所谓的被动打开。服务器一旦监听到某个连接请求(收到同步报文段),就将该连接放入内核等待队列中,并向客户端发送带SYN标志的确认报文段。此时该连接处于SYN_RCVD状态。如果服务器成功地接收到客户端发送回的确认报文段,则该连接转移到E**STABLISHED状态。ESTABLISHED状态是连接双方能够进行双向数据传输的状态。
当客户端主动关闭连接时(通过close或shutdown系统调用向服务器发送结束报文段),服务器通过返回确认报文段使连接进入CLOSE_WAIT状态。这个状态的含义很明确﹔等待服务器应用程序关闭连接。通常,服务器检测到客户端关闭连接后,也会立即给客户端发送一个结束报文段来关闭连接。这将使连接转移到LAST_ACK状态,以等待客户端对结束报文段的最

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

图中还给出了客户端从FIN_WAIT_1状态直接进人TIME_WAIT状态的一条线路(不经过FIN_WAIT_2状态),前提是处于FIN_WAIT_1状态的服务器直接收到带确认信息的结束报文段(而不是先收到确认报文段,再收到结束报文段)。这种情况对应于图3-6中的服务器不发送TCP报文段5。后一次确认。一旦确认完成,连接就彻底关闭了。

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

表现图
在这里插入图片描述

TIME_WAIT

存在原因:
可靠地终止 TCP连接。

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

保证让迟来的TCP报文段有足够的时间被识别并丢弃。

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

复位报文段

前面讨论的连接终止方式都是正常的终止方式:数据交换完成之后,一方给另一方发送结束报文段。TCP提供了异常终止一个连接的方法,即给对方发送一个复位报文段。一旦发送了复位报文段,发送端所有排队等待发送的数据都将被丢弃。
应用程序可以使用socket选项SO_LINGER来发送复位报文段,以异常终止一个连接。我们将在第5章讨论sO_LINGER选项。

带外数据

有些传输层协议具有带外(Out Of Band,OOB)数据的概念,用于迅速通告对方本端发生的重要事件。因此,带外数据比普通数据(也称为带内数据〉有更高的优先级,它应该总是立即被发送,而不论发送缓冲区中是否有排队等待发送的普通数据。带外数据的传输可以使用-条独立的传输层连接,也可以映射到传输普通数据的连接中。实际应用中,带外数据的使用很少见,已知的仅有telnet、ftp等远程非活跃程序)
UDP没有实现带外数据传输,TCP也没有真正的带外数据。不过TCP利用其头部中的紧急指针标志和紧急指针两个字段,给应用程序提供了一种紧急方式。TCP的紧急方式利用传输普通数据的连接来传输紧急数据。这种紧急数据的含义和带外数据类似,因此后文也将TCP紧急数据称为带外数据。
在这里插入图片描述
只有最后一个字节才能被当成紧急数据
现在考虑TCP接收带外数据的过程。TCP接收端只有在接收到紧急指针标志时才检查紧急指针,然后根据紧急指针所指的位置确定带外数据的位置,并将它读入一个特殊的缓存中。这个缓存只有1字节,称为带外缓存。如果上层应用程序没有及时将带外数据从带外缓存中读出,则后续的带外数据(如果有的话)将覆盖它。

拥塞控制

TCP拥塞控制的标准文档是RFC 5681,其中详细介绍了拥塞控制的四个部分﹔慢启动(slow start)、拥塞避免(congestion avoidance)、快速重传(fast retransmit)和快速恢复(fastrecovery)。拥塞控制算法在 Linux下有多种实现,比如reno算法、vegas算法和cubic算法等。它们或者部分或者全部实现了上述四个部分。/proc/sys/net/ipv4/tcp_congestion_control文件指示机器当前所使用的拥塞控制算法。
拥塞控制的最终受控变量是发送端向网络一次连续写人(收到其中第一个数据的确认之前)的数据量,我们称为SWND (Send Window,发送窗口I)。不过,发送端最终以TCP报文段来发送数据,所以SWND限定了发送端能连续发送的TCP报文段数量。这些TCP报文段的最大长度(仅指数据部分)称为SMSs (Sender Maximum Segment Size,发送者最大段大小),其值一般等于MSS.
接收方可通过其接收通告窗口(RWND)来控制发送端的SWND。但这显然不够,所以发送端引入了一个称为拥塞窗口**(Congestion Window,CwND)的状态变量**。实际的SWND值是RWND和CWND中的较小者。图3-11显示了拥塞控制的输人和输出(可见,它是一个闭环反馈控制)。
在这里插入图片描述

慢启动与拥塞避免

TCP连接建立好之后,CWND将被设置成初始值IW(Initial Window),其大小为2~4个SMSS。但新的Linux内核提高了该初始值,以减小传输滞后。此时发送端最多能发送IW字节的数据。此后发送端每收到接收端的一个确认,其CWND就按照式增加
CWND+=min(N,SMSS)
其中N是此次确认中包含的之前未被确认的字节数。这样一来,CWND将按照指数形式扩大,这就是所谓的慢启动。慢启动算法的理由是,TCP模块刚开始发送数据时并不知道网络的实际情况,需要用一种试探的方式平滑地增加CWND的大小。
其中N是此次确认中包含的之前未被确认的字节数.这样一来,CWND将按照指数形式扩大,这就是所谓的慢启动.慢启动算法的理由是,TCP模块刚开始发送数据时并不知道网络的实际情况,需要用一种试探的方式平滑地增加CWND的大小。但是如果不施加其他手段,慢启动必然使得CWND很快膨胀(可见慢启动其实不慢)并最终导致网络拥塞。因此TCP拥塞控制中定义了另一个重要的状态变量:慢启动门限( slow start threshold size,ssthresh)。当CWND的大小超过该值时,TCP拥塞控制将进入拥塞避免阶段。
拥塞避免算法使得CWND按照线性方式增加,从而减缓其扩大。RFC 5681中提到了如下两种实现方式:
(1):每个RTT时间内按照上式计算新的CWND,而不论该RTT时间内发送端收到多少个确认。
(2):每收到一个对新数据的确认报文段,就按照式来更新CWND.
CMDN+=SMSS*SMSS/CWMD

在这里插入图片描述
判断出现拥塞发生的依据
(1)传输超时,或者说TCP重传定时器溢出。

如果发送端检测到拥塞发生是由于传输超时,即上述第-种情况,那么它将执行重传并做如下调整:
ssthresh=max(FlightSize/2,2*SMSS)
CWMD<=SMSS
其中 FlightSize是已经发送但未收到确认的字节数

(2)接收到重复的确认报文段。

快速重传以及快速恢复

在很多情况下,发送端都可能接收到重复的确认报文段,比如TCP报文段丢失,或者接收端收到乱序TCP报文段并重排之等。拥塞控制算法需要判断当收到重复的确认报文段时,网络是否真的发生了拥塞,或者说TCP报文段是否真的丢失了。具体做法是:发送端如果连续收到3个重复的确认报文段,就认为是拥塞发生了。然后它启用快速重传和快速恢复
(1)当收到第3个重复的确认报文段时,按照式下示计算ssthresh,然后立即重传丢失的报文段,并按照下2式设置CWND.
CWND=ssthresh+3*SMSS
(2)每次收到1个重复的确认时,设置CWND-CWND+SMSS。此时发送端可以发送新的TCP报文段(如果新的CWND允许的话)。
(3)当收到新数据的确认时,设置CWND=ssthresh ( ssthresh是新的慢启动门限值,由第一步计算得到)。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/129717.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!