I am creating a python chat app and with my client, I want to be able to send input()
to the server while also getting messages from the server, basically receiving messages while ALSO being able to send input(), i've "achieved" this by using a thread to receive messages, but I end up with a broken pipe error as soon as the client uses '!quit', which is the command to quit.
Here is the client code i've made:
import socketimport threadingimport sysimport timeclass Client: # data buffer size DATA_BUFFER_SIZE = 1024 def __init__(self, host, port): # create client socket then connect to server self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.client.connect((host, port)) # receive messages thread # daemon is true so it runs in the background self.receive_messages = threading.Thread(target=self.receive_messages, daemon=True) self.receive_messages.start() def close(self, send_message=True): if send_message: # send !quit to the server so it knows we are quitting the program self.client.send('!quit'.encode()) # wait for the server to send us 'ack' so then we know that the server knows we are about to quit # receive function waits for a message from the server, so it wont continue executing code, it will wait for data to be received print(self.client.recv(Client.DATA_BUFFER_SIZE)) # finally, quit print('closing client') self.client.close() # exit program with error code 0, meaning no error sys.exit(0) # receive messages function for receive messages thread # this lets the client receive new messages while also allowing the user to send messages def receive_messages(self): while True: time.sleep(1) self.client.send('ack'.encode()) msg = self.client.recv(Client.DATA_BUFFER_SIZE) if msg and msg.decode() == '!ack': break # loop function def loop(self): try: while True: msg = input('message -> ') # send input to the server self.client.send(msg.encode()) # receive message from server print(self.client.recv(Client.DATA_BUFFER_SIZE)) if msg == '!quit': # we've sent '!quit' already, so set send_message to false self.close(send_message=False) except KeyboardInterrupt: self.close()def main(): # create client and loop client = Client('172.31.196.42', 7777) client.loop()if __name__ == '__main__': main()
This might not help but here is the server code if needed:
import threadingimport socketimport sysclass Server: # buffer size for data that should be sent around DATA_BUFFER_SIZE = 1024 # connection class to make it easier handling multiple clients class Connection: def __init__(self, server): self.conn, self.addr = server.accept() print(f'accepted new connection from {self.addr}') self.user = 'Anonymous' def close(self): print(f'closing connection to user {self.user}, addr: {self.addr}') # close connection self.conn.close() def __init__(self, port): # get ip of host computer host = socket.gethostbyname(socket.gethostname()) # create server socket self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server.bind((host, port)) self.server.listen() print(f'server binded to {host}:{port}') self.running = True # clients self.clients = [] # accept connections thread # this thread will run in the background so the server will accept new clients while also handling current clients # daemon means it will run in the background, so we will set daemon to True self.accept_connections = threading.Thread(target=self.accept_connections, daemon=True) self.accept_connections.start() # close function def close(self): print('closing server') # loop through all the client connections, then close them for client in self.clients: client.close() # close the main server self.server.close() def close_client(self, client_id): # first close the connection self.clients[client_id].close() # then pop the client of the list self.clients.pop(client_id) # function that accepts connections in the background def accept_connections(self): while True: self.clients.append(Server.Connection(self.server)) # broadcast function - sends a message to all clients def broadcast(self, msg): for client in self.clients: # we must receive data before sending data client.conn.recv(Server.DATA_BUFFER_SIZE) client.conn.send(msg) def loop(self): # try except statement to catch when the server host presses CTRL+C, then close the server try: while True: for client in self.clients: # recieve message from client msg = client.conn.recv(Server.DATA_BUFFER_SIZE).decode() print(f'received message from {client.user}: {msg}') client.conn.send('ack'.encode()) if msg == '!quit': # the message is !quit, meaning we will now destroy the client out of the giant list of clients # close the client # self.clients.index(client) finds the id or index of the actual client self.close_client(self.clients.index(client)) else: # broadcast the message to all clients self.broadcast(f'{client.user}: {msg}'.encode()) except KeyboardInterrupt: # close server self.close() # exit program with code 0 meaning the program is fine sys.exit(0)def main(): # start server on port 7777 and loop server = Server(7777) server.loop()if __name__ == '__main__': main()