Skip to main content

py-netty :rocket:

CI codecov Downloads

py-netty is a lightweight Netty-style, event-driven TCP networking framework for Python, focused on non-blocking socket programming with callback-based channel handlers.

Features

  • Netty-style programming model: build TCP clients and servers with Bootstrap, ServerBootstrap, EventLoopGroup, Channel, and callback-based ChannelHandler APIs.
  • Non-blocking TCP I/O: sockets are managed by selector-backed event loops, keeping application code event-driven instead of thread-per-connection.
  • Thread-confined socket operations: recv and write are performed from the owning I/O thread, making channel behavior predictable under concurrency.
  • Backpressure-aware writes: low and high water marks expose channel writability changes, with defaults of 32 KiB and 64 KiB.
  • Adaptive read buffering: read buffer sizes grow or shrink based on recent traffic patterns to balance throughput and memory usage.
  • TLS support for clients and servers: wrap TCP channels with SSL/TLS, including optional certificate verification and custom SSL context hooks.
  • Cross-platform selector support: uses the best selector available on each platform, such as epoll on Linux, kqueue on macOS, and select on Windows.

Installation

pip install py-netty

Getting Started

Start an echo server:

from py_netty import ServerBootstrap
ServerBootstrap().bind(address='0.0.0.0', port=8080).close_future().sync()

Start an echo server (TLS):

from py_netty import ServerBootstrap
ServerBootstrap(certfile='/path/to/cert/file', keyfile='/path/to/key/file').bind(address='0.0.0.0', port=9443).close_future().sync()

As TCP client:

from py_netty import Bootstrap, ChannelHandlerAdapter


class HttpHandler(ChannelHandlerAdapter):
    def channel_read(self, ctx, buffer):
        print(buffer.decode('utf-8'))
        

remote_address, remote_port = 'www.google.com', 80
b = Bootstrap(handler_initializer=HttpHandler)
channel = b.connect(remote_address, remote_port).sync().channel()
request = f'GET / HTTP/1.1\r\nHost: {remote_address}\r\n\r\n'
channel.write(request.encode('utf-8'))
input() # pause
channel.close()

As TCP client (TLS):

from py_netty import Bootstrap, ChannelHandlerAdapter


class HttpHandler(ChannelHandlerAdapter):
    def channel_read(self, ctx, buffer):
        print(buffer.decode('utf-8'))
        

remote_address, remote_port = 'www.google.com', 443
b = Bootstrap(handler_initializer=HttpHandler, tls=True, verify=True)
channel = b.connect(remote_address, remote_port).sync().channel()
request = f'GET / HTTP/1.1\r\nHost: {remote_address}\r\n\r\n'
channel.write(request.encode('utf-8'))
input() # pause
channel.close()

TCP port forwarding:

from py_netty import ServerBootstrap, Bootstrap, ChannelHandlerAdapter, EventLoopGroup


class ProxyChannelHandler(ChannelHandlerAdapter):

    def __init__(self, remote_host, remote_port, client_eventloop_group):
        self._remote_host = remote_host
        self._remote_port = remote_port
        self._client_eventloop_group = client_eventloop_group
        self._client = None

    def _client_channel(self, ctx0):

        class __ChannelHandler(ChannelHandlerAdapter):
            def channel_read(self, ctx, bytebuf):
                ctx0.write(bytebuf)

            def channel_inactive(self, ctx):
                ctx0.close()

        if self._client is None:
            self._client = Bootstrap(
                eventloop_group=self._client_eventloop_group,
                handler_initializer=__ChannelHandler
            ).connect(self._remote_host, self._remote_port).sync().channel()
        return self._client

    def exception_caught(self, ctx, exception):
        ctx.close()

    def channel_read(self, ctx, bytebuf):
        self._client_channel(ctx).write(bytebuf)

    def channel_inactive(self, ctx):
        if self._client:
            self._client.close()


proxied_server, proxied_port = 'www.google.com', 443
client_eventloop_group = EventLoopGroup(1, 'ClientEventloopGroup')
sb = ServerBootstrap(
    parent_group=EventLoopGroup(1, 'Acceptor'),
    child_group=EventLoopGroup(1, 'Worker'),
    child_handler_initializer=lambda: ProxyChannelHandler(proxied_server, proxied_port, client_eventloop_group)
)
sb.bind(port=8443).close_future().sync()

Event-driven callbacks

Create handler with callbacks for interested events:

from py_netty import ChannelHandlerAdapter


class MyChannelHandler(ChannelHandlerAdapter):
    def channel_active(self, ctx: 'ChannelHandlerContext') -> None:
        # invoked when channel is active (TCP connection ready)
        pass

    def channel_read(self, ctx: 'ChannelHandlerContext', msg: Union[bytes, socket.socket]) -> None:
        # invoked when there is data ready to process
        pass

    def channel_inactive(self, ctx: 'ChannelHandlerContext') -> None:
        # invoked when channel is inactive (TCP connection is broken)
        pass

    def channel_registered(self, ctx: 'ChannelHandlerContext') -> None:
        # invoked when the channel is registered with a eventloop
        pass

    def channel_unregistered(self, ctx: 'ChannelHandlerContext') -> None:
        # invoked when the channel is unregistered from a eventloop
        pass

    def channel_handshake_complete(self, ctx: 'ChannelHandlerContext') -> None:
        # invoked when ssl handshake is complete, this only applies to client side
        pass

    def channel_writability_changed(self, ctx: 'ChannelHandlerContext') -> None:
        # invoked when pending data > high water mark or < low water mark
        pass

    def exception_caught(self, ctx: 'ChannelHandlerContext', exception: Exception) -> None:
        # invoked when there is any exception raised during process
        pass

Benchmark

The current benchmark uses the local echo performance runner in integration_tests/perf_echo.py. By default, each case starts an in-process localhost echo server for the selected engine. You can also provide --port with optional --host to run the same framed clients against a separately deployed echo server. The runner validates every echo and reports throughput, message rate, latency, and connection ramp-up time.

The following results were collected locally with:

python integration_tests/perf_echo.py --case all --engine all --timeout 20 --json
python integration_tests/perf_echo.py --case high_connection_scaling --engine all --timeout 30 --json

Environment: macOS 26.5 arm64, Python 3.12.10.

The default suite covers latency, payload throughput, backpressure, and moderate concurrency. It is useful for comparing broad behavior, not for declaring one engine universally faster.

Case Engine Connections Payload Messages Throughput Message rate p50 latency p95 latency Ramp-up
single_connection_latency py-netty 1 64 B 200 0.53 MiB/s 8,754 msg/s 0.10 ms 0.18 ms 0.54 ms
single_connection_latency asyncio 1 64 B 200 0.61 MiB/s 9,968 msg/s 0.09 ms 0.13 ms 0.36 ms
single_connection_latency threaded 1 64 B 200 1.48 MiB/s 24,198 msg/s 0.04 ms 0.05 ms 1.59 ms
backpressure_smoke py-netty 8 64 KiB 256 300.67 MiB/s 4,811 msg/s 40.49 ms 47.03 ms 6.19 ms
backpressure_smoke asyncio 8 64 KiB 256 732.10 MiB/s 11,714 msg/s 15.62 ms 20.29 ms 1.02 ms
backpressure_smoke threaded 8 64 KiB 256 823.77 MiB/s 13,180 msg/s 8.97 ms 11.51 ms 0.90 ms
large_payload_throughput py-netty 16 64 KiB 512 787.83 MiB/s 12,605 msg/s 30.47 ms 37.78 ms 2.78 ms
large_payload_throughput asyncio 16 64 KiB 512 896.81 MiB/s 14,349 msg/s 25.72 ms 33.34 ms 1.27 ms
large_payload_throughput threaded 16 64 KiB 512 799.81 MiB/s 12,797 msg/s 18.75 ms 25.12 ms 1.32 ms
small_payload_concurrency py-netty 32 1 KiB 6,400 38.20 MiB/s 39,121 msg/s 138.20 ms 154.94 ms 15.80 ms
small_payload_concurrency asyncio 32 1 KiB 6,400 81.92 MiB/s 83,889 msg/s 40.98 ms 66.23 ms 2.43 ms
small_payload_concurrency threaded 32 1 KiB 6,400 36.10 MiB/s 36,968 msg/s 87.09 ms 115.36 ms 2.63 ms
connection_ramp_up py-netty 64 64 B 64 1.06 MiB/s 17,332 msg/s 2.64 ms 3.24 ms 20.28 ms
connection_ramp_up asyncio 64 64 B 64 1.00 MiB/s 16,315 msg/s 1.88 ms 2.05 ms 7.61 ms
connection_ramp_up threaded 64 64 B 64 0.58 MiB/s 9,545 msg/s 2.49 ms 4.19 ms 5.15 ms

High Connection Scaling

The high connection-count suite stresses 128, 256, and 512 concurrent localhost connections with 20 messages per connection and 1 KiB payloads. It highlights where py-netty's event-loop model pulls ahead of the one-thread-per-connection threaded implementation.

