socket常见问题

socket编程中主动关闭VS被动关闭

tcp中server,client都可能是主动关闭方或者被动关闭方,现阐述下两者之间的关系:

 

客户端(client)                                                服务器(server)

close()                          Fin x  ->                   读通道关闭(close_wait)

写通道关闭                     <- Ack x+1    

 

读通道关闭(time_wait)    <- Fin y                   close()

                                     ack y+1 ->              写通道关闭      

2x msl                                                          closed

closed

 

 

1、 客户端是调用函数close(),这时,客户端会发送一个FIN给服务器。

2、 服务器收到FIN,关闭套接字读通道,并将自己状态设置为CLOSE_WAIT(表示被动关闭),

       并返回一个ACK给客户端。

3、 客户端收到ACK,关闭套接字写通道

 

      接下来,服务器会调用close():

 

1、 服务器close(),发送一个FIN到客户端。

2、 客户端收到FIN,关闭读通道,并将自己状态设置成TIME_WAIT,发送一个ACK给服务器。

3、 服务器收到ACK,关闭写通道,并将自己状态设置为CLOSE。

4、 客户端等待两个最大数据传输时间,然后将自己状态设置成CLOSED。

 

有了上面的背景知识,对于我们系统线上一个case分析就很简单了!

首先是主动关闭日志很多,后来是被动关闭的日志

由于server端发现了大量闲置的没有Io的socket连接,有监听器在监听是否存在闲置的socket连接,就释放并关闭这些连接,time_wait就出现了,这个时候应用方客户端重启应用,释放了资源包括一些客户端连接,这个时候close_wait出现了,正好是以上日志所反映的

 

同时time_wait状态的连接是不会释放内核资源,所以服务端不要轻易close!

 

socket中的read返回0

在socket中服务器与客户端进行通信,当其中一方调用close(即这一方会发送一个fin)关闭套接字之后,另一方read()会返回一个0。

我之前编写的一个服务器与客户端通信(一个服务器只连接一个客户端):服务器开两个进程,一个用于接收客户端发送的数据,另一个进程用于

向客户端发送数据。客户端开两个进程也是一个用于发送数据一个用于接收数据。由于创建了两个进程,那么套接字的引用计数都为2,只有当客户

端关闭两次套接字,在服务器的read()才会返回0

另外,如果在虚拟机上运行,打开两个shell,一个运行客户端,一个运行服务器,当关闭运行客户端的shell,则服务器的read()会返回0

python socket模块4种异常

  • 与一般I/O和通信问题有关的socket.error;

  • 与查询地址信息有关的socket.gaierror;

  • 与其他地址错误有关的socket.herror;

  • 与在一个socket上调用settimeout()后,处理超时有关的socket.timeout.

 

以 TCP 协议为例,若 socket 使用阻塞模式调用 recv(),返回空串时表示 TCP 连接已正常关闭。

示例代码如下

# sk = socket(...)
# ...
while True:
    data = sk.recv(1024)
    if not data:
        break  # 连接已经关闭
    # ...


python 解决close_wait过多问题

 

最近在公司遇到CLOSE_WAIT过多的问题,现在解决后总结下,先说下CLOSE_WAIT产生的原因:

首先要知道客户端和服务端的连接是使用套接字通信的,TCP/IP协议建立连接需要三次握手,而关闭client与server的连接需要进行四步,如图:

建立连接后常用的三个状态是:ESTABLISHED 表示正在通信,TIME_WAIT 表示主动关闭,CLOSE_WAIT 表示被动关闭。

通过上图,我们来分析,什么情况下,连接处于CLOSE_WAIT状态呢?

        就是服务端在被动关闭收到FIN,未发出自己FIN的情况下就处于CLOSE_WAIT状态了,通常CLOSE_WAIT的持续时间很

短,但是在某些特殊状态下就可能长时间存在,如果出现大量close_wait的现象,主要原因可能是一方关闭了socket链接,但是另一方

忙与读或者写,没有关闭连接。代码需要判断socket,一旦读到0,断开连接,read返回负,检查一下errno( errno 是记录系统的最后

一次错误代码。),如果不是AGAIN,就断开连接。

 

在linux中查看socket状态的命令:

    netstat -nt | awk '{wait[$NF]++}END{for(i in wait) print i,wait[i]}'

得到如下结果:

                            

 

然后查看处于CLOSE_WAIT的都是哪些端口:

    netstat -nt | awk '{if($NF=="CLOSE_WAIT"){wait[$5]++}}END{for(i in wait) print i,wait[i]}'

 

