@ -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') |