天津网站建设方案书,中国林业工程建设网站,wordpress如何删除以前主题的缓存,桂阳网站制作公司与IP协议相比#xff0c;TCP协议更靠近应用层#xff0c;因此在应用程序中有更强的可操作性。一些重要的socket选项都和TCP协议相关。
本章从以下方面讨论TCP协议#xff1a; 1.TCP头部信息。TCP头部信息出现在每个TCP报文段中#xff0c;用于指定通信的源端端口号、目的端…与IP协议相比TCP协议更靠近应用层因此在应用程序中有更强的可操作性。一些重要的socket选项都和TCP协议相关。
本章从以下方面讨论TCP协议 1.TCP头部信息。TCP头部信息出现在每个TCP报文段中用于指定通信的源端端口号、目的端端口号管理TCP连接控制两个方向的数据流。
2.TCP状态转移过程。TCP连接的任意一端都是一个状态机在TCP连接从建立到断开的过程中连接两端的状态机将经历不同的状态变迁。
3.TCP数据流。有两种TCP数据流交互数据流和成块数据流。TCP数据流中有一种特殊的数据称为紧急数据。
4.TCP数据流的控制。为保证可靠传输和提供通信质量内核需要对TCP数据流进行控制我们会讨论TCP数据流控制的两方面超时重传和拥塞控制。
传输层协议主要有TCP和UDPTCP相对于UDP的优点是面向连接、字节流、可靠传输。
使用TCP协议通信的双方必须先建立连接然后才能开始数据的读写双方都必须为该连接分配必要的内核资源以管理连接状态和连接上数据的传输。TCP连接是全双工的即双方的数据读写可通过一个连接进行。完成数据交换后通信双方必须断开连接以释放系统资源。
TCP协议的这种连接是一对一的所以基于广播和多播目标是多个主机地址的应用程序不能使用TCP服务而无连接的协议UDP则适合广播和多播。
字节流服务和数据报服务的区别对应到实际编程中则体现为通信双方是否必须执行相同次数的读、写操作只是表现形式。当发送端应用进程连续执行多次写操作时TCP模块先将这些数据放入TCP发送缓冲区中当TCP模块真正开始发送数据时发送缓冲区中的这些等待发送的数据可能被封装成一个或多个TCP报文段发出。因此TCP模块发送出的TCP报文段的个数与应用进程执行的写操作次数之间没有固定的数量关系。
当接收端收到一个或多个TCP报文段后TCP模块将它们携带的应用程序数据按照TCP报文段的序号依次放入TCP接收缓冲区中并通知应用进程读取数据。接收端应用进程可以一次性将TCP接收缓冲区中的数据全部读出也可以分多次读取这取决于用户指定的应用程序读缓冲区的大小因此应用进程执行读操作次数和TCP模块接收到的TCP报文段个数之间也没有固定的数量关系。
综上发送端指定的写操作次数和接收端执行的读操作次数之间没有任何数量关系这就是字节流的概念应用进程读数据的发送和接收时没有边界限制的。UDP则不然发送端应用进程每执行一次写操作UDP模块就将其封装成一个UDP数据报并发送之接收端必须针对每个UDP数据报执行读操作通过recvfrom系统调用且必须及时读取否则就会丢包常出现在较慢的服务器上并且如果用户没有指定足够的应用进程缓冲区来读取一个UDP数据报该UDP数据报将被截断。 TCP传输是可靠的首先TCP采用发送应答机制即发送端发送的每个TCP报文段都必须得到接收方的应答才认为这个TCP报文段传输成功其次TCP协议采用超时重传机制发送端在发出一个TCP报文段后启动定时器如果定时时间内未收到应答它将重发该报文段最后由于TCP报文段最终是以IP数据报发送的而IP数据报到达接收端可能乱序、重复所以TCP还会对接收到的TCP报文段重排、整理再交给应用层。
UDP协议和IP协议一样提供不可靠服务它们都需要上层协议来处理数据确认和超时重传。
TCP头部出现在每个TCP报文段中用于指定通信的源端端口、目的端端口、管理TCP连接等以下是TCP头部结构其中字段为管理TCP连接和控制数据流提供了足够的信息 16位端口号告知主机该报文段是来自哪里源端口以及传给哪个上层协议或应用程序目的端口的。进行TCP通信时客户端通常使用系统自动选择的临时端口号而服务器使用知名服务端口号。所有知名服务使用的端口号都定义在/etc/services文件中。
32位序号为一次TCP通信从TCP连接建立到断开过程中某个传输方向上的字节流中的每个字节编号。假设主机A和主机B进行TCP通信A发送给B的第一个TCP报文段中序号值被系统初始化为某个随机ISNInitial Sequence Number初始序号值在该传输方向上从A到B后续的TCP报文段中序号值被系统设置为ISN加上该报文段所携带数据的第一个字节在整个字节流中的偏移如某个TCP报文段传送的数据是字节流中第1025~2048字节那么该报文段的序号值就是ISN1025。另一个传输方向从B到A的TCP报文段的序号值也有相同含义。
32位确认号用作对另一方发送来的TCP报文段的响应其值是下一个期望收到的数据包的序号值即已经成功接收的数据的最后一个字节的序号值加1。假设主机A和主机B进行TCP通信那么A发送出的TCP报文段不仅携带自己的序号还包含对B发送的TCP报文段的确认号。反之B发出的TCP报文段也同时携带自己的序号和对A发送来的报文段的确认号。
4位头部长度标识该TCP头部有多少32bit字以4字节为单位由于4位能表示的最大值为15因此TCP头部长度最长是60字节。
6位标志位包含以下几项 1.URG标志表示紧急指针是否有效。
2.ACK标志表示确认号是否有效我们称携带ACK标志的TCP报文段为确认报文段。
3.PSH标志提示接收端应用进程应立即从TCP接收缓冲区中取走数据为接收后续数据腾出空间如果应用进程不将接收到的数据读走它们会一直留在TCP接收缓冲区中。
4.RST标志表示要求对方重新建立连接我们称携带RST标志的TCP报文段为复位报文段。
5.SYN标志表示请求建立一个连接我们称携带SYN标志的TCP报文段为同步报文段。
6.FIN标志表示通知对方本端要关闭连接了我们称携带FIN标志的TCP报文段为结束报文段。
16位窗口大小字段是TCP流量控制的一个手段窗口指的是接收窗口Receiver Window RWND它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据这样对方就可以控制发送数据的速度。
16位校验和字段由发送端填充接收端对TCP报文段执行校验以确定TCP报文段在传输过程中是否损坏这个校验不仅包括TCP头部也包括数据部分。
16位紧急指针是一个正的偏移量当URG标志生效时它和序号字段的值相加表示最后一个紧急数据的下一字节的序号即这个字段是紧急指针相对当前序号的偏移不妨称之为紧急偏移。TCP的紧急指针是发送端向接收端发送紧急数据的方法。
TCP头部最后一个选项字段是变长的可选信息这部分最多包含40字节因为TCP头部最长是60字节减去20字节固定部分。TCP头部选项结构 选项的第一个字段kind说明选项的类型有的TCP选项后没有后面两个字段仅包含1字节的kind字段。第二个字段指定该选项的总长度该长度包括kind字段和length字段占据的2字节。第三个字段info是选项的具体信息。常见的TCP选项有以下7种 各个kind字段的说明 1.kind0选项表结束选项。
2.kind1空操作nop选项没有特殊含义一般用于将TCP选项的总长度填充为4字节的整数倍。
3.kind2最大报文段长度选项TCP连接初始化时通信双方使用该选项来协商最大报文段长度Max Segment SizeMSSTCP模块通常将MSS设置为MTU-40字节20字节TCP头部和20字节IP头部这样携带TCP报文段的IP数据报的长度就不会超过MTU假设TCP头部和IP头部都不含选项字段且通常也确实不含选项字段从而避免本机发生IP分片。对以太网而言MSS值为1500-401460字节。
4.kind3窗口扩大因子选项TCP连接初始化时通信双方使用该选项协商接收通告窗口的扩大因子。在TCP头部中通告窗口字段是16位的因此最大为65535字节但为了TCP通信的吞吐量TCP模块通过窗口扩大因子提高通告窗口上限假设TCP头部中接收到的通告窗口大小为N窗口扩大因子为M那么TCP报文段的实际接收到的通告窗口大小为N*2 M ^M MM的取值范围为0~14。我们可通过修改/proc/sys/net/ipv4/tcp_window_scaling内核变量来启用或关闭窗口扩大因子选项。
和MSS选项一样窗口扩大因子选项只能出现在同步报文段中否则将被忽略。当连接建立好后每个数据传输方向的窗口扩大因子就固定不变了。窗口扩大选项的细节可参考RFC 1323。
5.kind4选择性确认Selective AcknowledgementSACK选项。TCP通信时如果某个TCP报文段丢失则TCP模块会重传最后被确认的TCP报文段后续的所有报文段这样原先已经正确传输的TCP报文段也可能重复发送如传了3个TCP报文段对方只收到了第1个和第3个因此对方只会确认第1个报文段已收到而本端会重传第2个和第3个即使第3个对方已经收到了从而降低TCP性能。SACK可使TCP模块只重新发送丢失的TCP报文段而不用发送所有未被确认的TCP报文段。本选项只用于连接初始化时表示是否支持SACK技术。我们可通过修改/proc/sys/net/ipv4/tcp_sack内核变量来启动或关闭选择性确认选项。
6.kind5SACK实际工作的选项该选项告诉发送方本端已经收到并缓存的不连续的数据块从而让发送端可以据此重传丢失的数据块。每个块边沿edge of block字段包含一个4字节的序号其中块左边沿表示没收到的不连续块的第一个数据的序号而块右边沿表示没收到的不连续块的最后一个数据的序号的下一个序号这样一对参数之间的数据是没有收到的。一个块信息占用8字节因此TCP头部选项中实际最多可以包含4个这样的不连续数据块需要考虑2字节的选项类型和长度字段。
7.kind8时间戳选项它提供了计算通信双方回路时间RTTRound Trip Time的方法从而为TCP流量控制提供了信息。我们可通过修改/proc/sys/net/ipv4/tcp_timestamps内核变量来启用或关闭时间戳选项。
用tcpdump抓取一个包含TCP报文段的数据包 tcpdump输出以上内容中Flags [S]表示该TCP报文段包含SYN标志因此它是一个同步报文段如果TCP报文段中包含其他标志则也会将其放在方括号中。
seq是序号值因为这是第一个SYN段因此此值是此次通信过程中该传输方向的ISN值也因此它没有针对对方发来的TCP报文段的确认值因为还没收到任何对方发送的TCP报文段。
win是通告窗口大小由于这是一个同步报文段因此此值反应的是最大窗口大小。
options是TCP选项具体选项在方括号中。mss是发送端通告的最大报文段长度通过ifconfig命令可查看接口的MTU为16436字节因此TCP报文段的MSS为16436-4016396字节。sackOK表示发送端支持并同意使用SACK选项。TS val是发送端的时间戳ecr是时间戳回显应答由于这是第一个SYN段因此它针对对方的时间戳的应答为0。nop是一个空操作选项。wscale指出发送端使用的窗口扩大因子为6。
接下来分析tcpdump输出的字节码中TCP头部对应的信息它从上图中的21字节开始除去IP首部 可见TCP报文段头部的二进制码和tcpdump输出的TCP报文段描述信息完全对应。
在ernest-laptop上执行telnet命令登录Kongming20的80端口我们抓取这一过程中客户和服务器交换的TCP报文段操作如下 执行telnet命令并在两台通信主机之间建立连接后telnet输出Connected to 192.168.1.109键入Ctrl]调出talnet程序的命令提示符然后在telnet命令提示符后输入quit以退出telnet客户程序从而结束TCP连接。整个过程中tcpdump的输出如下 由于整个过程没有发生应用层数据的交换所以TCP报文段的数据部分的长度上图中length字段总是0。我们将以上输出绘制成时序图 第一个TCP报文段包含SYN标志因此它是一个同步报文段即ernest-laptop客户端向Kongming20服务器发起连接请求同时该同步报文段包含一个ISN值为535734930的序号。第二个TCP报文段也是同步报文段表示Kongming20同意与ernest-laptop建立连接同时发送自己的ISN值为2159701207的序号并对第一个同步报文段进行确认确认值是535734931即第一个同步报文段的序号值加1。序号值是用来标识TCP数据流中的每一字节的但同步报文段比较特殊即使它没有携带任何应用进程数据也要占用一个序号值。第三个TCP报文段时ernest-laptop对第二个同步报文段的确认至此TCP连接就建立起来了建立TCP连接的这三个步骤被称为TCP三次握手。
从第三个TCP报文段开始tcpdump输出的序号值和确认值都是相对初始ISN值的偏移我们可以使用tcpdump的-S选项来打印序号的绝对值。
后面4个TCP报文段时关闭连接的过程。第四个TCP报文段包含FIN标志因此它是一个结束报文段即ernest-laptop要求关闭连接结束报文段和同步报文段一样也要占用一个序号值。Kongming20用TCP报文段5来确认该结束报文段。紧接着Kongming20发送自己的结束报文段6ernest-laptop则用TCP报文段7给予确认。实际上仅用于确认目的的确认报文段5是可以省略的因为结束报文段6也携带了该确认信息。确认报文段5是否出现在连接断开过程取决于TCP的延迟确认特性。
在连接关闭过程中因为ernest-laptop先发送结束报文段故称ernest-laptop执行主动关闭而Kongming20执行被动关闭。
一般TCP连接是由客户端发起并通过三次握手建立特殊情况是同时打开的而TCP连接的关闭可能是客户端执行主动关闭也可能是服务器执行主动关闭也可能是同时关闭和同时打开一样很少见。
TCP连接是全双工的所以它允许两个方向的数据传输被独立关闭即通信的一端可以发送结束报文段给对方告诉它本端已经完成了数据发送但允许继续接收来自对方的数据直到对方也发送结束报文段以关闭连接。TCP连接的这种状态称为半关闭half close状态 由上图服务器和客户端应用进程判断对方是否已关闭连接的方法是read系统调用返回0收到结束报文段。Linux还提供其他检测连接是否被对方关闭的方法。
socket网络编程接口通过shutdown函数提供对半关闭的支持。使用半关闭的应用程序很少见。
以上是很快建立连接的情况如果客户访问一个距离它很远的服务器或由于网络繁忙导致服务器对于客户端发出的同步报文段没有应答对于提供可靠服务的TCP来说此时客户端程序会先进行重连可能执行多次如果重连仍无效则通知应用进程连接超时。
为观察连接超时我们模拟一个繁忙的服务器环境在ernest-laptop上执行以下操作 iptables命令用于过滤数据包-F选项用于清空当前所有规则链中的规则即重置为默认设置-I INPUT含义为将规则插入名为INPUT的规则链中-p tcp表示只匹配TCP协议的数据包-i eth0表示只匹配从eth0网卡接收到的数据包-j DROP表示匹配到的包全部丢弃不做任何处理。这样客户端就无法得到SYN报文段的ACK。
接下来在Kongming20上执行telnet命令登录到ernest-laptop并用tcpdump抓取这个过程中双方交换的TCP报文段具体操作如下 从两次date命令的输出可以看出Kongming20建立TCP连接的超时时间是63秒本次tcpdump的输出如下 此次抓包我们保留了tcpdump输出的时间戳没有使用-t选项。
我们一共抓到6个TCP报文段它们都是同步报文段且具有相同的序号值这说明后5个同步报文段都是超时重连报文段。这些TCP报文段被发送的时间间隔分别为1、2、4、8、16秒由于定时器精度问题这些时间间隔都有一定偏差可以推断第6个TCP报文段的超时时间是32秒63-16-8-4-2-132因此TCP模块一共执行了5此重连操作这是由/proc/sys/net/ipv4/tcp_syn_retries内核变量所定义的。每次重连的超时时间都增加一倍在5此重连都失败的情况下TCP模块放弃连接并通知应用程序。
在应用程序中我们可以修改连接超时时间。
TCP连接的任意一端在任一时刻都处于某种状态TCP连接当前状态可通过netstat命令查看。我们接下来讨论TCP连接从建立到关闭的整个过程中通信两端状态的变化以下是完整的状态转移图它描绘了所有TCP状态以及可能的状态转换 上图中粗虚线表示典型的服务器端连接的状态转移粗实线表示典型的客户端连接的状态转移。CLOSED是一个假想的起始点并不是一个实际的状态。
先讨论服务器的典型状态转移过程。
服务器通过listen系统调用进入LISTEN状态被动等待客户端连接因此执行的是被动打开。服务器一旦监听到某个连接请求收到同步报文段就将该连接放入内核等待队列中并向客户端发送带SYN标志的确认报文段此时该连接处于SYN_RCVD状态。如果服务器成功接收到客户端发送回的确认报文段则该连接进入ESTABLISHED状态。ESTABLISHED状态是连接双方能进行双向数据传输的状态。
当客户端主动关闭连接时通过close或shutdown系统调用向服务器发结束报文段服务器通过返回确认报文段使连接进入CLOSE_WAIT状态含义为等待服务器应用程序关闭连接。通常服务器检测到客户端关闭连接后也会立即给客户端发送一个结束报文段来关闭连接这将使连接转移到LAST_ACK状态以等待客户端对结束报文段的最后一次确认一旦确认完成连接就彻底关闭了。
下面讨论客户端的典型状态转移过程。
客户端通过connect系统调用主动与服务器建立连接connect系统调用首先给服务器发送一个同步报文段使连接转移到SYN_SENT状态此后connect系统调用可能首先因为下面两个原因返回失败 1.如果connect连接的目标不存在未被任何进程监听或该端口被处于TIME_WAIT状态的连接所占用则服务器将给客户端发送一个复位报文段connect调用失败。
2.如果目标端口存在但connect函数在超时时间内未收到服务器的确认报文段则connect调用失败。
connect调用失败将使连接立即返回初始的CLOSED状态。如果客户端成功收到服务器的同步报文段和确认则connect调用成功返回连接转移至ESTABLISHED状态。
当客户端执行主动关闭时它向服务器发送一个结束报文段同时连接进入FIN_WAIT_1状态若此时客户端收到服务器该FIN报文段的ACK则连接转移至FIN_WAIT_2状态。当客户端处于FIN_WAIT_2状态时服务器处于CLOSE_WAIT状态这一对状态时半关闭状态。如果服务器随后也关闭连接发送结束报文段则客户端将给予确认并进入TIME_WAIT状态。
图3-8中还给出了客户端从FIN_WAIT_1状态直接进入TIME_WAIT状态的一条线路没有经过FIN_WAIT_2状态前提是处于FIN_WAIT_1状态的服务器收到的ACK报文中还带有FIN这种情况对应于图3-6中服务器把TCP报文段5和6合成一个TCP报文段发送。
处于FIN_WAIT_2状态的客户端需要等待服务器发送结束报文段才能转移到TIME_WAIT状态否则它将一直停留在这个状态。如果服务器端不是为了在半关闭状态下继续接收数据长时间地使客户停留在FIN_WAIT_2状态并无益处。客户执行半关闭后可能未等服务器关闭连接就强行退出了此时客户端连接由内核来接管可称之为孤儿连接与孤儿进程类似Linux为了防止孤儿连接长时间存留在内核中定义了两个内核变量/proc/sys/net/ipv4/tcp_max_orphans和/proc/sys/net/ipv4/tcp_fin_timeout前者指定内核能接管的孤儿连接数后者指定孤儿连接在内核中生存的时间孤儿连接处于FIN_WAIT_2状态的最长时间。 图3-8中还描绘了其他非典型的TCP状态转移路线如同时关闭、同时打开。
从上图看客户端连接在收到服务器的结束报文段报文段6后并没有进入CLOSE状态而是转移到TIME_WAIT状态此状态下客户端要等待2MSLMaximum Segment Life报文段最大生存时间的时间才能完全关闭。MSL是TCP报文段在网络中的最大生存时间RFC 1122的建议值是2分钟。
TIME_WAIT状态存在的原因 1.可靠地终止TCP连接。假设上图中报文段7丢失那么服务器将重发结束报文段因此客户端需要停留在某个状态以处理重复收到的结束报文段否则客户端将以复位报文段来回应服务器服务器会复位连接而非优雅地关闭连接因为它期望的是一个对于FIN的ACK。
2.保证让被延迟的TCP报文段有足够的时间被识别并丢弃。在Linux系统上一个TCP端口不能被同时打开两次及以上当一个TCP连接处于TIME_WAIT状态时我们无法使用该连接占着的端口来建立一个新连接。如果不存在TIME_WAIT状态则应用能立即建立一个与刚关闭的连接相似的连接相似指它们有相同IP地址和端口号这个新的与原来连接相似的连接被称为原来连接的化身incarnation新的化身可能接收到属于旧连接的、携带应用数据的TCP报文段被延迟的报文段这是不应该发生的。
由于TCP报文段的最大生存时间为MSL所以坚持2MSL时间的TIME_WAIT状态能确保网络上两个传输方向上的TCP报文段都已经消失被中转路由器丢弃因此一个连接的新的化身可以在2MSL时间后安全地建立不会收到属于原来连接的应用数据这就是为什么TIME_WAIT状态要维持2MSL时间。
有时我们希望避免TIME_WAIT状态因为当程序退出后我们希望能够立即重启它但由于处在TIME_WAIT状态的连接还占用着端口程序将无法启动直到2MSL超时时间结束。考虑以下例子在测试机器ernest-laptop上以客户端方式运行nc命令可以进行TCP/IP连接的创建、监听、传输数据等操作的工具登录本机Web服务且明确指定客户端使用12345端口与服务器通信然后从终端输入CtrlC终止客户端程序接着又启动nc程序以完全相同的方式再次连接本机Web服务具体操作如下 nc的-p选项指定本地端口。netstat的-n选项以数字形式列出IP地址-a选项表示列出所有连接包括在LISTEN状态的连接-t表示列出TCP连接。
上图我们使用netstat命令查看连接的状态其输出显示客户端程序被中断后连接进入TIME_WAIT状态12345端口仍被占用所以客户重启失败。
对客户端程序来说我们通常不用担心上面描述的重启问题因为客户端一般使用系统自动分配的临时端口号来建立连接该临时端口号是操作系统选择的一个未被占用的端口号以确保不会与其他正在使用的端口号冲突因此客户端程序一般可以立即重启。上例中只是为了说明问题我们强制客户使用12345端口才导致重启客户端失败。
但如果是服务器主动关闭连接后异常终止则因为它总是使用同一个知名服务端口号所以连接的TIME_WAIT状态将导致它不能立即重启但我们可以通过socket选项SO_REUSEADDR来强制进程立即使用处于TIME_WAIT状态的连接占用的端口。
某些特殊条件下TCP连接的一端会向另一端发送携带RST标志的报文段即复位报文段以通知对方关闭连接或重新建立连接。
当客户端访问一个不存在的端口时目标主机将给它发送一个复位报文段。我们在Kongming20上执行telnet命令登录ernest-laptop上一个不存在的54321端口并用tcpdump抓取该过程中两台主机交换的TCP报文段具体操作如下 telnet程序的输出显示连接被拒绝了因为这个端口不存在tcpdump抓取到的TCP报文段内容如下 可见ernest-laptop对于Kongming20的连接请求同步报文段回应了一个复位报文段tcpdump输出了R标志。收到复位报文段的一端应关闭连接或重新连接而不能回应这个复位报文段。上图中复位报文段的通告窗口也为0但复位报文段也不需要通告窗口字段。
当客户进程向服务器的某个端口发起连接而该端口被处于TIME_WAIT状态的连接所占用时客户程序也将收到复位报文段。
通过交换结束报文段而终止一个TCP连接是正常的终止方式TCP还提供了异常终止一个连接的方法即给对方发送一个复位报文段一旦发送了复位报文段发送端所有排队等待发送的数据都将被丢弃。
应用进程可使用socket选项SO_LINGER来发送复位报文段以异常终止一个连接。
考虑以下情形TCP连接的本端所在主机崩溃没有发出FIN对方接收不到FIN报文段此时对端还维持着原来的连接而本端机器崩溃并重启后就没有该连接的信息了。我们称这种状态为半打开状态处于这种状态的连接称为半打开连接如果对端往这个处于半打开状态的连接写入数据则本端将回应一个复位报文段。
例如我们在Kongming20上使用nc命令模拟一个服务器程序使其监听12345端口然后在ernest-laptop上运行telnetl命令登录到Kongming20的12345端口上接着拔掉ernest-laptop的网线并重启机器以中断Kongming20上的服务器程序此时ernest-laptop上运行的telnet客户端程序维持着一个半打开连接。然后接上ernest-laptop的网线然后客户端进程向该半打开连接中写入1个字节的数据a。我们运行tcpdump抓取整个过程交换的TCP报文段具体操作如下 上图中我们输入字符a后telnet的输出显示连接被服务器关闭了。tcpdump抓取到的内容如下 由上图前3个TCP报文段时正常建立TCP连接的3次握手过程。第4个TCP报文段由客户端发送给服务器它携带了3字节的应用数据这3字节依次是a\r\n但由于服务器程序已被中断所以Kongming20对客户端发送的数据回应了一个复位报文段5。
TCP报文段所携带的应用数据按长度可分为交互数据和成块数据。交互数据仅包含很少的字节使用交互数据的应用或协议对实时性要求高如telnet、ssh等。使用成块数据的应用或协议对传输效率要求高如ftp。
考虑以下情况在ernest-laptop上执行telnet命令登录自己机器然后在shell命令提示符后执行ls命令同时tcpdump抓取这一过程中telnet客户端和telnet服务器都在一台机器上交换的TCP报文段具体操作如下 以上过程引起服务器和客户端交换很多TCP报文段下面仅列出我们感兴趣的、执行ls命令产生的tcpdump输出 TCP报文段1由客户端发送给服务器它携带1个字节的应用数据l。TCP报文段2是服务器对TCP报文段1的确认同时回显字母l。TCP报文段3是客户端对TCP报文段2的确认。第4~6个TCP报文段是针对字母s的上述过程。TCP报文段7传送的2字节数据分别是客户键入的回车符\n和流结束符EOF本例中是0x00。TCP报文段8携带服务器返回的客户查询的目录的内容ls命令的输出。TCP报文段9是客户端对TCP报文段8的确认。TCP报文段10携带的也是服务器返回给客户端的数据包括一个回车符、一个换行符、客户端登录的用户的PS1环境变量第一级命令提示符。TCP报文段11是客户端对TCP报文段10的确认。
上述过程中客户端针对服务器返回的数据所发送的确认报文段TCP报文段6、9、11都不携带任何应用程序数据长度为0而服务器每次发送的确认报文段TCP报文段2、5、8、10都包含它要发送的应用进程数据。服务器采用了延迟确认即它不马上确认上次收到的数据而是在一段延迟后查看本端是否有数据需要发送如果有则和确认信息一起发出。因为服务器对客户请求处理地很快所以它发送确认报文段的时候总有数据一起发送。延迟确认可以减少发送TCP报文段的数量。由于用户的输入速度明显慢于客户端进程的处理速度所以客户端的确认报文段总是不携带任何应用数据。在TCP连接的建立和断开过程中也可能发生延迟确认。
上例是在本地环回上运行的结果在局域网中也能得到基本相同的结果但广域网中就未必如此了广域网上的交互数据流可能经受很大延迟且由于携带交互数据的小TCP报文段数量一般很多一个按键输入就导致一个TCP报文段因此很可能发生拥塞。解决该问题的一个有效方法是使用Nagle算法。
Nagle算法要求在任意时刻最多只能发送一个未被确认的TCP报文段在该TCP报文段的确认到达前不能发送其他TCP报文段除非等待过程中要发送的数据报长度满足MSS的长度。发送方在等待确认的同时收集本端需要发送的微量数据并在确认到来时将它们未满MSS如果已满MSS则不需等待确认到来以一个TCP报文段全部发出这样就极大地减少了网络上的微小TCP报文段的数量。该算法的另一个优点在于其自适应性确认到达得越快数据也就发送得越快。
下面考虑用FTP协议传输一个大文件。在ernest-laptop上启动一个vsftpd服务器程序升级的、安全版的ftp服务器程序并执行ftp命令登录到该服务器上然后再ftp命令提示符后输入get命令从服务器下载一个几百兆的大文件同时用tcpdump抓取这一过程中ftp客户端和vsftpd服务器交换的TCP报文段具体操作如下 以下是上图过程的部分tcpdump输出 上图中客户端发送的TCP报文段17、18分别是对TCP报文段2和16的确认从序号值和确认值来判断由此可见当传输大量大块数据时发送方会连续发送多个TCP报文段接收方可以一次确认所有这些报文段。在接收方两个相邻的ACK间发送方能发送多少个TCP报文段取决于接收方的通告窗口还需考虑拥塞窗口大小。报文段17中客户报告的通告窗口大小为30084*641925376字节窗口扩大因子为6而在TCP报文段18中客户的接收通告窗口大小为27317*641748288字节即客户能接收的数据量变少了这说明客户端的TCP接收缓冲区有更多的数据未被应用程序读取而留在其中了这些数据来自TCP报文段3~16。在服务器收到TCP报文段18后它至少还能连续发送未被确认的报文段数量为1748288/16384106个其中16384是成块数据的长度见报文段1~16的length值它小于但接近MSS规定的16396字节。
上图中的服务器每发送4个TCP报文段就传送一个PSH标志tcpdump输出标志P给客户端以通知客户应用进程尽快读取数据但这对服务器来说不是必需的因为它知道客户的TCP接收缓冲区中还有空间客户报告的通告窗口不为0。
我们修改上例中客户和服务器的TCP发送和接收缓冲区大小然后重新执行以上操作此次tcpdump的部分输出如下 从SYN报文段上图中没有列出可以看到客户端和服务器的窗口扩大因子都为0因此客户端和服务器通告的窗口大小都是3072字节。因为每个成块数据的长度为1536字节所以服务器在收到上一个成块TCP报文段的确认前最多还能再发送1个成块TCP报文段。
有些传输层协议具有带外Out Of BandOOB数据的概念用于迅速通告对方本端发生的重要事件因此带外数据比普通数据也称为带内数据有更高的优先级它应该总是立即被发送而不论发送缓冲区中是否有排队等待发送的普通数据。带外数据的传输可以使用一条独立的传输层连接也可以映射到传输普通数据的连接中。实际应用中带外数据的使用很少见已知的仅有telnet、ftp等程序使用。
UDP没有实现带外数据传输TCP也没有真正的带外数据但TCP利用其头部的紧急指针标志和紧急指针字段给应用提供了一种紧急方式。TCP的紧急方式利用传输普通数据的连接来传输紧急数据这种紧急数据的含义与带外数据类似因此也将TCP紧急数据称为带外数据。
先介绍TCP发送带外数据的过程假设一个进程已经往某个TCP连接的发送缓冲区中写入了N字节的普通数据并等待发送在数据被发送前该进程又向这个连接写入了3字节abc其中最后一个字节为带外数据此时待发送的TCP报文段的头部将被设置URG标志且紧急指针被置为指向带外数据的下一字节紧急指针的值为带外字节相对于当前TCP段数据部分开始处的偏移值。 由上图发送端一次只能发送一个字节字母c作为带外数据。如果TCP模块发送完以上报文段后又发送一个带有带外数据的报文段则后面的带外数据会覆盖前面的带外数据即一个TCP连接只能有一个带外数据。
TCP接收端只有在接收到紧急指针标志时才检查紧急指针然后根据紧急指针所指的位置确定带外数据的位置并将其读入一个特殊的缓存中这个缓存只有1字节称为带外缓存。如果上层应用没有及时将带外数据从带外缓存中读出则后续的带外数据将覆盖它。
上述的带外数据接收过程是TCP模块接收带外数据的默认方式如果我们给TCP连接设置了SO_OOBINLINE套接字选项则带外数据将和普通数据一样被TCP模块存放在TCP接收缓冲区中此时应用进程需要像读取普通数据一样来读取带外数据这种情况下socket接口提供了系统调用来识别带外数据。
以下讨论异常网络状况下出现超时和丢包TCP如何控制数据传输以保证其承诺的可靠服务。
TCP必须能重传超时时间内未收到确认的TCP报文段为此TCP模块为每个TCP报文段维护一个重传定时器该定时器在一个TCP报文段第一次被发送时启动如果超时时间内没有收到接收方的应答TCP模块将重传TCP报文段并重置定时器。我们下面通过一个例子来研究一下Linux下的超时重传策略。
在ernest-laptop上启动iperf服务器程序然后在Kongming20上执行telnet命令登录该服务器程序接下来telnet客户发送一些数据给服务器然后断开服务器的网线并再次从客户端发送一些数据给服务器同时用tcpdump抓取这一过程中客户端和服务器交换的TCP报文段以下是具体操作 iperf是一个测量网络状况的工具-s选项表示将其作为服务器运行它默认监听5001端口并丢弃该端口上接收到的所有数据相当于一个discard服务器。上述操作的部分tcpdump输出如下 报文段1~3是三次握手建立连接的过程。报文段4~5是客户端发送的数据1234\r\n及服务器确认的过程。报文段6是客户端第一次发送12\r\n的过程由于服务器的网线被断开所以客户端无法收到报文段6的确认报文段。此后客户端对报文段6进行了5次重传它们是报文段7~11这可从报文段的序号得知。此后数据包12~23都是ARP模块的输出内容即Kongming20查询ernest-laptop的MAC地址。
我们保留了tcpdump输出的时间戳观察TCP报文段6~11被发送的时间间隔它们分别为0.2、0.4、0.8、1.6、3.2秒由此可见一共进行了5次重传每次超时时间都增加一倍和TCP连接建立时的重传策略相似。在5次重传均失败的情况下底层的IP和ARP开始接管直到telnet客户端放弃连接为止。
Linux有两个内核参数与TCP超时重传有关/proc/sys/net/ipv4/tcp_retries1和/proc/sys/net/ipv4/tcp_retries2前者指定在底层IP接管前TCP最少执行的重传次数默认值为3后者指定连接放弃前TCP最多可以执行的重传次数默认值为15一般对应15~30分钟。上例中TCP重传发生了5此连接坚持的时间是15分钟可用date命令测量。
虽然超时会导致TCP报文段重传但TCP报文段的重传可发生在超时前即快速重传。
TCP模块的另一个任务是提高网络利用率降低丢包率保证网络资源对每条数据流的公平性这就是所谓的拥塞控制。
TCP拥塞避免的文档是RFC 5681其中介绍了拥塞控制的四个部分慢启动slow start、拥塞避免congestion avoidance、快速重传fast retransmit、快速恢复fast recovery。拥塞控制算法在Linux上有多种实现如reno算法、vegas算法、cubic算法等它们或部分或全部地实现了以上四部分。/proc/sys/net/ipv4/tcp_congestion_control文件指示机器当前使用的拥塞控制算法。
拥塞控制的最终受控变量是发送端向网络一次连续写入收到其中第一个数据的确认前的数据量我们称为SWNDSend Window发送窗口。报文段的数据部分的最大长度称为SMSSSender Maximum Segment Size发送者最大段大小其值一般等于MSS。
发送端需要合理地选择SWND的大小如果SWND太小会引起明显的网络延迟当已发送的、未收到ACK的数据已满SWND则需要等到确认才能再发下一个报文段如果SWND太大则容易导致网络拥塞一次连续发送可以发非常多报文段。接收方可通过其通告窗口RWNDReceive Window来控制发送端的SWND但这不够因此发送端还引入了拥塞窗口Congestion WindowCWND。实际的SWND是RWND和CWND中的较小者。
TCP连接建好后CWND被设为初始值IWInitial Window其大小为2~4个SMSS但新的Linux内核提高了该初始值以减小传输滞后。此时发送端最多能发送IW字节的数据此后发送端每收到接收端的一个确认其CWND就按下式增加 N是此次ACK确认了多少字节的数据这样CWND就按指数形式扩大这就是所谓慢启动。使用慢启动是由于TCP模块刚开始发送数据时不知道网络的实际情况需要用一种试探的方式增加CWND的大小。
如果不施加其他手段慢启动必然使CWND很快膨胀可见慢启动并不慢并最终导致网络拥塞因此TCP拥塞控制定义了慢启动门限slow start threshold sizessthresh当CWND超过该值时TCP拥塞控制将进入拥塞避免阶段。
拥塞避免算法使得CWND按线性方式增加从而减缓其扩大RFC 5681中提到了以下两种实现方式 1.每个RTT时间按式3-1计算一次新的CWND而不论该RTT时间内发送端收到多少个确认。
2.每收到一个对新数据的确认报文段就按下式来更新CWND 对式3-2的解释每收到一个对大小为SMSS的报文段的ACKCWND就增加SMSS的SMSS/CWND倍大小这样当我们收到的确认大小满一个CWND时就增加1个SMSS大小即慢启动阶段每发送一个CWND大小的数据如2个SMSS大小CWND就变为原来的二倍即4个SMSS大小而拥塞避免阶段每发送一个CWND大小的数据只增加一个SMSS的大小因此该公式使CWND按线性增加。
下图描述了慢启动和拥塞避免发生的时机和区别 上图中我们以SMSS为单位来显示CWND实际上它是以字节为单位的以次数为单位显示RTT这是为了方便讨论问题。上图我们假设当前ssthresh大小为16个SMSS实际的ssthresh远比这个大。
发送端以下面的两种情况来判断拥塞发生了 1.传输超时TCP重传定时器溢出。
2.在传输超时前接收到重复的确认报文段。
当拥塞发生时可能在慢启动阶段或拥塞避免阶段对以上两种情况有不同的处理方式。第一种情况发生时通过调整一些参数使连接再次进入慢启动和拥塞避免。第二种情况发生时则使用快速重传和快速恢复。
如果发送端检测到传输超时即上述第一种情况它将执行重传并做如下参数调整将ssthresh减少为当前值的一半不同实现减少的幅度略有差异将CWND设为一个较小值以便重新开始慢启动。
发送端可能会接收到重复的确认报文段如TCP报文段丢失或接收端收到乱序的TCP报文段。拥塞避免算法需要判断当收到重复的确认报文段时网络是否真的发生了拥塞即报文段是否真的丢失了具体做法是如果发送端连续收到3个重复的确认报文段就认为是拥塞发生了然后它将使用快速重传和快速恢复算法来处理拥塞过程如下 1.当收到第3个重复的确认报文段时将ssthresh减为当前值的一半然后立即重传丢失的报文段然后按下式重新设置CWND 2.每次收到一个重复的确认时设置CWNDCWNDSMSS以便允许发送端在等待非重复的ACK时可以发送新的TCP报文段而不是保持空闲我们收到重复的确认就意味着接收端还在接收后面的报文段只不过乱序了可能只丢了一个报文段其他报文段还能到达因此我们应该继续发送。
3.当收到新数据的确认时设置CWNDssthresh。
在快速重传和快速恢复完成后拥塞控制将恢复到拥塞避免阶段。