3

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.

David Ranieri
  • 39,972
  • 7
  • 52
  • 94
user3530012
  • 720
  • 2
  • 20
  • 37
  • 3
    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 Dec 23 '14 at 16:06
  • If I were you, I'd use ZeroMQ which transfers packets (called "messages") of arbitrary size, regardless of the underlying transportation mechanism. Much easier to set up and get reliable networking that way. – Ulrich Eckhardt Dec 23 '14 at 17:16
  • 1
    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. – Jonathan Leffler Dec 23 '14 at 17:20

4 Answers4

12

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

David Ranieri
  • 39,972
  • 7
  • 52
  • 94
5

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.

Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234
4

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.

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • 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? – cpx Jun 23 '17 at 14:48
  • Yes, see https://stackoverflow.com/a/20582916/841108 which I just mentioned in my answer – Basile Starynkevitch Jun 23 '17 at 15:21
2

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.

Jon
  • 428,835
  • 81
  • 738
  • 806
  • 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. – user3530012 Dec 23 '14 at 16:05
  • 2
    @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. – Jon Dec 23 '14 at 16:08
  • @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 `{`). – Jonathan Leffler Dec 23 '14 at 16:08
  • 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). – Jeremy Friesner Dec 23 '14 at 16:11
  • @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. – Jon Dec 23 '14 at 16:14
  • How about I have the char array in a struct and convert the struct to char array and send it over the socket? – user3530012 Dec 23 '14 at 16:48
  • @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. – Jon Dec 23 '14 at 16:52
  • @user3530012: There are multiple things wrong with that code. You can't do it like that. – Jon Dec 23 '14 at 17:02
  • @Jon would you please correct my code in an answer? – user3530012 Dec 23 '14 at 17:08