def _create_packet(self): packet_size = 0 packet_bytes = bytearray() sent_messages = [] self._log.debug('%d packets pending' % len(self._outgoing)) for message in self._outgoing: if message.require_ack: # a minimum resend delay is required for two reasons: # 1. deadlock with a 0ms latency connection causes _do_write # to never exit as _create_packet always returns data. # 2. resending every 0ms is just plain stupid. t = time.time() resend_delay = max(self.MINIMUM_RESEND_DELAY_MS, self._transport_latency) self._log.debug('LSAT: %s' % str(message.last_send_attempt_timestamp)) self._log.debug('l8nc: %s' % str(self._transport_latency)) self._log.debug('time: %s' % t) self._log.debug('rsnd: %s' % resend_delay) if message.last_send_attempt_timestamp is not None: if ((message.last_send_attempt_timestamp + resend_delay) >= t): self._log.debug('Waiting for ack.') continue if packet_size + message.length <= self.MTU: self._log.debug('Added data message into UDP packet') packet_size += message.length packet_bytes += message.message_bytes message.last_send_attempt_timestamp = time.time() sent_messages.append(message) else: self._log.debug('packet at MTU limit.') for sent_message in sent_messages: # Packets that require an ack are only removed # from the outgoing list if an ack is received. if not sent_message.require_ack: self._log.info('Message %d doesnt require ack - removing' % sent_message.message_id) self._outgoing.remove(sent_message) else: self._log.info( 'Message %d requires ack - waiting for response' % sent_message.message_id) return packet_bytes
def __init__(self, parent=None, message_factory=None): if message_factory is None: self.message_factory = parent.message_factory else: self.message_factory = message_factory self.parent = parent self._last_receive_timestamp = time.time() self._last_send_timestamp = time.time() self._keep_alive_send_timestamp = time.time() self._keep_alive_message_id = 0 # server: number of keepalives sent # client: number of keepalives received self._keepalive_count = 0 self._ping_id = 0 self._ping_send_timestamp = time.time() self._ping_meter = PingSampler() self.OnConnectRequestAccepted = Event() self.OnConnectRequestRejected = Event() self.OnConnectRequest = Event() self.OnError = Event() self.OnMessage = Event() self.OnDisconnect = Event() # Packet instances to be processed go in here self._incoming_messages = [] # List of OutgoingMessages self._outgoing = [] # In-order packet instances that have arrived early self._incoming_out_of_sequence_messages = [] self._incoming_ordered_sequence_number = 0 self._outgoing_ordered_sequence_number = 1 self._outgoing_message_id = 0 self._recent_message_ids = [] # Metrics self._in_bytes = 0 self._out_bytes = 0 self._in_packets = 0 self._out_packets = 0 self._in_messages = 0 self._out_messages = 0 ''' Default transport latency is high - This prevents spamming of the network prior to obtaining a calculated latency. ''' self._transport_latency = 0.3 # 0.1 = 100ms
def _send_ping(self): self._ping_id += 1 if (self._ping_id > netshared.USHRT_MAX): self._ping_id = 0 ping = self.message_factory.get_by_name('Ping')() ping.id.value = self._ping_id self.send_message(ping) self._ping_send_timestamp = time.time()
def _send_keep_alive(self): self._keep_alive_message_id += 1 if (self._keep_alive_message_id > netshared.USHRT_MAX): self._keep_alive_message_id = 0 message = self.message_factory.get_by_name('KeepAliveRequest')() message.id.value = self._keep_alive_message_id self.send_message(message) self._keep_alive_send_timestamp = time.time() self._keepalive_count += 1
def __init__(self, parent=None, address=None): self._connected = False self.parent = parent self._socket = parent.socket self._address = address self._last_receive_timestamp = time.time() self._pending_disconnect = False self.OnConnectRequest = Event() self.OnDisconnect = Event() self.OnError = Event() self.OnMessage = Event() self._connection = Service('Connection', {'parent':self}) self._connection.OnMessage += self._Connection_OnMessage self._connection.OnDisconnect += self._Connection_OnDisconnect self._connection.OnError += self._Connection_OnError self._connection.OnConnectRequest += self._Connection_OnConnectRequest
def __init__(self, parent=None, address=None): self._connected = False self.parent = parent self._socket = parent.socket self._address = address self._last_receive_timestamp = time.time() self._pending_disconnect = False self.OnConnectRequest = Event() self.OnDisconnect = Event() self.OnError = Event() self.OnMessage = Event() self._connection = Service('Connection', {'parent': self}) self._connection.OnMessage += self._Connection_OnMessage self._connection.OnDisconnect += self._Connection_OnDisconnect self._connection.OnError += self._Connection_OnError self._connection.OnConnectRequest += self._Connection_OnConnectRequest
def send_message(self, message, ordered=False, reliable=False): ''' Send a message and specify any options for the send method used. A message sent inOrder is implicitly sent as reliable. message is an instance of a subclass of packets.BasePacket. Returns the number of bytes added to the output queue for this message (header + message). ''' self._last_send_timestamp = time.time() self._outgoing_message_id += 1 message_id = self._outgoing_message_id if ordered: self._outgoing_ordered_sequence_number += 1 inorder_sequence_number = self._outgoing_ordered_sequence_number else: inorder_sequence_number = 0 packet_flags = bitfield() packet_flags[0] = int(ordered) packet_flags[1] = int(reliable) message_transport_header = struct.pack( '!'+self.MESSAGE_TRANSPORT_HEADER, message_id, inorder_sequence_number, int(packet_flags)) message_bytes = message.get_packet_bytes() total_length = len(message_bytes)+len(message_transport_header) self._out_bytes += total_length self._add_message_bytes_to_output_list( message_id, message_transport_header+message_bytes, ordered or reliable) self._log.debug('Packet data length = %s' % len(message_bytes)) self._log.debug('Header length = %s' % len(message_transport_header)) self._log.debug('Added %d byte %s packet in outgoing buffer' % (total_length, message.__class__.__name__)) return total_length
def send_message(self, message, ordered=False, reliable=False): ''' Send a message and specify any options for the send method used. A message sent inOrder is implicitly sent as reliable. message is an instance of a subclass of packets.BasePacket. Returns the number of bytes added to the output queue for this message (header + message). ''' self._last_send_timestamp = time.time() self._outgoing_message_id += 1 message_id = self._outgoing_message_id if ordered: self._outgoing_ordered_sequence_number += 1 inorder_sequence_number = self._outgoing_ordered_sequence_number else: inorder_sequence_number = 0 packet_flags = bitfield() packet_flags[0] = int(ordered) packet_flags[1] = int(reliable) message_transport_header = struct.pack( '!' + self.MESSAGE_TRANSPORT_HEADER, message_id, inorder_sequence_number, int(packet_flags)) message_bytes = message.get_packet_bytes() total_length = len(message_bytes) + len(message_transport_header) self._out_bytes += total_length self._add_message_bytes_to_output_list( message_id, message_transport_header + message_bytes, ordered or reliable) self._log.debug('Packet data length = %s' % len(message_bytes)) self._log.debug('Header length = %s' % len(message_transport_header)) self._log.debug('Added %d byte %s packet in outgoing buffer' % (total_length, message.__class__.__name__)) return total_length
def update(self): ''' Send any packets that are in the output buffer and read any packets that have been received. ''' try: self.parent.do_read(self._on_socket_data) except netshared.NetworkEndpointError: self.raiseOnError('Connection reset by peer') return if self._ping_meter.has_estimate(): self._transport_latency = self._ping_meter.get_ping() read_messages = self._update(self.parent._socket, self.parent._address) if len(read_messages) != 0: self._last_receive_timestamp = time.time() for message in read_messages: if self.message_factory.is_a(message, 'ConnectRequestAccepted'): self.OnConnectRequestAccepted(self, None) elif self.message_factory.is_a(message, 'ConnectRequestRejected'): self.OnConnectRequestRejected(self, None) elif self.message_factory.is_a(message, 'KeepAliveResponse'): if (message.id.value == self._keep_alive_message_id): self._ping_meter.add_sample( (time.time() - self._keep_alive_send_timestamp) * 1000) else: self._log.warning('Received old keep-alive, discarding') elif self.message_factory.is_a(message, 'KeepAliveRequest'): self._keepalive_count += 1 response = self.message_factory.get_by_name( 'KeepAliveResponse')() response.id.value = message.id.value self.send_message(response) elif self.message_factory.is_a(message, 'Pong'): if (message.id.value == self._ping_id): self._ping_meter.add_sample( (time.time() - self._ping_send_timestamp) * 1000) else: self._log.warning('Received old Pong, discarding') elif self.message_factory.is_a(message, 'Ping'): self._send_pong(message.id.value) elif self.message_factory.is_a(message, 'Disconnected'): self._log.debug('Received `Disconnected` message') self.OnDisconnect(self, None) elif self.message_factory.is_a(message, 'MessageAck'): self._process_message_ack(message.message_to_ack.value) elif self.message_factory.is_a(message, 'ConnectRequest'): # Unless the connection request is explicitly denied then # a connection is made - OnConnectRequest may return None # if no event handlers are bound. accept = True if (message.protocol.value != netshared.PROTOCOL_VERSION): self._log.error('Invalid protocol version for client') accept = False if self.OnConnectRequest(self.parent, message) is False: accept = False if accept: response = self.message_factory.get_by_name( 'ConnectRequestAccepted') self.send_reliable_message(response()) else: response = self.message_factory.get_by_name( 'ConnectRequestRejected') self.send_reliable_message(response()) self.pendingDisconnect = True else: self.OnMessage(self, message) if (time.time() > self._ping_send_timestamp + PING_REQUEST_FREQUENCY): if self.parent.is_server: self._keep_alive_send_timestamp = time.time() self._send_ping() if self.parent.is_server: # Server sends keep alive requests... if ((time.time() - self._keep_alive_send_timestamp) > (self.parent.timeout / 2)): self._send_keep_alive() # though it will eventually give up... if (time.time() - self._last_receive_timestamp) > (self.parent.timeout): self.OnError(self, 'Connection timed out') else: # ...Client waits for the connection to timeout if (time.time() - self._last_receive_timestamp) > (self.parent.timeout): self._log.info('Connection has timed out') self.OnError(self, 'Connection timed out')
def update(self): ''' Send any packets that are in the output buffer and read any packets that have been received. ''' try: self.parent.do_read(self._on_socket_data) except netshared.NetworkEndpointError: self.raiseOnError('Connection reset by peer') return if self._ping_meter.has_estimate(): self._transport_latency = self._ping_meter.get_ping() read_messages = self._update( self.parent._socket, self.parent._address) if len(read_messages) != 0: self._last_receive_timestamp = time.time() for message in read_messages: if self.message_factory.is_a(message, 'ConnectRequestAccepted'): self.OnConnectRequestAccepted(self, None) elif self.message_factory.is_a(message, 'ConnectRequestRejected'): self.OnConnectRequestRejected(self, None) elif self.message_factory.is_a(message, 'KeepAliveResponse'): if (message.id.value == self._keep_alive_message_id): self._ping_meter.add_sample( (time.time()-self._keep_alive_send_timestamp)*1000) else: self._log.warning('Received old keep-alive, discarding') elif self.message_factory.is_a(message, 'KeepAliveRequest'): self._keepalive_count += 1 response = self.message_factory.get_by_name('KeepAliveResponse')() response.id.value = message.id.value self.send_message(response) elif self.message_factory.is_a(message, 'Pong'): if (message.id.value == self._ping_id): self._ping_meter.add_sample( (time.time()-self._ping_send_timestamp)*1000) else: self._log.warning('Received old Pong, discarding') elif self.message_factory.is_a(message, 'Ping'): self._send_pong(message.id.value) elif self.message_factory.is_a(message, 'Disconnected'): self._log.debug('Received `Disconnected` message') self.OnDisconnect(self, None) elif self.message_factory.is_a(message, 'MessageAck'): self._process_message_ack(message.message_to_ack.value) elif self.message_factory.is_a(message, 'ConnectRequest'): # Unless the connection request is explicitly denied then # a connection is made - OnConnectRequest may return None # if no event handlers are bound. accept = True if (message.protocol.value != netshared.PROTOCOL_VERSION): self._log.error('Invalid protocol version for client') accept = False if self.OnConnectRequest(self.parent, message) is False: accept = False if accept: response = self.message_factory.get_by_name('ConnectRequestAccepted') self.send_reliable_message(response()) else: response = self.message_factory.get_by_name('ConnectRequestRejected') self.send_reliable_message(response()) self.pendingDisconnect = True else: self.OnMessage(self, message) if (time.time() > self._ping_send_timestamp + PING_REQUEST_FREQUENCY): if self.parent.is_server: self._keep_alive_send_timestamp = time.time() self._send_ping() if self.parent.is_server: # Server sends keep alive requests... if ((time.time()-self._keep_alive_send_timestamp)> (self.parent.timeout/2)): self._send_keep_alive() # though it will eventually give up... if (time.time()-self._last_receive_timestamp)>(self.parent.timeout): self.OnError(self, 'Connection timed out') else: # ...Client waits for the connection to timeout if (time.time()-self._last_receive_timestamp)>(self.parent.timeout): self._log.info('Connection has timed out') self.OnError(self, 'Connection timed out')