wire.py 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. """协议线层: 24B头(9×i32)+体, 全帧 Encode6BitBuf。提炼自 protocol_bot/cq2_pure_enter.py(实测验证)。"""
  2. from __future__ import annotations
  3. import socket
  4. import struct
  5. import sys
  6. import time
  7. from dataclasses import dataclass
  8. sys.path.insert(0, "protocol_bot")
  9. from encode6bitbuf_final import encode6, decode6 # type: ignore
  10. HDR = struct.Struct("<9i") # recog,msg,p1,p2,p3,zero?,len,tail → 对齐 cq2_pure_enter.send()
  11. @dataclass
  12. class Frame:
  13. recog: int
  14. msg_id: int
  15. p1: int
  16. p2: int
  17. p3: int
  18. tail: int
  19. body: bytes = b""
  20. def pack_frame(f: Frame) -> bytes:
  21. head = HDR.pack(0, f.msg_id, f.recog, f.p1, f.p2, f.p3, 0, len(f.body), f.tail)
  22. return encode6(head) + f.body
  23. def unpack_stream(buf: bytes) -> tuple[list[Frame], bytes]:
  24. out, pos = [], 0
  25. while pos + 48 <= len(buf):
  26. try:
  27. m = HDR.unpack(decode6(buf[pos:pos + 48]))
  28. except Exception:
  29. break
  30. body_len = m[7]
  31. if pos + 48 + body_len > len(buf):
  32. break
  33. out.append(Frame(recog=m[2], msg_id=m[1], p1=m[3], p2=m[4], p3=m[5],
  34. tail=m[8], body=buf[pos + 48:pos + 48 + body_len]))
  35. pos += 48 + body_len
  36. return out, buf[pos:]
  37. class Wire:
  38. """同步 TCP 线客户端。持跨 recv 调用的内部 buffer:半包 rest 留存到下次。"""
  39. def __init__(self, host: str, port: int, timeout: float = 10) -> None:
  40. self.sock = socket.create_connection((host, port), timeout=timeout)
  41. self._buf: bytes = b""
  42. def send(self, f: Frame) -> None:
  43. self.sock.sendall(pack_frame(f))
  44. def recv(self, deadline_s: float) -> list[Frame]:
  45. deadline = time.time() + deadline_s
  46. while time.time() < deadline:
  47. self.sock.settimeout(max(0.2, deadline - time.time()))
  48. try:
  49. d = self.sock.recv(65536)
  50. if not d:
  51. break
  52. self._buf += d
  53. except (socket.timeout, ConnectionResetError, OSError):
  54. break
  55. frames, self._buf = unpack_stream(self._buf)
  56. return frames
  57. def close(self) -> None:
  58. try:
  59. self.sock.close()
  60. except OSError:
  61. pass