rs-netty 1.1.0

A Tokio-native typed TCP/UDP pipeline framework inspired by Netty.
Documentation
# Lifecycle

`Life` is the optional lifecycle hook trait. The default implementation is `NoLife`, where all hooks are no-ops.

```rust
#[derive(Clone, Copy)]
struct PrintLife;

impl rs_netty::Life for PrintLife {
    async fn tcp_server_started(&self, local_addr: std::net::SocketAddr) -> rs_netty::Result<()> {
        println!("server started: {local_addr}");
        Ok(())
    }
}
```

## Hooks

Current hooks:

- `tcp_server_started(local_addr)`
- `tcp_server_stopped(local_addr)`
- `tcp_connection_opened(info)`
- `tcp_connection_closed(info, reason)`
- `udp_socket_started(local_addr)`
- `udp_socket_stopped(local_addr)`

TCP close reasons use `CloseReason`, including `PeerClosed`, `LocalClosed`, `ChannelClosed`, `HandlerClosed`, `ServerShutdown`, `IdleTimeout`, `IoError`, `DecodeError`, `EncodeError`, `FrameTooLarge`, and `HandlerError`.

## Startup And Shutdown Behavior

TCP server `start()` binds the listener and then calls `tcp_server_started`. If that hook returns an error, `start()` returns the error.

Each accepted TCP connection, and each connected TCP client, calls `tcp_connection_opened`. If that hook fails, the connection task returns an error.

Hook failures during shutdown are logged, and the runtime tries to preserve the original close result.

The UDP socket task calls `udp_socket_started` when it starts and `udp_socket_stopped` when it stops.

## Graceful Shutdown

TCP and UDP server `start()` methods return handles:

```rust
let server = TcpServer::bind("127.0.0.1:0")
    .pipeline(|| pipeline().codec(LineCodec::new()).handler(Echo))
    .start()
    .await?;

server.shutdown();
server.wait().await?;
# Ok::<(), rs_netty::Error>(())
```

TCP server shutdown uses a watch channel to notify both the accept loop and connection tasks. UDP server shutdown notifies the socket task to exit.

Clients do not have a server-style shutdown handle. Use `client.close().await?` to request local connection/socket shutdown, then `client.wait().await?` to join the task.

## Idle Timeout

TCP supports `idle_timeout(duration)`. It closes a connection that has had no socket reads for the configured duration, and reports `CloseReason::IdleTimeout` to lifecycle hooks. In the source, the timer is reset only by successful reads; outbound writes do not reset the read-idle timer.

UDP currently has no idle timeout.

## Connection Stats

TCP servers and clients can enable stats with `track_connection_stats()`. After that:

- `Context::stats()` returns the current connection stats.
- `Channel::stats()` returns a cloneable stats handle.
- stats include `connected_at`, `bytes_read`, `bytes_written`, `frames_read`, and `frames_written`.

Stats are disabled by default to avoid shared allocations and atomic updates when monitoring is not needed.