CLOSE_WAIT状态分析

先给出如下C程序.在此代码中,我们故意屏蔽了发送端的close socket函数.运行后我们会发现发送端有大量的CLOSE_WAIT状态的连接,此时接收端是大量的FIN_WAIT_2状态的连接.

main.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <netinet/tcp.h>
#include <netinet/in.h>


int socket_connect(const char *remoteip, int remoteport){
    int socketfd = -1;
    unsigned long uladdr = 0;
    struct sockaddr_in sa = {0};
   
    int bReuseaddr = 1;
    socklen_t optlen = sizeof(bReuseaddr);
/*   
    int keepAlive = 1;
    int keepIdle = 1;
    int keepInterval = 1;
    int keepCount = 2;
*/
    socketfd = socket(AF_INET, SOCK_STREAM, 0);
    if(socketfd < 0){
        printf("Error: create socket error/n");
        return -1;
    }
/*      
    if(setsockopt(socketfd, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(keepAlive)) < 0) {
        printf("Error: set socket [%d] opt keep alive error/n", socketfd);
        return -1;
    }
    if(setsockopt(socketfd, SOL_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(keepIdle)) < 0) {
        printf("Error: set socket [%d] opt keep idle error/n", socketfd);
        return -1;
    }
    if(setsockopt(socketfd, SOL_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(keepInterval)) < 0) {
        printf("Error: set socket [%d] opt keep alive error/n", socketfd);
        return -1;
    }
    if(setsockopt(socketfd, SOL_TCP, TCP_KEEPCNT, &keepCount, sizeof(keepCount)) < 0) {
        printf("Error: set socket [%d] opt keep alive error/n", socketfd);
        return -1;
    }
*/
 
    uladdr = inet_addr(remoteip);
    memset(&sa,0,sizeof(sa));
    sa.sin_family=AF_INET;
    sa.sin_port=htons(remoteport);
    sa.sin_addr.s_addr=uladdr;

    if( connect(socketfd, (struct sockaddr *)&sa, sizeof(struct sockaddr)) < 0) {
        printf("Error: connect exception/n");
        return -1;
    }
    return socketfd;
}

int socket_listen(int port, int listen_queue_size){
    int ret = 0;
    int s = 0;
    struct sockaddr_in  addr;
   
    int bReuseaddr = 1;
    socklen_t optlen = sizeof(bReuseaddr);
   
    s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    if(s < 0) {
        return -1;
    }
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    ret = bind(s,(struct sockaddr *)&addr, sizeof(struct sockaddr_in));
    if(ret < 0) {
        printf("Info: bind port %d failed/n",port);
        return -1;
    }
    ret = listen(s, listen_queue_size);
    if(ret < 0){
        return -1;
    }
    return s;
}

 

int socket_accept(int listen_socket,char* remoteip){
    int len = 0;
    struct sockaddr_in  remote_addr;
    int s = -1;
    len = sizeof(struct sockaddr_in);
    memset((char *)&remote_addr, 0, len);
    s = accept(listen_socket, (struct sockaddr *)&remote_addr, (socklen_t *)&len);
    if(s <= 0){
     printf("accept ret = %d/n",errno);
        return -1;
    }
    return s;
}


int main(int argc, char* argv[]){
    int sender = 0;
    int ret    = 0;
    int listen_sock = 0;
    if(argc !=2){
     printf("usage: ./test 1/n");
     return -1;
    }
    sender = atoi(argv[1]);
    if(sender){
     while(1){
         printf("new round connect/n");
         sleep(4);
         ret = socket_connect("10.224.55.145",8765);
         if(ret < 0){
             printf("socket connect failed/n");
             return -1;
         }else{
             printf("socket connect success, sock = %d/n",ret);
         //  close(ret);
            }
        }
    }else{
        listen_sock = socket_listen(8765,100);
        if(listen_sock < 0){
         printf("socket listen failed/n");
         return -1;
     }else{
            while(1){
             printf("before accept socket/n");
             ret = socket_accept(listen_sock,NULL);
             if(ret < 0){
                 printf("socket accept failed/n");
              return -1;
             }else{
                 printf("socket accept success, sock = %d/n",ret);
                 close(ret);
             }
         }
        }
    }
    return 0;
}

 

上面程序相当于接收端主动断掉当前连接,那么双方关闭此TCP连接需要的四次挥手如下:

SERVER ---   FIN  --- CLINET

CLINET  ---  ACK  --- SERVER

CLINET  ---   FIN  --- SERVER

SERVER ---   ACK --- CLINET

接受端关闭连接,步骤I,II没问题.由于发送端没有关闭连接,所以步骤III不会继续执行.此时,发送端等待自己关闭连接,而接收端等待发送断发送FIN报文.所以,此时,发送端处于CLOSE_WAIT状态,接收端处于FIN_WAIT_2状态.

 

同理,发送端主动关闭连接,而屏蔽接收端的close socket函数.则发送端会有大量FIN_WAIT_2状态的连接,而接收端会有大量的CLOSE_WAIT状态的连接.当然,造成这种问题也可能是在关闭连接前要处理大量事情.

 

如何解决此问题?通过设置SO_KEEPALIVE选项能保证此连接会在2个小时后被检测到有问题,此选项一般用于对端以一种非优雅的方式断开的情况.如果我们不能接受如此之长的等待时间,可以通过设置TCP_KEEPIDLE,TCP_KEEPINTVL,TCP_KEEPCNT三个选项来缩短这个时间,从而使CLOSE_WAIT状态的连接尽快释放,而不会越来越多. 如程序socket_connect函数中屏蔽代码实现的.

TCP_KEEPIDL:开始首次KeepAlive探测前的TCP空闭时间.

TCP_KEEPINTVL:两次KeepAlive探测间的时间间隔.

TCP_KEEPCNT:判定断开前的KeepAlive探测次数.

 

注意:这些属性都必须在客户端(发送端)设置.如果在服务器端(接收端)accepte后设置,只能释放接收端未能释放的连接.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值