> For the complete documentation index, see [llms.txt](https://litedb.gitbook.io/litedb-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://litedb.gitbook.io/litedb-docs/architecture_and_design/network_protocol/transport_protocol.md).

# 传输协议

传输层负责在 TCP 字节流上收发完整的 LiteDB 帧。它不解释 Payload 的业务含义，只识别固定帧头，从中取得整帧长度，再读取剩余字节。各类 Payload 的字段定义见[消息协议](/litedb-docs/architecture_and_design/network_protocol/message_protocol.md)。

## 传输模型

| 项目    | 当前实现                           |
| ----- | ------------------------------ |
| 承载协议  | TCP，异步 IO 基于 Asio 协程           |
| 定帧方式  | 固定 32 字节帧头 + `Frame Size` 长度前缀 |
| 字节序   | 帧头和 Payload 内的多字节数值均为大端        |
| 最大帧   | 16 MiB，包含帧头                    |
| 连接内并发 | 串行请求—响应，不支持流水线或多路复用            |
| 多连接   | 每个已接受连接由独立协程和独立 `Session` 处理   |

TCP 不保留消息边界：一次底层读取可能只得到半个帧头，也可能跨过多个应用帧。实现使用 `asio::async_read` 精确读满指定长度，因此线上的连续帧可以表示为：

```txt
TCP byte stream
|-------- Frame 1 --------|-------- Frame 2 --------| ...
| 32-byte header | payload| 32-byte header | payload|
```

## 固定帧头

v1 帧头恰好为 32 字节，不能使用 C++ `sizeof(FrameHeader)` 代替线上长度。

| Offset | 大小 | 类型    | 字段            | v1 约束                           |
| -----: | -: | ----- | ------------- | ------------------------------- |
|      0 |  4 | `u32` | `Magic`       | 固定为 `0x4C444250`，ASCII 为 `LDBP` |
|      4 |  4 | `u32` | `Frame Size`  | 整帧字节数，包含 32 字节帧头                |
|      8 |  2 | `u16` | `Version`     | 固定为 `1`                         |
|     10 |  2 | `u16` | `Header Size` | 固定为 `32`（`0x0020`）              |
|     12 |  2 | `u16` | `Kind`        | 必须是已定义的稀疏消息编号                   |
|     14 |  2 | `u16` | `Flags`       | v1 必须为 `0`                      |
|     16 |  8 | `u64` | `Request ID`  | 请求—响应关联 ID                      |
|     24 |  8 | `u64` | `Reserved`    | v1 必须为 `0`                      |

Payload 从 offset 32 开始：

```txt
+----------------------+ offset 0
| Magic                | 4 bytes
+----------------------+
| Frame Size           | 4 bytes
+----------------------+
| Version | Header Size| 2 + 2 bytes
+----------------------+
| Kind    | Flags      | 2 + 2 bytes
+----------------------+
| Request ID           | 8 bytes
+----------------------+
| Reserved             | 8 bytes
+----------------------+ offset 32
| Payload              | Frame Size - 32 bytes
+----------------------+
```

`Frame Size` 的合法范围是 `32..16777216`。因此默认最大 Payload 是 `16777216 - 32 = 16777184` 字节。

## 分阶段读帧

生产 TCP 路径使用 `async_read_frame(socket, max_frame_size)`，流程如下：

1. 用 `async_read` 精确读满 32 字节帧头；
2. 调用 `decode_frame_header` 一次性解析并校验线上头部，得到语义 `FrameHeader` 和 `frame_size`；
3. 再校验 `frame_size` 不超过调用方配置的 `max_frame_size`；
4. 分配 `frame_size - 32` 字节的 Payload 缓冲区，并精确读满；
5. 组装 `Frame { header, payload }` 交给服务端消息处理。

这一流程会在分配 Payload 前拒绝错误的 Magic、版本、头长、消息类型、Flags、Reserved 或超大帧。它也避免先拼出“头 + Payload”大缓冲区再复制 Payload。

这仍不是应用层流式处理：Payload 必须完整进入 `std::vector<std::byte>` 后，消息编解码器才会解析它，SQL 结果也仍以单帧返回。

`decode_frame(bytes)` 是另一条完整缓冲区解码入口，要求输入恰好包含一帧；截断或帧后存在多余字节都会失败。它适合协议测试、golden bytes、重放或其他已经拥有完整帧的传输，不用于当前 TCP 分阶段读取路径。

## 写帧

`async_write_frame(socket, frame)` 的流程是：

1. `encode_frame` 校验语义头部和 Payload 大小；
2. 编码器规范化写入 Magic、整帧长度、32 字节头长和全零 Reserved；
3. 把帧头和 Payload 编码成一个连续字节数组；
4. 用 `asio::async_write` 写完整个数组。

调用方提供的 `FrameHeader` 只包含 `version`、`kind`、`flags` 和 `request_id`。`Frame Size`、`Header Size`、Magic 与 Reserved 是线格式字段，由 codec 计算或填充。

## 连接生命周期

建立 TCP 连接后不能直接发送 SQL。服务端在每个连接上维护是否已握手的状态：

1. 第一帧必须是 `HelloRequest`；
2. 服务端确认客户端版本范围包含 v1，并返回同 `Request ID` 的 `HelloResponse`；
3. 握手后才处理 Ping、SQL、Cancel 和 Close；
4. `CloseRequest` 不需要响应，服务端结束连接。

若握手前收到其他已知消息，服务端会尽力返回 `HandshakeRequired` 错误帧，然后断开。若握手后收到方向错误的响应类消息或第二个 `HelloRequest`，服务端会返回 `UnexpectedMessage`，然后断开。

## 错误与断连策略

| 发生阶段                              | 行为                                              |
| --------------------------------- | ----------------------------------------------- |
| TCP 读写失败                          | 网络层创建带 `std::error_code` 上下文的 Asio 网络错误；服务端结束连接 |
| 帧头不足或字段非法                         | 协议错误直接向上传播；此时没有可信完整请求，服务端直接结束连接                 |
| 帧超过 16 MiB 硬上限                    | codec 返回 `FrameTooLarge`，不分配 Payload            |
| 帧超过 `ServerConfig.max_frame_size` | 网络层返回 `FrameTooLarge`，不读取 Payload               |
| 合法帧的 Payload 不符合消息 Schema         | 服务端通常先返回 `ErrorResponse`；是否断连取决于消息处理分支          |
| SQL 执行失败                          | 返回 `ErrorResponse`，连接保持可用                       |

`ServerConfig.max_frame_size` 限制的是整帧长度，不是 Payload 长度；它不能放宽协议内置的 16 MiB 硬上限。

## 当前边界

* 仅明文 TCP，无 TLS、认证、压缩或校验和；Magic 只能用于协议识别，不能验证内容完整性；
* 地址直接由 `asio::ip::make_address` 解析，当前没有 DNS 解析流程；
* 没有传输层读写超时、空闲超时、自动重连或重试；
* 没有分片和增量结果消费；每个 Payload 都会完整缓冲；
* 客户端一次 `roundtrip` 只允许一个在途请求，服务端也按连接逐帧串行执行。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://litedb.gitbook.io/litedb-docs/architecture_and_design/network_protocol/transport_protocol.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
