您当前的位置:首页 > 电脑百科 > 网络技术 > 网络知识

浅谈tcp的半打开连接

时间:2021-04-08 14:58:18  来源:今日头条  作者:ethnicity

tcp连接一端在进行完三次握手以后进入ESTABLISHED状态,如果连接的对端在某一时刻在网络中消失,而本端没有感知到,还是处于ESTABLISHED状态,那么本端的连接就被称为半打开连接(Half Open)。

连接的对端在网络中消失的情况有好多:

例如对端主机突然断电,tcp连接来不及发送任何信息就消失啦。

还有,连接路径上的某个nat设备aging-time过期,并且nat port被重用,虽然tcp连接的两端都还处于ESTABLISHED状态,可实际上两端的连接已经无法正常通信,此时这两端的连接都是半打开连接。(这种情况是我的猜测,还没有得到实践的检验。如果结论错误,就会修改掉!)

还有,listen socket的accept调用缓慢导致积压队列满,client端连接会成为半打开连接。这种情况是本次讨论的主题。

 

首先说下tcp的三次握手

浅谈tcp的半打开连接

 


server端的tcp连接在三次握手阶段会经历SYN_RECV状态到ESTABLISHED状态的变迁,其中SYN_RECV状态到连接存放于listen socket积压队列的半连接队列中,当连接由SYN_RECV状态变为ESTABLISHED状态,连接会被从半连接队列中移到已连接队列中。系统调用accept的作用就是从listen socket的已连接队列中取走一个连接,然后将该连接与进程绑定。

但是,如果listen socket的积压队列(半连接队列与连接队列)全部满后,对于新来的client连接会如何处理呢。答案是,linux不同版本的实现不同。

当前的实验环境:

zuchunlei@ubuntu14:~$ uname -a
Linux ubuntu14 4.4.0-31-generic #50~14.04.1-Ubuntu SMP Wed Jul 13 01:07:32 UTC 2016 x86_64 x86_64 x86

服务端代码:

In [1]: from socket import *
In [2]: sock = socket(AF_INET,SOCK_STREAM)
In [3]: sock.bind(("",10000))
In [4]: sock.listen(1)

为了简单,我将listen的backlog设置为1,并且不调用sock.accept方法。这样所有的ESTABLISHED状态的连接都存在积压队列中,并且没有和进程绑定起来。

使用netstat查看10000端口的状态:

Every 1.0s: sudo netstat -tnpoa|sed -n -e 2p -e /10000/p                                                                                                                                Sat Dec 16 20:23:03 2017

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name Timer
tcp        0      0 0.0.0.0:10000           0.0.0.0:*               LISTEN      1578/Python      off (0.00/0/0)

使用ss查看10000端口的状态:

Every 1.0s: ss -tnpoa|sed -n -e 1p -e /10000/p                                                                                                                                          Sat Dec 16 20:25:18 2017

State      Recv-Q Send-Q        Local Address:Port          Peer Address:Port
LISTEN     0      1                         *:10000                    *:*      users:(("ipython",1578,6))

解析一下,ss命令输出的State=Listen状态的数据时,其中Send-Q的大小表示该listen socket积压队列的长度,Recv-Q代表已完成三次握手,ESTABLISHED状态的连接个数。这样的连接存在于listen socket的已连接队列中。

用nc localhost 10000进行2次连接后,使用netstat查看10000端口的状态:

Every 1.0s: sudo netstat -tnpoa|sed -n -e 2p -e /10000/p                                                                                                                                Sat Dec 16 20:32:45 2017

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name Timer
tcp        0      0 0.0.0.0:10000           0.0.0.0:*               LISTEN      1578/python      off (0.00/0/0)
tcp        0      0 127.0.0.1:59890         127.0.0.1:10000         ESTABLISHED 6301/nc          off (0.00/0/0)
tcp        0      0 127.0.0.1:10000         127.0.0.1:59890         ESTABLISHED -                off (0.00/0/0)
tcp        0      0 127.0.0.1:10000         127.0.0.1:59892         ESTABLISHED -                off (0.00/0/0)
tcp        0      0 127.0.0.1:59892         127.0.0.1:10000         ESTABLISHED 6379/nc          off (0.00/0/0)

