您的位置:首页 > 理论基础 > 计算机网络

TCP 小记————三次握手四次挥手详解

2015-05-15 18:52 495 查看
今天抽空又重新回顾了下TCP协议。自然联想到TCP的三次握手,四次挥手机制。但是为什么要设计成三次握手?又为什么要设置成四次挥手呢?

连接建立

TCP是全双工通信,也就是同一个套接字,客户端和服务端都可以对其进行读和写。当客户端需要建立一个连接时,发起connect的同时,打通写通道,并向服务端发送SYN消息,同时将自身的状态置为SYN_SENT,等待服务端回复ACK。服务端在收到SYN后,将自身状态由LISTEN改为SYN_RCVD。同时,打开服务端的写通道,并且发送ACK并附带SYN,发送到客户端。客户端收到包后,解析得到ACK,表示自己之前发送的SYN已经得到了服务端的接收确认,同时又收到了服务端发来的SYN。此时,对于客户端来说,可以明确两点:1. Client 到 Server端的写通道已经建立;2.Server端也打开了自己的写通道,且通道正常。此时,对于客户端而言,一个全双工的通信链路已经建立,于是将自身的状态变更为ESTABLISHED。对于服务端,此时能明确的也两点:1. Client 到 Server的写通道已经建立;2. Server到Client的写通道已经发生写操作,但是尚未知道是否被Client端收到。基于第二点,Client端有必要发送ACK给Server端。当Server端收到了ACK后,表面两端的全双工通信正在建立,可以放心的进行读写。

其实,对于TCP协议而言,为了保证可靠性,其内核实现本身就是采用的握手方式,即一个TCP数据包对应一个ACK。可以看出,三次握手是最节省资源同时又能保障全双工通信成功建立的方式。连接建立后,客户端和服务端便可以方便的进行读写操作。流程图如下:



连接释放

当一个TCP连接的任务结束后,需要关闭该连接,释放资源。和三次握手不同的是,连接释放采用的是四次挥手的方式。Client端在任务结束后,调用close,发送FIN,同时自身的状态变为FIN_WAIT1。请求关闭的是Client到Server的通信。Server在收到第一个FIN后,明白Client主动发起了关闭,也就不应该在有数据从Client发往Server了,Server要关闭Client到Server这条通信里的读端口。将自身状态置为CLOSE_WAIT,回复Client一个ACK,告诉Client这条单向的连接已经关闭成功。Client收到ACK后,也将状态改为FIN_WAIT2。注意,此时只是关闭全双工通信的一半。Server到Client的write操作依然是正常的,但是read操作将会返回0。如果此时Server端阻塞或其他原因没有调用close,Client端会停留在FIN_WAIT2状态,知道超时2个MSL,然后自动置为CLOSED。正常情况下,Server检测到read失败后,需要调用close。往Client发送一个FIN,让Client关闭监听Server到Client的读端口。并且置为状态TIME_WAIT,然后恢复一个ACK给Server。此时的Client占用的套接字并不是立马置为CLOSED,而是需要等待2个MSL后。这么做是为了确保当前的socket被复用时,网络上不会有之前连接的残留包,引起程序的异常。Server在收到最后一个ACK后,便无所顾虑的直接置为CLOSED。

注意:在释放连接的时候,主动发起close的就是Client,被动发起的就是Server。所以,当一个服务器主动断开连接,那么服务器就是Client的角色。

如下是做的一次测试的结果,Server开启监听端口4000,在通信过程中,关闭Server端的程序。此时,主动发起close的就是Server。可以看到,Server端的连接一直处于FIN_WAIT2状态,超时后直接CLOSED。而Client端的状态也一直停留在CLOSE_WAIT。如果此时Client又调用了write的话,第一次write后会收到RST,第二次再write,系统会发出SIGPIPE,终止程序运行,并将连接置为CLOSED。所以,往FIN_WAIT2状态的套接字write是危险的。不过也可以sigignore该信号。

tcp        0      0 127.0.0.1:4000              127.0.0.1:59613             FIN_WAIT2   -                   timewait (1.66/0/0)
tcp        0      0 127.0.0.1:4000              127.0.0.1:59612             FIN_WAIT2   -                   timewait (1.66/0/0)
tcp        0      0 127.0.0.1:4000              127.0.0.1:59611             FIN_WAIT2   -                   timewait (1.66/0/0)
tcp        0      0 127.0.0.1:4000              127.0.0.1:59610             FIN_WAIT2   -                   timewait (1.66/0/0)
tcp        0      0 127.0.0.1:4000              127.0.0.1:59609             FIN_WAIT2   -                   timewait (1.70/0/0)
tcp        1      0 127.0.0.1:59613             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        1      0 127.0.0.1:59612             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        1      0 127.0.0.1:59611             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        1      0 127.0.0.1:59610             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        1      0 127.0.0.1:59609             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        0      0 127.0.0.1:4000              127.0.0.1:59613             FIN_WAIT2   -                   timewait (0.84/0/0)
tcp        0      0 127.0.0.1:4000              127.0.0.1:59612             FIN_WAIT2   -                   timewait (0.84/0/0)
tcp        0      0 127.0.0.1:4000              127.0.0.1:59611             FIN_WAIT2   -                   timewait (0.84/0/0)
tcp        0      0 127.0.0.1:4000              127.0.0.1:59610             FIN_WAIT2   -                   timewait (0.84/0/0)
tcp        0      0 127.0.0.1:4000              127.0.0.1:59609             FIN_WAIT2   -                   timewait (0.88/0/0)
tcp        1      0 127.0.0.1:59613             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        1      0 127.0.0.1:59612             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        1      0 127.0.0.1:59611             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        1      0 127.0.0.1:59610             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        1      0 127.0.0.1:59609             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        0      0 127.0.0.1:4000              127.0.0.1:59613             FIN_WAIT2   -                   timewait (0.04/0/0)
tcp        0      0 127.0.0.1:4000              127.0.0.1:59612             FIN_WAIT2   -                   timewait (0.04/0/0)
tcp        0      0 127.0.0.1:4000              127.0.0.1:59611             FIN_WAIT2   -                   timewait (0.04/0/0)
tcp        0      0 127.0.0.1:4000              127.0.0.1:59610             FIN_WAIT2   -                   timewait (0.04/0/0)
tcp        0      0 127.0.0.1:4000              127.0.0.1:59609             FIN_WAIT2   -                   timewait (0.08/0/0)
tcp        1      0 127.0.0.1:59613             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        1      0 127.0.0.1:59612             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        1      0 127.0.0.1:59611             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        1      0 127.0.0.1:59610             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        1      0 127.0.0.1:59609             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        1      0 127.0.0.1:59613             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        1      0 127.0.0.1:59612             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        1      0 127.0.0.1:59611             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        1      0 127.0.0.1:59610             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        1      0 127.0.0.1:59609             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        1      0 127.0.0.1:59613             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)
tcp        1      0 127.0.0.1:59612             127.0.0.1:4000              CLOSE_WAIT  7009/Client         off (0.00/0/0)


如果是Client端主动close呢?Server端在read失败后,主动调用close,走完四次挥手的所有流程,Client端变为TIME_WAIT情况如下:

tcp        0      0 127.0.0.1:40386             127.0.0.1:4000              ESTABLISHED 7149/Client         off (0.00/0/0)
tcp        0      0 127.0.0.1:40387             127.0.0.1:4000              ESTABLISHED 7149/Client         off (0.00/0/0)
tcp        0      0 127.0.0.1:40384             127.0.0.1:4000              ESTABLISHED 7149/Client         off (0.00/0/0)
tcp        0      0 127.0.0.1:40385             127.0.0.1:4000              ESTABLISHED 7149/Client         off (0.00/0/0)
tcp        0      0 127.0.0.1:4000              127.0.0.1:40385             ESTABLISHED 7152/Server         off (0.00/0/0)
tcp        0      0 127.0.0.1:4000              127.0.0.1:40384             ESTABLISHED 7151/Server         off (0.00/0/0)
tcp        0      0 127.0.0.1:4000              0.0.0.0:*                   LISTEN      7148/Server         off (0.00/0/0)
tcp        0      0 127.0.0.1:40383             127.0.0.1:4000              TIME_WAIT   -                   timewait (59.56/0/0)
tcp        0      0 127.0.0.1:40386             127.0.0.1:4000              TIME_WAIT   -                   timewait (59.56/0/0)
tcp        0      0 127.0.0.1:40387             127.0.0.1:4000              TIME_WAIT   -                   timewait (59.56/0/0)
tcp        0      0 127.0.0.1:40384             127.0.0.1:4000              TIME_WAIT   -                   timewait (59.56/0/0)
tcp        0      0 127.0.0.1:40385             127.0.0.1:4000              TIME_WAIT   -                   timewait (59.56/0/0)


慢系统调用

慢系统调用:指的是那些可能会一直阻塞的系统函数。比如,accept函数如果一直得不到连接请求,read函数如果一直读不到数据。。。在网络编程中很多函数都是慢系统调用函数。所以,阻塞模式下调用select,epoll会避免阻塞。

EINTR错误

在网络编程中,会使用很多慢系统调用函数,在阻塞模式下,如果系统抛出一个信号,需要被用户捕捉到,则会通过EINTR中断方式触发上下文切换。对于accept、read、recvfrom、select等函数,可以利用重启的方式进行处理。但是客户端的connect不能被重启,因为当connect发送异常,抛出信号后,再次去尝试连接,可能会导致SIGPIPE,引起程序退出。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: