CLOSE_WAIT状态解决方案

 更新时间:2021年8月17日 00:00  点击:1517

这个问题之前没有怎么留意过,是最近在面试过程中遇到的一个问题,面了两家公司,两家公司竟然都面到到了这个问题,不得不使我开始关注这个问题。说起CLOSE_WAIT状态,如果不知道的话,还是先瞧一下TCP的状态转移图吧。

 

关闭socket分为主动关闭(Active closure)和被动关闭(Passive closure)两种情况。前者是指有本地主机主动发起的关闭;而后者则是指本地主机检测到远程主机发起关闭之后,作出回应,从而关闭整个连接。将关闭部分的状态转移摘出来,就得到了下图:

TCP 状态变化

产生原因
通过图上,我们来分析,什么情况下,连接处于CLOSE_WAIT状态呢?
在被动关闭连接情况下,在已经接收到FIN,但是还没有发送自己的FIN的时刻,连接处于CLOSE_WAIT状态。
通常来讲,CLOSE_WAIT状态的持续时间应该很短,正如SYN_RCVD状态。但是在一些特殊情况下,就会出现连接长时间处于CLOSE_WAIT状态的情况。

出现大量close_wait的现象,主要原因是某种情况下对方关闭了socket链接,但是我方忙与读或者写,没有关闭连接。代码需要判断socket,一旦读到0,断开连接,read返回负,检查一下errno,如果不是AGAIN,就断开连接。

参考资料4中描述,通过发送SYN-FIN报文来达到产生CLOSE_WAIT状态连接,没有进行具体实验。不过个人认为协议栈会丢弃这种非法报文,感兴趣的同学可以测试一下,然后把结果告诉我;-)

为了更加清楚的说明这个问题,我们写一个测试程序,注意这个测试程序是有缺陷的。
只要我们构造一种情况,使得对方关闭了socket,我们还在read,或者是直接不关闭socket就会构造这样的情况。

server.c:

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
 
#define MAXLINE 80
#define SERV_PORT 8000
 
int main(void)
{
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];
    int i, n;
 
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
 
        int opt = 1;
        setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
 
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);
    
    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
 
    listen(listenfd, 20);
 
    printf("Accepting connections ...\n");
    while (1) {
        cliaddr_len = sizeof(cliaddr);
        connfd = accept(listenfd, 
                (struct sockaddr *)&cliaddr, &cliaddr_len);
        //while (1) 
                {
            n = read(connfd, buf, MAXLINE);
            if (n == 0) {
                printf("the other side has been closed.\n");
                break;
            }
            printf("received from %s at PORT %d\n",
                   inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                   ntohs(cliaddr.sin_port));
    
            for (i = 0; i < n; i++)
                buf[i] = toupper(buf[i]);
            write(connfd, buf, n);
        }
        //这里故意不关闭socket,或者是在close之前加上一个sleep都可以
        //sleep(5);
        //close(connfd);
    }
}

client.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
 
#define MAXLINE 80
#define SERV_PORT 8000
 
int main(int argc, char *argv[])
{
    struct sockaddr_in servaddr;
    char buf[MAXLINE];
    int sockfd, n;
    char *str;
    
    if (argc != 2) {
        fputs("usage: ./client message\n", stderr);
        exit(1);
    }
    str = argv[1];
    
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
 
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    servaddr.sin_port = htons(SERV_PORT);
    
    connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
 
    write(sockfd, str, strlen(str));
 
    n = read(sockfd, buf, MAXLINE);
    printf("Response from server:\n");
    write(STDOUT_FILENO, buf, n);
    write(STDOUT_FILENO, "\n", 1);
 
    close(sockfd);
    return 0;
}

结果如下:

debian-wangyao:~$ ./client a
Response from server:
A
debian-wangyao:~$ ./client b
Response from server:
B
debian-wangyao:~$ ./client c
Response from server:
C
debian-wangyao:~$ netstat -antp | grep CLOSE_WAIT
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        1      0 127.0.0.1:8000          127.0.0.1:58309         CLOSE_WAIT  6979/server     
tcp        1      0 127.0.0.1:8000          127.0.0.1:58308         CLOSE_WAIT  6979/server     
tcp        1      0 127.0.0.1:8000          127.0.0.1:58307         CLOSE_WAIT  6979/server  

