Reputation: 360
I've been writing a MUD in python, using asyncio.Protocol, however I have a problem with when users close their client (typically a terminal, since you connect via telnet) without disconnecting properly.
The server doesn't recognize the user as disconnected, and they remain in the game.
The problem only occurs when the client is connected remotely (for some reason, maybe someone can explain...) it doesn't happen when connecting from the localhost.
Is there a neat way to check a user is still actually connected (without additional software client-side), or on failing that how do I incorporate a timeout?
My Protocol looks something like this currently:
class User(Protocol):
def connection_made(self, transport):
self.transport = transport
self.addr = transport.get_extra_info('peername')
self.authd = False
self.name = None
self.admin = False
self.room = None
self.table = None
self.db = None
self.flags = []
print("Connected: {}".format(self.addr))
server.connected.append(self)
actions['help'](self, ['welcome'])
self.get_prompt()
def data_received(self, data):
msg = data.decode().strip()
args = msg.split()
if self.authd is False:
actions['login'](self, args)
return
if msg:
if args[0] in self.db.aliases:
args[0] = str(self.db.aliases[args[0]])
msg = ' '.join(args)
args = msg.split()
if msg[0] in server.channels:
ch = db.session.query(db.models.Channel).get(msg[0])
if msg[1] =='@':
channels.send_to_channel(self, ch, msg[2:], do_emote=True)
else:
channels.send_to_channel(self, ch, msg[1:])
self.get_prompt()
return
if args[0] in actions:
if self.is_frozen():
self.send_to_self("You're frozen solid!")
else:
actions[args[0]](self, args[1:] if len(args) > 1 else None)
self.get_prompt()
return
self.send_to_self("Huh?")
else:
if self.table is not None:
actions['table'](self, None)
elif self.room is not None:
actions['look'](self, None)
def send_to_self(self, msg):
msg = "\r\n" + msg
msg = colourify(msg)
self.transport.write(msg.encode())
@staticmethod
def send_to_user(user, msg):
msg = "\r\n"+msg
msg = colourify(msg)
user.transport.write(msg.encode())
@staticmethod
def send_to_users(users, msg):
msg = "\r\n"+msg
msg = colourify(msg)
for user in users:
user.transport.write(msg.encode())
def connection_lost(self, ex):
print("Disconnected: {}".format(self.addr))
server.connected.remove(self)
if self.authd:
self.save()
server.users.remove(self)
self.room.occupants.remove(self)
Note: I've chopped a lot of superfluous stuff out. If you want the full code, it's here.
Upvotes: 2
Views: 1245
Reputation: 17356
You may schedule a new timeout handler on every data_received()
call (with cancelling previous timeout handler, sure). I found the approach too cumbersome.
Or, as an option, switch to asyncio streams -- you may use asyncio.wait_for
or brand new not released yet asyncio.timeout
.
Upvotes: 1