Libnet HOWTO


About this document

This document is a introductory tutorial for using the networking library Libnet http://www.canvaslink.com/libnet/, by George Foot et al. It is designed supplement the Libnet documentation, as some people have trouble figuring out where to start. Most of it was lifted from the Libnet documentation, which is very good anyway.

The difference is that this text is designed to be read from start to finish, like a book. It is organised into chapters, with a small example at the end of each to demonstrate the functions described. It is hoped that this rearrangement will make it easier to learn the Libnet API. Once you have read this, you should consult the Libnet documentation for reference. Also, many aspects of Libnet will be completely missed by this tutorial.

As always, suggestions and improvements are welcome. Questions about this tutorial are also welcome, but questions about Libnet itself should be sent to the Libnet mailing list.

My email address is tjaden(at)users.sourceforge.net

Note: You are reading the first draft. Right now, this document is probably pretty worthless as a tutorial. Please help it improve by sending me any questions you want answered, or which parts you think require more explaination, and whatever else you can think of.

Note: You are reading the HTML version, which has a few problems with cross-references. Perhaps a newer version of `texi2html' will fix this.

Introduction to Libnet

Libnet is a low-level networking library, designed in particular for adding network support to games. In essence, it is one large abstraction layer, sitting atop the networking capabilities of the operating system.

The question is then, why use Libnet if it just wraps around other libraries? The simplest answer is, of course, that it is cross-platform. If you write a cross-platform program under one operating system, you can port it easily to another operating system. If you do not care about that, you are an idiot, and you should not be reading this.

Another reason is that Libnet can provide one single programming interface to many different types of network interfaces. For example, right now there are drivers for networking over the Internet, IPX networks, serial ports, as well as a special "localhost" driver. Others can be added quite easily.

Using Libnet

Before we can use Libnet, there are some things to know.

I will assume Libnet is compiled and installed properly on your system.

Initialising

Before we can use Libnet, we must initialise it.

Function: int net_init (void)
This function initialises the library, and should be called before any others. It returns zero on success.

Usually, we want to load some configuration data as well.

Function: int net_loadconfig (const char *filename)
Loads configuration data from a file.

filename can be NULL, a directory name or a filename (with or without an explicit directory).

If filename is NULL, the file `libnet.cfg' is read from the program's home directory (as in argv[0]). If filename is a directory then the file `libnet.cfg' is loaded from that directory. If filename names a file, that file is loaded.

The function returns zero on success.

Libnet supports many network drivers. There are fairly complicated ways to initialise the drivers you want to use (which I will not describe). The simplest way, however, is to call:

net_detectdrivers (net_drivers_all);
net_initdrivers (net_drivers_all);

This will detect all the network drivers that available for use on your particular system, and initialise them all, which is what you usually want to do.

Shutting down

net_init installs an exit function, so you do not usually need to shut Libnet down explicitly. However, if you want to reinitialise it for any reason, you must call net_shutdown before calling net_init again.

Function: int net_shutdown (void)
Shuts everything down nicely, closing any open channels and shutting down all initialised drivers.

Chapter example

#include <libnet.h>

int main ()
{
    /* Errors in initialisation are very unlikely,
     * so we don't check for it.  */
    net_init ();
    net_loadconfig (NULL);

    /* Detect and initialise drivers.  */
    net_detectdrivers (net_drivers_all);
    net_initdrivers (net_drivers_all);

    /* Do nothing.  */

    /* Quit.  */
    return 0;
}

Channels

Communications channels in Libnet are unreliable connections between two machines. This means that a message sent through a channel may be lost during transmission, be duplicated, or arrive in incorrect order. Despite all that, there are many cases where channels will be sufficient.

Opening a channel

Channels are represented with opaque objects of type NET_CHANNEL, and are created with the following function:

Function: NET_CHANNEL * net_openchannel (int type, char *binding)
Opens a communications channel.

type is one of the NET_DRIVER_* constants, and determines the network driver you want to use. You may use hard-coded constants, or the network driver classes system (recommended, but you'll need to look in the `classes.c' example, as it is not documented yet).

binding determines the local binding for the channel. This requires some explanation. It can take three types of arguments:

If you find all that confusing, the analogy in the Libnet documentation may help. Here it is again, in less accurate but less confusing terms:

Imagine you have just received an email account at your new ISP. Other people cannot contact you yet, because they do not know your email address. You must send the first message. Once you have done that, the receiver of the message may reply by looking at the From header in the email message. This is like the first case above.

