51学通信技术论坛

 找回密码
 立即注册
搜索
查看: 8252|回复: 3
打印 上一主题 下一主题

[原创] TCP,SYN,FIN,ACK,URG,PUSH,RST [复制链接]

Rank: 6Rank: 6Rank: 6Rank: 6Rank: 6Rank: 6

跳转到指定楼层
楼主
发表于 2011-6-21 20:30:32 |只看该作者 |倒序浏览
一键分享 一键分享
以下是实际分析中用到的相关知识,感觉不错拿出来分享,注:网sou

三次握手Three-way Handshake
一个虚拟连接的建立是通过三次握手来实现的

1. (B) –> [SYN] –> (A)
假如服务器A和客户机B通讯. 当A要和B通信时,B首先向A发一个SYN (Synchronize) 标记的包,告诉A请求建立连接.
注意: 一个 SYN包就是仅SYN标记设为1的TCP包(参见TCP包头Resources). 认识到这点很重要,只有当A受到B发来的SYN包,才可建立连接,除此之外别无他法。因此,如果你的防火墙丢弃所有的发往外网接口的SYN包,那么你将不 能让外部任何主机主动建立连接。
2. (B) <;– [SYN/ACK] <;–(A)
接着,A收到后会发一个对SYN包的确认包(SYN/ACK)回去,表示对第一个SYN包的确认,并继续握手操作.
注意: SYN/ACK包是仅SYN 和 ACK 标记为1的包.
3. (B) –> [ACK] –> (A)
B收到SYN/ACK 包,B发一个确认包(ACK),通知A连接已建立。至此,三次握手完成,一个TCP连接完成
Note: ACK包就是仅ACK 标记设为1的TCP包. 需要注意的是当三此握手完成、连接建立以后,TCP连接的每个包都会设置ACK位
这就是为何连接跟踪很重要的原因了. 没有连接跟踪,防火墙将无法判断收到的ACK包是否属于一个已经建立的连接.一般的包过滤(Ipchains)收到ACK包时,会让它通过(这绝对不是个 好主意). 而当状态型防火墙收到此种包时,它会先在连接表中查找是否属于哪个已建连接,否则丢弃该包
四次握手Four-way Handshake
四次握手用来关闭已建立的TCP连接
1. (B) –> ACK/FIN –> (A)
2. (B) <;– ACK <;– (A)
3. (B) <;– ACK/FIN <;– (A)
4. (B) –> ACK –> (A)
注意: 由于TCP连接是双向连接, 因此关闭连接需要在两个方向上做。ACK/FIN 包(ACK 和FIN 标记设为1)通常被认为是FIN(终结)包.然而, 由于连接还没有关闭, FIN包总是打上ACK标记. 没有ACK标记而仅有FIN标记的包不是合法的包,并且通常被认为是恶意的
连接复位Resetting a connection
四次握手不是关闭TCP连接的唯一方法. 有时,如果主机需要尽快关闭连接(或连接超时,端口或主机不可达),RST (Reset)包将被发送. 注意在,由于RST包不是TCP连接中的必须部分, 可以只发送RST包(即不带ACK标记). 但在正常的TCP连接中RST包可以带ACK确认标记
请注意RST包是可以不要收到方确认的?
无效的TCP标记Invalid TCP Flags
到目前为止,你已经看到了 SYN, ACK, FIN, 和RST 标记. 另外,还有PSH (Push) 和URG (Urgent)标记.
最常见的非法组合是SYN/FIN 包. 注意:由于 SYN包是用来初始化连接的, 它不可能和 FIN和RST标记一起出现. 这也是一个恶意攻击.
由于现在大多数防火墙已知 SYN/FIN 包, 别的一些组合,例如SYN/FIN/PSH, SYN/FIN/RST, SYN/FIN/RST/PSH。很明显,当网络中出现这种包时,很你的网络肯定受到攻击了。
别的已知的非法包有FIN (无ACK标记)和”NULL”包。如同早先讨论的,由于ACK/FIN包的出现是为了关闭一个TCP连接,那么正常的FIN包总是带有 ACK 标记。”NULL”包就是没有任何TCP标记的包(URG,ACK,PSH,RST,SYN,FIN都为0)。
到目前为止,正常的网络活动下,TCP协议栈不可能产生带有上面提到的任何一种标记组合的TCP包。当你发现这些不正常的包时,肯定有人对你的网络不怀好意。
UDP (用户数据包协议User Datagram Protocol)
TCP是面向连接的,而UDP是非连接的协议。UDP没有对接受进行确认的标记和确认机制。对丢包的处理是在应用层来完成的。(or accidental arrival).
此处需要重点注意的事情是:在正常情况下,当UDP包到达一个关闭的端口时,会返回一个UDP复位包。由于UDP是非面向连接的, 因此没有任何确认信息来确认包是否正确到达目的地。因此如果你的防火墙丢弃UDP包,它会开放所有的UDP端口(?)。
由于Internet上正常情况下一些包将被丢弃,甚至某些发往已关闭端口(非防火墙的)的UDP包将不会到达目的,它们将返回一个复位UDP包。
因为这个原因,UDP端口扫描总是不精确、不可靠的。
看起来大UDP包的碎片是常见的DOS (Denial of Service)攻击的常见形式 (这里有个DOS攻击的例子,http://grc.com/dos/grcdos.htm ).
ICMP (网间控制消息协议Internet Control Message Protocol)
如同名字一样, ICMP用来在主机/路由器之间传递控制信息的协议。 ICMP包可以包含诊断信息(ping, traceroute – 注意目前unix系统中的traceroute用UDP包而不是ICMP),错误信息(网络/主机/端口 不可达 network/host/port unreachable), 信息(时间戳timestamp, 地址掩码address mask request, etc.),或控制信息 (source quench, redirect, etc.) 。
你可以在http://www.iana.org/assignments/icmp-parameters中找到ICMP包的类型。
尽管ICMP通常是无害的,还是有些类型的ICMP信息需要丢弃。
Redirect (5), Alternate Host Address (6), Router Advertisement (9) 能用来转发通讯。
Echo (8), Timestamp (13) and Address Mask Request (17) 能用来分别判断主机是否起来,本地时间和地址掩码。注意它们是和返回的信息类别有关的。它们自己本身是不能被利用的,但它们泄露出的信息对攻击者是有用 的。
ICMP消息有时也被用来作为DOS攻击的一部分(例如:洪水ping flood ping,死 ping ?呵呵,有趣 ping of death)?/p>
包碎片注意A Note About Packet Fragmentation
如果一个包的大小超过了TCP的最大段长度MSS (Maximum Segment Size) 或MTU (Maximum Transmission Unit),能够把此包发往目的的唯一方法是把此包分片。由于包分片是正常的,它可以被利用来做恶意的攻击。
因为分片的包的第一个分片包含一个包头,若没有包分片的重组功能,包过滤器不可能检测附加的包分片。典型的攻击Typical attacks involve in overlapping the packet data in which packet header is 典型的攻击Typical attacks involve in overlapping the packet data in which packet header isnormal until is it overwritten with different destination IP (or port) thereby bypassing firewall rules。包分片能作为 DOS 攻击的一部分,它可以crash older IP stacks 或涨死CPU连接能力。
Netfilter/Iptables中的连接跟踪代码能自动做分片重组。它仍有弱点,可能受到饱和连接攻击,可以把CPU资源耗光。
握手阶段:
序号 方向 seq ack
1  A->B 10000 0
2 B->A 20000 10000+1=10001
3 A->B 10001 20000+1=20001
解释:
1:A向B发起连接请求,以一个随机数初始化A的seq,这里假设为10000,此时ACK=0
2:B收到A的连接请求后,也以一个随机数初始化B的seq,这里假设为20000,意思是:你的请求我已收到,我这方的数据流就从这个数开始。B的ACK是A的seq加1,即10000+1=10001
3:A收到B的回复后,它的seq是它的上个请求的seq加1,即10000+1=10001,意思也是:你的回复我收到了,我这方的数据流就从这个数开始。A此时的ACK是B的seq加1,即20000+1=20001
数据传输阶段:
序号  方向      seq ack size
23 A->B 40000 70000 1514
24 B->A 70000 40000+1514-54=41460 54
25 A->B 41460 70000+54-54=70000 1514
26 B->A 70000 41460+1514-54=42920 54
解释:
23:B接收到A发来的seq=40000,ack=70000,size=1514的数据包
24:于是B向A也发一个数据包,告诉B,你的上个包我收到了。B的seq就以它收到的数据包的ACK填充,ACK是它收到的数据包的SEQ加上数据包的大小(不包括以太网协议头,IP头,TCP头),以证实B发过来的数据全收到了。
25:A在收到B发过来的ack为41460的数据包时,一看到41460,正好是它的上个数据包的seq加上包的大小,就明白,上次发送的数据包已安全 到达。于是它再发一个数据包给B。这个正在发送的数据包的seq也以它收到的数据包的ACK填充,ACK就以它收到的数据包的seq(70000)加上包 的size(54)填充,即ack=70000+54-54(全是头长,没数据项)。
其实在握手和结束时确认号应该是对方序列号加1,传输数据时则是对方序列号加上对方携带应用层数据的长度.如果从以太网包返回来计算所加的长度,就嫌走弯路了.
另外,如果对方没有数据过来,则自己的确认号不变,序列号为上次的序列号加上本次应用层数据发送长度.



Rank: 6Rank: 6Rank: 6Rank: 6Rank: 6Rank: 6

沙发
发表于 2011-6-21 20:34:29 |只看该作者
注:网搜
http://www.cnblogs.com/lovemo1314/archive/2010/09/26/1835291.html

TCP数据包格式
首部格式:http://en.wikipedia.org/wiki/Transmission_Control_Protocol

建立和关闭连接时,SYN和FIN需要占一个序列号,其它的纯ACK不占序列号。


伪首部计算:http://www.tcpipguide.com/free/t ... PPseudoHeader-2.htm

TCP协议:http://www.networksorcery.com/enp/protocol/tcp.htm




选项:用来传递附加信息给终点。
二、流量控制与可变窗口  








三、TCP建立连接和关闭的三次握手









1.2 关闭一个 TCP 连接
TCP 连接建立起来后,就可以在两个方向传送数据流。当 TCP 的应用进程再没有数据需要发送时,就发关闭命令。 TCP 通过发送控制位 FIN=1 的数据片来关闭本方数据流,但还可以继续接收数据,直到对方关闭那个方向的数据流,连接就关闭。
TCP 协议使用修改的三次握手协议来关闭连接, 如图 3-11 所示,即终止一个连接要经过 4 次握手。这是因为 TCP 的半关闭( half-close )造成的。由于一个 TCP 连接是全双工(即数据在两个方向上能同时传递),因此每个方向必须单独地进行关闭。关闭的原则就是当一方完成它的数据发送任务后就能发送一个 FIN 来终止这个方向连接。当一端收到一个 FIN ,它必须通知应用层另一端已经终止了那个方向的数据传送。发送 FIN 通常是应用层进行关闭的结果。









从一方的 TCP 来说,连接的关闭有三种情况:
•   本方启动关闭
收到本方应用进程的关闭命令后, TCP 在发送完尚未处理的报文段后,发 FIN = 1 的报文段给对方,且 TCP 不再受理本方应用进程的数据发送。在 FIN 以前发送的数据字节,包括 FIN ,都需要对方确认,否则要重传。注意 FIN 也占一个顺序号。一旦收到对方对 FIN 的确认以及对方的 FIN 报文段,本方 TCP 就对该 FIN 进行确认,在等待一段时间,然后关闭连接。等待是为了防止本方的确认报文丢失,避免对方的重传报文干扰新的连接。
•   对方启动关闭
当 TCP 收到对方发来的 FIN 报文时,发 ACK 确认此 FIN 报文,并通知应用进程连接正在关闭。应用进程将以关闭命令响 应。 TCP 在发送完尚未处理的报文段后,发一个 FIN 报文给对方 TCP ,然后等待对方对 FIN 的确认,收到确认后关闭连接。若对方的确认未及时到达,在等待一段时间后也关闭连接。
•   双方同时启动关闭
连接双方的应用进程同时发关闭命令,则双方 TCP 在发送完尚未处理的报文段后,发送 FIN 报文。各方 TCP 在 FIN 前所发报文都得到确认后,发 ACK 确认它收到的 FIN 。各方在收到对方对 FIN 的确认后,同样等待一段时间再关闭连接。这称之为同时关闭( simultaneous close )。
1.3 TCP 状态机
TCP 协议的操作可以使用一个具有 11 种状态的有限状态机( Finite State Machine )来表示,图 3-12 描述了 TCP 的有限状态机,图中的圆角矩形表示状态,箭头表示状态之间的转换,各状态的描述如表 3-2 所示。图中用粗线表示客户端主动和被动的服务器端建立连接的正常过程:客户端的状态变迁用粗实线,服务器端的状态变迁用粗虚线。细线用于不常见的序列,如复位、同时打开、同时关闭等。图中的每条状态变换线上均标有“事件/动作”:事件是指用户执行了系统调用( CONNECT 、 LISTEN 、 SEND 或 CLOSE )、收到一个报文段( SYN 、 FIN 、 ACK 或 RST )、或者是出现了超过两倍最大的分组生命期的情况;动作是指发送一个报文段( SYN 、 FIN 或 ACK )或什么也没有(用“-”表示)。











1. 正常状态转换
我们用图 3-13 来显示在正常的 TCP 连接的建立与终止过程中,客户与服务器所经历的不同状态。读者可以对照图 3-12 来阅读,使用图 3-12 的状态图来跟踪图 3-13 的状态变化过程,以便明白每个状态的变化:



服务器端首先执行 LISTEN 原语进入被动打开状态( LISTEN ),等待客户端连接;
当客户端的一个应用程序发出 CONNECT 命令后,本地的 TCP 实体为其创建一个连接记录并标记为 SYN SENT 状态,然后给服务器发送一个 SYN 报文段;
服务器收到一个 SYN 报文段,其 TCP 实体给客户端发送确认 ACK 报文段同时发送一个 SYN 信号,进入 SYN RCVD 状态;
客户端收到 SYN + ACK 报文段,其 TCP 实体给服务器端发送出三次握手的最后一个 ACK 报文段,并转换为 ESTABLISHED 状态;
服务器端收到确认的 ACK 报文段,完成了三次握手,于是也进入 ESTABLISHED 状态。

   在此状态下,双方可以自由传输数据。当一个应用程序完成数据传输任务后,它需要关闭 TCP 连接。假设仍由客户端发起主动关闭连接。



客户端执行 CLOSE 原语,本地的 TCP 实体发送一个 FIN 报文段并等待响应的确认(进入状态 FIN WAIT 1 );
服务器收到一个 FIN 报文段,它确认客户端的请求发回一个 ACK 报文段,进入 CLOSE WAIT 状态;
客户端收到确认 ACK 报文段,就转移到 FIN WAIT 2 状态,此时连接在一个方向上就断开了;
服务器端应用得到通告后,也执行 CLOSE 原语关闭另一个方向的连接,其本地 TCP 实体向客户端发送一个 FIN 报文段,并进入 LAST ACK 状态,等待最后一个 ACK 确认报文段;
客户端收到 FIN 报文段并确认,进入 TIMED WAIT 状态,此时双方连接均已经断开,但 TCP 要等待一个 2 倍报文段最大生存时间 MSL ( Maximum Segment Lifetime ),确保该连接的所有分组全部消失,以防止出现确认丢失的情况。当定时器超时后, TCP 删除该连接记录,返回到初始状态( CLOSED )。
服务器收到最后一个确认 ACK 报文段,其 TCP 实体便释放该连接,并删除连接记录,返回到初始状态( CLOSED )。










1、建立连接协议(三次握手)
(1)客户端发送一个带SYN标志的TCP报文到服务器。这是三次握手过程中的报文1。

(2) 服务器端回应客户端的,这是三次握手中的第2个报文,这个报文同时带ACK标志和SYN标志。因此它表示对刚才客户端SYN报文的回应;同时又标志SYN给客户端,询问客户端是否准备好进行数据通讯。

(3) 客户必须再次回应服务段一个ACK报文,这是报文段3。

2、连接终止协议(四次握手)
   由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

 (1) TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送(报文段4)。
 (2) 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN将占用一个序号。
 (3) 服务器关闭客户端的连接,发送一个FIN给客户端(报文段6)。
 (4) 客户段发回ACK报文确认,并将确认序号设置为收到序号加1(报文段7)。

CLOSED: 这个没什么好说的了,表示初始状态。

LISTEN: 这个也是非常容易理解的一个状态,表示服务器端的某个SOCKET处于监听状态,可以接受连接了。

SYN_RCVD: 这个状态表示接受到了SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat你是很难看到这种状态的,除非你特意写了一个客户端测试程序,故意将三次TCP握手过程中最后一个ACK报文不予发送。因此这种状态时,当收到客户端的ACK报文后,它会进入到ESTABLISHED状态。

SYN_SENT: 这个状态与SYN_RCVD遥想呼应,当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,因此也随即它会进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。

ESTABLISHED:这个容易理解了,表示连接已经建立了。

FIN_WAIT_1: 这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。

FIN_WAIT_2:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你,稍后再关闭连接。

TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。

CLOSING: 这种状态比较特殊,实际情况中应该是很少见,属于一种比较罕见的例外状态。正常情况下,当你发送FIN报文后,按理来说是应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?其实细想一下,也不难得出结论:那就是如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。

CLOSE_WAIT: 这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。

LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。

最后有2个问题的回答,我自己分析后的结论(不一定保证100%正确)

1、 为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?

这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。

2、 为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?

这是因为:虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文,并保证于此。

注意:在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。

一般来说,tcp正常关闭需要四个包。比如a和b关闭连接,a先给b发一个fin,b会进行确认ack,然后b也会发出fin,当a接受到这个fin,并发出最后一个ack后,就会处于time_wait状态。这个时间长短跟操作系统有关,一般会在1-4分钟,也就是两倍的数据包(2msl)最大生存时间。TCP主动关闭方采用TIME_WAIT主要是为了实现终止 TCP全双工连接的可靠性及允许老的重复分节在网络中消逝,等过了2msl(大约1~4分钟)后TIME_WAIT就会消失。
TIME_WAIT状态的目的是为了防止最后a发出的ack丢失,让b处于LAST_ACK超时重发FIN

所以说,主动发起关闭连接的一方会进入time_wait状态,这个时候,进程所占用的端口号不能被释放。除非在你的程序中用setsockopt设置端口可重用(SOCK_REUSE)的选项,但这不是所有操作系统都支持的


   解决TIME_WAIT的办法主要有以下几种:   


   1、修改LINGER值,缩短关闭时间
   LINGER    lingerStruct;
   lingerStruct.l_onoff    =    1;
   lingerStruct.l_linger     =    0;
   setsockopt(m_socket,SOL_SOCKET,SO_LINGER,(char *)&lingerStruct,sizeof(lingerStruct));
   不过这种办法不是很安全的,不过现在网络都很好啦,不会有问题的。

   2、修改注册表
   [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\Tcpip\Parameters]
   "TcpTimedWaitDelay"=dword:00000005
   这个值好像是300秒到30秒之间,改成30秒后你会发现TIME_WAIT很快就会消失了。


   3、禁用LINGER
   //如果你使用的是Socket API,可以这样
   BOOL bDontLinger=FALSE;      
   setsockopt(m_socket,SOL_SOCKET,SO_DONTLINGER,(LPCTSTR)&bDontLinger,sizeof(BOOL));
   closesocket(s);

   //如果你使用的是CAsyncSocket,需要响应的修改,例如禁用LINGER可以这样
   BOOL bDontLinger=FALSE;   
   m_socket->SetSockOpt(SO_DONTLINGER,(const char *)&bDontLinger,sizeof(bDontLinger),SOL_SOCKET);
   m_socket->Close();

   4、客户端可以不BIND(),这样,即使断开连接后再次连接,SOCKET将使用不同的端口(1025-5000),
   等几分钟后,原有的端口就会自动关闭。

关闭BITCOMET后系统出现的几个TCP状态

Time_wait状态

TIME_WAIT状态是TCP协议中最容易被误解的特性之一。这很可能是因为最初的规约
RFC793中只对该状态做了扼要的解释,尽管后来的RFC,如RFC1185,对TIMEWAIT状态
做了详细说明。设置TIMEWAIT状态的原因主要有两个:
1)它实现了全双工的连接关闭。
2)它使过时的重复报文段作废。
下面我们对这两个原因做进一步的讨论。
TCP全双工关闭
图4-4给出了一般情况下连接关闭时的报文段交换过程。图中还给出了连接状态的变迁和在服务器端测得的RTT值。图中左侧为客户端,右侧为服务器端。要注意,其中的任何一端都可以主动关闭连接,但一般都是客户端执行主动关闭。下面我们来看看最后一个报文段(最后一个ACK)丢失时会发生什么现象。这个现象就给在图4-5中。















