当前位置:   article > 正文

【TCP/IP详解】TCP详解_如何判断tcp报文段的接收方是客户进程或服务器进程

如何判断tcp报文段的接收方是客户进程或服务器进程

        TCP和UDP都使用相同的网络层(IP),TCP却向应用层提供与UDP完全不同的服务。TCP提供一种面向连接的、可靠的字节流服务。在一个TCP连接中,仅有两 方进行彼此通信。主要通过一下方式提供可靠性:

        1. 应用数据被分割成TCP认为最为适合发送的数据块。这和UDP完全不同,应用程序产生的数据报长度将保持不变。由TCP传递给IP的信息单位称为报文段/segment

         2. 当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。

         3. TCP收到发自TCP连接另一端的数据,它将发送一个确认。这个确认不是立即发送,通常将推迟几分之一秒。

         4. TCP将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP将丢弃这个报文段和不确认收到此报文段(希望发端超时并重发)

         5. 既然TCP报文段作为IP数据包来传输,而IP数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。如果必要,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层。

         6. 既然IP数据报会发生重复,TCP的接收端必须丢弃重复的数据

         7. TCP提供流量控制。TCP连接的每一方都有固定大小的缓冲空间。TCP的接收端只允许另一端发送接收端缓冲区所能接纳的数据。这将防止较快主机致使较慢主机的缓冲区溢出

         两个application通过TCP连接交换8bit字节构成的字节流。TCP不在字节流中插入记录标识符——字节流服务(byte stream service)。如果一方的应用程序先传10字节,又传20字节,再传50字节,连接的另一方将无法了解发方每次发送了多少字节。收方可以分4次接收这次80个字节,每次接收10字节。一端将字节流放到TCP连接上,同样的字节流将出现在TCP连接的另一端。

         TCP对字节流的内容不作任何解释。TCP不知道传输的数据字节流是二进制数据,还是ASCII字符、EBCDIC字符或其他类型数据。对字节流的解释由TCP连接双方的应用层解释

