SDL:Tutorial:Using SDL net

From GPWiki
Jump to: navigation, search

Using SDL_net

This tutorial introduces the use of networking in your application, using the SDL_net library. Knowledge of BSD Sockets is recommended before starting with this tutorial.

Using TCP

If you need a reliable - but slow - connection, TCP fit your needs: this protocol is connection oriented - that is: you connect to a host, and transfer data using a socket. Data will arrive at the destination in order and will not be corrupted. In games, TCP is used mostly when you don't need a fast connection, since TCP can really slow down communications, but when you need to be sure that data arrives in order, and is not corrupted (in this case, UDP isn't the best choice).

In this simple client-server example, we do the following:

Server side
  1. Initialize SDL_net library
  2. Set a port to listen on
  3. Open a socket bound to that port
  4. Accept a connection
  5. Receive data
  6. Do something with the data
  7. Clean and quit
Client side
  1. Initialize SDL_net library
  2. Resolve the server address
  3. Open a socket
  4. Read data from the user
  5. Send the data
  6. Clean and exit

If you are experienced with sockets, you'll find that SDL_net is easier to manage.

After accepting the connection, we get some info about the host. This is optional, written here just for the sake of this tutorial.

Note also that this server accepts and manages only one connection at time, but of course you can use these techniques to manage many connections together (i.e. using threads).

Code

Here's the server code, with a compilation script included in the header:

#if 0
#!/bin/sh
gcc -Wall `sdl-config --cflags` tcps.c -o tcps `sdl-config --libs` -lSDL_net
 
exit
#endif
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#include "SDL_net.h"
 
int main(int argc, char **argv)
{
	TCPsocket sd, csd; /* Socket descriptor, Client socket descriptor */
	IPaddress ip, *remoteIP;
	int quit, quit2;
	char buffer[512];
 
	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);
	}
 
	/* Wait for a connection, send data and term */
	quit = 0;
	while (!quit)
	{
		/* This check the sd if there is a pending connection.
		* If there is one, accept that, and open a new socket for communicating */
		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)))
				/* Print the address, converting in the host format */
				printf("Host connected: %x %d\n", SDLNet_Read32(&remoteIP->host), SDLNet_Read16(&remoteIP->port));
			else
				fprintf(stderr, "SDLNet_TCP_GetPeerAddress: %s\n", SDLNet_GetError());
 
			quit2 = 0;
			while (!quit2)
			{
				if (SDLNet_TCP_Recv(csd, buffer, 512) > 0)
				{
					printf("Client say: %s\n", buffer);
 
					if(strcmp(buffer, "exit") == 0)	/* Terminate this connection */
					{
						quit2 = 1;
						printf("Terminate connection\n");
					}
					if(strcmp(buffer, "quit") == 0)	/* Quit the program */
					{
						quit2 = 1;
						quit = 1;
						printf("Quit program\n");
					}
				}
			}
 
			/* Close the client socket */
			SDLNet_TCP_Close(csd);
		}
	}
 
	SDLNet_TCP_Close(sd);
	SDLNet_Quit();
 
	return EXIT_SUCCESS;
}

And here is the code for the client:

#if 0
#!/bin/sh
gcc -Wall `sdl-config --cflags` tcpc.c -o tcpc `sdl-config --libs` -lSDL_net
 
exit
#endif
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#include "SDL_net.h"
 
int main(int argc, char **argv)
{
	IPaddress ip;		/* Server address */
	TCPsocket sd;		/* Socket descriptor */
	int quit, len;
	char buffer[512];
 
	/* Simple parameter checking */
	if (argc < 3)
	{
		fprintf(stderr, "Usage: %s host port\n", argv[0]);
		exit(EXIT_FAILURE);
	}
 
	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, argv[1], atoi(argv[2])) < 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);
	}
 
	/* Send messages */
	quit = 0;
	while (!quit)
	{
		printf("Write something:\n>");
		scanf("%s", buffer);
 
		len = strlen(buffer) + 1;
		if (SDLNet_TCP_Send(sd, (void *)buffer, len) < len)
		{
			fprintf(stderr, "SDLNet_TCP_Send: %s\n", SDLNet_GetError());
			exit(EXIT_FAILURE);
		}
 
		if(strcmp(buffer, "exit") == 0)
			quit = 1;
		if(strcmp(buffer, "quit") == 0)
			quit = 1;
	}
 
	SDLNet_TCP_Close(sd);
	SDLNet_Quit();
 
	return EXIT_SUCCESS;
}

Using UDP

In most games the protocol used is UDP, because it is faster than TCP. The speed of course doesn't come for free: UDP does not provide a reliable way to send packets since UDP packets are not guaranteed to reach their destination, and the packets may also arrive out of order. UDP is a connectionless protocol, this means that you don't need to establish a connection between hosts, but you simply send a message to a specified port or wait for a message on a port.

Anyway, SDL_net provides a half-way method for using connections with UDP: you can specify a channel and bind a remote host (and the relative port) to the UDP socket. In this way you don't need to supply the address of the source packet everytime you send something.

In this example of using UDP in connectionless mode, we do the following:

Server side
  1. Initialize SDL_net library
  2. Open a socket on a specified port
  3. Allocate memory for packets
  4. Wait for a packet from the client
  5. Do something with the packet
  6. Free the memory and quit
Client side
  1. Initialize SDL_net library
  2. Open a socket on a random usable port
  3. Resolve the server address
  4. Allocate memory for packets
  5. Fill and send the packet
  6. Free the memory and quit

As you can see, the procedure is quite simple, considering that initialization and freeing memory is done usually only once.

Code

Now let's write the code for this application.

File udps.c the server, with compiling script:

#if 0
#!/bin/sh
gcc -Wall `sdl-config --cflags` udps.c -o udps `sdl-config --libs` -lSDL_net
 
exit
#endif
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#include "SDL_net.h"
 
int main(int argc, char **argv)
{
	UDPsocket sd;       /* Socket descriptor */
	UDPpacket *p;       /* Pointer to packet memory */
	int quit;
 
	/* Initialize SDL_net */
	if (SDLNet_Init() < 0)
	{
		fprintf(stderr, "SDLNet_Init: %s\n", SDLNet_GetError());
		exit(EXIT_FAILURE);
	}
 
	/* Open a socket */
	if (!(sd = SDLNet_UDP_Open(2000)))
	{
		fprintf(stderr, "SDLNet_UDP_Open: %s\n", SDLNet_GetError());
		exit(EXIT_FAILURE);
	}
 
	/* Make space for the packet */
	if (!(p = SDLNet_AllocPacket(512)))
	{
		fprintf(stderr, "SDLNet_AllocPacket: %s\n", SDLNet_GetError());
		exit(EXIT_FAILURE);
	}
 
	/* Main loop */
	quit = 0;
	while (!quit)
	{
		/* Wait a packet. UDP_Recv returns != 0 if a packet is coming */
		if (SDLNet_UDP_Recv(sd, p))
		{
			printf("UDP Packet incoming\n");
			printf("\tChan:    %d\n", p->channel);
			printf("\tData:    %s\n", (char *)p->data);
			printf("\tLen:     %d\n", p->len);
			printf("\tMaxlen:  %d\n", p->maxlen);
			printf("\tStatus:  %d\n", p->status);
			printf("\tAddress: %x %x\n", p->address.host, p->address.port);
 
			/* Quit if packet contains "quit" */
			if (strcmp((char *)p->data, "quit") == 0)
				quit = 1;
		}		
	}
 
	/* Clean and exit */
	SDLNet_FreePacket(p);
	SDLNet_Quit();
	return EXIT_SUCCESS;
}

File udpc.c the client, with autocompiling script:

#if 0
#!/bin/sh
 
gcc -Wall `sdl-config --cflags` udpc.c -o udpc `sdl-config --libs` -lSDL_net
 
exit
#endif
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#include "SDL_net.h"
 
