tcpclient 2.1.2

Asynchronous tcpclient based on aqueue actor.
Documentation
# tcpclient

[![Crates.io](https://img.shields.io/crates/v/tcpclient.svg)](https://crates.io/crates/tcpclient)
[![Docs.rs](https://docs.rs/tcpclient/badge.svg)](https://docs.rs/tcpclient)
[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](https://github.com/luyikk/tcpclient#license)
[![MSRV](https://img.shields.io/badge/MSRV-1.75-orange.svg)](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html)

基于 [`aqueue`] actor 模型的异步 TCP 客户端。
Asynchronous TCP client built on the [`aqueue`] actor model.

[English]#english | [中文]#中文

---

<a name="english"></a>
## English

### Features

- **Actor-based architecture** — All write operations are serialized through [`aqueue::Actor`], ensuring thread safety without locks.
- **Stream-type agnostic** — Supports plain `TcpStream` out of the box, and any `AsyncRead + AsyncWrite` type (TLS, Unix sockets, etc.) via `connect_stream_type`.
- **Idempotent disconnect** — Safe to call `disconnect()` multiple times; no double-shutdown errors.
- **Flexible send API** — Owned buffers via `Deref<Target=[u8]>` (e.g. `Vec<u8>`, `Box<[u8]>`, `String`) or borrowed `&[u8]` with non-empty assertion.
- **Automatic reader lifecycle** — The user-supplied read closure runs in a spawned Tokio task; returning `Ok(true)` triggers graceful disconnect on exit.

### Quick Start

Add to your `Cargo.toml`:

```toml
[dependencies]
tcpclient = "2"
tokio = { version = "1", features = ["full"] }
log = "0.4"
```

**Minimum Supported Rust Version:** 1.75

### Usage

```rust
use tcpclient::{TcpClient, SocketClientTrait};
use tokio::io::AsyncReadExt;
use log::LevelFilter;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    env_logger::Builder::new().filter_level(LevelFilter::Debug).init();

    // Connect to echo server
    let client = TcpClient::connect(
        "127.0.0.1:5555",
        async move |_, client, mut reader| {
            let mut buff = [0u8; 7];
            while let Ok(len) = reader.read_exact(&mut buff).await {
                println!("{}", std::str::from_utf8(&buff[..len])?);
                client.send(&buff[..len]).await?;
            }
            Ok(true) // true = disconnect on exit
        },
        (), // token (arbitrary context data)
    ).await?;

    // Send data (guaranteed full write and flush)
    client.send_all_ref(b"hello!").await?;

    // Disconnect
    client.disconnect().await?;
    Ok(())
}
```

> **Note:** The example uses `async move` closures which require nightly Rust
> (`#![feature(async_closure)]`). On stable Rust, use a named async function or
> a closure returning a boxed future instead.

### Examples

More examples at: <https://github.com/luyikk/tcp_server/tree/master/examples>

### API Overview

- **send(buff)** — Best-effort write via `Deref<Target=[u8]>`. Returns bytes written.
- **send_all(buff)** — Full write + flush via `Deref<Target=[u8]>`.
- **send_ref(buff)** — Borrowed `&[u8]` variant of `send`. Asserts non-empty buffer.
- **send_all_ref(buff)** — Borrowed `&[u8]` variant of `send_all`. Asserts non-empty buffer.
- **flush()** — Flush the write buffer.
- **disconnect()** — Shut down the write half (idempotent).

All methods return `tcpclient::error::Result<T>`.

### TLS / Custom Streams

Use `connect_stream_type` to wrap the raw TCP stream (e.g. TLS handshake):

```rust,ignore
use tcpclient::TcpClient;

let client = TcpClient::connect_stream_type(
    "127.0.0.1:5555",
    |stream| async {
        // Upgrade TcpStream → TlsStream here
        Ok(stream) // return your wrapped stream
    },
    async move |_, client, mut reader| {
        // Handle the wrapped stream's read half
        Ok(true)
    },
    (),
).await?;
```

### License

Licensed under either of [MIT](https://opensource.org/licenses/MIT) or [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0), at your option.

---

<a name="中文"></a>
## 中文

### 功能特性

- **Actor 架构** — 所有写操作通过 [`aqueue::Actor`] 串行化,无需锁即可保证线程安全。
- **流类型无关** — 开箱即用支持 `TcpStream`,通过 `connect_stream_type` 支持任意 `AsyncRead + AsyncWrite` 类型(TLS、Unix socket 等)。
- **幂等断开**`disconnect()` 可安全重复调用,不会产生重复关闭错误。
- **灵活的发送 API** — 通过 `Deref<Target=[u8]>` 接受所有权缓冲区(`Vec<u8>``Box<[u8]>``String` 等),也接受 `&[u8]` 引用版本(带非空断言)。
- **自动读生命周期** — 用户提供的读闭包在独立 Tokio 任务中运行;返回 `Ok(true)` 会在退出时触发优雅断开。

### 快速开始

在 `Cargo.toml` 中添加:

```toml
[dependencies]
tcpclient = "2"
tokio = { version = "1", features = ["full"] }
log = "0.4"
```

**最低支持的 Rust 版本:** 1.75

### 使用示例

```rust
use tcpclient::{TcpClient, SocketClientTrait};
use tokio::io::AsyncReadExt;
use log::LevelFilter;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    env_logger::Builder::new().filter_level(LevelFilter::Debug).init();

    // 连接到 echo 服务器
    let client = TcpClient::connect(
        "127.0.0.1:5555",
        async move |_, client, mut reader| {
            let mut buff = [0u8; 7];
            while let Ok(len) = reader.read_exact(&mut buff).await {
                println!("{}", std::str::from_utf8(&buff[..len])?);
                client.send(&buff[..len]).await?;
            }
            Ok(true) // true = 退出时断开连接
        },
        (), // token(任意上下文数据)
    ).await?;

    // 发送数据(保证完整写入并刷新)
    client.send_all_ref(b"hello!").await?;

    // 断开连接
    client.disconnect().await?;
    Ok(())
}
```

> **注意:** 上述示例使用了 `async move` 闭包,需要 nightly Rust
>`#![feature(async_closure)]`)。在 stable Rust 上,可使用命名异步函数
> 或返回 boxed future 的闭包替代。

### 示例

更多示例见:<https://github.com/luyikk/tcp_server/tree/master/examples>

### API 总览

- **send(buff)** — 尽力写入,通过 `Deref<Target=[u8]>` 传递。返回写入字节数。
- **send_all(buff)** — 完整写入 + 刷新,通过 `Deref<Target=[u8]>` 传递。
- **send_ref(buff)**`send``&[u8]` 引用版本。断言缓冲区非空。
- **send_all_ref(buff)**`send_all``&[u8]` 引用版本。断言缓冲区非空。
- **flush()** — 刷新写缓冲区。
- **disconnect()** — 关闭写半(幂等)。

所有方法返回 `tcpclient::error::Result<T>`。

### TLS / 自定义流

使用 `connect_stream_type` 包装原始 TCP 流(如 TLS 握手):

```rust,ignore
use tcpclient::TcpClient;

let client = TcpClient::connect_stream_type(
    "127.0.0.1:5555",
    |stream| async {
        // 在这里将 TcpStream 升级为 TlsStream
        Ok(stream) // 返回包装后的流
    },
    async move |_, client, mut reader| {
        // 处理包装流的读半
        Ok(true)
    },
    (),
).await?;
```

### 许可证

根据 [MIT](https://opensource.org/licenses/MIT) 或 [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) 许可,任选其一。

[`aqueue`]: https://crates.io/crates/aqueue
[`aqueue::Actor`]: https://docs.rs/aqueue/latest/aqueue/struct.Actor.html