How to send messages with larger length than the buffer in socket programming?

14,761

Solution 1

You have an example in Beej's Guide to Network Programming:

7.3. Handling Partial send()s

Remember back in the section about send(), above, when I said that send() might not send all the bytes you asked it to? That is, you want it to send 512 bytes, but it returns 412. What happened to the remaining 100 bytes?

Well, they're still in your little buffer waiting to be sent out. Due to circumstances beyond your control, the kernel decided not to send all the data out in one chunk, and now, my friend, it's up to you to get the data out there.

You could write a function like this to do it, too:

int sendall(int s, char *buf, int *len)
{
    int total = 0;        // how many bytes we've sent
    int bytesleft = *len; // how many we have left to send
    int n;

    while(total < *len) {
        n = send(s, buf+total, bytesleft, 0);
        if (n == -1) { break; }
        total += n;
        bytesleft -= n;
    }

    *len = total; // return number actually sent here

    return n==-1?-1:0; // return -1 onm failure, 0 on success
} 

EDIT: As pointed out by @alk, replace all those ints with ssize_t - The type returned by send()

Solution 2

If you're sending or receiving UDP packets, then there is no way around it; you'll need a buffer big enough to hold the entire packet. Fortunately, in most cases you don't want to send or receive UDP packets bigger than around 1500 bytes anyway, (since that is the largest packet an Ethernet card can send without fragmenting it into smaller packets, and generally you want to avoid fragmentation if possible).

For TCP, keep in mind that you are sending/receiving a stream of bytes, rather than a series of individual packets, and that so the sizes you pass to send() and recv() will not generally correspond to the sizes of the network packets anyway. That means that calling send() 6 times, each with 100 bytes of data, isn't much different from calling send() 1 time with 600 bytes of data, and so on. As others have noted, you do have to carefully check the return value of each send() and recv() call to see how many bytes were actually sent or received, since the number sent/received can (and will) sometimes be smaller than the number of bytes you requested. You need to know how many bytes were actually sent in order to know which bytes are appropriate to send on the next call, and you need to know how many bytes were actually received in order to know how many bytes are now valid in your receive-buffer.

Solution 3

You need buffering on both ends.

When you send something, you are not assured that all your buffer would be sent. The send(2) syscall may return a partial byte count.

Likewise, when your recv something, the byte stream may be partial. The recv(2) syscall may return a partial byte count.

You probably need some event loop (e.g. above poll(2)) to mix both sending and receiving.

TCP/IP does not guarantee that a given send (in the emitter machine) correspond to one recv (in the receiving machine): the byte stream might have been chunked into arbitrary pieces in between (e.g. by routers). See also this.

Solution 4

Send the message piece by piece. You can only send pieces as large as the send buffer size, so you need to remember how much you have sent and how much remains; then you can use a loop to do what is required:

int bufferSize = 200;
int messageLength = 442; // or whatever
int sendPosition = 0;

while (messageLength) {
    int chunkSize = messageLength > bufferSize ? bufferSize : messageLength;
    memcpy(buffer, message + sendPosition, chunkSize);
    chunkSize = send(client, buffer, chunkSize, NULL);
    messageLength -= chunkSize;
    sendPosition += chunkSize;
}

Of course you could simply send from message instead of making a needless copy to buffer first, but I am just illustrating the concept here.

Your receiving code would then possibly need to assemble the message in its entirety before being able to process it, but that really depends on the protocol you have designed.

Share:
14,761

Related videos on Youtube

user3530012
Author by

user3530012

Updated on June 28, 2022

Comments

  • user3530012
    user3530012 almost 2 years

    I'm developing an application using Winsock in C++. I have a 200-byte-length char array for sending strings over the socket.

    My problem is when sending messages which are larger than the char array, so I decided to send them in multiple chunks but I have no idea how to do it.

    For sending and receiving the data, I'm using the regular send() and recv() functions:

    recv(client, buffer, 200, NULL);
    send(client, buffer, 200, NULL);
    

    Update

    I have a struct:

    struct Pack
    {
        unsigned int senderId;
        char str[200];
    }
    

    before sending I convert the struct to char array.

    Pack pk;
    strcpy_s(pk.str, 200, "Some text to send.\0");
    pk.senderId = 1 // user id
    char *buffer = (char*)pk;
    

    If the string size if larger than 200 the strcpy_s() crashes.

    • Jonathan Leffler
      Jonathan Leffler over 9 years
      Over and above the chunking mechanisms described in the answers below, the receiving end needs to know how long the message is so that it knows when it has got all the data that will be sent. This is where you need your protocol design — how does the receiver know when the data has all arrived. One common technique is to use TLV (type, length, value) encoding, where the message contains a type indicator (optional if everything is a string), followed by the length of the data, and then that many bytes of data.
    • Jonathan Leffler
      Jonathan Leffler over 9 years
      Note that strcpy_s() is designed to fail if the source string is longer than the destination string. When you're transmitting data, you are probably dealing with byte arrays rather than strings. You probably won't be sending the terminating null over the wire, but the receiver will need to assemble the data into a null-terminated string.
  • user3530012
    user3530012 over 9 years
    How can I display it as a whole message on the server? before receiving it on the other side, I have know idea how much the size would be.
  • Waihon Yew
    Waihon Yew over 9 years
    @user3530012: That means you need to transfer the message size before the contents, so you need your messages to have a header. The receiving code needs to be able to determine the size of this header without additional information (otherwise chicken-and-egg problem); the simplest way to do that is make the header fixed size.
  • Jonathan Leffler
    Jonathan Leffler over 9 years
    @user3530012: you need to design the protocol so you do know how long the data is supposed to be at the receiving end. You can send the length ahead of time, or you could use an encoding such a JSON for the message, where you know you've got the whole message when you get the correct ending character (usually the matching } for the opening {).
  • Jeremy Friesner
    Jeremy Friesner over 9 years
    The posted example code isn't checking the value returned by send() -- without doing that, it's not going to work reliably. The typical symptom will be "lost bytes" whenever the program is under non-trivial load (the bytes being lost when send() returns a value less than chunkSize, but the code-loop doesn't notice that and assumes that chunkSize bytes were sent).
  • Waihon Yew
    Waihon Yew over 9 years
    @JeremyFriesner: True and thanks for the suggestion. Although it's not really my intention to give production code here, I suppose this is important enough that it should be included in the PoC. I 'll edit accordingly.
  • user3530012
    user3530012 over 9 years
    How about I have the char array in a struct and convert the struct to char array and send it over the socket?
  • Waihon Yew
    Waihon Yew over 9 years
    @user3530012: How do you convert a struct to a char array? And what would you hope to accomplish that way? Anyway, the answer is simple: with TCP you need to have a header and you need to always buffer when receiving as others have said. Otherwise your app will break randomly because you are depending on things that TCP does not guarantee.
  • Waihon Yew
    Waihon Yew over 9 years
    @user3530012: There are multiple things wrong with that code. You can't do it like that.
  • user3530012
    user3530012 over 9 years
    @Jon would you please correct my code in an answer?
  • alk
    alk about 9 years
    All ints but n should be size_t. n should be ssize_t.
  • cpx
    cpx almost 7 years
    So, you are saying that if I send "AA BBBB CCC DDDDDD E" it could arrive at receiving end as "A ABB B BCC CDDD DDDE", right? Suppose if I prefix the length with message and I send "10ABCDEFGHIJ" then the length bytes itself could be fragmented? Shall I write a script to put together the message in this case?
  • Basile Starynkevitch
    Basile Starynkevitch almost 7 years
    Yes, see stackoverflow.com/a/20582916/841108 which I just mentioned in my answer