How to detect whether the socket is closed, socket closing detection and processing

socket closing detection and processing

Detect socket shutdown
reference SIGPIPE signal processing and sorting

When calling write, send, sendto and other sending functions, the SIGPIPE signal is triggered, resulting in the direct exit of the program.

Program received signal SIGPIPE, Broken pipe.
0x00007ffff7af2224 in write () from /lib/x86_64-linux-gnu/libc.so.6

After the program sets errno to EPIPE, the program receives the SIGPIPE signal sent by the kernel and exits.

Conditions for generating SIGPIPE:

  1. Call the read method on a socket that has received a FIN packet. If the receive buffer is empty, it returns 0, which is often referred to as "connection closed".

  2. When the write method is called for the first time for a socket that has received a FIN packet, if the sending buffer is OK, the write call will return the written data and send the data at the same time. However, the sent message will cause the opposite end to send back RST message. Because the socket at the opposite end has called close to completely close, it is in the state of neither sending nor receiving data. Therefore, when the write method is called the second time (assuming that after receiving RST), a SIGPIPE signal will be generated, resulting in the process exiting (which is why the SIGPIPE can be triggered only after the second write).

Handling SIGPIPE How to prevent SIGPIPEs (or handle them properly)

  1. Set signal processing function

    signal(SIGPIPE, SIG_IGN);

  2. Set the socket option, ignore the SIGPIPE signal and process the corresponding errno instead, so that the signal processing function does not need to be installed

    int set = 1;
    setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
    

Note: there is no so in Linux_ Nosigpipe signal: SO_NOSIGPIPE was not declared, but we can bring MSG on the signal calling send or recv_ NOSIGNAL

send(fd, buf, nBytes, MSG_NOSIGNAL);

The server closes the socket

The following tests are done. The following code is the client code fragment. The server uses nc to listen on a port as the server

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));

Close the server within 10s of sleep, and the printed log is as follows

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 captures packets as follows: close the server and capture packets

Server: 192.168.92.1:2233
Client: 192.168.92.139:port

The first three packets in the picture are three handshake packets. After three handshakes, press Ctrl + C to terminate the server. The server sends FIN to the client, and the client replies ACK.

When the client calls recv, there is a direct error. When calling send, the server will receive the data sent by the client (here are 5 bytes: hello), and then the server will reply to RST to reset the connection.

If the client sends the data first and closes the connection before the client calls recv, the client can still receive the data because the data has reached the client kernel buffer

Close the connection after the server sends data

Corresponding log

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

How about calling recv and send twice in a row?

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

You can see that send will send data (it should be sent to the kernel buffer), but errno = EINPROGRESS(Operation now in progress) cannot operate on resources

That is to say, as the customer service side, if the service side is closed, and then call recv to read the message from the service network, it will return 0, and errno = EINPROGRESS; When you call send, the server will return n (n > 0) and 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);
}

The client closes the socket

Test method: as a client, nc connects to the server and disconnects after three handshakes.

The following is the code of the server

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);

After closing the client, the server reads and writes the corresponding log

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

The errors received on the server are different

Try reading and writing many times and find that the error is still the same.

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);

The corresponding logs are as follows

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

That is, call recv to read the client on the server. If recv returns 0 and errno = EAGAIN (Resource temporarily unavailable), it means that the opposite socket is closed; If the call write returns n (n > 0) and errno = EAGAIN (Resource temporarily unavailable), it indicates that the peer socket is closed.

// MSG_NOSIGNAL indicates that the signal is ignored. If it is not ignored, SIGPIPE signal will be generated
// Cause the program to exit and set 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);
}

If recv returns - 1 and errno = EAGAIN, it means that the client has no data and the socket is normal; If the call to send returns - 1 and errno = EAGAIN, the socket is also normal.

Tags: Linux socket server computer networks

Posted on Sat, 23 Oct 2021 09:19:43 -0400 by perpetualshaun