由于没有收到客户的最后一个确认,服务器会超时,并重传最后一个FIN报文段。我们特意把服务器的重传超时(RTO)给得比图4-4中的RTT大,这是因为RTO的取值是估计的RTT值加上若干倍的RTT方差(卷2的第25章详细论述了如何测量RTT值以及如何计算RTO)。处理最后一个FIN报文段丢失的方法也是一样:服务器在超时后继续重传FIN。


这个例子说明了为什么TIMEWAIT状态要出现在执行主动关闭的一端:该端发出最后一个ACK报文段,而如果这个ACK丢失或是最后一个FIN丢失了,那么另一端将超时并重传最后的FIN报文段。因此,在主动关闭的一端保留连接的状态信息,这样它才能在需要的时候重传最后的确认报文段;否则,它收到最后的FIN报文段后就无法重传最后一个ACK,而只能发出RST报文段,从而造成虚假的错误信息。图4-5还说明了另一个问题,即如果重传的FIN报文段在客户端主机仍处于TIMEWAIT状态的时候到达,那么不仅仅最后一个ACK会重传,而且TIMEWAIT状态也重新开始。这时,TIMEWAIT状态的持续时间定时器重置为2倍的报文段最大生存时间,即2MSL。
问题是,执行了主动关闭的一端,为了处理图4-5所示的情况,需要在TIMEWAIT状态保持多长的时间?这取决于对端的RTO值;而RTO又取决于该连接的RTT值。RFC1185中指出RTT的值超过1分钟不太可能。但实际上RTO却很有可能长达1分钟:在广域网发生拥塞期间时就会有这种情形。这是因为拥塞会导致多次重传的报文段仍然丢失,从而使TCP的指数退避算法生效,RTO的值越来越大。过时的重复报文段失效设置TIMEWAIT状态的第二个原因是为让过时的重复报文段失效。TCP协议的运行基于一个基本的假设,即:互连网上的每一个IP数据报都有一个有限的生存期限,这个期限值是由IP首部的TTL(生存时间)字段决定的。每一台路由器在转发IP数据报时都要将其TTL值减1;但如果该IP数据报在路由器中等待的时间超过1秒,那就要把TTL的值减去等待的时间。实际上,很少有IP数据报在路由器中的等待时间超过1秒的,因而每个路由器通常都是把TTL的值减1(RFC1812[Baker1995])的5.3.1节)。由于TTL字段的长度是8比特,因此每个IP数据报所能经历的转发次数至多为255。
RFC793把该限制定义为报文段最大生存时间MSL,并规定其值为2分钟。该RFC同时指出,将报文段最大生存时间MSL定义为2分钟是一个工程上的选择,其值可以根据经验进行修改。最后,RFC793规定TIMEWAIT状态的持续时间为MSL的2倍。
图4-6给出的是一个连接关闭后在TIME_WAIT状态保持了2倍报文段最大生存时间(2MSL),然后发起建立新的连接替身。








