经过实际测试,根据测试结果做的socket断开检测。目前应用在项目中正常。有更好的方法,期待交流。
1、send非阻塞
client/server端send:超过我们判定为对端不在线,关闭本地的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);
}
问题描述:
当服务器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设置的信号句柄,可以一直有效,值到你再次改变它的设置。具体代码如下: