Recving entire packets

Started by
12 comments, last by rileyriley 22 years, 5 months ago
I'm having a pretty frustrating problem, and I'm pretty sure it is occuring because packets aren't getting completely sent. I'm using winsock with SOCK_STREAM sockets. Well, I'm using winsock wrapped in my own socket class. I'm pretty sure the class is not the problem. A more detailed explanation: I have a "logon" struct, that holds a packet ID, username (char[20]) and password(char[20]).
      	struct //logon packet

	{
		BYTE id;		
		char username[21];	
		char password[21];	
	} logon;  
Now, from the client, I do some initialization and send the packet:
  
logon.id = PID_LOGON;   //denotes that this is a login packet

strcpy(logon.username,"riley riley");
strcpy(logon.password,"howdy");

clientSocket.send((char *)&logon, sizeof(logon), 0);  
Then, in the server, I recv the packet, but not everything comes through.
  
//packet is defined above to have the same structure as logon from the client

memset(&packet, 0, sizeof(packet));  //zero out the packet


int bytesReceived = socket.recv((char *)&packet, sizeof(packet), 0);      
bytesReceived comes out being 16.. but only the id and username get transmitted. No part of password is transmitted. My question is, how do I get the rest of this packet? How do I even make sure it all got sent - MSDN warns that send isn't guaranteed to send all of the data I give it. And if I send another round of data later, how do I attach it to the end of the data already received in the server? Thanks for any help -riley Edited by - rileyriley on October 22, 2001 11:30:14 PM
--Riley
Advertisement
Hi.

I''m probably not qualified to answer blablabla, but anyway..

In short, I think you need to call recv several times in order to get the whole packet.

So the receiving machine needs to know how long a packet is. If the received amount of bytes is less than the packet length call recv again. Thus, either you have a fixed packet length or send the packet length in some kind of header in each packet.

You might also need to call send several times if you see that not the whole packet was sent.

I think this is how its done, haven''t done any serious tcp/ip. So someone correct me if I''m wrong!

Cheers

Ok, thanks, yeah that makes sense. I''d thought about that before, but I was thinking that it would be possible for some packets to arrive out of order - but SOCK_STREAM specifies that all the data arrives in the order it was sent, right?

Thanks,
-riley
--Riley
On thinking about what you said some more, I''ve come apon a little catch - if you''re using variable sized packets and send the size _in_ the packet, how can you be guaranteed that the server will even get the whole size?
--Riley
sockets (and most streamed reads) shoudl be double-buffered at some point. You packet protocol should probably be wrapped in control characters (STX and ETX are the standard) so that you can process thusly:

Define a buffer around 8k. This is your primary recv buffer. As long as read() returns data, append it to this primary buffer. You may need to use select() or wouldblock() to see if more data is available after the read(), but once all data is read, sweep the buffer for your control characters and then memmove() each COMPLETE packet (STX thru ETX) into your packet struct and move on to the next chunk.

Sound good? It may seem like a lot of bloat but TCP/IP is NOT ASSURED DELIVERY, meaning it only ensures the packets will be IN ORDER, not that they will ALWAYS GET THERE. A very important distinciton.

---------
-WarMage
...my sensei is a tcp/ip ninja...
I ended up writing all of the data recved to a buffer, like you suggested. But instead of then sweeping the buffer for control "packet boundary" characters, I do the following:

I have a boolean variable that reflects whether or not my program is in "mid packet." If it is not mid-packet, I read a single byte, the ID of what kind of packet is being sent, and set midpacket to true. Then, from that single byte received, I look up the size of that kind of packet and call it totalPacketSize.

Every time select notifies my program that the socket has incoming data waiting, I call recv again, with totalPacketSize - lengthOfDataAlreadyRead as the requested number of bytes to read. I add however many bytes are received to lengthOfDataAlreadyRead (actually called dataLen), and repeat the process until dataLen equals totalPacketSize, so I know that the whole packet has been received. Then I copy the buffer to the appropriate packet structure (I actually have a bunch unioned into one generic PACKET). Then I set midpacket to false again, and dataLen to 0, and the process can start over for the next packet.

    	PACKET packet;	int bytesReceived;	if (!midPacket)	{		midPacket = true;				//we just want to read a single byte, the id of the packet, and then figure out wth to do with the rest		bytesReceived = socket.recv((char *)buffer, 1, 0);		if (bytesReceived == 1) //this will always be true but whatever		{			totalPacketSize = getPacketSize(buffer[0]); //remember buffer[0] now holds the packet ID			if (totalPacketSize == -1)				cout << "ERROR: Invalid packet type sent!!" << endl << flush;			dataLen = 1;		}	}		bytesReceived = socket.recv((char *)buffer + dataLen, totalPacketSize - dataLen, 0);	dataLen += bytesReceived;	if (bytesReceived == 0)	{		cout << socket.ip << " disconnected :(\n" << flush;		socket.disconnect();		active = false; //deactivate myself		return;	}		if (dataLen >= totalPacketSize)  //if we've gotten the whole packet	{		//copy the buffer to the packet		memcpy(&packet, buffer, dataLen);                midPacket = false;		switch(packet.id)  //decide how to interpret the data:		{		case PID_LOGON:			cout << "User " << packet.logon.username << " has logged on.\n" << flush; 			break; //and etc...    


This seems like it shoudl work fine for me. The only problem would be if packets could come partially, which is something that I still don't know about - can the first part of a packet come and then the start of another before the end of the first packet?

Thanks
-riley

Edited by - rileyriley on October 23, 2001 3:23:53 PM
--Riley
quote:Original post by rileyriley
The only problem would be if packets could come partially, which is something that I still don''t know about - can the first part of a packet come and then the start of another before the end of the first packet?


No, that''s a function of the TCP layer of the implementation. If separate segments of datagrams are received in overlapping fashion, the TCP layer can either queue or discard the offending segment(s) on continue assembling the first datagram. I believe what happens is that the driver dumps the segment and responds to the remost system that the packet was not recieved.

---------
-WarMage
...this is a job for rm -rf!...

Thanks - is that also true of streams?
--Riley
quote:Original post by rileyriley
Thanks - is that also true of streams?

I would say no, because a Winsock stream is a specialized stream, and streams in general cnnot be out of order. Winsock just happens to have to implement TCP/IP, which stipulates that packets may be recievd out of order. Read up on the spec for TCP/IP and it will be much clearer

---------
-WarMage
...it''s a party in your mouth...
Being a streamed protocol, TCP has the following features:

1) Guaranteed delivery. As long as the connection doesn''t break, all bytes sent will be received. No bytes will be duplicated.

2) In-order delivery. All bytes will be received in the order they were sent.

3) There are no packet boundaries.


This has the following implications:

- when you call to recv(), you might only receive the beginning of a structure the server send()s as a whole

- two blocks of data that are send() seperately may be received by a single call to recv()

- you do not have to have any special packet boundary markers (like the STX and ETX previously mentioned) or length indicator if your packets are fixed in size. If your packets vary in size, you need a header giving the length of the packet before the actual data

- you don''t need to introduce sequence numbers for your packets


If you were to use anything like sequencing or packet boundary marker, you''d just duplicate the efforts that are already done in the TCP layer, wasting both processing speed and network bandwidth.

cu,
Prefect

One line of sourcecode says more than a thousand words.
Widelands - laid back, free software strategy

This topic is closed to new replies.

Advertisement