SDL_net connection problem

Started by
17 comments, last by rip-off 14 years, 12 months ago
Using C++ and SDL_net, I'm completely new to SDL_net and I'm trying to do a simple test app to send data to server. I used the example source code from: http://gpwiki.org/index.php/SDL:Tutorial:Using_SDL_net But I think I'm not getting it work right... Here's my server example.. Init:
	TCPsocket sd, csd; /* Socket descriptor, Client socket descriptor */
	IPaddress ip, *remoteIP;
	char buffer[512];

	if(server)
	{
		if (SDLNet_Init() < 0)
		{
			fprintf(stderr, "SDLNet_Init: %s\n", SDLNet_GetError());
			exit(EXIT_FAILURE);
		}
 
		/* Resolving the host using NULL make network interface to listen */
		if (SDLNet_ResolveHost(&ip, NULL, 2000) < 0)
		{
			fprintf(stderr, "SDLNet_ResolveHost: %s\n", SDLNet_GetError());
			exit(EXIT_FAILURE);
		}
 
		/* Open a connection with the IP provided (listen on the host's port) */
		if (!(sd = SDLNet_TCP_Open(&ip)))
		{
			fprintf(stderr, "SDLNet_TCP_Open: %s\n", SDLNet_GetError());
			exit(EXIT_FAILURE);
		}
	}
Loop:
	if(server)
	{
		if ((csd = SDLNet_TCP_Accept(sd)))
		{
			/* Now we can communicate with the client using csd socket
			* sd will remain opened waiting other connections */
 
			/* Get the remote address */
			if ((remoteIP = SDLNet_TCP_GetPeerAddress(csd)))
				printf("Host connected: %x %d\n", SDLNet_Read32(&remoteIP->host), SDLNet_Read16(&remoteIP->port));
			else
				fprintf(stderr, "SDLNet_TCP_GetPeerAddress: %s\n", SDLNet_GetError());
				
			if (SDLNet_TCP_Recv(csd, buffer, 512) > 0)
			{
				printf("Client say: %s\n", buffer);
			}
		}
	}
Quit:
if(server)
	{
		SDLNet_TCP_Close(sd);
		SDLNet_Quit();
	}
--------------- And the clientside.. Init:
	IPaddress ip;		/* Server address */
	TCPsocket sd;		/* Socket descriptor */
	int len;
	char buffer[512];

	if(client)
	{
		if (SDLNet_Init() < 0)
		{
			fprintf(stderr, "SDLNet_Init: %s\n", SDLNet_GetError());
			exit(EXIT_FAILURE);
		}
 
		/* Resolve the host we are connecting to */
		if (SDLNet_ResolveHost(&ip, "localhost", 2000) < 0)
		{
			fprintf(stderr, "SDLNet_ResolveHost: %s\n", SDLNet_GetError());
			exit(EXIT_FAILURE);
		}
 
		/* Open a connection with the IP provided (listen on the host's port) */
		if (!(sd = SDLNet_TCP_Open(&ip)))
		{
			fprintf(stderr, "SDLNet_TCP_Open: %s\n", SDLNet_GetError());
			exit(EXIT_FAILURE);
		}
	}
Loop:
		if(client)
		{
			sprintf(buffer, "blabla");

			len = strlen(buffer) + 1;
			if (SDLNet_TCP_Send(sd, (void *)buffer, len) < len)
			{
				fprintf(stderr, "SDLNet_TCP_Send: %s\n", SDLNet_GetError());
			}
		}
Quit:
if(client)
	{
		SDLNet_TCP_Close(sd);
		SDLNet_Quit();

	}
What I'm doing wrong here? The client app connects to the server but sends the "blabla" message only once (prints it out in the server's console). Shouldn't it print the message all the time while connected? I tried to search google for SDL_net source code example in some simple game, but didn't find any help. Sorry if the sources look too messy, don't mind the 'if(client)' and 'if(server)' lines. Cheers
What the h*ll are you?
Advertisement
You probably want to hold on to the connection once it is established. At the moment you accept a connection, receive once from it and then discard the pointer to that socket.

There are two simple options.