解决方法
基本的思想就是要检测出对方已经关闭的socket,然后关闭它。

1.代码需要判断socket,一旦read返回0,断开连接,read返回负,检查一下errno,如果不是AGAIN,也断开连接。(注:在UNP 7.5节的图7.6中,可以看到使用select能够检测出对方发送了FIN,再根据这条规则就可以处理CLOSE_WAIT的连接)

2.给每一个socket设置一个时间戳last_update,每接收或者是发送成功数据,就用当前时间更新这个时间戳。定期检查所有的时间戳,如果时间戳与当前时间差值超过一定的阈值,就关闭这个socket。

3.使用一个Heart-Beat线程,定期向socket发送指定格式的心跳数据包,如果接收到对方的RST报文,说明对方已经关闭了socket,那么我们也关闭这个socket。

4.设置SO_KEEPALIVE选项,并修改内核参数

前提是启用socket的KEEPALIVE机制:
//启用socket连接的KEEPALIVE
int iKeepAlive = 1;
setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&iKeepAlive, sizeof(iKeepAlive));

tcp_keepalive_intvl (integer; default: 75; since Linux 2.4)
The number of seconds between TCP keep-alive probes.

tcp_keepalive_probes (integer; default: 9; since Linux 2.2)
The  maximum  number  of  TCP  keep-alive  probes  to  send before giving up and killing the connection if no response is obtained from the other end.

tcp_keepalive_time (integer; default: 7200; since Linux 2.2)
The number of seconds a connection needs to be idle before TCP begins sending out  keep-alive  probes.   Keep-alives  are only  sent when the SO_KEEPALIVE socket option is enabled.  The default value is 7200 seconds (2 hours).  An idle connec‐tion is terminated after approximately an additional 11 minutes (9 probes an interval of 75  seconds  apart)  when  keep-alive is enabled.

echo 120 > /proc/sys/net/ipv4/tcp_keepalive_time
echo 2 > /proc/sys/net/ipv4/tcp_keepalive_intvl
echo 1 > /proc/sys/net/ipv4/tcp_keepalive_probes

除了修改内核参数外,可以使用setsockopt修改socket参数,参考man 7 socket。

int KeepAliveProbes=1;
int KeepAliveIntvl=2;
int KeepAliveTime=120;
setsockopt(s, IPPROTO_TCP, TCP_KEEPCNT, (void *)&KeepAliveProbes, sizeof(KeepAliveProbes));
setsockopt(s, IPPROTO_TCP, TCP_KEEPIDLE, (void *)&KeepAliveTime, sizeof(KeepAliveTime));
setsockopt(s, IPPROTO_TCP, TCP_KEEPINTVL, (void *)&KeepAliveIntvl, sizeof(KeepAliveIntvl));

到此这篇关于CLOSE_WAIT状态解决方案的文章就介绍到这了,更多相关CLOSE_WAIT状态内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

[!--infotagslink--]

