Search code examples
c++socketswinapibufferrecv

How can I handle an incoming packet connection by parsing the buffer and placing the data into the correct struct?


I have an open connection that works fine, but now I'm adding a second reply type that I have to handle. The replies go to two different structs.

Reply type one is a struct of size 16 = replyTypeOne (int, int, int, int) Reply type two is a struct of size 64 = replyTypeTwo (int, int, int, char[48], int)

Before I was sending the reply straight to the correct struct - now I have to add a buffer to determine which struct the reply should go to.

old handle of code below:

iResult = recv(connectSocket, (char *)&replyTypeOne, sizeof(replyTypeOne), 0);

The above code would place the reply fields into their appropiate places. However, with the replyTypeTwo being different than replyTypeOne - I need a solution to determine the two replies from each other.

So I plan on using a buffer (maybe I should use MSG_PEEK??), to look at the incoming data and place it into the correct struct. I'm just not sure how to handle / parse the buffer reply atm.

EDIT: added replyTemp struct - int, int, int, int, char[48], int

#define DEFAULT_BUFLEN 128

int iResult;
char recvbuf[DEFAULT_BUFLEN];
int recvbuflen = DEFAULT_BUFLEN;

iResult = recv(connectSocket, (char *)&replyTemp, sizeof(replyTemp), 0)

if(iResult > 0)
{
 if(replyTemp.length > 16)
  {
   //handle replyTypeTwo -  assign replyTypeTwo fields to replyTemp fields
  } else {
   // handle replyTypeOne - asking replyTypeOne fields to replyTemp fields
  }
}
else if(iResult == 0)
{
  connectSocket = INVALID_SOCKET;
}

Solution

  • The simplest way to “peek” is to just recv and then recv some more.

    Since this appears to be TCP, you already need a recv loop. A recv loop is always necessary with TCP. When you ask to recv(sock, &buf, 16, 0), there's no guarantee you'll get 16 bytes. If the message got split across two packets at the sender, you could get only, say, the 10 bytes in the first packet, and not get the 6 bytes from the next packet until you do a second recv. So, you need to keep receiving until you get all 16 bytes. (I've written a longer explanation in Python terms, but the same idea applies to C++.)

    Once you're doing that, all we have to do after getting 16 bytes is check the length field (which you implied is the first 4 bytes in both structs). If it's 16, we're done; if not, we can just keep looping until we have 64 bytes.

    Ideally we want to read into a single buffer of 64 bytes, instead of reading a 16-byte buffer and a 48-byte buffer and then memmoving stuff around to concatenate them. Given that, we can just use a union to allow us to recv into a 64-byte char array, but then access the first 16 bytes as a reply_type_1 or the whole thing as a reply_type_2.

    So, here’s a quick and dirty example (without handling errors, EOF, etc.):

    typeset union {
        reply_type_1 r1;
        reply_type_2 r2;
        char buf[64];
    } msg_type;
    
    int len = 0;
    msg_type msg;
    while (len < sizeof(reply_type_1)) {
        n = recv(sock, &msg.buf[len], sizeof(reply_type_1)-len, 0);
        len += n;
    }
    if msg.r1.length > sizeof(reply_type_1) {
        while (len < sizeof(reply_type_2)) {
            n = recv(sock, &msg.buf[len], sizeof(reply_type_2)-len, 0);
            len += n;
        }
        do_stuff_r2(msg.r2);
    } else {
        do_stuff_r1(msg.r1);
    }
    

    Storing into msg.buf and then accessing msg.r1 is effectively the same thing as just having a char buf[64] and then doing do_stuff_r1(*reinterpret_cast<reply_type_1 *>(&buf)). So, if do_stuff_r1 takes a reply_type_1, it gets a copy of the first 16 bytes as a reply_type_1 struct, and if it takes a reply_type_1 &, it gets a reference to the first 16 bytes as a reply_type_1 struct.