由于该连接的新的替身必须在前一个连接替身关闭2MSL之后才能再次发起,而且由于前一个连接替身的过时重复报文段在TIMEWAIT状态的第1个报文段最大生存时间里就已经消失,因此我们可以保证前一次连接的过时重复报文段不会在新的连接中出现,也就不可能被误认为是第二次连接的报文段。
TIMEWAIT状态的自结束
RFC793中规定,处于TIMEWAIT状态的连接在收到RST后变迁到CLOSED状态,这称
为TIME_WAIT状态的自结束。RFC1337[Braden1992a]中则建议不要用RST过早地结束TIMEWAIT状态。

主要有两个原因
1。防止上一次连接中的包,迷路后重新出现,影响新连接(经过2MSL,上一次连接中所有的重复包都会消失)
2。可靠的关闭TCP连接在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。

四、TCP确认与重传机制

1累计确认  确认针对数据流中的数据,不是报文段
优点:无二义性,确认丢失不一定会引起重传。

实例:A向B发送数据
1)设发送端窗口为{1201,2000};分2报文段传送;
2)第一个报文段{1201,1600},第二个报文段{1601,2000};
B首先发回确认{1601},再发回确认{2001}
3)若A在定时器未到之前只收到确认{2001},而{1601}丢失,则A不会重传  {1201,1600}报文段;  

  











