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.
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.
Before we can use Libnet, there are some things to know.
I will assume Libnet is compiled and installed properly on your system.
Before we can use Libnet, we must initialise it.
Usually, we want to load some configuration data as well.
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.
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.
#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;
}
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.
Channels are represented with opaque objects of type NET_CHANNEL,
and are created with the following function:
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:
NULL, in which case, you are saying: "I don't
care what address I get, since I will be initiating the contact
sequence, and I can tell the remote side my address later". This is
designed for a client.
"" (i.e. the empty string), in which case, you
are saying "Give me the default address for this driver type, as I want
other people to be able to find me easily." This is designed for a
server.
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.
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:
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.
NET_CHANNEL *chan = net_openchannel (NET_DRIVER_WSOCK, NULL); net_assigntarget (chan, "127.0.0.1:12345");
Sometimes it is useful to get the local address of a channel. The following function will do that:
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.
Once the channel is opened and a target is assigned, we can now send some data.
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.
We need a way to check if there is data waiting to be received on a particular channel. The following function does just that:
We also need a way to actually receive the data.
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.
When you are done with a channel, you must close it.
/* 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 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.
Like channels, we must open conns before we can use them. Conns are
represented by the opaque data type NET_CONN.
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.
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 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:
We should check regularly if a client is trying to contact us through this listening conn, using this function:
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 needs to contact the listening conn of the server, using the following functions:
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.
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).
Once the connection process is complete, data can be sent with the following function.
Note: RDM stands for "Reliably Delivered Message".
We also need a way to check if data has arrived in a conn, and then to retrieve it.
Sometimes data will arrive in a conn, but we don't actually want it.
Returns non-zero if a packet was removed; zero if no packets were queued, or if an error occured.
After you are done with a conn, you must close it with
net_closeconn.
/* 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;
}
[ Should I write a small Allegro + Libnet example here? I'm not sure it is necessary. ]
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:
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?
Jump to: n
This document was generated on 17 March 2001 using texi2html 1.56k.