693 lines
14 KiB
Markdown
693 lines
14 KiB
Markdown
# Gomog TCP 协议参考文档
|
||
|
||
**版本**: v1.0.0-alpha
|
||
**最后更新**: 2026-03-14
|
||
**协议**: MongoDB Wire Protocol (兼容)
|
||
|
||
---
|
||
|
||
## 📖 目录
|
||
|
||
1. [概述](#概述)
|
||
2. [消息格式](#消息格式)
|
||
3. [操作码](#操作码)
|
||
4. [OP_MSG 协议](#op_msg-协议)
|
||
5. [使用示例](#使用示例)
|
||
6. [客户端开发](#客户端开发)
|
||
7. [错误处理](#错误处理)
|
||
|
||
---
|
||
|
||
## 概述
|
||
|
||
### 什么是 TCP 协议?
|
||
|
||
Gomog 的 TCP 协议实现了 MongoDB Wire Protocol 的兼容层,允许任何 MongoDB 客户端直接连接到 Gomog 服务器,无需修改代码。
|
||
|
||
### 协议特点
|
||
|
||
- ✅ **MongoDB 兼容**: 支持标准 MongoDB 线协议
|
||
- ✅ **双向通信**: 基于 TCP 的长连接
|
||
- ✅ **高效传输**: 二进制协议,低开销
|
||
- ✅ **并发安全**: 支持多客户端并发连接
|
||
- ✅ **会话管理**: 自动管理连接状态
|
||
|
||
### 默认配置
|
||
|
||
| 配置项 | 默认值 | 说明 |
|
||
|--------|--------|------|
|
||
| 监听地址 | `:27017` | MongoDB 默认端口 |
|
||
| 最大连接数 | 无限制 | 可配置 |
|
||
| 读超时 | 无 | 可配置 |
|
||
| 写超时 | 无 | 可配置 |
|
||
|
||
---
|
||
|
||
## 消息格式
|
||
|
||
### 消息头(Message Header)
|
||
|
||
所有 MongoDB Wire Protocol 消息都以 16 字节的头部开始:
|
||
|
||
```
|
||
0 4 8 12 13 14 15
|
||
+-------------------+-------------------+-----------+--+--+--+--+
|
||
| Length | RequestID |ResponseTo |Op|Op|Op|Op|
|
||
+-------------------+-------------------+-----------+--+--+--+--+
|
||
```
|
||
|
||
| 字段 | 大小 | 类型 | 说明 |
|
||
|------|------|------|------|
|
||
| `Length` | 4 bytes | int32 | 消息总长度(包括头部) |
|
||
| `RequestID` | 4 bytes | int32 | 请求 ID,由客户端生成 |
|
||
| `ResponseTo` | 4 bytes | int32 | 响应到的请求 ID(服务器响应时使用) |
|
||
| `OpCode` | 4 bytes | int32 | 操作码 |
|
||
|
||
### 字节序
|
||
|
||
所有多字节字段都使用**小端字节序**(Little-Endian)。
|
||
|
||
---
|
||
|
||
## 操作码
|
||
|
||
### 支持的操作码
|
||
|
||
| 操作码 | 名称 | 说明 |
|
||
|--------|------|------|
|
||
| 1 | OP_REPLY | 服务器响应 |
|
||
| 4 | OP_UPDATE | 更新文档 |
|
||
| 8 | OP_INSERT | 插入文档 |
|
||
| 2004 | OP_QUERY | 查询文档 |
|
||
| 2006 | OP_GETMORE | 获取更多结果 |
|
||
| 2007 | OP_DELETE | 删除文档 |
|
||
| 2013 | OP_MSG | 扩展协议消息(主要使用) |
|
||
|
||
### OP_MSG(推荐)
|
||
|
||
OP_MSG 是 MongoDB 3.6+ 引入的扩展协议,支持所有数据库操作。
|
||
|
||
---
|
||
|
||
## OP_MSG 协议
|
||
|
||
### 消息格式
|
||
|
||
```
|
||
0 4 16
|
||
+---------------+------------------------------+
|
||
| OpCode | Flags |
|
||
+---------------+------------------------------+
|
||
| Checksum | Sections... |
|
||
+--------------+-------------------------------+
|
||
```
|
||
|
||
### 标志位(Flags)
|
||
|
||
| 标志 | 值 | 说明 |
|
||
|------|-----|------|
|
||
| `checksumPresent` | 0x00000001 | 存在校验和 |
|
||
| `moreToCome` | 0x00000002 | 更多消息到来 |
|
||
|
||
### Section 格式
|
||
|
||
每个 Section 包含:
|
||
|
||
```
|
||
+-------------+------------------+
|
||
| Kind (1B) | Data... |
|
||
+-------------+------------------+
|
||
```
|
||
|
||
#### Body Section (Kind = 0)
|
||
|
||
包含主要的命令文档。
|
||
|
||
#### Document Sequence Section (Kind = 1)
|
||
|
||
包含文档序列。
|
||
|
||
### 常用命令
|
||
|
||
#### insert 命令
|
||
|
||
```json
|
||
{
|
||
"insert": "collection_name",
|
||
"documents": [
|
||
{"_id": 1, "name": "Alice"},
|
||
{"_id": 2, "name": "Bob"}
|
||
],
|
||
"ordered": true
|
||
}
|
||
```
|
||
|
||
#### find 命令
|
||
|
||
```json
|
||
{
|
||
"find": "collection_name",
|
||
"filter": {"age": {"$gt": 25}},
|
||
"projection": {"name": 1},
|
||
"limit": 10
|
||
}
|
||
```
|
||
|
||
#### update 命令
|
||
|
||
```json
|
||
{
|
||
"update": "collection_name",
|
||
"updates": [
|
||
{
|
||
"q": {"name": "Alice"},
|
||
"u": {"$set": {"age": 31}}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
#### delete 命令
|
||
|
||
```json
|
||
{
|
||
"delete": "collection_name",
|
||
"deletes": [
|
||
{
|
||
"q": {"name": "Bob"},
|
||
"limit": 1
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
#### aggregate 命令
|
||
|
||
```json
|
||
{
|
||
"aggregate": "collection_name",
|
||
"pipeline": [
|
||
{"$match": {"status": "active"}},
|
||
{"$group": {"_id": "$category", "count": {"$sum": 1}}}
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 使用示例
|
||
|
||
### 使用 MongoDB Shell
|
||
|
||
```bash
|
||
# 连接到 Gomog
|
||
mongosh --host localhost --port 27017
|
||
|
||
# 切换到数据库
|
||
use testdb
|
||
|
||
# 插入文档
|
||
db.users.insertOne({name: "Alice", age: 30})
|
||
|
||
# 批量插入
|
||
db.users.insertMany([
|
||
{name: "Bob", age: 25},
|
||
{name: "Charlie", age: 35}
|
||
])
|
||
|
||
# 查询
|
||
db.users.find({age: {$gt: 28}})
|
||
|
||
# 聚合
|
||
db.orders.aggregate([
|
||
{$match: {status: "completed"}},
|
||
{$group: {_id: "$customerId", total: {$sum: "$amount"}}}
|
||
])
|
||
|
||
# 更新
|
||
db.users.updateOne(
|
||
{name: "Alice"},
|
||
{$set: {age: 31}}
|
||
)
|
||
|
||
# 删除
|
||
db.users.deleteOne({name: "Bob"})
|
||
```
|
||
|
||
### 使用 Node.js MongoDB Driver
|
||
|
||
```javascript
|
||
const { MongoClient } = require('mongodb');
|
||
|
||
async function main() {
|
||
// 连接到 Gomog
|
||
const client = new MongoClient('mongodb://localhost:27017');
|
||
|
||
try {
|
||
await client.connect();
|
||
console.log('Connected to Gomog!');
|
||
|
||
const db = client.db('testdb');
|
||
const users = db.collection('users');
|
||
|
||
// 插入
|
||
await users.insertOne({name: 'Alice', age: 30});
|
||
|
||
// 查询
|
||
const result = await users.findOne({name: 'Alice'});
|
||
console.log(result);
|
||
|
||
// 更新
|
||
await users.updateOne(
|
||
{name: 'Alice'},
|
||
{$set: {age: 31}}
|
||
);
|
||
|
||
// 聚合
|
||
const aggResult = await users.aggregate([
|
||
{$group: {_id: null, avgAge: {$avg: '$age'}}}
|
||
]).toArray();
|
||
console.log(aggResult);
|
||
|
||
} finally {
|
||
await client.close();
|
||
}
|
||
}
|
||
|
||
main().catch(console.error);
|
||
```
|
||
|
||
### 使用 Python PyMongo
|
||
|
||
```python
|
||
from pymongo import MongoClient
|
||
|
||
# 连接到 Gomog
|
||
client = MongoClient('mongodb://localhost:27017/')
|
||
db = client.testdb
|
||
users = db.users
|
||
|
||
# 插入
|
||
user = {"name": "Alice", "age": 30}
|
||
result = users.insert_one(user)
|
||
print(f"Inserted ID: {result.inserted_id}")
|
||
|
||
# 批量插入
|
||
users.insert_many([
|
||
{"name": "Bob", "age": 25},
|
||
{"name": "Charlie", "age": 35}
|
||
])
|
||
|
||
# 查询
|
||
for user in users.find({"age": {"$gt": 28}}):
|
||
print(user)
|
||
|
||
# 更新
|
||
result = users.update_one(
|
||
{"name": "Alice"},
|
||
{"$set": {"age": 31}}
|
||
)
|
||
print(f"Modified: {result.modified_count}")
|
||
|
||
# 聚合
|
||
pipeline = [
|
||
{"$group": {"_id": None, "avgAge": {"$avg": "$age"}}}
|
||
]
|
||
result = list(users.aggregate(pipeline))
|
||
print(result)
|
||
```
|
||
|
||
### 使用 Go MongoDB Driver
|
||
|
||
```go
|
||
package main
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"log"
|
||
"time"
|
||
|
||
"go.mongodb.org/mongo-driver/bson"
|
||
"go.mongodb.org/mongo-driver/mongo"
|
||
"go.mongodb.org/mongo-driver/mongo/options"
|
||
)
|
||
|
||
func main() {
|
||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||
defer cancel()
|
||
|
||
// 连接到 Gomog
|
||
client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017"))
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
defer client.Disconnect(ctx)
|
||
|
||
db := client.Database("testdb")
|
||
users := db.Collection("users")
|
||
|
||
// 插入
|
||
_, err = users.InsertOne(ctx, bson.M{"name": "Alice", "age": 30})
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
|
||
// 查询
|
||
var result bson.M
|
||
err = users.FindOne(ctx, bson.M{"name": "Alice"}).Decode(&result)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
fmt.Printf("Found: %+v\n", result)
|
||
|
||
// 更新
|
||
_, err = users.UpdateOne(
|
||
ctx,
|
||
bson.M{"name": "Alice"},
|
||
bson.M{"$set": bson.M{"age": 31}},
|
||
)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
|
||
// 聚合
|
||
pipeline := mongo.Pipeline{
|
||
{{"$group", bson.D{{"_id", nil}, {"avgAge", bson.D{{"$avg", "$age"}}}}}},
|
||
}
|
||
cursor, err := users.Aggregate(ctx, pipeline)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
defer cursor.Close(ctx)
|
||
|
||
var results []bson.M
|
||
if err = cursor.All(ctx, &results); err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
fmt.Printf("Aggregation: %+v\n", results)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 客户端开发
|
||
|
||
### 实现 OP_MSG 解析
|
||
|
||
```go
|
||
package tcp
|
||
|
||
import (
|
||
"encoding/binary"
|
||
"io"
|
||
)
|
||
|
||
// ReadMessageHeader 读取消息头
|
||
func ReadMessageHeader(r io.Reader) (*MessageHeader, error) {
|
||
header := &MessageHeader{}
|
||
|
||
// 读取 Length
|
||
if err := binary.Read(r, binary.LittleEndian, &header.Length); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 读取 RequestID
|
||
if err := binary.Read(r, binary.LittleEndian, &header.RequestID); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 读取 ResponseTo
|
||
if err := binary.Read(r, binary.LittleEndian, &header.ResponseTo); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 读取 OpCode
|
||
if err := binary.Read(r, binary.LittleEndian, &header.OpCode); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return header, nil
|
||
}
|
||
|
||
// WriteMessage 写入消息
|
||
func WriteMessage(w io.Writer, requestID uint32, responseTo uint32, opCode uint32, payload []byte) error {
|
||
length := uint32(16 + len(payload))
|
||
|
||
// 写入 Length
|
||
if err := binary.Write(w, binary.LittleEndian, length); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 写入 RequestID
|
||
if err := binary.Write(w, binary.LittleEndian, requestID); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 写入 ResponseTo
|
||
if err := binary.Write(w, binary.LittleEndian, responseTo); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 写入 OpCode
|
||
if err := binary.Write(w, binary.LittleEndian, opCode); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 写入 Payload
|
||
if _, err := w.Write(payload); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
```
|
||
|
||
### 处理 OP_MSG
|
||
|
||
```go
|
||
func handleOPMsg(header *MessageHeader, body []byte) ([]byte, error) {
|
||
// 解析 Section
|
||
kind := body[0]
|
||
documentData := body[1:]
|
||
|
||
// 解析 BSON 文档
|
||
var doc bson.Raw
|
||
if err := bson.Unmarshal(documentData, &doc); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 获取命令
|
||
command := doc.Lookup("insert").StringValue()
|
||
if command == "" {
|
||
command = doc.Lookup("find").StringValue()
|
||
}
|
||
|
||
// 执行相应操作
|
||
switch {
|
||
case doc.Lookup("insert").Valid():
|
||
return handleInsert(doc)
|
||
case doc.Lookup("find").Valid():
|
||
return handleFind(doc)
|
||
case doc.Lookup("update").Valid():
|
||
return handleUpdate(doc)
|
||
case doc.Lookup("delete").Valid():
|
||
return handleDelete(doc)
|
||
case doc.Lookup("aggregate").Valid():
|
||
return handleAggregate(doc)
|
||
default:
|
||
return nil, errors.New("unknown command")
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 错误处理
|
||
|
||
### 错误响应格式
|
||
|
||
```json
|
||
{
|
||
"ok": 0,
|
||
"errmsg": "错误描述",
|
||
"code": 错误码,
|
||
"codeName": "错误名称"
|
||
}
|
||
```
|
||
|
||
### 常见错误
|
||
|
||
#### 连接错误
|
||
|
||
```
|
||
E11000 duplicate key error
|
||
NoSuchKey: key not found
|
||
NamespaceNotFound: collection does not exist
|
||
```
|
||
|
||
#### 语法错误
|
||
|
||
```
|
||
BadValue: invalid parameter type
|
||
FailedToParse: unable to parse query
|
||
Location40352: unsupported operator
|
||
```
|
||
|
||
### 错误处理示例
|
||
|
||
```javascript
|
||
try {
|
||
await db.users.insertOne({_id: 1, name: "Alice"});
|
||
await db.users.insertOne({_id: 1, name: "Bob"}); // 重复键错误
|
||
} catch (error) {
|
||
console.error(error.code); // 11000
|
||
console.error(error.message); // duplicate key error
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 性能优化
|
||
|
||
### 1. 使用批量操作
|
||
|
||
```javascript
|
||
// ❌ 不推荐:逐条插入
|
||
for (let i = 0; i < 1000; i++) {
|
||
await db.users.insertOne({id: i});
|
||
}
|
||
|
||
// ✅ 推荐:批量插入
|
||
const docs = Array.from({length: 1000}, (_, i) => ({id: i}));
|
||
await db.users.insertMany(docs);
|
||
```
|
||
|
||
### 2. 使用投影
|
||
|
||
```javascript
|
||
// ❌ 不推荐:返回所有字段
|
||
db.users.find({status: "active"});
|
||
|
||
// ✅ 推荐:只返回需要的字段
|
||
db.users.find(
|
||
{status: "active"},
|
||
{projection: {name: 1, email: 1}}
|
||
);
|
||
```
|
||
|
||
### 3. 使用索引
|
||
|
||
```javascript
|
||
// 为常用查询创建索引
|
||
db.users.createIndex({email: 1});
|
||
db.users.createIndex({status: 1, createdAt: -1});
|
||
```
|
||
|
||
### 4. 限制结果集
|
||
|
||
```javascript
|
||
// 总是使用 limit
|
||
db.users.find({}).limit(100);
|
||
```
|
||
|
||
---
|
||
|
||
## 监控与调试
|
||
|
||
### 查看当前连接
|
||
|
||
```javascript
|
||
// 在 Gomog 服务器日志中查看
|
||
tail -f /var/log/gomog/gomog.log | grep "connection"
|
||
```
|
||
|
||
### 慢查询分析
|
||
|
||
```yaml
|
||
# 配置慢查询阈值
|
||
log:
|
||
level: "debug"
|
||
slow_query_threshold: "100ms"
|
||
```
|
||
|
||
### 启用详细日志
|
||
|
||
```yaml
|
||
server:
|
||
mode: "dev" # 开发模式会输出详细日志
|
||
```
|
||
|
||
---
|
||
|
||
## 最佳实践
|
||
|
||
### 1. 连接池管理
|
||
|
||
```javascript
|
||
const client = new MongoClient(uri, {
|
||
maxPoolSize: 10,
|
||
minPoolSize: 5,
|
||
maxIdleTimeMS: 30000
|
||
});
|
||
```
|
||
|
||
### 2. 重试逻辑
|
||
|
||
```javascript
|
||
const session = client.startSession();
|
||
await session.withTransaction(async () => {
|
||
// 事务操作
|
||
}, {
|
||
maxCommitTimeMS: 10000,
|
||
readConcern: {level: 'local'},
|
||
writeConcern: {w: 'majority'}
|
||
});
|
||
```
|
||
|
||
### 3. 超时设置
|
||
|
||
```javascript
|
||
const cursor = db.collection.find({}, {
|
||
timeout: true
|
||
}).maxTimeMS(5000);
|
||
```
|
||
|
||
---
|
||
|
||
## 附录
|
||
|
||
### A. 数据类型映射
|
||
|
||
| BSON 类型 | JavaScript | Go | Python |
|
||
|-----------|------------|----|---------|
|
||
| String | String | string | str |
|
||
| Int32 | Number | int32 | int |
|
||
| Int64 | BigInt | int64 | int |
|
||
| Double | Number | float64 | float |
|
||
| Boolean | Boolean | bool | bool |
|
||
| ObjectId | ObjectId | primitive.ObjectID | ObjectId |
|
||
| Date | Date | time.Time | datetime |
|
||
| Array | Array | []interface{} | list |
|
||
| Object | Object | map[string]interface{} | dict |
|
||
| Null | null | nil | None |
|
||
|
||
### B. 保留字段
|
||
|
||
以下字段名在 Gomog 中有特殊含义:
|
||
|
||
- `_id`: 文档唯一标识符
|
||
- `$*`: 操作符前缀
|
||
- `$$`: 系统变量前缀
|
||
|
||
### C. 相关链接
|
||
|
||
- [MongoDB Wire Protocol 官方文档](https://www.mongodb.com/docs/manual/reference/mongodb-wire-protocol/)
|
||
- [OP_MSG 规范](https://github.com/mongodb/specifications/blob/master/source/message/OP_MSG.rst)
|
||
- [BSON 规范](http://bsonspec.org/)
|
||
|
||
---
|
||
|
||
**维护者**: Gomog Team
|
||
**许可证**: MIT
|