Jump to content
  • Advertisement
Sign in to follow this  
gangsta15

[SDL] How to recieve data from server correctly?

This topic is 2528 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm creating an isometric multiplayer game, and use SDL_Net and TCP sockets type for network. So if one client player moved, server have to send this client player new coordinates to other clients, therefore I need to recieve server data in main loop of client, but it freezes my game. What am I doing wrong? I saw sdl multiplayer game example, and it would like recieving server data in main loop, . Please help. I attached my game sources, and another network game sources.
P.S. my sources - SDL_Iso_City, using Visual Studio 2008

Share this post


Link to post
Share on other sites
Advertisement

I had a quick look into your modules.

And I closed them quick.

It would be a great improvement, specialy if someone should read the code and wants to help you, if the code has some comments of what you would expect from the operations and loops and such things.

Another thing is that it would be very helpfull if you can give more details when it stops. With a debugger you are able to check how far your code runs without a problem at what line of code it hangs and the like.

I expects that the SDL_CreateThread() function does not work. Because you are passing on a pointer to a method of the class into it as run-function. This normaly does not work because of some differences in the way a function and a method is called. So I would expect that your engine never receives anything but I maybe wrong.

Share this post


Link to post
Share on other sites
It sounds like you are using blocking I/O, which causes your program to stop when there is no data in the buffer.

Consider creating a minimal example program. That is, a server that periodically sends commands to a client (e.g. every 5 seconds), and a client that is not allowed to block (e.g. prints a message every second). The server and client should be simple text programs, small enough to post inline here.

Share this post


Link to post
Share on other sites
Sorry( I've loaded wrong sources, where I were testing SDL threads. Briefly:
Server:

  1. accepts client connection
  2. (when client connected) in loop gets input text and sends it to client
  3. when it is out of the loop, close the socket and exit

Client:

  1. connects to the server
  2. (when connected) in loop manages mouse and keyboard input (mouse select cells on isometric map, and move there player, arrows scroll the map), recieve server message to buffer, renders the message text

Client header:

#include "SDL.h"
#include "SDL_image.h"
#include "SDL_ttf.h"
#include "SDL_net.h"
#include "SDL_thread.h"
#include "math.h"
#include <string>
#include "cMap.h"
TTF_Font* tahomaFont = NULL;
SDL_Color textColor = {255, 255, 255};
SDL_Surface* text;
class cEngine
{
public:
SDL_Surface* Images;
SDL_Event Event; //event variable in the main loop
char Quit; //loop variable
cMap* GameMap; //game map
SDL_Surface* Player;
float movespeed;
Uint32 old_time, current_time;
float ftime;
//inet
IPaddress ip; //server address
TCPsocket sd; //socket descriptor
char buffer[256]; //buffer to recieve server data to
cEngine();
void Go();
void Initiliaze(); //Engine init
void Loop(); //Main loop
void CleanUp(); //Cleaning up
void ApplySurface(int offset_x, int offset_y, SDL_Surface* source, SDL_Surface* destination, SDL_Rect* clip);
SDL_Surface* LoadImage(std::string filename);
void DrawPlayer(int index_x, int index_y); //Draws player at selected positions
void ClearScreen(); //Fills the screen with some color
void Scroll(); //Adds offset to render the map (map scrolling)
void GetServerCmd(); //Recieves data from server
private:
SDL_Surface* Screen;
};


Client cpp file:

#include "cEngine.h"
int main(int argc, char* args[])
{
cEngine Engine;

Engine.Go();
return 0;
}
//----------------------------------------------------------------------------------
cEngine::cEngine()
{
Screen = NULL;
Images = new SDL_Surface[3]; //???-?? ????????-??????????? ???????????
Quit = 0; //?????????? ??? ???????? ?????
GameMap = new cMap(); //?????
Player = NULL;
TTF_Init();
tahomaFont = TTF_OpenFont("data\\tahoma.ttf", 11);
movespeed = 30.0f;
}
void cEngine::Initiliaze()
{
SDL_Init(SDL_INIT_EVERYTHING);
SDLNet_Init();
cEngine::Screen = SDL_SetVideoMode(640, 480, 32, SDL_SWSURFACE); //(!) ????? ??????? HWSURFACE | DOUBLEBUF
GameMap->SetScreenSurface(Screen); //Set GameMap screen surf to current
//inet
SDLNet_ResolveHost(&ip, "localhost", 2000);
sd = SDLNet_TCP_Open(&ip);
//Recieve greet message from server ("Hello!")
SDLNet_TCP_Recv(sd, (void*)buffer, sizeof(buffer));
}
void cEngine::Go()
{
Initiliaze();

//Loads images to tile array
GameMap->Tiles[0].LoadTileFromFile("data\\active.png");
GameMap->Tiles[1].LoadTileFromFile("data\\grass.png");
GameMap->Tiles[2].LoadTileFromFile("data\\ground.png");
GameMap->Tiles[3].LoadTileFromFile("data\\player.png");
GameMap->Tiles[4].LoadTileFromFile("data\\selection.png");
//Sets the player surface
Player = GameMap->Tiles[3].tile_surface;

/*
//THIS OPTION DISABLED, AND HAS NO EFFECT EVEN ENABLED
//Sets every map cell tile id to 1 to be rendered with tile ID 1 (grass.png)
for(int i = 0; i < 100; i++)
{
GameMap->Map = 1;
}
*/
//Main loop
Loop();
CleanUp();
}
void cEngine::Loop()
{
while(Quit == 0)
{
//Operations to get frame independent map movement (map scroll)
old_time = current_time;
current_time = SDL_GetTicks();
ftime = (current_time - old_time) / 1000.0f;
//-------------------------------------------------------------
while(SDL_PollEvent(&Event))
{
//X windows button pressed
if(Event.type == SDL_QUIT)
{
Quit = 1;
}
if( Event.type == SDL_MOUSEBUTTONDOWN )
{
if( Event.button.button == SDL_BUTTON_LEFT )
{
int x, y;
//Gets map cell indexes by screen coordinates
GameMap->GetMapIndexesByCoordinates(Event.button.x, Event.button.y, &x, &y);
//If selected cell on map (not out of the map)
if(x > -1 && y > -1 && x < 10 && y < 10)
{
//Sets the index coordinates to current
GameMap->cellSelected = 1;
GameMap->sel_index_x = x;
GameMap->sel_index_y = y;
}
}
}
}
//Recieve message from server and write it to buffer variable
GetServerCmd();
//Draw map, selected cell, player
Scroll();
ClearScreen();
GameMap->Draw();
GameMap->DrawSelection();
DrawPlayer(GameMap->sel_index_x, GameMap->sel_index_y);
//Draw recieved from server buffer message
text = TTF_RenderText_Solid(tahomaFont, buffer, textColor);
ApplySurface(0, 0, text, Screen, 0);
SDL_Flip(Screen);
SDL_Delay(0);
}
}
void cEngine::ApplySurface(int offset_x, int offset_y, SDL_Surface* source, SDL_Surface* destination, SDL_Rect* clip = 0)
{
SDL_Rect rect;
rect.x = offset_x;
rect.y = offset_y;
SDL_BlitSurface(source, clip, destination, &rect);
}
SDL_Surface* cEngine::LoadImage(std::string filename)
{
//????????? ???????????
SDL_Surface* loadedImage = NULL; //????????? ????????? ??? ????????
SDL_Surface* optimizedImage = NULL; //???????? ???????????
loadedImage = IMG_Load(filename.c_str());
if(loadedImage != NULL)
{
optimizedImage = SDL_DisplayFormatAlpha(loadedImage);
SDL_FreeSurface(loadedImage);
}
return optimizedImage;
}
void cEngine::CleanUp()
{
SDL_FreeSurface(Screen);
SDL_Quit();
SDLNet_TCP_Close(sd);
SDLNet_Quit();
}
//Draws player surface at selected cell coordinates
void cEngine::DrawPlayer(int index_x, int index_y)
{
//If cell selected:
if(GameMap->cellSelected == 1)
{
int x, y;
GameMap->GetCoordinatesByMapIndexes(index_x, index_y, &x, &y);
x += 32;
y += 16;
x -= 7;
y -= 42;
ApplySurface(x, y, Player, Screen);
}
}
void cEngine::ClearScreen()
{
SDL_FillRect(Screen, &Screen->clip_rect, SDL_MapRGB(Screen->format, 0, 0, 0));
}
void cEngine::Scroll()
{
Uint8 *keystates = SDL_GetKeyState( NULL );
float move = movespeed * ftime;

if(keystates[SDLK_UP] == 1)
{
GameMap->top_y += move;
}
if(keystates[SDLK_DOWN] == 1)
{
GameMap->top_y -= move;
}
if(keystates[SDLK_LEFT] == 1)
{
GameMap->top_x += move;
}
if(keystates[SDLK_RIGHT] == 1)
{
GameMap->top_x -= move;
}
}
void cEngine::GetServerCmd()
{
//Recieve data from server, this function (GetServerCmd) must be in the loop
SDLNet_TCP_Recv(sd, (void*)buffer, sizeof(buffer));
}


Server cpp file:

#include <stdio.h>
#include <string.h>
#include <conio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
#include "SDL_net.h"
int main(int argc, char* args[])
{
TCPsocket sd, csd;
IPaddress ip;
IPaddress* remoteIP;
int quit, quit2;
char buffer[256];
unsigned char num[4];
SDLNet_Init();
SDLNet_ResolveHost(&ip, NULL, 2000);
sd = SDLNet_TCP_Open(&ip);
quit = 0;
while(quit != 1)
{
if((csd = SDLNet_TCP_Accept(sd)))
{
sprintf(buffer, "Hello!");
SDLNet_TCP_Send(csd, (void*)buffer, sizeof(buffer));
remoteIP = SDLNet_TCP_GetPeerAddress(csd);
Uint32 addr = SDLNet_Read32(&remoteIP->host);
memcpy(num, &addr, sizeof(long));
printf("Client connected. [%i.%i.%i.%i : %d]\n", num[0], num[1], num[2], num[3], SDLNet_Read16(&remoteIP->port));

quit2 = 0;
while(quit2 != 1)
{
char buff[256];
//cin >> buff;
sprintf(buff, "SHIT");
SDLNet_TCP_Send(csd, (void*)buff, sizeof(buff));
SDL_Delay(0);
}
SDLNet_TCP_Close(csd);
}
SDL_Delay(0);
}
SDLNet_TCP_Close(sd);
SDLNet_Quit();
printf("Client disconnected, press any key to quit...");
_getch();
return 0;
}


So there also 2 files cMap.h, cMap.cpp which are describing the map class, but the not so important in this case.

Later I replaced in server code text input (cin >> buff; to sprintf(buff, "SHIT");) and client no longer freezing. Out that the problem was in text input?
One more question. If I want the server to handle client messages, and in the same time get the input, I have to take getting text input into another thread?

But if in the server I use this loop:

while(quit2 != 1)
{
if(SDLNet_TCP_Recv(csd, (void*)buffer, sizeof(buffer)) <= 0)
{
quit = 1;
quit2 = 1;
}
char buff[256];
//cin >> buff;
sprintf(buff, "SHIT");
SDLNet_TCP_Send(csd, (void*)buff, sizeof(buff));
SDL_Delay(0);
}

Trying to recieve data from client, client freezes. I recieve data from client to see if It has disconnected, but practicly client doesn't send any data. What should I do? Client will send data, but - in some cases - not in the loop (for example mouse button down, key down or anything else).

Added: attached new sources.

Share this post


Link to post
Share on other sites
I have solved the problem just taking out server recv function to another thread. Now it works fine. New sources attached.

Share this post


Link to post
Share on other sites
Looking at your code, there are a number of other things you need to fix before you can "recieve data from server correctly". Right now it just appears to work.

First of all, your code has minimal error checking. This will cost you in the long run, because someday each of these function calls are going to fail, and they'll fail silently. Even simply writing a line to a log file, indicating the function being called and the result of SDL_GetError() or SDLNet_GetError(), that would be much better because when your program crashes on someone else's machine, you'll have something you can look at to diagnose the issue.

The next step is to have proper error handling, where your program either gracefully handles minor errors, or perhaps shuts down cleanly (rather than unceremoniously crashing).

Your server doesn't handle multiple clients. Perhaps this is a legitimate design decision, because the game you are writing will only be two player. However, mostly you will want to manage multiple clients at the same time.

Another problem is that your program doesn't seem to handle TCP's stream-like behaviour. There is no guarantee that the data you give to a send() call will all come in a single recv() call. Due to TCP's internal buffering, you might get the first half of the data from one recv(), and the second half from the next. Or you might get the data from two send() calls in a single recv(). Or both! The only guarantee you do get is that the bytes will come out in the same order as you put them in.

Most games don't want to send a continuous stream, they want to send lots of individual messages. There are two common approaches to solving this, delimited messages and length prefixed messages.

For delimited messages, pick a byte (or sequence of bytes) as your delimiter. As you read data from the stream, move it into an application buffer. Any time the application buffer contains the delimiter, you have a full message that your application can process. Process the message, and then clear the message from the buffer (taking care to preserve any extra bytes you've received from the next message). Repeat while the client is sending you data. For example, a simple chat server might use the newline character as a delimiter. A more advanced chat server might choose the NUL character as a delimiter, allowing the user to send multi-line responses as a single message. This strategy is common in text formats, and rare with binary formats.

Length prefixed messages are as they sound, before you write a message, you write a few bytes beforehand, indicating the length of the next message payload. A simple example is to write a single byte of size, and then the message. For obvious reasons, this limits your message payload size to 256 bytes long. Or you might choose 2, 3, 4 or more bytes of length data, allowing messages of length 2^16 and 2^24, 2^32, etc. For many games, you won't need some of these longer limits. The process here is to read the N bytes that make up the length prefix first. You will need to be able to buffer a partial message prefix if you want to avoid blocking. When you have a full length prefix, you can attempt to read that many bytes into an application buffer, and send this buffer off for processing.

You could also do some application side buffering on the sending side - again if you don't want to block the sender. If you don't care about blocking you can keep trying to send until the OS finally accepts all your data into it's buffer. However, if blocking is a concern. and you detect that (another) call to send() would block, put any pending data in an outbound buffer. In particular, the remaining data from a partial send should be buffered, otherwise you can break your delimited data, causing the messages to leak together.

On the server, you might wrap the client TCP socket and associated buffers in a structure, which you would manage in a container.

Share this post


Link to post
Share on other sites
Thank you for your detailed answer! You described all in details, but I have some questions.

At first - about error checking: yeah, I know that there must be a lot of error checking and logging functions, but I had a little time for coding, and I therefore shut my eyes to that. So, now I understand it is very important part of the program, and will fix it.

Then:

Your server doesn't handle multiple clients. Perhaps this is a legitimate design decision, because the game you are writing will only be two player. However, mostly you will want to manage multiple clients at the same time.[/quote]
I plan server to manage multiple clients, but I was solving the problem with program blocking and didn't make this just to deal with the problem (just made server to manage only 1 client). I started to code multi-client server and my clients structure (later maybe - class), looks like this:

struct player
{
int connected;
TCPsocket psd; //player socket
IPaddress pip; //player ip struct
//Thread?
player() { connected = 0; }
};

player players[MAX_PLAYERS];

and one more question - I'm using blocking sockets, and I read that server working with blocking sockets must have each thread for each client connection, to manage client data. So, there is a cycle in the thread, but what doest it have to do? Recieve or send data? How does it works? (sorry if I'm asking about frequently asked problem, maybe there is some articles where the algorithm is described?)

And the last question about this:

Another problem is that your program doesn't seem to handle TCP's stream-like behaviour. There is no guarantee that the data you give to a send() call will all come in a single recv() call. Due to TCP's internal buffering, you might get the first half of the data from one recv(), and the second half from the next. Or you might get the data from two send() calls in a single recv(). Or both! The only guarantee you do get is that the bytes will come out in the same order as you put them in.
Most games don't want to send a continuous stream, they want to send lots of individual messages. There are two common approaches to solving this, delimited messages and length prefixed messages.
[/quote]
As I understand, my program is using delimited messages. Struct of message looks as:

enum command
{
CMD_PLAYER_MOVE,
CMD_PLAYER_KICK,
...
};
class message
{
public:
command cmd;
int params[3];
};

In my 32bit system, struct's size is 16 bytes (int = 4bytes, enum variable = int). About delimeter: as you said, for example I choose "\0" char is delimeter, and I must to place it end the end of the structure, like this:

class message
{
public:
command cmd;
int params[3];
char delimeter = "\0";
};

then, when I will recieve data from server, I must "recv" data and place it in the buffer, while it won't be delimeter char ("\0") in the recieved data, right?

Thank for your help.

Share this post


Link to post
Share on other sites
To avoid blocking the receiving thread, you can call select(). If select says that there is data to read from a socket, the next call to recv() will not block, but will return some amount of data up to the amount you request. Typically you will want to request a large amount of data (say, up to 16 kB), and deal with whatever you actually get (the return value from recv()).

Using "\0" as a "delimiter" when sending raw structs is not a good idea, because an "int" that has the value 1 will actually be encoded in memory as "\1" "\0" "\0" "\0" (or "\0" "\0" "\0" "\1" depending on byte order).

Before you start writing code for networking, or other systems programming, it's important to understand exactly how data structures are represented in memory, and how pointers and reinterpret casting works.

Share this post


Link to post
Share on other sites
In addition to hplus's comments, there appears to be no need for a delimiter in your current protocol. It seems to send fixed sized messages, so a delimiter would be redundant.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!