class CubeWorldServer: exit_code = None world = None old_time = None skip_index = 0 def __init__(self, loop, config): self.loop = loop self.config = config base = config.base # game-related self.update_packet = packets.ServerUpdate() self.update_packet.reset() self.connections = set() self.players = MultikeyDict() self.updated_chunks = set() use_same_loop = base.update_fps == base.network_fps if use_same_loop: def update_callback(): self.update() self.send_update() else: update_callback = self.update self.update_loop = LoopingCall(update_callback) self.update_loop.start(1.0 / base.update_fps, now=False) if use_same_loop: self.send_loop = self.update_loop else: self.send_loop = LoopingCall(self.send_update) self.send_loop.start(1.0 / base.network_fps, now=False) self.mission_loop = LoopingCall(self.update_missions) self.mission_loop.start(base.mission_update_rate, now=False) # world self.world = World(self, self.loop, base.seed, use_tgen=base.use_tgen, use_entities=base.use_entities, chunk_retire_time=base.chunk_retire_time, debug=base.world_debug_info) if base.world_debug_file is not None: debug_fp = open(base.world_debug_file, 'wb') self.world.set_debug(debug_fp) # server-related self.git_rev = base.get('git_rev', None) self.passwords = {} for k, v in base.passwords.items(): self.passwords[k.lower()] = v self.scripts = ScriptManager() for script in base.scripts: self.load_script(script) # time self.extra_elapsed_time = 0.0 self.start_time = loop.time() self.set_clock('12:00') # start listening self.loop.set_exception_handler(self.exception_handler) self.loop.create_task(self.create_server(self.build_protocol, port=base.port, family=socket.AF_INET)) def exception_handler(self, loop, context): exception = context.get('exception') if isinstance(exception, TimeoutError): pass else: loop.default_exception_handler(context) def build_protocol(self): return CubeWorldConnection(self) def drop_item(self, item_data, pos): item = packets.ChunkItemData() item.drop_time = 750 # XXX provide sane values for these item.scale = 0.1 item.rotation = 185.0 item.something3 = item.something5 = item.something6 = 0 item.pos = pos item.item_data = item_data self.world.get_chunk(get_chunk(pos)).add_item(item) def add_packet_list(self, items, l, size): for item in iterate_packet_list(l): items.append(item.data.copy()) def handle_tgen_packets(self, in_queue): if in_queue is None: return p = self.update_packet self.add_packet_list(p.player_hits, in_queue.player_hits, in_queue.player_hits_size) self.add_packet_list(p.sound_actions, in_queue.sound_actions, in_queue.sound_actions_size) self.add_packet_list(p.particles, in_queue.particles, in_queue.particles_size) self.add_packet_list(p.block_actions, in_queue.block_actions, in_queue.block_actions_size) self.add_packet_list(p.shoot_actions, in_queue.shoot_packets, in_queue.shoot_packets_size) self.add_packet_list(p.kill_actions, in_queue.kill_actions, in_queue.kill_actions_size) self.add_packet_list(p.damage_actions, in_queue.damage_actions, in_queue.damage_actions_size) self.add_packet_list(p.passive_actions, in_queue.passive_packets, in_queue.passive_packets_size) self.add_packet_list(p.missions, in_queue.missions, in_queue.missions_size) def update_missions(self): max_dist = self.config.base.mission_max_distance p = self.update_packet added = set() for connection in self.players.values(): player_entity = connection.entity if player_entity is None: continue min_pos = (player_entity.pos - max_dist) // constants.MISSION_SCALE max_pos = (player_entity.pos + max_dist) // constants.MISSION_SCALE for x in range(min_pos.x, max_pos.x): for y in range(min_pos.y, max_pos.y): if (x, y) in added: continue added.add((x, y)) reg_x = x // constants.MISSIONS_IN_REGION reg_y = y // constants.MISSIONS_IN_REGION try: reg = self.world.get_region((reg_x, reg_y)) except KeyError: continue local_x = x % constants.MISSIONS_IN_REGION local_y = y % constants.MISSIONS_IN_REGION try: m = reg.get_mission((local_x, local_y)) except (IndexError, ValueError): continue mission_packet = packets.MissionPacket() mission_packet.x = x mission_packet.y = y mission_packet.something1 = 0 mission_packet.something2 = 0 mission_packet.info = m.info p.missions.append(mission_packet) def send_entity_data(self, entity): base = self.config.base # full entity packet for new, close players entity_packet.set_entity(entity, entity.entity_id) full = packets.write_packet(entity_packet) # pos entity packet if not entity.is_tgen: entity_packet.set_entity(entity, entity.entity_id, entitydata.POS_FLAG) only_pos = packets.write_packet(entity_packet) # reduced rate packet skip_reduced = self.skip_index != 0 entity_packet.set_entity(entity, entity.entity_id, entitydata.POS_FLAG) reduced = packets.write_packet(entity_packet) max_distance = base.max_distance max_reduce_distance = base.max_reduce_distance old_close_players = entity.close_players new_close_players = {} for connection in self.players.values(): player_entity = connection.entity if player_entity is None: continue if entity is player_entity: continue if entity.full_update: connection.send_data(full) new_close_players[connection] = entity.copy() continue dist = (player_entity.pos - entity.pos).length if dist > max_distance: if not entity.is_tgen: connection.send_data(only_pos) continue old_ref = old_close_players.get(connection, None) if old_ref is None: connection.send_data(full) new_close_players[connection] = entity.copy() continue if dist > max_reduce_distance and skip_reduced: connection.send_data(reduced) new_close_players[connection] = old_ref continue new_mask = entitydata.get_mask(old_ref, entity) entity_packet.set_entity(entity, entity.entity_id, new_mask) connection.send_packet(entity_packet) new_close_players[connection] = entity.copy() entity.close_players = new_close_players entity.full_update = False def update(self): self.scripts.call('update') out_packets = self.world.update(self.update_loop.dt) self.handle_tgen_packets(out_packets) def send_update(self): self.skip_index = (self.skip_index + 1) % self.config.base.reduce_skip for entity in self.world.entities.values(): self.send_entity_data(entity) self.broadcast_packet(update_finished_packet) # other updates update_packet = self.update_packet for chunk in self.updated_chunks: chunk.on_update(update_packet) if not update_packet.is_empty(): self.broadcast_packet(update_packet) update_packet.reset() # reset drop times for chunk in self.updated_chunks: chunk.on_post_update() self.updated_chunks.clear() # time update new_time = (self.get_time(), self.get_day()) if new_time != self.old_time: time_packet.time = new_time[0] time_packet.day = new_time[1] self.broadcast_packet(time_packet) self.old_time = new_time def send_chat(self, value): chat_packet.entity_id = 0 chat_packet.value = value self.broadcast_packet(chat_packet) def play_sound(self, name, pos=None, pitch=1.0, volume=1.0): sound = packets.SoundAction() sound.set_name(name) sound.pitch = pitch sound.volume = volume if pos is not None: sound.pos = pos self.update_packet.sound_action.append(sound) return extra_server_update.reset() for player in self.players.values(): sound.pos = player.entity.pos extra_server_update.sound_actions = [sound] player.send_packet(extra_server_update) def broadcast_packet(self, packet): data = packets.write_packet(packet) for player in self.players.values(): player.send_data(data) # line/string formatting options based on config def format(self, value): format_dict = {'server_name': self.config.base.server_name} return value % format_dict def format_lines(self, value): lines = [] for line in value: lines.append(self.format(line)) return lines # script methods def load_script(self, name, update=False): try: return self.scripts[name] except KeyError: pass try: mod = __import__('scripts.%s' % name, globals(), locals(), [name]) if update: importlib.reload(mod) except ImportError as e: traceback.print_exc() return None script = mod.get_class()(self) print('Loaded script %r' % name) return script def unload_script(self, name): try: self.scripts[name].unload() except KeyError: return False print('Unloaded script %r' % name) return True def call_command(self, user, command, args): """ Calls a command from an external interface, e.g. IRC, console """ return self.scripts.call('on_command', user=user, command=command, args=args).result def get_mode(self): return self.scripts.call('get_mode').result # command convenience methods (for /help) def get_commands(self): for script in self.scripts.get(): if script.commands is None: continue for command in script.commands.values(): yield command def get_command(self, name): for script in self.scripts.get(): if script.commands is None: continue name = script.aliases.get(name, name) command = script.commands.get(name, None) if command: return command # binary data store methods def load_data(self, name, default=None): path = os.path.join(self.config.base.save_path, f'{name}.dat') try: with open(path, 'r', newline=None) as fp: data = fp.read() except IOError: return default return eval(data) def save_data(self, name, value): os.makedirs(self.config.base.save_path, exist_ok=True) path = os.path.join(self.config.base.save_path, f'{name}.dat') data = pprint.pformat(value, width=1) with open(path, 'w') as fp: fp.write(data) # time methods def set_clock(self, value): day = self.get_day() time = parse_clock(value) self.start_time = self.loop.time() self.extra_elapsed_time = day * constants.MAX_TIME + time def get_elapsed_time(self): dt = self.loop.time() - self.start_time dt *= self.config.base.time_modifier * constants.NORMAL_TIME_SPEED return dt * 1000 + self.extra_elapsed_time def get_time(self): return int(self.get_elapsed_time() % constants.MAX_TIME) def get_day(self): return int(self.get_elapsed_time() / constants.MAX_TIME) def get_clock(self): return get_clock_string(self.get_time()) # stop/restart def stop(self, code=None): print('Stopping...') self.exit_code = code if self.world: self.world.stop() self.scripts.unload() self.loop.stop() # asyncio wrappers def get_interface(self): return self.config.base.network_interface def create_datagram_endpoint(self, *arg, port=0, **kw): host = self.get_interface() addr = (host, port) return self.loop.create_datagram_endpoint(*arg, local_addr=addr, **kw) def create_server(self, *arg, **kw): return self.loop.create_server(*arg, host=self.get_interface(), **kw) def connect_connection(self, *arg, **kw): host = self.get_interface() return self.loop.create_connection(*arg, local_addr=(host, 0), **kw)
class CubeWorldServer: exit_code = None world = None def __init__(self, loop, config): self.loop = loop self.config = config base = config.base # game-related self.update_packet = packets.ServerUpdate() self.update_packet.reset() self.connections = set() self.players = MultikeyDict() self.chunks = {} self.updated_chunks = set() self.update_loop = LoopingCall(self.update) self.update_loop.start(1.0 / base.update_fps, now=False) # world self.world = World(self, self.loop, base.seed, base.use_tgen, base.use_entities) # server-related self.git_rev = base.get('git_rev', None) self.passwords = {} for k, v in base.passwords.items(): self.passwords[k.lower()] = v self.scripts = ScriptManager() for script in base.scripts: self.load_script(script) # time self.extra_elapsed_time = 0.0 self.start_time = loop.time() self.set_clock('12:00') # start listening asyncio.Task(self.create_server(self.build_protocol, port=base.port)) def build_protocol(self): return CubeWorldConnection(self) def drop_item(self, item_data, pos): item = packets.ChunkItemData() item.drop_time = 750 # XXX provide sane values for these item.scale = 0.1 item.rotation = 185.0 item.something3 = item.something5 = item.something6 = 0 item.pos = pos item.item_data = item_data self.world.get_chunk(get_chunk(pos)).add_item(item) def update(self): self.scripts.call('update') # entity updates for entity_id, entity in self.world.entities.items(): entity_packet.set_entity(entity, entity_id, entity.mask) entity.mask = 0 self.broadcast_packet(entity_packet) self.broadcast_packet(update_finished_packet) # other updates update_packet = self.update_packet for chunk in self.updated_chunks: chunk.on_update(update_packet) self.broadcast_packet(update_packet) update_packet.reset() # reset drop times for chunk in self.updated_chunks: chunk.on_post_update() self.updated_chunks.clear() # time update time_packet.time = self.get_time() time_packet.day = self.get_day() self.broadcast_packet(time_packet) def send_chat(self, value): chat_packet.entity_id = 0 chat_packet.value = value self.broadcast_packet(chat_packet) def play_sound(self, name, pos=None, pitch=1.0, volume=1.0): sound = packets.SoundAction() sound.set_name(name) sound.pitch = pitch sound.volume = volume if pos is not None: sound.pos = pos self.update_packet.sound_action.append(sound) return extra_server_update.reset() for player in self.players.values(): sound.pos = player.entity.pos extra_server_update.sound_actions = [sound] player.send_packet(extra_server_update) def broadcast_packet(self, packet): data = packets.write_packet(packet) for player in self.players.values(): player.transport.write(data) # line/string formatting options based on config def format(self, value): format_dict = {'server_name': self.config.base.server_name} return value % format_dict def format_lines(self, value): lines = [] for line in value: lines.append(self.format(line)) return lines # script methods def load_script(self, name, update=False): try: return self.scripts[name] except KeyError: pass try: mod = __import__('scripts.%s' % name, globals(), locals(), [name]) if update: importlib.reload(mod) except ImportError as e: traceback.print_exc() return None script = mod.get_class()(self) print('Loaded script %r' % name) return script def unload_script(self, name): try: self.scripts[name].unload() except KeyError: return False print('Unloaded script %r' % name) return True def call_command(self, user, command, args): """ Calls a command from an external interface, e.g. IRC, console """ return self.scripts.call('on_command', user=user, command=command, args=args).result def get_mode(self): return self.scripts.call('get_mode').result # command convenience methods (for /help) def get_commands(self): for script in self.scripts.get(): if script.commands is None: continue for command in script.commands.values(): yield command def get_command(self, name): for script in self.scripts.get(): if script.commands is None: continue name = script.aliases.get(name, name) command = script.commands.get(name, None) if command: return command # binary data store methods def load_data(self, name, default=None): path = './save/%s.dat' % name try: with open(path, 'rU') as fp: data = fp.read() except IOError: return default return eval(data) def save_data(self, name, value): path = './save/%s.dat' % name data = pprint.pformat(value, width=1) with open(path, 'w') as fp: fp.write(data) # time methods def set_clock(self, value): day = self.get_day() time = parse_clock(value) self.start_time = self.loop.time() self.extra_elapsed_time = day * constants.MAX_TIME + time def get_elapsed_time(self): dt = self.loop.time() - self.start_time dt *= self.config.base.time_modifier * constants.NORMAL_TIME_SPEED return dt * 1000 + self.extra_elapsed_time def get_time(self): return int(self.get_elapsed_time() % constants.MAX_TIME) def get_day(self): return int(self.get_elapsed_time() / constants.MAX_TIME) def get_clock(self): return get_clock_string(self.get_time()) # stop/restart def stop(self, code=None): print('Stopping...') self.exit_code = code if self.world: self.world.stop() self.scripts.unload() self.loop.stop() # asyncio wrappers def get_interface(self): return self.config.base.network_interface def create_datagram_endpoint(self, *arg, port=0, **kw): host = self.get_interface() addr = (host, port) return self.loop.create_datagram_endpoint(*arg, local_addr=addr, **kw) def create_server(self, *arg, **kw): return self.loop.create_server(*arg, host=self.get_interface(), **kw) def connect_connection(self, *arg, **kw): host = self.get_interface() return self.loop.create_connection(*arg, local_addr=(host, 0), **kw)
class CubeWorldServer: exit_code = None world = None old_time = None skip_index = 0 def __init__(self, loop, config): self.loop = loop self.config = config base = config.base # game-related self.update_packet = packets.ServerUpdate() self.update_packet.reset() self.connections = set() self.players = MultikeyDict() self.updated_chunks = set() use_same_loop = base.update_fps == base.network_fps if use_same_loop: def update_callback(): self.update() self.send_update() else: update_callback = self.update self.update_loop = LoopingCall(update_callback) self.update_loop.start(1.0 / base.update_fps, now=False) if use_same_loop: self.send_loop = self.update_loop else: self.send_loop = LoopingCall(self.send_update) self.send_loop.start(1.0 / base.network_fps, now=False) self.mission_loop = LoopingCall(self.update_missions) self.mission_loop.start(base.mission_update_rate, now=False) # world self.world = World(self, self.loop, base.seed, use_tgen=base.use_tgen, use_entities=base.use_entities, chunk_retire_time=base.chunk_retire_time, debug=base.world_debug_info) if base.world_debug_file is not None: debug_fp = open(base.world_debug_file, 'wb') self.world.set_debug(debug_fp) # server-related self.git_rev = base.get('git_rev', None) self.passwords = {} for k, v in base.passwords.items(): self.passwords[k.lower()] = v self.scripts = ScriptManager() for script in base.scripts: self.load_script(script) # time self.extra_elapsed_time = 0.0 self.start_time = loop.time() self.set_clock('12:00') # start listening self.loop.set_exception_handler(self.exception_handler) self.loop.create_task( self.create_server(self.build_protocol, port=base.port, family=socket.AF_INET)) def exception_handler(self, loop, context): exception = context.get('exception') if isinstance(exception, TimeoutError): pass else: loop.default_exception_handler(context) def build_protocol(self): return CubeWorldConnection(self) def drop_item(self, item_data, pos): item = packets.ChunkItemData() item.drop_time = 750 # XXX provide sane values for these item.scale = 0.1 item.rotation = 185.0 item.something3 = item.something5 = item.something6 = 0 item.pos = pos item.item_data = item_data self.world.get_chunk(get_chunk(pos)).add_item(item) def add_packet_list(self, items, l, size): for item in iterate_packet_list(l): items.append(item.data.copy()) def handle_tgen_packets(self, in_queue): if in_queue is None: return p = self.update_packet self.add_packet_list(p.player_hits, in_queue.player_hits, in_queue.player_hits_size) self.add_packet_list(p.sound_actions, in_queue.sound_actions, in_queue.sound_actions_size) self.add_packet_list(p.particles, in_queue.particles, in_queue.particles_size) self.add_packet_list(p.block_actions, in_queue.block_actions, in_queue.block_actions_size) self.add_packet_list(p.shoot_actions, in_queue.shoot_packets, in_queue.shoot_packets_size) self.add_packet_list(p.kill_actions, in_queue.kill_actions, in_queue.kill_actions_size) self.add_packet_list(p.damage_actions, in_queue.damage_actions, in_queue.damage_actions_size) self.add_packet_list(p.passive_actions, in_queue.passive_packets, in_queue.passive_packets_size) self.add_packet_list(p.missions, in_queue.missions, in_queue.missions_size) def update_missions(self): max_dist = self.config.base.mission_max_distance p = self.update_packet added = set() for connection in self.players.values(): player_entity = connection.entity if player_entity is None: continue min_pos = (player_entity.pos - max_dist) // constants.MISSION_SCALE max_pos = (player_entity.pos + max_dist) // constants.MISSION_SCALE for x in range(min_pos.x, max_pos.x): for y in range(min_pos.y, max_pos.y): if (x, y) in added: continue added.add((x, y)) reg_x = x // constants.MISSIONS_IN_REGION reg_y = y // constants.MISSIONS_IN_REGION try: reg = self.world.get_region((reg_x, reg_y)) except KeyError: continue local_x = x % constants.MISSIONS_IN_REGION local_y = y % constants.MISSIONS_IN_REGION try: m = reg.get_mission((local_x, local_y)) except (IndexError, ValueError): continue mission_packet = packets.MissionPacket() mission_packet.x = x mission_packet.y = y mission_packet.something1 = 0 mission_packet.something2 = 0 mission_packet.info = m.info p.missions.append(mission_packet) def send_entity_data(self, entity): base = self.config.base # full entity packet for new, close players entity_packet.set_entity(entity, entity.entity_id) full = packets.write_packet(entity_packet) # pos entity packet if not entity.is_tgen: entity_packet.set_entity(entity, entity.entity_id, entitydata.POS_FLAG) only_pos = packets.write_packet(entity_packet) # reduced rate packet skip_reduced = self.skip_index != 0 entity_packet.set_entity(entity, entity.entity_id, entitydata.POS_FLAG) reduced = packets.write_packet(entity_packet) max_distance = base.max_distance max_reduce_distance = base.max_reduce_distance old_close_players = entity.close_players new_close_players = {} for connection in self.players.values(): player_entity = connection.entity if player_entity is None: continue if entity is player_entity: continue if entity.full_update: connection.send_data(full) new_close_players[connection] = entity.copy() continue dist = (player_entity.pos - entity.pos).length if dist > max_distance: if not entity.is_tgen: connection.send_data(only_pos) continue old_ref = old_close_players.get(connection, None) if old_ref is None: connection.send_data(full) new_close_players[connection] = entity.copy() continue if dist > max_reduce_distance and skip_reduced: connection.send_data(reduced) new_close_players[connection] = old_ref continue new_mask = entitydata.get_mask(old_ref, entity) entity_packet.set_entity(entity, entity.entity_id, new_mask) connection.send_packet(entity_packet) new_close_players[connection] = entity.copy() entity.close_players = new_close_players entity.full_update = False def update(self): self.scripts.call('update') out_packets = self.world.update(self.update_loop.dt) self.handle_tgen_packets(out_packets) def send_update(self): self.skip_index = (self.skip_index + 1) % self.config.base.reduce_skip for entity in self.world.entities.values(): self.send_entity_data(entity) self.broadcast_packet(update_finished_packet) # other updates update_packet = self.update_packet for chunk in self.updated_chunks: chunk.on_update(update_packet) if not update_packet.is_empty(): self.broadcast_packet(update_packet) update_packet.reset() # reset drop times for chunk in self.updated_chunks: chunk.on_post_update() self.updated_chunks.clear() # time update new_time = (self.get_time(), self.get_day()) if new_time != self.old_time: time_packet.time = new_time[0] time_packet.day = new_time[1] self.broadcast_packet(time_packet) self.old_time = new_time def send_chat(self, value): chat_packet.entity_id = 0 chat_packet.value = value self.broadcast_packet(chat_packet) def play_sound(self, name, pos=None, pitch=1.0, volume=1.0): sound = packets.SoundAction() sound.set_name(name) sound.pitch = pitch sound.volume = volume if pos is not None: sound.pos = pos self.update_packet.sound_action.append(sound) return extra_server_update.reset() for player in self.players.values(): sound.pos = player.entity.pos extra_server_update.sound_actions = [sound] player.send_packet(extra_server_update) def broadcast_packet(self, packet): data = packets.write_packet(packet) for player in self.players.values(): player.send_data(data) # line/string formatting options based on config def format(self, value): format_dict = {'server_name': self.config.base.server_name} return value % format_dict def format_lines(self, value): lines = [] for line in value: lines.append(self.format(line)) return lines # script methods def load_script(self, name, update=False): try: return self.scripts[name] except KeyError: pass try: mod = __import__('scripts.%s' % name, globals(), locals(), [name]) if update: importlib.reload(mod) except ImportError as e: traceback.print_exc() return None script = mod.get_class()(self) print('Loaded script %r' % name) return script def unload_script(self, name): try: self.scripts[name].unload() except KeyError: return False print('Unloaded script %r' % name) return True def call_command(self, user, command, args): """ Calls a command from an external interface, e.g. IRC, console """ return self.scripts.call('on_command', user=user, command=command, args=args).result def get_mode(self): return self.scripts.call('get_mode').result # command convenience methods (for /help) def get_commands(self): for script in self.scripts.get(): if script.commands is None: continue for command in script.commands.values(): yield command def get_command(self, name): for script in self.scripts.get(): if script.commands is None: continue name = script.aliases.get(name, name) command = script.commands.get(name, None) if command: return command # binary data store methods def load_data(self, name, default=None): path = os.path.join(self.config.base.save_path, f'{name}.dat') try: with open(path, 'r', newline=None) as fp: data = fp.read() except IOError: return default return eval(data) def save_data(self, name, value): os.makedirs(self.config.base.save_path, exist_ok=True) path = os.path.join(self.config.base.save_path, f'{name}.dat') data = pprint.pformat(value, width=1) with open(path, 'w') as fp: fp.write(data) # time methods def set_clock(self, value): day = self.get_day() time = parse_clock(value) self.start_time = self.loop.time() self.extra_elapsed_time = day * constants.MAX_TIME + time def get_elapsed_time(self): dt = self.loop.time() - self.start_time dt *= self.config.base.time_modifier * constants.NORMAL_TIME_SPEED return dt * 1000 + self.extra_elapsed_time def get_time(self): return int(self.get_elapsed_time() % constants.MAX_TIME) def get_day(self): return int(self.get_elapsed_time() / constants.MAX_TIME) def get_clock(self): return get_clock_string(self.get_time()) # stop/restart def stop(self, code=None): print('Stopping...') self.exit_code = code if self.world: self.world.stop() self.scripts.unload() self.loop.stop() # asyncio wrappers def get_interface(self): return self.config.base.network_interface def create_datagram_endpoint(self, *arg, port=0, **kw): host = self.get_interface() addr = (host, port) return self.loop.create_datagram_endpoint(*arg, local_addr=addr, **kw) def create_server(self, *arg, **kw): return self.loop.create_server(*arg, host=self.get_interface(), **kw) def connect_connection(self, *arg, **kw): host = self.get_interface() return self.loop.create_connection(*arg, local_addr=(host, 0), **kw)