Network Programming with ENet

 in

Lines 1–17 are boilerplate. Note that on line 3, I include enet/enet.h and not simply enet.h. The ENet documentation indicates that enet.h may conflict on some systems, so the enet directory must be used. The global buffer defined on line 6 is where I will put user input. Lines 7–11 simply define some variables that the ENet library requires.

The real ENet code begins on line 18 with the call to enet_initialize(). Once the library is initialized, I create the client host on line 22. As you'll see, clients and servers are both created with a call to enet_host_create(). The only difference is that for a client, you send NULL as the first argument. For a server, this argument tells ENet what address to bind to. Because a client doesn't have to bind, you pass in NULL. The second argument sets the limit on how many connections to make room for. This example client will connect only to one server, so I pass in 1. These two arguments are the only differences between creating a client and a server!

The third argument indicates how many channels I expect to use, 0-indexed by the way. Finally, the last two arguments indicate bandwidth limitations in bits per second for upload and download, respectively. Of course, I check to see if the call to enet_host_create() was successful and continue.

Lines 27–33 tell the client what address and port the server is on and to try to connect to it. If the client can't connect, it will terminate.

If the program gets to line 34, it has connected to the server, and it's time to identify the user. The enet_host_service() function is ENet's event dispatcher, but this will be made more clear in the server code. For now, understand that all I'm doing is waiting for the server to confirm the connection so you can identify yourselves. If you don't see the ENET_EVENT_TYPE_CONNECT, you know you didn't really get connected and should terminate. On lines 38–41, I create and send a packet to the server that simply contains the user's name. (I'll have more to say about packets when I examine the server code.)

The rest of the program, from line 46, is the main event loop. (I'll discuss enet_host_service() and the switch statement that follows it in more detail when I discuss the server.) The code starting from line 58 is fairly simple. Here I get input from the user and check if it's an empty line or if it's a line with just a q on it. Otherwise, I create a packet and send it. Obviously, I never really get to line 71, but I included it for instructional purposes.

The client really isn't very difficult to write and understand. As you're about to see, the server code is almost identical. Let's take a look at the server code in Listing 3.

Listing 3. Server Code


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

 6  ENetAddress  address;
 7  ENetHost   *server;
 8  ENetEvent  event;
 9  ENetPacket      *packet;

10  char    buffer[BUFFERSIZE];

11  int  main(int argc, char ** argv) {
12  int  i;

13    if (enet_initialize() != 0) {
14      printf("Could not initialize enet.");
15      return 0;
16    }

17    address.host = ENET_HOST_ANY;
18    address.port = PORT;

19    server = enet_host_create(&address, 100, 2, 0, 0);

20    if (server == NULL) {
21      printf("Could not start server.\n");
22      return 0;
23    }

24    while (1) {
25      while (enet_host_service(server, &event, 1000) > 0) {
26        switch (event.type) {

27          case ENET_EVENT_TYPE_CONNECT:
28            break;

29          case ENET_EVENT_TYPE_RECEIVE:
30            if (event.peer->data == NULL) {
31              event.peer->data = 
                  malloc(strlen((char*) event.packet->data)+1);
32              strcpy((char*) event.peer->data, (char*) 
                 event.packet->data);

33              sprintf(buffer, "%s has connected\n", 
                  (char*) event.packet->data);
34              packet = enet_packet_create(buffer, 
                  strlen(buffer)+1, 0);
35              enet_host_broadcast(server, 1, packet);
36              enet_host_flush(server);
37            } else {
38              for (i=0; ipeerCount; i++) {
39                if (&server->peers[i] != event.peer) {
40                  sprintf(buffer, "%s: %s", 
41                    (char*) event.peer->data, (char*)
                       event.packet->data);
42                  packet = enet_packet_create(buffer,
                       strlen(buffer)+1, 0);
43                              enet_peer_send(&server->peers[i], 0,
                                packet);
44                  enet_host_flush(server);
45                } else {

46                }
47              }
48            }
49            break;

50          case ENET_EVENT_TYPE_DISCONNECT:
51            sprintf(buffer, "%s has disconnected.", (char*)
                  event.peer->data);
52            packet = enet_packet_create(buffer, strlen(buffer)+1, 0);
53            enet_host_broadcast(server, 1, packet);
54            free(event.peer->data);
55            event.peer->data = NULL;
56            break;

57          default:
58            printf("Tick tock.\n");
59            break;
60        }

61      }
62    }

63    enet_host_destroy(server);
64    enet_deinitialize();
65  }

As you can see, the first 24 lines of code are almost identical to those found in the client code, with two notable exceptions. On lines 17–19, I tell the server to bind to the default IP address, 0.0.0.0, and allocate space for up to 100 client connections. In this case, I don't set any limits on bandwidth utilization.

On line 25, I call enet_host_service() until it returns 0. Each time enet_host_service() returns a nonzero value, I know that something has happened, and the switch statement that follows is used to determine what happened. Note the third argument indicates how many milliseconds to wait for something to happen. If I had passed a 0 in this argument, the call to enet_host_service() would be completely nonblocking.

The ENET_EVENT_TYPE_CONNECT event indicates that a client has connected to the server. Normally, you'd want to initialize resources for the client. But in this case, there is nothing to do until the client has identified itself. I left this case intact for instructional purposes.

The ENET_EVENT_TYPE_RECEIVE event is dispatched when the server receives a message from a client. For this event, there are two possible scenarios:

  1. The client hasn't identified itself yet, and this is the first message I've received from them.

  2. The client has been identified, and this is a normal chat message.

I check to see which is the case in the conditional on line 30. This line also points out an issue that comes up in the forums from time to time, so I'll explain it in a bit more detail.

Most server applications have to store at least some information about each client. Typically, they use an array of structures to store this information. Intuition tells you that one of the things you should store in a given client's structure is a pointer to whatever data type allows you to communicate with it. But with ENet, that intuition is wrong. Instead, ENet's peer data type provides a field, data, that you can use to store a pointer. This pointer presumably would point to the client's information structure. So, it's almost backward from what you expect. But, this is kind of an elegant solution; ENet manages its data, and you manage yours, separately.

The only client data that you care about is the name of the user associated with a given client. If you don't already have the user's name, and you receive a message from his or her client, you can assume that the client is identifying itself to you and you should store the user's name. I do this in lines 31 and 32. Then, in lines 33–36, I announce the new user to the rest of the clients. Note that I create a packet on line 34, but the call to enet_host_broadcast() deallocates it. It is a major error to deallocate that data structure yourself.

In lines 37–49, you can see the case where the client already is identified. All you have to do is send a message to the other clients indicating the name of the "speaker" and what he or she "said". To do this, you loop over ENet's list of peers. For each peer, check to see if it's the same peer that sent the message. If it is, you skip it, as people really don't want their own messages echoed back to them. Otherwise, you create a message and send it. This way, each client knows what was said, and by whom.

The ENET_EVENT_TYPE_DISCONNECT event indicates that a client has disconnected. In this case, you announce that the user has disconnected and deallocate the space used to store the user's name. On line 55, I set the data pointer back to NULL just in case ENet decides to re-use this structure when another client connects.

If no event is received, the default case is executed, and the server simply prints "Tick tock" to the console, as a reassurance that it is still running.

And, there you have it—a chat client in 72 lines of code and a multi-user chat server in 65 lines of code, and much of the code was identical. In fact, in the program upon which this code is based, I actually use identical code for both the client and server. Rather than have a block of code in the switch statement, I simply call an event handler, which is implemented in a separate code module, one for the client and one for the server. Then, depending on which module I link against, I can have a client or a server. This has the added benefit of isolating all of the ENet-specific code in one source file.

As you can see, the ENet library is almost trivial to use, but it encapsulates sophisticated network communications capabilities. Of course, what this really means is that it's just fun to use.

Network image via Shutterstock.com

______________________

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