netstat显示当前客户端程序nc连接已经建立完成,服务端的2个连接也处于ESTABLISHED状态,但因为当前没有accept调用,所以服务端的两个连接的进程PID显示为-,表示当前连接没有和进程绑定起来。

使用ss查看10000端口的状态:

Every 1.0s: ss -tnpoa|sed -n -e 1p -e /10000/p                                                                                                                                          Sat Dec 16 20:36:10 2017

State      Recv-Q Send-Q        Local Address:Port          Peer Address:Port
LISTEN     2      1                         *:10000                    *:*      users:(("ipython",1578,6))
ESTAB      0      0                 127.0.0.1:59890            127.0.0.1:10000  users:(("nc",6301,3))
ESTAB      0      0                 127.0.0.1:10000            127.0.0.1:59890
ESTAB      0      0                 127.0.0.1:10000            127.0.0.1:59892
ESTAB      0      0                 127.0.0.1:59892            127.0.0.1:10000  users:(("nc",6379,3))

通过ss可以看到,当前LISTEN状态的RECV-Q值为2,表示有2个ESTABLISHED状态的连接在已连接队列中等待应用层调用accept取走。

用nc localhost 10000进行第三次连接后,netstat查看10000端口的状态:

Every 1.0s: sudo netstat -tnpoa|sed -n -e 2p -e /10000/p                                                                                                                                Sat Dec 16 20:41:18 2017

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name Timer
tcp        0      0 0.0.0.0:10000           0.0.0.0:*               LISTEN      1578/python      off (0.00/0/0)
tcp        0      0 127.0.0.1:59890         127.0.0.1:10000         ESTABLISHED 6301/nc          off (0.00/0/0)
tcp        0      0 127.0.0.1:10000         127.0.0.1:59896         SYN_RECV    -                on (1.06/3/0)
tcp        0      0 127.0.0.1:59896         127.0.0.1:10000         ESTABLISHED 10989/nc         off (0.00/0/0)
tcp        0      0 127.0.0.1:10000         127.0.0.1:59890         ESTABLISHED -                off (0.00/0/0)
tcp        0      0 127.0.0.1:10000         127.0.0.1:59892         ESTABLISHED -                off (0.00/0/0)
tcp        0      0 127.0.0.1:59892         127.0.0.1:10000         ESTABLISHED 6379/nc          off (0.00/0/0)

可以看到对于第三个客户端nc,连接状态为ESTABLISHED,表示3次握手已经正确完成。而对于服务端,当前的连接状态为SYN_RECV,表示半连接状态,因为当前积压队列已经满,没有空间再存放ESTABLISHED连接,所以该连接无法从SYN_RECV状态变为ESTABLISHED状态,虽然能正确接收到nc端的第三个ACK段。

此时使用tcpdump进行抓包:

