def test_basic_server_client(): # Start server server = Server() server.bind() assert server._listening is False server.serve() assert server._listening is True assert server.active_connections == 0 assert server.accepted_connections == 0 # No connection to accept assert server.accept_connection(timeout=0) == -1 # Connect client client = Client(host=server.ip, port=server.port) client.connect() # Server accepts connection conn_idx = server.accept_connection() assert conn_idx == 0 assert server.active_connections == 1 assert server.accepted_connections == 1 # Client sends message msg = b'Hello' _, size = client.send(msg) assert size == len(msg) # Server receives received = server.receive(size, conn_idx=conn_idx) assert received == msg # Server sends reply msg = b'World' server.send(msg, conn_idx) received = client.receive(1024) assert received == msg client.close() server.close()
def test_two_clients(): server = Server() server.bind() server.serve() client1 = Client(host=server.ip, port=server.port) client2 = Client(host=server.ip, port=server.port) # Client 1 connect and send message client1.connect() conn_1 = server.accept_connection(5) msg1 = b'Hello!' _, size1 = client1.send(msg1) # Client 2 connect and send message before server received msg from 1 client2.connect() conn_2 = server.accept_connection(5) msg2 = b'Hey' _, size2 = client2.send(msg2) # Server responds to 1 assert server.receive(size1, conn_idx=conn_1) == msg1 resp1 = b'Yo1' server.send(resp1, conn_idx=conn_1) # Server responds to 2 assert server.receive(size2, conn_idx=conn_2) == msg2 resp2 = b'Yo2' server.send(resp2, conn_idx=conn_2) # Clients receiving responses assert client1.recv(1024) == resp1 assert client2.recv(1024) == resp2 client1.close() client2.close() server.close()
class TCPServer(Driver): """ Driver for a server that can send and receive messages over TCP. Supports multiple connections. This is built on top of the :py:class:`testplan.common.utils.sockets.server.Server` class, which provides equivalent functionality and may be used outside of MultiTest. :param host: Host name to bind to. Default: 'localhost' :type host: ``str`` :param port: Port number to bind to. Default: 0 (Random port) :type port: ``int`` Also inherits all :py:class:`~testplan.testing.multitest.driver.base.Driver`` options. """ CONFIG = TCPServerConfig def __init__(self, **options): super(TCPServer, self).__init__(**options) self._host = None self._port = None self._server = None @property def host(self): """Target host name.""" return self._host @property def port(self): """Port number assigned.""" return self._port @property def socket(self): """ Returns the underlying ``socket`` object """ return self._server.socket def accept_connection(self, timeout=10): """Doc from Server.""" return self._server.accept_connection(timeout=timeout) accept_connection.__doc__ = Server.accept_connection.__doc__ def send_text(self, msg, standard='utf-8', **kwargs): """ Encodes to bytes and calls :py:meth:`TCPServer.send <testplan.testing.multitest.driver.tcp.server.TCPServer.send>`. """ return self.send(bytes(msg.encode(standard)), **kwargs) def send(self, msg, conn_idx=None, timeout=30): """Doc from Server.""" return self._server.send(msg=msg, conn_idx=conn_idx, timeout=timeout) send.__doc__ = Server.send.__doc__ def receive_text(self, standard='utf-8', **kwargs): """ Calls :py:meth:`TCPServer.receive <testplan.testing.multitest.driver.tcp.server.TCPServer.receive>` and decodes received bytes. """ return self.receive(**kwargs).decode(standard) def receive(self, size=None, conn_idx=None, timeout=30): """Receive bytes from the given connection.""" received = None timeout_info = TimeoutExceptionInfo() try: receive_kwargs = dict(conn_idx=conn_idx, timeout=timeout or 0) if size is None: receive_kwargs['size'] = 1024 receive_kwargs['wait_full_size'] = False else: receive_kwargs['size'] = size receive_kwargs['wait_full_size'] = True received = self._server.receive(**receive_kwargs) except socket.timeout: if timeout is not None: raise TimeoutException( 'Timed out waiting for message on {0}. {1}'.format( self.cfg.name, timeout_info.msg())) return received def starting(self): """Starts the TCP server.""" super(TCPServer, self).starting() self._server = Server(host=self.cfg.host, port=self.cfg.port) self._server.bind() self._server.serve() self._host = self.cfg.host self._port = self._server.port def _stop_logic(self): if self._server: self._server.close() def stopping(self): """Stops the TCP server.""" super(TCPServer, self).stopping() self._stop_logic() def aborting(self): """Abort logic that stops the server.""" self._stop_logic()