Files
Gym_CPU/communication/server.py

130 lines
4.0 KiB
Python

import logging
import socket
import time
from select import select
from communication.world_parser import WorldParser
logger = logging.getLogger(__file__)
class Server:
def __init__(self, host: str, port: int, world_parser: WorldParser):
self.world_parser: WorldParser = world_parser
self.__host: str = host
self.__port: int = port
self.__socket: socket.socket = self._create_socket()
self.__send_buff = []
self.__rcv_buffer_size = 1024
self.__rcv_buffer = bytearray(self.__rcv_buffer_size)
def _create_socket(self) -> socket.socket:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
return sock
def connect(self) -> None:
logger.info("Connecting to server at %s:%d...", self.__host, self.__port)
# Always reconnect with a fresh socket object.
try:
self.__socket.close()
except OSError:
pass
self.__socket = self._create_socket()
while True:
try:
self.__socket.connect((self.__host, self.__port))
break
except ConnectionRefusedError:
logger.error(
"Connection refused. Make sure the server is running and listening on {self.__host}:{self.__port}."
)
time.sleep(0.05)
logger.info(f"Server connection established to {self.__host}:{self.__port}.")
def shutdown(self) -> None:
try:
self.__socket.shutdown(socket.SHUT_RDWR)
except OSError:
pass
try:
self.__socket.close()
except OSError:
pass
def send_immediate(self, msg: str) -> None:
"""
Sends only the desired message, without the buffer
"""
try:
self.__socket.send(
(len(msg)).to_bytes(4, byteorder="big") + msg.encode()
) # Add message length in the first 4 bytes
except BrokenPipeError:
print("\nError: socket was closed by rcssserver3d!")
exit()
def send(self) -> None:
"""
Send all committed messages
"""
if not self.__send_buff:
return
if len(select([self.__socket], [], [], 0.0)[0]) != 0:
logger.debug("Socket is readable while sending; keeping full-duplex command send.")
self.send_immediate(("".join(self.__send_buff)))
self.__send_buff = []
def commit(self, msg: str) -> None:
"""
Appends message to buffer
"""
self.__send_buff.append(msg)
def commit_and_send(self, msg: str = "") -> None:
"""
Appends a message to buffer and then sends the buffer
"""
self.commit(msg)
self.send()
def receive(self):
while True:
if (
self.__socket.recv_into(
self.__rcv_buffer, nbytes=4, flags=socket.MSG_WAITALL
) != 4
):
raise ConnectionResetError
msg_size = int.from_bytes(self.__rcv_buffer[:4], byteorder="big", signed=False)
if msg_size > self.__rcv_buffer_size:
self.__rcv_buffer_size = msg_size
self.__rcv_buffer = bytearray(self.__rcv_buffer_size)
if (
self.__socket.recv_into(
self.__rcv_buffer, nbytes=msg_size, flags=socket.MSG_WAITALL
) != msg_size
):
raise ConnectionResetError
self.world_parser.parse(
message=self.__rcv_buffer[:msg_size].decode()
)
# 如果socket没有更多数据就退出
if len(select([self.__socket], [], [], 0.0)[0]) == 0:
break
def commit_beam(self, pos2d: list, rotation: float) -> None:
assert len(pos2d) == 2
self.commit(f"(beam {pos2d[0]} {pos2d[1]} {rotation})")