1+ import asyncio
2+ from asyncio import StreamWriter , StreamReader
13from enum import IntEnum
2- from pathlib import Path
3- from typing import Any , List , Callable , Optional
4+ from typing import Any , Awaitable , Optional , Callable
45
5- from netqasm .sdk .external import NetQASMConnection
6- from simulaqron .sdk import SimulaQronConnection , Socket
6+ from simulaqron .general .host_config import SocketsConfig
77
88
99class ServingStatus (IntEnum ):
@@ -51,37 +51,47 @@ def stop_serving(self) -> ServingStatus:
5151 return ServingStatus .STOP
5252
5353
54- class SimulaQronProtocol :
55- def __init__ (self , network_config : str | Path , name : str ):
56- self ._network_config = network_config
57- self ._node_name = name
58- # TODO - Load the network configuration in simulaqron!
59- pass
60-
61-
62- class SimulaQronClassicalClient (SimulaQronProtocol ):
63- def __init__ (self , network_config : str | Path , name : str ):
54+ class SimulaQronClassicalClient :
55+ def __init__ (self , sockets_config : SocketsConfig ):
6456 """
6557 Classical client used to send classical messages to remote nodes. The given node name
6658 must exist on the given network configuration.
6759
68- :param network_config: The path of the Network configuration.
69- :type network_config: str | Path
70- :param name: The name of the node to connect to. The name *must* exist in the network
71- configuration file.
72- :type name: str
60+ :param sockets_config: The sockets configuration for the whole network.
61+ :type sockets_config: SocketsConfig
7362 """
74- super (). __init__ ( network_config , name )
63+ self . _sockets_config = sockets_config
7564
76- def connect_to (self , node_name : str ) -> None :
65+ async def _run_client (self , hostname : str , port : int , callback : Callable [[StreamReader , StreamWriter ], Awaitable [None ]]):
66+ reader , writer = await asyncio .open_connection (hostname , port )
67+ await callback (reader , writer )
68+
69+
70+ def run_client (self , node_name : str , callback : Callable [[StreamReader , StreamWriter ], Awaitable [None ]]) -> None :
7771 """
78- Connects to the node with the given name.
72+ Connects to the node with the given name. Once the connection has been established,
73+ the given callback will be executed to start the interaction with the server.
74+ The given function must have the following signature::
75+
76+ async def connected_handler(reader: StreamReader, writer: StreamWriter):
77+ # Send a message to the server
78+ writer.write("Hello world!".encode("utf-8"))
79+ # Afterwards, you might want to receive an answer
80+ message = await reader.read(255)
81+ print(message.decode("utf-8"))
82+
83+ After calling this function, the
7984
8085 :param node_name: The name of the node to connect to. The name *must* exist in the
8186 configuration file given when constructing this client.
8287 :type node_name: str
88+ :param callback: The function to be called when the connection is established.
89+ :type callback: Callable[[StreamReader, StreamWriter], Awaitable[None]]
8390 """
84- pass
91+ if node_name not in self ._sockets_config .hostDict :
92+ raise RuntimeError (f"The node with name '{ node_name } ' is not on the network configuration." )
93+ socket_config = self ._sockets_config .hostDict [node_name ]
94+ asyncio .run (self ._run_client (socket_config .hostname , socket_config .port , callback ))
8595
8696 def send_message (self , message : str ) -> None :
8797 """
@@ -90,36 +100,46 @@ def send_message(self, message: str) -> None:
90100 :param message: The message to send.
91101 :type message: str
92102 """
103+ pass
93104
94105
95- class SimulaQronClassicalServer (SimulaQronProtocol ):
96- def __init__ (self , network_config : str | Path , name : str , simulaqron_connection : Optional [SimulaQronConnection ] = None ):
97- super ().__init__ (network_config , name )
98- self ._message_handlers : List [Callable [[SimulaQronState , str ], ServingStatus ]] = []
99- self ._connection = simulaqron_connection
100- self ._message_handlers : List [Callable [[SimulaQronState , str , NetQASMConnection ], ServingStatus ]] = []
101- # TODO - Define what else to do in the constructor
106+ class SimulaQronClassicalServer :
107+ def __init__ (self , sockets_config : SocketsConfig , name : str ):
108+ self ._node_name = name
109+ self ._sockets_data = sockets_config .hostDict [self ._node_name ]
110+ self ._connection_handler : Optional [Callable [[StreamReader , StreamWriter ], Awaitable [None ]]] = None
102111
103- def register_message_handler (self , handler : Callable [[SimulaQronState , str ], ServingStatus ]) -> None :
112+ def register_client_handler (self , handler : Callable [[StreamReader , StreamWriter ], Awaitable [ None ] ]) -> None :
104113 """
105- Registers the given function as a message handler. The given function must have the following signature::
114+ Registers the given function as a client handler. The given function must have the following signature::
106115
107- def handler(state: SimulaQronState, message: str, connection: NetQASMConnection) -> ServingStatus:
108- return ServingStatus.CONTINUE
116+ async def handler(reader: StreamReader, writer: StreamWriter):
117+ reader = await reader.read(255)
118+ ...
119+ # Handle a new connection here
120+ # E.g. send a response to the client
121+ writer.write("answer".encode("utf-8"))
109122
110- The passed function will be called once a message arrives form the remote.
111- The function must return a value to signal the server loop to keep handling or not.
112- Any other value that must be returned to the remote, must be passed using the ``add_return_values`` method
113- from the ``SimulaQronState`` object passed to the handler.
123+ The passed function will be called once a new client connects.
114124
115- :param handler: The function to be used as a handler.
116- :type handler: Callable[[SimulaQronState, str], ServingStatus]
125+ :param handler: The function to be used as a handler. This *must* be a python coroutine
126+ (python "async" function).
127+ :type handler: Callable[[StreamReader, StreamWriter], Awaitable[None]]
117128 """
118- self ._message_handlers .append (handler )
129+ self ._connection_handler = handler
130+
131+ async def _build_server (self ):
132+ if self ._connection_handler is None :
133+ print ("No connection handler - Did you forget to register it?" )
134+ return
135+ server = await asyncio .start_server (self ._connection_handler , self ._sockets_data .hostname , self ._sockets_data .port )
136+ print (f"BOB INFO: === { self ._node_name } Server ===" )
137+ print (f"BOB DEBUG: Listening on { self ._sockets_data .hostname } :{ self ._sockets_data .port } " )
138+ async with server :
139+ await server .serve_forever ()
119140
120141 def start_serving (self ) -> None :
121142 """
122143 Starts the serving the clients using the registered handlers.
123144 """
124- # TODO - Implement
125- pass
145+ asyncio .run (self ._build_server ())
0 commit comments