Skip to main content
GameDev.net gamedev.net
🔒 Locked

winsock tcp lag *UPDATE* Corrupted messages

Started by MichaeldeJong Dec 24, 2011 at 8:06 PM 18 replies 3.1k views
Original Post
MichaeldeJong
MichaeldeJong
Hello,

I'm programming an online RPG using c++ and winsock (TCP-only / client-server based).
I tested it on my LAN network and it worked fine on my own computer. It sends like 1 position update message every 60 ms.
But when I'm testing this on another PC, there is a huge lag. It is only receiving a message every 500 ms or so.
So it's really slow when youre testing on another computer that is not running the server. (it's still LAN)
Is it actually a good idea to send position updates (17 times a second) using TCP?
Thanks a lot.
TheUnbeliever
TheUnbeliever
I'm going to guess these messages are pretty small? TCP will merge messages for a number of reasons, including to ensure that packets are reasonably large, minimizing overhead. Locally, there's no reason for the driver to do this, so you get the updates essentially immediately (1/17th second is ~58 ms) but as soon as you're actually broadcasting over a network, you get delays while the transmission buffer fills up.
[TheUnbeliever]
DanDannyPantry
DanDannyPantry
First - check you haven't got Nagle's enabled.
Secondly.
Is it actually a good idea to send position updates (17 times a second) using TCP?[/quote]
UDP would be MUCH better suited for this.
MichaeldeJong
MichaeldeJong
I turned off nagle's algorithm but still the same problem.works perfectly on local host ( server-side). Only receives a few of those 17 messages per second on another pc on my LAN.
MichaeldeJong
MichaeldeJong

I'm going to guess these messages are pretty small? TCP will merge messages for a number of reasons, including to ensure that packets are reasonably large, minimizing overhead. Locally, there's no reason for the driver to do this, so you get the updates essentially immediately (1/17th second is ~58 ms) but as soon as you're actually broadcasting over a network, you get delays while the transmission buffer fills up.


I think you are right, because it's like receiving 3 messages at once and then having a delay, etc.
So how can I solve this problem?
ApochPiQ
ApochPiQ
What's likely going on is that you have confused TCP for a packet-oriented protocol. TCP is a stream protocol. You put stuff in, you take stuff out - there is no guarantee that what you put in will be the same size as what you get out. All TCP guarantees is that (A) everything will arrive and (B) it will arrive in the same order it was sent.

See the Multiplayer and Network Programming forum FAQ for a lot of solutions to this.
MichaeldeJong
MichaeldeJong

What's likely going on is that you have confused TCP for a packet-oriented protocol. TCP is a stream protocol. You put stuff in, you take stuff out - there is no guarantee that what you put in will be the same size as what you get out. All TCP guarantees is that (A) everything will arrive and (B) it will arrive in the same order it was sent.

See the Multiplayer and Network Programming forum FAQ for a lot of solutions to this.


Thanks, I think the problem is still TCP_NODELAY. Let's see how I can turn it off the right way.
I'm not sure which socket tho.
Madhed
Madhed
I'll go with Neglected and ApochPIQ, have a look at the forum FAQ especially UDP. It's not as hard as it sounds at first and will give you much better results.
MichaeldeJong
MichaeldeJong
Sometimes I get these kind of corrupted messages, though:
Howker_corruptedmessage.PNG
Madhed
Madhed
How do you separate the Messages? Can you post the structure of your Message?
Bacterius
Bacterius
Could you possibly be using threads in your client? Console write access is not thread-safe by default so if you are writing to the console simultaneously from multiple threads the result is potentially undefined. Other than that TCP guarantees data integrity so your server must be screwing up and sending corrupted messages at some point (or your client is interpreting them incorrectly). We need to know more for further analysis.
“If I understand the standard right it is legal and safe to do this but the resulting value could be anything.”
MichaeldeJong
MichaeldeJong
Thank you so much for your time! I really appreciate it.

I'm not using threads on the client side.

This is what my position update message looks like:

P,ID,X,Y;
P = Prefix for position update message
ID = Player ID
X = X Position
Y = Y Position
' , ' (comma) = terminator
' ; ' (semicolon) = message terminator

So something like this is send to the server:
Let's assume that there are 3 clients that need to be updated.
The server will send something like this:
P,0,5,2;P,1,7,3;P,2,5,2;

The client will receive this message and parse it.

This is what I'm using for receiving/parsing: (There's nothing special about sending the messages, you just need to put a semicolon after every command)


