... continued
Continues the discussion on implementing a chat server.
We'll cover the following...
The code widget below runs a simulation of the chat server with four users that message each other (including themselves) randomly.
from threading import Threadfrom threading import Lockimport socket, time, randomclass ChatServer:def __init__(self, port):self.port = portself.lock = Lock()self.clients = {}def handle_client(self, client_socket):user = "unknown"while True:data = client_socket.recv(4096).decode()command, param = data.split(",")# register handlerif command == "register":print("\n{0} registered\n".format(param))with self.lock:self.clients[param] = client_socketuser = paramclient_socket.send("ack".encode())# list handlerif command == "list":with self.lock:names = self.clients.keys()names = ",".join(names)client_socket.send(names.encode())# chat handlerif command == "chat":to_socket = Nonewith self.lock:if param in self.clients:to_socket = self.clients[param]if to_socket is not None:to_socket.send(("{0} says hi\n".format(user)).encode())else:print("\nNo user by the name <{0}>\n".format(param))def run_server(self):# networking stuff to setup the connection, that the# can ignoresocket_connection = socket.socket()socket_connection.bind(('', self.port))socket_connection.listen(5)# perpetually listen for new connectionswhile True:client_socket, addr = socket_connection.accept()# spawn a thread to deal with a new client and immediately go back to# listening for new incoming connectionsThread(target=self.handle_client, args=(client_socket,), daemon=True).start()class User:def __init__(self, name, server_host, server_port):self.name = nameself.server_port = server_portself.server_host = server_hostdef receive_messages(self, server_socket):while True:print("\n{0} received: {1}\n".format(self.name, server_socket.recv(4096).decode()))def run_cleint(self):server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server_socket.connect((self.server_host, self.server_port))# register and receive ackserver_socket.send("register,{0}".format(self.name).encode())server_socket.recv(4096).decode()# wait for friends to jointime.sleep(3)# get list of friendsserver_socket.send("list,friends".encode())list_of_friends = server_socket.recv(4096).decode().split(",")num_friends = len(list_of_friends)# start listening for incoming messagesThread(target=self.receive_messages, args=(server_socket,), daemon=True).start()while True:# randomly select a friend and send a messagefriend = list_of_friends[random.randint(0, num_friends - 1)]server_socket.send("chat,{0}".format(friend).encode())time.sleep(random.randint(2, 6))if __name__ == "__main__":server_port = random.randint(10000, 65000)server_host = "127.0.0.1"server = ChatServer(server_port)# start serverThread(target=server.run_server, daemon=True).start()time.sleep(1)# jane gets onlinejane = User("Jane", server_host, server_port)Thread(target=jane.run_cleint, daemon=True).start()zak = User("Zak", server_host, server_port)Thread(target=zak.run_cleint, daemon=True).start()abhishek = User("Abhishek", server_host, server_port)Thread(target=abhishek.run_cleint, daemon=True).start()igor = User("Natasha", server_host, server_port)Thread(target=igor.run_cleint, daemon=True).start()time.sleep(30)
Note that our implementation makes generous use of daemon threads so that when the main program thread exits, all the threads are killed and the code-widget doesn't time out. The output will show various users receiving hi messages from other users.
Next, we'll pivot our implementation to an asynchronous one.
Asynchronous Implementation
The asynchronous paradigm to implement a chat server involves using a single thread, usually called the event loop. In the case of Python, we'll not implement an event loop from scratch rather leverage asyncio
module for the job.
Similar to the multithreaded approach, we need an entity to continuously listen for new incoming connections. We'll use the asyncio's start_server()
coroutine. The coroutine takes in the server's hostname and a port to listen for incoming connections on. Once a client connects with the server, the coroutine invokes a user-specified callback to invoke. The callback includes the details to read and write from the connected client. The code snippet to start the server would look like as follows:
server_port = random.randint(10000, 65000)
server_host = "127.0.0.1"
chat_server = ChatServer(server_port)
server = await