This document explains how to write network games with ClanLib's network model:
The lowest level is CL_Socket. This is platform independent version of the system level socket functions, encapsulated in a class for your convience. A simple example:
/* Gets clanlib.org's index.html page: */
try
{
std::string request_msg = "GET index.html HTTP/1.0\n\n";
CL_Socket sock(CL_Socket::tcp);
sock.connect(CL_IPAddress("clanlib.org", "80"));
sock.send(request_msg);
while (true)
{
char buffer[16*1024+1];
int received = sock.recv(buffer, 16*1024);
if (received == 0) break; // end of stream. server closed connection.
buffer[received] = 0;
std::cout << buffer;
}
std::cout << std::endl;
}
catch (CL_Error err)
{
std::cout << "Why wont things never work as planned? " << err.message.c_str() << std::endl;
}
The CL_BufferedSocket class is not completely implemented and should not be used. I'm not even anymore sure it was a good idea and it might be pending for removal.
Additionally to this, CL_Socket can use the CL_EventTrigger and CL_EventListener to wait for socket data. A small example:
void wait_for_socket_data(CL_Socket &sock, int timeout)
{
CL_EventTrigger *read_trigger = sock.get_read_trigger();
read_trigger->wait(timeout);
}
void wait_for_sockets(std::list<CL_Socket> &sockets, int timeout)
{
CL_EventListener listener;
std::list<CL_Socket>::iterator it;
for (it = sockets.begin(); it != sockets.end(); it++)
{
CL_Socket &sock = *it;
listener.add_trigger(sock.get_read_trigger();
}
listener.wait(timeout);
}
CL_OutputSource_Socket is a CL_OutputSource compatible wrapper for CL_Socket. It can be mixed with CL_Socket since the CL_Socket class reference counts the handle to the system level socket. A small example of its usage:
void send_init_handshake(CL_Socket &sock)
{
CL_OutputSource_Socket output(sock);
// output.set_big_endian_mode(); // ha! as if that actually worked...
output.write_string("Yo dude! You've entered the 1337 socket owned by Cl4nL1b");
output.write_int(42);
}
You will find a similar CL_InputSource_Socket for reading from sockets.
That was the lowest level socket support. It doesnt do much except save you from the trouble of setting up some annoying C structs and filling them with data.
ClanLib features a networking engine called NetSession. It is built on top of the lower level socket interface in ClanLib, so its all up to the game developer which level API is prefered. The NetSession engine provides the following core features:
So what is a CL_NetSession exactly?
When a netsession is initially constructed, it will not listen for incoming computers on any ports, nor will it connect to any remote system. It can become a server, a client or any kind of combination you prefer.
There are two ways a new computer can enter a netsession. Either CL_NetSession::start_listen() is called, making it accept connecting computers on the specified port, or CL_NetSession::connect() is called, making the netsession connect to an other computer.
There is nothing that prevents you from mixing those two calls. Eg. to have a Peer To Peer network model, or to make servers connect to other servers. Its also no problem to make a client connect to two different servers at the same time, or to disconnect from them again.
// Connect to a server
CL_NetSession netsession("MyGame");
CL_NetComputer server = netsession.connect(CL_IPAddress("myserver.coolgames.com", "4322"));
...
// Start a server which listens to incoming connections:
CL_NetSession netsession("MyGame");
slots.connect(netsession.sig_computer_connected(), this, &Server::on_connect);
slots.connect(netsession.sig_computer_disconnected(), this, &Server::on_disconnect);
netsession.start_listen("4322");
...
void Server::on_connect(CL_NetComputer &computer)
{
std::cout << "A computer connected from " << computer.get_address().get_address() << std::endl;;
}
void Server::on_disconnect(CL_NetComputer &computer)
{
std::cout << "Computer " << computer.get_address().get_address() << "disconnected" << std::endl;
}
When a computer enters the system, it is represented by a CL_NetComputer handle. The signal CL_NetSession::sig_computer_connected() is emitted when a new computer enters the system.
If the incoming computer is already known to the system, CL_NetSession::sig_computer_reconnected() is emitted instead. A reconnecting computer can only be recognized if not all original CL_NetComputer handles to it has been destroyed. This allows the application to control for how long time an earlier computer can be recognized by the system. For instance, if a game wants to remember old computers for ten minutes, it could store the CL_NetComputer handle when being emitted by CL_NetSession::sig_computer_disconnected(). After the 10 minutes timeout, it just need to destroy the instance it kept, and the netsession will forget about the previous connected computer.
To send a message to an other computer, t