Network Programming with ENet
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:
-
The client hasn't identified itself yet, and this is the first message I've received from them.
-
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
Today’s modular x86 servers are compute-centric, designed as a least common denominator to support a wide range of IT workloads. Those generic, virtualized IT workloads have much different resource optimization requirements than hyperscale and cloud applications. They have resulted in a “one size fits all” enterprise IT architecture that is not optimized for a specific set of IT workloads, and especially not emerging hyperscale workloads, such as web applications, big data, and object storage. In this report, you will learn how shifting the focus from traditional compute-centric IT architectures to an innovative disaggregated fabric-based architecture can optimize and scale your data center.
Sponsored by AMD
Built-in forensics, incident response, and security with Red Hat Enterprise Linux 6
Every security policy provides guidance and requirements for ensuring adequate protection of information and data, as well as high-level technical and administrative security requirements for a system in a given environment. Traditionally, providing security for a system focuses on the confidentiality of the information on it. However, protecting the data integrity and system and data availability is just as important. For example, when processing United States intelligence information, there are three attributes that require protection: confidentiality, integrity, and availability.
Learn more about catching the bad guy in this free white paper.
Sponsored by DLT Solutions
| Using Salt Stack and Vagrant for Drupal Development | May 20, 2013 |
| Making Linux and Android Get Along (It's Not as Hard as It Sounds) | May 16, 2013 |
| Drupal Is a Framework: Why Everyone Needs to Understand This | May 15, 2013 |
| Home, My Backup Data Center | May 13, 2013 |
| Non-Linux FOSS: Seashore | May 10, 2013 |
| Trying to Tame the Tablet | May 08, 2013 |
- Using Salt Stack and Vagrant for Drupal Development
- Making Linux and Android Get Along (It's Not as Hard as It Sounds)
- New Products
- Validate an E-Mail Address with PHP, the Right Way
- Drupal Is a Framework: Why Everyone Needs to Understand This
- A Topic for Discussion - Open Source Feature-Richness?
- Home, My Backup Data Center
- New Products
- The Pari Package On Linux
- New Products
- This is the easiest tutorial
5 hours 23 min ago - Ahh, the Koolaid.
11 hours 2 min ago - git-annex assistant
17 hours 1 min ago - direct cable connection
17 hours 24 min ago - Agreed on AirDroid. With my
17 hours 34 min ago - I just learned this
17 hours 38 min ago - enterprise
18 hours 8 min ago - not living upto the mobile revolution
20 hours 59 min ago - Deceptive Advertising and
21 hours 35 min ago - Let\'s declare that you have
21 hours 36 min ago
Enter to Win an Adafruit Prototyping Pi Plate Kit for Raspberry Pi

It's Raspberry Pi month at Linux Journal. Each week in May, Adafruit will be giving away a Pi-related prize to a lucky, randomly drawn LJ reader. Winners will be announced weekly.
Fill out the fields below to enter to win this week's prize-- a Prototyping Pi Plate Kit for Raspberry Pi.
Congratulations to our winners so far:
- 5-8-13, Pi Starter Pack: Jack Davis
- 5-15-13, Pi Model B 512MB RAM: Patrick Dunn
- Next winner announced on 5-21-13!
Free Webinar: Linux Backup and Recovery
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.



Comments
nice.. but..
it sounds good but installation is trouble-matic :P
WTH? Why don't you just use
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
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
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
Nice tutorial, but the code does not compile, please fixe it. Thanks