# tcpclient
[](https://crates.io/crates/tcpclient)
[](https://docs.rs/tcpclient)
[](https://github.com/luyikk/tcpclient#license)
[](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.
---
<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