Using sockets

Introduction

In this tutorial you will learn the basics of network programming with sockets, and you will be able to exchange data between two distant computers.

What is a socket ?

For those who are new to network programming, sockets are the most important thing to know. Almost everything (at a low level) relies on sockets. A sockets can be seen as a pipe between two computers, and once you've setup such a pipe, you can start exchanging data.

A lot of protocols exist to communicate on the network, and each protocol has several types of sockets. But all you have to know is the internet protocol (IP), with TCP and UDP types of sockets ; this is what almost every network software uses, and this is what SFML uses in its network package.

TCP and UDP sockets are quite different, here is why :

To sum up, TCP sockets are safer but slower. There are only a few cases that require UDP sockets : intensive real-time applications like RPG/FPS/RTS games, or broadcasting (sending data to every computer of a sub-network).

Handling network addresses

Before starting using sockets, let's explain one useful utility classe included in the network package : sf::IPAddress. It wraps an IP address (v4) and provides useful functions for initialization, comparison, etc. As network programming involves communications with other computers, you'll need this class each time you connect / send / receive.

First of all, you have to include the header for the network package. As usual, it will include all the headers needed to use the network classes.

#include <SFML/Network.hpp>

You can initialize a sf::IPAddress from different sources :

sf::IPAddress Address1;                  // By default : invalid address
sf::IPAddress Address2("192.168.0.1");   // From a string representation
sf::IPAddress Address3("computer_name"); // From a host name
sf::IPAddress Address4(192, 168, 0, 1);  // From 4 bytes
sf::IPAddress Address5 = sf::IPAddress::LocalHost; // 127.0.0.1 -- your own computer

You can also get your public IP address, either from the local network point of view or from the internet :

// Your address in the local area network (like 192.168.1.100 -- the one you get with ipconfig)
sf::IPAddress Address6 = sf::IPAddress::GetLocalAddress();

// Your address in the world wide web (like 83.2.124.68 -- the one you get with www.whatismyip.org)
sf::IPAddress Address7 = sf::IPAddress::GetPublicAddress();

Please note that GetPublicAddress() is very slow : the only way to get a public address is to get it from the outside (especially when you are behind a proxy or a firewall), so it connects to an external website (www.whatismyip.org) and parse the returned web page to extract the public IP address. So, don't use it too much.

You can get the string representation (in the form "xxx.xxx.xxx.xxx") of an address with its ToString() function :

sf::IPAddress Address("computer_name");
std::string IP = Address.ToString();

You can also check the validity of an address with its IsValid() function :

sf::IPAddress Address("computer_name");
if (Address.IsValid())
    // Ok, host found
else
    // Invalid address

Using a UDP socket

UDP sockets are connectionless, so they are the easiest ones to use. No connection is needed, all you have to do is sending and receiving data. The only step required is to bind the socket to a port before receiving data.

SFML sockets allow sending raw data, defined by a pointer to a byte array and its size :

// Create the UDP socket
sf::SocketUDP Socket;

// Create bytes to send
char Buffer[] = "Hi guys !";

// Send data to "192.168.0.2" on port 4567
if (Socket.Send(Buffer, sizeof(Buffer), "192.168.0.2", 4567) != sf::Socket::Done)
{
    // Error...
}

Here, we send our byte array (containing "Hi guys !") to the computer which IP address is 192.168.0.2, on port 4567. The second parameter is a sf::IPAddress, so you could as well use a network name, a broadcast address or whatever type of address.
If you don't want to use a specific port, you can just take any free port number between 1024 and 65535 (ports less than 1024 are reserved). And of course, make sure that your firewall is not blocking this port !

The Send function, as well as all other functions that can block, returns a socket status (see sf::Socket::Status) which can be :

Receiving data is exactly the same, except you first need to bind the socket to a port before receiving data from this port.

// Create the UDP socket
sf::SocketUDP Socket;

// Bind it (listen) to the port 4567
if (!Socket.Bind(4567))
{
    // Error...
}

Then you can receive a byte array, its size, and the address / port of the sender.

char Buffer[128];
std::size_t Received;
sf::IPAddress Sender;
unsigned short Port;
if (Socket.Receive(Buffer, sizeof(Buffer), Received, Sender, Port) != sf::Socket::Done)
{
    // Error...
}

// Show the address / port of the sender
std::cout << Sender << ":" << Port << std::endl;

// Show the message
std::cout << Buffer << std::endl; // "Hi guys !"

Please note that Receive is blocking, meaning that it won't return until it has received something if the socket is in blocking mode (which is the default).

When you don't need the socket anymore, you have to close it (the destructor won't do it for you !) :

Socket.Close();

Using a TCP socket

TCP sockets must be connected before sending or receiving data. Here is how it works.

First, you open a socket and make it listen to a given port.

sf::SocketTCP Listener;
if (!Listener.Listen(4567))
{
    // Error...
}

Then, you can wait for connections on this port.

sf::IPAddress ClientAddress;
sf::SocketTCP Client;
if (Listener.Accept(Client, &ClientAddress) != sf::Socket::Done)
{
    // Error...
}

The Accept function will wait until an incoming connection arrives, and return a new socket that will be used to exchange data with the connected computer. If you pass a sf::IPAddress to the function, it will be filled with the client's address (so you can know who is connected).
If the socket is in non-blocking mode, the function will return immediatly if there's no connection arriving, with the status sf::Socket::NotReady.

Let's now have a look at the client's code. All you have to do is creating a socket and connect to the server on the port that he's listening to.

sf::SocketTCP Client;
if (Client.Connect(4567, "192.168.0.2") != sf::Socket::Done)
{
    // Error...
}

Now, both the client and the server are ready to communicate. The functions for sending and receiving data are the same as UDP sockets, except that you don't need to give the port and the host address. So basically, the only parameters are the array to send / receive and its size.

char Buffer[] = "Hi guys !";
if (Client.Send(Buffer, sizeof(Buffer)) != sf::Socket::Done)
{
    // Error...
}
char Buffer[128];
std::size_t Received;
if (Client.Receive(Buffer, sizeof(Buffer), Received) != sf::Socket::Done)
{
    // Error...
}

For TCP sockets, the Send and Receive functions can return sf::Socket::Disconnected, which means the socket has been disconnected.

As for UDP sockets, don't forget to close the socket when you have finished.

Conclusion

SFML provides a low-level but easy to use set of classes for exchanging data through the network. But don't forget that network programming is not easy, and you will have to use efficient algorithms and data structures to build robust and fast programs. I advise you to read some good tutorials about network programming (not about sockets, but about techniques and general algorithms), especially if you are new to network programming.

In the next tutorial, we'll see another utility class that allow easier data manipulation through the network.