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 # 连接已经关闭
# ...
 

最近在公司遇到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

上面这些参数,如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(保持连接)机制。

socket常见问题的更多相关文章

  1. JAVA网络编程Socket常见问题 【长连接专题】

    一. 网络程序运行过程中的常见异常及处理 第1个异常是 java.net.BindException:Address already in use: JVM_Bind. 该异常发生在服务器端进行new ...

  2. socket基本用法

    socket介绍 1.什么是socket socket是应用层与传输层中间的一个软件抽象层,它是一组接口.它把TCP/IP这些复杂的协议统一封装起来 这样我们只要知道如何使用socket就好,就已经符 ...

  3. 我的Android进阶之旅------>经典的大牛博客推荐(排名不分先后)!!

    本文来自:http://blog.csdn.net/ouyang_peng/article/details/11358405 今天看到一篇文章,收藏了很多大牛的博客,在这里分享一下 谦虚的天下 柳志超 ...

  4. linux c TCP连接通讯

    服务端: 1.申请服务端自己的socket 2.对addr赋值 3.bind文件描述符和地址信息 4.listen监听服务 5.等待accept客户端的连接 6.处理建立好的连接 7.关闭socket ...

  5. 浅谈android Socket 通信及自建ServerSocket服务端常见问题

    摘  要:TCP/IP通信协议是可靠的面向连接的网络协议,它在通信两端各建立一个Socket,从而在两端形成网络虚拟链路,进而应用程序可通过可以通过虚拟链路进行通信.Java对于基于TCP协议的网络通 ...

  6. Android笔记:Socket通讯常见问题

    经验证的socket通讯问题 1.如果是模拟器和本机PC直接通讯,需要使用本机IP地址 而不是 10.0.2.2  如本机的静态地址为192.168.1.2 则直接使用该地址 2.接收和连接代码不能在 ...

  7. Socket通信常见问题

    1.检查服务器防火墙入站规则,是否允许对应端口通过.如果是云服务器,还需要通过对应账户去设置安全规则 2.服务端监听或绑定端口时,最好使用IPAddress.Any监听所有网口的改端口,创建socke ...

  8. LR常见问题整理

    1.LoadRunner录制脚本时为什么不弹出IE浏览器? 当一台主机上安装多个浏览器时,LoadRunner录制脚本经常遇到不能打开浏览器的情况,可以用下面的方法来解决. LR11 无法弹出ie浏览 ...

  9. windows 下配置 Nginx 常见问题(转)

    windows 下配置 Nginx 常见问题 因为最近的项目需要用到负载均衡,不用考虑,当然用大名鼎鼎的Nginx啦.至于Nginx的介绍,这里就不多说了,直接进入主题如何在Windows下配置. 我 ...

随机推荐

  1. python 爬虫抓取 MOOC 中国课程的讨论区内容

    一:selenium 库 selenium 每次模拟浏览器打开页面,xpath 匹配需要抓取的内容.可以,但是特别慢,相当慢.作为一个对技术有追求的爬虫菜鸡,狂补了一些爬虫知识.甚至看了 scrapy ...

  2. 【异常】ser class threw exception: java.sql.SQLException: The last packet successfully received from the server was 39,444 milliseconds ago. The last

    1 详细异常 ser class threw exception: java.sql.SQLException: The last packet successfully received from ...

  3. 2018年长沙理工大学第十三届程序设计竞赛 I 连续区间的最大公约数

    连续区间的最大公约数 思路:参照BZOJ 4488: [Jsoi2015]最大公约数脑补出的一个\(map\)套\(vector\)的写法,写起来比线段树短,运行时间比线段树快. 代码: #pragm ...

  4. python3 基础二——基本的数据类型二

    一.数字(Number) 1.Python支持三种不同的数值类型:整型(int),浮点型(float),复数(complex) 2.Python数字数据类型用于存储数值 3.数据类型是不允许改变的,这 ...

  5. JVM内存空间划分与作用详解

    在之前已经对Java的字节码进行了非常详细而又系统的学习了,接下来开启jvm内存相关的新篇章,在一个新知识开头之前肯定得理论化的对其进行一个整体的介绍,所以摒弃浮躁,先来看看相关的理论,主要是看一下J ...

  6. java学习笔记13-重写与重载

    重写 重写是子类对父类允许访问的方法实现过程进行重新编写,返回值和参数都不能变. 重写方法不能抛出新的检查异常和比被重写方法更加宽泛的异常 访问权限不能比被重写方法低 声明为final的方法不能被重写 ...

  7. linux 确定端口使用情况

    lsof -i:3533 确定端口已经在使用

  8. mac安装MySQLdb(mysql-python模块)

    折腾了有一会,网上资料很多,但是在实际安装时就会发现有很多坑,如下实战操作可行.供参考. Command "python setup.py egg_info" failed wit ...

  9. python panda读写内存溢出:MemoryError

    pandas中read_xxx的块读取功能 pandas设计时应该是早就考虑到了这些可能存在的问题,所以在read功能中设计了块读取的功能,也就是不会一次性把所有的数据都放到内存中来,而是分块读到内存 ...

  10. Spark1

    Spark集群 0.0体验安装Spark在集群单节点 1.tar tar -xzvf xxx.tgz -C /soft/ ln -s /soft/spark-2.1.0-bin-hadoop2.7 / ...