错误码EWOULDBLOCK
在某些套接字的函数操作不能立即完成时,会出现错误码EWOULDBLOCK
和EAGAIN
Linux EINTR错误码
在类UNIX/Linux中调用一些socket
函数时(connect,send,recv,epoll_wait
等),除了在函数调用出错时会返回-1,这些函数可能被信号中断时也会返回-1,此时我们可以通过错误码errno
判断是不是EINTR
,来确定是不是被信号中断。如果是,则说明被信号中断,我们需要再次调用该函数进行重试。
bool SendData(const char* buf, int buf_length)
{
//已发送的字节数
int sent_bytes = 0;
int ret = 0;
while(true)
{
ret = send(m_hSocket, buf + sent_bytes, buf_length - sent_bytes, 0);
if(nRet == -1)
{
if(errno == EWOULDBLOCK)
{
break;
}
else if(errno == EINTR)
continue;
else
return false;
}
else if(nRet == 0)
return false;
sent_bytes += ret;
if(sent_bytes == buf_length)
break;
}
return true;
}
Linux SIGPIPE信号
为了描述方便,以下将TCP的通信双方用A和B来代替。当A关闭连接时,若B继续向A发送数据,则根据TCP的规定,B会收到A的一个RST报文应答,若B继续向这个服务器发送数据,系统就会产生一个SIGPIPE
信号给该B进程,告诉它这个连接已经断开了。系统对SIGPIPE
信号的默认处理行为是让B进程退出。
操作系统对SIGPIPE
信号的这种默认处理行为非常不友好,TCP是全双工的信道,可以看作两条单工信道,TCP连接两端的两个端点各负责一条。当对端执行close()
关闭时,虽然本意是关闭整个两条信道,但本端只收到FIN包。按照TCP规定的语义,表示对端只关闭了其所负责的那一条单工信道,虽然不再发送数据,但仍然可以继续接收数据。
也就是说,因为TCP的限制,通信的一方无法获知对端的socket
是调用了close()
还是调用了shutdown()
函数。
int shutdown(int socket,int how);
shutdown
函数的参数how
可以被设置为SHUT_RD,SHUT_WR,SHUT_RDWR
分别表示关闭收通道,发通道或者同时关闭收发通道。
对一个已经收到FIN包的socket调用read/recv
方法,如果接收缓冲区已经为空,则会返回0,这就是连接关闭状态。但第1次对其调用write/send
方法时,如果发送缓冲区没问题,则发送成功(即write/send
函数的返回值大于0),但发送的报文会导致对端回应RST
报文。因为上一次程序调用write/send
是正常的,所以再次尝试调用write/send
函数时,会因为产生SIGPIPE
信号而导致进程退出。
为了避免出现这种情况,我们可以捕获SIGPIPE
信号并对其进行处理或者忽略该信号,忽略该信号的代码如下:
signal(SIGPIPE, SIG_IGN);
这样设置后,第2次调用write/send
方法时会返回-1,同时,errno
错误码被置为SIGPIPE
,程序便能知道对端已经关闭。