socket-netty 0.3.0__tar.gz
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.
- socket_netty-0.3.0/MANIFEST.in +2 -0
- socket_netty-0.3.0/PKG-INFO +402 -0
- socket_netty-0.3.0/README.md +382 -0
- socket_netty-0.3.0/examples/01_echo_client.py +49 -0
- socket_netty-0.3.0/examples/01_echo_server.py +56 -0
- socket_netty-0.3.0/examples/02_game_protocol_client.py +96 -0
- socket_netty-0.3.0/examples/02_game_protocol_server.py +132 -0
- socket_netty-0.3.0/pynetty/__init__.py +137 -0
- socket_netty-0.3.0/pynetty/bootstrap/__init__.py +3 -0
- socket_netty-0.3.0/pynetty/bootstrap/bootstrap.py +162 -0
- socket_netty-0.3.0/pynetty/buffer/__init__.py +18 -0
- socket_netty-0.3.0/pynetty/buffer/allocator.py +113 -0
- socket_netty-0.3.0/pynetty/buffer/bytebuf.py +256 -0
- socket_netty-0.3.0/pynetty/channel/__init__.py +24 -0
- socket_netty-0.3.0/pynetty/channel/channel.py +130 -0
- socket_netty-0.3.0/pynetty/channel/channel_future.py +143 -0
- socket_netty-0.3.0/pynetty/channel/channel_option.py +59 -0
- socket_netty-0.3.0/pynetty/channel/channel_pipeline.py +173 -0
- socket_netty-0.3.0/pynetty/channel/datagram_channel.py +164 -0
- socket_netty-0.3.0/pynetty/channel/event_loop.py +157 -0
- socket_netty-0.3.0/pynetty/channel/flow_control.py +99 -0
- socket_netty-0.3.0/pynetty/channel/protocol_adapter.py +59 -0
- socket_netty-0.3.0/pynetty/exceptions.py +94 -0
- socket_netty-0.3.0/pynetty/handler/__init__.py +47 -0
- socket_netty-0.3.0/pynetty/handler/channel_handler.py +83 -0
- socket_netty-0.3.0/pynetty/handler/channel_handler_context.py +104 -0
- socket_netty-0.3.0/pynetty/handler/codec.py +152 -0
- socket_netty-0.3.0/pynetty/handler/protolib_codec.py +152 -0
- socket_netty-0.3.0/pynetty/handler/ssl_context.py +62 -0
- socket_netty-0.3.0/pynetty/handler/timeout.py +159 -0
- socket_netty-0.3.0/pyproject.toml +32 -0
- socket_netty-0.3.0/setup.cfg +4 -0
- socket_netty-0.3.0/socket_netty.egg-info/PKG-INFO +402 -0
- socket_netty-0.3.0/socket_netty.egg-info/SOURCES.txt +44 -0
- socket_netty-0.3.0/socket_netty.egg-info/dependency_links.txt +1 -0
- socket_netty-0.3.0/socket_netty.egg-info/requires.txt +1 -0
- socket_netty-0.3.0/socket_netty.egg-info/top_level.txt +1 -0
- socket_netty-0.3.0/tests/test_bytebuf.py +75 -0
- socket_netty-0.3.0/tests/test_codec.py +281 -0
- socket_netty-0.3.0/tests/test_concurrency.py +69 -0
- socket_netty-0.3.0/tests/test_core_extras.py +80 -0
- socket_netty-0.3.0/tests/test_echo.py +71 -0
- socket_netty-0.3.0/tests/test_flow_and_allocator.py +204 -0
- socket_netty-0.3.0/tests/test_network_extras.py +138 -0
- socket_netty-0.3.0/tests/test_pipeline.py +370 -0
- socket_netty-0.3.0/tests/test_tls.py +78 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: socket-netty
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Asynchronous networking library for Python, inspired by Netty
|
|
5
|
+
Author: Button
|
|
6
|
+
Keywords: networking,asyncio,netty,tcp,udp,game-server,protocol
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Classifier: Topic :: System :: Networking
|
|
16
|
+
Classifier: Framework :: AsyncIO
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: protolib>=0.4.3
|
|
20
|
+
|
|
21
|
+
# pynetty 🐍🛠️
|
|
22
|
+
|
|
23
|
+
Asynchronous networking library for Python, closely inspired by
|
|
24
|
+
[Netty](https://netty.io) (Java). Built on top of `asyncio`, with no
|
|
25
|
+
external dependencies.
|
|
26
|
+
|
|
27
|
+
## Coverage relative to Netty
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
Netty Py 🐍
|
|
31
|
+
├── Bootstrap ✅
|
|
32
|
+
├── ServerBootstrap ✅
|
|
33
|
+
├── Channel ✅ (TCP)
|
|
34
|
+
├── ChannelPipeline ✅
|
|
35
|
+
├── ChannelHandler ✅ (inbound + outbound)
|
|
36
|
+
├── ChannelHandlerContext ✅
|
|
37
|
+
├── EventLoop ✅
|
|
38
|
+
├── EventLoopGroup ✅
|
|
39
|
+
├── ChannelFuture ✅ (+ ChannelPromise)
|
|
40
|
+
├── ByteBuf ✅
|
|
41
|
+
├── Allocator ✅ (Unpooled + Pooled)
|
|
42
|
+
├── TCP ✅
|
|
43
|
+
├── UDP ✅ (DatagramChannel)
|
|
44
|
+
├── Socket options ✅ (ChannelOption)
|
|
45
|
+
├── Backpressure ✅ (WriteBufferWaterMark)
|
|
46
|
+
├── Concurrency ✅ (ChannelExecutor)
|
|
47
|
+
├── TLS/SSL ✅ (SslContextBuilder)
|
|
48
|
+
├── Encoders / Decoders ✅ (length-based framing)
|
|
49
|
+
├── Idle handlers ✅ (IdleStateHandler)
|
|
50
|
+
├── Timeouts ✅ (Read/WriteTimeoutHandler)
|
|
51
|
+
├── Exception handling ✅ (exception_caught via the pipeline,
|
|
52
|
+
│ io.netty.*-style exception classes)
|
|
53
|
+
└── Declarative packet codec ✅ (ProtolibCodec, via the protolib
|
|
54
|
+
dependency)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install socket-netty
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
(The PyPI distribution is named `socket-netty`, but the importable
|
|
64
|
+
package is still `pynetty` — your code stays `import pynetty`.)
|
|
65
|
+
|
|
66
|
+
Or, for local development from a clone of this repo:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install -e .
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`pynetty` declares [`protolib`](https://pypi.org/project/protolib/) as
|
|
73
|
+
an install dependency, so either install method pulls it in
|
|
74
|
+
automatically — you don't need a separate install step to use
|
|
75
|
+
`ProtolibCodec` below.
|
|
76
|
+
|
|
77
|
+
## Structure
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
pynetty/
|
|
81
|
+
exceptions.py -> Netty-style exception hierarchy (DecoderException,
|
|
82
|
+
EncoderException, CorruptedFrameException, etc.)
|
|
83
|
+
buffer/ -> ByteBuf, ByteBufAllocator (Unpooled/Pooled)
|
|
84
|
+
handler/ -> ChannelHandler, codecs, protolib bridge, SSL,
|
|
85
|
+
timeouts/idle
|
|
86
|
+
channel/ -> Channel, DatagramChannel, ChannelPipeline,
|
|
87
|
+
EventLoop/Group, ChannelFuture, ChannelOption,
|
|
88
|
+
flow control (backpressure + concurrency)
|
|
89
|
+
bootstrap/ -> ServerBootstrap (server), Bootstrap (client)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Example: TCP echo server
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
import asyncio
|
|
96
|
+
from pynetty import ServerBootstrap, ChannelInboundHandler
|
|
97
|
+
|
|
98
|
+
class EchoHandler(ChannelInboundHandler):
|
|
99
|
+
async def channel_active(self, ctx):
|
|
100
|
+
print("Client connected:", ctx.channel.remote_address())
|
|
101
|
+
|
|
102
|
+
async def channel_read(self, ctx, msg):
|
|
103
|
+
await ctx.write(msg) # echo
|
|
104
|
+
|
|
105
|
+
async def exception_caught(self, ctx, exc):
|
|
106
|
+
print("Error:", exc)
|
|
107
|
+
await ctx.close()
|
|
108
|
+
|
|
109
|
+
async def main():
|
|
110
|
+
def init_channel(channel):
|
|
111
|
+
channel.pipeline.add_last("echo", EchoHandler())
|
|
112
|
+
|
|
113
|
+
server = await ServerBootstrap().child_handler(init_channel).bind("0.0.0.0", 9000)
|
|
114
|
+
await server.serve_forever()
|
|
115
|
+
|
|
116
|
+
asyncio.run(main())
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Exceptions
|
|
120
|
+
|
|
121
|
+
pynetty raises the same exception hierarchy Netty does, under
|
|
122
|
+
`io.netty.*`. Every exception's `str()` is prefixed with its
|
|
123
|
+
fully-qualified Java-style path, so logs and `exception_caught` output
|
|
124
|
+
look exactly like a real Netty stack:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from pynetty import DecoderException
|
|
128
|
+
|
|
129
|
+
async def exception_caught(self, ctx, exc):
|
|
130
|
+
print(exc)
|
|
131
|
+
# io.netty.handler.codec.DecoderException: Failed to decode packet
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Available exceptions (all subclass `NettyException`):
|
|
135
|
+
|
|
136
|
+
| Class | Netty path |
|
|
137
|
+
|---|---|
|
|
138
|
+
| `ChannelException` | `io.netty.channel.ChannelException` |
|
|
139
|
+
| `DuplicateHandlerNameError` | `io.netty.channel.ChannelPipelineException` |
|
|
140
|
+
| `CodecException` | `io.netty.handler.codec.CodecException` |
|
|
141
|
+
| `DecoderException` | `io.netty.handler.codec.DecoderException` |
|
|
142
|
+
| `EncoderException` | `io.netty.handler.codec.EncoderException` |
|
|
143
|
+
| `CorruptedFrameException` | `io.netty.handler.codec.CorruptedFrameException` |
|
|
144
|
+
| `TooLongFrameException` | `io.netty.handler.codec.TooLongFrameException` |
|
|
145
|
+
| `ReadTimeoutError` | `io.netty.handler.timeout.ReadTimeoutException` |
|
|
146
|
+
| `WriteTimeoutError` | `io.netty.handler.timeout.WriteTimeoutException` |
|
|
147
|
+
| `IndexOutOfBoundsError` | `io.netty.buffer.IndexOutOfBoundsException` |
|
|
148
|
+
|
|
149
|
+
`LengthFieldBasedFrameDecoder` and `ByteToMessageCodec` automatically
|
|
150
|
+
wrap any unexpected error raised inside `decode()` into a
|
|
151
|
+
`DecoderException`, and `LengthFieldPrepender` does the same for
|
|
152
|
+
`EncoderException` — matching Netty's own behavior of never letting a
|
|
153
|
+
raw internal error escape a codec unwrapped.
|
|
154
|
+
|
|
155
|
+
## Socket options
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from pynetty import ServerBootstrap, ChannelOption
|
|
159
|
+
|
|
160
|
+
server = (
|
|
161
|
+
ServerBootstrap()
|
|
162
|
+
.child_handler(init_channel)
|
|
163
|
+
.option(ChannelOption.SO_REUSEADDR, True)
|
|
164
|
+
.option(ChannelOption.SO_BACKLOG, 128)
|
|
165
|
+
.option(ChannelOption.TCP_NODELAY, True) # low latency, typical for games
|
|
166
|
+
)
|
|
167
|
+
await server.bind("0.0.0.0", 9000)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## TLS/SSL
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from pynetty import ServerBootstrap, Bootstrap, SslContextBuilder
|
|
174
|
+
|
|
175
|
+
# Server
|
|
176
|
+
server_ctx = SslContextBuilder.for_server("cert.pem", "key.pem").build()
|
|
177
|
+
server = ServerBootstrap().child_handler(init_channel).ssl(server_ctx)
|
|
178
|
+
await server.bind("0.0.0.0", 9443)
|
|
179
|
+
|
|
180
|
+
# Client
|
|
181
|
+
client_ctx = SslContextBuilder.for_client().trust_manager("cert.pem").build()
|
|
182
|
+
channel = await Bootstrap().handler(init_client).ssl(client_ctx).connect("myserver.com", 9443)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## UDP
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from pynetty import DatagramBootstrap, ChannelInboundHandler
|
|
189
|
+
|
|
190
|
+
class UdpHandler(ChannelInboundHandler):
|
|
191
|
+
async def channel_read(self, ctx, msg):
|
|
192
|
+
data, addr = msg # UDP delivers (bytes, (host, port))
|
|
193
|
+
await ctx.write((b"pong", addr))
|
|
194
|
+
|
|
195
|
+
channel = await DatagramBootstrap().handler(
|
|
196
|
+
lambda ch: ch.pipeline.add_last("udp", UdpHandler())
|
|
197
|
+
).bind("0.0.0.0", 9001)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Idle / Timeouts
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
from pynetty import IdleStateHandler, ReadTimeoutHandler
|
|
204
|
+
|
|
205
|
+
async def on_idle(ctx, event):
|
|
206
|
+
print("Idle channel:", event.state)
|
|
207
|
+
await ctx.write(b"PING") # e.g. a game protocol heartbeat
|
|
208
|
+
|
|
209
|
+
def init_channel(channel):
|
|
210
|
+
idle = IdleStateHandler(reader_idle_seconds=30)
|
|
211
|
+
idle.on_idle = on_idle
|
|
212
|
+
channel.pipeline.add_last("idle", idle)
|
|
213
|
+
channel.pipeline.add_last("read_timeout", ReadTimeoutHandler(60)) # closes if no data in 60s
|
|
214
|
+
channel.pipeline.add_last("my_handler", MyHandler())
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## EventLoopGroup (real multi-threaded concurrency)
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
from pynetty import EventLoopGroup
|
|
221
|
+
|
|
222
|
+
worker_group = EventLoopGroup(num_threads=4)
|
|
223
|
+
worker_group.start()
|
|
224
|
+
|
|
225
|
+
loop = worker_group.next_loop() # round-robin
|
|
226
|
+
future = loop.submit(lambda: my_heavy_coroutine())
|
|
227
|
+
result = future.result(timeout=5) # blocking, from any thread
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## ChannelFuture / ChannelPromise
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
from pynetty import ChannelFuture
|
|
234
|
+
|
|
235
|
+
future = channel.new_future()
|
|
236
|
+
future.add_listener(lambda f: print("Finished:", f.is_success()))
|
|
237
|
+
|
|
238
|
+
# Still directly awaitable, Python-style:
|
|
239
|
+
result = await future
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Backpressure (WriteBufferWaterMark)
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
from pynetty import ServerBootstrap, WriteBufferWaterMark
|
|
246
|
+
|
|
247
|
+
server = (
|
|
248
|
+
ServerBootstrap()
|
|
249
|
+
.child_handler(init_channel)
|
|
250
|
+
.water_mark(WriteBufferWaterMark(low=32*1024, high=64*1024))
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# In the handler:
|
|
254
|
+
if not ctx.channel.is_writable():
|
|
255
|
+
# pause sending more data until it becomes writable again
|
|
256
|
+
...
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Concurrency (ChannelExecutor)
|
|
260
|
+
|
|
261
|
+
Guarantees a channel's messages are processed one at a time, in
|
|
262
|
+
order, even across `await` points (avoids race conditions on shared
|
|
263
|
+
handler state):
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
from pynetty import ChannelExecutor
|
|
267
|
+
|
|
268
|
+
executor = ChannelExecutor()
|
|
269
|
+
executor.start()
|
|
270
|
+
|
|
271
|
+
async def channel_read(self, ctx, msg):
|
|
272
|
+
await executor.submit(self._process(ctx, msg))
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Allocator (buffer pool)
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
from pynetty import PooledByteBufAllocator
|
|
279
|
+
|
|
280
|
+
allocator = PooledByteBufAllocator()
|
|
281
|
+
buf = allocator.buffer(256)
|
|
282
|
+
buf.write_int(42)
|
|
283
|
+
# ... use the buffer ...
|
|
284
|
+
allocator.release(buf) # goes back to the pool, ready to be reused
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Protocol framing (length-prefixed packets)
|
|
288
|
+
|
|
289
|
+
Very common in game protocols (Minecraft, Free Fire, etc.):
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
from pynetty import LengthFieldBasedFrameDecoder, LengthFieldPrepender
|
|
293
|
+
|
|
294
|
+
def init_channel(channel):
|
|
295
|
+
channel.pipeline.add_last("frame_decoder", LengthFieldBasedFrameDecoder(4))
|
|
296
|
+
channel.pipeline.add_last("frame_prepender", LengthFieldPrepender(4))
|
|
297
|
+
channel.pipeline.add_last("my_handler", MyHandler())
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Protocol decoding with protolib (declarative packets)
|
|
301
|
+
|
|
302
|
+
`LengthFieldBasedFrameDecoder`/`LengthFieldPrepender` above only solve
|
|
303
|
+
framing (where one packet ends and the next begins) — you still have
|
|
304
|
+
to hand-write the code that turns those raw bytes into a meaningful
|
|
305
|
+
packet. [`protolib`](https://pypi.org/project/protolib/) solves that
|
|
306
|
+
second half: you describe every packet's fields in a `.yml`/`.json`
|
|
307
|
+
file, and `ProtolibCodec` plugs that description directly into the
|
|
308
|
+
pipeline. `channel_read` then hands your handler a ready-made
|
|
309
|
+
`{"name": ..., "params": {...}}` dict instead of raw bytes, and
|
|
310
|
+
`write` accepts the same shape (or a `(name, params)` tuple) going
|
|
311
|
+
out.
|
|
312
|
+
|
|
313
|
+
`ProtolibCodec` has two framing modes, controlled by `framed`:
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
from pynetty import ServerBootstrap, ChannelInboundHandler, ProtolibCodec
|
|
317
|
+
|
|
318
|
+
# framed=True (default): the codec frames the stream itself via
|
|
319
|
+
# protolib's own PacketFramer (varint length-prefix, Minecraft-style).
|
|
320
|
+
# Don't also add LengthFieldBasedFrameDecoder in this mode.
|
|
321
|
+
def init_channel(channel):
|
|
322
|
+
channel.pipeline.add_last(
|
|
323
|
+
"protolib", ProtolibCodec("my_protocol.yml", state="play",
|
|
324
|
+
direction_in="toServer", direction_out="toClient"),
|
|
325
|
+
)
|
|
326
|
+
channel.pipeline.add_last("game", GameHandler())
|
|
327
|
+
|
|
328
|
+
class GameHandler(ChannelInboundHandler):
|
|
329
|
+
async def channel_read(self, ctx, msg):
|
|
330
|
+
print(msg["name"], msg["params"]) # ready-made dict
|
|
331
|
+
await ctx.write(("keep_alive", {"id": 1})) # (name, params) out
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
```python
|
|
335
|
+
# framed=False: for fixed-size / non-varint protocols (e.g. Minecraft
|
|
336
|
+
# Classic/ClassiCube), keep your own LengthFieldBasedFrameDecoder in
|
|
337
|
+
# front and let ProtolibCodec just parse/serialize the complete frame
|
|
338
|
+
# it's handed.
|
|
339
|
+
def init_channel(channel):
|
|
340
|
+
channel.pipeline.add_last("frame_decoder", LengthFieldBasedFrameDecoder(1))
|
|
341
|
+
channel.pipeline.add_last(
|
|
342
|
+
"protolib", ProtolibCodec("classicube_protocol.yml", framed=False),
|
|
343
|
+
)
|
|
344
|
+
channel.pipeline.add_last("game", GameHandler())
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
`protocol` accepts a `protolib.Protocol` instance you already built,
|
|
348
|
+
or anything `Protocol(...)` itself accepts (a `.yml`/`.json` path, an
|
|
349
|
+
in-memory string, or a parsed dict) — `ProtolibCodec` builds the
|
|
350
|
+
`Protocol` for you in that case. Any parsing/serialization error
|
|
351
|
+
protolib raises is wrapped into `DecoderException`/`EncoderException`,
|
|
352
|
+
same as the rest of the codecs in this library.
|
|
353
|
+
|
|
354
|
+
## ByteBuf
|
|
355
|
+
|
|
356
|
+
```python
|
|
357
|
+
from pynetty import ByteBuf
|
|
358
|
+
|
|
359
|
+
buf = ByteBuf()
|
|
360
|
+
buf.write_varint(1000)
|
|
361
|
+
buf.write_string("hello")
|
|
362
|
+
buf.write_int(-42)
|
|
363
|
+
|
|
364
|
+
data = buf.to_bytes()
|
|
365
|
+
|
|
366
|
+
read_buf = ByteBuf.wrapped(data)
|
|
367
|
+
n = read_buf.read_varint()
|
|
368
|
+
s = read_buf.read_string()
|
|
369
|
+
i = read_buf.read_int()
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Supports: `byte`, `unsigned_byte`, `short`, `unsigned_short`, `int`,
|
|
373
|
+
`unsigned_int`, `long`, `float`, `double`, `boolean`, `varint`,
|
|
374
|
+
`varlong` (Minecraft/protobuf style), `string`, and raw bytes.
|
|
375
|
+
|
|
376
|
+
## Tests
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
python3 tests/test_bytebuf.py
|
|
380
|
+
python3 tests/test_echo.py
|
|
381
|
+
python3 tests/test_core_extras.py # EventLoop, ChannelFuture, Allocator
|
|
382
|
+
python3 tests/test_network_extras.py # UDP, socket options, timeouts, idle
|
|
383
|
+
python3 tests/test_tls.py # end-to-end TLS (generates temporary certs)
|
|
384
|
+
python3 tests/test_concurrency.py # ChannelExecutor
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Design notes
|
|
388
|
+
|
|
389
|
+
- The entire pipeline is 100% `async`/`await`.
|
|
390
|
+
- `EventLoopGroup` uses real OS threads (each with its own asyncio
|
|
391
|
+
loop), unlike the rest of the library which normally runs on a
|
|
392
|
+
single loop — it's the honest way to replicate Netty's model
|
|
393
|
+
(thread pool) in Python.
|
|
394
|
+
- `ByteBuf` is a simple implementation backed by `bytearray`, with no
|
|
395
|
+
manual refcounting (Python already has GC).
|
|
396
|
+
- `PooledByteBufAllocator` recycles buffers by size "bucket" (powers
|
|
397
|
+
of 2), useful in high-traffic game servers to reduce GC pressure.
|
|
398
|
+
- Exceptions mirror Netty's own `io.netty.*` hierarchy (see
|
|
399
|
+
[Exceptions](#exceptions) above), so error messages and logs read
|
|
400
|
+
the same as a real Netty stack trace.
|
|
401
|
+
- Designed for use both in game projects (custom servers, protocol
|
|
402
|
+
reversing) and as a general-purpose library.
|