-1

When I send() and recv() data from my program locally it works fine.

However, on my remote server, the same program, which usually receives data in chunks of 4096, will receive in buffers capped at 1428, which rarely jump above this number.

Worse of all, after a minute or so of transferring data the socket just freezes and stops execution, and the program perpetually stays in this frozen state, like so:

Received: 4096
Received: 4096
Received: 3416

The server is simple, it accepts a connection from a client and receives data in chunks of 4096, which works absolutely fine locally, but on my remote server it is failing consistently, unless I only send a small chunk of data (sending 1000 byte files worked fine).

int main()
{

    while(1){

        int servSock = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
        if(servSock < 0){
            fprintf(stderr, "Socket error.\n");
            continue;
        }

        struct sockaddr_in servAddr;
        memset(&servAddr, 0, sizeof(servAddr));
        servAddr.sin_family = AF_INET;
        servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servAddr.sin_port = htons(atoi(STANDARD_PORT));

        if(bind(servSock, (struct sockaddr*) &servAddr, sizeof(servAddr)) < 0){
            fprintf(stderr, "Bind error.\n");
            close(servSock);
            continue;
        }

        if(listen(servSock, BACKLOG) < 0){
            fprintf(stderr, "Listen error.\n");
            close(servSock);
            continue;
        }
        printf("%s", "Listening on socket for incoming connections.\n");

        struct sockaddr_in clntAddr;
        socklen_t clntAddrLen = sizeof(clntAddr);

        while(1) {
        int newsock = accept(servSock, (struct sockaddr*) &clntAddr, &clntAddrLen);
        if(newsock < 0){
            fprintf(stderr, "Accept connection error");
                                return 1;

            continue;
        }

        char clntName[INET_ADDRSTRLEN];
        if (inet_ntop(AF_INET, &clntAddr.sin_addr.s_addr, clntName, sizeof(clntName)) != NULL)
            printf("Handling client %s:%d\n", clntName, ntohs(clntAddr.sin_port));

        char file[17];
        memset(file, 0, 17);
        int recvd =  recv(newsock, file, 16, 0);
        file[17] = '\0';
        char local_file_path[200];
        memset(local_file_path, 0, 200);

        strcat(local_file_path, "/home/");
        strcat(local_file_path, file);
        printf(local_file_path);
        FILE* fp = fopen(local_file_path, "wb");

        char buffer[4096];
        while(1)
        {
            memset(buffer, 0, 4096);
            recvd =  recv(newsock, buffer, 4096, 0);
            printf("Received: %d\n", recvd);
            fwrite(buffer, sizeof(char), recvd, fp);
            if(recvd == -1 || recvd == 0) {
                fclose(fp);
                break;
            } else
        }
        close(newsock);
        }
        close(servSock);
    }
    return 1;
}

EDIT: For more context, this is a Windows server I am adapting to linux. Perhaps the recv() call is blocking when it shouldn't be, I'm going to test with flags.

