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.



In this tutorial, I will show you a simple c++ library to add network resources to your game.

Firstly, we have to add the SDL_net headers and libraries to your desktop environment. If you are using a linux distro like Ubuntu, it is enough to download the libsdl-netx.x-dev package from the repositories. If not, the process is analogous of including sdl mixer, image, etc, better explained by Tim's Tutorials. Don't forget, as well, to set the linker options in your IDE, adding the SDL_net library.

So, let's create our two files, CNet.h and CNet.cpp, that will build our framework that we'll help us at making network games. It's necessary to say that this library, for the time being, only works with TCP/IP protocol.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cstring>
#include "SDL_net.h"

typedef char charbuf [256];

class CNet{
  public:
    static bool Init(); //Initialize SDL_net
    static void Quit(); //Exit SDL_net
};
Here is our header file, and, after the includes and the charbuf declaration, our first class. Its aim is initialize and end the network environment, with two static functions. However, they are unnecessary, if we're using SDL_Init(SDL_INIT_EVERYTHING).
class CNetMessage {
  protected:
    charbuf buffer; //message stored in buffer, limited to 256 bytes
    enum bufstates { //message states
    EMPTY,
    READING,
    WRITING,
    FULL };
    bufstates state; //the message state
    void reset(); // reset message: fulfill it with zeroes and change its state to EMPTY
  public:
    CNetMessage(); //constructor
//Virtual member function that indicates how many bytes may have to be loaded into the instance. By default, it works with strings that have a maximum
//of 255 characters.
    virtual int NumToLoad();

//Virtual member function that indicates how many bytes may have to be downloaded from the instance.
    virtual int NumToUnLoad();

    void LoadBytes(charbuf& inputbuffer, int n); //Load a char set into the message buffer
    void UnLoadBytes(charbuf& destbuffer); //Unload a char set from the buffer
    void finish(); //set the state object to full. No more data to be loaded
};
The class above provides the needed interface for the application to deal with the packets sent through the network. It's limited to a maximum of 256 bytes.

Regardless this class uses a buffer with a fixed length, the packets sent through the network can be much more smaller, according to the program's needs. In our TIC TAC TOE game, only one byte is sent. To work with this customization, we put some message states, to help the framework user to build a derived class.

The LoadBytes and UnloadBytes member functions load and unload in a buffer the wanted chars. They were determined by the virtual functions NumToLoad and NumToUnload, each of which indicates how many bytes have to be loaded or unloaded. By default, this class is able to work with strings that have a maximum of 255 characters. If you want to work with other data structures, as we'll do at the game making, you may override this member function.

Last, the finish() member function, which sets the class ready to be used. So, we can send through the socket, in the same packet, a message with two different data structures, avoiding unnecessary network pollution. Below, the implementation code of this and the latter classes:
#include "CNet.h"

bool CNet::Init() {
    if (SDLNet_Init() < 0) {
        std::cerr << "SDLNet_Init: " << SDLNet_GetError() << std::endl;
          return false;
        }
        else
            return true;
}

void CNet::Quit() {
    SDLNet_Quit();
}


CNetMessage::CNetMessage() {
    reset();
}

void CNetMessage::reset() {
    for (int i =0; i < 256; i++)
        buffer [i] = 0;
    state = EMPTY;
}

void CNetMessage::finish() {
    if (state == READING)
        state = FULL;
}

int CNetMessage::NumToLoad() {
    if (state == EMPTY)
        return 255;
    else
        return 0;
}

int CNetMessage::NumToUnLoad() {
    if (state == FULL)
        return strlen(buffer) + 1;
    else
        return 0;
}

void CNetMessage::LoadBytes(charbuf& inputbuffer, int n) {
    for (int i = 0; i < n; i++)
        buffer[i] = inputbuffer[i];
    state = READING;
}

void CNetMessage::UnLoadBytes(charbuf& destbuffer) {
    for (int i=0; i < this->NumToUnLoad(); i++)
        destbuffer[i] = buffer[i];
    reset();
}
Now, back to the header file. Let's take a look at the CIpAddress class:
class CIpAddress {
  private:
    IPaddress m_Ip; //the IPaddress structure
  public:
    CIpAddress(); //constructor
    CIpAddress (Uint16 port); //create and associate a port to the instance
    CIpAddress (std::string host, Uint16 port); //create and associate a port and a host to the instance
    void SetIp (IPaddress sdl_ip); //set a CIpAddress object from an existing SDL IPaddress
    bool Ok() const; //True if the object have a port and a host associated to it
    IPaddress GetIpAddress() const; //return a SDL_net IPaddress structure
    Uint32 GetHost() const; //return the host
    Uint16 GetPort() const; //return the port
};
Basically, that is a C++ interface for the SDL NET structure IPaddress. It has informations about the host IP address, and the port which the program is associated to. Here is the definition of the class members, at CNet.cpp:
CIpAddress::CIpAddress (Uint16 port) {
    if (SDLNet_ResolveHost(&m_Ip, NULL, port) < 0){
        std::cerr << "SDLNet_ResolveHost: " << SDLNet_GetError() << std::endl;
        m_Ip.host = 0;
        m_Ip.port = 0;
    }
}


