From d25bc1906d4ef405731da77264c49680f87c4873 Mon Sep 17 00:00:00 2001 From: myitinos Date: Tue, 23 Apr 2019 10:55:21 +0800 Subject: [PATCH] added server side script --- server/ClientConnection.py | 53 +++++++++++++++++++++++++++++++ server/Server.py | 65 ++++++++++++++++++++++++++++++++++++++ server/__main__.py | 51 ++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 server/ClientConnection.py create mode 100644 server/Server.py create mode 100644 server/__main__.py diff --git a/server/ClientConnection.py b/server/ClientConnection.py new file mode 100644 index 0000000..6d53a84 --- /dev/null +++ b/server/ClientConnection.py @@ -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) diff --git a/server/Server.py b/server/Server.py new file mode 100644 index 0000000..d8430a2 --- /dev/null +++ b/server/Server.py @@ -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) diff --git a/server/__main__.py b/server/__main__.py new file mode 100644 index 0000000..523f0ee --- /dev/null +++ b/server/__main__.py @@ -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')