socket的关闭检测及处理
检测socket关闭
reference SIGPIPE 信号处理整理
调用write, send, sendto等发送函数时,触发 SIGPIPE 信号,导致程序直接退出。
Program received signal SIGPIPE, Broken pipe.
0x00007ffff7af2224 in write () from /lib/x86_64-linux-gnu/libc.so.6
程序将 errno 设置为 EPIPE 之后,程序接受到内核发送过来的 SIGPIPE 信号退出。
产生 SIGPIPE 的条件:
-
对一个已经收到 FIN 包的 socket 调用 read 方法,如果接收缓冲已空,则返回 0,这就是常说的“连接关闭”表示。
-
对一个已经收到 FIN 包的 socket 第一次调用 write 方法时,如果发送缓冲没问题,则 write 调用会返回写入的数据量,同时进行数据发送。但是发送出去的报文会导致对端发回 RST 报文。因为对端的 socket 已经调用了 close 进行了完全关闭,已经处于既不发送,也不接收数据的状态。所以第二次调用 write 方法时(假设在收到 RST 之后)会生成 SIGPIPE 信号,导致进程退出(这就是为什么第二次 write 才能触发 SIGPIPE 的原因)。
处理SIGPIPE How to prevent SIGPIPEs (or handle them properly)
-
设置信号处理函数
signal(SIGPIPE, SIG_IGN);
-
设置socket option,忽略SIGPIPE信号,转为处理对应的errno,这样就不用安装信号处理函数
int set = 1; setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
注意:在Linux中没有SO_NOSIGPIPE信号: SO_NOSIGPIPE was not declared,但是我们可以在调用send或者recv的信号带上MSG_NOSIGNAL
send(fd, buf, nBytes, MSG_NOSIGNAL);
服务端关闭socket
做了如下测试,如下代码是客户端代码片段,服务端用nc监听一个端口作为服务端
sleep(10);
char buf[32];
ret = recv(event[i].data.fd, buf, 32, MSG_NOSIGNAL);
LOG_DEBUG("recv ret: %d, errno: %d, error: %s", ret, errno, strerror(errno));
ret = send(event[i].data.fd, "hello", 5, MSG_NOSIGNAL);
LOG_DEBUG("send ret: %d, errno: %d, error: %s", ret, errno, strerror(errno));
在sleep的10s时间内关闭服务端,打印的日志如下
2021-10-23 Sat 11:38:02 [debug] epolltestconnect.c:163 In function 'server_run' --> recv ret: 0, errno: 115, error: Operation now in progress
2021-10-23 Sat 11:38:02 [debug] epolltestconnect.c:165 In function 'server_run' --> send ret: 5, errno: 115, error: Operation now in progress
wireshark抓包如下 关闭服务端之后抓包
服务端:192.168.92.1:2233
客户端:192.168.92.139:port
图片的前三个包是三次握手包,在三次握手之后,按下Ctrl + C终止服务端,服务端向客户端发送FIN,客户端回复ACK。
在客户端调用recv直接出错,调用send的时候,服务端会收到客户端发过来的数据(这里是5个字节:hello),接着服务端回复RST重置连接。
如果是在客户端先发送数据,在客户端调用recv之前关闭连接,那么客户端还是能收到数据,因为数据已经到达客户端内核缓冲区
服务端发送数据之后关闭连接
对应的日志
2021-10-23 Sat 12:02:25 [debug] epolltestconnect.c:163 In function 'server_run' --> recv ret: 6, errno: 115, error: Operation now in progress
2021-10-23 Sat 12:02:25 [debug] epolltestconnect.c:165 In function 'server_run' --> send ret: 5, errno: 115, error: Operation now in progress
如果连续调用两次recv和send,回怎么样呢?
2021-10-23 Sat 12:08:56 [debug] epolltestconnect.c:163 In function 'server_run' --> recv ret: 0, errno: 115, error: Operation now in progress
2021-10-23 Sat 12:08:56 [debug] epolltestconnect.c:165 In function 'server_run' --> send ret: 5, errno: 115, error: Operation now in progress
2021-10-23 Sat 12:08:56 [debug] epolltestconnect.c:167 In function 'server_run' --> recv ret: 0, errno: 115, error: Operation now in progress
2021-10-23 Sat 12:08:56 [debug] epolltestconnect.c:169 In function 'server_run' --> send ret: 5, errno: 115, error: Operation now in progress
可以看到send的话是会把数据发出去的(应该是发到内核缓冲区),但是errno = EINPROGRESS(Operation now in progress)并不能对资源进行操作.
也就是说作为客服端,如果服务端关闭,再调用recv从服务网读取消息,会返回0,且errno = EINPROGRESS; 调用send像服务端发送信息会返回n(n>0),且errno = EINPROGRESS
ret = recv(server_fd, buf, bufsize, MSG_NOSIGNAL);
if (ret == 0 && errno = EINPROGRESS) {
// server side closed, close client side here.
close(sever_fd);
}
ret = send(server_fd, buf, bufsize, MSG_NOSIGNAL);
if (ret > 0 && errno = EINPROGRESS) {
// server side closed, close client side here.
close(sever_fd);
}
客户端关闭socket
测试方法:nc作为客户端去连接服务端,三次握手之后就断开。
以下是服务端的代码
client_fd = accept(fd, (struct sockaddr *)&client_addr, &client_addr_len);
sleep(10);
ret = recv(client_fd, buf, 32, MSG_NOSIGNAL);
LOG_DEBUG("recv ret: %d, errno: %d, error: %s", ret, errno, strerror(errno));
ret = send(client_fd, "hello", 5, MSG_NOSIGNAL);
LOG_DEBUG("send ret: %d, errno: %d, error: %s", ret, errno, strerror(errno));
close(client_fd);
关闭客户端后再服务端读写, 对应的日志
2021-10-23 Sat 12:55:40 [debug] epolltestconnect.c:219 In function 'main' --> recv ret: 0, errno: 11, error: Resource temporarily unavailable
2021-10-23 Sat 12:55:40 [debug] epolltestconnect.c:221 In function 'main' --> send ret: 5, errno: 11, error: Resource temporarily unavailable
client:192.168.92.1
server:192.168.92.139:2233
在服务端收到的错误不一样
尝试多次读写,发现得到的错误还是一样的。
client_fd = accept(fd, (struct sockaddr *)&client_addr, &client_addr_len);
sleep(10);
ret = recv(client_fd, buf, 32, MSG_NOSIGNAL);
LOG_DEBUG("recv ret: %d, errno: %d, error: %s", ret, errno, strerror(errno));
ret = send(client_fd, "hello", 5, MSG_NOSIGNAL);
LOG_DEBUG("send ret: %d, errno: %d, error: %s", ret, errno, strerror(errno));
ret = recv(client_fd, buf, 32, MSG_NOSIGNAL);
LOG_DEBUG("recv ret: %d, errno: %d, error: %s", ret, errno, strerror(errno));
ret = send(client_fd, "hello", 5, MSG_NOSIGNAL);
LOG_DEBUG("send ret: %d, errno: %d, error: %s", ret, errno, strerror(errno));
close(client_fd);
对应的日志如下
2021-10-23 Sat 13:04:18 [debug] epolltestconnect.c:219 In function 'main' --> recv ret: 0, errno: 11, error: Resource temporarily unavailable
2021-10-23 Sat 13:04:18 [debug] epolltestconnect.c:221 In function 'main' --> send ret: 5, errno: 11, error: Resource temporarily unavailable
2021-10-23 Sat 13:04:18 [debug] epolltestconnect.c:223 In function 'main' --> recv ret: 0, errno: 11, error: Resource temporarily unavailable
2021-10-23 Sat 13:04:18 [debug] epolltestconnect.c:225 In function 'main' --> send ret: 5, errno: 11, error: Resource temporarily unavailable
也就是说在服务端调用recv读客户端,如果recv返回0,且errno = EAGAIN (Resource temporarily unavailable), 表示对端socket关闭;如果调用write返回n(n>0),且errno = EAGAIN (Resource temporarily unavailable), 表示对端socket关闭。
// MSG_NOSIGNAL表示忽略信号,如果不忽略,会产生SIGPIPE信号
// 导致程序退出,并设置errno = EPIPE
ret = recv(client_fd, buf, bufsize, MSG_NOSIGNAL);
if (ret == 0 && errno == EAGAIN) {
// client side closed, close server side here
close(client_fd);
}
ret = send(client_fd, buf, bufsize, MSG_NOSIGNAL);
if (ret > 0 && errno == EAGAIN) {
// client side closed, close server side here
close(client_fd);
}
如果是recv返回-1,且errno = EAGAIN,表示客户端无数据到达,socket是正常的;如果调用send返回-1,且errno = EAGAIN,socket也是正常的。