Network Programming with ENet

 in

Cross-platform network programming made easy.

Creating a multiplayer game can be a lot of fun, but navigating the complexities of IP network programming can be a headache. That's kind of a strange statement, but the two go hand in hand. You can't write a multiplayer game without some sort of network-based communications, and game-related network programming introduces difficulties not often found with more simple applications. For example, most game developers are concerned with bandwidth utilization and throttling. There's also player session management to contend with. Then, there's the problem of message fragmentation, acknowledgement and sequencing. Oh, and you'd really like to be able to make your game run on both Linux and Windows. That's a tall order for developers who probably are more concerned with writing their games than they are in becoming experts in cross-platform network programming. Fortunately, the ENet library (http://enet.bespin.org) takes care of these details and presents developers with a simple, flexible and consistent API.

ENet's event-driven programming model makes client session management very simple. The library dispatches an event when a peer connects or disconnects, and when a message is received from a peer. The developer simply writes event handlers that take care of initializing and deallocating resources, and acting upon incoming messages. This means you don't have to worry about the complexities of forking, preforking, threading or nonblocking calls to connect() and accept() in order to handle multiple connections. With ENet, all you do is make periodic calls to its event dispatcher and handle the events as they come in.

ENet provides for both reliable and unreliable transmission. The networking industry really needs to find a better term than "unreliable", however. Unreliable means that a packet will be sent out, but the receiving end won't be expected to acknowledge receiving the packet. In a "reliable" protocol, every packet must be acknowledged upon receipt. If a peer sends out a packet and requests acknowledgement and doesn't receive it in a timely fashion, the packet will be resent automatically until it is acknowledged, or the peer is deemed to be unreachable. The nice thing about ENet is that the same API provides both reliable and unreliable semantics.

ENet also makes it easy to write real-time client-server applications by taking care of packet fragmentation and sequencing chores for you. Put simply, fragmentation and reassembly is done automatically and is transparent to the developer. Sequencing also is handled transparently and can be turned on and off at will. ENet's concept of a communications channel is related to sequencing. ENet buffers each channel separately and empties each buffer in numerical sequence. The end result is that you can transmit multiple data streams and that lower-numbered channels have higher priority. For example, you could put all real-time game update packets into channel 1, while system status packets could be in a lower-priority channel.

For the sake of demonstration, I discuss both the client and server for a simple chat program. The code I'm using is based on a 3-D video game I'm writing in my limited free time. However, while stripping the code down to its basics, I left something out and couldn't get it to work. So, I posted a description of my problem and code snippets to the ENet e-mail list. Within an hour, I had a reply that showed me how to fix my problem. Thanks, Nuno.

In this example, a user starts the client and provides a user name as a command parameter. Once a client session has been created, the client is expected to tell the server the name of the user, as the very first message sent from the client. Then, anything the user types will be sent to the server. Any messages that come from the server will be displayed on the client's screen. Because all user input is taken in a blocking fashion, the user won't actually see any incoming messages until pressing the Enter key. This isn't ideal, but the point of the code is to demonstrate the ENet library, not nonblocking I/O on STDIN and the necessary cursor control. (In a real-world situation, your programs would be generating messages, such as player movement and projectile impact, in real time anyway.) If the user simply presses the Enter key, no message is sent to the server, but any queued-up messages will be displayed. If the user types the letter q and presses Enter, the client disconnects and terminates.

The server also is very simple. When a client connects, the server waits for the client to identify the user. Then, the server sends a broadcast message announcing the new user's connections. When a client disconnects, that fact also is broadcast to all connected users. When a client sends a message, that message is sent to every connected client, except the one who sent it. Like I said, it's a very simple chat system.

Let's look at some code. First, let's get a few #defines out of the way. Take a look at config.h shown in Listing 1.

Listing 1. config.h11088l1.qrk

#define HOST "localhost"
#define PORT (7000)
#define BUFFERSIZE (1000)

This is pretty straightforward, so let's look at the client code shown in Listing 2.

Listing 2. Client Code


 1  #include <stdio.h>
 2  #include <string.h>
 3  #include <enet/enet.h>
 4  #include "config.h"

 5  #include <unistd.h>

 6  char  buffer[BUFFERSIZE];

 7  ENetHost  *client;
 8  ENetAddress  address;
 9  ENetEvent  event;
10  ENetPeer  *peer;
11  ENetPacket  *packet;

12  int  main(int argc, char ** argv) {
13    int connected=0;

14    if (argc != 1) {
15      printf("Usage: client username\n");
16      exit;
17    }

18    if (enet_initialize() != 0) {
19      printf("Could not initialize enet.\n");
20      return 0;
21    }

22    client = enet_host_create(NULL, 1, 2, 5760/8, 1440/8);

23    if (client == NULL) {
24      printf("Could not create client.\n");
25      return 0;
26    }

27    enet_address_set_host(&address, HOST);
28    address.port = PORT;

29    peer = enet_host_connect(client, &address, 2, 0);

30    if (peer == NULL) {
31      printf("Could not connect to server\n");
32      return 0;
33    }

34    if (enet_host_service(client, &event, 1000) > 0 &&
35      event.type == ENET_EVENT_TYPE_CONNECT) {

36      printf("Connection to %s succeeded.\n", HOST);
37      connected++;

38      strncpy(buffer, argv[1], BUFFERSIZE);
39      packet = enet_packet_create(buffer, strlen(buffer)+1,
          ENET_PACKET_FLAG_RELIABLE);
40      enet_peer_send(peer, 0, packet);

41    } else {
42      enet_peer_reset(peer);
43      printf("Could not connect to %s.\n", HOST);
44      return 0;
45    }

46    while (1) {
47      while (enet_host_service(client, &event, 1000) > 0) {
48        switch (event.type) {
49          case ENET_EVENT_TYPE_RECEIVE:
50            puts( (char*) event.packet->data);
51            break;
52          case ENET_EVENT_TYPE_DISCONNECT:
53            connected=0;
54            printf("You have been disconnected.\n");
55            return 2;
56        }
57      }

58      if (connected) {
59        printf("Input: ");
60        gets(buffer);

61        if (strlen(buffer) == 0) { continue; }

62        if (strncmp("q", buffer, BUFFERSIZE) == 0) {
63          connected=0;
64          enet_peer_disconnect(peer, 0);
65          continue;
66        } 

67        packet = enet_packet_create(buffer, strlen(buffer)+1,
            ENET_PACKET_FLAG_RELIABLE);
68        enet_peer_send(peer, 0, packet);
69      }
70    }

71    enet_deinitialize();
72  }
______________________

Mike Diehl is a freelance Computer Nerd specializing in Linux administration, programing, and VoIP. Mike lives in Albuquerque, NM. with his wife and 3 sons. He can be reached at mdiehl@diehlnet.com

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

nice.. but..

Anonymous's picture

it sounds good but installation is trouble-matic :P

WTH? Why don't you just use

Anonymous's picture

WTH? Why don't you just use the proactor in boost.asio? Boost is cross-platform, it is the "official" c++ lib (well, as official as an unofficial library could get :)), has pretty good documentation, and a good model.
Since games are usually developed in c++ (as far as I know), this would be a perfect fit.
By the way, there are really short and concise server/client examples with boost.asio.
Oh, and also, boost is pre-installed on most modern day linux distros, getting the boost lib is as easy as `sudo apt-get install libboost-dev` (for ubuntu), and linking to it is as easy as `-lboost_asio-mt`.

If you really want a c lib for networking, why not just pick GLib? Many major open source (also cross platform) projects are built on it.
However, GLib is much worse in documentation than boost (and obviously less user friendly automatically, since it's c and not c++).

I got problems on compilation

TOC's picture

I got problems on compilation too:

1. For the client ther's some warning about using gets function and in about the switch statement (ther's no default clause)!

2. For the server, the variable ipeerCount is not defined and when i replace it with : server->peerCount i got a violent SEGFAULT on the line 43 (enet_peer_send), Any ideas?

Thanks.

I got problems on compilation

TOC's picture

I got problems on compilation too:

1. For the client ther's some warning about using gets function and in about the switch statement (ther's no default clause)!

2. For the server, the variable ipeerCount is not defined and when i replace it with : server->peerCount i got a violent SEGFAULT on the line 43 (enet_peer_send), Any ideas?

Thanks.

Nice tutorial, but the code

evariste's picture

Nice tutorial, but the code does not compile, please fixe it. Thanks

Webinar
One Click, Universal Protection: Implementing Centralized Security Policies on Linux Systems

As Linux continues to play an ever increasing role in corporate data centers and institutions, ensuring the integrity and protection of these systems must be a priority. With 60% of the world's websites and an increasing share of organization's mission-critical workloads running on Linux, failing to stop malware and other advanced threats on Linux can increasingly impact an organization's reputation and bottom line.

Learn More

Sponsored by Bit9

Webinar
Linux Backup and Recovery Webinar

Most companies incorporate backup procedures for critical data, which can be restored quickly if a loss occurs. However, fewer companies are prepared for catastrophic system failures, in which they lose all data, the entire operating system, applications, settings, patches and more, reducing their system(s) to “bare metal.” After all, before data can be restored to a system, there must be a system to restore it to.

In this one hour webinar, learn how to enhance your existing backup strategies for better disaster recovery preparedness using Storix System Backup Administrator (SBAdmin), a highly flexible bare-metal recovery solution for UNIX and Linux systems.

Learn More

Sponsored by Storix