0

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:

  1. Client sends a message before client closes
  2. 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?

mid_kid
  • 1,460
  • 2
  • 13
  • 15
  • If you send data to a closed connection, you get a RST response. Any following use of the socket will get `ECONNRESET`. – Barmar May 22 '23 at 16:15
  • Sure, but the send happens before the connection is closed, and the error is only returned upon recv(), why? Can I catch it as it happens instead of getting it in recv()? – mid_kid May 22 '23 at 16:17
  • 1
    No you can't, because sending is done asynchronously. – Barmar May 22 '23 at 16:18
  • Also, when I re-order the lines in Test 2, to close the connection *before* sending, recv() behaves as it does in Test 1, making this behavior extra weird. I just didn't want to make the question too broad, so I left this tidbit out. – mid_kid May 22 '23 at 16:20
  • 1
    The relevant reason for your case from [the answer](https://stackoverflow.com/a/35093313/3081018) to the duplicated question is *"the peer closed the connection while he still had unread data pending"*: you've send data from server to client but the client closed the connection without reading the data. – Steffen Ullrich May 22 '23 at 17:26
  • That does answer the first half of the question. I'm leaving on the duplicate mark, but it'd be awesome if someone could answer the second half or detail why it isn't possible. – mid_kid May 22 '23 at 17:56

0 Answers0