zuchunlei@ubuntu14:~$ sudo tcpdump -i any tcp port 10000 -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes
20:50:15.739292 IP 127.0.0.1.10000 > 127.0.0.1.59896: Flags [S.], seq 2458870060, ack 3925261891, win 43690, options [mss 65495,sackOK,TS val 1340001 ecr 1339751,nop,wscale 7], length 0
20:50:15.739301 IP 127.0.0.1.59896 > 127.0.0.1.10000: Flags [.], ack 1, win 342, options [nop,nop,TS val 1340001 ecr 1339751], length 0
20:50:17.738724 IP 127.0.0.1.10000 > 127.0.0.1.59896: Flags [S.], seq 2458870060, ack 3925261891, win 43690, options [mss 65495,sackOK,TS val 1340501 ecr 1340001,nop,wscale 7], length 0
20:50:17.738772 IP 127.0.0.1.59896 > 127.0.0.1.10000: Flags [.], ack 1, win 342, options [nop,nop,TS val 1340501 ecr 1339751], length 0
20:50:21.739110 IP 127.0.0.1.10000 > 127.0.0.1.59896: Flags [S.], seq 2458870060, ack 3925261891, win 43690, options [mss 65495,sackOK,TS val 1341501 ecr 1340501,nop,wscale 7], length 0
20:50:21.739158 IP 127.0.0.1.59896 > 127.0.0.1.10000: Flags [.], ack 1, win 342, options [nop,nop,TS val 1341501 ecr 1339751], length 0
20:50:29.738975 IP 127.0.0.1.10000 > 127.0.0.1.59896: Flags [S.], seq 2458870060, ack 3925261891, win 43690, options [mss 65495,sackOK,TS val 1343501 ecr 1341501,nop,wscale 7], length 0
20:50:29.739022 IP 127.0.0.1.59896 > 127.0.0.1.10000: Flags [.], ack 1, win 342, options [nop,nop,TS val 1343501 ecr 1339751], length 0
20:50:45.739231 IP 127.0.0.1.10000 > 127.0.0.1.59896: Flags [S.], seq 2458870060, ack 3925261891, win 43690, options [mss 65495,sackOK,TS val 1347501 ecr 1343501,nop,wscale 7], length 0
20:50:45.739310 IP 127.0.0.1.59896 > 127.0.0.1.10000: Flags [.], ack 1, win 342, options [nop,nop,TS val 1347501 ecr 1339751], length 0

对于SYN_RECV状态的连接,linux会启动定时器进行重传三次握手的第二段[S.],在4次重传后,如果当前listen socket已连接队列中依然没有空间,则将SYN_RECV状态的连接丢弃。

等待4次重传后,使用netstat查看10000端口状态:

Every 1.0s: sudo netstat -tnpoa|sed -n -e 2p -e /10000/p                                                                                                                                Sat Dec 16 20:58:20 2017

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name Timer
tcp        0      0 0.0.0.0:10000           0.0.0.0:*               LISTEN      1578/python      off (0.00/0/0)
tcp        0      0 127.0.0.1:59890         127.0.0.1:10000         ESTABLISHED 6301/nc          off (0.00/0/0)
tcp        0      0 127.0.0.1:10000         127.0.0.1:59890         ESTABLISHED -                off (0.00/0/0)
tcp        0      0 127.0.0.1:59896         127.0.0.1:10000         ESTABLISHED 15954/nc         off (0.00/0/0)
tcp        0      0 127.0.0.1:10000         127.0.0.1:59892         ESTABLISHED -                off (0.00/0/0)
tcp        0      0 127.0.0.1:59892         127.0.0.1:10000         ESTABLISHED 6379/nc          off (0.00/0/0)

server端将SYN_RECV状态的连接丢弃后,此时第三个nc客户端连接就已经成为了半打开连接。

 

对半打开连接进行send/recv操作时的影响:

如果此时,第三个nc客户端发送数据,则因为连接对对端不存在,对端会回复RST段,本端收到RST段后也会将连接重置。

如果第三个nc客户端只接收数据的话,则这个客户端永远阻塞在recv调用中无法返回。为了有效解决这种问题,客户端可以启动tcp的keepalive,因为默认tcp发送keepalive probe的间隔时间较长,应用可以通过设置socket option(
TCP_KEEPDILE/TCP_KEEPINTVL/TCP_KEEPCNT)将发送keepalive probe的时间设短些。

 

今早我测试了一下最新版ubuntu16.04的实现,发现如果listen socket的积压队列满后,新来客户端的连接不再成为ESTABLISHED状态,而是在SYN_SENT状态进行进行SYN段的超时重传,而服务端不返回任何tcp段。

新版的测试环境:

zuchunlei@box:~$ uname -a
Linux box 4.10.0-28-generic #32~16.04.2-Ubuntu SMP Thu Jul 20 10:19:48 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

与之前的测试场景一样,当前只关注第三个nc客户端连接的状态。

使用netstat查看10000端口的状态:

Every 1.0s: sudo netstat -tnpoa|sed -n -e 2p -e /10000/p                                                                                                                                Sat Dec 16 21:21:57 2017

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name Timer
tcp        0      0 0.0.0.0:10000           0.0.0.0:*               LISTEN      2022/python      off (0.00/0/0)
tcp        0      0 127.0.0.1:36516         127.0.0.1:10000         ESTABLISHED 2347/nc          off (0.00/0/0)
tcp        0      1 127.0.0.1:36520         127.0.0.1:10000         SYN_SENT    2522/nc          on (5.18/3/0)
tcp        0      0 127.0.0.1:10000         127.0.0.1:36518         ESTABLISHED -                off (0.00/0/0)
tcp        0      0 127.0.0.1:36518         127.0.0.1:10000         ESTABLISHED 2388/nc          off (0.00/0/0)
tcp        0      0 127.0.0.1:10000         127.0.0.1:36516         ESTABLISHED -                off (0.00/0/0)

此时,第三个nc客户端连接状态为SYN_SENT,进行超时重传SYN段。

使用tcpdump抓去第三个nc客户端的tcp包:

zuchunlei@box:~$ sudo tcpdump -i any tcp port 10000 -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
21:21:47.357226 IP 127.0.0.1.36520 > 127.0.0.1.10000: Flags [S], seq 1445936074, win 43690, options [mss 65495,sackOK,TS val 4214107076 ecr 0,nop,wscale 7], length 0
21:21:48.358267 IP 127.0.0.1.36520 > 127.0.0.1.10000: Flags [S], seq 1445936074, win 43690, options [mss 65495,sackOK,TS val 4214107327 ecr 0,nop,wscale 7], length 0
21:21:50.373837 IP 127.0.0.1.36520 > 127.0.0.1.10000: Flags [S], seq 1445936074, win 43690, options [mss 65495,sackOK,TS val 4214107831 ecr 0,nop,wscale 7], length 0
21:21:54.565832 IP 127.0.0.1.36520 > 127.0.0.1.10000: Flags [S], seq 1445936074, win 43690, options [mss 65495,sackOK,TS val 4214108879 ecr 0,nop,wscale 7], length 0
21:22:02.758111 IP 127.0.0.1.36520 > 127.0.0.1.10000: Flags [S], seq 1445936074, win 43690, options [mss 65495,sackOK,TS val 4214110927 ecr 0,nop,wscale 7], length 0
21:22:18.885934 IP 127.0.0.1.36520 > 127.0.0.1.10000: Flags [S], seq 1445936074, win 43690, options [mss 65495,sackOK,TS val 4214114959 ecr 0,nop,wscale 7], length 0
21:22:51.141643 IP 127.0.0.1.36520 > 127.0.0.1.10000: Flags [S], seq 1445936074, win 43690, options [mss 65495,sackOK,TS val 4214123023 ecr 0,nop,wscale 7], length 0

可以看到客户端在进行超时重传SYN段的过程中,服务端没有发送一个包。

在客户端SYN_SENT超时后,使用netstat查看10000端口状态:

Every 1.0s: sudo netstat -tnpoa|sed -n -e 2p -e /10000/p                                                                                                                       Sat Dec 16 21:27:36 2017

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name Timer
tcp        0      0 0.0.0.0:10000           0.0.0.0:*               LISTEN      2022/python      off (0.00/0/0)
tcp        0      0 127.0.0.1:36516         127.0.0.1:10000         ESTABLISHED 2347/nc          off (0.00/0/0)
tcp        0      0 127.0.0.1:10000         127.0.0.1:36518         ESTABLISHED -                off (0.00/0/0)
tcp        0      0 127.0.0.1:36518         127.0.0.1:10000         ESTABLISHED 2388/nc          off (0.00/0/0)
tcp        0      0 127.0.0.1:10000         127.0.0.1:36516         ESTABLISHED -                off (0.00/0/0)