That is fine if you know the other person's address, but what if nobody knows anyone else's email address? The answer to this is to have a "default" address. If you don't know where to send a message, then the default address is a good guess. This is like the second case.

The analogy is quite broken already, so I will not try to explain the third case here. Don't worry, you'll hardly ever use it.

The function returns a pointer to the NET_CHANNEL struct it creates, or NULL on error.

Assigning a target

When a channel is opened, it does not point anywhere. If you try and send data through the channel, it will not know where it it supposed to go. The following function assigns a target for a channel:

Function: int net_assigntarget (NET_CHANNEL *channel, char *target)
Sets the target of the given channel.

channel is the channel whose target address needs changing. target is the new target address. The format of target depends on the network type being used by the channel.

Returns zero on success, non-zero on error (i.e. address in wrong format). A zero return does not indicate that the target can necessarily be reached.

Example

NET_CHANNEL *chan = net_openchannel (NET_DRIVER_WSOCK, NULL);
net_assigntarget (chan, "127.0.0.1:12345");

Getting the local address

Sometimes it is useful to get the local address of a channel. The following function will do that:

Function: char * net_getlocaladdress (NET_CHANNEL *channel)
Returns the local address of channel.

Note that "local address" means the address of the channel according to this computer. For example, the Internet sockets drivers have a bit of trouble with this, since a computer can have more than one IP address and it's not trivial to find out even one of these.

Because of all this, it's probably best to tell the user this local address and let them figure out what the other computer should use.

Sending data

Once the channel is opened and a target is assigned, we can now send some data.

Function: int net_send (CHANNEL *channel, void *buffer, int size)
Sends some data through a channel.

channel is the channel to send the data through. buffer points to the data to send. size is the size of the data in bytes. It returns zero on success, but this does not mean that the data was received on the remote side.

Receiving data

We need a way to check if there is data waiting to be received on a particular channel. The following function does just that:

Function: int net_query (CHANNEL *channel)
Returns non-zero if there is data pending in channel.

We also need a way to actually receive the data.

Function: int net_receive (CHANNEL *channel, void *buffer, int maxsize, char *from)
Receive some data from a channel.

channel is the channel to receive from. buffer is a buffer to hold the data, of length maxsize. If from is not NULL, the address of the source of the data will be stored in the buffer it points to (which should be able to hold NET_MAX_ADDRESS_LENGTH characters).

Returns the number of bytes received. 0 means there was no data to read. -1 indicates that an error occured.

Closing a channel

When you are done with a channel, you must close it.

Function: int net_closechannel (NET_CHANNEL *channel)
Closes the specified channel.

Channels example

/* Demonstrates the channel functions.  This example is
 * a lot like one found in the Libnet package.  */

#include <stdio.h>
#include <string.h>
#include <libnet.h>

/* Use the driver of your choice.
 * e.g. NET_DRIVER_WSOCK_WIN under Windows.  */
#define DRIVER  NET_DRIVER_SOCKETS

int main (int argc, char *argv[])
{
    NET_CHANNEL *chan;
    char *binding;
    int server = 0;
    char buf[256];
    
    /* Search command-line arguments for a "-s" option.  If 
     * found then we will run as the server, otherwise as the
     * client.  (Not that it makes much difference here).  */
    for (argv++; *argv; argv++)
        if (strcmp (*argv, "-s") == 0)
            server = 1;

    net_init ();
    net_loadconfig (NULL);
    
    net_detectdrivers (net_drivers_all);
    net_initdrivers (net_drivers_all);

    /* If we are the server we want to have the "default"
     * address (i.e. binding should be the empty string).
     * If we are the client, our address doesn't matter
     * (i.e. the binding should be NULL).  */
    if (server)
        binding = "";
    else
        binding = NULL;

    /* Open the channel.  */
    chan = net_openchannel (DRIVER, binding);
    if (!chan) {
        puts ("Error opening channel.");
        return 1;
    }

    /* Show the local address.  */
    printf ("Local address: %s\n", net_getlocaladdress (chan));

    /* Assign a target.  */
    puts ("Enter target address:");
    fgets (buf, sizeof buf, stdin);
    if (net_assigntarget (chan, buf) != 0) {
        puts ("Could not use that address; quitting.");
        return 1;
    }
    
    puts ("Enter text to send.  Enter `.' to quit.");
    
    while (1) {
        /* Check if the remote side sent us any messages.  */
        while (net_query (chan))
            /* If so, receive them and print them out.  */
            if (net_receive (chan, buf, sizeof buf, NULL) > 0)
                printf ("Received: %s\n", buf);

        /* Let user enter something.  */
        if (!fgets (buf, sizeof buf, stdin))
            break;
        buf[strlen (buf) - 1] = 0;   /* strip newline */

        /* Quit condition.  */
        if (strcmp (buf, ".") == 0)
            break;

        /* Send the text if it is not a blank line.  */
        if (buf[0])
            net_send (chan, buf, strlen (buf) + 1);
    }

    /* Close the channel.  */
    net_closechannel (chan);

    /* Quit.  */
    return 0;
}