得到如下结果:

                           

第一条命令会把当前所有的状态进行分类,第二个命令,他会定位到具体出问题的端口,再根据端口找到相应的程序,修改相应的程序即可。

 

        上面还提到一种状态就是TIME_WAIT ,如果TIME_WAIT过多,也会对系统造成影响。TIME_WAIT 和CLOSE_WAIT过多

都会影响系统的扩展性,影响用户请求、其他系统对本系统的访问,这两种状态过多产生的影响一致,但不可用同样的方法去

解决,CLOSE_WAIT的情况需要从程序本身出发,而TIME_WAIT更倾向于修改系统参数。

   TIME_WAIT产生的原因第一张图中已经说明了,是主动关闭的一方所处的状态,然后在保持这个状态2MSL(max segment lifetime)时间之

后,彻底关闭回收资源。为什么要等2MSL的时间呢?这是TCP/IP的设计者规定的,主要出于以下两个方面的考虑:

  1.防止上一次连接中的包,迷路后重新出现,影响新连接(经过2MSL,上一次连接中所有的重复包都会消失)
  2.可靠的关闭TCP连接。在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发fin, 如果这时主动方处于 CLOSED 状态 ,就

     会响应 rst 而不是 ack。所以主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。另外这么设计TIME_WAIT 会定时的回收资源,并不会占用很

     大资源的,除非短时间内接受大量请求或者受到攻击。

 

 

解决思路很简单,就是让服务器能够快速回收和重用那些TIME_WAIT的资源。
 
下面我们来看一下/etc/sysctl.conf文件的配置:
#表示开启重用。允许将TIME_WAIT sockets重新用于新的TCP连接,默认为0,表示关闭
1、net.ipv4.tcp_tw_reuse = 1

#表示开启TCP连接中TIME_WAIT sockets的快速回收,默认为0,表示关闭
2、net.ipv4.tcp_tw_recycle = 1

#<span style="font-family:FangSong_GB2312;">表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN_WAIT_2状态的时间(可改为30,一般来说FIN_WAIT_2的连接也极少)</span>
3、net.ipv4.tcp_fin_timeout = 30

#<span style="font-family:SimHei;color:#333333;LINE-HEIGHT: 22px;">控制 TCP/IP 尝试验证空闲连接是否完好的频率</span>
4、net.ipv4.tcp_keepalive_time = 600

#表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。
5、net.ipv4.tcp_max_syn_backlog = 8192 

#表示系统同时保持TIME_WAIT的最大数量,如果超过这个数字,TIME_WAIT将立刻被清除并打印警告信息。默认为180000,改为60000。
6、net.ipv4.tcp_max_tw_buckets = 60000

#记录的那些尚未收到客户端确认信息的连接请求的最大值。对于有128M内存的系统而言,缺省值是1024,小内存的系统则是128。
7、net.ipv4.tcp_max_syn_backlog = 65536

#每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目。
8、net.core.netdev_max_backlog = 32768

#web应用中listen函数的backlog默认会给我们内核参数的net.core.somaxconn限制到128,而nginx定义的NGX_LISTEN_BACKLOG默认为511,所以有必要调整这个值。
9、net.core.somaxconn = 32768

#定义默认的发送窗口大小;对于更大的 BDP 来说,这个大小也应该更大。
10、net.core.wmem_default = 8388608

#该文件指定了接收套接字缓冲区大小的缺省值(以字节为单位)。
11、net.core.rmem_default = 8388608

#最大socket读buffer。
12、net.core.rmem_max = 16777216

#最大socket写buffer。
13、net.core.wmem_max = 16777216

#为了打开对端的连接,内核需要发送一个SYN并附带一个回应前面一个SYN的ACK。也就是所谓三次握手中的第二次握手。这个设置决定了内核放弃连接之前发送SYN+ACK包的数量。
14、net.ipv4.tcp_synack_retries = 2

#对于一个新建连接,内核要发送多少个 SYN 连接请求才决定放弃。不应该大于255,默认值是5,对应于180秒左右时间。
15、net.ipv4.tcp_syn_retries = 2 

#表示开启TCP连接中TIME-WAITsockets的快速回收,默认为0,表示关闭
16、net.ipv4.tcp_tw_recycle = 1

#开启重用。允许将TIME-WAITsockets重新用于新的TCP连接。
17、net.ipv4.tcp_tw_reuse = 1

#同样有3个值,意思是:低于第一个值,TCP没有内存压力;在第二个值下,进入内存压力阶段;高于第三个值,TCP拒绝分配socket(内存单位是页)。
18、net.ipv4.tcp_mem = 94500000 915000000 927000000

#系统中最多有多少个TCP套接字不被关联到任何一个用户文件句柄上。
19、net.ipv4.tcp_max_orphans = 3276800

#每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目。
20、net.core.netdev_max_backlog = 8096

#表示开启SYNCookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭。
21、net.ipv4.tcp_syncookies = 1
View Code

上面这些参数,如1、2、3、6、14、15、16、17等和CLOSE_WAIT或TIME_WAIT有关,优化相关参数让服务跑的更好。

 

名词解释:

套接字:源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字。其用于标识客户端请求的服务器和服务。套接字,是支持TCP/IP网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。

 

拔掉网线时Socket的检查方法

最近在做有关于TCP采集程序时,发现在客户端与服务器通过TCP socket进行通信的时候,如果客户端应用程序正常或者异常退出时,服务器都可以在对应的socket通信连接上获得响应(如返回0,或者抛出异常)。但是,如果在客户端的网线被拔掉的情况下,那么默认情况下,服务器端需要很长的时间才会知道客户端的网线断掉。对于许多服务器应用程序来说,这么长的反应时间是不能允许的,在这种情况下通常使用“心跳机制”来解决类似的问题,这是一种可行的办法。
    
    由于TCP采集程序只是通过长连接来接收消息,而不能与客户端建立心跳机制,所以唯一可行的办法就是设置超时机制,在非阻塞模式工作的情况下,可以通过空闲计数来判断是否连接超时,在连接空闲情况下TCP采集程序会休眠10ms,并且空闲计数器加1,当收到数据时空闲计数器清零,因此当空闲计数达到3000次的时候,说明socket连接在30秒内没有收到数据,此时认为连接超时,主动的断开连接,释放socket资源。
 如何检查Socket是否断开

最近在做一个TCP采集程序,使用到C/S的结构。功能比较的简单,就是TCP采集程序作为服务器,信令采集设备作为客户端,客户端与服务器端之间建立长连接之后,开始发送信令报文给服务器。在服务器端使用多线程方式来处理每个客户端的socket连接,服务器端不主动断开链路,也没有心跳机制来维护连接的状态,客户端发送数据的时间也是不一定的,只要有采集到信令数据时才进行发送。在客户端socket断开后,服务器端应该能够知道并且释放socket资源。

判断socket是否已经断开的方法是使用非阻塞的select方式进行socket检查,步骤如下:

1)设置接收到的socket为异步方式;

2)使用select()函数测试一个socket是否可读;

3)如果select()函数返回的值为1,但是使用recv()函数读取的数据长度为0,那么说明该socket已经断开。(windows和linux中select函数返回值是整数,python是经过封装后的,所有返回值是有变化的描述符)

如果recv()返回值小于等于0时,客户端的连接已经断开,但是还需要判断errno是否等于EINTR。如果errno=EINTR则说明recv()函数是由于程序接收到中断信号后返回的,socket连接应该还是正常,步应该close掉socket连接。

       注:对于阻塞socket的recv函数会在以下三种情况下返回值:

1)接收到数据时会返回;

2)程序接收到信号时返回-1,errno=EINTR;

3)Socket出现问题时,返回-1,具体的错误码请查看man recv;

4)一定要养成查看man说明,内容很详细,很有帮助。

这种方法经过长时间的测试证明是有效的,仅供大家参考。

此外,UNP卷一上有很多socket异常情况下的模拟解释,大家可以去阅读下。如果网络中间有多级路由,路由当掉等很多情况出现,所以建议程序中在应用层中加入心跳(heartbeat机制)和重连来维持连接的状态。

TCP protocol has a timer to determine if the connection is abnormally closed. But this timeout value is very long by default and if you want to check this situation as soon as possible to improve performance, the best solution is to introduce a keepalive mechanism in application protocol design.

TCP协议有一个定时器来决定连接是否被异常关闭。但是该超时时间值缺省的情况下会非常长,如果你希望尽快的检查出这种状态改进性能,最好的方法就是在应用程序协议设计的时候引入keepalive(保持连接)机制。

posted @ 2019-11-18 16:58  南哥的天下  阅读(3341)  评论(0编辑  收藏  举报