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