SDL 2.0 Tutorial-04: Networking 1

For the next few posts I plan to run through some of the basics of TCP communication using the SDLNet library. The goal here is to write a simple “resource” game where the client sends a “quest” request and, after some amount of time has passed, the quest is considered complete and the client’s resource count increases a little. This series will provide an excellent setup for developing a player driven economy type game.

Our first goal will be to write a server which manages all the client connections. To keep things simple we will be working with one single resource; here we are using wood as the resource. We’ll start with a few #define ‘s

//-----------------------------------------------------------------------------
#define MAX_PACKET 0xFF
#define MAX_SOCKETS 0x10

//-----------------------------------------------------------------------------
#define WOOD_WAIT_TIME 5000

//-----------------------------------------------------------------------------
#define FLAG_QUIT 0x0000
#define FLAG_WOOD_UPDATE 0x0010

Here we have three different type of #define ‘s

  • MAX_PACKET and MAX_SOCKETS define the maximum size of the TCP packets we are willing to process and the maximum number of clients we will allow online at once.
  • WOOD_WAIT_TIME is the amount of time in milliseconds to wait before the quest for obtaining additional wood units is complete.
  • FLAG_QUIT and FLAG_WOOD_UPDATE are 16 bit packet flags used to determine the type of packet we are receiving and we then process the rest of the packet’s data based on this flag.

Next we need a structure which we can use to keep all the client’s information separate from one another.

typedef struct {
	int in_use;
	int questing;
	uint8_t amt_wood;
	uint32_t timer_wood;
} Client;

Here we have a few important variables which we need in order to efficiently work with each of the clients which connect to the server. in_use allows us to determine whether the current client is being used by an active socket connection or not. This will prevent us from running checks on client structures which don’t have corresponding network connections. questing is a simple boolean which tells us whether the current client is running a wood fetching quest or not. amt_wood is the amount of the wood resource which the current client has. timer_wood is a timestamp for when the wood fetching quest for this particular client was started – this will help us determine when the wood fetching quest has been completed.

Next we need some global state variables which will make writing our little server a bit easier.

int next_ind = 0;
TCPsocket server_socket;
Client clients[MAX_SOCKETS];
SDLNet_SocketSet socket_set;
TCPsocket sockets[MAX_SOCKETS];

next_ind is the index into the sockets and clients arrays for the next player that will connect to the server. When one client connects next_ind gets updated so that we never overwrite a connected clients information.

First thing we do in int main(int argc, char** argv); is initialize a few SDL components and the SDLNet library.

if(SDL_Init(SDL_INIT_TIMER|SDL_INIT_EVENTS) != 0) {
	fprintf(stderr, "ER: SDL_Init: %sn", SDL_GetError());
	exit(-1);
}

if(SDLNet_Init() == -1) {
	fprintf(stderr, "ER: SDLNet_Init: %sn", SDLNet_GetError());
	exit(-1);
}

Next we’ll want to open the server’s TCP socket on a specific port, here I’m using port 8099. Any function specific information can be found within the SDLNet online API.

IPaddress ip;
if(SDLNet_ResolveHost(&ip, NULL, 8099) == -1) {
	fprintf(stderr, "ER: SDLNet_ResolveHost: %sn", SDLNet_GetError());
	exit(-1);
}

server_socket = SDLNet_TCP_Open(&ip);
if(server_socket == NULL) {
	fprintf(stderr, "ER: SDLNet_TCP_Open: %sn", SDLNet_GetError());
	exit(-1);
}

Finally, before the main processing loop, we will setup an SDLNet socket set and add the server_socket to the socket set so that we may be informed when a net connection is looking to be accepted by the server.

socket_set = SDLNet_AllocSocketSet(MAX_SOCKETS+1);
if(socket_set == NULL) {
	fprintf(stderr, "ER: SDLNet_AllocSocketSet: %sn", SDLNet_GetError());
	exit(-1);
}

if(SDLNet_TCP_AddSocket(socket_set, server_socket) == -1) {
	fprintf(stderr, "ER: SDLNet_TCP_AddSocket: %sn", SDLNet_GetError());
	exit(-1);
}

It’s worth noting that the size of the socket set is MAX_SOCKETS+1 , the plus one accounts for the addition of the server socket. The main processing loop is setup like the following (currently hollow):

int running = 1;
while(running) {
	int num_rdy = SDLNet_CheckSockets(socket_set, 1000);

	if(num_rdy <= 0) {
		// NOTE: none of the sockets are ready
	} else {
		// NOTE: some number of the sockets are ready
	}
}

While the server is running we check for any sockets from the socket set which might have packets for us to process; we check the socket set for a second (1000 milliseconds). If there aren’t any sockets ready to be processed then we use this time to run other various server activities like checking is quests have been completed, etc. If there is some number of sockets ready to be processed then we process those sockets.

In the next tutorial we will fill in these two parts of the if-statement above; this is where the real magic is! To clean up everything after the server is done running we will run the following code.

if(SDLNet_TCP_DelSocket(socket_set, server_socket) == -1) {
	fprintf(stderr, "ER: SDLNet_TCP_DelSocket: %sn", SDLNet_GetError());
	exit(-1);
} SDLNet_TCP_Close(server_socket);

int i;
for(i=0; i<MAX_SOCKETS; ++i) {
	if(sockets[i] == NULL) continue;
	CloseSocket(i);
}

SDLNet_FreeSocketSet(socket_set);
SDLNet_Quit();
SDL_Quit();

The missing piece here is the void CloseSocket(int index); function which is basically just a shorthand for closing a client socket and is stated below:

void CloseSocket(int index) {
	if(sockets[index] == NULL) {
		fprintf(stderr, "ER: Attempted to delete a NULL socket.n");
		return;
	}

	if(SDLNet_TCP_DelSocket(socket_set, sockets[index]) == -1) {
		fprintf(stderr, "ER: SDLNet_TCP_DelSocket: %sn", SDLNet_GetError());
		exit(-1);
	}

	memset(&clients[index], 0x00, sizeof(Client));
	SDLNet_TCP_Close(sockets[index]);
	sockets[index] = NULL;
}

For the next tutorial we’ll pick up from here and fill in the if-statement mentioned earlier. Once the server-side code has been setup to process a simple player request to perform a quest to gather wood, we’ll start working on a simple client-side quest button, quest timer, and resource counter. If there is time or interest we will add persistence to the resource count for each player using SQLite.