1 #pragma once
2 
3 /**
4  * @file
5  * Networking library wrapping ENet.
6  *
7  * The aim is to hide ENet behind an object-oriented, threaded API
8  * that could theoretically be implemented with another networking
9  * library without changes to the user's code.
10  *
11  * Requires linking agains ENet 1.3.x and boost_thread libraries.
12  *
13  * QUICKGUIDE:
14  *   1. Create ENetContainer library and keep it in scope
15  *   2. Create a class inheriting NetworkListener and implement the functions
16  *   3. Create a NetworkObject, giving it an instance of your NetworkListener
17  */
18 
19 #include <cstdlib>
20 #include <cstdio>
21 #include <iostream>
22 #include <sstream>
23 #include <string>
24 #include <stdexcept>
25 #include <boost/shared_ptr.hpp>
26 #include <boost/bind.hpp>
27 #include <boost/thread.hpp>
28 #include <boost/noncopyable.hpp>
29 #include <boost/lexical_cast.hpp>
30 #include <enet/enet.h>
31 
32 #include "address.hpp"
33 #include "types.hpp"
34 
35 // Version check
36 #if ENET_VERSION < ENET_VERSION_CREATE(1,3,0)
37 	#error ENet versions below 1.3.0 not supported
38 #endif
39 
40 namespace net {
41 
42 	const unsigned ENetChannels = 3; // 0 = Sequenced, 1 = Reliable, 2 = Unsequenced
43 
44 	typedef enet_uint16 peer_id_t;
45 
46 	enum PacketFlags {
47 		PACKET_SEQUENCED = 0,
48 		PACKET_RELIABLE = 1,
49 		PACKET_UNSEQUENCED = 2
50 	};
51 
52 	/**
53 	 * RAII Wrapper for the library
54 	 *
55 	 * Create an instance when you want to initialize networking and
56 	 * keep it in scope during the time you need it. Deinit is automatic
57 	 * upon destruction.
58 	 */
59 	struct ENetContainer {
ENetContainernet::ENetContainer60 		ENetContainer() { enet_initialize(); }
~ENetContainernet::ENetContainer61 		~ENetContainer() { enet_deinitialize(); }
62 	};
63 
64 	/// Convert ENetPeer to net::Address
convert(const ENetPeer * peer)65 	inline Address convert(const ENetPeer* peer) {
66 		if (peer) return Address(peer->address.host, peer->address.port);
67 		else return Address();
68 	}
69 
70 	/// Convert ENetAddress to net::Address
convert(const ENetAddress addr)71 	inline Address convert(const ENetAddress addr) {
72 		return Address(addr.host, addr.port);
73 	}
74 
75 	/// Network traffic container
76 	struct NetworkTraffic {
NetworkTrafficnet::NetworkTraffic77 		NetworkTraffic(const enet_uint8* pckd = NULL, size_t pckl = 0, enet_uint32 evdata = 0):
78 			peer_id(0), peer_address(), peer_data(NULL), packet_data(pckd),
79 			packet_length(pckl), ping(0), event_data(evdata) {}
NetworkTrafficnet::NetworkTraffic80 		NetworkTraffic(ENetPeer* peer, void* dptr, const enet_uint8* pckd = NULL, size_t pckl = 0, enet_uint32 evdata = 0):
81 			peer_id(peer->incomingPeerID), peer_address(convert(peer)), peer_data(dptr), packet_data(pckd),
82 			packet_length(pckl), ping(peer->roundTripTime), event_data(evdata) {}
83 		peer_id_t peer_id; ///< Peer ID assigned by the library
84 		Address peer_address; ///< Address from which the peer connected
85 		void* peer_data; ///< User data associated with the peer that sent the traffic
86 		const enet_uint8* packet_data; ///< The actual packet data (empty for connect/disconnect events)
87 		size_t packet_length; ///< Length of the packet data in bytes
88 		unsigned ping; ///< Average round-trip time to the peer
89 		enet_uint32 event_data; ///< Data associated with the event
90 	};
91 
92 	// Serialization functions
93 
94 	/*template <typename T>
95 	enet_uint8* convert(T& obj) {
96 		return reinterpret_cast<enet_uint8*>(&obj);
97 	}*/
98 
99 	/*template <typename T>
100 	enet_uint8 const* convert(const T& obj) {
101 		return reinterpret_cast<enet_uint8 const*>(&obj);
102 	}*/
103 
104 	template <typename T>
convert(T & obj)105 	NetworkTraffic convert(T& obj) {
106 		return NetworkTraffic(reinterpret_cast<enet_uint8*>(&obj), sizeof(T));
107 	}
108 
109 	/*template <typename T>
110 	const NetworkTraffic convert(const T& obj) {
111 		return NetworkTraffic(reinterpret_cast<enet_uint8 const*>(&obj), sizeof(T));
112 	}*/
113 
114 	/**
115 	 * @brief Callback class prototype
116 	 *
117 	 * Inherit this class and implement the functions to get network events.
118 	 * The events are sent from another thread, but there will not be two
119 	 * simultaneous events.
120 	 */
121 	class NetworkListener {
122 	public:
123 		/// Someone connected
124 		/// @param e the traffic associated with the event
connectionEvent(NetworkTraffic const & e)125 		virtual void connectionEvent(NetworkTraffic const& e) {}
126 		/// Someone disconnected
127 		/// @param e the traffic associated with the event
disconnectEvent(NetworkTraffic const & e)128 		virtual void disconnectEvent(NetworkTraffic const& e) {}
129 		/// Someone sent some data
130 		/// @param e the traffic associated with the event
receiveEvent(NetworkTraffic const & e)131 		virtual void receiveEvent(NetworkTraffic const& e) {}
132 	};
133 
134 
135 	/**
136 	 * Networking class capable of acting as client or server
137 	 *
138 	 * This is the main object that provides an object-oriented API
139 	 * for the underlying low-level networking implementation.
140 	 *
141 	 * A thread is used so that the networking events are asynchronous.
142 	 * There is no need for user to poll - use NetworkListener class instead.
143 	 *
144 	 * Connections are automatically terminated upon destruction of the instance.
145 	 *
146 	 * This class is thread-safe.
147 	 */
148 	class NetworkObject: public boost::noncopyable {
149 	public:
150 
151 		/**
152 		 * Constructor.
153 		 * @param listener class to receive network events
154 		 * @param port the port for listening for new connections, defaults to any port
155 		 * @param data optional application specific data to associate with the NetworkObject instance
156 		 * @param max_connections maximum number of peers
157 		 */
NetworkObject(NetworkListener & listener,int port=-1,void * data=NULL,int max_connections=16)158 		NetworkObject(NetworkListener& listener, int port = -1, void* data = NULL, int max_connections = 16): m_quit(false), m_host(NULL), m_listener(listener), m_data(data)
159 		{
160 			m_address.host = ENET_HOST_ANY;
161 			m_address.port = port < 0 ? ENET_PORT_ANY : port;
162 			// Create host at address, max_conns, unlimited up/down bandwith
163 			m_host = enet_host_create(&m_address, max_connections, ENetChannels, 0, 0);
164 			if (!m_host) throw std::runtime_error("An error occurred while trying to create an ENet host.");
165 			// Start listener thread
166 			m_thread = boost::thread(boost::bind(&NetworkObject::listen, boost::ref(*this)));
167 		}
168 
169 		/// Destructor.
~NetworkObject()170 		~NetworkObject() {
171 			terminate();
172 			// Gracefully disconnect all peers
173 			boost::mutex::scoped_lock lock(m_mutex);
174 			if (m_host) {
175 				ENetEvent e;
176 				enet_host_service(m_host, &e, 5); // Dummy service to allow for sending pending messages
177 				for (size_t i = 0; i < m_host->peerCount; ++i)
178 					enet_peer_disconnect(&m_host->peers[i], 0);
179 				enet_host_destroy(m_host);
180 				m_host = NULL;
181 			}
182 		}
183 
184 		/// Server thread, do not call directly
listen()185 		void listen() {
186 			ENetEvent e;
187 			while (!m_quit) {
188 				{
189 					boost::mutex::scoped_lock lock(m_mutex);
190 					enet_host_service(m_host, &e, 5);
191 				}
192 				switch (e.type) {
193 					case ENET_EVENT_TYPE_NONE: {
194 						break;
195 					} case ENET_EVENT_TYPE_CONNECT: {
196 						m_peers[e.peer->incomingPeerID] = e.peer;
197 						m_listener.connectionEvent(NetworkTraffic(e.peer, e.peer->data, NULL, 0, e.data));
198 						break;
199 					} case ENET_EVENT_TYPE_DISCONNECT: {
200 						m_listener.disconnectEvent(NetworkTraffic(e.peer, e.peer->data, NULL, 0, e.data));
201 						e.peer->data = NULL;
202 						m_peers.erase(e.peer->incomingPeerID);
203 						break;
204 					} case ENET_EVENT_TYPE_RECEIVE: {
205 						m_listener.receiveEvent(NetworkTraffic(e.peer, e.peer->data, e.packet->data, e.packet->dataLength, e.data));
206 						enet_packet_destroy(e.packet); // Clean-up
207 						break;
208 					}
209 				}
210 				boost::this_thread::sleep(boost::posix_time::milliseconds(1));
211 			}
212 		}
213 
214 		/**
215 		 * Connect to a peer.
216 		 * @param host the address to connect to
217 		 * @param port the port to connect to
218 		 * @param data application specific data that can be retrieved in events
219 		 */
connect(const std::string & host,int port,void * data=NULL,uint32_t connection_data=0)220 		void connect(const std::string& host, int port, void* data = NULL, uint32_t connection_data = 0) {
221 			// Set properties
222 			ENetAddress address;
223 			enet_address_set_host(&address, host.c_str());
224 			address.port = port;
225 			// Initiate the connection
226 			ENetPeer* peer = NULL;
227 			boost::mutex::scoped_lock lock(m_mutex);
228 			peer = enet_host_connect(m_host, &address, ENetChannels, connection_data);
229 			if (!peer) throw std::runtime_error("No available peers for initiating an ENet connection.");
230 			peer->data = data;
231 		}
232 
233 		/**
234 		 * Connect to a peer.
235 		 * @param addr the address to connect to
236 		 * @param data application specific data that can be retrieved in events
237 		 */
connect(const Address & addr,void * data=NULL,uint32_t connection_data=0)238 		void connect(const Address& addr, void* data = NULL, uint32_t connection_data = 0) {
239 			// Set properties
240 			ENetAddress address;
241 			address.host = addr.host;
242 			address.port = addr.port;
243 			// Initiate the connection
244 			ENetPeer* peer = NULL;
245 			boost::mutex::scoped_lock lock(m_mutex);
246 			peer = enet_host_connect(m_host, &address, ENetChannels, connection_data);
247 			if (!peer) throw std::runtime_error("No available peers for initiating an ENet connection.");
248 			peer->data = data;
249 		}
250 
251 		/// Send a packet to everyone
broadcast(const NetworkTraffic & msg,int flags=0)252 		void broadcast(const NetworkTraffic& msg, int flags = 0) {
253 			ENetPacket* packet = enet_packet_create(msg.packet_data, msg.packet_length, flags);
254 			// Pick channel for sending and broadcast to all peers
255 			int channel = 0;
256 			if (flags & PACKET_RELIABLE) channel = 1;
257 			else if (flags & PACKET_UNSEQUENCED) channel = 2;
258 			// Send
259 			boost::mutex::scoped_lock lock(m_mutex);
260 			enet_host_broadcast(m_host, channel, packet);
261 		}
262 
263 		/// Send a string to everyone
broadcast(const std::string & msg,int flags=0)264 		void broadcast(const std::string& msg, int flags = 0) {
265 			NetworkTraffic traffic(reinterpret_cast<const enet_uint8*>(msg.c_str()), msg.length());
266 			broadcast(traffic, flags);
267 		}
268 
269 		/// Send a packet to a specific peer
send(peer_id_t peer_id,const NetworkTraffic & msg,int flags=0)270 		void send(peer_id_t peer_id, const NetworkTraffic& msg, int flags = 0) {
271 			ENetPeer* peer = getPeerPtr(peer_id);
272 			if (!peer) return;
273 			ENetPacket* packet = enet_packet_create(msg.packet_data, msg.packet_length, flags);
274 			// Pick channel for sending and broadcast to all peers
275 			int channel = 0;
276 			if (flags & PACKET_RELIABLE) channel = 1;
277 			else if (flags & PACKET_UNSEQUENCED) channel = 2;
278 			// Send
279 			boost::mutex::scoped_lock lock(m_mutex);
280 			enet_peer_send(peer, channel, packet);
281 		}
282 
283 		/// Send a string to a specific peer
send(peer_id_t peer_id,const std::string & msg,int flags=0)284 		void send(peer_id_t peer_id, const std::string& msg, int flags = 0) {
285 			NetworkTraffic traffic(reinterpret_cast<const enet_uint8*>(msg.c_str()), msg.length());
286 			send(peer_id, traffic, flags);
287 		}
288 
289 		/// Disconnects the given peer
disconnect(peer_id_t peer_id,bool force=false,uint32_t disconnection_data=0)290 		void disconnect(peer_id_t peer_id, bool force = false, uint32_t disconnection_data = 0) {
291 			ENetPeer* peer = getPeerPtr(peer_id);
292 			if (!peer) return;
293 			if (force) enet_peer_disconnect_now(peer, disconnection_data);
294 			else enet_peer_disconnect(peer, disconnection_data);
295 		}
296 
297 		/// Returns a raw ENet peer pointer
getPeerPtr(peer_id_t peer_id)298 		ENetPeer* getPeerPtr(peer_id_t peer_id) {
299 			Peers::iterator it = m_peers.find(peer_id);
300 			if (it != m_peers.end()) return it->second;
301 			return NULL;
302 		}
303 
304 		/// Get address of the local host
getAddress() const305 		Address getAddress() const {
306 			boost::mutex::scoped_lock lock(m_mutex);
307 			return convert(m_address);
308 		}
309 
310 		/// Terminate the network thread. Automatically called upon destruction.
terminate()311 		void terminate() { m_quit = true; m_thread.join(); }
312 
313 	private:
314 		bool m_quit;
315 		ENetAddress m_address;
316 		ENetHost* m_host;
317 		typedef std::map<peer_id_t, ENetPeer*> Peers;
318 		Peers m_peers;
319 		mutable boost::mutex m_mutex;
320 		boost::thread m_thread;
321 		NetworkListener& m_listener;
322 		void* m_data;
323 	};
324 }
325