jsocket 1.9.2__py3-none-any.whl → 1.9.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
jsocket/__init__.py CHANGED
@@ -1,75 +1,75 @@
1
1
  """ @package jsocket
2
- @brief Main package importing two modules, jsocket_base and tserver into the scope of jsocket.
3
-
4
- @example example_servers.py
5
-
6
- @mainpage JSocket - Fast & Scalable JSON Server & Client
7
- @section Installation
8
-
9
- The jsocket package should always be installed using the stable PyPi releases.
10
- Either use "easy_install jsocket" or "pip install jsocket" to get the latest stable version.
11
-
12
- @section Usage
13
-
14
- The jsocket package is for use during the development of distributed systems. There are two ways to
15
- use the package. The first and simplest is to create a custom single threaded server by overloading the
16
- the jsocket.ThreadedServer class (see example one below).
17
-
18
- The second, is to use the server factory functionality by overloading the jsocket.ServerFactoryThread
19
- class and passing the declaration to the jsocket.ServerFactory(FactoryThread) object. This creates a
20
- multithreaded custom JSON server for any number of simultaneous clients (see example two below).
21
-
22
- @section Examples
23
- @b 1: The following snippet simply creates a custom single threaded server by overloading jsocket.ThreadedServer
24
- @code
25
- class MyServer(jsocket.ThreadedServer):
26
- # This is a basic example of a custom ThreadedServer.
27
- def __init__(self):
28
- super(MyServer, self).__init__()
29
- self.timeout = 2.0
30
- logger.warning("MyServer class in customServer is for example purposes only.")
31
-
32
- def _process_message(self, obj):
33
- # virtual method
34
- if obj != '':
35
- if obj['message'] == "new connection":
36
- logger.info("new connection.")
37
- @endcode
38
-
39
- @b 2: The following snippet creates a custom factory thread and starts a factory server. The factory server
40
- will allocate and run a factory thread for each new client.
41
-
42
- @code
43
- import jsocket
44
-
45
- class MyFactoryThread(jsocket.ServerFactoryThread):
46
- # This is an example factory thread, which the server factory will
47
- # instantiate for each new connection.
48
- def __init__(self):
49
- super(MyFactoryThread, self).__init__()
50
- self.timeout = 2.0
51
-
52
- def _process_message(self, obj):
53
- # virtual method - Implementer must define protocol
54
- if obj != '':
55
- if obj['message'] == "new connection":
56
- logger.info("new connection.")
57
- else:
58
- logger.info(obj)
59
-
60
- server = jsocket.ServerFactory(MyFactoryThread)
61
- server.timeout = 2.0
62
- server.start()
63
-
64
- client = jsocket.JsonClient()
65
- client.connect()
66
- client.send_obj({"message": "new connection"})
67
-
68
- client.close()
69
- server.stop()
70
- server.join()
71
- @endcode
72
-
2
+ @brief Main package importing two modules, jsocket_base and tserver into the scope of jsocket.
3
+
4
+ @example example_servers.py
5
+
6
+ @mainpage JSocket - Fast & Scalable JSON Server & Client
7
+ @section Installation
8
+
9
+ The jsocket package should always be installed using the stable PyPi releases.
10
+ Either use "easy_install jsocket" or "pip install jsocket" to get the latest stable version.
11
+
12
+ @section Usage
13
+
14
+ The jsocket package is for use during the development of distributed systems. There are two ways to
15
+ use the package. The first and simplest is to create a custom single threaded server by overloading the
16
+ the jsocket.ThreadedServer class (see example one below).
17
+
18
+ The second, is to use the server factory functionality by overloading the jsocket.ServerFactoryThread
19
+ class and passing the declaration to the jsocket.ServerFactory(FactoryThread) object. This creates a
20
+ multithreaded custom JSON server for any number of simultaneous clients (see example two below).
21
+
22
+ @section Examples
23
+ @b 1: The following snippet simply creates a custom single threaded server by overloading jsocket.ThreadedServer
24
+ @code
25
+ class MyServer(jsocket.ThreadedServer):
26
+ # This is a basic example of a custom ThreadedServer.
27
+ def __init__(self):
28
+ super(MyServer, self).__init__()
29
+ self.timeout = 2.0
30
+ logger.warning("MyServer class in customServer is for example purposes only.")
31
+
32
+ def _process_message(self, obj):
33
+ # virtual method
34
+ if obj != '':
35
+ if obj['message'] == "new connection":
36
+ logger.info("new connection.")
37
+ @endcode
38
+
39
+ @b 2: The following snippet creates a custom factory thread and starts a factory server. The factory server
40
+ will allocate and run a factory thread for each new client.
41
+
42
+ @code
43
+ import jsocket
44
+
45
+ class MyFactoryThread(jsocket.ServerFactoryThread):
46
+ # This is an example factory thread, which the server factory will
47
+ # instantiate for each new connection.
48
+ def __init__(self):
49
+ super(MyFactoryThread, self).__init__()
50
+ self.timeout = 2.0
51
+
52
+ def _process_message(self, obj):
53
+ # virtual method - Implementer must define protocol
54
+ if obj != '':
55
+ if obj['message'] == "new connection":
56
+ logger.info("new connection.")
57
+ else:
58
+ logger.info(obj)
59
+
60
+ server = jsocket.ServerFactory(MyFactoryThread)
61
+ server.timeout = 2.0
62
+ server.start()
63
+
64
+ client = jsocket.JsonClient()
65
+ client.connect()
66
+ client.send_obj({"message": "new connection"})
67
+
68
+ client.close()
69
+ server.stop()
70
+ server.join()
71
+ @endcode
72
+
73
73
  """