In this run, py-netty kept a much steadier message rate as connection count increased, while the threaded implementation degraded more quickly. Compared with threaded sockets, py-netty delivered 24% higher message rate at 128 connections, 48% higher at 256 connections, and 57% higher at 512 connections. This makes the 256-connection case the clearest inflection point for the threaded approach in this local test. asyncio is included as a standard library event-loop baseline and remained the fastest engine by raw message rate in these high-connection cases.

Case Engine Connections Payload Messages Throughput Message rate p50 latency p95 latency Ramp-up
high_connection_128 py-netty 128 1 KiB 2,560 45.45 MiB/s 46,537 msg/s 49.45 ms 49.86 ms 28.65 ms
high_connection_128 asyncio 128 1 KiB 2,560 65.19 MiB/s 66,753 msg/s 20.37 ms 31.71 ms 35.85 ms
high_connection_128 threaded 128 1 KiB 2,560 36.56 MiB/s 37,442 msg/s 25.44 ms 55.51 ms 9.38 ms
high_connection_256 py-netty 256 1 KiB 5,120 45.72 MiB/s 46,817 msg/s 97.92 ms 100.84 ms 75.80 ms
high_connection_256 asyncio 256 1 KiB 5,120 60.68 MiB/s 62,134 msg/s 42.07 ms 67.71 ms 118.30 ms
high_connection_256 threaded 256 1 KiB 5,120 30.79 MiB/s 31,534 msg/s 25.04 ms 34.16 ms 47.32 ms
high_connection_512 py-netty 512 1 KiB 10,240 40.85 MiB/s 41,831 msg/s 222.10 ms 228.65 ms 83.80 ms
high_connection_512 asyncio 512 1 KiB 10,240 60.88 MiB/s 62,346 msg/s 86.16 ms 135.54 ms 64.47 ms
high_connection_512 threaded 512 1 KiB 10,240 25.98 MiB/s 26,606 msg/s 53.93 ms 94.08 ms 121.09 ms

Metrics are informational and environment-dependent. The comparison uses three local implementations: py-netty, Python asyncio, and blocking sockets with one thread per connection (threaded). The performance runner fails only on functional problems such as missing echoes, payload mismatches, connection failures, or timeouts.

Throughput

echo throughput comparison

Message Rate

echo message rate comparison

Latency

echo latency comparison

Connection Ramp-up

echo connection ramp-up comparison

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

py_netty-1.1.2.tar.gz (40.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

py_netty-1.1.2-py3-none-any.whl (22.4 kB view details)

Uploaded Python 3

File details

Details for the file py_netty-1.1.2.tar.gz.

File metadata

  • Download URL: py_netty-1.1.2.tar.gz
  • Upload date:
  • Size: 40.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for py_netty-1.1.2.tar.gz
Algorithm Hash digest
SHA256 d9cba0537def33132cb8a4be70f42fb262e0a5f4564bd90950a75def01822f4e
MD5 926ee246bcbaaef2c6f6f70675c5a97a
BLAKE2b-256 9a404b3a5f9247e15a1c3cc8e50f5f2d5ab50ad8df9a2b3ebabb45bc7b07a738

See more details on using hashes here.

File details

Details for the file py_netty-1.1.2-py3-none-any.whl.

File metadata

  • Download URL: py_netty-1.1.2-py3-none-any.whl
  • Upload date:
  • Size: 22.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for py_netty-1.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 2f04c8016377c6f6101a4f69f5c7c1d17a82d2c9344239c226d8e8575b9af903
MD5 d2a4cc9322ef42765122efd818e26935
BLAKE2b-256 a739c86077e1dc281afc078dc13df0fe8fed75e66743cf871125c3b6c45e8113

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.1.2 This release

2 files

1.1.0

2 files

1.0.12

2 files

1.0.9

2 files

1.0.8

2 files

1.0.7

2 files

1.0.6

2 files

1.0.5

2 files

1.0.4

2 files

1.0.3

2 files

1.0.2

2 files

1.0.1

2 files

1.0.0

2 files

0.0.42

2 files

0.0.41

2 files

0.0.40

2 files

0.0.39

2 files

0.0.38

2 files

0.0.37

2 files

0.0.36

2 files

0.0.35

2 files

0.0.34

2 files

0.0.33

2 files

0.0.32

2 files

0.0.31

2 files

0.0.30

2 files

0.0.22

2 files

0.0.21

2 files

0.0.20

2 files

0.0.19

2 files

0.0.17

2 files

0.0.16

2 files

0.0.14

2 files

0.0.13

2 files

0.0.12

2 files

0.0.11

2 files

0.0.10

2 files

0.0.9

2 files

0.0.8

2 files

0.0.7

2 files

0.0.6

1 file

0.0.5

2 files

0.0.4

2 files

0.0.3

2 files

0.0.2

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page