Package horizons :: Package network :: Module connection
[hide private]
[frames] | no frames]

Source Code for Module horizons.network.connection

  1  # ################################################### 
  2  # Copyright (C) 2008-2017 The Unknown Horizons Team 
  3  # team@unknown-horizons.org 
  4  # This file is part of Unknown Horizons. 
  5  # 
  6  # Unknown Horizons is free software; you can redistribute it and/or modify 
  7  # it under the terms of the GNU General Public License as published by 
  8  # the Free Software Foundation; either version 2 of the License, or 
  9  # (at your option) any later version. 
 10  # 
 11  # This program is distributed in the hope that it will be useful, 
 12  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 14  # GNU General Public License for more details. 
 15  # 
 16  # You should have received a copy of the GNU General Public License 
 17  # along with this program; if not, write to the 
 18  # Free Software Foundation, Inc., 
 19  # 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
 20  # ################################################### 
 21   
 22   
 23  import logging 
 24  import time 
 25   
 26  from horizons import network 
 27  from horizons.i18n import gettext as T 
 28  from horizons.network import enet, packets 
 29   
 30  # maximal peers enet should handle 
 31  MAX_PEERS = 1 
 32   
 33  # current server/client protocol the client understands 
 34  # increment that after incompatible protocol changes 
 35  SERVER_PROTOCOL = 1 
 36   
 37  # time in ms the client will wait for a packet 
 38  # on error client may wait twice that time 
 39  SERVER_TIMEOUT = 5000 
40 41 42 -class Connection:
43 """Low-level interface to enet. 44 45 Handles sending and receiving packets. 46 """ 47 log = logging.getLogger("network") 48
49 - def __init__(self, process_async_packet, server_address, client_address=None):
50 try: 51 if client_address: 52 client_address = enet.Address(*client_address) 53 54 self.host = enet.Host(client_address, MAX_PEERS, 0, 0, 0) 55 except (IOError, MemoryError): 56 # these exceptions do not provide any information. 57 raise network.NetworkException("Unable to create network structure." 58 "Maybe invalid or irresolvable client address.") 59 60 self.server_address_parameters = server_address 61 self.server_address = None 62 self.server_peer = None 63 self.packetqueue = [] 64 self.process_async_packet = process_async_packet
65 66 # Connection setup / keepalive 67 68 @property
69 - def is_connected(self):
70 return self.server_peer is not None
71
72 - def connect(self):
73 """Connect to master server. 74 75 After this, you can use `send_packet` and `receive_packet` to communicate 76 with the server. 77 """ 78 if self.is_connected: 79 raise network.AlreadyConnected("We are already connected to a server") 80 81 self.log.debug("[CONNECT] to server {}".format(self.server_address)) 82 try: 83 if self.server_address is None: 84 # can only construct address now, as it resolves the target and requires internet connection 85 self.server_address = enet.Address(*self.server_address_parameters) 86 self.server_peer = self.host.connect(self.server_address, 1, SERVER_PROTOCOL) 87 except (IOError, MemoryError): 88 raise network.NetworkException(T("Unable to connect to server.") + " " + 89 T("Maybe invalid or irresolvable server address.")) 90 91 event = self.host.service(SERVER_TIMEOUT) 92 if event.type != enet.EVENT_TYPE_CONNECT: 93 self._reset() 94 raise network.UnableToConnect(T("Unable to connect to server."))
95
96 - def disconnect(self, server_may_disconnect=False):
97 """End connection to master server. 98 99 This function should _never_ throw an exception. 100 """ 101 if not self.is_connected: 102 return 103 104 if self.server_peer.state == enet.PEER_STATE_DISCONNECTED: 105 self._reset() 106 return 107 108 try: 109 # wait for a disconnect event or empty event 110 if server_may_disconnect: 111 while True: 112 event = self.host.service(SERVER_TIMEOUT) 113 if event.type == enet.EVENT_TYPE_DISCONNECT: 114 break 115 elif event.type == enet.EVENT_TYPE_NONE: 116 break 117 118 # disconnect from server if we're still connected 119 if self.server_peer.state != enet.PEER_STATE_DISCONNECTED: 120 self.server_peer.disconnect() 121 while True: 122 event = self.host.service(SERVER_TIMEOUT) 123 if event.type == enet.EVENT_TYPE_DISCONNECT: 124 break 125 elif event.type == enet.EVENT_TYPE_NONE: 126 raise IOError("No packet from server") 127 except IOError: 128 self.log.debug("[DISCONNECT] Error while disconnecting from server. Maybe server isn't answering any more") 129 130 self._reset() 131 self.log.debug("[DISCONNECT] done")
132
133 - def ping(self):
134 """Handle incoming packets. 135 136 Enet doesn't need to send pings. Call this regularly. Incoming packets can be 137 handled by process_async_packet, otherwise will be added to a queue. 138 """ 139 if not self.is_connected: 140 raise network.NotConnected() 141 142 packet = self._receive(0) 143 if packet is not None: 144 if not self.process_async_packet(packet): 145 self.packetqueue.append(packet) 146 return True 147 148 return False
149 150 # Send / Receive 151
152 - def send_packet(self, packet):
153 """Send a packet to the server. 154 155 packet has to be a subclass of `horizons.network.packets.packet`. 156 """ 157 if self.server_peer is None: 158 raise network.NotConnected() 159 160 packet = enet.Packet(packet.serialize(), enet.PACKET_FLAG_RELIABLE) 161 self.server_peer.send(0, packet)
162
163 - def receive_packet(self, packet_type=None, timeout=SERVER_TIMEOUT):
164 """Return the first received packet. 165 166 If packet_type is given, only a packet of that type will be returned. 167 """ 168 169 if self.packetqueue: 170 if packet_type is None: 171 return self.packetqueue.pop(0) 172 173 for p in self.packetqueue: 174 if not isinstance(p[1], packet_type): 175 continue 176 self.packetqueue.remove(p) 177 return p 178 179 if packet_type is None: 180 return self._receive(timeout) 181 182 start = time.time() 183 timeleft = timeout 184 while timeleft > 0: 185 packet = self._receive(timeleft) 186 # packet type is None -> return whatever we received 187 if packet_type is None: 188 return packet 189 # otherwise only process non-None packets 190 if packet is not None: 191 if isinstance(packet[1], packet_type): 192 return packet 193 if not self.process_async_packet(packet): 194 self.packetqueue.append(packet) 195 timeleft -= time.time() - start 196 raise network.FatalError("No reply from server")
197
198 - def _receive_event(self, timeout=SERVER_TIMEOUT):
199 """Receives next event of type NONE or RECEIVE.""" 200 if self.server_peer is None: 201 raise network.NotConnected() 202 try: 203 event = self.host.service(timeout) 204 205 if event.type == enet.EVENT_TYPE_NONE: 206 return None 207 elif event.type == enet.EVENT_TYPE_DISCONNECT: 208 self._reset() 209 self.log.warning("Unexpected disconnect from %s", event.peer.address) 210 raise network.CommandError("Unexpected disconnect from {}".format(event.peer.address)) 211 elif event.type == enet.EVENT_TYPE_CONNECT: 212 self._reset() 213 self.log.warning("Unexpected connection from %s", event.peer.address) 214 raise network.CommandError("Unexpected connection from {}".format(event.peer.address)) 215 216 return event 217 except IOError as e: 218 raise network.FatalError(e)
219
220 - def _receive(self, timeout=SERVER_TIMEOUT):
221 """Receive event and return unpacked packet.""" 222 try: 223 event = self._receive_event(timeout) 224 if event is None or event.type != enet.EVENT_TYPE_RECEIVE: 225 return None 226 227 packet = packets.unserialize(event.packet.data) 228 except Exception as e: 229 try: 230 event 231 except NameError: 232 pass 233 else: 234 self.log.error("Unknown packet from %s!", event.peer.address) 235 errstr = "Pickle/Security: {}".format(e) 236 print("[FATAL] {}".format(errstr)) # print that even when no logger is enabled! 237 self.log.error("[FATAL] %s", errstr) 238 self.disconnect() 239 raise network.FatalError(errstr) 240 241 if isinstance(packet, packets.cmd_error): 242 # handle special errors here 243 # the game got terminated by the client 244 raise network.CommandError(packet.errorstr, cmd_type=packet.type) 245 elif isinstance(packet, packets.cmd_fatalerror): 246 self.log.error("[FATAL] Network message: %s", packet.errorstr) 247 self.disconnect(server_may_disconnect=True) 248 raise network.FatalError(packet.errorstr) 249 250 return [event.peer, packet]
251
252 - def _reset(self):
253 self.log.debug("[RESET]") 254 if self.is_connected: 255 self.server_peer.reset() 256 self.server_peer = None 257 self.host.flush()
258