74
74
  from jsocket.jsocket_base import *
75
75
  from jsocket.tserver import *
jsocket/jsocket_base.py CHANGED
@@ -1,26 +1,25 @@
1
1
  """ @namespace jsocket_base
2
- Contains JsonSocket, JsonServer and JsonClient implementations (json object message passing server and client).
2
+ Contains JsonSocket, JsonServer and JsonClient implementations (json object message passing server and client).
3
3
  """
4
- __author__ = "Christopher Piekarski"
5
- __email__ = "chris@cpiekarski.com"
4
+ __author__ = "Christopher Piekarski"
5
+ __email__ = "chris@cpiekarski.com"
6
6
  __copyright__= """
7
- This file is part of the jsocket package.
8
- Copyright (C) 2011 by
9
- Christopher Piekarski <chris@cpiekarski.com>
7
+ Copyright (C) 2011 by
8
+ Christopher Piekarski <chris@cpiekarski.com>
10
9
 
11
- The jsocket_base module is free software: you can redistribute it and/or modify
12
- it under the terms of the GNU General Public License as published by
13
- the Free Software Foundation, either version 3 of the License, or
14
- (at your option) any later version.
10
+ Licensed under the Apache License, Version 2.0 (the "License");
11
+ you may not use this file except in compliance with the License.
12
+ You may obtain a copy of the License at
15
13
 
16
- The jsocket package is distributed in the hope that it will be useful,
17
- but WITHOUT ANY WARRANTY; without even the implied warranty of
18
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
- GNU General Public License for more details.
14
+ http://www.apache.org/licenses/LICENSE-2.0
20
15
 
21
- You should have received a copy of the GNU General Public License
22
- along with jsocket_base module. If not, see <http://www.gnu.org/licenses/>."""
23
- __version__ = "1.0.2"
16
+ Unless required by applicable law or agreed to in writing, software
17
+ distributed under the License is distributed on an "AS IS" BASIS,
18
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
+ See the License for the specific language governing permissions and
20
+ limitations under the License.
21
+ """
22
+ __version__ = "1.0.3"
24
23
 
25
24
  import json
26
25
  import socket
@@ -30,179 +29,200 @@ import time
30
29
 
31
30
  logger = logging.getLogger("jsocket")
32
31
 
33
- class JsonSocket(object):
34
- def __init__(self, address='127.0.0.1', port=5489, timeout=2.0):
35
- self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
36
- self.conn = self.socket
37
- self._timeout = timeout
38
- self._address = address
39
- self._port = port
40
-
41
- def send_obj(self, obj):
42
- msg = json.dumps(obj)
43
- if self.socket:
44
- frmt = "=%ds" % len(msg)
45
- packed_msg = struct.pack(frmt, bytes(msg,'ascii'))
46
- packed_hdr = struct.pack('!I', len(packed_msg))
47
- self._send(packed_hdr)
48
- self._send(packed_msg)
49
-
50
- def _send(self, msg):
51
- sent = 0
52
- while sent < len(msg):
53
- sent += self.conn.send(msg[sent:])
54
-
55
- def _read(self, size):
56
- data = b''
57
- while len(data) < size:
58
- data_tmp = self.conn.recv(size-len(data))
59
- data += data_tmp
60
- if data_tmp == b'':
61
- raise RuntimeError("socket connection broken")
62
- return data
63
-
64
- def _msg_length(self):
65
- d = self._read(4)
66
- s = struct.unpack('!I', d)
67
- return s[0]
68
-
69
- def read_obj(self):
70
- size = self._msg_length()
71
- data = self._read(size)
72
- frmt = "=%ds" % size
73
- msg = struct.unpack(frmt, data)
74
- return json.loads(str(msg[0],'ascii'))
75
-
76
- def close(self):
77
- logger.debug("closing all connections")
78
- self._close_connection()
79
- self._close_socket()
80
-
81
- def _close_socket(self):
82
- logger.debug("closing main socket")
83
- if self.socket.fileno() != -1:
84
- self.socket.shutdown(socket.SHUT_RDWR)
85
- self.socket.close()
86
-
87
- def _close_connection(self):
88
- logger.debug("closing the connection socket")
89
- if self.conn.fileno() != -1:
90
- self.conn.shutdown(socket.SHUT_RDWR)
91
- self.conn.close()
92
-
93
- def _get_timeout(self):
94
- return self._timeout
95
-
96
- def _set_timeout(self, timeout):
97
- self._timeout = timeout
98
- self.socket.settimeout(timeout)
99
-
100
- def _get_address(self):
101
- return self._address
102
-
103
- def _set_address(self, address):
104
- pass
105
-
106
- def _get_port(self):
107
- return self._port
108
-
109
- def _set_port(self, port):
110
- pass
111
-
112
- timeout = property(_get_timeout, _set_timeout,doc='Get/set the socket timeout')
113
- address = property(_get_address, _set_address,doc='read only property socket address')
114
- port = property(_get_port, _set_port,doc='read only property socket port')
115
-
116
-
32
+
33
+ def _socket_fileno(sock):
34
+ try:
35
+ return sock.fileno()
36
+ except Exception: # pylint: disable=broad-exception-caught
37
+ return None
38
+
39
+
40
+ class JsonSocket:
41
+ """Lightweight JSON-over-TCP socket wrapper with length-prefixed framing."""
42
+
43
+ def __init__(self, address='127.0.0.1', port=5489, timeout=2.0):
44
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
45
+ self.conn = self.socket
46
+ self._timeout = timeout
47
+ self._address = address
48
+ self._port = port
49
+ # Ensure the primary socket respects timeout for accept/connect operations
50
+ self.socket.settimeout(self._timeout)
51
+
52
+ def send_obj(self, obj):
53
+ """Send a JSON-serializable object over the connection."""
54
+ msg = json.dumps(obj, ensure_ascii=False)
55
+ if self.socket:
56
+ payload = msg.encode('utf-8')
57
+ frmt = f"={len(payload)}s"
58
+ packed_msg = struct.pack(frmt, payload)
59
+ packed_hdr = struct.pack('!I', len(packed_msg))
60
+ self._send(packed_hdr)
61
+ self._send(packed_msg)
62
+
63
+ def _send(self, msg):
64
+ """Send all bytes in `msg` to the peer."""
65
+ sent = 0
66
+ while sent < len(msg):
67
+ sent += self.conn.send(msg[sent:])
68
+
69
+ def _read(self, size):
70
+ """Read exactly `size` bytes from the peer or raise on disconnect."""
71
+ data = b''
72
+ while len(data) < size:
73
+ data_tmp = self.conn.recv(size - len(data))
74
+ data += data_tmp
75
+ if data_tmp == b'':
76
+ raise RuntimeError("socket connection broken")
77
+ return data
78
+
79
+ def _msg_length(self):
80
+ """Read and unpack the 4-byte big-endian length header."""
81
+ d = self._read(4)
82
+ s = struct.unpack('!I', d)
83
+ return s[0]
84
+
85
+ def read_obj(self):
86
+ """Read a full message and decode it as JSON, returning a Python object."""
87
+ size = self._msg_length()
88
+ data = self._read(size)
89
+ frmt = f"={size}s"
90
+ msg = struct.unpack(frmt, data)
91
+ return json.loads(msg[0].decode('utf-8'))
92
+
93
+ def close(self):
94
+ """Close active connection and the listening socket if open."""
95
+ logger.debug(
96
+ "closing sockets (socket fd=%s, conn fd=%s)",
97
+ _socket_fileno(self.socket),
98
+ _socket_fileno(self.conn),
99
+ )
100
+ self._close_connection()
101
+ self._close_socket()
102
+
103
+ def _close_socket(self):
104
+ """Best-effort shutdown and close of the main socket."""
105
+ logger.debug("closing main socket (fd=%s)", _socket_fileno(self.socket))
106
+ try:
107
+ if self.socket and self.socket.fileno() != -1:
108
+ try:
109
+ self.socket.shutdown(socket.SHUT_RDWR)
110
+ except OSError:
111
+ pass
112
+ try:
113
+ self.socket.close()
114
+ except OSError:
115
+ pass
116
+ except OSError:
117
+ pass
118
+
119
+ def _close_connection(self):
120
+ """Best-effort shutdown and close of the accepted connection socket."""
121
+ logger.debug("closing connection socket (fd=%s)", _socket_fileno(self.conn))
122
+ try:
123
+ if self.conn and self.conn is not self.socket and self.conn.fileno() != -1:
124
+ try:
125
+ self.conn.shutdown(socket.SHUT_RDWR)
126
+ except OSError:
127
+ pass
128
+ try:
129
+ self.conn.close()
130
+ except OSError:
131
+ pass
132
+ except OSError:
133
+ pass
134
+
135
+ def _get_timeout(self):
136
+ """Get the current socket timeout in seconds."""
137
+ return self._timeout
138
+
139
+ def _set_timeout(self, timeout):
140
+ """Set the socket timeout in seconds and apply to the main socket."""
141
+ self._timeout = timeout
142
+ self.socket.settimeout(timeout)
143
+
144
+ def _get_address(self):
145
+ """Return the configured bind address."""
146
+ return self._address
147
+
148
+ def _set_address(self, _address):
149
+ """No-op: address is read-only after initialization."""
150
+ return None
151
+
152
+ def _get_port(self):
153
+ """Return the configured bind port."""
154
+ return self._port
155
+
156
+ def _set_port(self, _port):
157
+ """No-op: port is read-only after initialization."""
158
+ return None
159
+
160
+ timeout = property(_get_timeout, _set_timeout, doc='Get/set the socket timeout')
161
+ address = property(_get_address, _set_address, doc='read only property socket address')
162
+ port = property(_get_port, _set_port, doc='read only property socket port')
163
+
164
+
117
165
  class JsonServer(JsonSocket):
118
- def __init__(self, address='127.0.0.1', port=5489):
119
- super(JsonServer, self).__init__(address, port)
120
- self._bind()
121
-
122
- def _bind(self):
123
- self.socket.bind( (self.address,self.port) )
124
-
125
- def _listen(self):
126
- self.socket.listen(1)
127
-
128
- def _accept(self):
129
- return self.socket.accept()
130
-
131
- def accept_connection(self):
132
- self._listen()
133
- self.conn, addr = self._accept()
134
- self.conn.settimeout(self.timeout)
135
- logger.debug("connection accepted, conn socket (%s,%d,%d)" % (addr[0],addr[1],self.conn.gettimeout()))
136
-
137
- def _is_connected(self):
138
- return True if not self.conn else False
139
-
140
- connected = property(_is_connected, doc="True if server is connected")
141
-
142
-
166
+ """Server socket that accepts one connection at a time."""
167
+
168
+ def __init__(self, address='127.0.0.1', port=5489):
169
+ super().__init__(address, port)
170
+ self._bind()
171
+
172
+ def _bind(self):
173
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
174
+ self.socket.bind((self.address, self.port))
175
+
176
+ def _listen(self):
177
+ self.socket.listen(5)
178
+
179
+ def _accept(self):
180
+ return self.socket.accept()
181
+
182
+ def accept_connection(self):
183
+ """Listen and accept a single client connection; set timeout accordingly."""
184
+ self._listen()
185
+ self.conn, addr = self._accept()
186
+ self.conn.settimeout(self.timeout)
187
+ logger.debug(
188
+ "connection accepted, conn socket (%s,%d,%s)", addr[0], addr[1], str(self.conn.gettimeout())
189
+ )
190
+
191
+ def _reset_connection_ref(self):
192
+ """Reset the server's connection reference to the listening socket."""
193
+ self.conn = self.socket
194
+
195
+ def _is_connected(self):
196
+ try:
197
+ return (self.conn is not None) and (self.conn is not self.socket) and (self.conn.fileno() != -1)
198
+ except (OSError, AttributeError):
199
+ return False
200
+
201
+ connected = property(_is_connected, doc="True if server has an active client connection")
202
+
203
+
143
204
  class JsonClient(JsonSocket):
144
- def __init__(self, address='127.0.0.1', port=5489):
145
- super(JsonClient, self).__init__(address, port)
146
-
147
- def connect(self):
148
- for i in range(10):
149
- try:
150
- self.socket.connect( (self.address, self.port) )
151
- except socket.error as msg:
152
- logger.error("SockThread Error: %s" % msg)
153
- time.sleep(3)
154
- continue
155
- logger.info("...Socket Connected")
156
- return True
157
- return False
158
-
159
-
160
- if __name__ == "__main__":
161
- """ basic json echo server """
162
- import threading
163
- logger.setLevel(logging.DEBUG)
164
- FORMAT = '[%(asctime)-15s][%(levelname)s][%(module)s][%(funcName)s] %(message)s'
165
- logging.basicConfig(format=FORMAT)
166
-
167
- def server_thread():
168
- logger.debug("starting JsonServer")
169
- server = JsonServer()
170
- server.accept_connection()
171
- while 1:
172
- try:
173
- msg = server.read_obj()
174
- logger.info("server received: %s" % msg)
175
- server.send_obj(msg)
176
- except socket.timeout as e:
177
- logger.debug("server socket.timeout: %s" % e)
178
- continue
179
- except Exception as e:
180
- logger.error("server: %s" % e)
181
- break
182
-
183
- server.close()
184
-
185
- t = threading.Timer(1,server_thread)
186
- t.start()
187
-
188
- time.sleep(2)
189
- logger.debug("starting JsonClient")
190
-
191
- client = JsonClient()
192
- client.connect()
193
-
194
- i = 0
195
- while i < 10:
196
- client.send_obj({"i": i})
197
- try:
198
- msg = client.read_obj()
199
- logger.info("client received: %s" % msg)
200
- except socket.timeout as e:
201
- logger.debug("client socket.timeout: %s" % e)
202
- continue
203
- except Exception as e:
204
- logger.error("client: %s" % e)
205
- break
206
- i = i + 1
207
-
208
- client.close()
205
+ """Client socket for connecting to a JsonServer and exchanging JSON messages."""
206
+
207
+ def __init__(self, address='127.0.0.1', port=5489):
208
+ super().__init__(address, port)
209
+
210
+ def connect(self):
211
+ """Attempt to connect to the server up to 10 times with backoff."""
212
+ for attempt in range(1, 11):
213
+ try:
214
+ logger.debug("connect attempt %d to %s:%s", attempt, self.address, self.port)
215
+ self.socket.connect((self.address, self.port))
216
+ except socket.error as msg:
217
+ logger.error("SockThread Error: %s", msg)
218
+ # Recreate the socket to avoid retrying on a potentially bad fd.
219
+ self._close_socket()
220
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
221
+ self.socket.settimeout(self._timeout)
222
+ self.conn = self.socket
223
+ logger.debug("recreated socket for retry %d to %s:%s", attempt, self.address, self.port)
224
+ time.sleep(3)
225
+ continue
226
+ logger.info("...Socket Connected")
227
+ return True
228
+ return False