CIpAddress::CIpAddress (std::string host, Uint16 port) {
    if (SDLNet_ResolveHost(&m_Ip, host.c_str(), port) < 0) {
        std::cerr << "SDLNet_ResolveHost: " << SDLNet_GetError() << std::endl;
        m_Ip.host = 0;
        m_Ip.port = 0;
    }
}

CIpAddress::CIpAddress() {
    m_Ip.host = 0;
    m_Ip.port = 0;
}

bool CIpAddress::Ok() const {
    return !(m_Ip.port == 0);
}

void CIpAddress::SetIp (IPaddress sdl_ip) { //sets a CTcpSocket object from a existing SDL socket
    m_Ip = sdl_ip;
}

IPaddress CIpAddress::GetIpAddress() const {
    return m_Ip;
}

Uint32 CIpAddress::GetHost() const {
    return m_Ip.host;
}

Uint16 CIpAddress::GetPort() const {
    return m_Ip.port;
}
Again, back to header, let's analyze CTcpSocket, a framework to the SDL NET TCP socket structure. In a few words, a socket is a connection, a "pipe" between two computers, that delivers messages to them, in and out. In a TCP socket, there are necessarily two fixed computers in each pipe's end, and messages always are got in the right order that they have sent.
class CTcpSocket {
  protected:
    TCPsocket m_Socket; //the TCP socket structure
    SDLNet_SocketSet set; //a set of sockets. Used here only to check existing packets
  public:
    CTcpSocket();
    virtual ~CTcpSocket();
    virtual void SetSocket (TCPsocket  the_sdl_socket); //set a CTcpSocket object from a existing SDL socket
    bool Ok() const; //indicate if theres is a TCPsocket associated to the instance
    bool Ready() const; //True if there are bytes ready to be read
    virtual void OnReady(); //pure virtual
};
In the code above, the member function Ready() plays an important role: it checks whether there is or not activity in the socket, freeing the program's flow to do what they have to do, without waiting messages from the other side of the pipe. Below, the code in CNet.cpp:
CTcpSocket::CTcpSocket() {
    m_Socket = NULL;
    set = SDLNet_AllocSocketSet(1);
}

CTcpSocket::~CTcpSocket() {
    if (!(m_Socket == NULL)) {
          SDLNet_TCP_DelSocket(set,m_Socket);
        SDLNet_FreeSocketSet(set);
        SDLNet_TCP_Close (m_Socket);
    }
}

void CTcpSocket::SetSocket (TCPsocket the_sdl_socket) {
    if (!(m_Socket == NULL)) {
          SDLNet_TCP_DelSocket(set,m_Socket);
          SDLNet_TCP_Close (m_Socket);
      }
    m_Socket = the_sdl_socket;
    if(SDLNet_TCP_AddSocket(set,m_Socket)==-1) {
        std::cerr << "SDLNet_TCP_AddSocket: " << SDLNet_GetError() << std::endl;
        m_Socket = NULL;
    }
}

bool CTcpSocket::Ok() const {
    return !(m_Socket == NULL);
}

bool CTcpSocket::Ready() const {
    bool rd = false;
    int numready = SDLNet_CheckSockets(set, 0);
    if (numready == -1)
            std::cerr << "SDLNet_CheckSockets: " << SDLNet_GetError() << std:: endl;
      else
            if (numready)
                rd = SDLNet_SocketReady (m_Socket);
      return rd;
}

void CTcpSocket::OnReady() {

}
From this class, we will derive CHostSocket, according to the piece of code of CNet.h:
class CClientSocket;


class CHostSocket : public CTcpSocket {
  public:
    CHostSocket (CIpAddress& the_ip_address); //create and open a new socket, with an existing CIpAddress object

    CHostSocket (Uint16 port); //create and open a new socket with the desired port
    bool Accept (CClientSocket&); //set a client CTcpSocket object after listening to the port
    virtual void OnReady(); //pure virtual
};
A host socket is a socket whose utility is listen to a certain port, to check whether there is someone wanting to connect to the service provided by the program, in the machine it runs. If found, we have to use the member Accept to associate another socket to the client. Here is the implementation:
CHostSocket::CHostSocket (CIpAddress& the_ip_address) {
    CTcpSocket::CTcpSocket();
    IPaddress iph = the_ip_address.GetIpAddress();
    if (!(m_Socket = SDLNet_TCP_Open(&iph))) {
            SDLNet_FreeSocketSet(set);
            std::cerr << "SDLNet_TCP_Open: " << SDLNet_GetError() << std::endl;
    }
}

