From aa793be1dbdf3d8b35857f22ee1e187613b66333 Mon Sep 17 00:00:00 2001 From: erichhasl Date: Sun, 15 Oct 2017 18:24:35 +0200 Subject: [PATCH] add python server and client --- .gitignore | 6 ++ python/client.py | 133 +++++++++++++++++++++++++++++++++ python/server.py | 187 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 326 insertions(+) create mode 100644 .gitignore create mode 100644 python/client.py create mode 100644 python/server.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c8f64c --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.swp +*.swo +*.swn +*.swm +python/__pycache__/* +*.pyc diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..f6f43de --- /dev/null +++ b/python/client.py @@ -0,0 +1,133 @@ +import socket +import struct +import ssl +from threading import Thread +import select + + +class SocketClient: + + NO_BUFFERING = 0 + LENGTH_BUFFERING = 1 + DELIMITER_BUFFERING = 2 + + def __init__(self, ssl_cert=None, buffering=LENGTH_BUFFERING, + delimiter='\n'): + self.terminated = False + self.connected = False + self.ssl_cert = ssl_cert + self.buffering = buffering + self.delimiter = delimiter + self.left = b'' + print("Initialized socket client") + + def connect(self, host, port): + try: + self.socket = socket.socket() + # if using a ssl certificate, wrap the socket using ssl + if self.ssl_cert: + self.socket = ssl.wrap_socket(self.socket, + ca_certs=self.ssl_cert, + cert_reqs=ssl.CERT_REQUIRED) + self.socket.connect((host, port)) + self.connected = True + return True + except socket.error: + print("ERROR: Error while connecting!") + self.connected = False + return False + + def start(self): + print("Client has started") + Thread(target=self.handle_server).start() + + def disconnect(self): + print("disconnecting") + self.connected = False + self.send("quit") + self.terminated = True + self.socket.close() + + def send(self, data): + try: + if self.buffering is SocketClient.DELIMITER_BUFFERING: + msg = (data + self.delimiter).encode() + elif self.buffering is SocketClient.LENGTH_BUFFERING: + msg = struct.pack('>I', len(data)) + data.encode() + else: + msg = data.encode() + self.socket.sendall(msg) + except Exception as e: + print("ERROR: Error while sending", e) + + def handle_server(self): + while not self.terminated: + try: + read_sockets, write_sockets, in_error = \ + select.select([self.socket, ], [self.socket, ], [], 5) + except select.error: + print("FATAL ERROR: Connection error") + self.socket.shutdown(2) + self.socket.close() + if len(read_sockets) > 0: + if self.buffering is SocketClient.DELIMITER_BUFFERING: + recv, self.left = recvuntil(self.socket, self.delimiter, + self.left) + elif self.buffering is SocketClient.LENGTH_BUFFERING: + raw_msglen = recvall(self.socket, 4) + if not raw_msglen: + print("Connection closed") + self.on_quit() + return None + msglen = struct.unpack('>I', raw_msglen)[0] + recv = recvall(self.socket, msglen) + elif self.buffering is SocketClient.NO_BUFFERING: + recv = self.socket.recv(2048) + + if len(recv) > 0: + reply = self.on_receive(recv) + if len(write_sockets) > 0 and reply: + self.socket.send(reply) + print("finished handling server") + + def on_receive(self, query): + pass + + def on_quit(self): + pass + + +def recvall(sock, n): + data = b'' + while len(data) < n: + packet = sock.recv(n - len(data)) + if not packet: + return None + data += packet + return data + + +def recvuntil(sock, delimiter, left_over): + data = left_over + left = b'' + while True: + packet = sock.recv(512) + if not packet: + return None + data += packet + idx = data.find(delimiter.encode()) + if idx >= 0: + data = data[:idx] + left = data[idx+len(delimiter):] + break + return data, left + +if __name__ == '__main__': + client = SocketClient(buffering=SocketClient.DELIMITER_BUFFERING, + delimiter='\r\n') + client.connect('localhost', 4242) + client.start() + client.on_receive = lambda x: print("-->", x) + while True: + x = input("> ") + client.send(x) diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..1c74b90 --- /dev/null +++ b/python/server.py @@ -0,0 +1,187 @@ +import socket +import select +import ssl +import struct +import threading +from threading import Thread +import logging as log + + +class SocketServer: + + NO_BUFFERING = 0 + LENGTH_BUFFERING = 1 + DELIMITER_BUFFERING = 2 + + def __init__(self, ssl_cert=None, ssl_key=None, buffering=LENGTH_BUFFERING, + delimiter='\n'): + self.terminated = False + self.connected = False + self.buffering = buffering + self.ssl_cert, self.ssl_key = ssl_cert, ssl_key + self.delimiter = delimiter + log.debug("Initialized socket server") + + def connect(self, host, port): + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + if self.ssl_cert and self.ssl_key: + self.socket = ssl.wrap_socket(self.socket, + certfile=self.ssl_cert, + keyfile=self.ssl_key, + server_side=True) + try: + self.socket.bind((host, port)) + except socket.error as e: + log.critical("Error: %s", str(e)) + self.socket.listen(5) + + def start(self): + Thread(target=self.wait_for_connections).start() + + def wait_for_connections(self): + while not self.terminated: + self.accept_connection() + + def accept_connection(self): + if self.terminated: + return + try: + conn, addr = self.socket.accept() + except OSError as e: + log.error("OSError while accepting connection: %s", str(e)) + return + Thread(target=self.pre_handle_client, args=(conn, addr)).start() + + def clean_up(self): + self.terminated = True + self.socket.shutdown(socket.SHUT_RDWR) + self.socket.close() + + def pre_handle_client(self, conn, addr): + log.info("Established connection to %s", addr) + client = ClientConnection(conn, addr, self.buffering, self.delimiter) + connected = self.on_connect(client) + + while not self.terminated and connected: + try: + read, write, error = select.select([client.conn, ], + [client.conn, ], + [], 5) + except (select.error, ValueError): + log.warn("FATAL ERROR: Connection error %s", addr) + self.on_disconnect(client) + break + if len(read) > 0: + text = client.recv() + if text is None: + log.critical("no text --> disconnecting") + self.on_disconnect(client) + break + log.debug("Received: %s %s", text, addr) + try: + reply = self.on_receive(client, text) + except TypeError: + pass + else: + if reply is not None: + client.send(reply) + + log.critical("Terminating connection with %s", addr) + client.quit() + + def on_receive(self, client, query): + return query + + def on_connect(self, client): + return True + + def on_disconnect(self, client): + pass + + +class ClientConnection: + + def __init__(self, conn, addr, buffering, delimiter): + self.conn = conn + self.addr = addr + self.lock = threading.Lock() + self.buffering, self.delimiter = buffering, delimiter + self.left = b'' + + def ask(self, key, question): + self.send(key, question) + self.conn.settimeout(2) + return self.recv() + + def recv(self): + try: + if self.buffering is SocketServer.DELIMITER_BUFFERING: + recv, self.left = recvuntil(self.conn, self.delimiter, + self.left) + elif self.buffering is SocketServer.LENGTH_BUFFERING: + raw_msglen = recvall(self.conn, 4) + if not raw_msglen: + log.debug("%s No msglen: EOF", self.addr) + return None + msglen = struct.unpack('>I', raw_msglen)[0] + recv = recvall(self.conn, msglen) + else: + recv = self.conn.recv(1024) + return recv.decode("utf-8") + except Exception as e: + log.critical("%s Error while receiving: %s", self.addr, str(e)) + return None + + def send(self, query): + if self.buffering is SocketServer.DELIMITER_BUFFERING: + msg = (query + self.delimiter).encode() + elif self.buffering is SocketServer.LENGTH_BUFFERING: + msg = struct.pack('>I', len(query)) + query.encode() + else: + msg = query.encode() + with self.lock: + try: + self.conn.sendall(msg) + except Exception as e: + log.debug("%s Failed to send %s: %s", self.addr, msg, e) + + def on_recv(self): + pass + + def quit(self): + self.conn.close() + + +def recvall(sock, n): + data = b'' + while len(data) < n: + packet = sock.recv(n - len(data)) + if not packet: + return None + data += packet + return data + + +def recvuntil(sock, delimiter, left_over): + data = left_over + left = b'' + while True: + packet = sock.recv(512) + if not packet: + return None + data += packet + idx = data.find(delimiter.encode()) + if idx >= 0: + data = data[:idx] + left = data[idx+len(delimiter):] + break + return data, left + +if __name__ == '__main__': + log.basicConfig(level=log.DEBUG) + server = SocketServer(buffering=SocketServer.DELIMITER_BUFFERING, + delimiter='\r\n') + server.connect('localhost', 4242) + server.start()