void World::CheckNetwork(Local *local)
{
//#######RECEIVE MESSAGES#######
std::string message;
int receivedMessages = 0;
int maxMessages = 7; //receive a maximum of 7 messages per frame
while(true)
{
if(receivedMessages >= maxMessages)
break;

message = m_Network->Receive();
if(message.length() > 0) //if succesfully received message (0 bytes = no message)
{

if(m_Network->MessageParse(message, 0) == "P" && m_Network->MessageParse(message, 1) == "-1") //this client's own position correction from the server
local->ServerCorrection(message);
else if(m_Network->MessageParse(message, 0) == "P") //regular client position update
UpdateClients(message);
if(m_Network->MessageParse(message, 0) == "R") //remove client
RemoveClients(message);
receivedMessages++;
}
else
break;
}
}




string Network::Receive()
{
if(m_ReceivedMessage.length() > 2048)
m_ReceivedMessage.clear();

string message = "";
char buffer[1024];
memset(buffer, 0, 1024);
int bytesReceived = 0;
bytesReceived = recv(m_sConnect, buffer, 1024, 0);
if(bytesReceived > 0 && strlen(buffer) > 0)
m_ReceivedMessage += buffer;

int term = m_ReceivedMessage.find(';');
if(term >= 0)
{
message = m_ReceivedMessage.substr(0, term);
m_ReceivedMessage.erase(0, term+1);
}
if(message.length() > 0)cout << message << endl;
return message;
}




TCP is a stream based networking protocol. This means that the client sometimes receive only some bits of a message:
sent mesage: P,0,5,2;

Client receives: P,0
Client receives a frame later: ,5,2;

So I just wrote something that saves these bits of a message in a buffer which is max 2048 bytes long.
And on every frame the client checks if a finished message is ready. If it can find one it will remove this message
from the buffer and parse & read it.



std::string Network::MessageParse(std::string m, unsigned int p)
{
vector<int> pos;

for(unsigned int i = 0; i < m.length(); i++)
{
if(m == ',')
pos.push_back(i);
}

if(pos.size() > 0)
{
if(p == 0)
return m.substr(0, pos

);
else if(p == pos.size())
return m.substr(pos[p-1]+1, m.find('\0')-1);
else if(p > 0 && p < pos.size())
return m.substr(pos[p-1]+1, pos

-2);
}

return "";
}




Thanks a lot for your time!
Madhed
Madhed
There's a potential buffer overflow in Network::Recevie().
Hint: what happens when you send a string that is longer than 1023 characters?

Don't know if that explains the error though

Edit: another question: what happens when the first received byte is a '\0'?
MichaeldeJong
MichaeldeJong

There's a potential buffer overflow in Network::Recevie().
Hint: what happens when you send a string that is longer than 1023 characters?

Don't know if that explains the error though


It means it can only receive 1023 characters at a time.
If you send something bigger than 1023, you'd need to wait
atleast 2 frames to receive it.


Edit: another question: what happens when the first received byte is a '\0'?


I tested it, and nothing unusual happened...
Madhed
Madhed
Ok let's see:


char buffer[1024];
memset(buffer, 0, 1024);
int bytesReceived = 0;
bytesReceived = recv(m_sConnect, buffer, 1024, 0);
if(bytesReceived > 0 && strlen(buffer) > 0)
m_ReceivedMessage += buffer;


Send a 1024 char string: 1024 characters + a NULL to terminate the string.
the whole buffer is filled with charcters but no NULL terminator -> strlen will search for it, going way beyond the buffer until it hits a NULL -> program might crash when strlen hits protected memory.

Send a string with NULL as first character eg: "\0P1,2,3".
strlen will return 0 -> the whole received data will be thrown away.
MichaeldeJong
MichaeldeJong
I'll change the system and only send input state of the players to the client, so the client will simulate the physics, and the server will send the real positions once in a while.
This means less messages are sent.
MichaeldeJong
MichaeldeJong

Ok let's see:


char buffer[1024];
memset(buffer, 0, 1024);
int bytesReceived = 0;
bytesReceived = recv(m_sConnect, buffer, 1024, 0);
if(bytesReceived > 0 && strlen(buffer) > 0)
m_ReceivedMessage += buffer;


Send a 1024 char string: 1024 characters + a NULL to terminate the string.
the whole buffer is filled with charcters but no NULL terminator -> strlen will search for it, going way beyond the buffer until it hits a NULL -> program might crash when strlen hits protected memory.

Send a string with NULL as first character eg: "\0P1,2,3".
strlen will return 0 -> the whole received data will be thrown away.


Thanks, I'll look at it again later (it's kinda late now) and fix it.

Edit: What if I fixed these two things, would it fix the corrupted messages problem that occur sometimes?
Madhed
Madhed
Might fix it. There could be other bugs of course.
IMHO the whole method of extracting packets from TCP streams is a lost cause. Might as well just implement a simple reliability layer on top of UDP in the mean time.
MichaeldeJong
MichaeldeJong

Might fix it. There could be other bugs of course.
IMHO the whole method of extracting packets from TCP streams is a lost cause. Might as well just implement a simple reliability layer on top of UDP in the mean time.


Thanks a lot! It fixed the problem!

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.