TCP是一个面向连接的协议,在发送数据之前,双方必须通过三次握手协议建立连接,而在终止连接的时候执行4次握手协议。
连接建立和终止先看一下telnet连接服务器80端口的抓包:
上图由wireshark抓取,并显示了TCP状态图(注意:由于网络阻塞,发生了丢包现象,4是对2的重发,而5是对4的响应(同3相同))。
根据上图可以看到建立一个TCP连接的过程为(三次握手的过程):
- 客户端向服务器端发送一个SYN请求,同时传送一个初始序列号(ISN);
- 服务器发回包含客户端初始序列号的SYN报文段作为应答,同时将ACK序号设置为ISN+1;
- 客户端向服务器发送一个ACK确认,ACK序号为ISN+1.
终止一个TCP连接需要4次握手,这是由于TCP的半关闭(当一方调用shutdown关闭连接后,另一端还是可以发送数据,典型的例子为rsh)导致的:TCP连接是全双工的,连接的每一端在关闭连接时都向对方发送一个FIN来终止连接,同时对方会对其进行确认(回复ACK)。通常,都是一方完成主动关闭,另一方来完成被动关闭:
- 以上面的抓包为例,客户端向服务器发送了一个FIN(NO. 6);
- 服务器端对上面的FIN进行确认(NO. 7),同时向客户端发送一个FIN(这儿其实是两个动作,一个是对上面FIN的ACK,另一个是发送一个FIN,但由于TCP的捎带ACK机制,两者放在一个包里发送了);
- 客户端对服务器端的FIN进行确认(NO. 8)。
很多情况下,连接可能不会那么顺利,比如发生了丢包就需要重传(NO. 4)。当连接无法建立的时候,会是什么情况呢,看下抓包情况:
可以看到,当连接失败时会进行几次重试,但在31s后放弃连接。重试的时间间隔很有规律:1,2, 4, 8, 16,即每次的重试间隔都是上次间隔的2倍。
MSS最大报文长度(MSS)表示TCP传往另一端的最大块数据的长度。MSS在连接建立时传送给对方,只会出现在SYN报文段中。
MSS让主机限制另一端发送数据报的长度。
TCP状态变迁图TIME_WAIT状态
- TIME_WAIT状态也称2MSL等待状态,它是任何报文段被丢弃前在网络内的最长时间。当TCP执行一个主动关闭,并发回一个最后ACK,该连接必须处于TIME_WAIT状态,并停留2MSL时间,这可以让TCP再次发送最后的ACK以防这个ACK丢失。
- 在该2MSL期间,该连接占用的端口不可用,任何迟到的报文也会丢弃。通常,为了服务器的快速重启(需要重用端口),可以设置SO_REUSEADDR选项。
- 而对于一个被动关闭的TCP连接来说,不会进入TIME_WAIT状态。
FIN_WAIT_2状态表示我们已经发出了FIN,并且对方已对其进行了确认,但对方还未发回FIN以关闭对方连接。只有当对方完成这个关闭,本端才会由FIN_WAIT_2状态进入TIME_WAIT状态。
复位报文段- 当连接到一个不在监听的端口时,客户端回收到一个RST响应(UDP连接到一个不存在的端口时会产生一个ICMP端口不可达的差错)。
- 在连接终止时,也可以通过发送一个复位报文段而不是FIN来终止连接,可通过设置SO_LINGER来这么做。
- 可通过TCP的SO_KEEPALIVE选项来检测半打开连接,当检测到这种连接时会发送一个RST报文。关于该选项更多的内容参见http://www.tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/。
SO_LINGER 选项
此选项指定函数close对面向连接的协议如何操作(如TCP)。内核缺省close操作是立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方。
SO_LINGER选项用来改变此缺省设置。使用如下结构:
struct linger {
int l_onoff; /* 0 = off, nozero = on */
int l_linger; /* linger time */
};
有下列三种情况:
1、设置 l_onoff为0,则该选项关闭,l_linger的值被忽略,等于内核缺省情况,close调用会立即返回给调用者,如果可能将会传输任何未发送的数据;
2、设置 l_onoff为非0,l_linger为0,则套接口关闭时TCP夭折连接,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST给对方,而不是通常的四分组终止序列,这避免了TIME_WAIT状态;
3、设置 l_onoff 为非0,l_linger为非0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。如果套接口缓冲区中仍残留数据,进程将处于睡眠状态,直 到(a)所有数据发送完且被对方确认,之后进行正常的终止序列(描述字访问计数为0)或(b)延迟时间到。此种情况下,应用程序检查close的返回值是非常重要的,如果在数据发送完并被确认前时间到,close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失。close的成功返回仅告诉我们发送的数据(和FIN)已由对方TCP确认,它并不能告诉我们对方应用进程是否已读了数据。如果套接口设为非阻塞的,它将不等待close完成。
注释:l_linger的单位依赖于实现: 4.4BSD假设其单位是时钟滴答(百分之一秒),但Posix.1g规定单位为秒。
同时打开
两个应用程序同时执行主动打开的情况是可能的,虽然发生的可能性较低。每一端都发送一个SYN,并传递给对方,且每一端都使用对端所知的端口作为本地端口。例如:
主机a中一应用程序使用7777作为本地端口,并连接到主机b 8888端口做主动打开。
主机b中一应用程序使用8888作为本地端口,并连接到主机a 7777端口做主动打开。
tcp协议在遇到这种情况时,只会打开一条连接。
这个连接的建立过程需要4次数据交换,而一个典型的连接建立只需要3次交换(即3次握手)
但多数伯克利版的tcp/ip实现并不支持同时打开。
如果应用程序同时发送FIN,则在发送后会首先进入FIN_WAIT_1状态。在收到对端的FIN后,回复一个ACK,会进入CLOSING状态。在收到对端的ACK后,进入TIME_WAIT状态。这种情况称为同时关闭。
同时关闭也需要有4次报文交换,与典型的关闭相同。
参考《TCP/IP详解》
http://blog.csdn.net/factor2000/article/details/3929816
http://hi.baidu.com/duanzhimin/item/490ca1d970c5bced55347f51