Files
neonsr/game_server/net/gateway.py
2025-04-17 20:13:39 +08:00

153 lines
4.8 KiB
Python

import asyncio
from nazo_rand import randrange
from game_server.net.kcp import get_conv
from game_server.net.packet import NetOperation
from game_server.net.session import PlayerSession
from utils.logger import Info,Warn,Debug
from database.mongodb import get_database
from game_server.srtools.gateway import set_gateway_instance
class KCPGateway(asyncio.DatagramProtocol):
def __init__(self, db):
self.id_counter = 0
self.sessions : dict[int, PlayerSession] = {}
self.running = True
self.shutdown_event = asyncio.Event()
self.db = db
self.timeout_check_interval = 5
self.save_duration = 60
def get_active_connection(self,uid) -> PlayerSession:
for key, session in self.sessions.items():
if session.player.data.uid == uid:
return session
return None
async def check_sessions_timeout(self):
while self.running:
for conv_id, session in list(self.sessions.items()):
if session.is_timeout():
self.drop_kcp_session(conv_id)
await asyncio.sleep(self.timeout_check_interval)
async def periodic_save_sessions(self):
while self.running:
start = asyncio.get_event_loop().time()
for session in self.sessions.values():
session.player.save_all()
elapsed = asyncio.get_event_loop().time() - start
Info(f"Database saved in {elapsed:.2f}s")
await asyncio.sleep(self.save_duration)
def connection_made(self, transport):
self.transport = transport
Info(f"Listening on {self.transport.get_extra_info('sockname')}")
asyncio.create_task(self.check_sessions_timeout())
asyncio.create_task(self.periodic_save_sessions())
def datagram_received(self, data, addr):
data_len = len(data)
Debug(f"Received {data_len} bytes from {addr}: {[b for b in data]}")
if data_len == 20:
self.process_net_operation(NetOperation.from_bytes(data), addr)
elif data_len >= 28:
self.process_kcp_payload(data, addr)
else:
Warn("Unknown data length received")
def process_net_operation(self, op: NetOperation, addr):
if (op.head, op.tail) == (0xFF, 0xFFFFFFFF):
self.establish_kcp_session(op.data, addr)
elif (op.head, op.tail) == (0x194, 0x19419494):
self.drop_kcp_session(op.conv_id)
else:
Warn(f"Unknown magic pair: {op.head}-{op.tail}")
def establish_kcp_session(self, data, addr):
conv_id, session_token = self.next_conv_pair()
session_id = conv_id << 32 | session_token
Info(f"New connection: {addr} with conv_id: {conv_id}")
self.sessions[conv_id] = PlayerSession(
self.transport, session_id, addr, self.db
)
net_op = NetOperation(
head=0x145,
conv_id=conv_id,
session_token=session_token,
data=data,
tail=0x14514545,
).to_bytes()
self.transport.sendto(net_op, addr)
def drop_kcp_session(self, conv_id):
if conv_id in self.sessions:
self.sessions[conv_id].player.save_all()
del self.sessions[conv_id]
Info(f"Dropped KCP session with conv_id: {conv_id}")
else:
Warn(
f"Attempted to drop non-existent KCP session with conv_id: {conv_id}"
)
def process_kcp_payload(self, data, addr):
conv_id = get_conv(data)
session = self.sessions.get(conv_id)
if session:
session.update_last_received()
asyncio.create_task(session.consume(data))
else:
Warn(f"Received data for unknown session conv_id: {conv_id}")
def next_conv_pair(self):
self.id_counter += 1
return self.id_counter, randrange(0, 0xFF)
def connection_lost(self, exc):
Info("UDP connection lost, shutting down...")
for session in self.sessions.values():
session.player.save_all()
self.running = False
self.shutdown_event.set()
def shutdown(self):
Info("Shutting down server...")
for session in self.sessions.values():
session.player.save_all()
self.running = False
self.shutdown_event.set()
@staticmethod
async def new(host, port):
loop = asyncio.get_running_loop()
db = get_database()
transport, protocol = await loop.create_datagram_endpoint(
lambda: KCPGateway(db), local_addr=(host, port)
)
set_gateway_instance(protocol)
try:
await protocol.shutdown_event.wait()
except asyncio.CancelledError:
Info("Server tasks cancelled.")
finally:
transport.close()
Info("Server stopped.")