Conns

Conns are like channels, but they are reliable. Messages will reach the destination precisely once, and in the correct order. If a cable is disconnected, however, there's nothing we can do about it.

Opening a conn

Like channels, we must open conns before we can use them. Conns are represented by the opaque data type NET_CONN.

Functions: NET_CONN * net_openconn (int type, char *binding)
Opens a conn over the specified network type.

type is the type of the network to use. binding can determine the local binding. @xref{bindings, , net_openchannel}.

Returns a pointer to the NET_CONN struct created, or NULL on error.

Connecting conns

Unlike channels, conns have a defined connection process. You cannot send data from one conn to another until they have gone through the handshaking process, and afterwards cannot connect to another conn without disconnecting first.

The connection process is very different for servers and clients, so they will be discussed separately.

The server

The server is the one waiting for clients to connect to it. To do this, we open a new conn, then set it up to be a listening conn, using this function:

Function: int net_listen (NET_CONN *conn)
Makes a conn start listening (waiting for connection attempts). Only works on an idle conn. Returns zero on success, nonzero otherwise.

We should check regularly if a client is trying to contact us through this listening conn, using this function:

Function: NET_CONN * net_poll_listen (NET_CONN *conn)
Polls a listening channel for incoming connections. If there are any, this function accepts the first one queued and creates a new conn to talk to the connecting computer. If a new conn is created, it is returned. Otherwise NULL is returned.

Once the new conn is returned, we can begin to send and receive data through it. The listening conn will continue to listen for new connections.

The client

The client needs to contact the listening conn of the server, using the following functions:

Function: int net_connect (NET_CONN *conn, char *addr)
Initiates a connection attempt.

conn is the conn to connect; addr is the target address (which is network driver dependent).

Returns zero if successful in initiating; non-zero otherwise. If the return value is zero, the app should keep calling net_poll_connect until a connection is established or refused, or until the app gets bored.

Function: int net_poll_connect (NET_CONN *conn)
Polls a connecting conn to monitor connection progress.

Returns zero if the connection is still in progress, nonzero if the connection process has ended. A nonzero return value is either positive (connection established) or negative (connection not established).

Sending data

Once the connection process is complete, data can be sent with the following function.

Note: RDM stands for "Reliably Delivered Message".

Function: int net_send_rdm (NET_CONN *conn, void *buffer, int size)
Sends data down a conn. Analogous to @xref{net_send}.

Receiving data

We also need a way to check if data has arrived in a conn, and then to retrieve it.

Function: int net_query_rdm (NET_CONN *conn)
Tests whether data can be read from a conn. Analogous to @xref{net_query}, but this function actually returns the size of the next queued packet.

Function: int net_receive_rdm (NET_CONN *conn, void *buffer, int maxsize)
Receives data from a conn. Analogous to @xref{net_receive}.

Ignoring data

Sometimes data will arrive in a conn, but we don't actually want it.

Function: int net_ignore_rdm (NET_CONN *conn)
If there are any incoming packets waiting to be read, this causes the first to be dropped; otherwise nothing happens. Note that the sender isn't notified, and will have received a confirmation of the packet's arrival. This function is intended for use if a large packet is in the queue and you weren't expecting to have to deal with it; call this function to remove the packet.

Returns non-zero if a packet was removed; zero if no packets were queued, or if an error occured.

Closing a conn

After you are done with a conn, you must close it with net_closeconn.

Functions: int net_closeconn (NET_CONN *conn)
Closes a previously opened conn.

Conns example

/* Demonstrates the conn functions.  This example was adapted
 * from the channel example.  */

#include <stdio.h>
#include <string.h>
#include <libnet.h>

/* Use the driver of your choice.
 * e.g. NET_DRIVER_WSOCK_WIN under Windows.  */
#define DRIVER  NET_DRIVER_SOCKETS

