feat: EDC 服务 — Python/uvloop 实现,UDP/TCP 异步网络服务

This commit is contained in:
wangfq
2026-05-27 10:23:15 +08:00
commit a10d176f68
11 changed files with 1076 additions and 0 deletions

193
src/server.py Normal file
View File

@@ -0,0 +1,193 @@
"""EDC 服务器主入口 — UDP + TCP 异步服务
UDP 端口 5500: 发现设备 (Count_Off)、心跳 (Heartbeat)、信息查询
UDP 端口 5505: 消息监听
TCP 端口 5550: 时间同步 (TimeStamp)、设备数据上报 (TSReport, SerialNet)
"""
import asyncio
import logging
import signal
import sys
try:
import uvloop # type: ignore
except ImportError:
uvloop = None
from src.config import (
UDP_PORT, UDP_MSG_PORT, TCP_PORT, BIND_HOST, LOG_LEVEL,
)
from src.models import init_pool, close_pool
from src.protocol import parse_message, make_error_response
from src.handlers import (
handle_count_off,
handle_heartbeat,
handle_timestamp,
handle_tsreport,
handle_serial_net,
handle_device_info,
parse_loop,
)
logging.basicConfig(
level=getattr(logging, LOG_LEVEL),
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
logger = logging.getLogger("edc")
class EDCProtocol:
"""asyncio UDP 协议处理器"""
def __init__(self):
self.transport = None
def connection_made(self, transport):
self.transport = transport
def datagram_received(self, data, addr):
asyncio.ensure_future(self._handle(data, addr))
async def _handle(self, data: bytes, addr: tuple):
msg = parse_message(data)
if msg is None:
return
method = msg.get("Method", "")
logger.debug(f"UDP {method} from {addr}")
try:
response = None
if method == "Count_Off":
response = await handle_count_off(msg, addr)
elif method == "Heartbeat":
response = await handle_heartbeat(msg)
elif method == "Device_Info":
response = await handle_device_info(msg)
elif method == "TSReport":
await handle_tsreport(msg)
elif method == "SerialNet":
await handle_serial_net(msg)
if response and self.transport:
self.transport.sendto(response.encode("utf-8"), addr)
except Exception as e:
logger.error(f"处理 {method} 异常: {e}", exc_info=True)
async def handle_tcp_client(reader: asyncio.StreamReader,
writer: asyncio.StreamWriter):
"""TCP 客户端连接处理"""
addr = writer.get_extra_info("peername")
logger.info(f"TCP 连接: {addr}")
try:
while True:
line = await reader.readline()
if not line:
break
msg = parse_message(line)
if msg is None:
continue
method = msg.get("Method", "")
logger.debug(f"TCP {method} from {addr}")
try:
response = None
if method == "TimeStamp":
response = await handle_timestamp(msg)
elif method == "TSReport":
await handle_tsreport(msg)
elif method == "SerialNet":
await handle_serial_net(msg)
elif method == "Heartbeat":
response = await handle_heartbeat(msg)
elif method == "Device_Info":
response = await handle_device_info(msg)
if response:
writer.write((response + "\n").encode("utf-8"))
await writer.drain()
except Exception as e:
logger.error(f"TCP 处理 {method} 异常: {e}")
device_id = msg.get("Device_id", "")
err = make_error_response(method, device_id, str(e))
writer.write((err + "\n").encode("utf-8"))
await writer.drain()
except (ConnectionResetError, BrokenPipeError):
pass
finally:
logger.info(f"TCP 断开: {addr}")
writer.close()
await writer.wait_closed()
async def main():
logger.info("EDC 服务启动中...")
# 初始化数据库
await init_pool()
# 启动业务解析后台任务
asyncio.create_task(parse_loop())
# 启动 UDP 服务 (端口 5500)
loop = asyncio.get_running_loop()
udp_transport, _ = await loop.create_datagram_endpoint(
lambda: EDCProtocol(), # type: ignore[arg-type]
local_addr=(BIND_HOST, UDP_PORT),
)
logger.info(f"UDP 服务监听 {BIND_HOST}:{UDP_PORT}")
# 启动 UDP 消息端口 (5505)
udp_msg_transport, _ = await loop.create_datagram_endpoint(
lambda: EDCProtocol(), # type: ignore[arg-type]
local_addr=(BIND_HOST, UDP_MSG_PORT),
)
logger.info(f"UDP 消息监听 {BIND_HOST}:{UDP_MSG_PORT}")
# 启动 TCP 服务 (端口 5550)
tcp_server = await asyncio.start_server(
handle_tcp_client,
BIND_HOST,
TCP_PORT,
)
logger.info(f"TCP 服务监听 {BIND_HOST}:{TCP_PORT}")
# 优雅退出
stop_event = asyncio.Event()
def _shutdown():
logger.info("收到关闭信号,正在退出...")
stop_event.set()
loop.add_signal_handler(signal.SIGINT, _shutdown)
loop.add_signal_handler(signal.SIGTERM, _shutdown)
await stop_event.wait()
# 清理
tcp_server.close()
await tcp_server.wait_closed()
udp_transport.close()
udp_msg_transport.close()
await close_pool()
logger.info("EDC 服务已停止")
def run():
"""入口函数"""
if uvloop:
uvloop.install()
logger.info("uvloop 已启用")
asyncio.run(main())
if __name__ == "__main__":
run()