2超时与重传

(1)定时器时间是由报文段的平均往返时间确定的
TimeOut=B*RTT(Round Trip Time)

(2) RTT的确定

某个报文段的 RTT=收到确认的时间-发送的时间(但是在实际过程中很难测出)

平均RTT=a*Old_RTT+(1-a)*New_RTT
a接近于1的含义:时间不敏感型
a接近于0的含义:时间敏感型





(3) RTT的不精确性

当重传一个数据时,此时收到的确认报文,主机无法判断出到底是对哪个报文的确认。





若认为是对原报文确认,增加了对RTT的估值,降低了效率,该重传也没重传;若认为是对重传报文的确认,RTT值变小,导致正确传送的数据也重传,这就造成了不必要的重传。

RTT的Karn补偿方法

1) 忽略重传报文,不用估算其RTT。
2) 当然就不用重传报文的RTT来修正定时器的Timeout值。
3) 发生重传时,增加Timeout

New_Timeout=r*Old_Timeout
4) Timeout不会无限增加,受上限约束,无重传时,重新估计

Timeout值。实验证明r=2网络就能很好地工作了,小于2会导致时限不稳定。

Karn算法:计算往返估计值时,忽略对应于重传报文段的样本,但是要使用补偿策略,对一个重传分组的许多后续分组,其定时时限均不变,直到获得一个新的有效样本时再修改时限值。

五、拥塞控制

拥塞(Congestion)

拥塞是因为报文在一个或多个路由器发生过栽而导致严重延迟。一旦发生拥塞,延迟显著增加,路由器开始将报文缓存起来,直到它有能力将其发送出去。最坏情况下,到达拥塞路由器的报文持续增加到超过器存储能力,此时路由器就丢弃 (discard)报文。





TCP拥塞控制策略

(1)拥塞发生时,报文传送时延增加、使得定时器超时。在拥塞情况下,定时器超时重传会加重拥塞至网络瘫痪。

(2)TCP拥塞处理策略

1)假设报文丢弃大多数都是拥塞引起的

2)加速递减机制:发生拥塞时的抑制机制:指数递减

3)慢启动:拥塞解除时的恢复机制:指数增加

4)拥塞避免:避免慢起动窗口增长过大的机制恢复:指数增加-〉线性增加

路由器报文丢弃策略与拥塞控制
(1) 早期路由器使用尾部每端丢弃(TAIL-DROP) 策略来处理拥塞:

若报文到来时队列已满,则丢弃该报文。所以,路由器丢弃队列的尾部。尾部丢弃带来的问题就是全局性同步(Global synchronization),路由器不是丢弃一个连接的n个报文段,而是丢弃n个连接的各一个报文段,造成所有 N 个连接的发送端同时进入慢起动。慢启动会显著降低流量。

(2) 解决办法

随机丢弃策略 (Random Early Discard、RED)
1)RED 尽可能避免末端丢弃
2)RED 用两个阈值分别表示队列的位置:Tmin 与 Tmax



图17

RED 基本操作:若目前队列的报文数小于 Tmin,则把新报文加入队列中。若目前队列的报文数大于 Tmax,丢弃新报文。若目前队列的报文数在Tmin 与 Tmax之间,根据一概率P来随机决定是否丢弃此报文。

选择Tmin和Tmax时注意:Tmin 必须大到确保输出链路有高使用率,Tmax - Tmin必须大于在一个tcp往返时间内增加的队列大小(例如 Tmax 为 Tmin 的两倍)。

利用平均队列长度Avg来确定p  

这种策略可以在队列满时p较大;在突发情况下不会丢弃数据。加权平均队列大小为avg=(1–γ)*Old_avg+γ*Current_queue_size

其中γ介于0和1之间。若γ足够小(例如0.002),平均值会呈长期稳定的趋势(亦即old_avg),从而不受突发通讯的影响。



