The following user tutorial was created by Kahshoo-heem, for the purpose of expanding upon the SDLTutorials.com series, and expounding upon the use of SDL. This tutorial, though not purposely a part of the SDLTutorials.com or created for the series, may be a branch or addition to the series. Please read notes by the author for any additional code and/or framework used by the author. If you wish to submit your own tutorial to this site, please visit the “User Tutorials” page.
Once we have made our common library, it’s time to think on designing the game. Network games can have many architectures, but, normally, there are always at least two applications: a server and a client.
A server can be dedicated, like most online multiplayer games over the internet. But, in this case, the server will be playing too. To make things simpler, the server always will be “X”, and the player that starts the game, too. It’ll be responsible for waiting clients to connect, too.
At each player turn, the program sets the grid status, refreshes the screen, and sends a message to the other player. Receiving this message, the other player is now able to make valid inputs, as the first one can only exit or minimize his application.
At this point, we may have to create a state for the application: connected. Without it, one can start the game alone, and send messages to a person who is not online.
Now, let’s take a look at the messages the program has to make. As we said in the first tutorial, we will send only a byte, that corresponds to the ID of the grid cell clicked. So we have to modify our CNetMessage class, as this byte can be zero, and the program can take the message as an empty string. Therefore, we will make the class CNetMessageApp. Here is the header file, CNetApp.h:
class CNetMessageApp : public CNetMessage {
private:
//Virtual function that indicates how many bytes may have to be loaded onto the object. Overrides the parent class function member to work with only a byte
virtual int NumToLoad();
//Virtual function that indicates how many bytes may have to be downloaded from the object. Overrides the parent class function member to work with only a byte
virtual int NumToUnLoad();
public:
//Function simpler than LoadBytes(), inherited from the parent class, as it only works with one byte
void LoadByte(char);
//Function simpler than UnLoadBytes(), inherited from the parent class, as it only works with one byte
char UnLoadByte();
};
Here comes the implementation, at CNetApp.cpp:
int CNetMessageApp::NumToLoad(){
if (state == EMPTY)
return 1;
else
return 0;
}
int CNetMessageApp::NumToUnLoad() {
if (state == FULL)
return 1;
else
return 0;
}
void CNetMessageApp::LoadByte(char ID) {
charbuf c;
c[0] = ID;
LoadBytes(c, 1);
finish();
}
char CNetMessageApp::UnLoadByte() {
charbuf c;
UnLoadBytes (c);
return c[0];
}
As we can see, very simple. Finally, we can modify the original files of Tim’s TIC TAC TOE game.
At the header file CApp.h, in the client side, we will have to add the following lines:
#include "CNetApp.h"
.
.
.
private:
// Network status indicator
bool Connected;
// Network objects
CClientSocket* tcpclient;
CIpAddress* remoteip;
CNetMessageApp msg;
And, in the server side, we don’t need the help of CIpAddress object, so it can be deleted. However, we have to add a tcp socket that looks for – or, better saying, listen to – clients:
CHostSocket* tcplistener;
And we are able to get into the implementation code. Firstly, let’s set this members when creating Capp object. At CApp.cpp, in the server side, we’ll have to add these lines to the constructor:
tcpclient = NULL;
Connected = false;
And, in the client side,
tcpclient = NULL;
remoteip = NULL;
Now, let’s see CApp_Oninit.cpp. We may have only to create the objects above, except CNetMessage, that is created automatically by CApp. In the server side, the code of CApp::OnInit() will be added by these lines:
tcplistener = new CHostSocket (1234);
if (!tcplistener->Ok())
exit(EXIT_FAILURE);
//Creating client socket
tcpclient = new CClientSocket();
And in the client side,
tcpclient = new CClientSocket ();
remoteip = new CIpAddress ("192.168.0.102", 1234);
Note that the client, in this example, will communicate with a determined machine. In my home network, “192.168.0.102″ will be the server’s address, and “1234″ will be the port we have chosen to use. Otherwise, in a LAN, there is a manner to broadcast a required connection, but this method uses UDP sockets. Thus, someone can use this kind of protocol to get the server address, and, after that, work with TCP sockets to make reliable and simple communication over the network.
On the other hand, that is the code on CApp_OnCleanup – it will destroy these objects:
Server side:
delete tcpclient;
Client side:
delete tcpclient;
Now, we can examine what happens when we click the mouse in our application. At CApp::OnLButtonDown() we’ll change these lines:
SetCell(ID, GRID_TYPE_X);
CurrentPlayer = 1;
}else{
SetCell(ID, GRID_TYPE_O);
CurrentPlayer = 0;
}
}
to these:
CApp_OnEvent – Server side:
if (Connected) {
//Player == 0 will always be Server. Always starts to play and always be "X"
if(CurrentPlayer == 0) {
SetCell(ID, GRID_TYPE_X);
CurrentPlayer = 1;
//Send a message to the client, telling the ID of the clicked cell
msg.LoadByte((char) ID);
tcpclient->Send(msg);
}
}
CApp_OnEvent – Client side:
//CurrentPlayer is not zero, the game is necessarily connected
if(CurrentPlayer != 0) {
SetCell(ID, GRID_TYPE_O);
CurrentPlayer = 0;
//Send a message to the server, telling the ID of the clicked cell
msg.LoadByte((char) ID);
tcpclient->Send(msg);
}
As we can see, no much changes have been made. If it’s not the application’s turn, it does nothing. If it is, the application sets the grid and player status, like it did priorly, and sends a message to the remote application. There’s no need to modify the rendering behavior.
Lastly, we have to write CApp OnLoop() member function. We have to do two things: try to connect, or, if yet connected, receive the messages sent over the network. Below, the code of CApp_OnLoop.cpp, server side:
void CApp::OnLoop() {
//if not connected, listen to the port to detect if there is a client waiting there
if (!Connected) {
if (tcplistener->Accept (*tcpclient)) {
Connected = true;
}
}
else {
//if connected, checks the socket for messages ready to be read
if (tcpclient->Ready()) {
// if there is a message, try to receive it. In case of disconnection, the TCP protocol sends
// a message with no bytes
if (tcpclient->Receive (msg)) {
//if there is a valid message, we can set the grid and player status
SetCell((int) msg.UnLoadByte(), GRID_TYPE_O);
CurrentPlayer = 0;
}
else
Connected = false;
}
}
}
The commented lines say by themselves the program’s flow. On the other side, the client’s code:
if (!Connected) {
if (tcpclient->Connect(*remoteip)) {
if (tcpclient->Ok()) {
Connected = true;
}
}
}
else {
//if connected, checks the socket for messages ready to be read
if (tcpclient->Ready()){
// if there is a message, try to receive it. In case of disconnection, the TCP protocol sends
// a message with no bytes
if (tcpclient->Receive (msg)) {
//if there is a valid message, we can set the grid and player status
SetCell((int) msg.UnLoadByte(), GRID_TYPE_X);
CurrentPlayer = 1;
}
else {
Connected = false;
}
}
}
}
Then we have reached the end of this tutorial. I hope you enjoyed it, and feel free to use and modify the library shown here.
SDL Net Tutorial Files:
sdl-net-clientfiles.tar
sdl-net-commonfiles.tar
sdl-net-serverfiles.tar
Did you like this tutorial/blog post? Feel free to donate to keep more comin', and have more contests.
Ok, that’s my fault – again.
On the contrary I’ve said in the first part of the tutorial, SDL_Init(SDL_INIT_EVERYTHING) do not initialize SDL NET. According to the documentation, the subsystems started by this flag are:
SDL_INIT_TIMER The timer subsystem
SDL_INIT_AUDIO The audio subsystem
SDL_INIT_VIDEO The video subsystem
SDL_INIT_CDROM The cdrom subsystem
SDL_INIT_JOYSTICK The joystick subsystem
Therefore, you need to add CNet::Init() and CNet::Quit() to CApp_OnInit.cpp and CApp_OnCleanup.cpp files, respectively.
However, linux treats network initialization in a different way than Windows. I tested the host application in a linux workstation, and all gone well. For this I was trapped.
So it’s not enough to remember that, besides SDL is platform independent, we have always to test our programs in the various system and configurations we want they will run.
And that’s what I’ll do. After that, I will rewrite this tutorial.
obsrv,
Probably, the network security policy of your system is blocking your workstation to act as a server. Unfortunately, I don’t have a windows machine at the moment to test this hypothesis. But you can lay down the firewall, temporarily, of course , and check if the program works.
I noticed stderr.txt is full of:
SDLNet_TCP_Open: Couldn’t create socket
I read that response but altering the code did not help.
Please, see the response I wrote at “SDL Net – Part 1″ tutorial.
If you don’t want to update that line at CNet.cpp, you can download the files again, as I think Tim has already uploaded the fixed files.
I have compiled SDL Net Tutorial Files but when I run server executable a window of SDL appears for half-second and dissapears. Client executable works fine. Maybe there is problem with code?