相关文章

  • 409错误是什么 http 409错误怎么解决

    409错误是什么?http 409错误怎么解决呢?不少站长在遇到这个错误代码之后都一筹莫展,本次一聚教程网为大家带来了详细的说明,快来看看吧。 409错误是什么: HTTP 40...2017-01-22
  • http 405错误是什么 http 405错误怎么解决

    http 405错误是什么?http 405错误怎么解决?相信很多站长都在找这两个问题的答案,本次小编为大家带来了详细的教程,快来看看吧。 405错误是什么: HTTP 405错误是H...2017-01-22
  • 403错误是什么 403错误怎么解决

    403错误是HTTP状态码的一种,属于“请示错误”,表示服务器拒绝请求。如果在搜索引擎尝试抓取您网站上的有效网页时显示此状态代码,那么,这可能是您的服务器或主机拒绝搜索...2017-01-22
  • 412错误是什么 412错误怎么解决

    412错误是什么?412错误怎么解决?本次一聚教程网将为大家带来详细的介绍,帮助大家全面了解412错误的意思以及解决412错误的方法。 412错误是什么: HTTP 412错误,(Precond...2017-01-22
  • Perl CPAN::Modulelist的解决办法

    今天用CPAN安装Term::ReadLine,报了个这样的错误 Going to read /root/.cpan/sources/modules/03modlist.data.gz Can't locate object method "data" via package "C...2016-11-25
  • 407错误是什么 407错误怎么解决

    407错误是什么?407错误怎么解决?不少站长都遇到过407错误,下面小编将告诉大家如何处理407错误。 407错误是什么: HTTP 407错误是HTTP协议状态码的一种,表示需要代...2017-01-22
  • 406错误是什么 406错误怎么解决

    HTTP 406错误是HTTP协议状态码的一种,表示无法使用请求的内容特性来响应请求的网页。一般是指客户端浏览器不接受所请求页面的 MIME 类型。 而MIME类型是在把输出...2017-01-22
  • 410错误是什么 http 410错误怎么解决

    410错误是HTTP协议状态码的一种,本次一聚教程网将为大家详细介绍HTTP 410错误是什么,以及410错误的解决办法。 410错误是什么: HTTP 410错误是HTTP协议状态码的...2017-01-22
  • HTTP 400错误是什么 HTTP 400错误怎么解决

    每当遇到http错误代码为400,代表客户端发起的请求不符合服务器对请求的某些限制,或者请求本身存在一定的错误,那么HTTP 400错误怎么解决呢?请看下文介绍。 目前400错...2017-01-22
  • Jrebel启动失败解决方案详解

    这篇文章主要介绍了Jrebel启动失败解决方案详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-07-07
  • 小记一次mysql主从配置解决方案

      今天研究了个开源项目,数据库是mysql的,其中的脚本数据需要备份,由于本人的机器时mac pro,而且mac下的数据库连接工具都不怎么好用,就想着如何利用windows下的数据库连接工具使用,并做相关备份,另外windows系统下的sqlyo...2015-10-21
  • php 502 bad gateway的解决方案

    502 bad gateway是php-fpm的问题对于这个问题就是配置参数的问题了,下面我们整理了一些关于php-fpm错误问题的解决办法,具体如下。 今天升级完PHP出现了502 Bad Gat...2016-11-25
  • 解决The surface has been released 错误

    在android开发过程中,APP需要用到摄像头录制视频音效、播放视频的功能,并且通过第三方线程调用Handler动态的addview和removeview添加和删除播放视频的组件——MediaPla...2016-09-20
  • PHP下MAIL的另一解决方案

    前一段时间我接触到DEC Tru64 Unix 我在上面装了PHP+APACHE,可以用提供的mail函数始终不能正常发信,于是自编了一个函数,它利用UNIX下的管道和PHP的SOCK函数进行发信,经...2016-11-25
  • 解决php编译中遇到各种error办法

    php编译安装碰到各种的问题了,我们这里整理了一篇关于解决php编译中遇到各种error办法的文章,希望此文章能够为各位带来帮助呀。 1) Configure: error: xml2-config...2016-11-25
  • Invalid byte 1 of 1-byte UTF-8 sequence解决办法

    在利用php解析xml时提示Invalid byte 1 of 1-byte UTF-8 sequence错误了,这个问题我百度查实说是编码问题,结果我把编码处理一下果然KO了,下面我来分享一下解决办法。...2016-11-25
  • 解决:Bitmap too large to be uploaded into a texture exception问题

    下面我们一起来看一篇关于 解决:Bitmap too large to be uploaded into a texture exception问题解决办法。 最近做项目发现其他手机没有问题,但是出现了一个手机报...2016-09-20
  • 解决css中display:inline-block中间的空白间隙

    display:inline-block在页面中设置是一个规则,这个在许多的浏览器不初支持了,我们使用display:inline-block会发现会发现容器中有一个空白间隙了,下面我们来看看。 1...2016-09-14
  • libcurl升级后找不到libcurl.so.3解决之法

    本文章今天来给大家介绍一个libcurl升级后找不到libcurl.so.3解决之法,希望对各位朋友有帮助呀。 系统装的是libcurl 7.19的版本,编译的动态库是libcurl.so.4 自...2016-11-25
  • Django生成数据库及添加用户报错解决方案

    这篇文章主要介绍了Django生成数据库及添加用户报错解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-10-09