经过实际测试,根据测试结果做的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>。
再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!希望你也加入到我们人工智能的队伍中来!http://www.captainbed.net