Rewrote timeout system

This commit is contained in:
Emily 2020-03-30 00:59:53 +02:00
parent e9ef282c95
commit a30b9f1a62
5 changed files with 29 additions and 25 deletions

View File

@ -1,5 +1,7 @@
import struct import struct
import socket import socket
import asyncio
from time import time
from . import base from . import base
from . import query from . import query
@ -9,6 +11,8 @@ STATE_UNKNOWN = (0, base.BaseClient)
STATE_QUERY = (1, query.QueryClient) STATE_QUERY = (1, query.QueryClient)
STATE_PLAYER = (2, player.PlayerClient) STATE_PLAYER = (2, player.PlayerClient)
TIMEOUT = 10 # assume connection is closed after 10 seconds of inactivity (change this to a higher value so you dont timeout while debugging might be a good idea)
class Client: class Client:
def __init__(self, server: "__ServerInstance__", ip: str, port: int): def __init__(self, server: "__ServerInstance__", ip: str, port: int):
self.server = server self.server = server
@ -16,19 +20,33 @@ class Client:
self.port = port self.port = port
self.set_state(STATE_UNKNOWN) self.set_state(STATE_UNKNOWN)
self.last_active = time()
self.keep_alive_task = asyncio.create_task( self.keep_alive() )
self.connected = True # keep_alive will set this to False if connection has not been interacted with for a while (allowing server loop to remove their reference)
def set_state(self, state: tuple): def set_state(self, state: tuple):
#self.keep_alive_task.cancel()
self.state = state self.state = state
self.client = self.state[1](self.server, self.ip, self.port) self.client = self.state[1](self.server, self.ip, self.port)
async def on_packet(self, packet: bytes): async def on_packet(self, packet: bytes):
self.last_active = time()
if self.state == STATE_UNKNOWN: if self.state == STATE_UNKNOWN:
# We are currently unaware if this is a player client or query client, but we got a packet that will be our check to know # We are currently unaware if this is a player client or query client, but we got a packet that will be our check to know
if packet.startswith(b"SAMP"): if packet.startswith(b"SAMP"):
await self.client.on_state_change()
self.set_state(STATE_QUERY) self.set_state(STATE_QUERY)
else: else:
await self.client.on_state_change()
self.set_state(STATE_PLAYER) self.set_state(STATE_PLAYER)
await self.client.on_packet(packet) await self.client.on_packet(packet)
async def keep_alive(self): # Maybe bad name for this method as it rather checks if connection is dropped
while True:
timestamp = time()
if self.last_active + TIMEOUT - timestamp < 0:
self.connected = False
return
await asyncio.sleep(self.last_active + TIMEOUT - timestamp)

View File

@ -1,13 +1,9 @@
import socket import socket
import struct import struct
import asyncio
from time import time
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
TIMEOUT = 30 # assume connection is closed after 30 seconds if inactivity
class BaseClient: class BaseClient:
def __init__(self, server: "__ServerInstance__", ip: str, port: int): def __init__(self, server: "__ServerInstance__", ip: str, port: int):
self.server = server self.server = server
@ -15,27 +11,10 @@ class BaseClient:
self.port = port self.port = port
self.ip_uint, = struct.unpack(b"<I", bytes(int(x) for x in self.ip.split("."))) self.ip_uint, = struct.unpack(b"<I", bytes(int(x) for x in self.ip.split(".")))
self.last_active = time()
self.keep_alive_task = asyncio.create_task( self.keep_alive() )
self.connected = True # keep_alive will set this to False if connection has not been interacted with for a while (allowing server loop to remove their reference)
async def on_packet(self, packet: bytes): async def on_packet(self, packet: bytes):
logger.debug("on_packet(%s)" % packet) logger.debug("on_packet(%s)" % packet)
self.last_active = time()
async def send(self, packet: bytes): async def send(self, packet: bytes):
sock: socket.socket = self.server.socket sock: socket.socket = self.server.socket
sock.sendto(packet, (self.ip, self.port)) sock.sendto(packet, (self.ip, self.port))
async def on_state_change(self): # Stop the keep alive task as the whole class is being replaced, TODO: Find out if this could be automated with a magic method
self.keep_alive_task.cancel()
async def keep_alive(self):
while True:
timestamp = time()
if self.last_active + TIMEOUT - timestamp < 0:
self.connected = False
return
await asyncio.sleep(self.last_active + TIMEOUT - timestamp)

View File

@ -11,4 +11,4 @@ class PlayerClient(BaseClient):
logger.debug("Client resolved to PlayerClient") logger.debug("Client resolved to PlayerClient")
async def on_packet(self, packet: bytes): async def on_packet(self, packet: bytes):
logger.debug("on_packet(%s)" % packet) logger.debug("on_packet(%s)" % packet)

View File

@ -22,7 +22,7 @@ class QueryClient(BaseClient):
} }
async def on_packet(self, packet: bytes): async def on_packet(self, packet: bytes):
await super().on_packet(packet) logger.debug("on_packet(%s)" % packet)
if len(packet) <= 10: # Invalid if len(packet) <= 10: # Invalid
return return

View File

@ -50,4 +50,11 @@ class Server:
self.clients[addr] = Client(self, ip, port) self.clients[addr] = Client(self, ip, port)
await self.clients[addr].on_packet(data) await self.clients[addr].on_packet(data)
disconnected = [c for c in self.clients.values() if c.connected == False]
for c in disconnected: # Remove dead connections
addr = (c.ip, c.port)
if addr in self.clients:
del self.clients[addr]
logger.debug("free(%s)" % c)
await asyncio.sleep(0) await asyncio.sleep(0)