六、糊涂窗口综合症与短分组

主要特征是发送短的报文段,造成带宽浪费。

当收发两端的应用程序以不同的速率工作时,软件性能会出现严重的问题。如接收方每次仅接收一个八位组,发送方能够快速地生成数据,发送方TCP软件所传输的报文段就能很快填满整个缓冲区。接收方每读取一个八位组数据就通告发送方一个八位组的窗口。如此这样,每次就只有一个八位组窗口正常工作,而在传输过程中加上各种协议数据的首部,TCP20字节,IP20字节,有效数据比1/41,加上物理帧头比例更小。这就浪费了网络带宽。

1、接收方糊涂窗口的避免

启发式策略:接收方为当前可用的窗口值维护一个内部记录,并在窗口大小显著增加之前推迟发送增加窗口的通告。窗口大小至少为缓冲区空间的一半或最长报文段数据的字节数时TCP认为是显著的。

通俗点说就是当接收方缓冲区达到饱和时通告一个0窗口,当接收应用程序取到至少占缓冲区一半的八位组数据时再发送更收的窗口通告。



推迟确认技术:就是在收到一个报文段后并不马上发送确认。

优点:降低通信量提高吞吐率。为了使发送方正确估计RTT,至少每隔一个报文段要进行正常的确认。  确认时间最多500ms.



缺点:(1)当接收方确认迟延太大时,发送方会进行报文段的重传,不必要的重传浪费了网络带宽,降低了吞吐率,还加大了收发双方的计算负载。

(2)确认迟延混乱了往返时间的估计值。

2、发送方对糊涂窗口的避免

目的是防止发送短报文段,所以发送方在发送报文段之前必须延迟,以积聚合理的数据量,这就叫组块技术(clumping)。

策略:在一个连接上已经传输的数据还未被确认的情况下,发送方的应用程序又生成了后续数据,并照常将数据送到缓冲区中。但并不发送后续报文段,而是等到数据足以填满一个最大长度的报文段之后再把缓冲区中的数据发送出去。(Nagle算法)











TCP的计时器.



重传计时器---若在计时器截止时间之前收到了对此数据段的确认,则撤销此计时器;若在收到对此特定数据段的确认之前计时器截止期到,则重传此数据段,并将计时器复位.设一次数据传输的往返时间为RTT,则重传时间=2RRT....数据传输的往返时间RTT是利用发送端发送数据时产生的时间戳和当前时间计算得来的.时间戳存放在TCP数据首部的可选项中.



坚持计时器---假定接收端的TCP宣布了窗口大小为0,发送端的TCP将停止传送数据段,直到接收端的TCP发送确认并宣布一个非0的窗口大小为止.但应该注意一点的是,在TCP中对确认是不需要确认的.若确认丢失,接收端的TCP就认为它完成任务了,并等待发送端的TCP发送更多的数据段.发送端的TCP由于没有收到确认,就等等对方发送确认来通知窗口的大小.双方进入了死锁等待的情况.为了解开这个死锁,TCP为每个连接使用一个坚持计时器.当发送端收到一个窗口大小为0的确认时,就启动坚持计时器.当坚持计时器期限到时,发送端的TCP就发送一个特殊的数据段,称为探测数据段,这个数据段只有一个字节的数据.它有一个序号,但它的序号永远不需要确认,它只是提醒接收端的TCP:确认已经丢失,必须重传.坚持计时器的时间值设置是重传时间的数值.但若没有收到接收端来的响应.就需要发送另一个探测数据段,并将坚持计时器的值加倍和复位,直到这个值增大到极限值(通常是60s)为止.在此之后,如果还没有得到响应,发送端每隔60s就发送一个探测数据段,直到窗口重新打开.



保活计时器---用来防止在两个TCP之间的连接长时间空闲.假定客户打开了到服务器的连接,传送了一些数据,然后就保持沉默了,也许这个客户出现了故障.在这种情况下,这个连接将永远地处于打开状态,白白浪费了服务器宝贵的资源...所以每当服务器收到客户的信息就将保活计时器复位.超时时间通常设为2小时.若服务器过了2小时还没有收到客户的信息,它就发送探测数据段.若发送10个探测数据段(每个相隔75s)还没有响应,就假定客户出了故障,因而就中止该连接.



时间等待计时器---TCP协议在断开的时候,如果A发送完最后一个ACK就立即关闭连接,而这时,如果这个ACK数据段丢失了,B无法判断是FIN丢失还是ACK丢失,因此B会重传FIN数据段,而此时A已经关闭了连接,B永远也无法收到A的ACK字段了.
因此TCP协议设置了一个时间等待计时器,A在发送了最后一个ACK报文后,并不立即关闭连接,而是经过一个时间等待计时器的时间再关闭.这个时间可以保证A能收到重复的FIN数据段...由于此时数据连接已经完成了建立和断开的生存周期,所以数据段的寿命期已经知道.时间等待计时器的值通常设置为一个数据段寿命期的两倍..



拥塞控制算法

 Reno是目前应用最广泛且较为成熟的算法。该算法所包含的慢启动、拥塞避免和快速重传、快速恢复机制,是现有的众多算法的基础。

1.慢启动与拥塞避免

TCP发送端采用慢启动和拥塞避免算法来控制向网络输送的数据量。为了实现这些算法,必须向TCP每个连接状态加入3个参量:

丢失窗口(lw):丢失窗口是在一个TCP根据它的重传定时器检测到了数据丢失之后,拥塞窗口的尺寸。

重启窗口(rw):重启窗口是TCP在一段闲置期之后重新开始传送后拥塞窗口的尺寸(如
果使用慢启动算法;

  (1)拥塞窗口(cwnd),如前所述,它是对发送端收到确认(ACK)之前能向网络传送的最大数据量的一个发送端的限制。

  (2)接收端通知窗口(rwnd),它是对未完成数据量的接收端的限制,cwnd和rwnd的最小值决定了数据传送。

  (3)慢启动阀值(ssthresh),被用来确定是用慢启动还是用拥塞避免算法来控制数据传送,具体用法如下:当cwnd<ssthresh时使用慢启动算法;cwnd>ssthresh时使用拥塞避免算法;当cwnd=ssthresh时,发送端既可以使用慢启动也可以使用拥塞避免。ssthresh的初始值可以任意大(比如,一些实现中使用接收端通知窗口的尺寸),但是一旦对拥塞响应之后,其大小可能会被减小。

  在不清楚网络环境的情况下向网络传送数据,要求TCP缓慢地探测网络以确定可用带宽,以避免突然传送大量数据而使网络拥塞。为达此目的,在传送开始时,采用了慢启动机制,这个机制在修复了由重发定时器探测到的数据丢失之后也被采用。

  首先要确定的是cwnd的初始值IW(初始窗口大小),这里规定它必须小于或等于2*SMSS字节而且不能大于两个数据段。

  在慢启动期间,每收到一个新的ACK,cwnd最多增长1。直到cwnd超过ssthresh或者检测到拥塞时,停止执行慢启动算法,转入拥塞避免阶段。在拥塞避免期间,cwnd在每个ACK以1/cwnd(或每个RTT增加SMISS个字节)的速度递增。拥塞避免算法一直保持直到检测出拥塞。等式(5.1.1)给出了一个在拥塞避免期间用来修正cwnd值的公式:

  cwnd+=1/cwnd (5.1.1)

  每收到一个非重复的ACK都采用等式(5.1.1)来调整cwnd。等式(5.1.1)用于近似拥塞避免算法的增长。



在实现中,在拥塞避免期间常用公式:

cwnd+=SMSS*SMSS/cwnd

来修正cwnd的值,当SMSS*SMSS/cwnd<1时,cwnd+=1。

  另一种改进的方案是每当新的ACK到来时记下被新确认的字节数,然后cwnd就可增加相应字节数,这个增加的数目最多可达到SMSS字节。

  一旦TCP发送端使用重传定时器检测到包丢失时,ssthresh的值就如下设置:

  Ssthresh=max(FlightSize/2,2*SMSS) (5.1.2)

  式中,Filght Size是已发送但未收到ACK的数据的大小。

  在重发了丢失的数据段之后,cwnd必须被设置成LW(丢失窗口),它等于一个满尺寸数据段的大小。再发丢失的数据段之后,发送端起用慢启动算法增长窗口直到该窗口大小增长到等于新设置的ssthresh值之后,又采用拥塞避免算法了。



2.快速重传与快速恢复

  当接收端收到一个失序的数据报时,会立即发回一个重复ACK,这个ACK的目的是告知发送端收到一个失序的数据报并说明其所期望的接受序号。从发送端的角度看,重复ACK可能是许多网络问题引起的。首先,它们有可能是因为包丢失而引起。在此情况下,在此数据段之后的所有数据段都会触发重复ACK。其次,重复ACK可能是由于网络对数据段的重新排序引起的。最后,重复ACK有可能是ACK或数据段被网络复制所引起的。此外,当接收端部分或完整地填补了序号空缺应立即发送一个ACK,这样可以更及时地通知发送端,使其迅速从重发状态中恢复过来。快速重传算法如图5-2所示。

  TCP发送端应该使用快速重传算法来探测或者修复数据丢失,在收到3个重复ACK(即连续的4个相同的ACK,标志着1个数据段已丢失)时,TCP不等重传定时器超时就立即重传看来已丢失的数据段。此后起用快速恢复算法来进行新的数据传输,直到1个非重复 ACK到达。

下面是快速传送/快速恢复算法的实现:

(1)当第二个重复ACK收到时,ssthresh根据等式(5.1.2)设值。即

ssthresh=max(FlightSize/2,2*SMSS)

Filght Size是已发送但未收到ACK的数据的大小。

(2)重传丢失的数据段并将cwnd的值设置为ssthresh+3*SMSS,称之为给拥塞窗口“充气”。

cwnd=ssthresh+3*SMSS

(3)此后对每个接收到一个重复ACK,将cwnd增大SMSS字节,这将人为地扩充拥塞窗口用以反映已经离开网络的附加数据段。

  (4)如果cwnd和接收端的通知窗口值允许的话,发送一个数据段。

  (5)当下一个确认新数据的ACK到达时,设定cwnd值为ssthresh(步骤1设置的值),这称作给窗口“放气”。这个ACK必须是步骤1触发的重发引起的确认,重发之后一个RTT(在接收端有次序紊乱的数据段的情况下,它可能一会儿就到达)。另外,此ACK应该确认丢失数据段和第二个重复ACK期间的数据段,如果它们一个也没有丢失的话。

Reno算法的性能分析

  从Reno运行机制中很容易看出,为了维持一个动态平衡,必须周期性地产生一定量的丢失,再加上AIMD机制--减少快,增长慢,尤其是在大窗口环境下,由于一个数据报的丢失所带来的窗口缩小要花费很长的时间来恢复,这样,带宽利用率不可能很高且随着网络的链路带宽不断提升,这种弊端将越来越明显。

公平性方面,根据统计数据,Reno的公平性还是得到了相当的肯定,它能够在较大的网络范围内理想地维持公平性原则。





快速重传与快速恢复算法
       在收到一个失序的报文段时, T C P立即需要产生一个A C K(一个重复的A C K)。这个重复的A C K不应该被迟延。该重复的A C K的目的在于让对方知道收到一个失序的报文段,并告诉对方自己希望收到的序号。
       由于我们不知道一个重复的A C K是由一个丢失的报文段引起的,还是由于仅仅出现了几个报文段的重新排序,因此我们等待少量重复的A C K到来。假如这只是一些报文段的重新排序,则在重新排序的报文段被处理并产生一个新的A C K之前,只可能产生1 ~ 2个重复的A C K。如果一连串收到3个或3个以上的重复A C K,就非常可能是一个报文段丢失了。于是我们就重传丢失的数据报文段,而无需等待超时定时器溢出。这就是快速重传算法。接下来执行的不是慢启动算法而是拥塞避免算法。这就是快速恢复算法。
       没有执行慢启动的原因是由于收到重复的A C K不仅仅告诉我们一个分组丢失了。由于接收方只有在收到另一个报文段时才会产生重复的A C K,而该报文段已经离开了网络并进入了接收方的缓存。也就是说,在收发两端之间仍然有流动的数据,而我们不想执行慢启动来突然减少数据流。这个算法通常按如下过程进行实现:
     1) 当收到第3个重复的A C K时,将s s t h re s h设置为当前拥塞窗口c w n d的一半。重传丢失的报文段。设置c w n d为s s t h re s h加上3倍的报文段大小。
     2) 每次收到另一个重复的A C K时, c w n d增加1个报文段大小并发送1个分组(如果新的c w n d允许发送)。
     3) 当下一个确认新数据的A C K到达时,设置c w n d为s s t h re s h(在第1步中设置的值)。这个A C K应该是在进行重传后的一个往返时间内对步骤1中重传的确认。另外,这个A C K也应该是对丢失的分组和收到的第1个重复的A C K之间的所有中间报文段的确认。这一步采用的是拥塞避免,因为当分组丢失时我们将当前的速率减半。

使用道具 举报

Rank: 6Rank: 6Rank: 6Rank: 6Rank: 6Rank: 6

板凳
发表于 2011-6-21 20:35:38 |只看该作者
网搜:
源端口:16位;

目的端口:16位

序列码:32位,当SYN出现,序列码实际上是初始序列码(ISN),而第一个数据字节是ISN+1;

确认码:32位,如果设置了ACK控制位,这个值表示一个准备接收的包的序列码;

数据偏移量:4位,指示何处数据开始;

保留:6位,这些位必须是0;

控制位:6位;

窗口:16位;

校验位:16位;

优先指针:16位,指向后面是优先数据的字节;

选项:长度不定;但长度必须以字节记;选项的具体内容我们结合具体命令来看;

填充:不定长,填充的内容必须为0,它是为了保证包头的结合和数据的开始处偏移量能够被32整除;

 

我们前面已经说过有一个TCB的东西了,TCB里有存储了包括发送方,接收方的套接字,用户的发送和接收的缓冲区指针等变量。除了这些还有一些变量和发送接收序列号有关:

发送序列变量

SND.UNA - 发送未确认

SND.NXT - 发送下一个

SND.WND - 发送窗口

SND.UP - 发送优先指针

SND.WL1 - 用于最后窗口更新的段序列号

SND.WL2 - 用于最后窗口更新的段确认号

ISS - 初始发送序列号

 

接收序列号

RCV.NXT - 接收下一个

RCV.WND - 接收下一个

RCV.UP - 接收优先指针

IRS - 初始接收序列号

下图会帮助您了解发送序列变量间的关系:



当前段变量

SEG.SEQ - 段序列号

SEG.ACK - 段确认标记

SEG.LEN - 段长

SEG.WND - 段窗口

SEG.UP - 段紧急指针

SEG.PRC - 段优先级

连接进程是通过一系列状态表示的,这些状态有:LISTEN,SYN-SENT,SYN-RECEIVED,ESTABLISHED,FIN-WAIT-1,FIN-WAIT-2,CLOSE-WAIT,CLOSING,LAST-ACK,TIME-WAIT和 CLOSED。CLOSED表示没有连接,各个状态的意义如下:

LISTEN - 侦听来自远方TCP端口的连接请求;

SYN-SENT - 在发送连接请求后等待匹配的连接请求;

SYN-RECEIVED - 在收到和发送一个连接请求后等待对连接请求的确认;

ESTABLISHED - 代表一个打开的连接,数据可以传送给用户;

FIN-WAIT-1 - 等待远程TCP的连接中断请求,或先前的连接中断请求的确认;

FIN-WAIT-2 - 从远程TCP等待连接中断请求;

CLOSE-WAIT - 等待从本地用户发来的连接中断请求;

CLOSING - 等待远程TCP对连接中断的确认;

LAST-ACK - 等待原来发向远程TCP的连接中断请求的确认;

TIME-WAIT - 等待足够的时间以确保远程TCP接收到连接中断请求的确认;

CLOSED - 没有任何连接状态;

TCP连接过程是状态的转换,促使发生状态转换的是用户调用:OPEN,SEND,RECEIVE,CLOSE,ABORT和STATUS;传送过来的数据段,特别那些包括以下标记的数据段SYN,ACK,RST和FIN;还有超时,上面所说的都会时TCP状态发生变化。

 

下面的图表示了TCP状态的转换,但这图中没有包括错误的情况和错误处理,不要把这幅图看成是总说明了。



 

3.3. 序列号

请注意,我们在TCP连接中发送的字节都有一个序列号。因为编了号,所以可以确认它们的收到。对序列号的确认是累积性的,也就是说,如果用户收到对X的确认信息,这表示在X以前的数据(不包括X)都收到了。在每个段中字节是这样安排的:第一个字节在包头后面,按这个顺序排列。我们需要认记实际的序列空间是有限的,虽然很大,但是还是有限的,它的范围是0到2的32次方减1。我想熟悉编程的一定知道为什么要在计算两个段是不是相继的时候要使用2的32次方为模了。TCP必须进行的序列号比较操作种类包括以下几种:

(a) 决定一些发送了的但未确认的序列号;

(b) 决定所有的序列号都已经收到了;

(c) 决定下一个段中应该包括的序列号。

对于发送的数据TCP要接收确认,处理确认时必须进行下面的比较操作:

SND.UNA = 最老的确认了的序列号;

SND.NXT = 下一个要发送的序列号;

SEG.ACK = 接收TCP的确认,接收TCP期待的下一个序列号;

SEG.SEQ = 一个数据段的第一个序列号;

SEG.LEN = 数据段中包括的字节数;

SEG.SEQ+SEG.LEN-1 = 数据段的最后一个序列号。

请注意下面的关系:

SND.UNA < SEG.ACK =< SND.NXT

如果一个数据段的序列号小于等于确认号的值,那么整个数据段就被确认了。而在接收数据时下面的比较操作是必须的:

RCV.NXT = 期待的序列号和接收窗口的最低沿;

RCV.NXT+RCV.WND-1 = 最后一个序列号和接收窗口的最高沿;

SEG.SEQ = 接收到的第一个序列号;

SEG.SEQ+SEG.LEN-1 = 接收到的最后一个序列号;

 

上面几个量有如下关系:

RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND 或 RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND

测试的第一部分是检查数据段的开始部分是否在接收窗口中,第二部分是检查数据段的结束部分是否也在接收窗口内;上面两个检查通过任何一个就说明它包括窗口要求的数据。实际中的情况会更复杂一些,因为有零窗口和零数据段长,因此我们有下面四种情况:

段长度
接收窗口
测试

0
0
SEG.SEQ = RCV.NXT

0
>0
RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND

>0
0
不可接受

>0
>0
RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND或RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND


请注意接收窗口的大小可以为零,在窗口为零时它只用来接收ACK信息,因此对于一个TCP来说,它可以使用零大小窗口在发送数据的同时接收数据。即使接收窗口的大小为零,TCP必须处理所有接收到信息的RST和URG域。

我们也应用计数的方式保护了一些特定的控制信息,这是通过隐式地使用一些控制标记使数据段能够可靠地重新发送(或确认)为达到的。控制信息并不在段数据空间中传送,因此,我们必须采用隐式指定序列号进行控制。SYN和FIN是需要保护的控制量,这两个控制量也只在连接打开和关闭时使用。SYN被认为是在第一个实际数据之间的数据,而FIN是最后一个实际数据之后的数据。段长度(SEG.LEN)包括数据和序列号空间,如果出现了SYN,那么SEG.SEQ是SYN的序列号。

初始序列号选择

协议对于特定连接被重复使用没有什么限制。连接是由一对套接字定义的。新的连接实例被定义为连接的另一次恢复,这就带来了问题:TCP如果确定多个数据段是从以前连接的另一次恢复中取得的呢?这个问题在连接迅速打开和关闭,或因为内存原因被关闭然后又迅速建立后显示特别突出。

为了避免混乱,用户必须避免因此恢复使用某一连接,而使序列号发生混乱。我们必须保证序列号的正确性,即使TCP失败,根本不知道以前的序列号是什么的情况下也要保证序列号的正确性。当新的连接被创建时,产生一个新的初始序列号(ISN)产生子,它用来选择一个新的32位ISN。产生子和32位时钟的低度位字节相关,低位字节的刷新频率大概是4微秒,因此ISN的循环时间大概是4.55小时。因此我们把网络包的最长生存时间(MSL)小于4.55小时,因此我们可以认为ISN是唯一的。对于每个连接都有发送序列号和接收序列号,初始发送序列号(ISS)由发送TCP选择,而初始接收序列号是在连接建立过程中产生的。

对于将要连接或初始化的连接,两个TCP必须和对方的初始序列号同步。这通过交换一个控制位SYN和初始序列号完成。我们把带有SYN的数据段称为"SYNs"。同步的获得过程这里就不重复了,每方必须发送自己的序列号并返回对对方序列号的确认。

1) A --> B SYN 本方序列号是X

2) A <-- B ACK 本方序列号被确认

3) A <-- B SYN 对方序列号是Y

4) A --> B ACK 确认对方序列号

上面的第2步和第3步可以合并,这时可以成为3阶段,所以我们可以称它为三消息握手。这个过程是必须的,因为序列号不和全局时钟关联,TCP也可以有不同的机制选择ISN。接收到第一个SYN的接收方不可能知道这个数据段是不是被延时,除非它记住了在连接上使用的最近的序列号(这通常是不可能的),因此它必须要求发送者确认。

为了保证TCP获得的确认是刚才发送的段产生的,而不是仍然在网络中的老数据段产生的,因此TCP必须在MSL时间之内保持沉默。在本文中,我们假设MSL=2小时,这是出于工程的需要,如果用户觉得可以,他可以改变MSL。请注意如果TCP重新初始化,而内存中的序列号正在使用,不需要等待,但必须确认使用的序列号比当前使用的要大。