TCP首部】

        每个TCP段都包含源端和目的端的端口号,用于寻找发端和收端应用进程。这两个值加上IP首部中的源端IP地址和目的端IP地址唯一确定一个TCP连接。一个IP地址和一个端口号也称为一个插口(socket。插口对(socket pair = 客户IP地址 + 客户端口号 + 服务器IP地址   + 服务器端口号的四元组)可唯一确定互联网中每个TCP连接的双方。

         序号用来标识从TCP发端向TCP收端发送的数据字节流,它表示在这个报文段中的第一个数据字节。如果将字节流看作在两个应用程序间的单向流动,则TCP用序号对每个字节进行计数。序号是32bit的无符号数,范围为 [ 0,  232 -1 ]

         当建立一个新的连接时,SYN标志变1ACK标志变1。序号字段包含由这个主机选择的该连接的初始序号ISN(initial sequence Number)。该主机要发送数据的第一个字节序号为这个ISN加1,因为SYN标志消耗了一个序号。

         既然每个传输的字节都被计数,确认序号包含发送确认的一端所期望收到的下一个序号。因此,确认序号应当是上次已成功收到数据字节序号➕1。只有ACK标志为1时,确认序号字段才有效。发送ACK无需任何代价,因为32bit的确认序号字段和ACK标志一样,总时TCP首部的一部分。

          TCP为应用层提供全双工服务。数据能在两个方向上独立地进行传输。因此连接的每一端必须保持每个方向上的传输数据序号。TCP可以表述为一个没有选择确认 or 否认的滑动窗口协议。是因为TCP首部中的确认序号表示发方已成功收到字节,但还不包含确认序号所指的字节。当前还无法对数据流中选定的部分进行确认。TCP首部6个标志比特如下:

URG

紧急指针(urgent pointer)有效

ACK

确认序号有效

PSH

接收方应该尽快将这个报文段交给应用层

RST

重建连接

SYN

同步序号用来发起一个连接

FIN

发端完成发送任务

【滑动窗口】

        TCP的流量控制由连接的每一端通过声明的窗口大小来提供。窗口大小为字节数,起始于确认序号字段指明的值,这个值是接收端正期望接收的字节。窗口大小是一个16bit字段,最大值为65535字节

【检验和】

        检验和覆盖了整个TCP报文段:TCP首部和TCP数据。这是一个强制性的字段,一定是由发端计算和存储,并由收端进行验证。TCP检验和的计算和UDP检验和的计算相似。

 

~~~~~【TCP连接的建立与终止】~~~~~

三次握手

建立一条TCP连接的步骤:

         1. 请求端(通常称为客户)发送一个SYN段指明客户打算连接的服务器的端口,以及初始序号(ISN)。这个SYN段为报文段1.

         2. 服务器发回包含服务器的初始序号的SYN报文段(报文段2)作为应答。同时,将确认序号设置为客户的ISN➕1以对客户的SYN报文段进行确认。一个SYN将占用一个序号。

         3. 客户必须将确认号设置为服务器的ISN➕1以对服务器的SYN报文段进行确认(报文段3)。

        发送第一个SYN的一端将执行主动打开(active open。接收这个SYN并发回下一个SYN的另一端执行被动打开(passive open。当一个端为建立连接而发送它的SYN时,它为连接选择一个初始序号。ISN随时间而变化,因此每个连接都将具有不同的ISN。ISN可看作是一个32bit的计数器,每4ms1这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它作错误的解释

~~~~~~~~~~~~【科普小课堂】~~~~~~~~~~~~~

         ***如何选择序号?***

在4.4BSD中,系统初始化时初始的发送序号被初始化为1。这个变量每0.5 秒增加64000,并每隔9.5h 又回到0(对应这个计数器每8ms➕1,而不是每4ms➕1).另外,每次建立一个连接后,这个变量将增加64000.

【四次握手】

         建立连接需要3次握手,而终止一个连接需要4次握手。这是由于TCP半关闭(half-close造成的。既然一个TCP连接是全双工(即数据在两个方向上能同时传递),因此每个方向必须单独地进行关闭。这原则就是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向连接。当一端收到一个FIN,它必须知道应用层另一端几经终止了那个方向的数据传送。发送FIN通常是应用层进行关闭的结果

         收到一个FIN只意味着在这一方向上没有数据流动。一个TCP连接在收到一个FIN后仍能发送数据。而这对利用半关闭的应用来说是可能的,尽管在实际应用中只有很少的TCP应用程序这样做。

         首先进行关闭的一方(即发送第一个FIN)将执行主动关闭,而另一方(收到这个FIN)执行被动关闭。通常一方完成主动关闭而另一方完成被动关闭。如上图中,当服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号➕1(报文段5)。和SYN一样,一个FIN将占用一个序号。同时TCP服务器还向应用程序(即丢弃服务器)传送一个文件结束符。接着这个服务器程序就关闭它的连接,导致它的TCP端发送一个FIN(报文段6),客户必须发回一个确认,并确认序号设置为收到序号➕1(报文段7)。

         

       上图中,这些FINACK是由TCP软件自动产生的。连接通常是由客户端发起的。这样第一个FIN从客户传到服务器,每一端都能主动关闭这个连接(即首先发送FIN)。然而一般由客户端决定何时终止连接,因为客户进程通常由用户交互控制,用户会输入如“quit”等来终止进程。

【连接建立超时】

         第一次超时时间在 [ 5.59 s, 5.93s ] 之间变化,第二次超时时间总是24.00。BSD版的TCP采用一种500ms的定时器,如下:

        当滴答计数器为0时,6秒的定时器便会超时,这个定时器会在以后的24秒(48个滴答)重新复位。之后的下一个定时器将更接近24秒,因为当TCP的500ms定时器被内核调用时,它就会被修改一次。

【最大报文段长度】

         最大报文段长度(MSS表示TCP传往另一端的最大块数据的长度。当一个连接建立时,连接的双方都要通告自己的MSS。MSS的默认值时536字节(这个默认值允许20字节的IP首部和20字节的TCP首部以适合576字节IP数据报)。如果没有分段发生,MSS还是越大越好。报文段越大允许每个报文段传送的数据就越多,相对IP和TCP首部有更高的网络利用率对于一个以太网,MSS <= 1460字节,使用IEEE802.3的封装,MSS <= 1452字节

         

TCP半关闭】

        TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力——半关闭。为了使用这个特性,编程接口必须为应用程序提供一种方式来说明“我已经完成了数据传送,因此发送一个文件结束(FIN)给另一端,但我还想接收另一端发来的数据,直到它给我发来文件结束”。

**为什么要半关闭?**

        没有半关闭,需要其他的一些技术让客户通知服务器,客户端已经完成了它的数据传送,但仍要接收来自服务器的数据。使用两个TCP连接也可作为一个选择,但使用半关闭的单连接更好。

【TCP状态变迁图】

 

         

         实线箭头——> 正常的客户端状态变迁虚线箭头——> 正常的服务器状态变迁

         第二点是两个导致进入ESTABLISHED状态的变迁对应打开一个连接,而两个导致从ESTABLISHED状态离开的变迁对应关闭一个连接。ESTABLISHED状态是连接双方能够进行双向数据传递的状态

        只有当SYN_RCVD状态是从LISTEN状态(正常情况)进入,而不是从SYN_SENT状态(同时打开)进入时,从SYN_RCVD回到LISTEN的状态变迁才是有效的。这意味着如果执行被动关闭(进入LISTEN),收到一个SYN,发送一个带ACK的SYN(进入SYN_RCVD),然后收到一个RST,而不是一个ACK,便又回到LISTEN状态并等待另一个连接请求的到来。

 

2MSL等待状态】

         TIME_WAIT状态/2MSL等待状态——每个具体TCP实现必须选择一个报文段最大生存时间MSLMaximum Segment  Lifetime。它是任何报文段被丢弃前在网络内的最长时间。这个时间是有限的,因为TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。对IP数据报TTL的限制是基于跳数,而不是定时器。

         对一个具体实现所给定的MSL值,处理的原则是:TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIME_WAIT状态停留的时间为2倍的MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)。

          这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的插口(客户的IP地址和端口号,服务器的IP地址和端口号)不能再被使用。这个连接只能在2MSL结束后才能再被使用。2MSL等待期间,插口中使用的本地端口在默认情况下不能再被使用

         在连接处于2MSL等待时,任何迟到的报文段将被丢弃。因为处于2MSL等待的、由该插口对(socket pair)定义的连接在这段时间内不能被使用,因此当要建立一个有效的连接时,来自该连接的一个较早替身(你 carnation的迟到报文段作为新连接的一部分不可能不被曲解(一个连接由一个插口对来定义。一个连接的新的实例(instance)称为该连接的替身)。

 

【平静时间的概念】   

         对于来自某个连接的较早替身的迟到报文段,2MSL等待可防止将它解释成使用相同插口对的新连接的一部分。但这只有在处于2MSL等待连接中的主机处于正常工作状态时才有效

         如果使用处于2MSL等待端口的主机出现故障,它会在MSL秒内重新启动,并立即使用故障前仍处于2MSL的插口对来建立一个新的连接吗?如果是这样,在故障前从这个连接发出而迟到的报文段会被错误地当作属于重启后新连接的报文段。无论如何选择重启后新连接的初始序号,都会发生这种情况。

         为了防止这种情况,RFC 793指出TCP在重启动后的MSL秒内不能建立任何连接。这就称为平静时间(quiet time。大多数主机重启动的时间都比MSL秒要长。

 

FIN_WAIT_2 状态】

         在FIN_WAIT_2状态已经发出了FIN,并且另一端也已对它进行确认。除非在实行半关闭,否则将等待另一端的应用层意识到它已收到一个文件结束符说明,并发一个FIN来关闭另一方向的连接。只有当另一端的进程完成这个关闭,才会从FIN_WAIT_2 状态进入TIME_WAIT状态。一端可能永远保持这个状态,另一端也将处于CLOSE_WAIT状态,并一直保持这个状态直到应用层决定进行关闭

 

【复位报文段】

         TCP首部中的RST比特是用于“复位”的。一般说来,无论何时一个报文段发往基准的连接(referenced connection)出现错误,TCP都会发出一个复位报文段。基准的连接是指目的IP地址和目的端口号以及源IP地址和源端口号指明的连接

 

【到不存在的端口的连接请求】

         产生复位的一种常见情况是当连接请求到时,目的端口没有进程正在听。对于UDP,当一个数据报到达目的端口时,该端口没在使用,它将产生一个ICMP端口不可达的信息。而TCP则使用复位

 

【异常终止一个连接】

         终止一个连接的正常方式是一方你发送FIN,也称为有序释放(orderly release,因为在所有排队数据都已发送之后才发送FIN,正常情况下没有任何数据丢失。但也有可能发送一个复位报文段而不是FIN来中释放一个连接,称为异常释放(abortive release。异常终止一个连接对应用程序来说有2个优点:

         1. 丢弃任何待发数据并立即发送复位报文段

         2. RST的接收方会区分另一端执行的是异常关闭还是正常关闭

        应用程序使用的API必须提供产生异常关闭而不是正常关闭的手段。使用sock程序能够观察这种异常关闭的过程。Sock API通过“linger on close”选项(SO_LINGER)提供了这种异常关闭的能力。加上 -L 选项并将停留时间设为0。这将导致连接关闭时进行复位而不是正常的FIN。

        

【检测半打开连接】

         如果一方已经关闭或异常终止连接而另一方却还不知道,这样的TCP连接称为半打开(Half-Open。任何一端的主机异常都可能导致发生这种情况。只要不打算在半打开连接上传输数据,仍处于连接状态的一方就不会检测另一方已经出现异常。

         半打开连接的另一个常见原因是当客户主机突然掉电而不是正常的结束客户应用程序后再关机,这可能发生在使用PC机作为Telnet的客户主机上。

 

【同时打开】

         两个应用程序同时彼此执行主动打开的情况是可能的,尽管发生的可能性极小。每一方必须发送一个SYN,且这些SYN必须传递给对方。这需要每一方使用一个对方熟知的端口作为本地端口。

         TCP是特意设计为了可以处理同时打开,对于同时打开它仅建立一条连接而不是两条连接(其他的协议族,最突出的是OSI运输层,在这种情况下将建立两条连接而不是一条连接)。如下图:

         过程:两端几乎在同时发送SYN并进入SYN_SENT状态。当每一端收到SYN时,状态变为SYN_RCVD同时它们都再发SYN并对收到的SYN进行确认。当双方都收到SYN及相应的ACK时,状态都变迁为ESTABLISHED一个同时打开的连接需要交换4个报文段,比正常的3次握手多一个。此外,要注意的是没有将任何一端称为客户或服务器,因为每一端既是客户又是服务器。

【同时关闭】

       同时关闭(simultaneous close:当应用层发出关闭命令时,两端均ESTABLISHED变为FIN_WAIT _1。这将导致双方各发送一个FIN,两个FIN经过网络传送后分别到达另一端。收到FIN后,状态由FIN_WAIT_1变迁到CLOSING,并发送最后的ACK。当收到最后的ACK时,状态变化为TIME_WAIT。同时关闭与正常关闭使用的段交换数目相同。4个报文段。

TCP选项】

         大多数的TCP服务器进程是并发。当一个新的连接请求到达服务器时,服务器接受这个请求,并调用一个新进程来处理这个新的客户请求。不同的OS使用不同的技术来调用新的服务器进程。在Unix系统下,常用的技术是使用fork函数来创建新的进程。如果系统支持,也可使用轻型进程,即线程(thread

**当一个服务器进程接受一来自客户进程的服务请求时是如何处理端口?

**如果多个连接请求几乎同时到达会发生什么情况?

TCP服务器端口号】

netstat -a -n -f inet

 

-a

将显示网络中的所有主机端,不仅仅是处于ESTABLISHED的主机端

-n

将以点分十进制的形式显示IP地址,而不是通过DNS将地址转化为主机名,同时还要求显示端口号而不是服务名称。

-f inet

仅要求显示使用TCP或UDP的主机

        TCP使用由本地地址和远端地址组成的4元组= 目的IP地址 目的端口号 IP地址 源端口号来处理传入的多个连接请求。TCP仅通过目的端口号无法确定那个进程接收了一个连接请求。

【限定的本地IP地址】

服务器不能任选其本地IP地址而必须使用特定的IP地址。

         1.如果为sock程序指明一个IP地址(或主机名),并将它作为服务器,那么该IP地址就成为处于LISTEN服务器的本地IP地址。如

Sock -s  IP地址  端口号       ——连接仅局限于来自SLIPIP地址)。

【限定的远端IP地址】

         UDP服务器通常在指定IP本地地址和本地端口外,还能指定远端IP地址和远端端口。RFC 793中显示的接口函数允许一个服务器在执行被动打开时,可指明远端插口(等待一个特定的客户执行主动打开),也可不指明远端插口(等待任何客户)。大多数API都不支持这么做,服务器必须不指明远端插口,而等待连接请求的到来,然后检查客户端的IP地址和端口号。TCP服务器进行连接时3种类型的地址绑定由先到后如下:

          Iport是服务器的熟知端口,而localIP必须是一个本地接口的IP地址。表中由上到下就是绑定的优先顺序。

【呼入连接请求队列】

当服务器正处于忙时,TCP是如何处理这些呼入的连接请求???

         1. 正等待连接请求的一端有一个固定长度的连接队列,该队列中的连接已被TCP接受(即三次握手已经完成),但还没有被应用程序所接受

         注意区分TCP接受一个连接是将其放入这个队列,而应用层接受连接是将其从该队列中移除

         2. 应用层将指明该队列的最长长度,成为积压值(backlog。范围为05之间的整数,包括0和5。

         3. 当一个连接请求(SYN)到达时,TCP使用一个算法根据当前连接队列中的连接数来确定是否接收这个连接。积压值说明的是TCP监听的端点已被TCP接受而等待应用层接受的最大连接数。这个积压值对OS所允许的最大连接数,或者并发服务器所能并发处理的客户数并无影响。

         4. 如果对于新的连接请求,该TCP监听的端点的连接队列中还有空间,TCP模块将对SYN进行确认并完成连接的建立。但应用层只有在3次握手中的第三个报文段收到后才会知道这个新连接时。另外,当客户进程的主动打开成功但服务器的应用层还不知道这个新的连接时,它可能会认为服务器进程已经准备好接收数据了(如果发生这种情况,服务器的TCP仅将接收的数据放入缓冲队列)。

         5. 如果对于新的连接请求,连接队列已没有空间,TCP将不理会收到的SYN,也不发回任何报文段(即不发回RST。如果应用层不能及时接受已被TCP接受的连接,这些连接可能占满整个连接队列,客户的主动打开最终超时。

         当队列已满时,TCP将不理会传入的SYN,也不发回RST作为应答,因为这是一个软错误而不是一个硬错误通常队列已满是由于应用程序或OS忙造成的,这样可防止应用程序对传入的连接进行服务。这个条件在一个很短的时间内可以改变。但如果服务器的TCP以系统复位作为响应,客户进程的主动打开将被废弃(如果服务器程序没有启动)。由于不应答SYN,服务器程序迫使客户TCP随后重传SYN,以等待连接队列又空间接受新的连接 。

~~~~~~~~【TCP的交互数据流】~~~~~~~~

       据研究发现,如果按照分组数量计算,约有一半的TCP报文段包含成块数据(如FTP、电子邮件和Usenet新闻),另一半则包含交互数据(如Telnet和Rlogin)。如果按照字节计算,则成块数据与交互数据的比例约为90%和10%。这是因为成块数据的报文段基本上都是满长度(full-sized,通常为512字节的用户数据),而交互数据则小得多

【交互式输入】

          Rlogin每次总是从客户发送一个字节,而不是每次一行到服务器,并且需要远程系统(服务器)回显客户键入的字符。会产生4个报文段:

1. 来自客户的交互按键;2. 来自服务器的按键确认;3. 来自服务器的按键回显;4. 来自客户的按键回显确认。

         TCP是怎样确认的?!

         第一行以序号0发送数据字节,第2行通过将确认序号设为1,也就是最后成功收到的字节的序号加1,来对其进行确认。

【经受时延的确认】

         绝大多数实现采用的时延为200ms,即TCP将以最大200ms的时延等待是否数据一起发送

Nagle算法】

         该算法要求一个TCP连接上最多只能有一个未被确认的未完成的小分组,在该分组的确认到达之前不能发送其他的小分组。相反,TCP收集这些少量的分组, 并在确认到来时以一个分组的方式发出去。该算法的优越之处在于它是自适应的:确认到达得越快,数据也就发送得越快。而在希望减少微小分组数目的低速广域网上,则会发送更少的分组。

         在以太网上一个字节被发送、确认和回显的平均往返时间约为16ms。为了产生比这个速度更快的数据,每秒键入的字符必须 >= 60。这表明在局域网环境下两个主机之间发送数据时很少使用这个算法。

         插口API用户可以使用TCP_NODELAY选项来关闭Nagle算法,Host Requirements RFC声明TCP必须实现Nagle算法,但必须为应用提供一种方法来关闭该算法在某个连接上执行。

       在较慢的广域网环境中,通常使用Nagle算法来减少这些小报文段的数目。这个算法限制发送者任何时候只能有一个发送的小报文段未被确认。

       对于这小的报文段,接收方使用经受时延的确认方法来判断确认是否可被推迟发送,以便与回送数据一起发送。这样通常会减少报文段的数目,尤其是对于需要回显用户输入字符的Rlogin会话。

        交互数据总是以小于最大报文段长度的分组发送。在Rlogin中通常只有一个字节从客户发送到服务器。Telnet允许一次发送一行输入数据,但是目前大多数实现仍然发送一个字节。

~~~~~~~~【TCP的成块数据流】~~~~~~~~

【正常数据流】

         Sock -i  -s 7777

         -i和-s指示程序作为以一个“吸收(sink)”服务器运行(从网络上读取并丢弃数据),服务器端口指明为7777。相应的客户程序运行为:

         Sock -i -n8 服务器 7777

         该命令指示客户向网络发送8个1024字节的数据。

使用TCP的滑动窗口协议时,接收方不必确认每一个收到的分组。TCP中,ACK是累积的——它们表示接收方已经正确收到了一直到确认序号减1的所有字节

        在线路上看到的分组顺序依赖于许多无法控制的因素:发送方TCP的实现、接收方TCP的实现、接收进程读取数据(依赖于OS的调度)和网络的动态性(如以太网的冲突和退避等)。对这两个TCP而言,没有一种单一的、正确的方法来交换给定数量的数据。

**【滑动窗口】**

 

         当接收方确认数据后,这个滑动窗口不时地向右移动。窗口两个边沿的相对运动增加或减少了窗口的大小。术语描述如下:

         1. 称窗口左边沿向右边沿靠近为窗口合拢。这种现象发生在数据被发送和确认时

         2. 当窗口右边沿向右边移动时将允许发送更多的数据,称之为窗口张开。这种现象发生在另一端的接收进程读取已经确认的数据并释放了TCP的接收缓存时

         3. 当右边沿向左移动时,称之为窗口收缩。Host Requirement RFC强烈建议不要使用这种方式。但TCP必须能够在某一端产生这种情况时处理。

 

         因为窗口的左边沿受另一端发送的确认序号的控制,因此不可能向左边移动。如果接收到一个指示窗口左边沿向左移动的ACK,则它被认为是一个重复ACK,并被丢弃。如果左边沿到达右边沿,则称其为一个零窗口,此时发送方不能够发送任何数据。

         1. 发送方不必发送一个全窗口大小的数据

         2. 来自接收方的一个报文段确认数据并把窗口向右边滑动。这是因为窗口的大小是相对于确认序号的。

         3. 正如从报文段7到报文段8中变化的那样,窗口的大小可以减少,但是窗口的右边沿却不能够向左移动。

         4. 接收方在发送一个ACK前不必等待窗口被填满。

【窗口大小】

         插口API允许进程设置发送和接收缓存的大小,接收缓存的大小是该连接上所能够通告的最大窗口大小。由接收方提供的窗口的大小通常可以由接收进程控制,这将影响TCP的性能。

PUSH标志】

         发送方使用该标志通知接收方将所收到的数据全部提交给接收进程,数据包括与PUSH一起传送的数据以及接收方TCP已经为接收进程收到的其他数据。一个好的TCP实现能够自行决定何时设置这个标志

         代码中的注释表明该算法对那些只有在缓存被填满或收到一个PUSH标志时才向应用程序提交数据的TCP实现有效。使用插口API通知TCP设置正在接收数据的PUSH标志或得到该数据是否被设置PUSH标志的信息是不可能的。

【慢启动】

         如果在发送方和接收方之间存在多个路由器和速率较慢的链路时,就有可能出现一些问题。一些中间路由器必须缓存分组,并可能耗尽存储器的空间。“慢启动(slow start)”算法通过观察到新分组进入网络的速率应该与另一端返回确认的速率相同而进行工作

         慢启动为发送发的TCP增加了另一个窗口:拥塞窗口(congestion window),记为cwnd。当与另一个网络的主机建立TCP连接时,拥塞窗口被初始化为1个报文段(即另一端通告的报文段大小)。每收到一个ACK,拥塞窗口就增加一个报文段(cwnd以字节为单位,但是慢启动以报文段大小为单位进行增加)。发送发取拥塞窗口与通告窗口中的最小值作为发送上限。拥塞窗口是发送方使用的流量控制,而通告窗口则是接收方使用的流量控制

         发送方开始时发送一个报文段,然后等待ACK。当收到该ACK时,拥塞窗口从1增加为2,即可以发送两个报文段。当收到这两个报文段的ACK时,拥塞窗口就增加4。这是一种指数增加的关系

【成块数据吞吐量】

         ACK报文段通常比数据报文段小,因为通常只有一个IP首部➕一个TCP首部。

         ***通常发送一个分组的时间取决于两个因素:传播时延(由光的有限速率、传输设备的等待时间等引起)和一个取决于媒体速率(即媒体每秒可传输的比特数)的发送时延。对于一个给定的两个接点之间的通路,传播时延一般是固定的,而发送时延则取决于分组的大小。在速率较慢的情况下,发送时延起主要作用。

         理想连接稳定状态:发送方和接收方之间的管道(pipe)被填满,不论拥塞窗口和通告窗口是多少,都不能再容纳更多的数据。每当接收方再某一时间单位从网络上移去一个报文段,发送方就再发送一个报文段到网络上。但是不管有多少报文段填充了这个管道,返回路径上总是具有相同数目的ACK

【带宽时延乘积】

         接收方的通告窗口必须 > = 这个数目,因为通告窗口限制了发送方能够发送的段的数目。可以计算通道的容量为:

         不论是带宽or时延均会影响发送方和接收方之间通路的容量。增加一倍的RTT会使通路容量也增加一倍。

【拥塞】

        1.当数据到达一个大的通道(如一个快速局域网)并向一个较小的管道(如一个较慢的广域网)发送时便会发生拥塞

       2. 当多个输入流到达一个路由器,而路由器的输出流小于这些输入流的总和时也会发生拥塞

         大多数的主机都连接在局域网上,并通过一个路由器与速率相对较低的广域网相连。其典型情况如下:

【紧急方式】

        紧急方式(urgent mode:使一端可以告诉另一端有些具有某种方式的“紧急数据”已经放置在普通的数据流中。另一端被通知这个紧急数据已被放置在普通数据流中,由接收方决定如何处理

         可以通过设置TCP首部中的两个字段来发出这种从一端到另一端的紧急数据已经被放置在数据流中的通知。URG比特被置1,并且一个16bit的紧急指针被置为一个正的偏移量,该偏移量必须与TCP首部中的序号字段相加,以便得出紧急数据的最后一个字节的序号。

         TCP必须通知接收进程,何时已接收到一个紧急数据指针以及何时某个紧急数据指针还不在此连接上,或者紧急指针是否在数据流中向前移动。接着接收进程可以读取数据流,并必须能够告诉被告知何时碰到了紧急数据指针。只要从接收方当前读取位置到紧急数据指针之间有数据存在,就认为应用程序处于“紧急方式”在紧急指针通过之后,应用程序便转回到正常方式。TCP本身对紧急数据知之甚少,没有办法指明紧急数据从数据流的何处开始。TCP通过连接传送的唯一信息就是紧急方式已经开始(TCP首部中的URG比特)和指向紧急数据最后一个字节的指针。其他的事情留给application去处理。

***紧急方式有什么用???***

         1.TelnetRlogin从服务器到客户使用紧急方式是因为在这个方向上的数据流很可能要被客户的TCP停止(即通告了一个大小为0的窗口)。但是如果服务器进程进入了紧急方式,尽管它不能够发送任何数据,服务器TCP也会立即发送紧急指针和URG标志。当客户TCP接收到这个通知时就会通知客户进程,于是客户可以从服务器读取其输入、打开窗口并使数据流动。

如果接收方处理第一个紧急指针之前,发送方多次进入紧急方式会发什么情况?

         在数据流中的紧急指针会向前移动,而其在接收方的前一个位置将丢失。接收方只有一个紧急指针,每当对方有新的值到达时它将被覆盖。这意味着如果发送方进入紧急方式时所写的内容对接收方非常重要,那么这些字节数据必须被发送方用某种方式特别标记。

~~~~~~~~【TCP的超时与重传】~~~~~~~~

         TCP提供可靠的运输层,使用的方法之一就是确认从另一端收到的数据。但数据和确认都有可能会丢失。TCP通过在发送时设置一个定时器来解决问题。如果当定时器溢出时还没有收到确认,它就重传该数据。对于每个连接,TCP管理4不同的定时器。

         1. 重传定时器使用于当希望收到另一端的确认。

         2. 坚持(persist)定时器使窗口大小信息保持不断流动,即使另一端关闭了其接收窗口。

         3.  保活(keepalive)定时器可检测一个空闲连接的另一端何时崩溃或重启。

         4. 2MSL定时器测量一个连接处于TIME_WAIT状态的时间。

【往返时间/ RTT测量】

        Karn算法:当一个超时和重传发生时,在重传数据的确认最后到达之前,不能更新RTT估计器,因为不知道ACK对应哪次传输(也许第一次传输被延迟而并没有被丢弃,也有可能第一次传输的ACK被延迟)。

【拥塞避免算法】

         拥塞避免算法是一种处理丢失分组的方法。该算法假定由于分组受到损坏引起的丢失是非常少的(远小于1%),因此分组丢失就意味着在源主机和目的主机之间的某处网络发生了拥塞。有两种分组丢失的指示:发生超时和接收到重复的确认

         拥塞避免算法和慢启动算法是两个目的不同、独立的算法。但是当拥塞发生时,希望能降低分组进入网络的传输速率,于是可以调用慢启动来作到这一点。在实际中这两个算法通常在一起实现。拥塞避免算法和慢启动算法需要对每个连接维持两个变量:一个拥塞窗口cwnd 和一个慢启动门限 ssthresh。具体过程如下:

          1. 对于一个给定的连接,初始化cwnd = 1个报文段,ssthresh = 65535个字节

          2. TCP输出例程的输出不能超过cwnd和接收方通告窗口的大小。拥塞避免是发送方使用的流量控制,而通告窗口则是接收方进行的流量控制。前者是发送方感受到的网络拥塞的估计,而后者则与接收方在该连接上的可用缓存大小有关。
         3. 当拥塞发生时(超时或收到重复确认),ssthresh被设置为当前窗口大小的一半(cwnd和接受方通告窗口大小的最小值,但最少为2个报文段)。此外,如果是超时引起了拥塞,则cwnd被设置为1个报文段(这就是慢启动)。

          4. 当新的数据被对方确认是,就增加cwnd,但增加的方法依赖于是否在进行慢启动或拥塞避免。如果cwnd <= ssthresh,则正在进行慢启动,否则正在进行拥塞避免。慢启动一直持续到回到当拥塞发生时所处位置的半时候才停止,然后转为执行拥塞避免。

~~~~【慢启动算法 VS 拥塞避免算法】~~~~

        慢启动算法:初始设置cwnd1个报文段,此后每收到一个确认就1。这会使窗口按指数方式增长:发送1个报文段,然后是2个,接着是4个……. 慢启动只是采用了比引起拥塞更慢些的分组传输速率,但在慢启动期间进入网络的分组数增加的速率仍然是在增加的。只有在达到ssthresh拥塞避免算法起作用时,这种增加的速率才会慢下来

          拥塞避免算法:要求每次收到一个确认时将cwnd增加 1/cwnd。这是一种加性增长(additive increase。若希望在一个RTT内最多为cwnd增加1个报文段(不管在这个RTT中收到了多少个ACK),然而慢启动将根据这个RTT中收到的确认的个数增加cwnd。对比图如下:

【快速重传与快速恢复算法】

         一个重复的ACK是由一个丢失的报文段引起的,还是由于仅仅出现了几个报文段的重新排序。若是一些报文段的重新排序,则在重新排序的报文段被处理并产生一个新的ACK之前,只可能产生1~2个重复的ACK。如果一连串收到3个或3个以上的重复ACK,就非常可能是丢失了一个报文段。需要重传丢失的数据报文段,而无需等待超时定时器溢出——快速重传算法。接下来执行的不是慢启动而是拥塞避免算法快速恢复算法

这个算法执行过程如下:

         1. 当收到3重复的ACK时,将ssthresh设置为当前拥塞窗口cwnd /2。重传丢失的报文段并设置cwnd = ssthresh3倍的报文段

         2. 每次收到另一个重复的ACK时,cwnd增加1个报文段大小并发送1个分组(如果新的cwnd允许发送)。

        3. 当下一个确认新数据的ACK到达时,设置cwndssthresh(拥塞窗口/2。这个ACK应该是在进行重传后的一个RTT内对步骤1中重传的确认。另外,这个ACK也应该是对丢失的分组和收到的第1个重复的ACK之间的所有中间报文段的确认。这一步采用的是拥塞避免,因为当分组丢失时将当前的速率减半。

【按每条路由进行度量】

         较新的TCP实现在路由表项中维持许多介绍过的指标。当一个TCP连接关闭时,如果已经发送了足够多的数据来获得有意义统计资料,且目的结点的路由表项不是一个默认的表项,那么下列信息就保存在路由表项中以备下次使用:被平滑的RTT、被平滑的均值偏差、慢启动门限。所谓“足够多的数据”是指16个窗口的数据,这样就可得到16个RTT采样,从而使被平滑的RTT过滤器能够集中在正确结果的5%以内。

         而且,管理员可以使用route(8)命令来设置给定路由的度量:前一段中给出的三个指标以及MT、输出的带宽时延乘积、输入的带宽时延乘积。当建立一个新的连接时,不论是主动还是被动,如果该连接将要使用的路由表项已经有这些度量的值,则用这些度量对相应的变量进行初始化。

ICMP差错】

         TCP是怎样处理一个给定的连接返回的ICMP的差错?TCP能够遇到的最常见的ICMP差错就是源站抑制、主机不可达、网络不可达。处理:

         1. 一个接收到的源站抑制引起拥塞窗口cwnd被置为1个报文段大小来发起慢启动,但是慢启动门限ssthresh没有变化,所以窗口将打开直至它或开放了所有的通路(受窗口大小和RTT的限制)或发生了拥塞。

         2. 一个接收到的主机不可达或网络不可达实际上都被忽略,因为这两个差错都被认为是短暂现象。这有可能是由于中间路由器被关闭而导致选路协议要花数分钟才能稳定到另一个替换路由。

         在这个过程中就可能发生这两个ICMP差错中的一个,但是连接并不必被关闭。相反,TCP试图发送引起该差错的数据,尽管最终有可能会超时。

【重新分组】

         当TCP超时并重传时,它不一定要重传同样的报文段。相反,TCP允许进行重新分组而发送一个较大的报文段,这将有助于提高性能<= 接受方声明的MSS)。在协议中这是允许的,因为TCP是使用字节序号而不是报文段序号来进行识别它所要发送的数据和确认

【TCP坚持定时器

        TCP通过让接受方指明希望从发送方接收的数据字节数(即通告窗口大小)来进行Traffic Control。如果窗口大小为0会发生什么情况?这将有效地阻止发送方传送数据,直到窗口变为非0为止。TCP不对ACK报文段进行确认,只确认那些包含有数据的ACK报文段

         如果一个确认丢失了,则双方就有可能因为等待对方而使连接终止:接受方等待接收数据(因为它已经向发送方通告了一个0的窗口),而发送方在等待允许它继续发送数据的窗口更新。为防止这种死锁情况发生,发送方使用了一个坚持定时器(persist timer周期性地接收方查询,以便发现窗口是否已增大,范围在560 s之间。这些从发送方发出的报文段称为窗口探查(window probe

         糊涂窗口综合症SWSSilly Window Syndrome:发生在两端中的任何一端。接收方可以通告一个小的窗口(而不是一直等到有大的窗口是才通告),而发送方也可以发送少量的数据(而不是等待其他的数据以便发送一个大的报文段)。可以在任何一端采取措施避免出现糊涂窗口综合症的现象。

         1. 接受方不通告小窗口。通常的算法是接收方不通告一个比当前窗口大的窗口(可以为0),除非窗口可以增加一个报文段大小(也就是将要接收的MSS)或可以增加接收方缓存空间的一半,不论实际有多少。

         2. 发送方避免出现糊涂窗口综合症的措施是只有以下条件之一满足时才发送数据:A. 可以发送一个满长度的报文段;B. 可以发送至少是接收方通告窗口大小一半的报文段;C. 可以发送任何数据并且不希望接收ACK(也就是没有还未被确认的数据)或该连接上不能使用Nagle算法

         条件(B)主要对付那些总是通告小窗口(也许比1个报文段还小)的主机,条件(C)使在有尚未被确认的数据(正在等待被确认)以及在不能使用Nagle算法的情况下,避免发送小的报文段。如果应用程序在进程小数据的写操作(例如比该报文段还小),条件(C)可以避免出现糊涂窗口综合症。

         在尚未被确认数据情况,若Nagle算法阻止发送小的Data,多小?

         从条件(A)中可以看出所谓“小”是指字节数 < 报文段大小。条件(B)仅用来对付较老的、原始的主机。步骤2中的条件B要求发送方始终监视另一方通告的最大窗口大小,这是一种发送方猜测对方接收缓存大小的企图。虽然在连接建立时接收缓存的大小可能会减少,但在实际中这种情况很少见。

【TCP保活定时器

         场景:启动一个客户与服务器建立一个连接,然后离去数小时、数天、数个星期或数月,而连接依然保持。中间路由器可以崩溃和重启,电话线可以被挂断再连通,但是只有两端的主机没有被重启,则连接依然保持建立。这种非活动状态可以导致应用进程中的任何一个终止其活动。

         许多时候一个服务器希望知道客户主机是否崩溃并关机或者崩溃又重新启动。许多实现提供的保活定时器可以提供这种能力。然而RFC提供了3个不使用保活定时器的理由:A. 在出现短暂差错的情况下,这可能会使一个非常好的连接释放掉;B. 它们耗费不必要的带宽;C. 在按分组计费的情况下会在互联网上花掉更多的?

          在连接两个端系统的网络出现临时故障的时候,保活选项会引起一个实际上很好的连接终止。保活功能主要是为服务器应用程序提供的。服务器应用程序希望知道客户主机是否崩溃,从而可以代表客户使用资源。保活功能就是试图在服务器端检测到这种半开放的连接

         如果一个给定的连接在2个小时之内没有任何动作,则服务器向客户发送一个探查报文段。客户主机必须处于以下4个状态之一

         1. 客户主机仍然正常运行,并从服务器可达。客户的TCP响应正常,而服务器也知道对方是正常工作的。服务器在2个小时以后将保活定时器复位。如果在2个小时定时器到时间之前应用程序的通信量通过此连接,则定时器在交换数据后的未来2个小时再复位。

         2. 客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的TCP都没有响应。服务器将不能够收到探查的响应,并在75s后超时。服务器总共发送10个这样的探查,每个间隔75。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。

         3. 客户主机崩溃并已经重新启动。这时服务器将收到一个对其保活探查的响应,但是这个响应是一个复位,使得服务器终止这个连接。

         4. 客户主机正常运行,但是从服务器不可达。这与状态2相同,因为TCP不能够区分状态4与状态2之间的区别,它所能发现的就是没有接收到探查的响应。

         服务器不用关注客户主机被关闭和重新启动的情况(这指的是一个操作员的关闭,而不是主机崩溃)。当系统被操作员关闭时,所有的应用程序也被终止(也就是客户进程),这会使客户的TCP在连接上发出一个FIN接收到FIN将使服务器的TCP向服务器进程报告文件结束,使服务器可以检测到这个情况。

         在第1种情况下,服务器的应用程序没有感觉到保活探查的发生。TCP层负责一切。这个过程对Application都是透明的/transparent,直至第23 or 4种情况发生。在这3种情况下,服务器application将收到来自它的TCP差错报告(通常服务器已经向网络发出了读操作请求,然后等待来自客户的数据。如果保活功能返回一个差错,则该差错将作为读操作的返回值给服务器)。其差错对应如下:

客户主机已经崩溃,并且关闭或者正在重新启动

类似“连接超时”

客户主机崩溃并已经重新启动

类似“连接被对方复位”

客户主机正常运行,但是从服务器不可达

类似“连接超时”

 

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号