coolguy123
  • 67
  • 6
  • ethernet has a maximum packet size (MTU) of 1500 bytes, so larger sends will be split into multiple packets. Your code may have to change depending on how your socket was created. Please show a [mre] – stark Sep 09 '22 at 13:16
  • Are you sure the client is actually closing the connection when it's done? It looks like the server is probably just waiting for more data because it doesn't know it's the end. – user253751 Sep 09 '22 at 13:17
  • Both the client and the server are hanging. The client program just stops sending data, and the server freezes as well. @stark Edited to include the server code. – coolguy123 Sep 09 '22 at 13:19
  • `char file[17];`, `file[17] = '\0';` will invoke [Undefined Behaviour](https://en.cppreference.com/w/c/language/behavior). Last valid index is `16`. – Oka Sep 09 '22 at 13:20
  • @Oka do buffers have to be always multiples of two? Are buffers suchs buff[200] or buff[300] also invalid? – coolguy123 Sep 09 '22 at 13:25
  • @coolguy123: The sizes of buffers do not have to be multiples of powers of two. You can also write `char buff[200];` or `char buff[300];`. – Andreas Wenzel Sep 09 '22 at 13:28
  • You misunderstand. Valid indices for *accessing* `char file[17];` are `0` to `16`. Accessing `file[17]` is one past the end of the buffer. There are no requirements on buffer (array) *sizes* other than a minimum size of `1`. The assignment is additionally unneeded, as you zero-out the array beforehand with `memset`. – Oka Sep 09 '22 at 13:28
  • I see. I was confused about why '\0' at the end of a 17 long buffer would cause undefined behaviour, considering I only used memory up to the 16th char. – coolguy123 Sep 09 '22 at 13:29
  • 1
    In your previous comment, you wrote: `"considering I only used memory up to the 16th char"` -- This statement is incorrect. In the line `file[17] = '\0';`, you are accessing the 18th `char`. `file[0]` is the 1st `char`, so `file[17]` is the 18th `char`. – Andreas Wenzel Sep 09 '22 at 13:33
  • That's correct, my mistake. I've fixed the error by making my buffer one byte larger. – coolguy123 Sep 09 '22 at 13:35
  • 1
    `printf(local_file_path);` is dangerous, being another potential source of [Undefined Behaviour](https://en.cppreference.com/w/c/language/behavior). If `local_file_path` contains a byte sequence that constitutes a format specifier (`%s`, `%d`, etc.), the function will try to read the corresponding arguments - which do not exist. Use `printf("%s", local_file_path);`, or consider alternatives (e.g., [`puts`](https://man7.org/linux/man-pages/man3/puts.3.html)). – Oka Sep 09 '22 at 13:41
  • Thanks, changed that. My server is working now, for some reason. I think it's download speed is just terribly bad, so sometimes it gets "clogged". I don't know if that is a common ocurrence though. I think my best option is to add a timeout feature to the sockets. – coolguy123 Sep 09 '22 at 13:49
  • Note that although the transfer might indeed be unexpectedly slow, the difference in `read()` size is not necessarily evidence of that. Note also that it is possible that the issue is in the network, not in either program. – John Bollinger Sep 09 '22 at 14:01
  • Just this: 'int recvd = recv(newsock, file, 16, 0);' is unsafe. The recv() call could load any number [1..16] bytes into the 'file' buffer - you will not know how many because you do not correctly and completely handle the result returned by recv(). You are trying to build a TCP server/client while not understanding TCP. – Martin James Sep 09 '22 at 16:12
  • 'I think my best option is to add a timeout feature to the sockets' no. Your best option is to learn about TCP and fix your design/bugs. – Martin James Sep 09 '22 at 16:14
  • Also, 'memset(buffer, 0, 4096);' is just a waste of cycles. – Martin James Sep 09 '22 at 16:21
  • ..and you have yet to demonstrate that is is the server end of your connection that is causing the majority of your problems.. – Martin James Sep 09 '22 at 16:24
  • Edit in the client code. – Martin James Sep 09 '22 at 16:25
  • BTW, there is a 'sockets' tag that has much valuable Q&A. – Martin James Sep 09 '22 at 16:32
  • 1
    @Martin James I used memset because this data is being written in binary mode to a file. I assumed that since it is not a string/not null terminated, the entire 4096 bytes will be written in the last iteration of my loop, even though I will probably receive less than that, and this would include all garbage memory. – coolguy123 Sep 09 '22 at 16:42
  • 'the entire 4096 bytes will be written in the last iteration of my loop' no. You write the same number of bytes that you read: 'fwrite(buffer, sizeof(char), recvd, fp);'. There is no need to memset the buffer. You will not write any garbage bytes. – Martin James Sep 09 '22 at 17:34

1 Answers1

1

However, on my remote server, the same program, which usually receives data in chunks of 4096, will receive in buffers capped at 1428, which rarely jump above this number.

Insufficient context has been presented for confidence, but that looks like a plausible difference between a socket whose peer is on the same machine (one connected to localhost, for example) and one whose peer is physically separated from it by an ethernet network. The 1428 is pretty close to the typical MTU for such a network, and you have to allow space for protocol headers.

Additionally, you might be seeing that one system coallesces the payloads from multiple transport-layer packets more or differently than the other does, for any of a variety of reasons.

In any case, at the userspace level, the difference in transfer sizes for a stream socket is not semantically meaningful. In particular, you cannot rely upon one end of the connection to read data in the same size chunks that the other sends it. Nor can you necessarily rely on receiving data in full-buffer units, regardless of the total amount being transferred or the progress of the transfer.

Worse of all, after a minute or so of transferring data the socket just freezes and stops execution, and the program perpetually stays in this frozen state, like so:

"Worst" suggests other "bad", which you have not described. But yes, your code is susceptible to freezing. You will not see EOF on the socket until the remote peer closes their side, cleanly. The closure part is what EOF means for a network socket. The cleanness part is required, at the protocol level, for the local side to recognize the closure. If the other end holds the connection open but doesn't write anything else to it then just such a freeze will occur. If the other side is abruptly terminated, or physically or logically cut off from the network without a chance to close their socket, then just such a freeze will occur.

And indeed, you remarked in comments that ...

Both the client and the server are hanging. The client program just stops sending data, and the server freezes as well.

If the client hangs mid-transfer, then, following from the above, there is every reason to expect that the server will freeze, too. Thus, it sounds like you may be troubleshooting the wrong component.

Perhaps the recv() call is blocking when it shouldn't be, I'm going to test with flags.

There is every reason to think the recv() call is indeed blocking when you don't expect it to do. It's highly unlikely that it is blocking when it shouldn't.

It is possible to set timeouts for socket operations, so that they eventually will fail instead of hanging indefinitely when the remote side fails. Doing so would allow your server to recover, but it would not resolve the client-side issue. You'll need to look into that more deeply.*


*You might see the client unfreeze after the server times out and closes the connection on its end. Don't take that as a resolution.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Thank you for the elaborate answer. I think the issue was mainly connectivity and the client program freezing. I'm not a TCP expert by any means, but it could be that the incongruence between the client 4096 bytes at X speed and the server taking in this data at X / 4 speed might be causing this. I'm going to set timeouts, as well as look into a better internet speed plan. – coolguy123 Sep 09 '22 at 14:19
  • 1
    'it could be that the incongruence between the client 4096 bytes at X speed and the server taking in this data at X / 4 speed might be causing this' no. TCP has a windowed flow-control mechanism built in. Your main problem is that you do not understand TCP. Setting timeouts will just add more complexity on top of a buggy implementation, likely making things worse:( – Martin James Sep 09 '22 at 16:16
  • That's true, I have no knowledge about how TCP works under the hood, besides the basics of HTTP/S (headers, content, response) and the layers (hardware, transport, application, etc). I think I'm going to study it through the RFC's. – coolguy123 Sep 09 '22 at 16:40