检查非正常断开的tcp连接

当客户端connect端连接上之后,正常通信一会儿,1.客户端断电或者2.断网或者3.关闭fd,服务器怎么检测到客户端已经断开,并且把对应的sockfd 关闭(close(fd))?

1.第一种方法是通过客户端或者服务端开启一个线程不停的发送心跳包,通过心跳包判断客户端存活

2.第二种利用recv和send的返回值和产生的errno来判断tcp连接情况

1、send非阻塞

client/server端send: 

①对端close(fd)检测:send 返回-1,错误errno == EPIPE,说明对端进程被kill掉,即正常close(fd)的效果

②网线断开/断电关机检测(超时检测):send 返回-1,errno == EWOULDBLOCK,意为“套接字设置为非阻塞式,但所请求的操作需要阻塞”

但该错误码,除了指网线断开,也发生在socket内核缓冲区满。

所以我们通过加超时检测或者计数来判断。select超时,则判定为网络连接失败,关闭本地端socket fd

2、recv非阻塞

client/server端recv:

①对端close(fd)检测(超时检测):recv 返回0,说明缓存区数据被读完,实测两种情况,对端没有再发数据,对端fd被关闭了。

通过select超时来检测,当超时如2s,我们认为对端连接断开,应关闭本地的fd。

②网线断开/断电关机检测(超时检测):recv 返回-1,errno == EAGAIN,意为“没有可读写数据,缓冲区无数据”。

所以此时需要select超时检测,若超时,要么对端没有再发数据,要么对端的网线断了或者直接断电了

3.利用tcp自带的keep –alive机制

1.直接查看tcp状态机状态(类似netstat 指令的效果)

/*
系统头文件/usr/include/netinet/tcp.h 
enum
{   
    TCP_ESTABLISHED = 1,
    TCP_SYN_SENT,
    TCP_SYN_RECV,
    TCP_FIN_WAIT1,
    TCP_FIN_WAIT2,
    TCP_TIME_WAIT,
    TCP_CLOSE,
    TCP_CLOSE_WAIT,
    TCP_LAST_ACK,
    TCP_LISTEN,
    TCP_CLOSING 
}; 
*/
int tcp_state(int tcp_fd)
{
    struct tcp_info info;
    int optlen = sizeof(struct tcp_info);
    if (getsockopt (tcp_fd,IPPROTO_TCP,TCP_INFO, &info, (socklen_t *)&optlen) < 0)
    {
        printf ("getsockopt() TCP_INFO error\n");
        return -1; 
    }
    printf ("%d\n",info.tcpi_state);
    if (info.tcpi_state == TCP_ESTABLISHED)
    {
        printf("connect\n");
        return 0;
    }
    else 
    {
        printf("not connect\n");
        return -1;        
    }
}

Tcp是面向连接的,在实际应用中通常都需要检测连接是否还可用.如果不可用,可分为:

  1. 连接的对端正常关闭.
  2. 连接的对端非正常关闭,这包括对端设备掉电,程序崩溃,网络被中断等.这种情况是不能也无法通知对端的,所以连接会一直存在

2.很多系统tcp实现了keepalive功能,即tcp的心跳:

    int keepAlive = 1; // 开启keepalive属性
    int keepIdle = 10; // 如该连接在10秒内没有任何数据往来,则进行探测 
    int keepInterval = 5; // 探测时发包的时间间隔为5 秒
    int keepCount = 3; // 探测尝试的次数.如果第1次探测包就收到响应了,则后2次的不再发.
    setsockopt(socketfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
    setsockopt(socketfd, SOL_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle));
    setsockopt(socketfd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
    setsockopt(socketfd, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));

设置启用tcp keepalive功能,经实测有效,有tcp心跳包,超时自动挂断

tcp协议栈有个keepalive的属性,可以主动探测socket是否可用,不过这个属性的默认值很大.

Linux方法:

全局设置可更改/etc/sysctl.conf,加上:

net.ipv4.tcp_keepalive_intvl = 20
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.tcp_keepalive_time = 60

 

server.c:

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/tcp.h>
#include<errno.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#define N  128

#define errlog(errmsg) do{perror(errmsg); exit(1);}while(0)
/*
系统头文件/usr/include/netinet/tcp.h 
enum
{   
    TCP_ESTABLISHED = 1,
    TCP_SYN_SENT,
    TCP_SYN_RECV,
    TCP_FIN_WAIT1,
    TCP_FIN_WAIT2,
    TCP_TIME_WAIT,
    TCP_CLOSE,
    TCP_CLOSE_WAIT,
    TCP_LAST_ACK,
    TCP_LISTEN,
    TCP_CLOSING 
}; 
*/
int tcp_state(int tcp_fd)
{
    struct tcp_info info;
    int optlen = sizeof(struct tcp_info);
    if (getsockopt (tcp_fd,IPPROTO_TCP,TCP_INFO, &info, (socklen_t *)&optlen) < 0)
    {
        printf ("getsockopt() TCP_INFO error\n");
        return -1; 
    }
    printf ("%d\n",info.tcpi_state);
    if (info.tcpi_state == TCP_ESTABLISHED)
    {
        printf("connect\n");
        return 0;
    }
    else 
    {
        printf("not connect\n");
        return -1;        
    }
}

