I'm writing a server program in python, and I've noticed that under some circumstances, when the client disconnects, socket.recv() raises a ConnectionResetError.
While debugging this issue, I've noticed that this only happens when the server has sent data that the client hasn't received before disconnecting, causing recv() to issue an ECONNRESET.
To further introspect the issue, discarding any possible python-specific issues, I've created a test program in C. This program creates a server socket, and executes the following tests, connecting a new client to the server each time:
- Client sends a message before client closes
- Server sends a message before client closes
After every test, recv() is executed twice, printing any errors that occur, or alternatively the contents of the received data.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
static const struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = 0xd204, // 1234
.sin_addr = {0x0100007f} // 127.0.0.1
};
// Create a client socket, connect, and accept a server connection
void doconnect(int server, int *client, int *srconn)
{
*client = socket(AF_INET, SOCK_STREAM, 0);
if (*client == -1) { perror("socket"); exit(1); }
int rc = connect(*client, (struct sockaddr *)&addr, sizeof(addr));
if (rc == -1) { perror("connect"); exit(1); }
*srconn = accept(server, NULL, NULL);
if (*srconn == -1) { perror("accept"); exit(1); }
}
// Execute recv(), printing received contents if successful
void dorecv(int srconn)
{
ssize_t recvlen;
char recvbuf[1024];
recvlen = recv(srconn, recvbuf, 1024, 0);
if (recvlen == -1) { perror("recv"); return; }
printf("%ld: b'", recvlen);
fwrite(recvbuf, recvlen, 1, stdout);
printf("'\n");
}
int main()
{
int rc;
int server;
// Create server socket
server = socket(AF_INET, SOCK_STREAM, 0);
if (server == -1) { perror("socket"); exit(1); }
rc = setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int));
if (rc == -1) { perror("setsockopt"); exit(1); }
rc = bind(server, (struct sockaddr *)&addr, sizeof(addr));
if (rc == -1) { perror("bind"); exit(1); }
rc = listen(server, 0);
if (rc == -1) { perror("listen"); exit(1); }
int client, srconn;
puts("Test 1: Client sends a message before client closes");
doconnect(server, &client, &srconn);
rc = send(client, "foo", 3, 0);
if (rc == -1) { perror("send"); exit(1); }
rc = close(client);
if (rc == -1) { perror("close"); exit(1); }
dorecv(srconn);
dorecv(srconn);
puts("Test 2: Server sends a message before client closes");
doconnect(server, &client, &srconn);
rc = send(srconn, "foo", 3, 0);
if (rc == -1) { perror("send"); exit(1); }
rc = close(client);
if (rc == -1) { perror("close"); exit(1); }
dorecv(srconn);
dorecv(srconn);
}
This program outputs the following:
$ cc -std=c17 -Wall -Wextra test.c && ./a.out
Test 1: Client sends a message before client closes
3: b'foo'
0: b''
Test 2: Server sends a message before client closes
recv: Connection reset by peer
0: b''
As expected, calling recv() after the client has closed the connection usually returns 0 after all data has been received, making client disconnection easy to detect. However, in the second test, the first call to recv() results in an ECONNRESET error. Unfortunately, ECONNRESET isn't listed as an error for recv() in its manpage, so I can't tell why it's happening.
Why is ECONNRESET being raised here? Assuming this only happens after a send(), is there any way to catch/trigger this error before the recv() call, so I can handle it elsewhere?