【TCP】TCP连接建立和终止
2015-03-03 16:38
351 查看
对于TCP连接建立和终止状态分析,基于自己编写的代码来逐步来分析,而现成的例子并不能全面的进行状态分析;比如listen中backlog对三次握手的影响;
![](http://img.blog.csdn.net/20150418200952842?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2t5dXBwb3Vy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
![](http://img.blog.csdn.net/20150418201046756?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2t5dXBwb3Vy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
使用lsof查看监听描述符,编号为3;
(2)三次握手的过程由内核完成的,在accept返回之前三次握手已经完成;三次握手具体过程具体如下:
(a)客户端执行connect主动打开时,将会发送SYN,客户端从CLOSED变为SYN_SENT;
(b)服务器端接收SYN,发送SYN+ACK;服务器从LISTEN至SYN_RCV;
(c)客户端接收SYN+ACK,发送ACK;客户端从SYN_SENT至ESTABLISHED
(d)服务器端接收ACK;服务器从SYN_RCVD至ESTABLISHED;
仍在sockets::listen(_listenfd);后加入语句::sleep(100);观察三次握手的过程;打开一个客户端,使用netstat查看如下,其中会产生两个套接字对(ESTABLISHED),第1个是客户端的,第2个是服务端的;
三次握手wireshark截图:
![](http://img.blog.csdn.net/20150303171946766?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2t5dXBwb3Vy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
使用lsof查看描述符,会增加一个连接描述符号,client的连接描述符编号为3;由于server未从accept中返回,因此虽然TCP的连接已经建立,但是服务器端连接描述符还没有返回;
wireshark截图:服务端超时重传发送SYN+ACK,客户端发送ACK确认,但是服务器由于已完成连接的队列(ESTABLISHED状态)已满,丢弃ACK,继续超时重传SYN+ACK,5次后放弃;
![](http://img.blog.csdn.net/20150303182138902?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2t5dXBwb3Vy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
使用netstat查看如下,服务器超时重传5次后放弃,消除了SYN_RECV状态,此时此连接已经断开;但是客户端(端口号为41985)的连接仍是ESTABLISHED状态,如果客户端发送数据,服务器将会发送RESET报文;
RESET报文wireshark截图:
![](http://img.blog.csdn.net/20150303185249765?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2t5dXBwb3Vy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
(2)此时关闭第一个客户端1(端口号为43888),那么服务器端将会read到0(read返回0前,就已经发送ACK,客户端状态已变为FIN_WAIT_2),从而执行到close后客户端1转变为TIME_WAIT;而且已完成连接队列将会多出一个位置,当服务端内核再次超时重传发送SYN+ACK时,客户端(端口号为43892)发送SYN的ACK,服务端将会从SYN_RECV状态变为ESTABLISHED状态;具体如下:
![](http://img.blog.csdn.net/20150303211122272?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2t5dXBwb3Vy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
(3)若此时不关闭第一个客户端1,而关闭客户端3;此时由于服务器仍阻塞在执行read客户端1数据上,因此不能执行close,服务端(端口号为43826)将处于CLOSE_WAIT状态;客户端3(端口号为43826)将位于FIN_WAIT_2状态;端口号为43828的服务端状态仍旧为SYN_RECV;
(3.2)此时若关闭客户端1,2,服务器将依次read为0(read返回0前,就已经发送了ACK,客户端状态已变为FIN_WAIT_2),close后客户端状态将转变为TIME_WAIT,因此客户端1,2将处于TIME_WAIT状态;服务器端程序将会accept返回到43826,read为0,(服务端状态为CLOSE_WAIT)close发送FIN,但是客户端3上的连接已经从FIN_WAIT_2状态消失,将会已RESET报文进行回复;
RESET报文wireshark截图:
![](http://img.blog.csdn.net/20150303204703508?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2t5dXBwb3Vy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
(2)关闭服务器,状态如下;服务器只针对端口号为43899的客户端进行正常的终止,也就是和第一个客户端进行正常的序列终止;而对于端口号43900和43901都是直接使用RESET报文进行响应的,因为此时服务器端的连接已经消失;
关闭服务器wireshark截图:
![](http://img.blog.csdn.net/20150303212131235?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2t5dXBwb3Vy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
|-- Net
| |-- CMakeLists.txt
| |-- SocketsOps.cc
| |-- SocketsOps.h
|-- test4
| |-- CMakeLists.txt
| |-- TcpClient.cc
| |-- TcpClient.h
| |-- TcpServer.cc
| `-- TcpServer.h
(注,其中Net目录下,本程序目前只需要使用SocketsOps.h,SocketsOps.cc)
线程模型:posix
gcc 版本: 4.9.0 (GCC),注(支持C++11 )
Cmake版本:cmake 2.8.0
https://github.com/sykpour/MyCode/tree/master/Linux/test4
(注,本节关键代码位于test4中)
TCP连接的建立和终止示意图
TCP状态转换图
服务器端核心代码
void TcpServer::run() { assert(!_started); sockets::bind(_listenfd, _serverAddress); sockets::listen(_listenfd); printf("TcpServer start...\n"); _started = true; while (_started) { int connfd = sockets::accept(_listenfd, NULL); if (connfd < 0) continue; char buf[20]; int n; while ((n = sockets::read(connfd, buf, sizeof buf)) > 0) { sockets::writen(connfd, buf, n); } if (n <= 0) ::close(connfd); } }说明:上述代码是一个TCP迭代服务器的核心代码;服务器端口号为6002;
客户端核心代码
void TcpClient::request(const struct sockaddr_in& serverAddr) { int err = sockets::connect(_connectfd, serverAddr); if (err < 0) { printf("In TcpClient::request, connect error : %s\n", strerror_r(errno, g_errorbuf, sizeof g_errorbuf)); return; } char buf[10]; while (fgets(buf, sizeof buf, stdin)) { int n = sockets::writen(_connectfd, buf, strlen(buf) + 1); printf("client send (%d bytes): %s\n", n , buf); if ((n = sockets::readn(_connectfd, buf, n)) == 0) //receive Fin { printf("server exit\n"); break; } printf("client receive : %s\n", buf); } }说明:上述代码是一个TCP客户端的核心代码;请求的服务器端口号为6002;
TCP连接建立
(1)在服务器端核心代码中sockets::listen(_listenfd);后加入语句::sleep(100);使用netstat查看如下,选项-a表示查看所有的状态;TCP服务器被动打开,从CLOSED至LISTEN;sykpour@sykpour:~/Desktop/MyCode/Linux/build/bin$ netstat -a -n | grep 6002 tcp 0 0 0.0.0.0:6002 0.0.0.0:* LISTEN
使用lsof查看监听描述符,编号为3;
sykpour@sykpour:~/Desktop/MyCode/Linux/build/bin$ lsof -i@0.0.0.0:6002 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME server 23180 sykpour 3u IPv4 249295 0t0 TCP *:x11-2 (LISTEN)
(2)三次握手的过程由内核完成的,在accept返回之前三次握手已经完成;三次握手具体过程具体如下:
(a)客户端执行connect主动打开时,将会发送SYN,客户端从CLOSED变为SYN_SENT;
(b)服务器端接收SYN,发送SYN+ACK;服务器从LISTEN至SYN_RCV;
(c)客户端接收SYN+ACK,发送ACK;客户端从SYN_SENT至ESTABLISHED
(d)服务器端接收ACK;服务器从SYN_RCVD至ESTABLISHED;
仍在sockets::listen(_listenfd);后加入语句::sleep(100);观察三次握手的过程;打开一个客户端,使用netstat查看如下,其中会产生两个套接字对(ESTABLISHED),第1个是客户端的,第2个是服务端的;
sykpour@sykpour:~/Desktop/MyCode/Linux/build/bin$ netstat -a -n | grep 6002 tcp 0 0 0.0.0.0:6002 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:6002 127.0.0.1:41922 ESTABLISHED
tcp 0 0 127.0.0.1:41922 127.0.0.1:6002 ESTABLISHED
三次握手wireshark截图:
使用lsof查看描述符,会增加一个连接描述符号,client的连接描述符编号为3;由于server未从accept中返回,因此虽然TCP的连接已经建立,但是服务器端连接描述符还没有返回;
sykpour@sykpour:~/Desktop/MyCode/Linux/build/bin$ lsof -i@0.0.0.0:6002 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME server 24128 sykpour 3u IPv4 252661 0t0 TCP *:x11-2 (LISTEN) client 24155 sykpour 3u IPv4 252728 0t0 TCP localhost:41922->localhost:x11-2 (ESTABLISHED)(3)在listen中,有一个backlog参数,它影响已完成连接的队列(ESTABLISHD状态, 如今已经不影响未完成连接队列(SYN_RCVD状态);本程序中backlog为2;从下面输出可知,服务器端已经完成ESTABLISHED状态的实际数目为4,客户端(端口号为41985)已经进入了ESTABLISHED状态,而服务器端处于SYN_RECV状态(原因是已完成连接队列已满,服务器端不会接收41895的ACK);就算此时客户端(41985)发送数据也只是写入了对应的内核缓冲区,不能代表服务器已经接收了这些数据,客户端的内核程序将会超时重传这些数据;
sykpour@sykpour:~/Desktop/MyCode/Linux/build/bin$ netstat -a -n | grep 6002 tcp 0 0 0.0.0.0:6002 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:6002 127.0.0.1:41985 SYN_RECV
tcp 0 0 127.0.0.1:6002 127.0.0.1:41970 ESTABLISHED
tcp 0 0 127.0.0.1:41972 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:6002 127.0.0.1:41971 ESTABLISHED
tcp 0 0 127.0.0.1:6002 127.0.0.1:41972 ESTABLISHED
tcp 0 0 127.0.0.1:41985 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:6002 127.0.0.1:41969 ESTABLISHED
tcp 0 0 127.0.0.1:41969 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:41971 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:41970 127.0.0.1:6002 ESTABLISHED
wireshark截图:服务端超时重传发送SYN+ACK,客户端发送ACK确认,但是服务器由于已完成连接的队列(ESTABLISHED状态)已满,丢弃ACK,继续超时重传SYN+ACK,5次后放弃;
使用netstat查看如下,服务器超时重传5次后放弃,消除了SYN_RECV状态,此时此连接已经断开;但是客户端(端口号为41985)的连接仍是ESTABLISHED状态,如果客户端发送数据,服务器将会发送RESET报文;
sykpour@sykpour:~/Desktop/MyCode/Linux/build/bin$ netstat -a -n | grep 6002 tcp 0 0 0.0.0.0:6002 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:6002 127.0.0.1:41970 ESTABLISHED
tcp 0 0 127.0.0.1:41972 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:6002 127.0.0.1:41971 ESTABLISHED
tcp 0 0 127.0.0.1:6002 127.0.0.1:41972 ESTABLISHED
tcp 0 0 127.0.0.1:41985 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:6002 127.0.0.1:41969 ESTABLISHED
tcp 0 0 127.0.0.1:41969 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:41971 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:41970 127.0.0.1:6002 ESTABLISHED
RESET报文wireshark截图:
TCP连接终止
客户端主动关闭
(1)启动服务器,由上backlog=2,可打开4个客户端1, 2, 3 , 4,服务端和客户端都可以建立好ESTABLISHED状态;而由于服务器是一个迭代服务器,打开第5个客户端时,服务器状态为SYN_RECV状态;具体状态如下:sykpour@sykpour:~/Desktop/MyCode/Linux/build/bin$ netstat -a -n | grep 6002 tcp 0 0 0.0.0.0:6002 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:6002 127.0.0.1:43892 SYN_RECV
tcp 0 0 127.0.0.1:6002 127.0.0.1:43889 ESTABLISHED
tcp 0 0 127.0.0.1:43889 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:43888 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:6002 127.0.0.1:43891 ESTABLISHED
tcp 0 0 127.0.0.1:6002 127.0.0.1:43888 ESTABLISHED
tcp 0 0 127.0.0.1:43892 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:6002 127.0.0.1:43890 ESTABLISHED
tcp 0 0 127.0.0.1:43890 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:43891 127.0.0.1:6002 ESTABLISHED
(2)此时关闭第一个客户端1(端口号为43888),那么服务器端将会read到0(read返回0前,就已经发送ACK,客户端状态已变为FIN_WAIT_2),从而执行到close后客户端1转变为TIME_WAIT;而且已完成连接队列将会多出一个位置,当服务端内核再次超时重传发送SYN+ACK时,客户端(端口号为43892)发送SYN的ACK,服务端将会从SYN_RECV状态变为ESTABLISHED状态;具体如下:
sykpour@sykpour:~/Desktop/MyCode/Linux/build/bin$ netstat -a -n | grep 6002 tcp 0 0 0.0.0.0:6002 0.0.0.0:* LISTEN报文交互wireshark截图:
tcp 0 0 127.0.0.1:6002 127.0.0.1:43892 ESTABLISHED
tcp 0 0 127.0.0.1:6002 127.0.0.1:43889 ESTABLISHED
tcp 0 0 127.0.0.1:43889 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:43888 127.0.0.1:6002 TIME_WAIT
tcp 0 0 127.0.0.1:6002 127.0.0.1:43891 ESTABLISHED
tcp 0 0 127.0.0.1:43892 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:6002 127.0.0.1:43890 ESTABLISHED
tcp 0 0 127.0.0.1:43890 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:43891 127.0.0.1:6002 ESTABLISHED
(3)若此时不关闭第一个客户端1,而关闭客户端3;此时由于服务器仍阻塞在执行read客户端1数据上,因此不能执行close,服务端(端口号为43826)将处于CLOSE_WAIT状态;客户端3(端口号为43826)将位于FIN_WAIT_2状态;端口号为43828的服务端状态仍旧为SYN_RECV;
sykpour@sykpour:~/Desktop/MyCode/Linux/build/bin$ netstat -a -n | grep 6002 tcp 0 0 0.0.0.0:6002 0.0.0.0:* LISTEN(3.1)经过一段时间后,查看状态如下;此时客户端3(端口号为43826)的FIN_WAIT_2状态消失,但是服务端(端口号为43826)的CLOSE_WAIT仍旧存在,而且端口号为43828的客户端SYN_RECV状态已经消失;
tcp 0 0 127.0.0.1:6002 127.0.0.1:43828 SYN_RECV
tcp 0 0 127.0.0.1:43826 127.0.0.1:6002 FIN_WAIT2
tcp 0 0 127.0.0.1:6002 127.0.0.1:43824 ESTABLISHED
tcp 0 0 127.0.0.1:43828 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:43827 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:43824 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:43825 127.0.0.1:6002 ESTABLISHED
tcp 1 0 127.0.0.1:6002 127.0.0.1:43826 CLOSE_WAIT
tcp 0 0 127.0.0.1:6002 127.0.0.1:43825 ESTABLISHED
tcp 0 0 127.0.0.1:6002 127.0.0.1:43827 ESTABLISHED
sykpour@sykpour:~/Desktop/MyCode/Linux/build/bin$ netstat -a -n | grep 6002 tcp 0 0 0.0.0.0:6002 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:6002 127.0.0.1:43824 ESTABLISHED
tcp 0 0 127.0.0.1:43828 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:43827 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:43824 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:43825 127.0.0.1:6002 ESTABLISHED
tcp 1 0 127.0.0.1:6002 127.0.0.1:43826 CLOSE_WAIT
tcp 0 0 127.0.0.1:6002 127.0.0.1:43825 ESTABLISHED
tcp 0 0 127.0.0.1:6002 127.0.0.1:43827 ESTABLISHED
(3.2)此时若关闭客户端1,2,服务器将依次read为0(read返回0前,就已经发送了ACK,客户端状态已变为FIN_WAIT_2),close后客户端状态将转变为TIME_WAIT,因此客户端1,2将处于TIME_WAIT状态;服务器端程序将会accept返回到43826,read为0,(服务端状态为CLOSE_WAIT)close发送FIN,但是客户端3上的连接已经从FIN_WAIT_2状态消失,将会已RESET报文进行回复;
RESET报文wireshark截图:
服务端主动关闭
(1)启动服务器,打开3个客户端1, 2, 3 服务端和客户端都可以建立好ESTABLISHED状态如下;sykpour@sykpour:~/Desktop/MyCode/Linux/build/bin$ netstat -a -n | grep 6002 tcp 0 0 0.0.0.0:6002 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:6002 127.0.0.1:43901 ESTABLISHED
tcp 0 0 127.0.0.1:6002 127.0.0.1:43897 TIME_WAIT
tcp 0 0 127.0.0.1:6002 127.0.0.1:43899 ESTABLISHED
tcp 0 0 127.0.0.1:6002 127.0.0.1:43900 ESTABLISHED
tcp 0 0 127.0.0.1:43901 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:43900 127.0.0.1:6002 ESTABLISHED
tcp 0 0 127.0.0.1:43899 127.0.0.1:6002 ESTABLISHED
(2)关闭服务器,状态如下;服务器只针对端口号为43899的客户端进行正常的终止,也就是和第一个客户端进行正常的序列终止;而对于端口号43900和43901都是直接使用RESET报文进行响应的,因为此时服务器端的连接已经消失;
sykpour@sykpour:~/Desktop/MyCode/Linux/build/bin$ netstat -a -n | grep 6002 tcp 0 0 127.0.0.1:6002 127.0.0.1:43897 TIME_WAIT tcp 0 0 127.0.0.1:6002 127.0.0.1:43899 FIN_WAIT2 tcp 1 0 127.0.0.1:43899 127.0.0.1:6002 CLOSE_WAIT
关闭服务器wireshark截图:
代码运行
完整目录
|-- Net| |-- CMakeLists.txt
| |-- SocketsOps.cc
| |-- SocketsOps.h
|-- test4
| |-- CMakeLists.txt
| |-- TcpClient.cc
| |-- TcpClient.h
| |-- TcpServer.cc
| `-- TcpServer.h
(注,其中Net目录下,本程序目前只需要使用SocketsOps.h,SocketsOps.cc)
编译构建环境
线程模型:posixgcc 版本: 4.9.0 (GCC),注(支持C++11 )
Cmake版本:cmake 2.8.0
完整代码下载链接
https://github.com/sykpour/MyCode/tree/master/Linux/test4
(注,本节关键代码位于test4中)
相关文章推荐
- TCP连接的建立和终止
- TCP连接建立和终止小结
- TCP建立连接和终止协议详细解说
- TCP/IP学习笔记(二):TCP连接的建立与终止
- TCP/IP建立连接与终止连接
- TCP/IP详解学习笔记(13)-- TCP连接的建立与终止
- TCP/IP详解卷1 读书笔记:第十八章 TCP连接的建立与终止
- UNIX网络编程——TCP的连接建立与终止、基本TCP客户/服务器套接字函数
- TCP连接的建立和终止 详解
- TCP 连接的建立和终止
- 20160402_TCP连接的建立、终止和状态转换
- TCP的三路握手建立连接与四次挥手终止连接
- 第18章(一)TCP连接建立和终止的正常情况
- 2.6 TCP连接的建立和终止
- TCP连接的建立和终止
- TCP连接建立和终止及TCP状态转换
- TCP连接的建立和终止过程
- TCP 连接的建立和终止
- TCP连接的建立与终止
- TCP连接的建立和终止