| @ -0,0 +1,53 @@ | |||||
| #!/usr/bin/env python3.7 | |||||
| from Server import Server | |||||
| class ClientConnection(object): | |||||
| """ | |||||
| Client connection object | |||||
| """ | |||||
| def __init__(self, server: Server, conn, addr, buffSize=4096, encoding='utf8'): | |||||
| self.server = server | |||||
| self.conn = conn | |||||
| self.addr = addr | |||||
| self.name = '{}:{}'.format(addr[0], addr[1]) | |||||
| self.buffSize = buffSize | |||||
| self.encoding = encoding | |||||
| def serve(self) -> None: | |||||
| """ | |||||
| Server this connection and start lisneting for incoming | |||||
| message | |||||
| """ | |||||
| self.send( | |||||
| '[system] Type \':quit\' to quit this conversation') | |||||
| while True: | |||||
| msg = self.recv() | |||||
| if msg != ':quit' and len(msg) > 0: | |||||
| self.server.sendToAll(self, msg) | |||||
| else: | |||||
| self.send('[system] :goodbye:') | |||||
| break | |||||
| self.kill() | |||||
| def send(self, msg: str, end='\n') -> None: | |||||
| """ | |||||
| Wrapper method for socker.send and encode the message to | |||||
| set encoding | |||||
| """ | |||||
| self.conn.send(bytes(msg + end, self.encoding)) | |||||
| def recv(self) -> str: | |||||
| """ | |||||
| Wrapper method for socket.recv and decode the message from | |||||
| set encoding | |||||
| """ | |||||
| return self.conn.recv(self.buffSize).decode(self.encoding).split('\n')[0] | |||||
| def kill(self) -> None: | |||||
| """ | |||||
| Close this connection and remove it from server | |||||
| """ | |||||
| self.conn.close() | |||||
| self.server.removeClient(self) | |||||
| @ -0,0 +1,65 @@ | |||||
| #!/usr/bin/env python3.7 | |||||
| import socket # socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR | |||||
| import logging | |||||
| import threading | |||||
| from ClientConnection import ClientConnection | |||||
| class Server(object): | |||||
| """ | |||||
| A simple chat application server | |||||
| """ | |||||
| clients = [] | |||||
| soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |||||
| soc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |||||
| def __init__(self, host: str, port: int, maxClients=64): | |||||
| self.host = host | |||||
| self.port = port | |||||
| self.soc.bind((host, port)) | |||||
| self.maxClients = maxClients | |||||
| def run(self) -> None: | |||||
| """ | |||||
| Start the server and start to listen for incoming connection | |||||
| """ | |||||
| self.soc.listen(self.maxClients) | |||||
| logging.info('Start listening on {}:{} maximum {} clients'.format( | |||||
| self.host, self.port, self.maxClients)) | |||||
| while True: | |||||
| conn, addr = self.soc.accept() | |||||
| client = ClientConnection(self, conn, addr) | |||||
| msg = '{}:{} has joined the chat.'.format( | |||||
| client.addr[0], client.addr[1]) | |||||
| self._announce(msg) | |||||
| self.clients.append(client) | |||||
| threading.Thread(target=client.serve).start() | |||||
| def removeClient(self, client: ClientConnection) -> None: | |||||
| """ | |||||
| Remove client from this server list of connected clients | |||||
| """ | |||||
| msg = '{}:{} has left the chat.'.format(client.addr[0], client.addr[1]) | |||||
| self.clients.remove(client) | |||||
| self._announce(msg) | |||||
| def _announce(self, announcement: str) -> None: | |||||
| """ | |||||
| Send announcement from system to all connected clients | |||||
| """ | |||||
| msg = '[system] {}'.format(announcement) | |||||
| logging.info(msg) | |||||
| for client in self.clients: | |||||
| client.send(msg) | |||||
| def sendToAll(self, sender: ClientConnection, msg: str) -> None: | |||||
| """ | |||||
| Broadcast message from one client to other clients | |||||
| """ | |||||
| msg = '[{}:{}] {}'.format(sender.addr[0], sender.addr[1], msg) | |||||
| logging.info(msg) | |||||
| for client in self.clients: | |||||
| if client != sender: | |||||
| client.send(msg) | |||||
| @ -0,0 +1,51 @@ | |||||
| #!/usr/bin/env python3.7 | |||||
| import logging | |||||
| from Server import Server | |||||
| def initLogging(logFileName: str) -> None: | |||||
| """ | |||||
| Initialise the logging method | |||||
| """ | |||||
| logFormatter = logging.Formatter( | |||||
| fmt="[%(asctime)s][%(levelname)s] %(message)s", | |||||
| datefmt='%d-%b-%y %H:%M:%S') | |||||
| rootLogger = logging.getLogger() | |||||
| fileHandler = logging.FileHandler(logFileName, mode='a') | |||||
| fileHandler.setFormatter(logFormatter) | |||||
| rootLogger.addHandler(fileHandler) | |||||
| consoleHandler = logging.StreamHandler() | |||||
| consoleHandler.setFormatter(logFormatter) | |||||
| rootLogger.addHandler(consoleHandler) | |||||
| rootLogger.setLevel(logging.INFO) | |||||
| if __name__ == "__main__": | |||||
| import argparse | |||||
| parser = argparse.ArgumentParser() | |||||
| parser.add_argument('host', | |||||
| metavar='HOST', | |||||
| help='IP to listen.', | |||||
| type=str) | |||||
| parser.add_argument('port', | |||||
| metavar='PORT', | |||||
| help='Port to listen.', | |||||
| type=int) | |||||
| parser.add_argument('--logFile', | |||||
| metavar='LOGPATH', | |||||
| help='Path to log file, default is log.txt', | |||||
| type=str, | |||||
| required=False, | |||||
| default='log.txt') | |||||
| args = parser.parse_args() | |||||
| initLogging(args.logFile) | |||||
| try: | |||||
| Server(args.host, args.port).run() | |||||
| except KeyboardInterrupt: | |||||
| logging.info('Stopped by user input') | |||||