From 11b3c9982ecea763bd559d014a8d223f4b273330 Mon Sep 17 00:00:00 2001 From: Shang Date: Sun, 14 Oct 2018 21:01:22 -0400 Subject: [PATCH 01/10] Python3 version --- README.md | 31 ++----- client_test.py | 17 ++++ jsonsocket.py | 248 +++++++++++++++++++++++++------------------------ server_test.py | 12 +++ 4 files changed, 164 insertions(+), 144 deletions(-) create mode 100644 client_test.py create mode 100644 server_test.py diff --git a/README.md b/README.md index 3dae46a..58621a0 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,16 @@ jsonsocket ========== -This is a small Python library for sending data over sockets. +This is a single-file Python3 library for sending json-like data over sockets. -It allows sending lists, dictionaries, strings, etc. It can handle very large data (I've tested it with 10GB of data). Any JSON-serializable data is accepted. +(Original repos was Python2, forked it to support Python3) -Examples: +It allows sending lists, dictionaries, strings, etc. Any JSON-serializable data is accepted. -```python -from jsonsocket import Client, Server - -host = 'localhost' -port = '8000' - -# Client code: -client = Client() -client.connect(host, port).send({'some_list': [123, 456]}) -response = client.recv() -# response now is {'data': {'some_list': [123, 456]}} -client.close() - - -# Server code: -server = Server(host, port) -server.accept() -data = server.recv() -# data now is: {'some_list': [123, 456]} -server.send({'data': data}).close() +Example & test: +```bash +$python3 server_test.py & +$python3 client_test.py ``` + diff --git a/client_test.py b/client_test.py new file mode 100644 index 0000000..e89be72 --- /dev/null +++ b/client_test.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +from jsonsocket import Client, Server + +host = 'localhost' +port = 8001 + +# Client code: +client = Client() +client.connect(host, port).send({'some_list': [123, 456]}) +response = client.recv() +# response now is {'data': {'some_list': [123, 456]}} +client.close() + + + + diff --git a/jsonsocket.py b/jsonsocket.py index 9dfe7a6..2f61d88 100644 --- a/jsonsocket.py +++ b/jsonsocket.py @@ -1,134 +1,140 @@ -import json, socket +#!/usr/bin/env python3 +import json +import socket + class Server(object): - """ - A JSON socket server used to communicate with a JSON socket client. All the - data is serialized in JSON. How to use it: - - server = Server(host, port) - while True: - server.accept() - data = server.recv() - # shortcut: data = server.accept().recv() - server.send({'status': 'ok'}) - """ - - backlog = 5 - client = None - - def __init__(self, host, port): - self.socket = socket.socket() - self.socket.bind((host, port)) - self.socket.listen(self.backlog) - - def __del__(self): - self.close() - - def accept(self): - # if a client is already connected, disconnect it - if self.client: - self.client.close() - self.client, self.client_addr = self.socket.accept() - return self - - def send(self, data): - if not self.client: - raise Exception('Cannot send data, no client is connected') - _send(self.client, data) - return self - - def recv(self): - if not self.client: - raise Exception('Cannot receive data, no client is connected') - return _recv(self.client) - - def close(self): - if self.client: - self.client.close() - self.client = None - if self.socket: - self.socket.close() - self.socket = None + """ + A JSON socket server used to communicate with a JSON socket client. All the + data is serialized in JSON. How to use it: + + server = Server(host, port) + while True: + server.accept() + data = server.recv() + # shortcut: data = server.accept().recv() + server.send({'status': 'ok'}) + """ + + backlog = 5 + client = None + + def __init__(self, host, port): + self.socket = socket.socket() + self.socket.bind((host, port)) + self.socket.listen(self.backlog) + + def __del__(self): + self.close() + + def accept(self): + # if a client is already connected, disconnect it + if self.client: + self.client.close() + self.client, self.client_addr = self.socket.accept() + return self + + def send(self, data): + if not self.client: + raise Exception('Cannot send data, no client is connected') + _send(self.client, data) + return self + + def recv(self): + if not self.client: + raise Exception('Cannot receive data, no client is connected') + return _recv(self.client) + + def close(self): + if self.client: + self.client.close() + self.client = None + if self.socket: + self.socket.close() + self.socket = None class Client(object): - """ - A JSON socket client used to communicate with a JSON socket server. All the - data is serialized in JSON. How to use it: - - data = { - 'name': 'Patrick Jane', - 'age': 45, - 'children': ['Susie', 'Mike', 'Philip'] - } - client = Client() - client.connect(host, port) - client.send(data) - response = client.recv() - # or in one line: - response = Client().connect(host, port).send(data).recv() - """ - - socket = None - - def __del__(self): - self.close() - - def connect(self, host, port): - self.socket = socket.socket() - self.socket.connect((host, port)) - return self - - def send(self, data): - if not self.socket: - raise Exception('You have to connect first before sending data') - _send(self.socket, data) - return self - - def recv(self): - if not self.socket: - raise Exception('You have to connect first before receiving data') - return _recv(self.socket) - - def recv_and_close(self): - data = self.recv() - self.close() - return data - - def close(self): - if self.socket: - self.socket.close() - self.socket = None + """ + A JSON socket client used to communicate with a JSON socket server. All the + data is serialized in JSON. How to use it: + + data = { + 'name': 'Patrick Jane', + 'age': 45, + 'children': ['Susie', 'Mike', 'Philip'] + } + client = Client() + client.connect(host, port) + client.send(data) + response = client.recv() + # or in one line: + response = Client().connect(host, port).send(data).recv() + """ + + socket = None + + def __del__(self): + self.close() + + def connect(self, host, port): + self.socket = socket.socket() + self.socket.connect((host, port)) + return self + + def send(self, data): + if not self.socket: + raise Exception('You have to connect first before sending data') + _send(self.socket, data) + return self + + def recv(self): + if not self.socket: + raise Exception('You have to connect first before receiving data') + return _recv(self.socket) + + def recv_and_close(self): + data = self.recv() + self.close() + return data + + def close(self): + if self.socket: + self.socket.close() + self.socket = None ## helper functions ## def _send(socket, data): - try: - serialized = json.dumps(data) - except (TypeError, ValueError), e: - raise Exception('You can only send JSON-serializable data') - # send the length of the serialized data first - socket.send('%d\n' % len(serialized)) - # send the serialized data - socket.sendall(serialized) + try: + serialized = json.dumps(data).encode('utf-8') + print(serialized) + except (TypeError, ValueError) as e: + raise Exception('You can only send JSON-serializable data') + # send the length of the serialized data first + message = '{}\n'.format(len(serialized)).encode('utf-8') + socket.send(message) + # send the serialized data + socket.sendall(serialized) + def _recv(socket): - # read the length of the data, letter by letter until we reach EOL - length_str = '' - char = socket.recv(1) - while char != '\n': - length_str += char + # read the length of the data, letter by letter until we reach EOL + length_str = '' char = socket.recv(1) - total = int(length_str) - # use a memoryview to receive the data chunk by chunk efficiently - view = memoryview(bytearray(total)) - next_offset = 0 - while total - next_offset > 0: - recv_size = socket.recv_into(view[next_offset:], total - next_offset) - next_offset += recv_size - try: - deserialized = json.loads(view.tobytes()) - except (TypeError, ValueError), e: - raise Exception('Data received was not in JSON format') - return deserialized + while char != b'\n': + length_str += char.decode('utf-8') + char = socket.recv(1) + total = int(length_str) + # use a memoryview to receive the data chunk by chunk efficiently + view = memoryview(bytearray(total)) + next_offset = 0 + while total - next_offset > 0: + recv_size = socket.recv_into(view[next_offset:], total - next_offset) + next_offset += recv_size + try: + deserialized = json.loads(view.tobytes().decode('utf-8')) + except (TypeError, ValueError) as e: + raise Exception('Data received was not in JSON format') + return deserialized diff --git a/server_test.py b/server_test.py new file mode 100644 index 0000000..8cee0d5 --- /dev/null +++ b/server_test.py @@ -0,0 +1,12 @@ +from jsonsocket import Server + +host = 'localhost' +port = 8001 + +# Server code: +server = Server(host, port) +server.accept() +data = server.recv() +# data now is: {'some_list': [123, 456]} +server.send({'data': data}).close() + From 02910abbe83b26ce8a237e977878c5a0efcfa055 Mon Sep 17 00:00:00 2001 From: Xavier Tolza Date: Thu, 6 Dec 2018 16:19:00 +0100 Subject: [PATCH 02/10] Turned into a pip module --- jsonsocket/__init__.py | 0 jsonsocket.py => jsonsocket/jsonsocket.py | 0 setup.py | 12 ++++++++++++ client_test.py => test/client_test.py | 2 +- server_test.py => test/server_test.py | 2 +- 5 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 jsonsocket/__init__.py rename jsonsocket.py => jsonsocket/jsonsocket.py (100%) create mode 100644 setup.py rename client_test.py => test/client_test.py (85%) rename server_test.py => test/server_test.py (82%) diff --git a/jsonsocket/__init__.py b/jsonsocket/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/jsonsocket.py b/jsonsocket/jsonsocket.py similarity index 100% rename from jsonsocket.py rename to jsonsocket/jsonsocket.py diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..b9d30f9 --- /dev/null +++ b/setup.py @@ -0,0 +1,12 @@ +from setuptools import setup + +name = 'jsonsocket' + +setup( + name=name, + version='1.0', + description='This is a small Python library for sending data over sockets. ', + author='github', + author_email='', + packages=[name], #same as name +) \ No newline at end of file diff --git a/client_test.py b/test/client_test.py similarity index 85% rename from client_test.py rename to test/client_test.py index e89be72..6aec33a 100644 --- a/client_test.py +++ b/test/client_test.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from jsonsocket import Client, Server +from jsonsocket.jsonsocket import Client host = 'localhost' port = 8001 diff --git a/server_test.py b/test/server_test.py similarity index 82% rename from server_test.py rename to test/server_test.py index 8cee0d5..95948aa 100644 --- a/server_test.py +++ b/test/server_test.py @@ -1,4 +1,4 @@ -from jsonsocket import Server +from jsonsocket.jsonsocket import Server host = 'localhost' port = 8001 From 86922b54ff85d70c1dda49facf99446ae683ab5c Mon Sep 17 00:00:00 2001 From: Xavier Tolza Date: Fri, 7 Dec 2018 13:41:14 +0100 Subject: [PATCH 03/10] Added UDP, async thread, pandas and numpy support --- .gitignore | 2 + jsonsocket/helpers.py | 56 +++++++++++++ jsonsocket/jsonsocket.py | 140 --------------------------------- jsonsocket/serialize.py | 50 ++++++++++++ jsonsocket/tcp.py | 166 +++++++++++++++++++++++++++++++++++++++ jsonsocket/udp.py | 63 +++++++++++++++ jsonsocket/udp_async.py | 71 +++++++++++++++++ requirements.txt | 1 + setup.py | 1 + test/__init__.py | 0 test/client_test.py | 17 ---- test/server_test.py | 12 --- test/test_TCP.py | 67 ++++++++++++++++ test/test_UDP.py | 44 +++++++++++ 14 files changed, 521 insertions(+), 169 deletions(-) create mode 100644 jsonsocket/helpers.py delete mode 100644 jsonsocket/jsonsocket.py create mode 100644 jsonsocket/serialize.py create mode 100644 jsonsocket/tcp.py create mode 100644 jsonsocket/udp.py create mode 100644 jsonsocket/udp_async.py create mode 100644 requirements.txt create mode 100644 test/__init__.py delete mode 100644 test/client_test.py delete mode 100644 test/server_test.py create mode 100644 test/test_TCP.py create mode 100644 test/test_UDP.py diff --git a/.gitignore b/.gitignore index 51cbe85..9b0255b 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,5 @@ coverage.xml # Sphinx documentation docs/_build/ +# Jetbrains IDE +.idea \ No newline at end of file diff --git a/jsonsocket/helpers.py b/jsonsocket/helpers.py new file mode 100644 index 0000000..7c0e093 --- /dev/null +++ b/jsonsocket/helpers.py @@ -0,0 +1,56 @@ +from time import time + +from jsonsocket.serialize import serialize, deserialize +from socket import timeout as tmout + + +class TimeoutError(Exception): + pass + + +def send(socket, data, socket_type="tcp", *args): + serialized = serialize(data) + # send the length of the serialized data first + message = '{}\n'.format(len(serialized)).encode('utf-8') + + if socket_type == "tcp": + socket.send(message) + # send the serialized data + socket.sendall(serialized) + elif socket_type == "udp": + socket.sendto(message + serialized + "\n", *args) + + +def receive(socket, socket_type="tcp", timeout=None): + # read the length of the data, letter by letter until we reach EOL + length_str = '' + tcp = socket_type == "tcp" + t0 = time() + if tcp: + char = socket.recv(1) + while char != b'\n' and (timeout is None or (timeout is not None and (time() - t0) < timeout)): + length_str += char.decode('utf-8') + char = socket.recv(1) + if length_str == '': + raise TimeoutError("Timout listening for data") + total = int(length_str) + # use a memoryview to receive the data chunk by chunk efficiently + view = memoryview(bytearray(total)) + next_offset = 0 + while total - next_offset > 0: + recv_size = socket.recv_into(view[next_offset:], total - next_offset) + next_offset += recv_size + deserialized = deserialize(view.tobytes()) + return deserialized + else: + if timeout: + socket.settimeout(timeout) + try: + char, addr = socket.recvfrom(2048 ** 2) + length, char = char.split("\n")[:2] + if len(char) != int(length): + raise Exception("Incorrect transmitted length") + deserialized = deserialize(char) + return deserialized, addr + except tmout: + raise TimeoutError("Timout listening for data") diff --git a/jsonsocket/jsonsocket.py b/jsonsocket/jsonsocket.py deleted file mode 100644 index 2f61d88..0000000 --- a/jsonsocket/jsonsocket.py +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env python3 -import json -import socket - - -class Server(object): - """ - A JSON socket server used to communicate with a JSON socket client. All the - data is serialized in JSON. How to use it: - - server = Server(host, port) - while True: - server.accept() - data = server.recv() - # shortcut: data = server.accept().recv() - server.send({'status': 'ok'}) - """ - - backlog = 5 - client = None - - def __init__(self, host, port): - self.socket = socket.socket() - self.socket.bind((host, port)) - self.socket.listen(self.backlog) - - def __del__(self): - self.close() - - def accept(self): - # if a client is already connected, disconnect it - if self.client: - self.client.close() - self.client, self.client_addr = self.socket.accept() - return self - - def send(self, data): - if not self.client: - raise Exception('Cannot send data, no client is connected') - _send(self.client, data) - return self - - def recv(self): - if not self.client: - raise Exception('Cannot receive data, no client is connected') - return _recv(self.client) - - def close(self): - if self.client: - self.client.close() - self.client = None - if self.socket: - self.socket.close() - self.socket = None - - -class Client(object): - """ - A JSON socket client used to communicate with a JSON socket server. All the - data is serialized in JSON. How to use it: - - data = { - 'name': 'Patrick Jane', - 'age': 45, - 'children': ['Susie', 'Mike', 'Philip'] - } - client = Client() - client.connect(host, port) - client.send(data) - response = client.recv() - # or in one line: - response = Client().connect(host, port).send(data).recv() - """ - - socket = None - - def __del__(self): - self.close() - - def connect(self, host, port): - self.socket = socket.socket() - self.socket.connect((host, port)) - return self - - def send(self, data): - if not self.socket: - raise Exception('You have to connect first before sending data') - _send(self.socket, data) - return self - - def recv(self): - if not self.socket: - raise Exception('You have to connect first before receiving data') - return _recv(self.socket) - - def recv_and_close(self): - data = self.recv() - self.close() - return data - - def close(self): - if self.socket: - self.socket.close() - self.socket = None - - -## helper functions ## - -def _send(socket, data): - try: - serialized = json.dumps(data).encode('utf-8') - print(serialized) - except (TypeError, ValueError) as e: - raise Exception('You can only send JSON-serializable data') - # send the length of the serialized data first - message = '{}\n'.format(len(serialized)).encode('utf-8') - socket.send(message) - # send the serialized data - socket.sendall(serialized) - - -def _recv(socket): - # read the length of the data, letter by letter until we reach EOL - length_str = '' - char = socket.recv(1) - while char != b'\n': - length_str += char.decode('utf-8') - char = socket.recv(1) - total = int(length_str) - # use a memoryview to receive the data chunk by chunk efficiently - view = memoryview(bytearray(total)) - next_offset = 0 - while total - next_offset > 0: - recv_size = socket.recv_into(view[next_offset:], total - next_offset) - next_offset += recv_size - try: - deserialized = json.loads(view.tobytes().decode('utf-8')) - except (TypeError, ValueError) as e: - raise Exception('Data received was not in JSON format') - return deserialized diff --git a/jsonsocket/serialize.py b/jsonsocket/serialize.py new file mode 100644 index 0000000..a438212 --- /dev/null +++ b/jsonsocket/serialize.py @@ -0,0 +1,50 @@ +import json + +use_numpy, use_pandas = True, True +try: + import numpy as np +except ImportError: + use_numpy = False + +try: + import pandas as pd +except ImportError: + use_pandas = False + + +class BetterEncoder(json.JSONEncoder): + def default(self, obj): + if use_numpy and isinstance(obj, np.ndarray): + return obj.tolist() + + if use_pandas and (isinstance(obj, pd.DataFrame) or isinstance(obj, pd.Series)): + res = dict(_decode_type=obj.__class__.__name__, _content=obj.to_dict()) + return res + return json.JSONEncoder.default(self, obj) + + +class BetterDecoder(json.JSONDecoder): + def __init__(self, *args, **kwargs): + super(BetterDecoder, self).__init__(object_hook=self.default, *args, **kwargs) + + def default(self, obj): + if use_pandas and isinstance(obj, dict): + if "_decode_type" in obj and "_content" in obj: + obj = eval("pd.%s(obj[\"_content\"])" % obj["_decode_type"]) + return obj + + +def serialize(data): + try: + res = json.dumps(data, cls=BetterEncoder).encode('utf-8') + except (TypeError, ValueError) as e: + raise Exception('You can only send JSON-serializable data') + return res + + +def deserialize(data): + try: + res = json.loads(data.decode('utf-8'), cls=BetterDecoder) + except (TypeError, ValueError) as e: + raise Exception('Data received was not in JSON format') + return res diff --git a/jsonsocket/tcp.py b/jsonsocket/tcp.py new file mode 100644 index 0000000..2f45580 --- /dev/null +++ b/jsonsocket/tcp.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +import socket +from threading import Thread + +from jsonsocket.helpers import send as _send, receive as _recv, TimeoutError + +class NoClient(Exception): + def __init__(self): + super(NoClient, self).__init__('Cannot send data, no client is connected') + +class ConnectFirst(Exception): + def __init__(self): + super(ConnectFirst, self).__init__('You have to connect first before sending data') + + +class Server(object): + """ + A JSON socket server used to communicate with a JSON socket client. All the + data is serialized in JSON. How to use it: + + server = Server(host, port) + while True: + server.accept() + data = server.recv() + # shortcut: data = server.accept().recv() + server.send({'status': 'ok'}) + """ + + backlog = 5 + client = None + + def __init__(self, host, port): + self.socket = socket.socket() + self.socket.bind((host, port)) + self.socket.listen(self.backlog) + + def __del__(self): + self.close() + + @property + def client_connected(self): + return self.client is not None + + def accept(self): + # if a client is already connected, disconnect it + if self.client: + self.client.close() + self.client, self.client_addr = self.socket.accept() + return self + + def send(self, data): + if not self.client: + raise NoClient() + _send(self.client, data, socket_type="tcp") + return self + + def recv(self, **kwargs): + if not self.client: + raise NoClient() + try: + res = _recv(self.client, socket_type="udp", **kwargs) + except TimeoutError: + self.close() + return None + return res + + def close(self): + if self.client: + self.client.close() + self.client = None + if self.socket: + self.socket.close() + self.socket = None + + +class Client(object): + """ + A JSON socket client used to communicate with a JSON socket server. All the + data is serialized in JSON. How to use it: + + data = { + 'name': 'Patrick Jane', + 'age': 45, + 'children': ['Susie', 'Mike', 'Philip'] + } + client = Client() + client.connect(host, port) + client.send(data) + response = client.recv() + # or in one line: + response = Client().connect(host, port).send(data).recv() + """ + + socket = None + + def __del__(self): + self.close() + + def connect(self, host, port): + self.socket = socket.socket() + self.socket.connect((host, port)) + return self + + def send(self, data): + if not self.socket: + raise ConnectFirst() + _send(self.socket, data, socket_type="tcp") + return self + + def recv(self): + if not self.socket: + raise ConnectFirst() + return _recv(self.socket, socket_type="tcp") + + def recv_and_close(self): + data = self.recv() + self.close() + return data + + def close(self): + if self.socket: + self.socket.close() + self.socket = None + + +class ThreadedServer(Thread): + def __init__(self, host, port, new_client_callback, new_message_callback, client_disconnect_callback=None, + timeout=5): + super(ThreadedServer, self).__init__() + self.client_disconnect_callback = client_disconnect_callback + self.timeout = timeout + self.new_message_callback = new_message_callback + self.new_client_callback = new_client_callback + self.server = Server(host, port) + self.__running = True + + def stop(self): + self.__running = False + + def run(self): + self.__running = True + + while self.__running: + self.server.accept() + client_addr = self.server.client_addr + self.new_client_callback(client_addr, self) + while 1: + try: + data = self.server.recv(timeout=self.timeout) + except (NoClient, socket.error): + break + if data is not None: + self.new_message_callback(data, self) + if self.client_disconnect_callback: + self.client_disconnect_callback(client_addr, self) + + def send(self, data): + self.server.send(data) + + def __enter__(self): + self.start() + + def __exit__(self, exc_type, exc_val, exc_tb): + self.stop() + self.server.close() + self.join() diff --git a/jsonsocket/udp.py b/jsonsocket/udp.py new file mode 100644 index 0000000..f9916c0 --- /dev/null +++ b/jsonsocket/udp.py @@ -0,0 +1,63 @@ +import socket +from jsonsocket.helpers import send as _send, receive as _recv + +from jsonsocket.udp_async import Receiver, Advertiser + + +class UDP(object): + def __init__(self): + self.__threads = [None, None] # receive and advertise + + def receive(self, host, port, timeout=None): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + if host == "broadcast": + s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + s.bind((host, port)) + data, addr = _recv(s, timeout=timeout,socket_type="udp") + return data, addr + + def send(self, data, ip, port): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + _send(s, data, "udp", (ip, port)) + + @property + def receiving(self): + return self.__threads[1] is not None + + def receive_async(self, host, port, callback, timeout=5): + if self.receiving: + raise Exception("Already receiving") + self.__threads[1] = t = Receiver(host, port, callback, timeout, self) + t.start() + return t + + def stop_receive_async(self): + if not self.receiving: + raise Exception("Not receiving") + t = self.__threads[1] + t.stop() + self.__threads[1] = None + + @property + def advertising(self): + return self.__threads[0] is not None + + def advertise(self, data, period, ip, port): + if self.advertising: + raise ValueError("Already advertising!") + self.__threads[0] = t = Advertiser(ip, port, data, period, self) + t.start() + return t + + def stop_advertising(self): + if not self.advertising: + raise Exception("Not advertising") + t = self.__threads[0] + t.stop() + t.join() + self.__threads[0] = None + + def join(self): + for t in self.__threads: + if t is not None: + t.join() diff --git a/jsonsocket/udp_async.py b/jsonsocket/udp_async.py new file mode 100644 index 0000000..e3ff45f --- /dev/null +++ b/jsonsocket/udp_async.py @@ -0,0 +1,71 @@ +import socket +from threading import Thread +from time import sleep + +import schedule + +from jsonsocket.helpers import TimeoutError + + +class UDPThread(Thread): + def __init__(self, host, port, client): + super(UDPThread, self).__init__() + self.client = client + self.host = host + self.port = port + self.__stop = False + + @property + def stopped(self): + return self.__stop + + def stop(self): + self.__stop = True + + def __del__(self): + self.stop() + self.join() + + def __enter__(self): + self.start() + + def __exit__(self, exc_type, exc_val, exc_tb): + self.stop() + self.join() + + +class Receiver(UDPThread): + def __init__(self, host, port, callback, timeout, client): + super(Receiver, self).__init__(host, port, client) + self.timeout = timeout + self.callback = callback + + def receive(self): + res = self.client.receive(self.host, self.port, timeout=self.timeout) + return res + + def run(self): + while not self.stopped: + res = None + try: + res = self.receive() + except TimeoutError: + pass + if res is not None: + self.callback(res[0], res[1], self.client) + + +class Advertiser(UDPThread): + def __init__(self, host, port, data, interval, client): + super(Advertiser, self).__init__(host, port, client) + self.interval = interval + self.data = data + + def send(self): + self.client.send(self.data, self.host, self.port) + + def run(self): + schedule.every(self.interval).seconds.do(self.send) + while not self.stopped: + schedule.run_pending() + sleep(schedule.idle_seconds()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..fee9790 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +schedule \ No newline at end of file diff --git a/setup.py b/setup.py index b9d30f9..b7c4e63 100644 --- a/setup.py +++ b/setup.py @@ -9,4 +9,5 @@ author='github', author_email='', packages=[name], #same as name + requirements=["schedule"] ) \ No newline at end of file diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/client_test.py b/test/client_test.py deleted file mode 100644 index 6aec33a..0000000 --- a/test/client_test.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python3 - -from jsonsocket.jsonsocket import Client - -host = 'localhost' -port = 8001 - -# Client code: -client = Client() -client.connect(host, port).send({'some_list': [123, 456]}) -response = client.recv() -# response now is {'data': {'some_list': [123, 456]}} -client.close() - - - - diff --git a/test/server_test.py b/test/server_test.py deleted file mode 100644 index 95948aa..0000000 --- a/test/server_test.py +++ /dev/null @@ -1,12 +0,0 @@ -from jsonsocket.jsonsocket import Server - -host = 'localhost' -port = 8001 - -# Server code: -server = Server(host, port) -server.accept() -data = server.recv() -# data now is: {'some_list': [123, 456]} -server.send({'data': data}).close() - diff --git a/test/test_TCP.py b/test/test_TCP.py new file mode 100644 index 0000000..d2c1211 --- /dev/null +++ b/test/test_TCP.py @@ -0,0 +1,67 @@ +from unittest import TestCase + +from jsonsocket.tcp import ThreadedServer, Client + + +def new_client(addr, _srv): + print("New client: %s" % str(addr)) + + +def new_data(data, _srv): + print("New data : %s" % str(data)) + # Echo + _srv.send(data) + + +def disconnect_client(addr, _srv): + print("Client disconnected: %s" % str(addr)) + + +class TestTCP(TestCase): + def test_general(self): + port = 1234 + srv = ThreadedServer("localhost", port, new_client, new_data, disconnect_client, timeout=1) + + with srv: + cl = Client() + cl.connect("localhost", port) + cl.send({"text": "Bonjour le monde!"}) + res = cl.recv() + print("Received the following echo:") + print(res) + print("Disconnecting") + cl.close() + + def test_numpy(self): + import numpy as np + port = 1237 + srv = ThreadedServer("localhost", port, new_client, new_data, disconnect_client, timeout=1) + + payload = {"numpy": np.linspace(0, 1, 9)} + with srv: + cl = Client() + cl.connect("localhost", port) + cl.send(payload) + res = cl.recv() + print("Received the following echo:") + print(res) + print("Disconnecting") + cl.close() + + def test_pandas(self): + import numpy as np + from pandas import DataFrame + + port = 1236 + srv = ThreadedServer("localhost", port, new_client, new_data, disconnect_client, timeout=1) + + payload = DataFrame(dict(A=[1, 2, 3], B=[4, 5, 6])) + with srv: + cl = Client() + cl.connect("localhost", port) + cl.send(payload) + res = cl.recv() + print("Received the following echo:") + print(res) + print("Disconnecting") + cl.close() diff --git a/test/test_UDP.py b/test/test_UDP.py new file mode 100644 index 0000000..9a37ef0 --- /dev/null +++ b/test/test_UDP.py @@ -0,0 +1,44 @@ +from time import sleep +from unittest import TestCase + +from jsonsocket.udp import UDP + + +def new_message(data, addr, _srv): + new_message.n_received += 1 + print("SERVER: Received data:") + print(data) + # Echo + _srv.send(data,*addr) + if new_message.n_received>=4 and _srv.receiving: + _srv.stop_receive_async() + +new_message.n_received=0 + + +class TestUDP(TestCase): + def test_general(self): + port = 1234 + data = {"data": "Bonjour!"} + s = UDP() + c = UDP() + + s.receive_async("0.0.0.0",port,new_message) + sleep(1) + c.send(data, "127.0.0.1", port) + + s.stop_receive_async() + s.join() + + def test_advertize(self): + port = 1234 + data = {"data": "Bonjour!"} + s = UDP() + c = UDP() + + s.receive_async("0.0.0.0", port, new_message) + sleep(1) + c.advertise(data, 1, "127.0.0.1", port) + + s.join() + c.stop_advertising() From 8ff092f83994ef803d9e40723febaab3bb29e0cf Mon Sep 17 00:00:00 2001 From: Xavier Tolza Date: Fri, 7 Dec 2018 17:54:23 +0100 Subject: [PATCH 04/10] many bugfixes --- jsonsocket/constants.py | 1 + jsonsocket/errors.py | 22 ++++++++++++ jsonsocket/helpers.py | 8 +++-- jsonsocket/tcp.py | 76 +++++++++++++++++++++++------------------ jsonsocket/udp.py | 14 ++++++-- test/test_TCP.py | 8 ++--- 6 files changed, 86 insertions(+), 43 deletions(-) create mode 100644 jsonsocket/constants.py create mode 100644 jsonsocket/errors.py diff --git a/jsonsocket/constants.py b/jsonsocket/constants.py new file mode 100644 index 0000000..13e0b4d --- /dev/null +++ b/jsonsocket/constants.py @@ -0,0 +1 @@ +broadcast_address = "" \ No newline at end of file diff --git a/jsonsocket/errors.py b/jsonsocket/errors.py new file mode 100644 index 0000000..f76fdb2 --- /dev/null +++ b/jsonsocket/errors.py @@ -0,0 +1,22 @@ +class IncorrectLength(Exception): + def __init__(self,message,length): + super(IncorrectLength, self).__init__("Incorrect transmitted length") + self.content = message + self.length = length + + +class NoClient(Exception): + def __init__(self): + super(NoClient, self).__init__('Cannot send data, no client is connected') + + +class ConnectFirst(Exception): + def __init__(self): + super(ConnectFirst, self).__init__('You have to connect first before sending data') + + +class AddressAlreadyInUse(Exception): + def __init__(self, host, port): + super(AddressAlreadyInUse, self).__init__("Address %s:%i already in use" % (host, port)) + self.host = host + self.port = port \ No newline at end of file diff --git a/jsonsocket/helpers.py b/jsonsocket/helpers.py index 7c0e093..503807e 100644 --- a/jsonsocket/helpers.py +++ b/jsonsocket/helpers.py @@ -1,5 +1,6 @@ from time import time +from jsonsocket.errors import IncorrectLength from jsonsocket.serialize import serialize, deserialize from socket import timeout as tmout @@ -18,7 +19,8 @@ def send(socket, data, socket_type="tcp", *args): # send the serialized data socket.sendall(serialized) elif socket_type == "udp": - socket.sendto(message + serialized + "\n", *args) + content = message + serialized + "\n" + socket.sendto(content, args) def receive(socket, socket_type="tcp", timeout=None): @@ -32,7 +34,7 @@ def receive(socket, socket_type="tcp", timeout=None): length_str += char.decode('utf-8') char = socket.recv(1) if length_str == '': - raise TimeoutError("Timout listening for data") + raise TimeoutError("Timeout listening for data") total = int(length_str) # use a memoryview to receive the data chunk by chunk efficiently view = memoryview(bytearray(total)) @@ -49,7 +51,7 @@ def receive(socket, socket_type="tcp", timeout=None): char, addr = socket.recvfrom(2048 ** 2) length, char = char.split("\n")[:2] if len(char) != int(length): - raise Exception("Incorrect transmitted length") + raise IncorrectLength(char,length) deserialized = deserialize(char) return deserialized, addr except tmout: diff --git a/jsonsocket/tcp.py b/jsonsocket/tcp.py index 2f45580..04bd964 100644 --- a/jsonsocket/tcp.py +++ b/jsonsocket/tcp.py @@ -2,16 +2,9 @@ import socket from threading import Thread +from jsonsocket.errors import NoClient, ConnectFirst from jsonsocket.helpers import send as _send, receive as _recv, TimeoutError -class NoClient(Exception): - def __init__(self): - super(NoClient, self).__init__('Cannot send data, no client is connected') - -class ConnectFirst(Exception): - def __init__(self): - super(ConnectFirst, self).__init__('You have to connect first before sending data') - class Server(object): """ @@ -54,13 +47,14 @@ def send(self, data): _send(self.client, data, socket_type="tcp") return self - def recv(self, **kwargs): + def recv(self, close_on_timeout=False, **kwargs): if not self.client: raise NoClient() try: - res = _recv(self.client, socket_type="udp", **kwargs) + res = _recv(self.client, socket_type="tcp", **kwargs) except TimeoutError: - self.close() + if close_on_timeout: + self.close() return None return res @@ -107,13 +101,13 @@ def send(self, data): _send(self.socket, data, socket_type="tcp") return self - def recv(self): + def recv(self, **kwargs): if not self.socket: raise ConnectFirst() - return _recv(self.socket, socket_type="tcp") + return _recv(self.socket, socket_type="tcp", **kwargs) - def recv_and_close(self): - data = self.recv() + def recv_and_close(self, **kwargs): + data = self.recv(**kwargs) self.close() return data @@ -123,10 +117,11 @@ def close(self): self.socket = None -class ThreadedServer(Thread): +class ServerAsync(Thread): def __init__(self, host, port, new_client_callback, new_message_callback, client_disconnect_callback=None, - timeout=5): - super(ThreadedServer, self).__init__() + exception_callback=None, timeout=5): + super(ServerAsync, self).__init__() + self.exception_callback = exception_callback self.client_disconnect_callback = client_disconnect_callback self.timeout = timeout self.new_message_callback = new_message_callback @@ -138,25 +133,40 @@ def stop(self): self.__running = False def run(self): - self.__running = True - - while self.__running: - self.server.accept() - client_addr = self.server.client_addr - self.new_client_callback(client_addr, self) - while 1: - try: - data = self.server.recv(timeout=self.timeout) - except (NoClient, socket.error): - break - if data is not None: - self.new_message_callback(data, self) - if self.client_disconnect_callback: - self.client_disconnect_callback(client_addr, self) + try: + self.__running = True + + while self.__running: + self.server.accept() + client_addr = self.server.client_addr + if self.new_client_callback: + self.new_client_callback(client_addr, self) + while 1: + try: + data = self.server.recv(timeout=self.timeout) + except (NoClient, socket.error): + break + if data is not None: + self.new_message_callback(data, self) + if self.client_disconnect_callback: + self.client_disconnect_callback(client_addr, self) + except Exception as e: + if self.exception_callback: + self.exception_callback(e) + else: + raise def send(self, data): self.server.send(data) + @property + def client_addr(self): + return self.server.client_addr + + @property + def client(self): + return self.server.client + def __enter__(self): self.start() diff --git a/jsonsocket/udp.py b/jsonsocket/udp.py index f9916c0..803b0a6 100644 --- a/jsonsocket/udp.py +++ b/jsonsocket/udp.py @@ -1,4 +1,6 @@ import socket + +from jsonsocket.constants import broadcast_address from jsonsocket.helpers import send as _send, receive as _recv from jsonsocket.udp_async import Receiver, Advertiser @@ -10,15 +12,21 @@ def __init__(self): def receive(self, host, port, timeout=None): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - if host == "broadcast": + if host == broadcast_address: s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - s.bind((host, port)) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + s.bind((host, port)) + except socket.error: + raise Exception("Address %s:%i already in use" % (host,port)) data, addr = _recv(s, timeout=timeout,socket_type="udp") return data, addr def send(self, data, ip, port): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - _send(s, data, "udp", (ip, port)) + if ip == broadcast_address: + s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + _send(s, data, "udp", ip, port) @property def receiving(self): diff --git a/test/test_TCP.py b/test/test_TCP.py index d2c1211..1de3cd5 100644 --- a/test/test_TCP.py +++ b/test/test_TCP.py @@ -1,6 +1,6 @@ from unittest import TestCase -from jsonsocket.tcp import ThreadedServer, Client +from jsonsocket.tcp import ServerAsync, Client def new_client(addr, _srv): @@ -20,7 +20,7 @@ def disconnect_client(addr, _srv): class TestTCP(TestCase): def test_general(self): port = 1234 - srv = ThreadedServer("localhost", port, new_client, new_data, disconnect_client, timeout=1) + srv = ServerAsync("localhost", port, new_client, new_data, disconnect_client, timeout=1) with srv: cl = Client() @@ -35,7 +35,7 @@ def test_general(self): def test_numpy(self): import numpy as np port = 1237 - srv = ThreadedServer("localhost", port, new_client, new_data, disconnect_client, timeout=1) + srv = ServerAsync("localhost", port, new_client, new_data, disconnect_client, timeout=1) payload = {"numpy": np.linspace(0, 1, 9)} with srv: @@ -53,7 +53,7 @@ def test_pandas(self): from pandas import DataFrame port = 1236 - srv = ThreadedServer("localhost", port, new_client, new_data, disconnect_client, timeout=1) + srv = ServerAsync("localhost", port, new_client, new_data, disconnect_client, timeout=1) payload = DataFrame(dict(A=[1, 2, 3], B=[4, 5, 6])) with srv: From 2616f909c9d51411b2562f19e18826e3aa2abc01 Mon Sep 17 00:00:00 2001 From: Xavier Tolza Date: Mon, 10 Dec 2018 12:07:59 +0100 Subject: [PATCH 05/10] tons of bugfixes --- jsonsocket/constants.py | 5 ++++- jsonsocket/helpers.py | 18 +++++++++++++----- jsonsocket/serialize.py | 27 ++++++++++++++++++++++----- jsonsocket/tcp.py | 2 ++ jsonsocket/udp.py | 4 ++-- 5 files changed, 43 insertions(+), 13 deletions(-) diff --git a/jsonsocket/constants.py b/jsonsocket/constants.py index 13e0b4d..730460e 100644 --- a/jsonsocket/constants.py +++ b/jsonsocket/constants.py @@ -1 +1,4 @@ -broadcast_address = "" \ No newline at end of file +import sys + +broadcast_address = "" +python3 = sys.version_info[0]>=3 \ No newline at end of file diff --git a/jsonsocket/helpers.py b/jsonsocket/helpers.py index 503807e..514c284 100644 --- a/jsonsocket/helpers.py +++ b/jsonsocket/helpers.py @@ -1,5 +1,7 @@ +import sys from time import time +from jsonsocket.constants import python3 from jsonsocket.errors import IncorrectLength from jsonsocket.serialize import serialize, deserialize from socket import timeout as tmout @@ -19,11 +21,14 @@ def send(socket, data, socket_type="tcp", *args): # send the serialized data socket.sendall(serialized) elif socket_type == "udp": - content = message + serialized + "\n" + padding = "\n" + if python3: + padding = bytes(padding, "utf-8") + content = message + serialized + padding socket.sendto(content, args) -def receive(socket, socket_type="tcp", timeout=None): +def receive(socket, socket_type="tcp", timeout=None, skip_size_info=False): # read the length of the data, letter by letter until we reach EOL length_str = '' tcp = socket_type == "tcp" @@ -49,9 +54,12 @@ def receive(socket, socket_type="tcp", timeout=None): socket.settimeout(timeout) try: char, addr = socket.recvfrom(2048 ** 2) - length, char = char.split("\n")[:2] - if len(char) != int(length): - raise IncorrectLength(char,length) + if type(char)==bytes: + char = char.decode("utf-8") + if not skip_size_info: + length, char = char.split("\n")[:2] + if len(char) != int(length): + raise IncorrectLength(char,length) deserialized = deserialize(char) return deserialized, addr except tmout: diff --git a/jsonsocket/serialize.py b/jsonsocket/serialize.py index a438212..ce0c6b0 100644 --- a/jsonsocket/serialize.py +++ b/jsonsocket/serialize.py @@ -17,9 +17,24 @@ def default(self, obj): if use_numpy and isinstance(obj, np.ndarray): return obj.tolist() - if use_pandas and (isinstance(obj, pd.DataFrame) or isinstance(obj, pd.Series)): - res = dict(_decode_type=obj.__class__.__name__, _content=obj.to_dict()) - return res + if use_pandas: + typename = obj.__class__.__name__ + res=None + if isinstance(obj, pd.DataFrame): + content = dict(zip(obj.columns, obj.values.T.tolist())) + for key, value in content.items(): + unique = np.unique(value) + if len(unique)==1: + content[key] = unique[0] + res = dict(_decode_type=typename, _content=content) + elif isinstance(obj, pd.Series): + res = dict(_decode_type=typename, _content=obj.to_dict()) + if res: + return res + if "int" == type(obj).__name__[:3]: + return int(obj) + if "float" == type(obj).__name__[:5]: + return float(obj) return json.JSONEncoder.default(self, obj) @@ -38,13 +53,15 @@ def serialize(data): try: res = json.dumps(data, cls=BetterEncoder).encode('utf-8') except (TypeError, ValueError) as e: - raise Exception('You can only send JSON-serializable data') + raise Exception('You can only send JSON-serializable data. Error is : %s' % e) return res def deserialize(data): + if type(data) == bytes: + data = data.decode('utf-8') try: - res = json.loads(data.decode('utf-8'), cls=BetterDecoder) + res = json.loads(data, cls=BetterDecoder) except (TypeError, ValueError) as e: raise Exception('Data received was not in JSON format') return res diff --git a/jsonsocket/tcp.py b/jsonsocket/tcp.py index 04bd964..b488d70 100644 --- a/jsonsocket/tcp.py +++ b/jsonsocket/tcp.py @@ -148,6 +148,8 @@ def run(self): break if data is not None: self.new_message_callback(data, self) + else: + break if self.client_disconnect_callback: self.client_disconnect_callback(client_addr, self) except Exception as e: diff --git a/jsonsocket/udp.py b/jsonsocket/udp.py index 803b0a6..c639a6e 100644 --- a/jsonsocket/udp.py +++ b/jsonsocket/udp.py @@ -10,7 +10,7 @@ class UDP(object): def __init__(self): self.__threads = [None, None] # receive and advertise - def receive(self, host, port, timeout=None): + def receive(self, host, port, timeout=None, **kwargs): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) if host == broadcast_address: s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) @@ -19,7 +19,7 @@ def receive(self, host, port, timeout=None): s.bind((host, port)) except socket.error: raise Exception("Address %s:%i already in use" % (host,port)) - data, addr = _recv(s, timeout=timeout,socket_type="udp") + data, addr = _recv(s, timeout=timeout,socket_type="udp", **kwargs) return data, addr def send(self, data, ip, port): From 0a8841dbc4777ab03d2827552a9795bea50ad3e7 Mon Sep 17 00:00:00 2001 From: Xavier Tolza Date: Tue, 11 Dec 2018 17:55:49 +0100 Subject: [PATCH 06/10] bugfixed error catching --- jsonsocket/tcp.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/jsonsocket/tcp.py b/jsonsocket/tcp.py index b488d70..46d0dbe 100644 --- a/jsonsocket/tcp.py +++ b/jsonsocket/tcp.py @@ -98,7 +98,12 @@ def connect(self, host, port): def send(self, data): if not self.socket: raise ConnectFirst() - _send(self.socket, data, socket_type="tcp") + try: + _send(self.socket, data, socket_type="tcp") + except socket.error as e: + if "reset by peer" in e.message: + raise NoClient() + raise return self def recv(self, **kwargs): From 71f5cd8fded2171cf16d4ae583b726abdf7adff3 Mon Sep 17 00:00:00 2001 From: Xavier Tolza Date: Tue, 11 Dec 2018 17:56:08 +0100 Subject: [PATCH 07/10] added serialization of complex numpy array --- jsonsocket/serialize.py | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/jsonsocket/serialize.py b/jsonsocket/serialize.py index ce0c6b0..18de3c5 100644 --- a/jsonsocket/serialize.py +++ b/jsonsocket/serialize.py @@ -1,4 +1,6 @@ import json +import struct +from base64 import b64encode, b64decode use_numpy, use_pandas = True, True try: @@ -11,20 +13,30 @@ except ImportError: use_pandas = False +complex_number_length = len(b64encode(struct.pack("ff", 0, 0)).decode("utf-8")) + 1 +complex1 = np.array([1, 1j]) + class BetterEncoder(json.JSONEncoder): def default(self, obj): if use_numpy and isinstance(obj, np.ndarray): + if "complex" in str(obj.dtype): + shape = obj.shape + values = obj.ravel() + values = np.concatenate([np.real(values), np.imag(values)]).reshape((2, len(values))).T.ravel() + encoded = b64encode(struct.pack("f" * len(values), *values)).decode("utf-8") + res = dict(_decode_type="numpy_complex", _shape=shape, _content=encoded) + return res return obj.tolist() if use_pandas: typename = obj.__class__.__name__ - res=None + res = None if isinstance(obj, pd.DataFrame): content = dict(zip(obj.columns, obj.values.T.tolist())) for key, value in content.items(): unique = np.unique(value) - if len(unique)==1: + if len(unique) == 1: content[key] = unique[0] res = dict(_decode_type=typename, _content=content) elif isinstance(obj, pd.Series): @@ -35,6 +47,10 @@ def default(self, obj): return int(obj) if "float" == type(obj).__name__[:5]: return float(obj) + if "complex" == type(obj).__name__[:7]: + encoded = b64encode(struct.pack("ff", np.real(obj), np.imag(obj))) + encoded = "c" + encoded.decode("utf-8") + return encoded return json.JSONEncoder.default(self, obj) @@ -43,6 +59,18 @@ def __init__(self, *args, **kwargs): super(BetterDecoder, self).__init__(object_hook=self.default, *args, **kwargs) def default(self, obj): + if type(obj) == str and len(obj) == complex_number_length and obj[0] == "c": + obj = np.array(struct.unpack("ff", b64decode(obj[1:]))) + if "_decode_type" in obj and obj["_decode_type"] == "numpy_complex": + content = obj["_content"] + shape = obj["_shape"] + content = b64decode(content) + content = struct.unpack("f" * (len(content) // 4), content) + content = np.reshape(content, (-1, 2)) + content = content * complex1[None, :] + content = np.sum(content,axis=1) + content = np.reshape(content, shape) + return content if use_pandas and isinstance(obj, dict): if "_decode_type" in obj and "_content" in obj: obj = eval("pd.%s(obj[\"_content\"])" % obj["_decode_type"]) @@ -63,5 +91,5 @@ def deserialize(data): try: res = json.loads(data, cls=BetterDecoder) except (TypeError, ValueError) as e: - raise Exception('Data received was not in JSON format') + raise Exception('Data received was not in JSON format. Error is %s' % str(e)) return res From 5850d36d62e4da053793b198d4ee0f815db5b492 Mon Sep 17 00:00:00 2001 From: Xavier Tolza Date: Wed, 12 Dec 2018 10:48:19 +0100 Subject: [PATCH 08/10] bugfixed tcp close when accepting connexions --- jsonsocket/tcp.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/jsonsocket/tcp.py b/jsonsocket/tcp.py index 46d0dbe..5aee03c 100644 --- a/jsonsocket/tcp.py +++ b/jsonsocket/tcp.py @@ -25,7 +25,10 @@ class Server(object): def __init__(self, host, port): self.socket = socket.socket() self.socket.bind((host, port)) + self.host=host + self.port=port self.socket.listen(self.backlog) + self.__accepting=False def __del__(self): self.close() @@ -34,11 +37,17 @@ def __del__(self): def client_connected(self): return self.client is not None + @property + def accepting_connexions(self): + return self.__accepting + def accept(self): # if a client is already connected, disconnect it if self.client: self.client.close() + self.__accepting = True self.client, self.client_addr = self.socket.accept() + self.__accepting = False return self def send(self, data): @@ -63,6 +72,10 @@ def close(self): self.client.close() self.client = None if self.socket: + if self.accepting_connexions: + c = Client() + c.connect("localhost", self.port) + c.close() self.socket.close() self.socket = None @@ -136,6 +149,7 @@ def __init__(self, host, port, new_client_callback, new_message_callback, client def stop(self): self.__running = False + self.server.close() def run(self): try: @@ -143,6 +157,7 @@ def run(self): while self.__running: self.server.accept() + if not self.__running: break client_addr = self.server.client_addr if self.new_client_callback: self.new_client_callback(client_addr, self) From 6637a28192f87ba952ce9c27d4cc8a4378c56126 Mon Sep 17 00:00:00 2001 From: Xavier Tolza Date: Wed, 12 Dec 2018 11:14:22 +0100 Subject: [PATCH 09/10] setting automatic version --- .gitmodules | 3 +++ auto_version | 1 + setup.py | 5 ++++- 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .gitmodules create mode 160000 auto_version diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d46b949 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "auto_version"] + path = auto_version + url = https://github.com/moble/auto_version.git diff --git a/auto_version b/auto_version new file mode 160000 index 0000000..b9da163 --- /dev/null +++ b/auto_version @@ -0,0 +1 @@ +Subproject commit b9da1638e033b0f6266be56898f6ff4dfa9d8363 diff --git a/setup.py b/setup.py index b7c4e63..7f6b18a 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,13 @@ from setuptools import setup +from auto_version import calculate_version + name = 'jsonsocket' +version = calculate_version() setup( name=name, - version='1.0', + version=version, description='This is a small Python library for sending data over sockets. ', author='github', author_email='', From f56587230ef5053f9acec8ec87b379edd5270f29 Mon Sep 17 00:00:00 2001 From: Xavier TOLZA Date: Wed, 16 Jan 2019 15:53:05 +0100 Subject: [PATCH 10/10] Added fallback for udp reception skip size setting --- jsonsocket/helpers.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/jsonsocket/helpers.py b/jsonsocket/helpers.py index 514c284..b81fd01 100644 --- a/jsonsocket/helpers.py +++ b/jsonsocket/helpers.py @@ -56,10 +56,13 @@ def receive(socket, socket_type="tcp", timeout=None, skip_size_info=False): char, addr = socket.recvfrom(2048 ** 2) if type(char)==bytes: char = char.decode("utf-8") - if not skip_size_info: - length, char = char.split("\n")[:2] - if len(char) != int(length): - raise IncorrectLength(char,length) + try: + if not skip_size_info: + length, char = char.split("\n")[:2] + if len(char) != int(length): + raise IncorrectLength(char,length) + except ValueError: + pass deserialized = deserialize(char) return deserialized, addr except tmout: