对于一个正常的TCP连接连说,TCP数据被封装在一个I P数据报中,客户端发送IP报文,服务器端接收IP报文。而一个IP报文的组成部分如下
IP和TCP头部一共占40个字节,假设客户端比较变态,每次往服务器端写一个字节的数据,而tcp立即发送附带这一个字节IP报文,那么网络需要传输41字节,但是只有实际信息量只有1字节。以前的互联网带宽比较低,如果有大量这种信息量比率低的报文在internet上传输,会大大影响internet的效率。
所以必须设计一种算法来处理这种情况,客户端可以设定一个超时时间和报文发送的阀值,在超时时间之内,tcp发送缓存里面的数据长度必须达到报文发送阀值才能够发送出去。
一种简单和好的方法就是采用 RFC 896 [Nagle 1984]中所建议的N a g l e算法。
该算法要求一个T C P连接上最多只能有一个未被确认的未完成的小分组,在该分组的确
认到达之前不能发送其他的小分组。相反, T C P收集这些少量的分组,并在确认到来时以一个分组的方式发出去。该算法的优越之处在于它是自适应的:确认到达得越快,数据也就发送得越快。而在希望减少微小分组数目的低速广域网上,则会发送更少的分组
如果才socket中设置了 TCP_NODELAY 为true,则将关闭 N a g l e 算法。
在正常情况,关闭一个tcp连接的过程是 客户端主动发送一个fin报文,然后服务器端收到之后发送一个ack报文,然后服务器端发送一个fin报文,然后客户端在返回一个ack报文。下面一幅图演示了具体的过程:
可以发现,主动发起关闭的一方(一般是客户端),tcp连接的状态会从FIN_WAIT1->FIN_WAIT2->TIME_WAIT->CLOSED
在客户端主动关闭TCP连接之后,并且服务器端也关闭了连接,发送了FIN报文,这个时候客户端就变成了TIME_WAIT状态,这之后就需要发送ack报文给服务器端,服务器端状态变为CLOSED。
这里面就存在一个问题,虽然客户端已经关闭TCP连接了,但是还是需要发送一个ack报文给服务器端,这段时间内客户端TCP连接的状态一直是TIME_WAIT状态,而且必须保证ack报文发送给服务器端。所以这个状态下不能把这个连接重新启用(一个连接就是一个[本地ip,本地端口,远程ip,远程端口],就是说客户端本地端口不能够创建新的连接)。
TIME_WAIT状态要位维持一定的时间,这个时间一般是2倍的报文段最大生存时间M S L(Maximum Segment Lifetime),简称2MSL。在这个时间之后,tcp连接状态变为CLOSED,对应的本地端口上才能够重新创建连接。为什么要这样,如果能够在端口上新创建连接,这个连接上对应的ip报文 服务器端就无法分辨到底是新的连接发送的还是老的连接上的报文信息,毕竟这个时候服务器端TCP通道还可以接收报文。
从上面可以看出,之所以有TIME_WAIT状态,就是确保客户端能够ack 服务器端关闭连接的报文,必须保证服务器端在关闭连接之后,在客户端才能够在同样的端口上创建连接,这样服务器端才能够区分报文的发送方。
某些实现和A P I提供了一种避开这个限制的方法。使用插口A P I时,可说明其中的
S O R E U S E A D D R选项。它将让调用者对处于2 M S L等待的本地端口进行赋值,但我们TCP原则上仍将避免使用仍处于2MSL连接中的端口。
上面描述了TCP报文正常关闭的情况是发送 FIN标志的报文,下面介绍一下TCP头部的 标志位
U R G 紧急指针( u rgent pointer)有效。
A C K 确认序号有效。
P S H 接收方应该尽快将这个报文段交给应用层。
R S T 重建连接。
S Y N 同步序号用来发起一个连接。
F I N 发端完成发送任务。
在某些情况,客户端或者服务端可能不想通过两次交互完成连接的关闭,而是强制关闭连接。这个时候就可以发送RST标志的报文。在这种情况,主动发起连接一方发送RST报文,立即关闭连接。服务器端收到之后RST报文之后,也会关闭连接。这就是我们经常看到的错误:“Connection Reset By Peer”
SO_LINGER设置一个具体的时间,如果时间设置为0,那么当我们关闭 连接的时候,就会像上面我们描述的那样。如果设置时间为timeout,那么在timeout这段时间内,如果数据没有发送完成,会强制发送一个RST报文,关闭连接,如果在timeout时间,报文发送完成,就发送一个FIN报文,正常关闭连接。
这个设置的是超时时间,客户端和服务器端表现的会不一样的。可以设置一个具体的timeout值
对于客户端,在发起连接的时候,如果在timeout时间内没有连接上,就产生异常
对于服务器,在监听客户端连接的时候,也就是accept方法,如果超过了指定时间,就异常。
客户端和服务器端 read数据的时候,如果超过了timeout时间,也会发生异常。
在某一些极端情况下,客户端和服务器端建立了tcp连接,但是客户端和服务器端都不发送数据,这样就导致了这一对连接就闲置在那里或者僵死了。一般情况下,我们可以通过上层应用检测这种情况,一旦在一段时间内客户端没有发送数据,服务器端就关闭连接。
在tcp协议里面,定义了保活定时器,tcp实现会定时的去探测对方端口是否是有效性的,一般情况下,会发送一个 tcp probe(tcp 探针),这个时间是2两个小时。
可能存在以下四种情况
1) 客户主机依然正常运行,并从服务器可达。客户的T C P响应正常,而服务器也知道对方是正常工作的。服务器在两小时以后将保活定时器复位。如果在两个小时定时器到时间之前有应用程序的通信量通过此连接,则定时器在交换数据后的未来2小时再复位。
2) 客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的T C P都没有响应。服务器将不能够收到对探查的响应,并在7 5秒后超时。服务器总共发送1 0个这样的探查,每个间隔7 5秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。
3) 客户主机崩溃并已经重新启动。这时服务器将收到一个对其保活探查的响应,但是这个响应是一个复位(RST),使得服务器终止这个连接。
4) 客户主机正常运行,但是从服务器不可达。这与状态2相同。
由于这个时间是2个小时,并且对上层应用透明,比如如果出现3这种情况,应用对socket操作,就会产生异常。所以一般情况下会设置为fasle ,来关闭这个保活定时器。由上层应用来负责连接管理。
爱公司的程序员
博客园blog地址:http://www.cnblogs.com/aigongsi/
本人版权归作者和博客园所有,欢迎转载,转载请注明出处。