int main(int argc, char **argv)
{
	UDPsocket sd;
	IPaddress srvadd;
	UDPpacket *p;
	int quit;
 
	/* Check for parameters */
	if (argc < 3)
	{
		fprintf(stderr, "Usage: %s host port\n", argv[0]);
		exit(EXIT_FAILURE);
	}
 
	/* Initialize SDL_net */
	if (SDLNet_Init() < 0)
	{
		fprintf(stderr, "SDLNet_Init: %s\n", SDLNet_GetError());
		exit(EXIT_FAILURE);
	}
 
	/* Open a socket on random port */
	if (!(sd = SDLNet_UDP_Open(0)))
	{
		fprintf(stderr, "SDLNet_UDP_Open: %s\n", SDLNet_GetError());
		exit(EXIT_FAILURE);
	}
 
	/* Resolve server name  */
	if (SDLNet_ResolveHost(&srvadd, argv[1], atoi(argv[2])) == -1)
	{
		fprintf(stderr, "SDLNet_ResolveHost(%s %d): %s\n", argv[1], atoi(argv[2]), SDLNet_GetError());
		exit(EXIT_FAILURE);
	}
 
	/* Allocate memory for the packet */
	if (!(p = SDLNet_AllocPacket(512)))
	{
		fprintf(stderr, "SDLNet_AllocPacket: %s\n", SDLNet_GetError());
		exit(EXIT_FAILURE);
	}
 
	/* Main loop */
	quit = 0;
	while (!quit)
	{
		printf("Fill the buffer\n>");
		scanf("%s", (char *)p->data);
 
		p->address.host = srvadd.host;	/* Set the destination host */
		p->address.port = srvadd.port;	/* And destination port */
 
		p->len = strlen((char *)p->data) + 1;
		SDLNet_UDP_Send(sd, -1, p); /* This sets the p->channel */
 
		/* Quit if packet contains "quit" */
		if (!strcmp((char *)p->data, "quit"))
			quit = 1;
	}
 
	SDLNet_FreePacket(p);
	SDLNet_Quit();
 
	return EXIT_SUCCESS;
}

In these examples no channel is used, so no binds are used. Anyway you can bind the connection in the client using

 SDLNet_UDP_Bind(udpSocketDescriptor, channelNumber, &serverIPIddress)
Compiling script note

As you can see, these sources (tested on Gentoo GNU/Linux 2005.1) have a compiling script in the header. This method is easy and fast, but portability is relative. So i think i shoud explain how this works: The row:

 #if 0

is used to prevent the script from being read by the compiler. In this way the script isn't processed, and won't generate errors. Of course, we need to add:

 exit
 #endif

to exit the script and the comment. The line:

 #!/bin/sh

is a directive to the shell, which specifies that the script should be executed with the Shell interpreter, whose binary file is under /bin/. Next, the compiling row:

 gcc -Wall `sdl-config --cflags` tcps.c -o tcps `sdl-config --libs` -lSDL_net

gcc is of course the GNU C Compiler, used to compile and link the source code. Note the use of sdl-config: the correct way to compile SDL is using this tool. This should be in any system where SDL is installed, and this tool provides help locating the correct directories where header files (sdl-config --cflags) and libraries (sdl-config --libs) are stored. To be more precise, some systems (like FreeBSD) don't have the sdl-config executable, but another one which works similarly (actually, sdl11-config if I'm not wrong). Since every OS can have different tools to locate the headers/libs directory, a standard way has been defined: If the enviroment variable SDL_CONFIG is set, then use its value as the path to the tool, otherwise use the sdl-config tool as default.

So a partially correct compiling script should be:

 #!/bin/sh
 if [ -z "$SDL_CONFIG" ]; then
 	SDL_CONFIG="sdl-config" # You should check if sdl-config exists
 fi
 gcc `$SDL_CONFIG --cflags` blah blah `$SDL_CONFIG --libs`

To be really correct, you should also check if the tool sdl-config is present on the system, using the "which" command.