Partial send() and recv()

Started by
7 comments, last by x33 21 years, 1 month ago
I think, I've got a solution for partial send() and recv() problem when using a TCP socket. It would be great to read for your comments. There's a MSG_PEEK flag for the recv() function which tells the recv() only to read data from the incoming queue, but not delete it. So when recv() is called with the MSG_PEEK flag it reads the data and when it is called next time with the same flag there's still the same data in the incoming queue. When called with the MSG_PEEK flag recv() returns the number of bytes currently pending to receive. So, for example if we were waiting for 8 bytes, we should do something like this: int remaining = recv (sd, buf, 8, MSG_PEEK); if (remaining > 0) // is there more data than we're waiting for this time? { int temp = recv (sd, buf, 8, 0); // recv data and delete it from the incoming queue if (temp < 0) { // process the errors } } I'm not sure whether the new incoming data is added to the existing bytes in the queue, but that should work, I think (I haven't tested it yet). If 'remaining' is -1, then it is indicating that there was an error, I think. One question: what if the 'remaining' variable is equal to zero after the recv() is called? Does it mean that there's no data at all in the incoming queue or does it mean that there's no more than 8 bytes in the queue? As for the send()... there's still a problem. "send() should return -1 (error) with the errno set to EMSGSIZE when the socket requires that message be sent atomically, and the size of the message to be sent made this impossible" ("Linux Socket Programming" by Sean Walton, Appendix A. + Linux send() manpage). So, there's got to be a way to tell the socket to send messages atomically. How to do that? Got any ideas? =) [edited by - x33 on March 4, 2003 10:23:08 PM]
"I'll be Bach!" (c) Johann Sebastian Schwarzenegger
Advertisement
The problem is this does not allow you to have variable sized messages. It also is exceedingly slow (under windows at least) as the message is copied to the buffer you supplied AS WELL AS remaining in the TCP buffer. This means while waiting for n bytes of data you could possibly copy an extra (n-1) * (m-1) bytes, where m is the number of times you loop through recv().

Managing partial recvs is not all that difficult. Just read into a buffer and advance the position until you eventually receive what you need.

[edited by - jermz on March 5, 2003 3:58:52 PM]
And what about the partial send()? Is there a way to manage it?
"I'll be Bach!" (c) Johann Sebastian Schwarzenegger
Use a header and buffer data as you should be doing with any protcol. "problem" solved.

Ben


IcarusIndie.com
quote:Original post by KalvinB
Use a header
What do you mean? Making my own header on top of the TCP/IP headers? But what if the header itself could not be sent (or sent partially)?

Is there a way to imitate real network when testing locally? Any tools or something?

[edited by - x33 on March 5, 2003 7:38:48 PM]
"I'll be Bach!" (c) Johann Sebastian Schwarzenegger
The TCP header doesn''t tell your client anything. It''s purely for Winsock''s use.

A header is a fixed number of bytes that contains various information about your message at fixed locations. If your header is 16 bytes and you only get 15, you know you don''t have a complete message. When you have at least 16 bytes buffered you know you have at least a header. Then you just look at the header which should tell you how many bytes the message body is among other things. If the amount of bytes you have buffered is 16 + the number of bytes in the message body then you have the entire message. If it''s less you keep buffering. If it''s more you move out the complete message for the game engine to have access to, move the remainder to the front of the buffer and repeat the process.

The only time you do the checks for the complete message and all that jazz is when the game calls the GetMessage() method of your winsock class which should return a single message if and only if at least one complete message is available. Otherwise you use a header variable as a flag to indicate no message available.

I set message.number to -1 to indicate there''s no message waiting to be acted on by the game engine. You then just keep calling GetMessage() and processing the messages until you get the flag to tell you there aren''t any more pending.

You can download my Winsock class at IcarusIndie.com in the DevZone under "My Released Source" to see how I do it. It''s not an entirely bug free version but it works well enough to show you how it''s done. I used it for months without any problems before running into any of the bugs.

Ben


IcarusIndie.com
I know what a header is. But I don't want to implement a complex messaging system in my game (it is quite simple). I want to solve the problem with standart winsock/BSDsocket tools. My messages are all of the fixed size (4 bytes long (+ TCP header + IP header, but I don't have to worry about those)). And I know that a socket won't split a 4-byte message into several TCP packets frequently, but there's still a chance that not all of the data is sent in one shot. For example I could use a 'int bytes = send (sd, buf, 4, 0);' and the 'bytes' variable can be equal to '2' after the send() is called. And I'd have to retransmit the rest of the buffer with the second call to send(). But I need to make sure that the whole buffer is sent with one packet and avoid implementing a header for my messages. So how do I make it?
P.S. Why didn't they make a support for SO_SNDLOWAT in winsock/BSDsockets? =)

[edited by - x33 on March 5, 2003 11:14:06 PM]
"I'll be Bach!" (c) Johann Sebastian Schwarzenegger

  int Send ( int sockfd, void* msg, size_t len ) {  size_t n = 0;  char* ptr = (char*)msg;  while ( n < len ) {    int sent = send ( sockfd, ptr+n, len-n, 0 );    if ( sent < 0 ) {      /* Some error occured during send */      throw "send error";    }    n += sent;  }  return n;}int Recv ( int sockfd, void* buf, size_t len ) {  size_t n = 0;  char* ptr = (char*)buf;  while ( n < len ) {    int recvd = recv ( sockfd, ptr+n, len-n, 0 );    if ( recvd < 0 ) {      /* Error */      throw "recv error";    }    n += recvd;  }  return n;}  


-Neophyte
Thanks, Neophyte. I understand what KalvinB meant. But I wanted to know whether it is possible to send/recv the whole buffer in one send()/recv() call or not. And if it is, how is that performed then?


"I'll be Bach!" (c) Johann Sebastian Schwarzenegger

[edited by - x33 on March 6, 2003 1:31:42 AM]
"I'll be Bach!" (c) Johann Sebastian Schwarzenegger

This topic is closed to new replies.

Advertisement