如果一台主机在未保留任何序列号的情况下失败,那么它应该在MSL时间之内不发出任何数据段。下面将会这一情况进行说明。TCP的实现可以不遵守这个规定,但是这会造成老数据被当成新数据接收,而新数据被当成老数据拒绝的情况。

每当数据段形成并进入输出队列,TCP会为它指定序列空间中的一个值。TCP中多复本检测和序列算法都依赖于这个地址空间,在对方发送或接收之前不会超过2的32次方个包存在于输出队列中。所有多余的数据段都会被删除。如果没有这个规定,会出现多个数据段被指定同一个序列号的情况,会造成混乱。数据段中序列号的多少和数据段中的字节数一样多。

在通常情况下,TCP保留下一个要发送的序列号和还未确认的最老的序列号,不要在没有确认的时候就再次使用,这样会有些风险,也正是因为这样的目的,所以序列空间很大。对于2M的网络,要4.5小时来耗尽序列空间,因为一个数据段可能的最大生存时间也不过十几分之一秒,这就留下了足够的空间;而在100M的网络上需要5.4分钟,虽然少了点,但也可以了。

如果在实现TCP时没有为保存序列号留下空间,那清除多余的包可能就不能实现了,因此推荐这种类型的TCP实现最好在失败后等待MSL时间,这样保证多余的包被删除。这种情况有时候也可能会出现在保留序列号的TCP实现中。如果TCP在选择一个另一个TCP连接正在使用的序列号时,这台主机突然失败了,这就产生了问题。这个问题的实质在于主机不知道它失败了多久,也不知道多余的复本是不是还在网络中。

处理这种问题的方法是等待MSL时间,如果不这样就要冒着对方错误接收数据的危险,要等待的时间也就称为“沉默时间”。实现者可以让用户选择是不是等待,但是无论用户如何也不见得非要等待MSL时间。

3.4. 建立一个连接

建立连接应用的是三消息握手。如果双方同时都发送SYN也没有关系,双方会发现这个SYN中没有确认,于是就知道了这种情况,通常来说,应该发送一个"reset"段来解决这种情况。三消息握手减少了连接失败的可能性。下面就是一个例子,在尖括号是的就是数据段中的内容和标记。其它的就不多说了。



在第2行,TCP A发送SYN初始化序列号,表示它要使用序列号100;第3行中,TCP B给出确认,并且期待着A的带有序列号101的数据段;第4行,TCP A给出确认,而在第5行,它也给出确认,并发送了一些数据,注意第4行的序列号与第5号的一样,因为ACK信息不占用序列号空间内的序列号。同时产生请求的情况如下图所示,只复杂一点。



使用三消息握手的主要原因是为了防止使用过期的数据段。为了这个目的,必须引入新的控制消息,RESET。如果接收TCP处理非同步状态,在接收到RESET后返回到LISTEN状态。如果TCP处理下面几种状态ESTABLISHED,FIN-WAIT-1,FIN-WAIT-2,CLOSE-WAIT,CLOSING,LAST-ACK,TIME-WAIT时,放弃连接并通过用户。我们下面就详细说明后一种情况。



通过上面的例子,我们可以看出TCP连接是如何从过期数据段的干扰下恢复的。请注意第4行和第5行中的RST(RESET信号)。

半开连接和其它非正常状态

如果一方在未通过另一方的情况下关闭连接,或双方虽然失败而不同步的情况我们称为半开连接状态。在一方试图发送数据时连接会自动RESET。然而这种情况毕竟属于不正常情况。应该做出相应的处理。如果A处的连接已经关闭,B处并不知道。当B希望发送数据到A时,就会收到RESET信号,表示这个TCP连接有误,要中止当前连接。

假设A和B两个进程相互通信的时候A的TCP发生了失败,A依靠操作系统支持TCP的存在,通常这种情况下会有恢复机制起作用,当TCP重新恢复的时候,A可能希望从恢复点开始工作。这样A可能会试图OPEN连接,然后在这个它认为还是打开的连接上传送数据,这时A会从本地(也就是A的)TCP上获得错误消息“未打开连接”。A的TCP将发送包括SYN的数据段。下面的例子将显示这一过程:



上面这个例子中,A方收到的信息并没有确认任何东西,这时候A发现出了问题,于是发送了RST控制信息。另一种情况是发生在A失败,而B方仍然试图发送数据时,下面的例子可以表示这种情况,请注意第2行中A对B发送来的信息不知所云。



在下面的例子中,A方和B方进行的被动连接,它们都在等待SYN信息。过期的包传送到B方使B回应了,而收到回应的A却发现不对头,传送RST控制信息,B方返回被动LISTEN状态。



现实中的情况太多了,我们列举一些产生RST控制信息的规则如下:通常情况下,RST在收到的信息不是期待的信息时产生。如果在不能确定时不要轻易发送RST控制信息。下面有三类情况:

如果连接已经不存在,而发送来的消息又不是RST,那么要返回RST。如果想拒绝对不存在的连接进行SYN,可以使用这种办法。如果到达的信息有一个ACK域,返回的RST信息可以从ACK域中取得序列号,如果没有这个域,就把RST的序列号设置为0,ACK域被设备为序列号和到达段长度之和。连接仍然处于CLOSE状态。

如果连接处于非同步状态(LISTEN,SYN-SENT,SYN-RECEIVED),而且收到的确认是对未发出包的确认或是接收到数据段的安全级别与不能连接要求的相一一致时,就发送RST。如果SYN未被确认时,而且收到的数据段的优先级比要求的优先级要高,那么要么提高本地优先级(得事先征得用户和系统的许可)要么发送RST;如果接收数据段的优先级比要求的优先级低,就算是匹配了,当然如果对方发现优先级不对提高了优先级,在下一个包中提高了优先级,这就不算是匹配了。如果连接已经进入SYN,那么接收到数据段的优先级必须和本地优先级一样,否则发送RST。如果到达的信息有一个ACK域,返回的RST信息可以从ACK域中取得序列号,如果没有这个域,就把RST的序列号设置为0,ACK域被设备为序列号和到达段长度之和。连接仍然处于与原来相同的状态。

如果连接处于同步状态(ESTABLISHED,FIN-WAIT-1,FIN-WAIT-2,CLOSE-WAIT,CLOSING,LAST-ACK,TIME-WAIT),任何超出接收窗口的序列号的数据段都产生如下结果:发出一个空确认数据段,此段中包括当前发送序列号,另外还包括一个确认指出希望接收的下一个数据段的序列号,连接仍然保存在原来的状态。如果因为安全级,优先级之类的问题,那就发送RST信号然后进入CLOSED状态。

使用道具 举报

Rank: 9Rank: 9

懒

地板
发表于 2011-6-21 20:38:57 |只看该作者
谢谢!辛苦了!对学习TCP很有帮助!特别是有很多基于TCP的应用。
www.gprshome.com: GPRS及移动通信技术学习交流分享平台。

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

站长邮箱|Archiver|51学通信 ( 粤ICP备11025688 )

GMT+8, 2024-4-23 17:15 , Processed in 0.023314 second(s), 12 queries .

Powered by Discuz! X2

© 2001-2011 Comsenz Inc.

回顶部