客户端连接消失。

 

在当前新版当linux实现中,由于listen socket积压队列满时,新的客户端连接并不会成为半打开连接,而是在connect调用时进行重传SYN段,如果达到了SYN_SENT状态的阈值后,tcp连接消失,应用层connect调用返回timeout异常!



Tags:tcp   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
1.TCP/IP 网络模型有几层?分别有什么用? TCP/IP网络模型总共有五层 1.应用层:我们能接触到的就是应用层了,手机,电脑这些这些设备都属于应用层。 2.传输层:就是为应用层提供网络...【详细内容】
2021-12-22  Tags: tcp  点击:(29)  评论:(0)  加入收藏
TCP握手的时候维护的队列 半连接队列(SYN队列) 全连接队列(accepted队列)半连接队列是什么?服务器收到客户端SYN数据包后,Linux内核会把该连接存储到半连接队列中,并响应SYN+ACK报...【详细内容】
2021-12-21  Tags: tcp  点击:(9)  评论:(0)  加入收藏
什么是滑动窗口?窗口是操作系统开辟的一块缓存空间,发送方在收到接收方ACK应答之前,必须在缓冲区保留已发送的数据,如果按期收到确认应答,数据就可以从缓冲区移除。什么是滑动窗...【详细内容】
2021-12-14  Tags: tcp  点击:(28)  评论:(0)  加入收藏
一、TCP协议位于传输层, 提供可靠的字节流服务。所谓的字节流服务(Byte Stream Service) 是指, 为了方便传输, 将大块数据分割成以报文段(segment) 为单位的数据包进行管理。 而可...【详细内容】
2021-11-26  Tags: tcp  点击:(34)  评论:(0)  加入收藏
TCP(Transmission Control Protocol)传输控制协议是一种面向连接的、可靠的、基于字节流的传输层协议 端口号取值范围0~6535 因为十六位二进制构成 2^16 ...【详细内容】
2021-10-19  Tags: tcp  点击:(89)  评论:(0)  加入收藏
昨晚有位读者问了我这么个问题: 大概意思是,一个已经建立的 TCP 连接,客户端中途宕机了,而服务端此时也没有数据要发送,一直处于 establish 状态,客户端恢复后,向服务端建立连接,此...【详细内容】
2021-09-17  Tags: tcp  点击:(71)  评论:(0)  加入收藏
想必大家已经知道我的niao性,搞个标题,就是不喜欢立马回答。就是要搞一大堆原理性的东西,再回答标题的问题。说这个是因为我这次会把问题的答案就放到开头吗?不!我就不!但是大家可...【详细内容】
2021-09-13  Tags: tcp  点击:(71)  评论:(0)  加入收藏
本文是参考【图解TCP/IP】TCP(Transmission Control Protocol)是传输控制协议,其作用于传输层,是一种提供了面向连接通信服务的协议看TCP的英文全称就知道,其主要作用就是传输 、...【详细内容】
2021-09-08  Tags: tcp  点击:(40)  评论:(0)  加入收藏
TCP 连接建立的三次握手1、先提出一个问题,可以不进行三次握手直接往服务端发送数据包吗?是不可以的,也是可以的;1)不可以是因为现在的TCP连接标准和规范要求传输数据前先确认两...【详细内容】
2021-09-03  Tags: tcp  点击:(123)  评论:(0)  加入收藏
入侵一些网站,电脑,制作一些病毒,学会多项编程,这是一个普通黑客都会的技能,那么真正黑客能厉害到什么程度呢?除了勒索病毒,熊猫烧香等自动感染的病毒被大家熟知外,还有更厉害的骚操...【详细内容】
2021-08-19  Tags: tcp  点击:(66)  评论:(0)  加入收藏
▌简易百科推荐
HTTP 报文是在应用程序之间发送的数据块,这些数据块将通过以文本形式的元信息开头,用于 HTTP 协议交互。请求端(客户端)的 HTTP 报文叫做请求报文,响应端(服务器端)的叫做响应...【详细内容】
2021-12-27  程序员蛋蛋    Tags:HTTP 报文   点击:(0)  评论:(0)  加入收藏
一 网络概念:1.带宽: 标识网卡的最大传输速率,单位为 b/s,比如 1Gbps,10Gbps,相当于马路多宽2.吞吐量: 单位时间内传输数据量大小单位为 b/s 或 B/s ,吞吐量/带宽,就是网络的使用率...【详细内容】
2021-12-27  码农世界    Tags:网络   点击:(2)  评论:(0)  加入收藏
1.TCP/IP 网络模型有几层?分别有什么用? TCP/IP网络模型总共有五层 1.应用层:我们能接触到的就是应用层了,手机,电脑这些这些设备都属于应用层。 2.传输层:就是为应用层提供网络...【详细内容】
2021-12-22  憨猪哥08    Tags:TCP/IP   点击:(29)  评论:(0)  加入收藏
TCP握手的时候维护的队列 半连接队列(SYN队列) 全连接队列(accepted队列)半连接队列是什么?服务器收到客户端SYN数据包后,Linux内核会把该连接存储到半连接队列中,并响应SYN+ACK报...【详细内容】
2021-12-21  DifferentJava    Tags:TCP   点击:(9)  评论:(0)  加入收藏
你好,这里是科技前哨。 随着“元宇宙”概念的爆火,下一代互联网即将到来,也成了互联网前沿热议的话题,12月9日美国众议院的听证会上,共和党议员Patrick McHenry甚至宣称,要调整现...【详细内容】
2021-12-17  王煜全    Tags:Web3   点击:(14)  评论:(0)  加入收藏
一、demopublic static void main(String[] args) throws Exception { RetryPolicy retryPolicy = new ExponentialBackoffRetry( 1000, 3);...【详细内容】
2021-12-15  程序员阿龙    Tags:Curator   点击:(20)  评论:(0)  加入收藏
一、计算机网络概述 1.1 计算机网络的分类按照网络的作用范围:广域网(WAN)、城域网(MAN)、局域网(LAN);按照网络使用者:公用网络、专用网络。1.2 计算机网络的层次结构 TCP/IP四层模...【详细内容】
2021-12-14  一口Linux    Tags:网络知识   点击:(30)  评论:(0)  加入收藏
无论是在外面还是在家里,许多人都习惯了用手机连接 WiFi 进行上网。不知道大家有没有遇到过这样一种情况, 明明已经显示成功连接 WiFi,却仍然提示“网络不可用”或“不可上网”...【详细内容】
2021-12-14  UGREEN绿联    Tags:WiFi   点击:(25)  评论:(0)  加入收藏
拉了千兆宽带,买了标称 1300Mbps 的无线路由器,为什么 WiFi 还是跑不满千兆?要回答这个问题,我们先得知道这个 1300Mbps 是怎么来的。开始回答之前先说明一下,这期只讲 802.11ac,...【详细内容】
2021-12-14  Ubiquiti优倍快    Tags:WiFi   点击:(83)  评论:(0)  加入收藏
问题背景IPv6环境下,在浏览器中通过http://[vip:port]访问web业务,提示无法访问此网站,[vip]的响应时间过长。分析过程之前碰到过多次在PC浏览器上无法访问vip的情况,排查方法也...【详细内容】
2021-12-13  云原生知识星球    Tags:网络问题   点击:(27)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条