linux socket tcp 断开检测

经过实际测试,根据测试结果做的socket断开检测。目前应用在项目中正常。有更好的方法,期待交流。

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超时检测,若超时,要么对端没有再发数据,要么对端的网线断了或者直接断电了。

超过我们判定为对端不在线,关闭本地的fd。

 /**
  *设置为非阻塞方式
  *@return -1 failure, 0 success.
**/
int set_socket_nonblock(int sockfd)
{    
      int block_flag = fcntl(sockfd, F_GETFL, 0);
      if(block_flag < 0){
        err_msg("get socket fd flag error:%s\n", strerror(errno));
        return -1;
      }
      else{
        if(fcntl(sockfd, F_SETFL, block_flag | O_NONBLOCK) < 0){
            err_msg("set socket fd non block error:%s\n", strerror(errno));
            return -1;
        }
      }
      return 0;
 }

/**
  *@return 
  * 0 success,
  *-1 failure,errno==EPIPE, 对端进程fd被关掉
  *-2 failure,网络断开或者对端超时未再发送数据
  *-3 failure,other errno
**/
int socket_nonblock_send(int fd, unsigned char* buffer,  unsigned int length, unsigned long timeout)
{
    if(length == 0 || buffer == NULL){
        err_msg("buffer point is NULL or length is zero\n");
        return 0;
    }
    unsigned int bytes_left;  //无符号
    long long written_bytes;  //有符号
    unsigned char* ptr;
    ptr = buffer;
    bytes_left = length;
    fd_set writefds;
    struct timeval tv;
    int ret = 0;
    while(bytes_left > 0){
        written_bytes = send(fd, ptr, bytes_left, MSG_NOSIGNAL);
        if(written_bytes < 0){
            if(errno == EINTR )     //由于信号中断,没写成功任何数据
                written_bytes = 0;
            else if(errno == EWOULDBLOCK){    //即EAGAIN,socket内核缓存区满,或者网线断开
                FD_ZERO(&writefds);
                FD_SET(fd, &writefds);
                tv.tv_sec = timeout/1000000;
                tv.tv_usec = timeout%1000000;
                ret = select(fd+1, NULL, &writefds, NULL, &tv); //阻塞,err:0 timeout err:-1 错误见errno
                if(ret == 0){    //超时,判定为网线断开
                    err_msg("select error:%s\n", strerror(errno));
                    return -2;
                }
                else if(ret < 0 && errno != EINTR) {
                    err_msg("select error:%s\n", strerror(errno));
                    return -2;;
                }
                written_bytes = 0;  //未超时,判定为socket缓冲区满导致的网络阻塞
            }
            else if(errno == EPIPE){     //连接套接字的本地端已关闭.(如对端被kill掉)
                err_msg("write socket error %d:%s\n", errno, strerror(errno));
                return -1;
            }
            else{       //其他错误
                err_msg("write socket error %d:%s\n", errno, strerror(errno));
                return -3;
            }
        }
        bytes_left -= written_bytes;
        ptr += written_bytes;
    }
    return 0;
}

/**
  *@return 
    the number of bytes read success,
    -1 failure, 对端进程fd被关掉或者对端超时未再发送数据
    -2 failure,网络断开或者对端超时未再发送数据
    -3 failure,other errno
**/
long long socket_nonblock_recv(int fd, unsigned char* buffer, unsigned int length, unsigned long timeout)
{
    unsigned int bytes_left;
    long long read_bytes;
    unsigned char* ptr;
    ptr = buffer;
    bytes_left = length;
    fd_set readfds;
    int ret = 0;
    struct timeval tv;
    while(bytes_left > 0){
        read_bytes = recv(fd, ptr, bytes_left, 0);
        if(read_bytes < 0){
            if(errno == EINTR)      //由于信号中断
                read_bytes = 0;
            else if(errno == EAGAIN){    //EAGAIN 没有可读写数据,缓冲区无数据
                if(length > bytes_left)    //说明上一循环把缓冲区数据读完,继续读返回-1,应返回已读取的长度
                    return (length - bytes_left);
                else{    //length == bytes_left,说明第一次调用该函数就无数据可读,可能是对端无数据发来,可能是对端网线断了
                    FD_ZERO(&readfds);
                    FD_SET(fd, &readfds);
                    tv.tv_sec = timeout/1000000;
                    tv.tv_usec = timeout%1000000;
                    ret = select(fd+1, &readfds, NULL, NULL, &tv); //阻塞,err:0 timeout err:-1 错误见errno
                    if(ret == 0){    //超时,判定为网线断开
                        err_msg("select error:%s\n", strerror(errno));
                        return -2;
                    }
                    else if(ret < 0 && errno != EINTR) {
                        err_msg("select error:%s\n", strerror(errno));
                        return -2;
                    }
                    //未超时,有数据到来,继续读
                    continue;
               }
            }
            else {      //其他错误
                err_msg("read socket buf error:%s\n", strerror(errno));
                return -3;
            }  
        }
        else if(read_bytes == 0){    //缓冲区数据读完,对端fd 关闭或对端没有发数据了,超时10s后判定为连接已断
            FD_ZERO(&readfds);
            FD_SET(fd, &readfds);
            tv.tv_sec = timeout/1000000;
            tv.tv_usec = timeout%1000000;
            ret = select(fd+1, NULL, NULL, &readfds, &tv); //阻塞,err:0 timeout err:-1 错误见errno
            if(ret == 0){   //超时,对端fd关闭,或对端没有再发数据
                err_msg("select error:%s\n", strerror(errno));
                return -1;
            }
            else if(ret < 0 && errno != EINTR){
                err_msg("select error:%s\n", strerror(errno));
                return -1;
            }
            //未超时,有数据到来,继续读
            continue;
        }
        bytes_left -= read_bytes;
        ptr += read_bytes;
    }
    return (length - bytes_left);
}

3、对端close掉socket fd,send导致程序终止问题。

问题描述:

当服务器close一个连接时,若client端接着发数据。根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不要再写了。

又或者当一个进程向某个已经收到RST的socket执行写操作是,内核向该进程发送一个SIGPIPE信号。该信号的缺省学位是终止进程,因此进程必须捕获它以免不情愿的被终止。

问题原因:
根据信号的默认处理规则SIGPIPE信号的默认执行动作是terminate(终止、退出),所以进程会退出。
系统里边定义了三种处理方法: 
    1)SIG_DFL     
    2)SIG_IGN     
    3)SIG_ERR     
若不想客户端退出,需要把 SIGPIPE默认执行动作屏蔽。


问题解决:

使用send的第四个参数MSG_NOSIGNAL,禁止send()函数向系统发送异常信息,此时将屏蔽SIGPIPE信号。

send(fd, ptr, bytes_left, MSG_NOSIGNAL);

以下是网络找到的方法,未测试:

http://blog.sina.com.cn/s/blog_6f7c07a00101g7u3.html

用signal(SIGCHLD,SIG_IGN)或者重载其处理方法。个人选了后者。两者区别在于signal设置的信号句柄只能起一次作用,信号被捕获一次后,信号句柄就会被还原成默认 值了;sigaction设置的信号句柄,可以一直有效,值到你再次改变它的设置。具体代码如下:
struct sigaction action;
action.sa_handler = handle_pipe;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
sigaction(SIGPIPE, &action, NULL);
void handle_pipe(int sig)
{//不做任何处理即可}
在源文件中要添加signal.h头文件:#include <signal.h>。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值