socketflow 0.1.0__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.
@@ -0,0 +1,407 @@
1
+ import threading
2
+ import socket as socket_module
3
+ from ..global_side.event import (
4
+ EventType,
5
+ ClientConnectData,
6
+ ClientDisconnectData,
7
+ MessageReceivedData,
8
+ ErrorData,
9
+ ServerStartData,
10
+ ServerStopData,
11
+ )
12
+ from ..global_side.dispatcher import EventDispatcher
13
+ from ..global_side.compression import MultiCompressor
14
+ from ..global_side.message_manager import message_manager
15
+ from ..global_side.message_handler import message_handler
16
+ from ..global_side.exceptions import ExceptionType
17
+ from typing import Optional
18
+ import uuid
19
+ import time
20
+ import concurrent.futures
21
+
22
+
23
+ class TcpServerProtocol:
24
+ def __init__(self, server):
25
+ self.server = server
26
+ self.socket = None
27
+ self.client_addr = None
28
+ self._buffer = bytearray()
29
+ self._last_ping_time = None
30
+ self._missed_pings = 0
31
+
32
+ def handle_data(self, data):
33
+ """Handle incoming data from client"""
34
+ self._buffer.extend(data)
35
+ self._missed_pings = 0
36
+
37
+ offset = 0
38
+ while len(self._buffer) - offset >= 4:
39
+ msg_len = int.from_bytes(self._buffer[offset : offset + 4], byteorder="big")
40
+
41
+ if len(self._buffer) - offset < 4 + msg_len:
42
+ break
43
+
44
+ start = offset + 4
45
+ end = start + msg_len
46
+ message_data = self._buffer[start:end]
47
+
48
+ offset = end
49
+
50
+ headers, body = message_handler.unpack_data(bytes(message_data))
51
+ if not headers or not isinstance(headers, dict):
52
+ error_msg = (
53
+ "Invalid message format"
54
+ if headers is None
55
+ else "Received message with invalid headers format"
56
+ )
57
+ self.server.dispatcher.emit(
58
+ EventType.Global.ERROR,
59
+ ErrorData(
60
+ error=ExceptionType.InvalidData(error_msg),
61
+ context="server.handle_data",
62
+ ),
63
+ )
64
+ continue
65
+
66
+ msg_type = headers.get("type")
67
+ if not msg_type:
68
+ self.server.dispatcher.emit(
69
+ EventType.Global.ERROR,
70
+ ErrorData(
71
+ error=ExceptionType.InvalidData(
72
+ "Received message without type"
73
+ ),
74
+ context="server.handle_data",
75
+ ),
76
+ )
77
+ continue
78
+
79
+ if msg_type == "__ping__":
80
+ pong_message = message_handler.create_pong()
81
+ try:
82
+ self.send(pong_message)
83
+ except Exception:
84
+ pass
85
+ elif msg_type == "__user__":
86
+ path = headers.get("path")
87
+ data_id = headers.get("id")
88
+ event_data = MessageReceivedData(
89
+ data=body, client_addr=self.client_addr, data_id=data_id
90
+ )
91
+
92
+ if data_id and data_id in self.server.pending_responses:
93
+ future = self.server.pending_responses.pop(data_id)
94
+ if hasattr(future, "set_result") and not future.done():
95
+ future.set_result(body)
96
+ continue
97
+
98
+ if path:
99
+ self.server.dispatcher.emit_path(path, event_data)
100
+ else:
101
+ self.server.dispatcher.emit(EventType.Server.MESSAGE, event_data)
102
+
103
+ if offset > 0:
104
+ del self._buffer[:offset]
105
+
106
+ def send(self, data):
107
+ """Send data to client"""
108
+ if self.socket:
109
+ try:
110
+ self.socket.sendall(data)
111
+ except Exception as e:
112
+ raise ExceptionType.MessageHandlerError(e)
113
+ else:
114
+ raise ExceptionType.NotConnected("Not connected to client")
115
+
116
+ def handle_connection_lost(self):
117
+ """Handle client disconnection"""
118
+ self.server._clients.pop(self.client_addr, None)
119
+
120
+ for data_id, future in list(self.server.pending_responses.items()):
121
+ if hasattr(future, "set_exception") and not future.done():
122
+ future.set_exception(
123
+ ExceptionType.NotConnected(
124
+ f"Client {self.client_addr} disconnected"
125
+ )
126
+ )
127
+ self.server.pending_responses.clear()
128
+
129
+ self.server.dispatcher.emit(
130
+ EventType.Server.CLIENT_DISCONNECT,
131
+ ClientDisconnectData(client_addr=self.client_addr, transport=self.socket),
132
+ )
133
+
134
+ if self.socket:
135
+ self.socket.close()
136
+
137
+ self.socket = None
138
+
139
+ def keepalive_check(self):
140
+ """Send periodic pings to client and check for missed pongs"""
141
+ while self.client_addr in self.server._clients:
142
+ try:
143
+ ping_message = message_handler.create_ping()
144
+ self.send(ping_message)
145
+ except Exception:
146
+ pass
147
+
148
+ if self.client_addr not in self.server._clients:
149
+ break
150
+
151
+ time.sleep(self.server.keepalive_interval)
152
+
153
+ self._missed_pings += 1
154
+
155
+ if self._missed_pings >= self.server.keepalive_max_missed:
156
+ self.server.dispatcher.emit(
157
+ EventType.Global.ERROR,
158
+ ErrorData(
159
+ error=ExceptionType.KeepaliveTimeout("Keepalive timeout"),
160
+ context="server.keepalive",
161
+ ),
162
+ )
163
+ self.handle_connection_lost()
164
+ break
165
+
166
+
167
+ class TcpServer:
168
+ def __init__(
169
+ self,
170
+ host: str = "127.0.0.1",
171
+ port: int = 8080,
172
+ compression_type: str = "zlib",
173
+ compression_level: int = 6,
174
+ compress: bool = True,
175
+ keepalive_interval: float = 30.0,
176
+ keepalive_max_missed: int = 3,
177
+ flow_control: bool = True,
178
+ recv_buffer_size: int = 65536,
179
+ send_buffer_size: int = 65536,
180
+ ):
181
+ self.host = host
182
+ self.port = port
183
+ self.dispatcher = EventDispatcher()
184
+ self._server = None
185
+ self._clients = {}
186
+ self.compression_type = compression_type
187
+ self.compression_level = compression_level
188
+ self.compress = compress
189
+ self.keepalive_interval = keepalive_interval
190
+ self.keepalive_max_missed = keepalive_max_missed
191
+ self.flow_control_enabled = flow_control
192
+ self.pending_responses = {}
193
+ self.seperator = b"\r\nSOCKETFLOW\r\n"
194
+ self.recv_buffer_size = recv_buffer_size
195
+ self.send_buffer_size = send_buffer_size
196
+
197
+ def _handle_connection(self, client_socket, address):
198
+ protocol = TcpServerProtocol(self)
199
+ protocol.socket = client_socket
200
+ protocol.client_addr = address
201
+ self._clients[address] = protocol
202
+
203
+ client_socket.setsockopt(
204
+ socket_module.SOL_SOCKET, socket_module.SO_RCVBUF, self.recv_buffer_size
205
+ )
206
+ client_socket.setsockopt(
207
+ socket_module.SOL_SOCKET, socket_module.SO_SNDBUF, self.send_buffer_size
208
+ )
209
+
210
+ client_socket.setsockopt(
211
+ socket_module.SOL_SOCKET, socket_module.SO_KEEPALIVE, 1
212
+ )
213
+
214
+ threading.Thread(target=protocol.keepalive_check, daemon=True).start()
215
+
216
+ self.dispatcher.emit(
217
+ EventType.Server.CLIENT_CONNECT,
218
+ ClientConnectData(client_addr=address, transport=client_socket),
219
+ )
220
+
221
+ while True:
222
+ try:
223
+ data = client_socket.recv(65536)
224
+ if not data:
225
+ break
226
+ protocol.handle_data(data)
227
+ except socket_module.timeout:
228
+ pass
229
+ except Exception:
230
+ break
231
+ protocol.handle_connection_lost()
232
+
233
+ def start(self):
234
+ """Start the server"""
235
+ try:
236
+ self._server = socket_module.socket(
237
+ socket_module.AF_INET, socket_module.SOCK_STREAM
238
+ )
239
+ self._server.setsockopt(
240
+ socket_module.SOL_SOCKET, socket_module.SO_REUSEADDR, 1
241
+ )
242
+ self._server.bind((self.host, self.port))
243
+ self._server.listen(100)
244
+
245
+ self.dispatcher.emit(
246
+ EventType.Server.START, ServerStartData(host=self.host, port=self.port)
247
+ )
248
+
249
+ # Start accepting connections in a separate thread
250
+ threading.Thread(target=self._accept_connections, daemon=True).start()
251
+
252
+ except Exception as e:
253
+ self.dispatcher.emit(
254
+ EventType.Global.ERROR, ErrorData(error=e, context="server.start")
255
+ )
256
+ raise
257
+
258
+ def _accept_connections(self):
259
+ """Accept incoming connections"""
260
+ while self._server: # Check if server is still running
261
+ try:
262
+ client_socket, address = self._server.accept()
263
+ threading.Thread(
264
+ target=self._handle_connection,
265
+ args=(client_socket, address),
266
+ daemon=True,
267
+ ).start()
268
+ except Exception as e:
269
+ if not self._server: # Server was stopped
270
+ break
271
+ self.dispatcher.emit(
272
+ EventType.Global.ERROR, ErrorData(error=e, context="server.accept")
273
+ )
274
+
275
+ def stop(self):
276
+ """Stop the server"""
277
+ if self._server:
278
+ self._server.close()
279
+ self._server = None
280
+
281
+ # Disconnect all existing clients
282
+ for client_addr, protocol in list(self._clients.items()):
283
+ if protocol.socket:
284
+ protocol.socket.close()
285
+ self._clients.clear()
286
+
287
+ self.dispatcher.emit(
288
+ EventType.Server.STOP, ServerStopData(host=self.host, port=self.port)
289
+ )
290
+
291
+ def send_client(
292
+ self,
293
+ client_addr: tuple,
294
+ data: bytes,
295
+ data_id: Optional[str] = None,
296
+ path: Optional[str] = None,
297
+ wait_response: bool = False,
298
+ wait_response_timeout: Optional[float] = 30.0,
299
+ ):
300
+ if data_id is None:
301
+ data_id = str(uuid.uuid4())
302
+
303
+ headers = {
304
+ "type": "__user__",
305
+ "id": data_id,
306
+ "path": path,
307
+ }
308
+
309
+ if self.compress:
310
+ try:
311
+ compressed_msg = MultiCompressor.compress(
312
+ data, method=self.compression_type, level=self.compression_level
313
+ )
314
+ headers["compressed"] = True
315
+ length_bytes, encoded_message = message_manager.encode_with_length(
316
+ headers, compressed_msg
317
+ )
318
+ except Exception as e:
319
+ self.dispatcher.emit(
320
+ EventType.Global.ERROR,
321
+ ErrorData(
322
+ error=ExceptionType.CompressionError(
323
+ f"Compression failed: {e}"
324
+ ),
325
+ context="server.send_client",
326
+ ),
327
+ )
328
+ raise ExceptionType.CompressionError(f"Compression failed: {e}")
329
+ else:
330
+ length_bytes, encoded_message = message_manager.encode_with_length(
331
+ headers, data
332
+ )
333
+
334
+ protocol = self._clients.get(client_addr)
335
+ if not protocol:
336
+ raise ExceptionType.NotConnected(f"Client {client_addr} not connected")
337
+
338
+ protocol.send(length_bytes + encoded_message)
339
+ if wait_response:
340
+ future = concurrent.futures.Future()
341
+ self.pending_responses[data_id] = future
342
+
343
+ def timeout_handler():
344
+ if data_id in self.pending_responses:
345
+ self.pending_responses.pop(data_id, None)
346
+ if not future.done():
347
+ future.set_exception(
348
+ ExceptionType.NoResponse(
349
+ f"No response received within {wait_response_timeout} timeout"
350
+ )
351
+ )
352
+
353
+ threading.Timer(wait_response_timeout, timeout_handler).start()
354
+
355
+ try:
356
+ return future.result()
357
+ except ExceptionType.NotConnected:
358
+ raise ExceptionType.NoResponse(
359
+ f"No response received within {wait_response_timeout} timeout - not connected"
360
+ )
361
+ except ExceptionType.NoResponse:
362
+ raise ExceptionType.NoResponse(
363
+ f"No response received within {wait_response_timeout} timeout"
364
+ )
365
+ except Exception as e:
366
+ raise ExceptionType.ClientError(
367
+ f"Error while waiting for response: {e}"
368
+ )
369
+ finally:
370
+ self.pending_responses.pop(data_id, None)
371
+
372
+ def disconnect_client(self, client_addr: tuple):
373
+ """Disconnect a specific client"""
374
+ protocol = self._clients.pop(client_addr, None)
375
+ if protocol and protocol.socket:
376
+ protocol.socket.close()
377
+
378
+ def wait(self):
379
+ """Wait for server to run (blocking)"""
380
+ try:
381
+ while self._server:
382
+ time.sleep(0.1)
383
+ except KeyboardInterrupt:
384
+ self.stop()
385
+ except Exception:
386
+ self.stop()
387
+
388
+ def start_and_wait(self):
389
+ """Start server and wait (blocking)"""
390
+ self.start()
391
+ self.wait()
392
+
393
+ def get_connected_clients(self):
394
+ return len(self._clients)
395
+
396
+ def is_connected(self, client_addr):
397
+ return client_addr in self._clients
398
+
399
+ def event(self, event_type: str):
400
+ return self.dispatcher.event(event_type)
401
+
402
+ def path(self, path: str, middleware=None):
403
+ return self.dispatcher.path(path, middleware)
404
+
405
+ def register_blueprint(self, blueprint):
406
+ blueprint._server = self # Associate blueprint with this server
407
+ self.dispatcher.register_blueprint(blueprint)
@@ -0,0 +1,308 @@
1
+ Metadata-Version: 2.4
2
+ Name: socketflow
3
+ Version: 0.1.0
4
+ Summary: An asynchronous networking library for Python with advanced features
5
+ Home-page: https://github.com/ayammaximilian/socketflow
6
+ Author: SocketFlow Team
7
+ Author-email: contact@socketflow.dev
8
+ License: MIT
9
+ Project-URL: Bug Reports, https://github.com/ayammaximilian/socketflow/issues
10
+ Project-URL: Source, https://github.com/ayammaximilian/socketflow
11
+ Project-URL: Documentation, https://socketflow.dev
12
+ Keywords: networking,tcp,socket,server,client,async,asyncio,real-time,messaging,clustering,ssl,tls,encryption,compression,middleware,events,blueprint,failover,load-balancing,redis,distributed
13
+ Platform: any
14
+ Classifier: Development Status :: 5 - Production/Stable
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.7
20
+ Classifier: Programming Language :: Python :: 3.8
21
+ Classifier: Programming Language :: Python :: 3.9
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Topic :: Internet
26
+ Classifier: Topic :: Internet :: WWW/HTTP
27
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
28
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
29
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
30
+ Classifier: Topic :: System :: Networking
31
+ Classifier: Topic :: Communications
32
+ Classifier: Topic :: Security :: Cryptography
33
+ Requires-Python: >=3.7
34
+ Description-Content-Type: text/markdown
35
+ Dynamic: author
36
+ Dynamic: author-email
37
+ Dynamic: classifier
38
+ Dynamic: description
39
+ Dynamic: description-content-type
40
+ Dynamic: home-page
41
+ Dynamic: keywords
42
+ Dynamic: license
43
+ Dynamic: platform
44
+ Dynamic: project-url
45
+ Dynamic: requires-python
46
+ Dynamic: summary
47
+
48
+ # SocketFlow
49
+
50
+ A high-performance, dependency-free TCP networking library for Python with advanced features like compression, event handling, bidirectional keepalive, and more.
51
+
52
+ ## Features
53
+
54
+ - **Zero Dependencies** - Uses only Python's standard library
55
+ - **Bidirectional Keepalive** - Both client and server independently monitor connection health
56
+ - **TCP-Level Keepalive** - OS-managed keepalive for reliable connection detection
57
+ - **Compression** - Support for zlib, lzma, and bz2 compression
58
+ - **Event-Driven Architecture** - Flexible event dispatcher for handling server/client events
59
+ - **Blueprint System** - Organize your code with reusable blueprints
60
+ - **Middleware Support** - Add custom middleware to request/response processing
61
+ - **Path-Based Routing** - Route messages to specific handlers using paths
62
+ - **Efficient Buffer Handling** - O(N) buffer processing with offset pattern
63
+ - **Type Hints** - Full type annotations for better IDE support
64
+ - **Cross-Platform** - Works on Windows, Linux, and macOS
65
+
66
+ ## Installation
67
+
68
+ ```bash
69
+ pip install socketflow
70
+ ```
71
+
72
+ **Full documentation available at:** https://socketflow.dev/
73
+
74
+ Or install from source:
75
+
76
+ ```bash
77
+ git clone https://github.com/ayammaximilian/socketflow.git
78
+ cd socketflow
79
+ pip install .
80
+ ```
81
+
82
+ ## Quick Start
83
+
84
+ ### Server Example
85
+
86
+ ```python
87
+ from socketflow import TcpServer, EventType
88
+
89
+ # Create server
90
+ server = TcpServer(
91
+ host="127.0.0.1",
92
+ port=8080,
93
+ keepalive_interval=30.0,
94
+ keepalive_max_missed=3,
95
+ compress=True
96
+ )
97
+
98
+ # Register event handler
99
+ @server.event(EventType.Server.MESSAGE)
100
+ def handle_message(data):
101
+ print(f"Received: {data}")
102
+ return "Response"
103
+
104
+ # Start server
105
+ server.start()
106
+ server.wait() # Keep server running
107
+ ```
108
+
109
+ ### Client Example
110
+
111
+ ```python
112
+ from socketflow import TcpClient, EventType
113
+
114
+ # Create client
115
+ client = TcpClient(
116
+ host="127.0.0.1",
117
+ port=8080,
118
+ keepalive_interval=30.0,
119
+ keepalive_max_missed=3,
120
+ compress=True
121
+ )
122
+
123
+ # Connect to server
124
+ client.connect()
125
+
126
+ # Register event handler
127
+ @client.event(EventType.Client.MESSAGE)
128
+ def handle_message(data):
129
+ print(f"Received: {data}")
130
+
131
+ # Send message
132
+ response = client.send("Hello, Server!", wait_response=True)
133
+ print(f"Server response: {response}")
134
+
135
+ # Disconnect
136
+ client.disconnect()
137
+ ```
138
+
139
+ ## Configuration
140
+
141
+ ### Server Options
142
+
143
+ | Parameter | Type | Default | Description |
144
+ |-----------|------|---------|-------------|
145
+ | `host` | str | "127.0.0.1" | Server host address |
146
+ | `port` | int | 8080 | Server port |
147
+ | `compression_type` | str | "zlib" | Compression algorithm (zlib, lzma, bz2) |
148
+ | `compression_level` | int | 6 | Compression level (1-9) |
149
+ | `compress` | bool | True | Enable compression |
150
+ | `keepalive_interval` | float | 30.0 | Keepalive interval in seconds |
151
+ | `keepalive_max_missed` | int | 3 | Max missed keepalives before disconnect |
152
+ | `recv_buffer_size` | int | 65536 | Receive buffer size |
153
+ | `send_buffer_size` | int | 65536 | Send buffer size |
154
+
155
+ ### Client Options
156
+
157
+ | Parameter | Type | Default | Description |
158
+ |-----------|------|---------|-------------|
159
+ | `host` | str | "127.0.0.1" | Server host address |
160
+ | `port` | int | 8080 | Server port |
161
+ | `compression_type` | str | "zlib" | Compression algorithm (zlib, lzma, bz2) |
162
+ | `compression_level` | int | 6 | Compression level (1-9) |
163
+ | `compress` | bool | True | Enable compression |
164
+ | `keepalive_interval` | float | 30.0 | Keepalive interval in seconds |
165
+ | `keepalive_max_missed` | int | 3 | Max missed keepalives before disconnect |
166
+ | `connection_timeout` | float | 10.0 | Connection timeout in seconds |
167
+ | `recv_buffer_size` | int | 65536 | Receive buffer size |
168
+ | `send_buffer_size` | int | 65536 | Send buffer size |
169
+
170
+ ## API Reference
171
+
172
+ ### TcpServer
173
+
174
+ #### Methods
175
+
176
+ - `start()` - Start the server
177
+ - `stop()` - Stop the server and disconnect all clients
178
+ - `wait()` - Block until server stops
179
+ - `start_and_wait()` - Start server and block
180
+ - `send_client(client_addr, data, path=None, wait_response=False)` - Send data to specific client
181
+ - `disconnect_client(client_addr)` - Disconnect a specific client
182
+ - `get_connected_clients()` - Get number of connected clients
183
+ - `is_connected(client_addr)` - Check if client is connected
184
+ - `event(event_type)` - Decorator to register event handler
185
+ - `path(path, middleware=None)` - Decorator to register path handler
186
+ - `register_blueprint(blueprint)` - Register a blueprint
187
+
188
+ ### TcpClient
189
+
190
+ #### Methods
191
+
192
+ - `connect()` - Connect to server
193
+ - `disconnect()` - Disconnect from server
194
+ - `send(data, path=None, wait_response=False)` - Send data to server
195
+ - `wait()` - Block until client disconnects
196
+ - `connect_and_wait()` - Connect and block
197
+ - `is_connected()` - Check if connected
198
+ - `event(event_type)` - Decorator to register event handler
199
+ - `path(path, middleware=None)` - Decorator to register path handler
200
+ - `register_blueprint(blueprint)` - Register a blueprint
201
+
202
+ ## Events
203
+
204
+ ### Server Events
205
+
206
+ - `EventType.Server.START` - Server started
207
+ - `EventType.Server.STOP` - Server stopped
208
+ - `EventType.Server.CLIENT_CONNECT` - Client connected
209
+ - `EventType.Server.CLIENT_DISCONNECT` - Client disconnected
210
+ - `EventType.Server.MESSAGE` - Message received from client
211
+
212
+ ### Client Events
213
+
214
+ - `EventType.Client.CONNECT` - Connected to server
215
+ - `EventType.Client.DISCONNECT` - Disconnected from server
216
+ - `EventType.Client.MESSAGE` - Message received from server
217
+
218
+ ### Global Events
219
+
220
+ - `EventType.Global.ERROR` - Error occurred
221
+
222
+ ## Path-Based Routing
223
+
224
+ Send messages to specific handlers using paths:
225
+
226
+ ```python
227
+ # Server
228
+ @server.path("/user/login")
229
+ def handle_login(data):
230
+ # Handle login
231
+ pass
232
+
233
+ @server.path("/user/register")
234
+ def handle_register(data):
235
+ # Handle registration
236
+ pass
237
+
238
+ # Client
239
+ client.send(data, path="/user/login")
240
+ ```
241
+
242
+ ## Blueprints
243
+
244
+ Organize your code with blueprints:
245
+
246
+ ```python
247
+ from socketflow import Blueprint
248
+
249
+ user_bp = Blueprint("user")
250
+
251
+ @user_bp.path("/login")
252
+ def login(data):
253
+ pass
254
+
255
+ @user_bp.path("/register")
256
+ def register(data):
257
+ pass
258
+
259
+ # Register blueprint
260
+ server.register_blueprint(user_bp)
261
+ ```
262
+
263
+ ## Keepalive
264
+
265
+ SocketFlow implements bidirectional keepalive at two levels:
266
+
267
+ 1. **Application-Level Keepalive** - Custom ping/pong messages
268
+ 2. **TCP-Level Keepalive** - OS-managed keepalive probes
269
+
270
+ Both client and server independently monitor connection health based on their own configurations.
271
+
272
+ ## Compression
273
+
274
+ Support for multiple compression algorithms:
275
+
276
+ - **zlib** - Fast compression, good balance
277
+ - **lzma** - High compression ratio, slower
278
+ - **bz2** - Good compression, moderate speed
279
+
280
+ ## Error Handling
281
+
282
+ SocketFlow provides custom exception types:
283
+
284
+ - `NotConnected` - Connection not established
285
+ - `ConnectionTimeout` - Connection attempt timed out
286
+ - `KeepaliveTimeout` - Keepalive timeout
287
+ - `CompressionError` - Compression/decompression error
288
+ - `InvalidData` - Invalid message format
289
+ - `NoResponse` - No response received within timeout
290
+ - `MessageHandlerError` - Message handling error
291
+
292
+ ## License
293
+
294
+ MIT License - see LICENSE file for details
295
+
296
+ ## Contributing
297
+
298
+ Contributions are welcome! Please feel free to submit a Pull Request.
299
+
300
+ ## Support
301
+
302
+ - GitHub Issues: https://github.com/ayammaximilian/socketflow/issues
303
+ - Documentation: https://socketflow.dev/
304
+
305
+ ## Requirements
306
+
307
+ - Python 3.7+
308
+ - No external dependencies (uses only standard library)