The first is to make an inner loop on the server:
if(server){	if ((csd = SDLNet_TCP_Accept(sd)))	{			if ((remoteIP = SDLNet_TCP_GetPeerAddress(csd)))			printf("Host connected: %x %d\n", SDLNet_Read32(&remoteIP->host), SDLNet_Read16(&remoteIP->port));		else			fprintf(stderr, "SDLNet_TCP_GetPeerAddress: %s\n", SDLNet_GetError());				// Change the "if" to a "while" loop			while (SDLNet_TCP_Recv(csd, buffer, 512) > 0)		{			printf("Client say: %s\n", buffer);		}		printf("Host disconnected\n");	}}

This will allow you to connect a single client.

To server multiple clients simultaneously, you would need some kind of container for storing the sockets, such as std::vector<>. You would use SDL_net's Socket Sets to keep the server non-blocking.
Thanks for the reply rip-off

Is there any example of connecting multiple clients to a server?
I checked the socket set stuff in the link but I can't figure out how you do process them in the main loop.
The message is sent to the server again..once, using 'if', but I think if I use 'while' with multiple connections, it doesn't work...

If the server and client is in the same application/game, the server SDL rendering loop gets stuck because of the while, so is there some alternate way than using while?

And how does it work to have server and client implemented to same application. So one could host a server, and others connect to it...with the same version of the app?

Dunno if I told it clear enough..

EDIT: Is there some alternate for std::vector<> since I haven't never used/worked with it?
What the h*ll are you?
Quote:Original post by NukeCorr
Thanks for the reply rip-off

Is there any example of connecting multiple clients to a server?
I checked the socket set stuff in the link but I can't figure out how you do process them in the main loop.
The message is sent to the server again..once, using 'if', but I think if I use 'while' with multiple connections, it doesn't work...

Yes, the while loop will soak up all your time until the connection dies. It was only a quick solution to show how a real program would work.

Quote:
If the server and client is in the same application/game, the server SDL rendering loop gets stuck because of the while, so is there some alternate way than using while?

Yes, the SocketSet will help you with this.

Quote:
And how does it work to have server and client implemented to same application. So one could host a server, and others connect to it...with the same version of the app?

This is certainly possible. You just have to have all the code paths available in the same application.

Quote:
EDIT: Is there some alternate for std::vector<> since I haven't never used/worked with it?

Yes, but you won't have used these either. Better to learn std::vector<>, it is a vital tool and part of the language, in the standard library.

Here is an example of a non-blocking multi-client server code:
#include <cstdio>#include <cstdlib>#include <cstring>#include <vector> #include "SDL_net.h"TCPsocket initialise(){	if (SDLNet_Init() < 0)	{		fprintf(stderr, "SDLNet_Init: %s\n", SDLNet_GetError());		exit(1);	} 	IPaddress ip;	const int port = 2000;	if (SDLNet_ResolveHost(&ip, NULL, port) < 0)	{		fprintf(stderr, "SDLNet_ResolveHost: %s\n", SDLNet_GetError());		SDLNet_Quit();		exit(1);	} 	if (TCPSocket server = SDLNet_TCP_Open(&ip))	{		return server;	}	else	{		fprintf(stderr, "SDLNet_TCP_Open: %s\n", SDLNet_GetError());		SDLNet_Quit();		exit(1);	}}// Typedef to help readability.typedef std::vector<TCPSocket> Clients;// Helper functions.void handleNewClients(Clients &clients, SDLNet_SocketSet &socketSet);void handleNetworkData(Clients &clients, SDLNet_SocketSet socketSet); int main(int argc, char **argv){	TCPsocket server = initialise(); 	Clients clients;	SDLNet_SocketSet socketSet = 0;	bool running = true;	while (running)	{		handleNewClients(clients, socketSet);				handleNetworkData(clients, socketSet);				// Here we can render and check for input, the usual SDL stuff.	}		// Clean up all the client sockets here.	// Don't forget about socketSet! 	SDLNet_TCP_Close(server);	SDLNet_Quit(); 	return 0;}void handleNewClients(Clients &clients, SDLNet_SocketSet &socketSet){	// Check if new connections have arrived.	// Add them in a loop to match the case of multiple connections arriving at the same time.	while(TCPSocket client = SDLNet_TCP_Accept(server)))	{		if (IPaddress *remoteIP = SDLNet_TCP_GetPeerAddress(client))		{			printf("Host connected: %x %d\n", SDLNet_Read32(&remoteIP->host), SDLNet_Read16(&remoteIP->port));		}		else		{			fprintf(stderr, "SDLNet_TCP_GetPeerAddress: %s\n", SDLNet_GetError());		}				// Add the new socket to the vector.		// The vector is a dynamic array, it will grow in size to ensure it can fit the new item.		clients.push_back(client);				// If we haven't allocated the socket set, allocate it now.		if(!socketSet)		{			// Use the capacity of the vector as a guide as to how many sockets to allocate.			socketSet = SDLNet_AllocSocketSet(clients.capacity());			if(!socketSet)			{				// error handling			}		}				// Try to add a socket to the set.		// If this fails, we probably need to make room for additional sockets in the socketSet.		// The socketSet, unlike the vector<>, does not grow to meet demand.		if(SDLNet_TCP_AddSocket(socketSet, client) < 0)		{			// If we are here, an error occured. Let's make the socketset bigger, again guided by the vector capacity.			SDL_FreeSocketSet(socketSet);			// The capacity() of a vector is >= to its size()			socketSet = SDLNet_AllocSocketSet(clients.capacity());			if(!socketSet)			{				// error handling			}			// Add all the current clients to the socket set...			for(int i = 0 ; i < clients.size() ; ++i)			{				// If we have a valid client.				if(clients)				{					// We have already ensured room for all the sockets, so if this fails					// we fall back on normal error handling. No point making it even bigger.					if(SDLNet_TCP_AddSocket(socketSet, clients) < 0)					{						// error handling					}				}			}		}	}}void handleNetworkData(Clients &clients, SDLNet_SocketSet socketSet){	// Check for network activity:	const int timeout = 0;	int active = SDLNet_CheckSockets(socketSet, timeout);	// If there is activity.	if(active > 0)	{		// Loop over all connected clients.		for(int i = 0 ; i < clients.size() ; ++i)		{			// If we are on a valid client.			if(TCPSocket *client = clients)			{				// And it's socket has had some activity				if(SDLNet_SocketReady(client))				{					const int bufferSize = 511;					// +1 to ensure there is room for NUL terminator					char buffer[bufferSize + 1];										// Read the data.					int bytes = SDLNet_TCP_Recv(client, buffer, bufferSize);										// Handle all cases.					switch(bytes)					{					case -1:						// error handling						break;					case 0:						// Socket was closed by remote host.						// Close the socket and null the pointer in the vector.						printf("Host %d disconnected\n", i);						SDLNet_TCP_DelSocket(socketSet, client);						SDLNet_TCP_Close(client);						clients = 0;						break;					default:						// We got some data!						// Ensure it is NUL terminated and print.						buffer[bytes] = '\0';						printf("Host %d says %s\n", i, buffer);						break;					}				}			}		}	}}

Its not perfect code. The two functions could be further broken down into additional functions, and good use of RAII wrapper classes for the SDLNet types would help a lot, but I figured that they would detract from the point, besides writing this is your job, not mine [smile]

Please note that I haven't even compiled it, so there may be a few syntax errors in there.

Now, while I say this is non-blocking there is still an issue of calls to SDL_TCP_Send blocking. Unfortunately SDL_Net doesn't appear to include any way to mark a socket explicitly non-blocking using its API. So you may still end up blocking when sending data. But this shouldn't block on receiving data anyway.
Thanks rip-off, your help has been valuable, and thanks for the example!
I'm sure I'll get something to work with this!
I'll reply here if I encounter any errors which I can't manage to fix.

Cheers
What the h*ll are you?
There were 25 errors but I fixed them, though the app crashes at line:

void handleNetworkData(Clients &clients, SDLNet_SocketSet socketSet){   ...   int active = SDLNet_CheckSockets(socketSet, timeout); // crashes here   ...}


What's the problem there?
What the h*ll are you?
I actually compiled and tested the code myself in the mean time. Most of the errors were my typo of TCPsocket, which I substituted with TCPSocket. The other errors were minor syntactical problems, with the notable exception of forgetting to pass the "server" socket as a parameter to handleNewConnections().

The problem on that line is that the socket set might still be NULL when we have no clients.

There are two solutions. One is to test the pointer before calling the function and the other is to test in the function.

E.g. either:
// inside main() ...bool running = true;while (running){	handleNewClients(server, clients, socketSet);			if(socketSet)	{		handleNetworkData(clients, socketSet);	}			// Here we can render and check for input, the usual SDL stuff.}

Or:
void handleNetworkData(Clients &clients, SDLNet_SocketSet socketSet){	if(!socketSet)	{		return;	}	// rest of handleNetworkData}


Here is a complete version which I have tested with telnet (though it still lacks error handling).
#include <cstdio>#include <cstdlib>#include <cstring>#include <vector> #include "SDL_net.h"#ifdef _WIN32#include "windows.h"#endifTCPsocket initialise(){#ifdef _WIN32	AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole();	freopen( "CON", "wt", stdout );	freopen( "CON", "wt", stderr );#endif		if (SDLNet_Init() < 0)	{		fprintf(stderr, "SDLNet_Init: %s\n", SDLNet_GetError());		exit(1);	} 	IPaddress ip;	const int port = 2000;	if (SDLNet_ResolveHost(&ip, NULL, port) < 0)	{		fprintf(stderr, "SDLNet_ResolveHost: %s\n", SDLNet_GetError());		SDLNet_Quit();		exit(1);	} 	if (TCPsocket server = SDLNet_TCP_Open(&ip))	{		return server;	}	else	{		fprintf(stderr, "SDLNet_TCP_Open: %s\n", SDLNet_GetError());		SDLNet_Quit();		exit(1);	}}// Typedef to help readability.typedef std::vector<TCPsocket> Clients;// Helper functions.void handleNewClients(TCPsocket server, Clients &clients, SDLNet_SocketSet &socketSet);void handleNetworkData(Clients &clients, SDLNet_SocketSet socketSet); int main(int, char **){	TCPsocket server = initialise(); 	Clients clients;	SDLNet_SocketSet socketSet = 0;	printf("[server]: Now listening for connections...\n");	bool running = true;	while (running)	{		handleNewClients(server, clients, socketSet);		handleNetworkData(clients, socketSet);				// Here we can render and check for input, the usual SDL stuff.	}		// Clean up all the client sockets here.	// Don't forget about socketSet! 	SDLNet_TCP_Close(server);	SDLNet_Quit(); 	return 0;}void handleNewClients(TCPsocket server, Clients &clients, SDLNet_SocketSet &socketSet){	// Check if new connections have arrived.	// Add them in a loop to match the case of multiple connections arriving at the same time.	while(TCPsocket client = SDLNet_TCP_Accept(server))	{		if (IPaddress *remoteIP = SDLNet_TCP_GetPeerAddress(client))		{			printf("Host connected: %x %d\n", SDLNet_Read32(&remoteIP->host), SDLNet_Read16(&remoteIP->port));		}		else		{			fprintf(stderr, "SDLNet_TCP_GetPeerAddress: %s\n", SDLNet_GetError());		}				// Add the new socket to the vector.		// The vector is a dynamic array, it will grow in size to ensure it can fit the new item.		clients.push_back(client);				// If we haven't allocated the socket set, allocate it now.		if(!socketSet)		{			// Use the capacity of the vector as a guide as to how many sockets to allocate.			socketSet = SDLNet_AllocSocketSet(clients.capacity());			if(!socketSet)			{				// error handling			}		}				// Try to add a socket to the set.		// If this fails, we probably need to make room for additional sockets in the socketSet.		// The socketSet, unlike the vector<>, does not grow to meet demand.		if(SDLNet_TCP_AddSocket(socketSet, client) < 0)		{			// If we are here, an error occured. Let's make the socketset bigger, again guided by the vector capacity.			SDLNet_FreeSocketSet(socketSet);			// The capacity() of a vector is >= to its size()			socketSet = SDLNet_AllocSocketSet(clients.capacity());			if(!socketSet)			{				// error handling			}			// Add all the current clients to the socket set...			for(Clients::size_type i = 0 ; i < clients.size() ; ++i)			{				// If we have a valid client.				if(clients)				{					// We have already ensured room for all the sockets, so if this fails					// we fall back on normal error handling. No point making it even bigger.					if(SDLNet_TCP_AddSocket(socketSet, clients) < 0)					{						// error handling					}				}			}		}	}}void handleNetworkData(Clients &clients, SDLNet_SocketSet socketSet){	if(!socketSet)	{		return;	}	// Check for network activity:	const int timeout = 0;	int active = SDLNet_CheckSockets(socketSet, timeout);	// If there is activity.	if(active > 0)	{		// Loop over all connected clients.		for(Clients::size_type i = 0 ; i < clients.size() ; ++i)		{			// If we are on a valid client.			if(TCPsocket client = clients)			{				// And it's socket has had some activity				if(SDLNet_SocketReady(client))				{					const int bufferSize = 511;					// +1 to ensure there is room for NUL terminator					char buffer[bufferSize + 1];										// Read the data.					int bytes = SDLNet_TCP_Recv(client, buffer, bufferSize);										// Handle all cases.					switch(bytes)					{					case -1:						// error handling						break;					case 0:						// Socket was closed by remote host.						// Close the socket and null the pointer in the vector.						printf("Host %d disconnected\n", i);						SDLNet_TCP_DelSocket(socketSet, client);						SDLNet_TCP_Close(client);						clients = 0;						break;					default:						// We got some data!						// Ensure it is NUL terminated and print.						buffer[bytes] = '\0';						printf("Host %d says %s\n", i, buffer);						break;					}				}			}		}	}}
I'm trying to send the received client data from server to all clients but I fail... this is a pain in the *ss

class CPlayer{public:	bool active;	int id;	int x, y;	int w, h;	int type;	bool colliding;	int dir;	bool isMoving;	int ai;	int vel;};struct Packet{	CPlayer Player;};


Server side
Packet buffer;int result = SDLNet_TCP_Recv(client, &buffer, sizeof(buffer));Send_to_clients(clients,buffer);...void Send_to_clients(Clients &clients, Packet data){	// Loop over all connected clients.	for(Clients::size_type i = 0 ; i < clients.size() ; ++i)	{		// If we are on a valid client.		if(TCPsocket client = clients)		{			// And it's socket has had some activity			if(SDLNet_SocketReady(client))			{				Packet buffer;				buffer = data;								int result = SDLNet_TCP_Send(client, &buffer, sizeof(buffer));				if(result<sizeof(buffer))				{					fprintf(stderr, "SDLNet_TCP_Send: %s\n", SDLNet_GetError());				}			}		}	}}


Client side
CPlayer Player[MAX_PLAYERS];int myPlayer = 0;...Packet buffer;buffer.Player = Player[myPlayer];if(SDLNet_TCP_Send(sd, &buffer, sizeof(buffer)) < sizeof(buffer)){	fprintf(stderr, "SDLNet_TCP_Send: %s\n", SDLNet_GetError());}Packet buffer2;if(SDLNet_TCP_Recv(sd, &buffer2, sizeof(buffer2)) < sizeof(buffer2)){    fprintf(stderr, "SDLNet_TCP_Recv: %s\n", SDLNet_GetError());}else    printf("Server sends x: %d y: %d\n",buffer2.Player.x,buffer2.Player.yPlayer[buffer2.Player.id] = buffer2.Player;


When I start server it is up and running, but when I connect the client to it, client gets stuck; server not. I noticed it gets stuck with the data receiving from server
What the h*ll are you?
Your send code shouldn't be using the "SDLNet_SocketReady" check. That is only examined when receiving data.

Other than that, you'll have to jump through the same hoops to make the client non-blocking when receiving as I showed you how to do with the server. This time it is a little easier, because you are only going to have one server so you can pre-allocate the socketSet at size 1.

On a slightly unrelated note: are you sure you need TCP for this task? TCP ensures delivery, but by making some performance sacrifices. But what good is guaranteed delivery when the data that comes in is very late?

UDP doesn't guarantee delivery, and will usually result in lower latency in the case of lost packets. But you would need to take care of lost or duplicate or out of order packets yourself.

There are a number of higher level libraries built on top of raw UDP which give you the benefits without the drawbacks. You can see a list of them in the Multiplayer and Network Programming Forum FAQ, under Question 7.
Well I would use UDP but TCP is better for games, right?

Anyway..
I got it working now as I wanted, I found some TCP multiserver and client examples from internet.
But I'm experiencing a huge lag, when I move the player 1 in client 1 window, then, client 2's window the player 1 moves after 5 second delay.
What the h*ll are you?

This topic is closed to new replies.

Advertisement