CHostSocket::CHostSocket (Uint16 port) {
    CIpAddress iplistener (port);
    if (!iplistener.Ok()) {
        m_Socket = NULL;
    }
    else {
        CTcpSocket::CTcpSocket();
        IPaddress iph = iplistener.GetIpAddress();
        if (!(m_Socket = SDLNet_TCP_Open(&iph))) {
            SDLNet_FreeSocketSet(set);
            std::cerr << "SDLNet_TCP_Open: " << SDLNet_GetError() << std::endl;
        }
    }
}


bool CHostSocket::Accept (CClientSocket& the_client_socket) {
    TCPsocket cs;
    if ((cs = SDLNet_TCP_Accept(m_Socket))) {
        the_client_socket.SetSocket(cs);
        return true;
    }
    else
        return false;
}

void CHostSocket::OnReady() {

}
So, a tcp socket client is a socket whose aim is to communicate with a remote host, who, in turn, is using a tcp client to send and receive messages too. Below, the header file for this class:
class CClientSocket : public CTcpSocket {
  private:
    CIpAddress m_RemoteIp; //the CIpAddress object corresponding to the remote host
  public:
    CClientSocket(); //constructor
    CClientSocket (std::string host, Uint16 port); //Create the object and connect to a host, in a given port
    bool Connect (CIpAddress& remoteip); //make a connection to communicate with a remote host
    bool Connect (CHostSocket& the_listener_socket); //make a connection to communicate with a client
    void SetSocket (TCPsocket  the_sdl_socket); //set a CTcpSocket object from an existing SDL_net socket
    CIpAddress getIpAddress () const; //return a CIpAddress object associated to the remote host
    virtual void OnReady(); //pure virtual
    bool Receive(CNetMessage& rData); //receive data and load it into a CNetMessage object
    bool Send (CNetMessage& sData); //send data from a CNetMessage object
};
The same way CHostSocket is, this class is derived from CTcpSocket, and uses the constructor or Connect() to make a connection to the host. If so, the object uses the Send() and Receive() member functions to throw and get messages through the network. Below, the implementation:
CClientSocket::CClientSocket() {
    CTcpSocket::CTcpSocket();
}

CClientSocket::CClientSocket (std::string host, Uint16 port) {
    CIpAddress remoteip (host.c_str(), port);
    if (!remoteip.Ok()) {
        m_Socket = NULL;
    }
    else {
        CTcpSocket::CTcpSocket();
        Connect(remoteip);
    }
}

bool CClientSocket::Connect (CHostSocket& the_listener_socket) {
    return false;
}

bool CClientSocket::Connect (CIpAddress& remoteip) {
    TCPsocket cs;
    IPaddress ip = remoteip.GetIpAddress();
    if ((cs = SDLNet_TCP_Open(&ip)))
    {
        SetSocket(cs);
        return true;
    }
    else {
        std::cerr << "SDLNet_TCP_Open: " << SDLNet_GetError() << std::endl;
        return false;
    }
}

void CClientSocket::SetSocket (TCPsocket the_sdl_socket) {
    CTcpSocket::SetSocket (the_sdl_socket);
    IPaddress* ips;
    if ((ips = SDLNet_TCP_GetPeerAddress(m_Socket))) {
    /* Print the address, converting it onto the host format */
        m_RemoteIp.SetIp(*ips);
        Uint32 hbo = m_RemoteIp.GetHost();
        Uint16 pbo = m_RemoteIp.GetPort();
        std::cout << "Client connected: " << SDLNet_Read32(&hbo) << ' '
            << SDLNet_Read16 (&pbo) << std::endl;
    }
    else
        std::cerr << "SDLNet_TCP_GetPeerAddress: " << SDLNet_GetError() << std::endl;
}

CIpAddress CClientSocket::getIpAddress () const {
    return m_RemoteIp;
}

bool CClientSocket::Receive(CNetMessage& rData) {
//Firstly, check if there is a socket
    if (m_Socket == NULL)
        return false;
    charbuf buf;

//Check if the instance can receive bytes, if it can, load the number of bytes specified by NumToLoad() virtual function
    while (rData.NumToLoad() > 0) {
        if (SDLNet_TCP_Recv(m_Socket, buf, rData.NumToLoad()) > 0) {
            rData.LoadBytes (buf, rData.NumToLoad());
        }
        else {
            return false;
        }
    }
    rData.finish();
    return true;
}

bool CClientSocket::Send (CNetMessage& sData) {
//check if there is a socket
    if (m_Socket == NULL)
        return false;
    charbuf buf;
    int len;

//Check if the instance can send bytes, if it can, unload the number of bytes specified by NumToLoad() virtual function
    while ((len = sData.NumToUnLoad()) > 0) {
        sData.UnLoadBytes (buf);
        if (SDLNet_TCP_Send(m_Socket, (void *)buf, len) < len) {
            std::cerr << "SDLNet_TCP_Send: " << SDLNet_GetError() << std::endl;
            return false;
        }
    }
    return true;
}

void CClientSocket::OnReady() {

}
So that, we have finished the first part of our tutorial. Next, we'll change the TIC TAC TOE source code to add playability over a network.