int main(int argc, const char *argv[])
{
    int socketfd,acceptfd;
    char buf[N]={};
    int ret =0 ;
    struct sockaddr_in serveraddr,clientaddr; 
    socklen_t  addrlen= sizeof(struct sockaddr_in);

    if((socketfd=socket(AF_INET,SOCK_STREAM,0))<0)
    {
        perror("fail to socket");
        exit(1);
    }
    serveraddr.sin_family=AF_INET;
    serveraddr.sin_addr.s_addr=inet_addr(argv[1]);
    serveraddr.sin_port=htons(8888);

    tcp_state(socketfd);

    int keepAlive = 1; // 开启keepalive属性
    int keepIdle = 10; // 如该连接在10秒内没有任何数据往来,则进行探测 
    int keepInterval = 5; // 探测时发包的时间间隔为5 秒
    int keepCount = 3; // 探测尝试的次数.如果第1次探测包就收到响应了,则后2次的不再发.
    setsockopt(socketfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
    setsockopt(socketfd, SOL_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle));
    setsockopt(socketfd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
    setsockopt(socketfd, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));

    if(bind(socketfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0)
    {
        perror("fail to bind");
        exit(1);
    }

    if(listen(socketfd,10)<0)
    {
        perror("fail  to  listen");
        exit(1);
    }
    if((acceptfd=accept(socketfd,(struct sockaddr *)&clientaddr,&addrlen))<0)
    {
        perror("fail to accept");
        exit(1);
    }  
    printf("%s----%d\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));

    while(1)
    {
        memset(buf,0,sizeof(buf));
        if((ret=recv(acceptfd,buf,6,0))<0)
        {
            perror("fail to recv");
            close(socketfd);
            return -1;
        }
        else if (ret == 0)
        {
            perror("fail to recv");
            close(socketfd);
            return -1;
        }

        //fprintf(stderr,"ret==%d--errno--%d---%s\n",ret,errno,strerror(errno));
        fprintf(stderr,"----%s\n",buf);

        if(strncmp(buf,"quit",4)==0)
        {
            break;
        }
        else
        {
            //	   strcat(buf," from server ...");
            //	   if(send(acceptfd,buf,N,0)<0)
            //	   {
            //		   perror("fail to server");
            //		   exit(1);
            //	   }
        }
    }

    close(socketfd);


    return 0;
}

client.c:

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<errno.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#define N  128
int main(int argc, const char *argv[])
{
    int socketfd;
    char buf[N]={0};
    struct sockaddr_in serveraddr,clientserver; 
    socklen_t  addrlen= sizeof(struct sockaddr_in);

    if((socketfd=socket(AF_INET,SOCK_STREAM,0))<0)
    {
        perror("fail to socket");
        exit(1);
    }
    serveraddr.sin_family=AF_INET;
    serveraddr.sin_addr.s_addr=inet_addr(argv[1]);
    serveraddr.sin_port=htons(8888);
    /* 
       clientserver.sin_family=AF_INET;
       clientserver.sin_addr.s_addr=inet_addr(argv[3]);
       clientserver.sin_port=htons(atoi(argv[4]));   //指定客户端的端口号

       if(bind(socketfd,(struct sockaddr *)&clientserver,sizeof(clientserver))<0)
       {
       perror("fail to bind");
       exit(1);
       }
       */

    if(connect(socketfd,(struct sockaddr *)&serveraddr,addrlen)<0)
    {
        perror("fail to connect");
        exit(1);
    }  
    printf("%s----%d\n",inet_ntoa(serveraddr.sin_addr),ntohs(serveraddr.sin_port));

    while(1)
    {
        //fgets(buf,N,stdin);  //成功返回读到的字符串
        //buf[strlen(buf)-1]='\0';
        sleep(5);
        if(send(socketfd,"hello",6,MSG_NOSIGNAL)<0)
        {
            perror("fail to send");
            exit(1);
        }
        else
        {
            //    if(recv(socketfd,buf,N,0)<0)
            //	{
            //	   perror("fail  to ");
            //	   exit(1);
            //	}
            printf("--------%s\n",buf);
        }
    }
    close(socketfd); 
    return 0;
}

测试结果:

lyz@lyz-virtual-machine:~/tcp_keepalive$ ./server  192.168.24.138
7
not connect
192.168.24.164----32840
----hello
----hello
----hello
fail to recv: Connection timed out

linux@ubuntu:~/wang/day1/tcp_keepalive$ ./client   192.168.24.138
192.168.24.138----8888
--------
--------
--------
--------
--------
--------
--------
--------
--------
--------
--------
--------
--------
--------

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值