tags:

views:

49

answers:

3

Hello, which is the beast approach to send packets that can be of different size using TCP sockets in C?

I wonder because we're trying to write a multiplayer games that needs a protocol which has many kinds of packets of different sizes.. according to recv documentation I can get how many bytes have been read but how should I manage to dispatch packets only when they are exaclty full?

Suppose that I have packets with a 5 bytes header that contains also the length of the payload.. should I use circular buffers or something like that to dispatch packets when ready and keep new partials?

+1  A: 

you can design a custom header for your packet transmission, which specifies packet length, indexing info (if packet fragmentation is implemented) and some hashing if you need.

some rough pseudocode as follows :

recv(socket, headerBuf, headerSize, MSG_WAITALL);
nPacketSize = headerBuf[16];    //sample
nByteRead = 0;

while (nByteRead != nPacketSize)
{
    nByteToRead = nPacketSize - nByteRead;
    nCurRead = recv(socket, someBuf, nByteToRead, MSG_PARTIAL);

    nByteRead += nCurRead;
    Sleep(0);   // Yield processor
}
YeenFei
A: 

TCP is a stream based protocol, not a datagram one. That means that there isn't necessarily a one to one correspondence between the number and size of buffers passed to send and what comes out of receive. That means that you need to implement your own "protocol" on top of TCP.

Examples of such protocols include HTTP, where HTTP messages are delineated by two consecutive carriage return, line feed pairs. \r\n\r\n. The only concern with such delineation is to make sure that the pattern can't occur in the body of the message, either that or to make sure it is escaped. Other protocols create a header which contains the information necessary to correctly identify and read the next piece of information. I can't think of an application that does this off the top of my head. You could even go for a hybrid approach that combines the two.

torak
+1  A: 

Create a static variable which represents the packet header, this variable will be five bytes long. Create an associated integer which counts how many of those five bytes have yet been read. Create a second integer which counts how many bytes of the "content" have been read. Zero both those integers. Create an associated char * pointer which eventually will point to the received packet content.

As data arrives (e.g., select indicates so), read the five bytes of header. You may receive these bytes gradually, thus you need the first integer count variable. Account for the header bytes you have received here.

When you are done receiving the header, sanity check it. Are the size values possible to satisfy (e.g. not greater than 2^30)? If so, malloc a buffer of that size or that size plus the header. (If you want the header contiguous, allocate sufficient space, then memcpy it into your new buffer.)

Now, as data arrives, place it in your allocated buffer. Account for the received bytes in the second integer you created. When you have received all the bytes the header called for, then repeat all the above.

Heath Hunnicutt
In other words, do not worry about the complexity of a ring buffer. You are using TCP. Use malloc and memcpy while you are at it.
Heath Hunnicutt
From what I see `select` allows me not to use a single thread for every client but just one that cycles through all opened sockets to check if there is a packet ready to be dispatches. I'm used to have a thread per client and then synchronize over data structures. Does it worth doing it with select? By the way thanks for now, it's clearer
Jack
Hi Jack. You can approach it either way: by using `select` and one thread, or by using a thread per connection, as you have already begun. If you use one thread per connection, then you can simply block on `recv` or `read` rather than use select. BUT: how do you tell your receiving threads that the client is closing down, and they must leave the `recv` call with an error? Usually, you simply `closesocket()` on that socket handle, and the call returns error. If you are using `select`, however, the approach is a global "quit socket" used like a semaphore in the `select` "error set".
Heath Hunnicutt