4

I'm trying to receive a single packet at a time from the server, since packets are going too fast, and each is of undefined size, calling recv() with number of bytes to read will read the first packet and maybe a part of the second packet. Since each packet is NULL terminated, I thought reading byte by byte until a NULL byte is received.

    int recvLen = 0;
    char TB;
    char recvBuffer[1024];
    while (recv(Socket, &TB, 1, 0) > 0 && TB != 0 && recvLen < 1024)
    {
        recvBuffer[recvLen] = TB;
        recvLen++;
    }

I don't think this method is efficient at all. If the server sent 1024 bytes, recv() will be called 1024 times.

Is there any other method to recv() until a NULL char is received, or some better method than this one I'm using?


EDIT: i added the packet size infront of the data sent from the server, but now, if a false packet or even sometimes for no reason, packets gets messed up and no correct data is received. here is my code

#define UPLOAD_LEN 2755
int PacketSize, recvLen;
char Size[4];
char recvBuffer[UPLOAD_LEN+1];
while(1)
{
    if(recv(Socket,Size,4,0)>0)
    {
        Size[4] = '\0';
        PacketSize = atoi(Size);
        if (PacketSize > UPLOAD_LEN || PacketSize <= 0) continue;
        recvLen = recv(Socket, recvBuffer, PacketSize, 0);
    } else recvLen = -1;
    if (recvLen > 0)
    {
        recvBuffer[recvLen] = '\0';
        ProcessData(recvBuffer);
    }
    else
    {
        closesocket(Socket);
    }
}
killercode
  • 1,666
  • 5
  • 29
  • 42
  • Is it possible for you to change the packet format? I.e., by including a header that specifies the length of the packet? – Karmastan Feb 19 '11 at 15:55

3 Answers3

5

I have never understood why communications protocols never support the one use case programmers expect to be able to do: exchange arbitrarily sized blobs with sends and recv's aligned on boundaries.

So theres no real shortcut here. You need to keep a persistent buffer that holds any data left over from the previous call to recv. Keep adding data to the end as you receive it, and return up to the terminating zero each time you find one. You'll probably have at least a partial following packet, so move that to the start of the buffer to serve as your initial state on the next call.

Chris Becke
  • 34,244
  • 12
  • 79
  • 148
2

Create a buffer and extract your protocol messages from that. If the buffer does not contain a complete message, then recv() until it does. Here's a simple C implementation to buffer a socket (lightly tested, compiles on MS VS2008):

#include <winsock2.h>
#include <string.h>

typedef struct buffsock {
    SOCKET s;
    char* buf;
    size_t maxlen;
    size_t curlen;
} buffsock_t;

void buffsock_init(buffsock_t* bs,SOCKET s,size_t maxlen)
{
    bs->s = s;
    bs->buf = malloc(maxlen);
    bs->maxlen = maxlen;
    bs->curlen = 0;
}

void buffsock_free(buffsock_t* bs)
{
    free(bs->buf);
    bs->buf = NULL;
    bs->maxlen = 0;
    bs->curlen = 0;
    bs->s = INVALID_SOCKET;
}

/* Attempt to fill internal buffer.
 * Returns 0 if socket closed.
 * Returns number of additional bytes in buffer otherwise.
 */
int buffsock_fill(buffsock_t* bs)
{
    int bytes;
    bytes = recv(bs->s,bs->buf + bs->curlen,bs->maxlen - bs->curlen,0);
    if(bytes == SOCKET_ERROR)
        return -1;
    bs->curlen += bytes;
    return bytes;
}

/* Return up to <bytes> from buffered socket.
 * If return value 0 socket was closed.
 * If return value >0 and <bytes socket received partial message.
 */
int buffsock_bytes(buffsock_t* bs,size_t bytes,void* msg)
{
    while(bs->curlen < bytes)
    {
        int result;
        result = buffsock_fill(bs);
        if(result == -1)
            return -1; /* error on socket */
        if(result == 0)
            break;
    }
    if(bytes > bs->curlen)
        bytes = bs->curlen;
    memcpy(msg,bs->buf,bytes);
    bs->curlen -= bytes;
    memmove(bs->buf,bs->buf + bytes,bs->curlen);
    return bytes;
}

/* Implmementation of a protocol with two big-endian bytes indicating
 * msg size followed by <size> bytes of message.
 * Returns -1 if error on socket.
 * Returns -2 if partial message recv'd (shouldn't happen as long as
 * internal buffer is bigger than max message size).
 * Returns -3 if user buffer not big enough to hold message.
 * Returns size of message otherwise.
 */
int get_protocol_message(buffsock_t* bs,void* msg,size_t maxlen)
{
    int bytes;
    u_short len;
    bytes = buffsock_bytes(bs,sizeof(u_short),&len);
    if(bytes == 0)
        return 0;  /* socket closed, no more messages */
    if(bytes == -1)
        return -1; /* error on socket */
    if(bytes < sizeof(u_short))
        return -2; /* partial message */
    len = ntohs(len);
    if(len > maxlen)
        return -3; /* message exceeds user buffer */
    bytes = buffsock_bytes(bs,len,msg);
    if(bytes < len)
        return -2; /* partial message */
    return bytes;
}

Use it like this:

int len;
char msg[256];
buffsock_t bs;
/* open a socket */
buffsock_init(&bs,sock,1024);
len = get_protocol_message(&bs,msg,sizeof(msg));

The key is TCP/IP has no concept of message boundaries, so recv() can return 1 to number of bytes requested. The received buffer could contain multiple or even partial messages.

This code just appends received data into a buffer. The protocol requests bytes from the buffer, and the buffer is filled from the socket. as bytes are removed the remaining buffered data is shifted to the beginning of the buffer.

In this case, two bytes are requested, converted to a length, then the remaining bytes are requested. If a request can't be satisfied, more data is recv'd.

Hope this helps.

Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
  • i didnt understand a single line of that code !!!, no idea what language is that, but i updated my first post with another way, is it the same? – killercode Feb 20 '11 at 08:13
  • thx, it works better now, ill tweak it a bit, its pretty smart method tho :D – killercode Feb 20 '11 at 18:39
  • I use sockets for sending and receiving JSON data. I call recv() one time. Then I COUNT the "{"s and "}"s. When there is at least one curly bracket and the number of both curly brackets is equal ("{"s > 0) and ("{"s == "}"s) I know I have all data and can stop. – Wolfgang Adamec Jun 11 '13 at 11:12
0

There are several ways that you could do this.

Option #1: Before sending out any information, send out an int at the front of your packet which contains the size of the packet. Read this int, and then allocate a buffer which is the length of the int that you just received. Then you can recv() the entire packet at one time.

Option #2: Read in 1024 bytes at a time. recv() will give you back the number of bytes read. You can then use strlen() to figure out if you have more than one packet in your buffer. It would probably make the most sense to make this recursive(assuming that you could have several packets in 1024 bytes); so that you split the packets based on NULL bytes.

rm5248
  • 2,590
  • 3
  • 17
  • 16
  • 1
    You do NOT want to use strlen here - what if there isn't any NUL at all? – bdonlan Feb 19 '11 at 16:13
  • The question is based specifically on detecting NULL bytes. That's what strlen does. However, it true that if you have only part of the packet, you could potentially run off the end of your array. You can get around that by making your own strlen(), which takes in the maximum length of the array, and will stop before you run off the end. – rm5248 Feb 19 '11 at 16:47
  • isn't it easier and more efficient to just allocate 1025 bytes instead and set the 1024th byte to zero? This way, if strlen() returns 1024 you know you haven't received the null terminator yet. – Sergei Tachenov Feb 19 '11 at 17:14