recv() until a NUL byte is received?

11.3k views Asked by At

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);
    }
}
3

There are 3 answers

3
Mark Tolonen On BEST ANSWER

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.

0
Chris Becke On

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.

3
rm5248 On

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.