int main (int argc, char *argv[])
{
    NET_CONN *listen;
    NET_CONN *conn;
    int server = 0;
    char buf[256];
    
    /* Search command-line arguments for a "-s" option.  If
     * found then we will run as the server, otherwise as the
     * client.  (Not that it makes much difference here).  */
    for (argv++; *argv; argv++)
        if (strcmp (*argv, "-s") == 0)
            server = 1;

    net_init ();
    net_loadconfig (NULL);
    
    net_detectdrivers (net_drivers_all);
    net_initdrivers (net_drivers_all);

    if (server) {
        /* If we are the server, open a listening conn and wait
         * for a client.  */
        listen = net_openconn (DRIVER, "");
        if (!listen) {
            puts ("Error opening conn.");
            return 1;
        }
        
        if (net_listen (listen) != 0) {
            puts ("Error making conn listen.");
            return 1;
        }

        puts ("Awaiting connection...");
        do conn = net_poll_listen (listen);
        while (!conn);

        /* We could keep the listening conn around and connect
         * to many clients at once, if we wanted to.  */
        net_closeconn (listen);
    }
    else {
        int status;

        /* If we are the client, open a conn and connect to the
         * server's listening conn.  */
        conn = net_openconn (DRIVER, NULL);
        if (!conn) {
            puts ("Error opening conn.");
            return 1;
        }

        puts ("Enter target address:");
        fgets (buf, sizeof buf, stdin);
        buf[strlen (buf) - 1] = 0;   /* strip newline */

        if (net_connect (conn, buf) != 0) {
            puts ("Error initiating connection.");
            return 1;
        }
        
        puts ("Connecting...");
        do status = net_poll_connect (conn);
        while (status == 0);

        if (status < 0) {
            puts ("Error connecting.");
            return 1;
        }
    }

    puts ("Enter text to send.  Enter `.' to quit.");
    
    while (1) {
        /* Check if the remote side sent us any messages.  */
        while (net_query_rdm (conn))
            /* If so, receive them and print them out.  */
            if (net_receive_rdm (conn, buf, sizeof buf) > 0)
                printf ("Received: %s\n", buf);

        /* Let user enter something.  */
        if (!fgets (buf, sizeof buf, stdin))
            break;
        buf[strlen (buf) - 1] = 0;   /* strip newline */

        /* Quit condition.  */
        if (strcmp (buf, ".") == 0)
            break;

        /* Send the text if it is not a blank line.  */
        if (buf[0])
            net_send_rdm (conn, buf, strlen (buf) + 1);
    }

    /* Close the conn.  */
    net_closeconn (conn);
    
    /* Quit.  */
    return 0;
}

An example

[ Should I write a small Allegro + Libnet example here? I'm not sure it is necessary. ]

Network limitations

Once you figure out the Libnet API, there is still the network to contend with. Just like a computer's processor speed, memory capacity, etc. are limitations you must work around, the bandwidth and latency of a network are limitations you will have to overcome.

Let's say a game is using a pure client-server model (e.g. Quake) -- all game processing occurs on the server, the client sends keystrokes for interpretation to the server, and receives back the locations of objects from the server. If it takes a quarter of a second for a message to reach its destination (say, on the Internet) then, at best, a key pressed on the client machine will only have a visible effect on the client's machine half a second later:

  1. client sends keystroke to server (250 msec)
  2. server processes action (x msec)
  3. server sends game object locations back to client (250 msec)

Hopefully your Internet connection is better than mine, but it illustrates a good point. You may not be able to implement exactly the game you want, or implement in the way that you really want to (e.g. the above example is a clean design, but the game would be unplayable on such a connection). You will need to deal with all the multitude of problems that crop up in networks.

For real-time games this is an especially difficult task. But, being games, we can take shortcuts. As long as all the gamers think they are interacting in approximately the same world, no-one will care. And that's as far as I'll go here, as I know nothing about this area. Perhaps you could enlighten me?

What now?

Function Index

Jump to: n

n

  • net_assigntarget
  • net_closechannel
  • net_closeconn
  • net_connect
  • net_getlocaladdress
  • net_ignore_rdm
  • net_init
  • net_listen
  • net_loadconfig
  • net_openchannel
  • net_openconn
  • net_poll_connect
  • net_poll_listen
  • net_query
  • net_query_rdm
  • net_receive
  • net_receive_rdm
  • net_send
  • net_send_rdm
  • net_shutdown

  • This document was generated on 17 March 2001 using texi2html 1.56k.