Friday, February 23, 2007

Introduction to SDL Net using JEDI-SDL Part 1 - A Basic Client

This tutorial will give you a short introduction into using SDL Net in your applications. For those of you who don't know SDL Net is a cross platform network library that works on Windows, Linux, BeOS and MacOS X. JEDI-SDL is a project which converts the C++ header files for SDL, SDL Net to Pascal so it can be used in Delphi, Kylix, Free Pascal and GNU Pascal. The idea being is that you can write one set of source code which will compile on every platform.

In this tutorial we will cover how to connect to a remote computer (in this case a web server) as well as how to send and read data. The goal is to give you a feel on how a SDL Net application hangs together.

Getting the JEDI-SDL units

These units are available at http://sourceforge.net/projects/jedi-sdl. The current version is 0.5, version 1.0 is being worked on at the moment.
Once you have the units you will need to configure your compiler to use them. Visit the JEDI-SDL homepage for more details.

Initialising SDL Net

As with SDL before you can use the SDL Net API you need to initialise it. This is extremely simple, one single call to

SDLNet_Init;

does the job. Don't forget the when you finish you application to call

SDLNet_Quit;

to shut down SDL Net.

Connecting using TCP

For the purposes of this tutorial we are going to assume that you have a web server that you want to connect to (in this case www.google.com). Before we can actually connect to a remote system we need to obtain it's IP address. I'm not going to explain all the details of how networks work, so lets just say the IP address is a way of identifying a computer on a network.

SDL Net have a simple function to obtain the IP address of a named computer or "host".

SDLNet_ResolveHost

This takes three parameters, the first is of type TIPAddress, this record will be filled in if the call is successful. The second parameter is the name of the computer you want to connect to and the third is the port on which you want to talk to the remote system on. Ports are like communication channels, when writing systems you will be expecting to communicate on certain channels. For example the standard port used for HTTP requests is port 80, for FTP requests it is port 21. The function will return a 0 if successful. Here is some example code

Pascal Code:

var IPAddress: TIPAddress;
....
if SDLNet_ResolveHost(IPAddress,'www.google.com', 80) <> 0 then
begin
//raise an error
end;


In the example we are going to find out the IP address of www.google.com. Note that if you are using a proxy server this will not work, in which case if you have a web server running on your machine you could try 'localhost'.

Now that we have the IP Address we can try and open a socket to the remote machine. SDL Net provides the SDLNet_TCP_Open which will open a socket given a TIPAddress. We need to declare our socket as a PTCPSocket, if this is nil after the call SDL Net failed to open the socket.


Pascal Code:

var Socket: PTCPSocket;
Socket := SDLNet_TCP_Open(IPAddress);
if Socket = nil then
begin
// raise an error

end;


Sending and Receiving using TCP

Assuming we got this far we can now go ahead and send and receive data to the remote computer. In this case we are going to send a very simple HTTP request. I'm not going to go into the details of HTTP, but it is a text based system that will allow use to test the SDL Net DLL. In your system you would probably be sending binary data representing the objects in your game/system.

In order to send string information things get a bit tricky, we need to make use of the PChar support routines. Because SDL Net was designed to work with PChar's we need to convert the string data. Allot of more seasoned Pascal programmers would probably just Cast the string as a PChar (e.g. PChar(string)), I tried this and SDL Net does not like this work around, so we need to copy the string data into a PChar and then pass that into the appropriate function.

Sending data is very easy, we use the function SDLNet_TCP_Send to send data of a specific length. Rather than go into details here is the code that does the job.


Pascal Code:

function SendHTTPRequest(var Socket: PTCPSocket; Request: string): Boolean;
var Error, Len: UInt32;
cdata: array[0..255] of char;
begin
StrPCopy(cdata, Request+#13#10+#13#10);
Len := StrLen(cdata);
Error := SDLNet_TCP_Send(Socket, @cdata, Len);
Result := Error = Len;
end;


This function sends a very basic HTTP request. The two carrage returns added to the end of the Request are there so the Web Server knows that the command is complete.
In order to test our Socket we are going to request a web page from www.google.com . In HTTP 1.0 this is very easy

'GET HTTP://www.google.com/index.html HTTP/1.0'


will get the index page from google. So to make our request we send


Pascal Code:

if not SendHTTPRequest(Socket, 'GET HTTP://www.google.com/index.html HTTP/1.0') then
begin
// raise an error
end;


So we have send the request, now the interesting part starts. Reading data in can be tricky, there are a number of ways to read data in. One method is to call SDLNet_TCP_Recv and assume that your are going to get a response, the problem with that is that this function will just wait for a response. I have found that is the server does not respond this call does not return. Fortunately SDL Net provides us with a solution, there are a number of functions that allow us to check the state of a socket to see if there is any data waiting to be read.

In order to check the state of a socket we must use a function called SDLNet_CheckSockets. This function takes two parameters, and socketset and a timeout value. The SocketSet is an array of sockets which can be created and managed using the SDLNet_AllocSocketSet, SDLNet_FreeSocketSet and SDLNet_TCP_AddSocket functions. The timeout can be 0 if you want to do a quick check to see if there is any data, or any value in milliseconds. The function SDLNet_CheckSockets will return a value greater than 0 if any sockets in the Socket set have data waiting, we can then use the SDLNet_SocketReady function to check if our socket has data. Here is some sample code.


Pascal Code:

function CheckSocket(Socket: PTCPSocket): Boolean;
const MAX_TIMEOUT = 5000;
var SocketSet: PSDLNet_SocketSet;
begin
SocketSet := SDLNet_AllocSocketSet(1);
try
SDLNet_TCP_AddSocket(SocketSet, Socket);
Result := SDLNet_CheckSockets(SocketSet, MAX_TIMEOUT) > 0;
if Result then
begin
Result := SDLNet_SocketReady(PSDLNet_GenericSocket(Socket));
end;
finally
SDLNet_FreeSocketSet(SocketSet);
end;
end;


Now this function will return true if our socket has data waiting. I admit it's not the most optimised approach as each time the Socketset is Allocated and destroyed. In a real system you would create and socketset at start up and add each new socket to it. We will deal with that situation in part 2.

So now that we can detect if we have data it's time to read the data. The function SDLNet_TCP_Recv will do this for us. This function will read n bytes of data from the Socket into a buffer and returns the number of bytes read. Our problem in this example is that we don't know how mange bytes we are going to be reading. In a real system you would probably be sending data of a specific size you would know in advance how many bytes to read. In this case we will have to read one byte at a time until there is no more data. To do this we will write a new function based on the code in CheckSockets.


Pascal Code:

function ReadStringFromSocket(Socket: PTCPSocket): string;
const MAX_TIMEOUT = 5000;
var SocketSet: PSDLNet_SocketSet;
C: Char;
begin
Result := '';
SocketSet := SDLNet_AllocSocketSet(1);
try
SDLNet_TCP_AddSocket(SocketSet, Socket);
while True do
begin
if SDLNet_CheckSockets(SocketSet, MAX_TIMEOUT) > 0 then
begin
if not SDLNet_SocketReady(PSDLNet_GenericSocket(Socket)) then Exit;
if SDLNet_TCP_Recv(Socket, @C, 1) < 1 then Exit;
Result := Result + C;
end;
end;
finally
SDLNet_FreeSocketSet(SocketSet);
end;
end;


In this function we read from the socket one character at a time until there is no more data or the SDLNet_TCP_Recv function does not read 1 byte. As this is just an example we will overlook the fact that this is not a very optimal way to do things. As we have already mentioned it's better to read as much data in on go as possible, so having fixed length messages is the preferred method.

That should be it, we have made a request to www.google.com and we should have got a response. Now all we need to do is shut down the connection.

Closing the connection

Closing a TCP connection is easy, keep in mind that you will need to remove any Sockets from any SocketSets before removing them, otherwise you'd get some weird results and probably some access violations.

SDLNet_TCP_Close will close the Socket you pass in, also I always set the Socket to nil just in case.

Conclusion

We that's all there is to using SDL Net. In our next article we will look at writing a basic message server that will accept connections and send messages to all the connected clients.
The full source code for this tutorial can be found at here.

http://www.pascalgamedevelopment.com/viewarticle.php?a=20&p=1#article