fix:更新客户端功能
This commit is contained in:
29
Makefile
29
Makefile
@@ -3,6 +3,7 @@ CC ?= gcc
|
|||||||
# ARM 交叉编译工具链前缀(可按本地环境替换)。
|
# ARM 交叉编译工具链前缀(可按本地环境替换)。
|
||||||
CROSS_COMPILE ?= arm-linux-gnueabihf-
|
CROSS_COMPILE ?= arm-linux-gnueabihf-
|
||||||
ARM_CC ?= $(CROSS_COMPILE)gcc
|
ARM_CC ?= $(CROSS_COMPILE)gcc
|
||||||
|
ARM64_CC ?= aarch64-linux-gnu-gcc
|
||||||
|
|
||||||
# 产物目录(native: build/,arm: build/arm/)。
|
# 产物目录(native: build/,arm: build/arm/)。
|
||||||
BUILD_DIR ?= build
|
BUILD_DIR ?= build
|
||||||
@@ -31,6 +32,9 @@ APP_TEST_SRC := src/apps/test_main.c
|
|||||||
APP_CLIENT_SRC := src/apps/client_main.c
|
APP_CLIENT_SRC := src/apps/client_main.c
|
||||||
APP_SERVER_SRC := src/apps/server_main.c
|
APP_SERVER_SRC := src/apps/server_main.c
|
||||||
APP_RELAY_SRC := src/apps/relay_main.c
|
APP_RELAY_SRC := src/apps/relay_main.c
|
||||||
|
APP_HUB_SRC := src/apps/hub_main.c
|
||||||
|
APP_PEER_SRC := src/apps/peer_main.c
|
||||||
|
APP_BRIDGE_SRC := src/apps/bridge_main.c
|
||||||
|
|
||||||
# 将源文件映射到 BUILD_DIR 下的对象文件路径。
|
# 将源文件映射到 BUILD_DIR 下的对象文件路径。
|
||||||
COMMON_OBJS := $(patsubst %.c,$(BUILD_DIR)/%.o,$(COMMON_SRCS))
|
COMMON_OBJS := $(patsubst %.c,$(BUILD_DIR)/%.o,$(COMMON_SRCS))
|
||||||
@@ -38,6 +42,9 @@ TEST_OBJ := $(patsubst %.c,$(BUILD_DIR)/%.o,$(APP_TEST_SRC))
|
|||||||
CLIENT_OBJ := $(patsubst %.c,$(BUILD_DIR)/%.o,$(APP_CLIENT_SRC))
|
CLIENT_OBJ := $(patsubst %.c,$(BUILD_DIR)/%.o,$(APP_CLIENT_SRC))
|
||||||
SERVER_OBJ := $(patsubst %.c,$(BUILD_DIR)/%.o,$(APP_SERVER_SRC))
|
SERVER_OBJ := $(patsubst %.c,$(BUILD_DIR)/%.o,$(APP_SERVER_SRC))
|
||||||
RELAY_OBJ := $(patsubst %.c,$(BUILD_DIR)/%.o,$(APP_RELAY_SRC))
|
RELAY_OBJ := $(patsubst %.c,$(BUILD_DIR)/%.o,$(APP_RELAY_SRC))
|
||||||
|
HUB_OBJ := $(patsubst %.c,$(BUILD_DIR)/%.o,$(APP_HUB_SRC))
|
||||||
|
PEER_OBJ := $(patsubst %.c,$(BUILD_DIR)/%.o,$(APP_PEER_SRC))
|
||||||
|
BRIDGE_OBJ := $(patsubst %.c,$(BUILD_DIR)/%.o,$(APP_BRIDGE_SRC))
|
||||||
|
|
||||||
# 按目标清理时,仅删除对应可执行文件与专属入口对象,避免影响其它产物。
|
# 按目标清理时,仅删除对应可执行文件与专属入口对象,避免影响其它产物。
|
||||||
CLIENT_CLEAN_FILES := $(BUILD_DIR)/omni_client $(CLIENT_OBJ)
|
CLIENT_CLEAN_FILES := $(BUILD_DIR)/omni_client $(CLIENT_OBJ)
|
||||||
@@ -50,9 +57,12 @@ TARGETS := \
|
|||||||
$(BUILD_DIR)/omni_test \
|
$(BUILD_DIR)/omni_test \
|
||||||
$(BUILD_DIR)/omni_client \
|
$(BUILD_DIR)/omni_client \
|
||||||
$(BUILD_DIR)/omni_server \
|
$(BUILD_DIR)/omni_server \
|
||||||
$(BUILD_DIR)/omni_relay
|
$(BUILD_DIR)/omni_relay \
|
||||||
|
$(BUILD_DIR)/omni_hub \
|
||||||
|
$(BUILD_DIR)/omni_peer \
|
||||||
|
$(BUILD_DIR)/omni_bridge
|
||||||
|
|
||||||
.PHONY: all arm clean clean-client clean-arm64-client help
|
.PHONY: all arm arm64 clean clean-client clean-arm64-client help
|
||||||
|
|
||||||
# 本机构建入口。
|
# 本机构建入口。
|
||||||
all: $(TARGETS)
|
all: $(TARGETS)
|
||||||
@@ -70,6 +80,15 @@ $(BUILD_DIR)/omni_server: $(COMMON_OBJS) $(SERVER_OBJ)
|
|||||||
$(BUILD_DIR)/omni_relay: $(COMMON_OBJS) $(RELAY_OBJ)
|
$(BUILD_DIR)/omni_relay: $(COMMON_OBJS) $(RELAY_OBJ)
|
||||||
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
|
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
|
||||||
|
|
||||||
|
$(BUILD_DIR)/omni_hub: $(BUILD_DIR)/src/core/logger.o $(HUB_OBJ)
|
||||||
|
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
|
||||||
|
|
||||||
|
$(BUILD_DIR)/omni_peer: $(BUILD_DIR)/src/core/logger.o $(PEER_OBJ)
|
||||||
|
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
|
||||||
|
|
||||||
|
$(BUILD_DIR)/omni_bridge: $(BUILD_DIR)/src/core/logger.o $(BRIDGE_OBJ)
|
||||||
|
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
|
||||||
|
|
||||||
# 通用编译规则:
|
# 通用编译规则:
|
||||||
# - 自动创建对象文件所在目录
|
# - 自动创建对象文件所在目录
|
||||||
# - 编译单个 .c 为 .o
|
# - 编译单个 .c 为 .o
|
||||||
@@ -81,6 +100,10 @@ $(BUILD_DIR)/%.o: %.c
|
|||||||
arm:
|
arm:
|
||||||
$(MAKE) BUILD_DIR=build/arm CC=$(ARM_CC) all
|
$(MAKE) BUILD_DIR=build/arm CC=$(ARM_CC) all
|
||||||
|
|
||||||
|
# ARM64 构建入口:适合大多数 Jetson 设备。
|
||||||
|
arm64:
|
||||||
|
$(MAKE) BUILD_DIR=build/arm64 CC=$(ARM64_CC) all
|
||||||
|
|
||||||
# 仅清理当前 BUILD_DIR 下的 omni_client 与其入口对象。
|
# 仅清理当前 BUILD_DIR 下的 omni_client 与其入口对象。
|
||||||
clean-client:
|
clean-client:
|
||||||
rm -f $(CLIENT_CLEAN_FILES)
|
rm -f $(CLIENT_CLEAN_FILES)
|
||||||
@@ -97,6 +120,8 @@ clean:
|
|||||||
help:
|
help:
|
||||||
@echo "make -> build native binaries in build/"
|
@echo "make -> build native binaries in build/"
|
||||||
@echo "make arm -> build ARM binaries in build/arm (arm-linux-gnueabihf-gcc)"
|
@echo "make arm -> build ARM binaries in build/arm (arm-linux-gnueabihf-gcc)"
|
||||||
|
@echo "make arm64 -> build ARM64 binaries in build/arm64 (aarch64-linux-gnu-gcc)"
|
||||||
|
@echo "generated binaries: omni_test omni_client omni_server omni_relay omni_hub omni_peer omni_bridge"
|
||||||
@echo "make clean-client -> remove omni_client and client_main.o in BUILD_DIR"
|
@echo "make clean-client -> remove omni_client and client_main.o in BUILD_DIR"
|
||||||
@echo "make clean-arm64-client -> remove omni_client and client_main.o in build/arm64"
|
@echo "make clean-arm64-client -> remove omni_client and client_main.o in build/arm64"
|
||||||
@echo "make clean -> remove build artifacts"
|
@echo "make clean -> remove build artifacts"
|
||||||
|
|||||||
82
README.md
82
README.md
@@ -5,6 +5,9 @@
|
|||||||
- 客户端:文件分片发送 + 异步接收服务端 ASCII 指令
|
- 客户端:文件分片发送 + 异步接收服务端 ASCII 指令
|
||||||
- 服务端:接收并写文件 + 交互输入指令下发客户端
|
- 服务端:接收并写文件 + 交互输入指令下发客户端
|
||||||
- 转发器:A->B 中转,支持运行时动态修改目标端口
|
- 转发器:A->B 中转,支持运行时动态修改目标端口
|
||||||
|
- Hub:云端多客户端注册/绑定/路由,支持 A ↔ C ↔ B 命令透传
|
||||||
|
- Peer:主动连接 Hub 的对等端,支持 `register / bind / send / say`
|
||||||
|
- 启动前时钟同步:客户端先测 `RTT + offset`,服务端据此输出补偿后的端到端时延
|
||||||
|
|
||||||
## 目录结构
|
## 目录结构
|
||||||
|
|
||||||
@@ -26,6 +29,8 @@ OmniSocket/
|
|||||||
│ │ └── logger.c # 性能统计日志
|
│ │ └── logger.c # 性能统计日志
|
||||||
│ └── apps/
|
│ └── apps/
|
||||||
│ ├── client_main.c # 客户端入口
|
│ ├── client_main.c # 客户端入口
|
||||||
|
│ ├── hub_main.c # Hub 入口(多客户端注册/路由)
|
||||||
|
│ ├── peer_main.c # Peer 入口(连接 Hub 的对等端)
|
||||||
│ ├── server_main.c # 服务端入口
|
│ ├── server_main.c # 服务端入口
|
||||||
│ ├── relay_main.c # 转发器入口
|
│ ├── relay_main.c # 转发器入口
|
||||||
│ └── test_main.c # 简易协议连通性测试
|
│ └── test_main.c # 简易协议连通性测试
|
||||||
@@ -46,6 +51,8 @@ make
|
|||||||
|
|
||||||
生成:
|
生成:
|
||||||
- `build/omni_client`
|
- `build/omni_client`
|
||||||
|
- `build/omni_hub`
|
||||||
|
- `build/omni_peer`
|
||||||
- `build/omni_server`
|
- `build/omni_server`
|
||||||
- `build/omni_relay`
|
- `build/omni_relay`
|
||||||
- `build/omni_test`
|
- `build/omni_test`
|
||||||
@@ -84,6 +91,8 @@ build/omni_client -p tcp|udp|kcp -H <server_ip> -P <server_port> -f <file> [-b <
|
|||||||
- 发送结束后额外发送 `FILE_END` 控制包
|
- 发送结束后额外发送 `FILE_END` 控制包
|
||||||
- 后台线程持续接收并打印服务端 ASCII 指令
|
- 后台线程持续接收并打印服务端 ASCII 指令
|
||||||
- `-w -1` 表示常驻模式,直到手动 `Ctrl+C`
|
- `-w -1` 表示常驻模式,直到手动 `Ctrl+C`
|
||||||
|
- 建连后会先自动发送 `TIME_SYNC_REQ/RESP/REPORT`,以最小 RTT 样本估算 `server_time - client_time`
|
||||||
|
- 若同步响应不可达(例如经过当前实现的单向 relay),文件传输仍继续,但服务端不会产出补偿后的 `end_to_end_delay_ms`
|
||||||
|
|
||||||
### `omni_relay`
|
### `omni_relay`
|
||||||
|
|
||||||
@@ -96,6 +105,36 @@ build/omni_relay -p tcp|udp|kcp -L <listen_port> -H <target_ip> -P <target_port>
|
|||||||
- `show`:显示当前目标
|
- `show`:显示当前目标
|
||||||
- `quit`:退出 relay
|
- `quit`:退出 relay
|
||||||
|
|
||||||
|
### `omni_hub`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
build/omni_hub -P <listen_port> [-b <bind_ip>] [-p tcp]
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
- 当前阶段只实现 TCP 控制面
|
||||||
|
- 多个 `omni_peer` 主动连接到 hub 后,先用 `client_id` 注册
|
||||||
|
- hub 维护 `client_id -> socket` 映射,并按 `dst_id` 转发 `PEER_TUNNEL`
|
||||||
|
|
||||||
|
### `omni_peer`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
build/omni_peer -H <hub_ip> -P <hub_port> -i <client_id> [-b <peer_id>] [-d <peer_id>] [-m <text>] [-w <wait_seconds|-1>]
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
- `-i`:当前 peer 的逻辑 ID,后续所有路由都依赖它,不依赖私网 IP
|
||||||
|
- `-b`:启动后先请求绑定默认目标
|
||||||
|
- `-m`:启动后立即发一条命令;若同时给了 `-d`,则直接发给该目标
|
||||||
|
- `-w`:one-shot 模式下等待若干秒再退出,`-1` 表示常驻
|
||||||
|
|
||||||
|
交互命令:
|
||||||
|
- `bind <peer_id>`:绑定默认目标
|
||||||
|
- `send <text>`:发给当前绑定目标
|
||||||
|
- `say <peer_id> <text>`:显式指定目标
|
||||||
|
- `show`:显示本地 `client_id / bound_peer`
|
||||||
|
- `quit`:退出
|
||||||
|
|
||||||
## 快速启动(本机)
|
## 快速启动(本机)
|
||||||
|
|
||||||
先准备一个测试文件:
|
先准备一个测试文件:
|
||||||
@@ -124,6 +163,10 @@ build/omni_client -p tcp -H 127.0.0.1 -P 9000 -f /tmp/input.bin
|
|||||||
cmp -s /tmp/input.bin /tmp/out_tcp.bin && echo OK || echo FAIL
|
cmp -s /tmp/input.bin /tmp/out_tcp.bin && echo OK || echo FAIL
|
||||||
```
|
```
|
||||||
|
|
||||||
|
日志观察:
|
||||||
|
- client / server 的 `summary` 日志会新增 `clock_sync_ok`、`clock_offset_ms`、`clock_sync_rtt_ms`、`clock_sync_samples`
|
||||||
|
- server 侧的 `end_to_end_avg_ms` 在 `clock_sync_ok=1` 时表示已经按 offset 补偿后的端到端时延
|
||||||
|
|
||||||
### UDP 直连(2 个终端)
|
### UDP 直连(2 个终端)
|
||||||
|
|
||||||
终端 1:
|
终端 1:
|
||||||
@@ -190,3 +233,42 @@ relay 终端可输入:
|
|||||||
show
|
show
|
||||||
set 127.0.0.1 9103
|
set 127.0.0.1 9103
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Hub / Peer 场景(3 个终端)
|
||||||
|
|
||||||
|
终端 1(cloud hub C):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
build/omni_hub -P 9200
|
||||||
|
```
|
||||||
|
|
||||||
|
终端 2(peer B):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
build/omni_peer -H 127.0.0.1 -P 9200 -i beta
|
||||||
|
```
|
||||||
|
|
||||||
|
终端 3(peer A):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
build/omni_peer -H 127.0.0.1 -P 9200 -i alpha
|
||||||
|
```
|
||||||
|
|
||||||
|
在 A 终端输入:
|
||||||
|
|
||||||
|
```text
|
||||||
|
bind beta
|
||||||
|
send hello-from-alpha
|
||||||
|
```
|
||||||
|
|
||||||
|
此时 B 终端会打印:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[peer alpha -> beta] hello-from-alpha
|
||||||
|
```
|
||||||
|
|
||||||
|
本地一键 smoke:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/local_peer_smoke_test.sh
|
||||||
|
```
|
||||||
|
|||||||
489
include/common.h
489
include/common.h
@@ -1,17 +1,25 @@
|
|||||||
/*
|
/*
|
||||||
* common.h
|
* common.h
|
||||||
|
* 全局公共定义:消息头、错误码、通用宏
|
||||||
. * 全局公共定义:消息头、错误码、通用宏
|
*
|
||||||
|
* 这个头文件承担两类职责:
|
||||||
|
* 1) 定义跨模块共享的线协议结构,保证 client / server / relay 的编码口径一致。
|
||||||
|
* 2) 提供与协议无关的基础工具,例如时间戳和 64 位字节序转换。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef OMNISOCKET_COMMON_H
|
#ifndef OMNISOCKET_COMMON_H
|
||||||
#define OMNISOCKET_COMMON_H
|
#define OMNISOCKET_COMMON_H
|
||||||
|
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
/* 统一的 16 字节消息头(应用层消息头) */
|
/*
|
||||||
|
* 统一的 16 字节应用层消息头。
|
||||||
|
* 注意:这不是 TCP/KCP/UDP 的传输层头,而是项目自己定义的“业务帧头”。
|
||||||
|
*/
|
||||||
typedef struct MsgHeader {
|
typedef struct MsgHeader {
|
||||||
uint32_t type; /* 消息类型:文件块/控制指令等 */
|
uint32_t type; /* 消息类型:文件块/控制指令等 */
|
||||||
uint32_t len; /* 后续负载长度(字节数) */
|
uint32_t len; /* 后续负载长度(字节数) */
|
||||||
@@ -20,15 +28,183 @@ typedef struct MsgHeader {
|
|||||||
|
|
||||||
#define MSG_HEADER_SIZE (sizeof(MsgHeader)) /* 16 字节 */
|
#define MSG_HEADER_SIZE (sizeof(MsgHeader)) /* 16 字节 */
|
||||||
|
|
||||||
|
/* 应用层消息类型约定。 */
|
||||||
enum {
|
enum {
|
||||||
MSG_TYPE_FILE_CHUNK = 1,
|
MSG_TYPE_FILE_CHUNK = 1, //文件数据分片(TransferChunkMeta + 实际文件字节)
|
||||||
MSG_TYPE_FILE_END = 2,
|
MSG_TYPE_FILE_END = 2, //文件传输结束通知
|
||||||
MSG_TYPE_COMMAND = 3,
|
MSG_TYPE_COMMAND = 3, //控制指令(服务端发起+客户端响应)
|
||||||
MSG_TYPE_RAW = 100
|
MSG_TYPE_TRANSFER_ACK = 4, //传输确认
|
||||||
|
MSG_TYPE_TIME_SYNC_REQ = 5, //时钟同步探测请求
|
||||||
|
MSG_TYPE_TIME_SYNC_RESP = 6, //时钟同步探测响应
|
||||||
|
MSG_TYPE_TIME_SYNC_REPORT = 7, //客户端确认后的最终 offset
|
||||||
|
MSG_TYPE_PEER_REGISTER = 8, //客户端向 hub 注册自己的逻辑 ID
|
||||||
|
MSG_TYPE_PEER_BIND = 9, //客户端请求把默认目标绑定到某个 peer_id
|
||||||
|
MSG_TYPE_PEER_STATUS = 10, //hub 返回给客户端的状态/错误/通知
|
||||||
|
MSG_TYPE_PEER_TUNNEL = 11, //hub 按 src_id/dst_id 转发的通用隧道消息
|
||||||
|
MSG_TYPE_RAW = 100 //原始数据(不带业务头,直接透传 payload)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* 默认分片大小(包)上限 */
|
||||||
#define OMNI_DEFAULT_MTU 1400u
|
#define OMNI_DEFAULT_MTU 1400u
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 文件分片元数据:
|
||||||
|
* - 用于进度统计、端到端延迟估算、UDP 丢包区间分析
|
||||||
|
* - offset_bytes 让接收端可以按原始偏移写盘,避免 UDP 乱序时直接追加写坏文件
|
||||||
|
*/
|
||||||
|
typedef struct TransferChunkMeta {
|
||||||
|
uint32_t transfer_id; /* 一次文件传输的逻辑 ID,用于区分多次任务。 */
|
||||||
|
uint32_t seq; /* 当前分片序号,从 1 开始。 */
|
||||||
|
uint32_t total_chunks; /* 本次传输总分片数,便于接收端判断是否丢片。 */
|
||||||
|
uint32_t window_id; /* 发送时间窗口编号,用于按秒聚合吞吐/丢包分布。 */
|
||||||
|
uint64_t total_bytes; /* 原始文件总大小。 */
|
||||||
|
uint64_t offset_bytes; /* 当前分片在原文件中的字节偏移。 */
|
||||||
|
uint32_t chunk_bytes; /* 当前分片实际承载的数据字节数。 */
|
||||||
|
uint32_t reserved; /* 预留字段,当前未使用,发送时写 0。 */
|
||||||
|
uint64_t origin_ts_ms; /* 分片在发送端最初产生的时间戳,用于端到端延迟估算。 */
|
||||||
|
} TransferChunkMeta;
|
||||||
|
|
||||||
|
#define TRANSFER_CHUNK_META_SIZE (sizeof(TransferChunkMeta))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 文件发送结束消息。
|
||||||
|
* 这类消息不携带实际文件内容,而是告诉接收端“数据面已经发完”,
|
||||||
|
* 便于输出汇总统计并回传 ACK。
|
||||||
|
*/
|
||||||
|
typedef struct TransferEndMeta {
|
||||||
|
uint32_t transfer_id;
|
||||||
|
uint32_t total_chunks;
|
||||||
|
uint64_t total_bytes;
|
||||||
|
uint32_t total_windows;
|
||||||
|
uint32_t reserved;
|
||||||
|
} TransferEndMeta;
|
||||||
|
|
||||||
|
#define TRANSFER_END_META_SIZE (sizeof(TransferEndMeta))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 传输结束确认消息。
|
||||||
|
* 服务端在刷盘和统计完成后回传该结构,客户端据此得知:
|
||||||
|
* - 哪个 transfer_id 完成了
|
||||||
|
* - 服务端实际写入了多少字节
|
||||||
|
* - 这次 ACK 对应的是哪次 FILE_END
|
||||||
|
*/
|
||||||
|
typedef struct TransferAckMeta {
|
||||||
|
uint32_t transfer_id;
|
||||||
|
uint32_t total_chunks;
|
||||||
|
uint64_t total_bytes;
|
||||||
|
uint64_t bytes_written;
|
||||||
|
uint64_t echoed_end_ts_ms;
|
||||||
|
} TransferAckMeta;
|
||||||
|
|
||||||
|
#define TRANSFER_ACK_META_SIZE (sizeof(TransferAckMeta))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 时钟同步探测请求。
|
||||||
|
* 客户端发出探测时带上自己的发送时刻 t0,服务端收到后会立即回包。
|
||||||
|
*/
|
||||||
|
typedef struct TimeSyncProbeMeta {
|
||||||
|
uint32_t probe_id;
|
||||||
|
uint32_t reserved;
|
||||||
|
uint64_t client_send_ts_ms;
|
||||||
|
} TimeSyncProbeMeta;
|
||||||
|
|
||||||
|
#define TIME_SYNC_PROBE_META_SIZE (sizeof(TimeSyncProbeMeta))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 时钟同步响应。
|
||||||
|
* 服务端把客户端的 t0 原样回显,并补上:
|
||||||
|
* - t1: 服务端收到请求的本地时间
|
||||||
|
* - t2: 服务端发出响应的本地时间
|
||||||
|
*/
|
||||||
|
typedef struct TimeSyncReplyMeta {
|
||||||
|
uint32_t probe_id;
|
||||||
|
uint32_t reserved;
|
||||||
|
uint64_t client_send_ts_ms;
|
||||||
|
uint64_t server_recv_ts_ms;
|
||||||
|
uint64_t server_send_ts_ms;
|
||||||
|
} TimeSyncReplyMeta;
|
||||||
|
|
||||||
|
#define TIME_SYNC_REPLY_META_SIZE (sizeof(TimeSyncReplyMeta))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 客户端选定最优样本后,把最终 offset 上报给服务端。
|
||||||
|
* offset 定义为:server_time - client_time。
|
||||||
|
*/
|
||||||
|
typedef struct TimeSyncReportMeta {
|
||||||
|
int64_t server_minus_client_offset_ms;
|
||||||
|
uint64_t best_rtt_ms;
|
||||||
|
uint32_t sample_count;
|
||||||
|
uint32_t reserved;
|
||||||
|
} TimeSyncReportMeta;
|
||||||
|
|
||||||
|
#define TIME_SYNC_REPORT_META_SIZE (sizeof(TimeSyncReportMeta))
|
||||||
|
|
||||||
|
#define OMNI_PEER_ID_SIZE 32u
|
||||||
|
#define OMNI_PEER_STATUS_DETAIL_SIZE 96u
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PEER_STATUS_OK = 0,
|
||||||
|
PEER_STATUS_ERROR = 1,
|
||||||
|
PEER_STATUS_REGISTERED = 2,
|
||||||
|
PEER_STATUS_BOUND = 3,
|
||||||
|
PEER_STATUS_UNBOUND = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* peer 注册消息:
|
||||||
|
* - client_id 是该客户端在 hub 中的逻辑身份
|
||||||
|
* - 后续 bind / route 都依赖这个 ID,而不是私网 IP
|
||||||
|
*/
|
||||||
|
typedef struct PeerRegisterMeta {
|
||||||
|
char client_id[OMNI_PEER_ID_SIZE];
|
||||||
|
uint32_t reserved;
|
||||||
|
} PeerRegisterMeta;
|
||||||
|
|
||||||
|
#define PEER_REGISTER_META_SIZE (sizeof(PeerRegisterMeta))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* peer 绑定消息:
|
||||||
|
* - peer_id 是当前会话的默认目标
|
||||||
|
* - 绑定成功后,客户端可以直接 send 文本而无需每次重复写目标 ID
|
||||||
|
*/
|
||||||
|
typedef struct PeerBindMeta {
|
||||||
|
char peer_id[OMNI_PEER_ID_SIZE];
|
||||||
|
uint32_t reserved;
|
||||||
|
} PeerBindMeta;
|
||||||
|
|
||||||
|
#define PEER_BIND_META_SIZE (sizeof(PeerBindMeta))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* hub -> peer 的状态消息:
|
||||||
|
* - code 区分成功、错误、解绑通知
|
||||||
|
* - self_id / peer_id 便于客户端更新本地状态
|
||||||
|
* - detail 留给人读日志和交互终端输出
|
||||||
|
*/
|
||||||
|
typedef struct PeerStatusMeta {
|
||||||
|
uint32_t code;
|
||||||
|
uint32_t reserved;
|
||||||
|
char self_id[OMNI_PEER_ID_SIZE];
|
||||||
|
char peer_id[OMNI_PEER_ID_SIZE];
|
||||||
|
char detail[OMNI_PEER_STATUS_DETAIL_SIZE];
|
||||||
|
} PeerStatusMeta;
|
||||||
|
|
||||||
|
#define PEER_STATUS_META_SIZE (sizeof(PeerStatusMeta))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 通用隧道头:
|
||||||
|
* - src_id 由 hub 在转发时写入真实发送方
|
||||||
|
* - dst_id 标识目标 peer
|
||||||
|
* - inner_type 复用现有业务消息类型,后续文件/视频消息也可直接套进来
|
||||||
|
*/
|
||||||
|
typedef struct PeerTunnelMeta {
|
||||||
|
char src_id[OMNI_PEER_ID_SIZE];
|
||||||
|
char dst_id[OMNI_PEER_ID_SIZE];
|
||||||
|
uint32_t inner_type;
|
||||||
|
uint32_t reserved;
|
||||||
|
} PeerTunnelMeta;
|
||||||
|
|
||||||
|
#define PEER_TUNNEL_META_SIZE (sizeof(PeerTunnelMeta))
|
||||||
|
|
||||||
/* 通用错误码(负数返回表示出错) */
|
/* 通用错误码(负数返回表示出错) */
|
||||||
enum {
|
enum {
|
||||||
OMNI_OK = 0,
|
OMNI_OK = 0,
|
||||||
@@ -38,14 +214,18 @@ enum {
|
|||||||
OMNI_ERR_TIMEOUT = -4
|
OMNI_ERR_TIMEOUT = -4
|
||||||
};
|
};
|
||||||
|
|
||||||
/* 获取当前单调时间(毫秒),用于延迟统计 */
|
/* 获取当前单调时间(毫秒),避免系统时间回拨影响延迟统计。 */
|
||||||
static inline uint64_t omni_now_ms(void)
|
static inline uint64_t omni_now_ms(void)
|
||||||
{
|
{
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
return (uint64_t)ts.tv_sec * 1000u + (uint64_t)(ts.tv_nsec / 1000000u);
|
return (uint64_t)ts.tv_sec * 1000u + (uint64_t)(ts.tv_nsec / 1000000u);
|
||||||
}
|
}
|
||||||
// 64 位整数主机序与网络序转换工具
|
|
||||||
|
/*
|
||||||
|
* 手写 64 位字节翻转工具。
|
||||||
|
* 之所以单独实现,是因为标准库广泛提供 htonl/ntohl,但 64 位版本并非所有平台都统一。
|
||||||
|
*/
|
||||||
static inline uint64_t omni_bswap64(uint64_t x)
|
static inline uint64_t omni_bswap64(uint64_t x)
|
||||||
{
|
{
|
||||||
return ((x & 0x00000000000000FFull) << 56) |
|
return ((x & 0x00000000000000FFull) << 56) |
|
||||||
@@ -58,6 +238,7 @@ static inline uint64_t omni_bswap64(uint64_t x)
|
|||||||
((x & 0xFF00000000000000ull) >> 56);
|
((x & 0xFF00000000000000ull) >> 56);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 将主机字节序的 64 位整数编码成网络字节序。 */
|
||||||
static inline uint64_t omni_htonll(uint64_t x)
|
static inline uint64_t omni_htonll(uint64_t x)
|
||||||
{
|
{
|
||||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
@@ -67,11 +248,56 @@ static inline uint64_t omni_htonll(uint64_t x)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 64 位网络字节序转回主机字节序;实现上与 htonll 对称。 */
|
||||||
static inline uint64_t omni_ntohll(uint64_t x)
|
static inline uint64_t omni_ntohll(uint64_t x)
|
||||||
{
|
{
|
||||||
return omni_htonll(x);
|
return omni_htonll(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 保留 int64_t 的原始位模式,便于把 signed offset 按网络字节序放到线协议里。
|
||||||
|
* 这里用 memcpy,避免依赖实现相关的强制类型转换。
|
||||||
|
*/
|
||||||
|
static inline uint64_t omni_i64_bits(int64_t x)
|
||||||
|
{
|
||||||
|
uint64_t bits = 0;
|
||||||
|
memcpy(&bits, &x, sizeof(bits));
|
||||||
|
return bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int64_t omni_i64_from_bits(uint64_t bits)
|
||||||
|
{
|
||||||
|
int64_t x = 0;
|
||||||
|
memcpy(&x, &bits, sizeof(x));
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void omni_i64_net_encode(int64_t *out_value, int64_t host_value)
|
||||||
|
{
|
||||||
|
uint64_t bits = omni_htonll(omni_i64_bits(host_value));
|
||||||
|
memcpy(out_value, &bits, sizeof(bits));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int64_t omni_i64_net_decode(const int64_t *net_value)
|
||||||
|
{
|
||||||
|
uint64_t bits = 0;
|
||||||
|
memcpy(&bits, net_value, sizeof(bits));
|
||||||
|
return omni_i64_from_bits(omni_ntohll(bits));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void omni_copy_fixed_ascii(char *dst, size_t dst_sz, const char *src)
|
||||||
|
{
|
||||||
|
if (!dst || dst_sz == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memset(dst, 0, dst_sz);
|
||||||
|
if (!src) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
snprintf(dst, dst_sz, "%s", src);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 将业务侧的消息头字段编码成网络字节序,便于跨主机传输。 */
|
||||||
static inline void omni_msg_header_encode(MsgHeader *out_hdr,
|
static inline void omni_msg_header_encode(MsgHeader *out_hdr,
|
||||||
uint32_t type,
|
uint32_t type,
|
||||||
uint32_t len,
|
uint32_t len,
|
||||||
@@ -82,6 +308,7 @@ static inline void omni_msg_header_encode(MsgHeader *out_hdr,
|
|||||||
out_hdr->timestamp = omni_htonll(timestamp_ms);
|
out_hdr->timestamp = omni_htonll(timestamp_ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 将网络帧头解码回本机可直接使用的主机字节序结构。 */
|
||||||
static inline void omni_msg_header_decode(const MsgHeader *net_hdr,
|
static inline void omni_msg_header_decode(const MsgHeader *net_hdr,
|
||||||
MsgHeader *host_hdr)
|
MsgHeader *host_hdr)
|
||||||
{
|
{
|
||||||
@@ -90,5 +317,247 @@ static inline void omni_msg_header_decode(const MsgHeader *net_hdr,
|
|||||||
host_hdr->timestamp = omni_ntohll(net_hdr->timestamp);
|
host_hdr->timestamp = omni_ntohll(net_hdr->timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* OMNISOCKET_COMMON_H */
|
/* 编码单个文件分片的元数据,发送端在每个 chunk 前都要附上它。 */
|
||||||
|
static inline void omni_transfer_chunk_meta_encode(TransferChunkMeta *out_meta,
|
||||||
|
uint32_t transfer_id,
|
||||||
|
uint32_t seq,
|
||||||
|
uint32_t total_chunks,
|
||||||
|
uint32_t window_id,
|
||||||
|
uint64_t total_bytes,
|
||||||
|
uint64_t offset_bytes,
|
||||||
|
uint32_t chunk_bytes,
|
||||||
|
uint64_t origin_ts_ms)
|
||||||
|
{
|
||||||
|
out_meta->transfer_id = htonl(transfer_id);
|
||||||
|
out_meta->seq = htonl(seq);
|
||||||
|
out_meta->total_chunks = htonl(total_chunks);
|
||||||
|
out_meta->window_id = htonl(window_id);
|
||||||
|
out_meta->total_bytes = omni_htonll(total_bytes);
|
||||||
|
out_meta->offset_bytes = omni_htonll(offset_bytes);
|
||||||
|
out_meta->chunk_bytes = htonl(chunk_bytes);
|
||||||
|
out_meta->reserved = 0;
|
||||||
|
out_meta->origin_ts_ms = omni_htonll(origin_ts_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 将网络中的分片元数据解码回主机字节序,供接收端统计与写盘使用。 */
|
||||||
|
static inline void omni_transfer_chunk_meta_decode(const TransferChunkMeta *net_meta,
|
||||||
|
TransferChunkMeta *host_meta)
|
||||||
|
{
|
||||||
|
host_meta->transfer_id = ntohl(net_meta->transfer_id);
|
||||||
|
host_meta->seq = ntohl(net_meta->seq);
|
||||||
|
host_meta->total_chunks = ntohl(net_meta->total_chunks);
|
||||||
|
host_meta->window_id = ntohl(net_meta->window_id);
|
||||||
|
host_meta->total_bytes = omni_ntohll(net_meta->total_bytes);
|
||||||
|
host_meta->offset_bytes = omni_ntohll(net_meta->offset_bytes);
|
||||||
|
host_meta->chunk_bytes = ntohl(net_meta->chunk_bytes);
|
||||||
|
host_meta->reserved = ntohl(net_meta->reserved);
|
||||||
|
host_meta->origin_ts_ms = omni_ntohll(net_meta->origin_ts_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 编码“文件发送完成”消息的元数据。 */
|
||||||
|
static inline void omni_transfer_end_meta_encode(TransferEndMeta *out_meta,
|
||||||
|
uint32_t transfer_id,
|
||||||
|
uint32_t total_chunks,
|
||||||
|
uint64_t total_bytes,
|
||||||
|
uint32_t total_windows)
|
||||||
|
{
|
||||||
|
out_meta->transfer_id = htonl(transfer_id);
|
||||||
|
out_meta->total_chunks = htonl(total_chunks);
|
||||||
|
out_meta->total_bytes = omni_htonll(total_bytes);
|
||||||
|
out_meta->total_windows = htonl(total_windows);
|
||||||
|
out_meta->reserved = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 解码 FILE_END 元数据,接收端据此补全汇总统计。 */
|
||||||
|
static inline void omni_transfer_end_meta_decode(const TransferEndMeta *net_meta,
|
||||||
|
TransferEndMeta *host_meta)
|
||||||
|
{
|
||||||
|
host_meta->transfer_id = ntohl(net_meta->transfer_id);
|
||||||
|
host_meta->total_chunks = ntohl(net_meta->total_chunks);
|
||||||
|
host_meta->total_bytes = omni_ntohll(net_meta->total_bytes);
|
||||||
|
host_meta->total_windows = ntohl(net_meta->total_windows);
|
||||||
|
host_meta->reserved = ntohl(net_meta->reserved);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 编码服务端回给客户端的传输确认信息。 */
|
||||||
|
static inline void omni_transfer_ack_meta_encode(TransferAckMeta *out_meta,
|
||||||
|
uint32_t transfer_id,
|
||||||
|
uint32_t total_chunks,
|
||||||
|
uint64_t total_bytes,
|
||||||
|
uint64_t bytes_written,
|
||||||
|
uint64_t echoed_end_ts_ms)
|
||||||
|
{
|
||||||
|
out_meta->transfer_id = htonl(transfer_id);
|
||||||
|
out_meta->total_chunks = htonl(total_chunks);
|
||||||
|
out_meta->total_bytes = omni_htonll(total_bytes);
|
||||||
|
out_meta->bytes_written = omni_htonll(bytes_written);
|
||||||
|
out_meta->echoed_end_ts_ms = omni_htonll(echoed_end_ts_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 解码 ACK 元数据,客户端用它计算确认 RTT 和最终落盘结果。 */
|
||||||
|
static inline void omni_transfer_ack_meta_decode(const TransferAckMeta *net_meta,
|
||||||
|
TransferAckMeta *host_meta)
|
||||||
|
{
|
||||||
|
host_meta->transfer_id = ntohl(net_meta->transfer_id);
|
||||||
|
host_meta->total_chunks = ntohl(net_meta->total_chunks);
|
||||||
|
host_meta->total_bytes = omni_ntohll(net_meta->total_bytes);
|
||||||
|
host_meta->bytes_written = omni_ntohll(net_meta->bytes_written);
|
||||||
|
host_meta->echoed_end_ts_ms = omni_ntohll(net_meta->echoed_end_ts_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 编码时钟同步探测请求。 */
|
||||||
|
static inline void omni_time_sync_probe_meta_encode(TimeSyncProbeMeta *out_meta,
|
||||||
|
uint32_t probe_id,
|
||||||
|
uint64_t client_send_ts_ms)
|
||||||
|
{
|
||||||
|
out_meta->probe_id = htonl(probe_id);
|
||||||
|
out_meta->reserved = 0;
|
||||||
|
out_meta->client_send_ts_ms = omni_htonll(client_send_ts_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 解码时钟同步探测请求。 */
|
||||||
|
static inline void omni_time_sync_probe_meta_decode(const TimeSyncProbeMeta *net_meta,
|
||||||
|
TimeSyncProbeMeta *host_meta)
|
||||||
|
{
|
||||||
|
host_meta->probe_id = ntohl(net_meta->probe_id);
|
||||||
|
host_meta->reserved = ntohl(net_meta->reserved);
|
||||||
|
host_meta->client_send_ts_ms = omni_ntohll(net_meta->client_send_ts_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 编码时钟同步响应。 */
|
||||||
|
static inline void omni_time_sync_reply_meta_encode(TimeSyncReplyMeta *out_meta,
|
||||||
|
uint32_t probe_id,
|
||||||
|
uint64_t client_send_ts_ms,
|
||||||
|
uint64_t server_recv_ts_ms,
|
||||||
|
uint64_t server_send_ts_ms)
|
||||||
|
{
|
||||||
|
out_meta->probe_id = htonl(probe_id);
|
||||||
|
out_meta->reserved = 0;
|
||||||
|
out_meta->client_send_ts_ms = omni_htonll(client_send_ts_ms);
|
||||||
|
out_meta->server_recv_ts_ms = omni_htonll(server_recv_ts_ms);
|
||||||
|
out_meta->server_send_ts_ms = omni_htonll(server_send_ts_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 解码时钟同步响应。 */
|
||||||
|
static inline void omni_time_sync_reply_meta_decode(const TimeSyncReplyMeta *net_meta,
|
||||||
|
TimeSyncReplyMeta *host_meta)
|
||||||
|
{
|
||||||
|
host_meta->probe_id = ntohl(net_meta->probe_id);
|
||||||
|
host_meta->reserved = ntohl(net_meta->reserved);
|
||||||
|
host_meta->client_send_ts_ms = omni_ntohll(net_meta->client_send_ts_ms);
|
||||||
|
host_meta->server_recv_ts_ms = omni_ntohll(net_meta->server_recv_ts_ms);
|
||||||
|
host_meta->server_send_ts_ms = omni_ntohll(net_meta->server_send_ts_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 编码客户端最终确认的 offset。 */
|
||||||
|
static inline void omni_time_sync_report_meta_encode(TimeSyncReportMeta *out_meta,
|
||||||
|
int64_t server_minus_client_offset_ms,
|
||||||
|
uint64_t best_rtt_ms,
|
||||||
|
uint32_t sample_count)
|
||||||
|
{
|
||||||
|
omni_i64_net_encode(&out_meta->server_minus_client_offset_ms,
|
||||||
|
server_minus_client_offset_ms);
|
||||||
|
out_meta->best_rtt_ms = omni_htonll(best_rtt_ms);
|
||||||
|
out_meta->sample_count = htonl(sample_count);
|
||||||
|
out_meta->reserved = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 解码客户端最终确认的 offset。 */
|
||||||
|
static inline void omni_time_sync_report_meta_decode(const TimeSyncReportMeta *net_meta,
|
||||||
|
TimeSyncReportMeta *host_meta)
|
||||||
|
{
|
||||||
|
host_meta->server_minus_client_offset_ms =
|
||||||
|
omni_i64_net_decode(&net_meta->server_minus_client_offset_ms);
|
||||||
|
host_meta->best_rtt_ms = omni_ntohll(net_meta->best_rtt_ms);
|
||||||
|
host_meta->sample_count = ntohl(net_meta->sample_count);
|
||||||
|
host_meta->reserved = ntohl(net_meta->reserved);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 编码 peer 注册消息。 */
|
||||||
|
static inline void omni_peer_register_meta_encode(PeerRegisterMeta *out_meta,
|
||||||
|
const char *client_id)
|
||||||
|
{
|
||||||
|
memset(out_meta, 0, sizeof(*out_meta));
|
||||||
|
omni_copy_fixed_ascii(out_meta->client_id, sizeof(out_meta->client_id), client_id);
|
||||||
|
out_meta->reserved = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 解码 peer 注册消息。 */
|
||||||
|
static inline void omni_peer_register_meta_decode(const PeerRegisterMeta *net_meta,
|
||||||
|
PeerRegisterMeta *host_meta)
|
||||||
|
{
|
||||||
|
memset(host_meta, 0, sizeof(*host_meta));
|
||||||
|
omni_copy_fixed_ascii(host_meta->client_id, sizeof(host_meta->client_id), net_meta->client_id);
|
||||||
|
host_meta->reserved = ntohl(net_meta->reserved);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 编码 peer bind 消息。 */
|
||||||
|
static inline void omni_peer_bind_meta_encode(PeerBindMeta *out_meta,
|
||||||
|
const char *peer_id)
|
||||||
|
{
|
||||||
|
memset(out_meta, 0, sizeof(*out_meta));
|
||||||
|
omni_copy_fixed_ascii(out_meta->peer_id, sizeof(out_meta->peer_id), peer_id);
|
||||||
|
out_meta->reserved = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 解码 peer bind 消息。 */
|
||||||
|
static inline void omni_peer_bind_meta_decode(const PeerBindMeta *net_meta,
|
||||||
|
PeerBindMeta *host_meta)
|
||||||
|
{
|
||||||
|
memset(host_meta, 0, sizeof(*host_meta));
|
||||||
|
omni_copy_fixed_ascii(host_meta->peer_id, sizeof(host_meta->peer_id), net_meta->peer_id);
|
||||||
|
host_meta->reserved = ntohl(net_meta->reserved);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 编码 hub 状态消息。 */
|
||||||
|
static inline void omni_peer_status_meta_encode(PeerStatusMeta *out_meta,
|
||||||
|
uint32_t code,
|
||||||
|
const char *self_id,
|
||||||
|
const char *peer_id,
|
||||||
|
const char *detail)
|
||||||
|
{
|
||||||
|
memset(out_meta, 0, sizeof(*out_meta));
|
||||||
|
out_meta->code = htonl(code);
|
||||||
|
out_meta->reserved = 0;
|
||||||
|
omni_copy_fixed_ascii(out_meta->self_id, sizeof(out_meta->self_id), self_id);
|
||||||
|
omni_copy_fixed_ascii(out_meta->peer_id, sizeof(out_meta->peer_id), peer_id);
|
||||||
|
omni_copy_fixed_ascii(out_meta->detail, sizeof(out_meta->detail), detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 解码 hub 状态消息。 */
|
||||||
|
static inline void omni_peer_status_meta_decode(const PeerStatusMeta *net_meta,
|
||||||
|
PeerStatusMeta *host_meta)
|
||||||
|
{
|
||||||
|
memset(host_meta, 0, sizeof(*host_meta));
|
||||||
|
host_meta->code = ntohl(net_meta->code);
|
||||||
|
host_meta->reserved = ntohl(net_meta->reserved);
|
||||||
|
omni_copy_fixed_ascii(host_meta->self_id, sizeof(host_meta->self_id), net_meta->self_id);
|
||||||
|
omni_copy_fixed_ascii(host_meta->peer_id, sizeof(host_meta->peer_id), net_meta->peer_id);
|
||||||
|
omni_copy_fixed_ascii(host_meta->detail, sizeof(host_meta->detail), net_meta->detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 编码 peer tunnel 头。 */
|
||||||
|
static inline void omni_peer_tunnel_meta_encode(PeerTunnelMeta *out_meta,
|
||||||
|
const char *src_id,
|
||||||
|
const char *dst_id,
|
||||||
|
uint32_t inner_type)
|
||||||
|
{
|
||||||
|
memset(out_meta, 0, sizeof(*out_meta));
|
||||||
|
omni_copy_fixed_ascii(out_meta->src_id, sizeof(out_meta->src_id), src_id);
|
||||||
|
omni_copy_fixed_ascii(out_meta->dst_id, sizeof(out_meta->dst_id), dst_id);
|
||||||
|
out_meta->inner_type = htonl(inner_type);
|
||||||
|
out_meta->reserved = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 解码 peer tunnel 头。 */
|
||||||
|
static inline void omni_peer_tunnel_meta_decode(const PeerTunnelMeta *net_meta,
|
||||||
|
PeerTunnelMeta *host_meta)
|
||||||
|
{
|
||||||
|
memset(host_meta, 0, sizeof(*host_meta));
|
||||||
|
omni_copy_fixed_ascii(host_meta->src_id, sizeof(host_meta->src_id), net_meta->src_id);
|
||||||
|
omni_copy_fixed_ascii(host_meta->dst_id, sizeof(host_meta->dst_id), net_meta->dst_id);
|
||||||
|
host_meta->inner_type = ntohl(net_meta->inner_type);
|
||||||
|
host_meta->reserved = ntohl(net_meta->reserved);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* OMNISOCKET_COMMON_H */
|
||||||
|
|||||||
@@ -9,23 +9,41 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef struct OmniMetricSummary {
|
||||||
|
uint64_t count;
|
||||||
|
double last;
|
||||||
|
double min;
|
||||||
|
double max;
|
||||||
|
double sum;
|
||||||
|
} OmniMetricSummary;
|
||||||
|
|
||||||
/* 通过该结构体收集全局统计信息 */
|
/* 通过该结构体收集全局统计信息 */
|
||||||
typedef struct OmniStats {
|
typedef struct OmniStats {
|
||||||
uint64_t start_ms; /* 起始时间(毫秒) */
|
uint64_t start_ms; /* 起始时间(毫秒) */
|
||||||
uint64_t last_report_ms; /* 上一次打印日志时间 */
|
uint64_t last_report_ms; /* 上一次打印日志时间 */
|
||||||
|
uint64_t window_start_ms; /* 当前吞吐统计窗口起点 */
|
||||||
|
|
||||||
uint64_t bytes_sent; /* 发送总字节数 */
|
uint64_t bytes_sent; /* 发送总字节数 */
|
||||||
uint64_t bytes_recv; /* 接收总字节数 */
|
uint64_t bytes_recv; /* 接收总字节数 */
|
||||||
|
uint64_t window_bytes_sent; /* 当前 1 秒窗口发送字节数 */
|
||||||
|
uint64_t window_bytes_recv; /* 当前 1 秒窗口接收字节数 */
|
||||||
|
|
||||||
uint64_t send_count; /* 调用 omni_send 次数 */
|
uint64_t send_count; /* 调用 omni_send 次数 */
|
||||||
uint64_t recv_count; /* 调用 omni_recv 次数 */
|
uint64_t recv_count; /* 调用 omni_recv 次数 */
|
||||||
|
|
||||||
uint64_t last_rtt_ms; /* 最近一次 RTT */
|
uint64_t last_rtt_ms; /* 最近一次 RTT */
|
||||||
|
uint64_t min_rtt_ms; /* 最小 RTT(更接近链路基线) */
|
||||||
uint64_t max_rtt_ms; /* 最大 RTT */
|
uint64_t max_rtt_ms; /* 最大 RTT */
|
||||||
|
|
||||||
uint64_t tcp_retrans; /* 预留:TCP 重传统计(如可从内核获取) */
|
uint64_t tcp_retrans; /* 预留:TCP 重传统计(如可从内核获取) */
|
||||||
uint64_t udp_retrans; /* UDP 上层重传次数 */
|
uint64_t udp_retrans; /* UDP 上层重传次数 */
|
||||||
uint64_t kcp_retrans; /* KCP 内部重传次数(可从 ikcp 统计) */
|
uint64_t kcp_retrans; /* KCP 内部重传次数(可从 ikcp 统计) */
|
||||||
|
uint64_t tcp_data_segs_out; /* TCP 累计发送的数据段数(含重传) */
|
||||||
|
uint64_t tcp_data_bytes_sent; /* TCP 累计发送的数据字节(含重传) */
|
||||||
|
uint64_t tcp_retrans_bytes; /* TCP 累计重传的数据字节 */
|
||||||
|
uint64_t kcp_data_segs_out; /* KCP 累计发送的数据分片数(含重传) */
|
||||||
|
uint64_t kcp_data_bytes_sent; /* KCP 累计发送的数据字节(含重传) */
|
||||||
|
uint64_t kcp_retrans_bytes; /* KCP 累计重传的数据字节 */
|
||||||
|
|
||||||
/* 延迟/耗时统计(单位:毫秒) */
|
/* 延迟/耗时统计(单位:毫秒) */
|
||||||
double send_call_avg_ms; /* omni_send 平均耗时(EWMA) */
|
double send_call_avg_ms; /* omni_send 平均耗时(EWMA) */
|
||||||
@@ -40,6 +58,23 @@ typedef struct OmniStats {
|
|||||||
|
|
||||||
uint64_t last_send_call_ms;
|
uint64_t last_send_call_ms;
|
||||||
uint64_t last_recv_call_ms;
|
uint64_t last_recv_call_ms;
|
||||||
|
|
||||||
|
uint64_t total_work_bytes; /* 本次任务总大小(文件总大小等) */
|
||||||
|
uint64_t progress_bytes; /* 当前进度字节数 */
|
||||||
|
|
||||||
|
double tx_current_mbps; /* 最近 1 秒发送速率(Mbps) */
|
||||||
|
double rx_current_mbps; /* 最近 1 秒接收速率(Mbps) */
|
||||||
|
double tx_avg_mbps; /* 从开始到当前的平均发送速率(Mbps) */
|
||||||
|
double rx_avg_mbps; /* 从开始到当前的平均接收速率(Mbps) */
|
||||||
|
|
||||||
|
OmniMetricSummary processing_delay_ms; // 上层处理耗时(如文件读写、加解密等)
|
||||||
|
OmniMetricSummary queue_delay_ms; // 排队延迟
|
||||||
|
OmniMetricSummary transmission_delay_ms; // 传输延迟
|
||||||
|
OmniMetricSummary propagation_delay_ms; // 传播延迟
|
||||||
|
OmniMetricSummary end_to_end_delay_ms; // 端到端延迟
|
||||||
|
OmniMetricSummary send_buffer_pct; // 发送缓冲区占用率
|
||||||
|
OmniMetricSummary recv_buffer_pct; // 接收缓冲区占用率
|
||||||
|
OmniMetricSummary cwnd; // 拥塞窗口大小
|
||||||
} OmniStats;
|
} OmniStats;
|
||||||
|
|
||||||
/* 初始化统计模块,在程序启动时调用一次 */
|
/* 初始化统计模块,在程序启动时调用一次 */
|
||||||
@@ -52,19 +87,46 @@ void logger_on_recv(size_t bytes);
|
|||||||
/* 记录一次 RTT(由上层在合适时机调用) */
|
/* 记录一次 RTT(由上层在合适时机调用) */
|
||||||
void logger_on_rtt(uint64_t rtt_ms);
|
void logger_on_rtt(uint64_t rtt_ms);
|
||||||
|
|
||||||
/* 记录 KCP 重传次数变化(可在 KCP 更新循环中调用) */
|
/* 记录 TCP 传输层累计快照(通常来自 TCP_INFO)。 */
|
||||||
void logger_on_kcp_retrans(uint64_t delta);
|
void logger_on_tcp_transport(uint64_t total_retrans,
|
||||||
|
uint64_t data_segs_out,
|
||||||
|
uint64_t data_bytes_sent,
|
||||||
|
uint64_t retrans_bytes);
|
||||||
|
|
||||||
|
/* 记录 KCP 数据分片首次发送。 */
|
||||||
|
void logger_on_kcp_tx(uint64_t segs, uint64_t bytes);
|
||||||
|
|
||||||
|
/* 记录 KCP 数据分片重传。 */
|
||||||
|
void logger_on_kcp_retrans(uint64_t segs, uint64_t bytes);
|
||||||
|
|
||||||
/* 记录一次耗时(ms) */
|
/* 记录一次耗时(ms) */
|
||||||
void logger_on_send_call_latency(uint64_t ms);
|
void logger_on_send_call_latency(uint64_t ms);
|
||||||
void logger_on_recv_call_latency(uint64_t ms);
|
void logger_on_recv_call_latency(uint64_t ms);
|
||||||
void logger_on_proto_send_latency(uint64_t ms);
|
void logger_on_proto_send_latency(uint64_t ms);
|
||||||
void logger_on_proto_recv_latency(uint64_t ms);
|
void logger_on_proto_recv_latency(uint64_t ms);
|
||||||
|
void logger_on_processing_latency(double ms);
|
||||||
|
void logger_on_queue_delay_est(double ms);
|
||||||
|
void logger_on_transmission_delay_est(double ms);
|
||||||
|
void logger_on_propagation_delay_est(double ms);
|
||||||
|
void logger_on_end_to_end_latency(double ms);
|
||||||
|
void logger_on_send_queue_bytes(size_t bytes);
|
||||||
|
void logger_on_recv_queue_bytes(size_t bytes);
|
||||||
|
void logger_on_send_transmission_bytes(size_t bytes);
|
||||||
|
void logger_on_recv_transmission_bytes(size_t bytes);
|
||||||
|
void logger_on_buffer_status(double send_pct, double recv_pct);
|
||||||
|
void logger_on_cwnd(double cwnd);
|
||||||
|
|
||||||
|
/* 记录任务总量与当前进度。 */
|
||||||
|
void logger_set_transfer_total(uint64_t total_bytes);
|
||||||
|
void logger_set_progress(uint64_t progress_bytes);
|
||||||
|
|
||||||
/* 计算当前吞吐量(返回:字节/秒) */
|
/* 计算当前吞吐量(返回:字节/秒) */
|
||||||
double logger_calculate_throughput(void);
|
double logger_calculate_throughput(void);
|
||||||
|
|
||||||
/* 打印一条结构化性能日志(例如每隔若干秒调用) */
|
/* 在 1 秒窗口到期时打印周期性性能日志。 */
|
||||||
|
void logger_maybe_print_performance_log(const char *tag);
|
||||||
|
|
||||||
|
/* 强制打印一条结构化性能日志(例如最终汇总前调用) */
|
||||||
void logger_print_performance_log(const char *tag);
|
void logger_print_performance_log(const char *tag);
|
||||||
|
|
||||||
/* 结构化通用日志(key=value 形式) */
|
/* 结构化通用日志(key=value 形式) */
|
||||||
@@ -75,4 +137,3 @@ void logger_log(const char *level, const char *component,
|
|||||||
OmniStats logger_get_snapshot(void);
|
OmniStats logger_get_snapshot(void);
|
||||||
|
|
||||||
#endif /* OMNISOCKET_LOGGER_H */
|
#endif /* OMNISOCKET_LOGGER_H */
|
||||||
|
|
||||||
|
|||||||
35024
omni_logs.jsonl
35024
omni_logs.jsonl
File diff suppressed because it is too large
Load Diff
49
scripts/local_peer_smoke_test.sh
Executable file
49
scripts/local_peer_smoke_test.sh
Executable file
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 本机 hub/peer smoke 测试:
|
||||||
|
# - 启动 1 个 omni_hub
|
||||||
|
# - 启动 2 个 omni_peer(beta 常驻,alpha 发一条消息给 beta)
|
||||||
|
# - 校验 beta 日志里确实收到了 alpha 的消息
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
BUILD_DIR="$ROOT_DIR/build"
|
||||||
|
TMP_DIR="$(mktemp -d /tmp/omnisocket-peer-smoke.XXXXXX)"
|
||||||
|
PORT=$((30000 + (RANDOM % 20000)))
|
||||||
|
|
||||||
|
PIDS=()
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
for pid in "${PIDS[@]:-}"; do
|
||||||
|
kill "$pid" 2>/dev/null || true
|
||||||
|
wait "$pid" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
rm -rf "$TMP_DIR"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
log() {
|
||||||
|
printf '[peer-smoke] %s\n' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log "building hub/peer binaries"
|
||||||
|
make -C "$ROOT_DIR" build/omni_hub build/omni_peer >/dev/null
|
||||||
|
|
||||||
|
log "starting hub on port $PORT"
|
||||||
|
"$BUILD_DIR/omni_hub" -P "$PORT" >"$TMP_DIR/hub.log" 2>&1 &
|
||||||
|
HUB_PID=$!
|
||||||
|
PIDS+=("$HUB_PID")
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
log "starting beta peer"
|
||||||
|
"$BUILD_DIR/omni_peer" -H 127.0.0.1 -P "$PORT" -i beta -w 4 >"$TMP_DIR/beta.log" 2>&1 &
|
||||||
|
BETA_PID=$!
|
||||||
|
PIDS+=("$BETA_PID")
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
log "starting alpha peer and sending command"
|
||||||
|
"$BUILD_DIR/omni_peer" -H 127.0.0.1 -P "$PORT" -i alpha -b beta -d beta -m "hello-from-alpha" -w 2 >"$TMP_DIR/alpha.log" 2>&1
|
||||||
|
wait "$BETA_PID"
|
||||||
|
|
||||||
|
grep -q "\[peer alpha -> beta\] hello-from-alpha" "$TMP_DIR/beta.log"
|
||||||
|
|
||||||
|
log "peer command tunnel passed"
|
||||||
567
src/apps/bridge_main.c
Normal file
567
src/apps/bridge_main.c
Normal file
@@ -0,0 +1,567 @@
|
|||||||
|
/*
|
||||||
|
* bridge_main.c
|
||||||
|
* 固定多跳代理:
|
||||||
|
* - 上游作为一个 peer 主动连接远端 hub
|
||||||
|
* - 下游作为一个轻量 hub 接入本地 peer
|
||||||
|
* - 将 bind / tunnel / status 在上下游之间原样转发
|
||||||
|
*
|
||||||
|
* 适用场景:
|
||||||
|
* - A 连接 C(hub)
|
||||||
|
* - B 连接 D(bridge)
|
||||||
|
* - D 再连接 C
|
||||||
|
* - C 只看见 bridge 暴露出来的逻辑 client_id
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define BRIDGE_MAX_PAYLOAD (PEER_TUNNEL_META_SIZE + 65536u)
|
||||||
|
|
||||||
|
typedef struct BridgeRuntime {
|
||||||
|
int listen_fd;
|
||||||
|
int upstream_fd;
|
||||||
|
int downstream_fd;
|
||||||
|
pthread_mutex_t upstream_mu;
|
||||||
|
pthread_mutex_t downstream_mu;
|
||||||
|
pthread_mutex_t state_mu;
|
||||||
|
atomic_int running;
|
||||||
|
char client_id[OMNI_PEER_ID_SIZE];
|
||||||
|
} BridgeRuntime;
|
||||||
|
|
||||||
|
static volatile sig_atomic_t g_stop = 0;
|
||||||
|
|
||||||
|
static void on_signal(int signo)
|
||||||
|
{
|
||||||
|
(void)signo;
|
||||||
|
g_stop = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void install_signal_handlers(void)
|
||||||
|
{
|
||||||
|
struct sigaction sa;
|
||||||
|
|
||||||
|
memset(&sa, 0, sizeof(sa));
|
||||||
|
sa.sa_handler = on_signal;
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
(void)sigaction(SIGINT, &sa, NULL);
|
||||||
|
(void)sigaction(SIGTERM, &sa, NULL);
|
||||||
|
(void)signal(SIGPIPE, SIG_IGN);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usage(const char *prog)
|
||||||
|
{
|
||||||
|
fprintf(stderr,
|
||||||
|
"Usage:\n"
|
||||||
|
" %s -H <upstream_hub_ip> -P <upstream_hub_port> -i <client_id> -L <listen_port> [-b <bind_ip>]\n",
|
||||||
|
prog);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int peer_id_is_valid(const char *id)
|
||||||
|
{
|
||||||
|
size_t len = 0;
|
||||||
|
|
||||||
|
if (!id || !id[0]) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (len = 0; id[len] != '\0'; ++len) {
|
||||||
|
unsigned char ch = (unsigned char)id[len];
|
||||||
|
|
||||||
|
if (len + 1u >= OMNI_PEER_ID_SIZE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!((ch >= 'a' && ch <= 'z') ||
|
||||||
|
(ch >= 'A' && ch <= 'Z') ||
|
||||||
|
(ch >= '0' && ch <= '9') ||
|
||||||
|
ch == '_' || ch == '-' || ch == '.')) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t read_n(int fd, void *buf, size_t n)
|
||||||
|
{
|
||||||
|
uint8_t *p = (uint8_t *)buf;
|
||||||
|
size_t done = 0;
|
||||||
|
|
||||||
|
while (done < n) {
|
||||||
|
ssize_t rc = recv(fd, p + done, n - done, 0);
|
||||||
|
if (rc == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (rc < 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
done += (size_t)rc;
|
||||||
|
}
|
||||||
|
return (ssize_t)done;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t write_n(int fd, const void *buf, size_t n)
|
||||||
|
{
|
||||||
|
const uint8_t *p = (const uint8_t *)buf;
|
||||||
|
size_t done = 0;
|
||||||
|
|
||||||
|
while (done < n) {
|
||||||
|
ssize_t rc = send(fd, p + done, n - done, 0);
|
||||||
|
if (rc < 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
done += (size_t)rc;
|
||||||
|
}
|
||||||
|
return (ssize_t)done;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int recv_app_message(int fd, MsgHeader *out_hdr, uint8_t *payload_buf, size_t payload_cap)
|
||||||
|
{
|
||||||
|
MsgHeader net_hdr;
|
||||||
|
ssize_t n;
|
||||||
|
|
||||||
|
n = read_n(fd, &net_hdr, MSG_HEADER_SIZE);
|
||||||
|
if (n == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (n != (ssize_t)MSG_HEADER_SIZE) {
|
||||||
|
return OMNI_ERR_IO;
|
||||||
|
}
|
||||||
|
|
||||||
|
omni_msg_header_decode(&net_hdr, out_hdr);
|
||||||
|
if (out_hdr->len > payload_cap) {
|
||||||
|
return OMNI_ERR_IO;
|
||||||
|
}
|
||||||
|
if (out_hdr->len > 0) {
|
||||||
|
n = read_n(fd, payload_buf, out_hdr->len);
|
||||||
|
if (n != (ssize_t)out_hdr->len) {
|
||||||
|
return OMNI_ERR_IO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger_on_recv(MSG_HEADER_SIZE + out_hdr->len);
|
||||||
|
logger_maybe_print_performance_log("bridge_recv");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int send_app_message_fd_locked(int fd,
|
||||||
|
uint32_t type,
|
||||||
|
const void *payload,
|
||||||
|
uint32_t payload_len)
|
||||||
|
{
|
||||||
|
MsgHeader hdr;
|
||||||
|
uint8_t header_buf[MSG_HEADER_SIZE];
|
||||||
|
|
||||||
|
omni_msg_header_encode(&hdr, type, payload_len, omni_now_ms());
|
||||||
|
memcpy(header_buf, &hdr, sizeof(header_buf));
|
||||||
|
|
||||||
|
if (write_n(fd, header_buf, sizeof(header_buf)) != (ssize_t)sizeof(header_buf)) {
|
||||||
|
return OMNI_ERR_IO;
|
||||||
|
}
|
||||||
|
if (payload_len > 0 && payload) {
|
||||||
|
if (write_n(fd, payload, payload_len) != (ssize_t)payload_len) {
|
||||||
|
return OMNI_ERR_IO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger_on_send(MSG_HEADER_SIZE + payload_len);
|
||||||
|
logger_maybe_print_performance_log("bridge_send");
|
||||||
|
return OMNI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int bridge_send_upstream(BridgeRuntime *rt,
|
||||||
|
uint32_t type,
|
||||||
|
const void *payload,
|
||||||
|
uint32_t payload_len)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&rt->upstream_mu);
|
||||||
|
rc = send_app_message_fd_locked(rt->upstream_fd, type, payload, payload_len);
|
||||||
|
pthread_mutex_unlock(&rt->upstream_mu);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int bridge_send_downstream(BridgeRuntime *rt,
|
||||||
|
uint32_t type,
|
||||||
|
const void *payload,
|
||||||
|
uint32_t payload_len)
|
||||||
|
{
|
||||||
|
int rc = OMNI_ERR_IO;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&rt->downstream_mu);
|
||||||
|
if (rt->downstream_fd >= 0) {
|
||||||
|
rc = send_app_message_fd_locked(rt->downstream_fd, type, payload, payload_len);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&rt->downstream_mu);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int send_status_to_downstream(BridgeRuntime *rt,
|
||||||
|
uint32_t code,
|
||||||
|
const char *self_id,
|
||||||
|
const char *peer_id,
|
||||||
|
const char *detail)
|
||||||
|
{
|
||||||
|
PeerStatusMeta status_meta;
|
||||||
|
|
||||||
|
omni_peer_status_meta_encode(&status_meta, code, self_id, peer_id, detail);
|
||||||
|
return bridge_send_downstream(rt,
|
||||||
|
MSG_TYPE_PEER_STATUS,
|
||||||
|
&status_meta,
|
||||||
|
PEER_STATUS_META_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int create_connected_socket(const char *host, uint16_t port)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
struct sockaddr_in addr;
|
||||||
|
|
||||||
|
fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (fd < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&addr, 0, sizeof(addr));
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_port = htons(port);
|
||||||
|
if (inet_pton(AF_INET, host, &addr.sin_addr) != 1) {
|
||||||
|
close(fd);
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int create_listen_socket(const char *bind_ip, uint16_t port)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
int reuse = 1;
|
||||||
|
struct sockaddr_in addr;
|
||||||
|
|
||||||
|
fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (fd < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
||||||
|
memset(&addr, 0, sizeof(addr));
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_port = htons(port);
|
||||||
|
if (bind_ip && bind_ip[0] != '\0') {
|
||||||
|
if (inet_pton(AF_INET, bind_ip, &addr.sin_addr) != 1) {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (listen(fd, 8) != 0) {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int bridge_send_upstream_register(BridgeRuntime *rt)
|
||||||
|
{
|
||||||
|
PeerRegisterMeta meta;
|
||||||
|
|
||||||
|
omni_peer_register_meta_encode(&meta, rt->client_id);
|
||||||
|
return bridge_send_upstream(rt, MSG_TYPE_PEER_REGISTER, &meta, PEER_REGISTER_META_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *upstream_thread_main(void *arg)
|
||||||
|
{
|
||||||
|
BridgeRuntime *rt = (BridgeRuntime *)arg;
|
||||||
|
uint8_t payload[BRIDGE_MAX_PAYLOAD];
|
||||||
|
|
||||||
|
while (atomic_load(&rt->running)) {
|
||||||
|
MsgHeader hdr;
|
||||||
|
int rc = recv_app_message(rt->upstream_fd, &hdr, payload, sizeof(payload));
|
||||||
|
|
||||||
|
if (rc == 0) {
|
||||||
|
logger_log("INFO", "bridge", "upstream_closed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (rc < 0) {
|
||||||
|
logger_log("ERROR", "bridge", "upstream_recv_failed rc=%d", rc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (hdr.type) {
|
||||||
|
case MSG_TYPE_PEER_STATUS:
|
||||||
|
case MSG_TYPE_PEER_TUNNEL:
|
||||||
|
if (bridge_send_downstream(rt, hdr.type, payload, hdr.len) != OMNI_OK) {
|
||||||
|
logger_log("WARN", "bridge",
|
||||||
|
"downstream_forward_skipped type=%u len=%u",
|
||||||
|
(unsigned)hdr.type,
|
||||||
|
(unsigned)hdr.len);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger_log("WARN", "bridge", "unexpected_upstream_type=%u len=%u",
|
||||||
|
(unsigned)hdr.type, (unsigned)hdr.len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic_store(&rt->running, 0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clear_downstream_fd(BridgeRuntime *rt, int fd)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&rt->downstream_mu);
|
||||||
|
if (rt->downstream_fd == fd) {
|
||||||
|
rt->downstream_fd = -1;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&rt->downstream_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_downstream_connection(BridgeRuntime *rt, int fd)
|
||||||
|
{
|
||||||
|
uint8_t payload[BRIDGE_MAX_PAYLOAD];
|
||||||
|
int registered = 0;
|
||||||
|
|
||||||
|
while (atomic_load(&rt->running)) {
|
||||||
|
MsgHeader hdr;
|
||||||
|
int rc = recv_app_message(fd, &hdr, payload, sizeof(payload));
|
||||||
|
|
||||||
|
if (rc == 0) {
|
||||||
|
logger_log("INFO", "bridge", "downstream_closed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (rc < 0) {
|
||||||
|
logger_log("ERROR", "bridge", "downstream_recv_failed rc=%d", rc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (hdr.type) {
|
||||||
|
case MSG_TYPE_PEER_REGISTER: {
|
||||||
|
PeerRegisterMeta register_meta;
|
||||||
|
|
||||||
|
if (hdr.len < PEER_REGISTER_META_SIZE) {
|
||||||
|
(void)send_status_to_downstream(rt, PEER_STATUS_ERROR, NULL, NULL, "short_register_payload");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
omni_peer_register_meta_decode((const PeerRegisterMeta *)payload, ®ister_meta);
|
||||||
|
if (strcmp(register_meta.client_id, rt->client_id) != 0) {
|
||||||
|
(void)send_status_to_downstream(rt,
|
||||||
|
PEER_STATUS_ERROR,
|
||||||
|
register_meta.client_id,
|
||||||
|
NULL,
|
||||||
|
"client_id_mismatch");
|
||||||
|
logger_log("WARN", "bridge",
|
||||||
|
"downstream_register_mismatch got=%s expect=%s",
|
||||||
|
register_meta.client_id,
|
||||||
|
rt->client_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
registered = 1;
|
||||||
|
(void)send_status_to_downstream(rt,
|
||||||
|
PEER_STATUS_REGISTERED,
|
||||||
|
rt->client_id,
|
||||||
|
NULL,
|
||||||
|
"bridge_register_ok");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MSG_TYPE_PEER_BIND:
|
||||||
|
case MSG_TYPE_PEER_TUNNEL:
|
||||||
|
if (!registered) {
|
||||||
|
(void)send_status_to_downstream(rt,
|
||||||
|
PEER_STATUS_ERROR,
|
||||||
|
rt->client_id,
|
||||||
|
NULL,
|
||||||
|
"register_first");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (bridge_send_upstream(rt, hdr.type, payload, hdr.len) != OMNI_OK) {
|
||||||
|
logger_log("ERROR", "bridge",
|
||||||
|
"upstream_forward_failed type=%u len=%u",
|
||||||
|
(unsigned)hdr.type,
|
||||||
|
(unsigned)hdr.len);
|
||||||
|
(void)send_status_to_downstream(rt,
|
||||||
|
PEER_STATUS_ERROR,
|
||||||
|
rt->client_id,
|
||||||
|
NULL,
|
||||||
|
"upstream_forward_failed");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger_log("WARN", "bridge", "unexpected_downstream_type=%u len=%u",
|
||||||
|
(unsigned)hdr.type, (unsigned)hdr.len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_downstream_fd(rt, fd);
|
||||||
|
shutdown(fd, SHUT_RDWR);
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
const char *upstream_ip = NULL;
|
||||||
|
const char *bind_ip = NULL;
|
||||||
|
const char *client_id = NULL;
|
||||||
|
int upstream_port = 0;
|
||||||
|
int listen_port = 0;
|
||||||
|
int opt;
|
||||||
|
BridgeRuntime rt;
|
||||||
|
pthread_t upstream_tid;
|
||||||
|
|
||||||
|
while ((opt = getopt(argc, argv, "H:P:i:L:b:")) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'H':
|
||||||
|
upstream_ip = optarg;
|
||||||
|
break;
|
||||||
|
case 'P':
|
||||||
|
upstream_port = atoi(optarg);
|
||||||
|
break;
|
||||||
|
case 'i':
|
||||||
|
client_id = optarg;
|
||||||
|
break;
|
||||||
|
case 'L':
|
||||||
|
listen_port = atoi(optarg);
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
bind_ip = optarg;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
usage(argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!upstream_ip || upstream_port <= 0 || listen_port <= 0 || !peer_id_is_valid(client_id)) {
|
||||||
|
usage(argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger_init();
|
||||||
|
install_signal_handlers();
|
||||||
|
|
||||||
|
memset(&rt, 0, sizeof(rt));
|
||||||
|
rt.listen_fd = -1;
|
||||||
|
rt.upstream_fd = -1;
|
||||||
|
rt.downstream_fd = -1;
|
||||||
|
omni_copy_fixed_ascii(rt.client_id, sizeof(rt.client_id), client_id);
|
||||||
|
atomic_init(&rt.running, 1);
|
||||||
|
pthread_mutex_init(&rt.upstream_mu, NULL);
|
||||||
|
pthread_mutex_init(&rt.downstream_mu, NULL);
|
||||||
|
pthread_mutex_init(&rt.state_mu, NULL);
|
||||||
|
|
||||||
|
rt.upstream_fd = create_connected_socket(upstream_ip, (uint16_t)upstream_port);
|
||||||
|
if (rt.upstream_fd < 0) {
|
||||||
|
perror("bridge upstream connect");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
if (bridge_send_upstream_register(&rt) != OMNI_OK) {
|
||||||
|
fprintf(stderr, "bridge upstream register failed\n");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
rt.listen_fd = create_listen_socket(bind_ip, (uint16_t)listen_port);
|
||||||
|
if (rt.listen_fd < 0) {
|
||||||
|
perror("bridge listen");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pthread_create(&upstream_tid, NULL, upstream_thread_main, &rt) != 0) {
|
||||||
|
perror("bridge pthread_create");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger_log("INFO", "bridge",
|
||||||
|
"listening bind_ip=%s listen_port=%u upstream=%s:%u client_id=%s",
|
||||||
|
bind_ip ? bind_ip : "0.0.0.0",
|
||||||
|
(unsigned)listen_port,
|
||||||
|
upstream_ip,
|
||||||
|
(unsigned)upstream_port,
|
||||||
|
rt.client_id);
|
||||||
|
|
||||||
|
while (atomic_load(&rt.running) && !g_stop) {
|
||||||
|
struct sockaddr_in peer_addr;
|
||||||
|
socklen_t peer_len = sizeof(peer_addr);
|
||||||
|
int cfd = accept(rt.listen_fd, (struct sockaddr *)&peer_addr, &peer_len);
|
||||||
|
|
||||||
|
if (cfd < 0) {
|
||||||
|
if (errno == EINTR && !g_stop) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (g_stop) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
perror("bridge accept");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&rt.downstream_mu);
|
||||||
|
if (rt.downstream_fd >= 0) {
|
||||||
|
pthread_mutex_unlock(&rt.downstream_mu);
|
||||||
|
close(cfd);
|
||||||
|
logger_log("WARN", "bridge", "reject_extra_downstream");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rt.downstream_fd = cfd;
|
||||||
|
pthread_mutex_unlock(&rt.downstream_mu);
|
||||||
|
|
||||||
|
logger_log("INFO", "bridge", "downstream_connected");
|
||||||
|
handle_downstream_connection(&rt, cfd);
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic_store(&rt.running, 0);
|
||||||
|
if (rt.listen_fd >= 0) {
|
||||||
|
close(rt.listen_fd);
|
||||||
|
}
|
||||||
|
pthread_join(upstream_tid, NULL);
|
||||||
|
if (rt.upstream_fd >= 0) {
|
||||||
|
shutdown(rt.upstream_fd, SHUT_RDWR);
|
||||||
|
close(rt.upstream_fd);
|
||||||
|
}
|
||||||
|
logger_print_performance_log("final");
|
||||||
|
pthread_mutex_destroy(&rt.state_mu);
|
||||||
|
pthread_mutex_destroy(&rt.downstream_mu);
|
||||||
|
pthread_mutex_destroy(&rt.upstream_mu);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
if (rt.listen_fd >= 0) {
|
||||||
|
close(rt.listen_fd);
|
||||||
|
}
|
||||||
|
if (rt.upstream_fd >= 0) {
|
||||||
|
close(rt.upstream_fd);
|
||||||
|
}
|
||||||
|
pthread_mutex_destroy(&rt.state_mu);
|
||||||
|
pthread_mutex_destroy(&rt.downstream_mu);
|
||||||
|
pthread_mutex_destroy(&rt.upstream_mu);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
* client_main.c
|
* client_main.c
|
||||||
* 客户端:读取大文件分片发送,同时后台接收服务端 ASCII 指令并打印
|
* 客户端:读取大文件分片发送,同时后台接收服务端控制/确认消息
|
||||||
*
|
*
|
||||||
* 线程模型:
|
* 整体模型:
|
||||||
* - 主线程:读取文件并发送 FILE_CHUNK / FILE_END
|
* 1) 主线程负责读文件、切 chunk、封装业务帧并发送。
|
||||||
* - 子线程:持续接收服务端 COMMAND 并打印
|
* 2) 接收线程负责监听服务端命令和最终 ACK。
|
||||||
*
|
* 3) 两个线程通过原子变量共享“运行状态”和“ACK 结果”。
|
||||||
* 消息格式:
|
|
||||||
* - 每条业务消息为 [MsgHeader(16B) + payload]
|
|
||||||
* - MsgHeader 字段由 common.h 中的 encode/decode 统一处理
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
@@ -23,28 +20,43 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#define CLIENT_FRAME_BUF_SIZE (MSG_HEADER_SIZE + 65536u)
|
/* 接收线程的单帧缓冲区上限:业务头 + chunk 元数据 + 最大 payload。 */
|
||||||
|
#define CLIENT_FRAME_BUF_SIZE (MSG_HEADER_SIZE + TRANSFER_CHUNK_META_SIZE + 65536u)
|
||||||
|
#define OMNI_TIME_SYNC_PROBE_COUNT 5u
|
||||||
|
#define OMNI_TIME_SYNC_REPLY_TIMEOUT_MS 500u
|
||||||
|
|
||||||
typedef struct ClientRuntime {
|
typedef struct ClientRuntime {
|
||||||
/* 协议抽象层句柄。 */
|
OmniContext *ctx; /* 底层协议上下文。 */
|
||||||
OmniContext *ctx;
|
atomic_int running; /* 共享退出标志,主线程和接收线程都会检查。 */
|
||||||
/* 线程共享运行标记:1=运行中,0=退出。 */
|
atomic_int ack_received; /* 是否已经收到服务端的传输完成确认。 */
|
||||||
atomic_int running;
|
atomic_ullong ack_rtt_ms; /* FILE_END -> TRANSFER_ACK 这一来一回的 RTT。 */
|
||||||
|
atomic_ullong ack_bytes_written; /* 服务端实际写盘字节数。 */
|
||||||
|
atomic_uint ack_transfer_id; /* 收到 ACK 对应的传输 ID。 */
|
||||||
|
atomic_uint sync_expected_probe_id; /* 当前正在等的 probe。 */
|
||||||
|
atomic_ullong sync_expected_client_send_ts_ms; /* 当前 probe 的客户端 t0。 */
|
||||||
|
atomic_int sync_reply_ready; /* 后台线程是否已经收到匹配响应。 */
|
||||||
|
atomic_ullong sync_reply_rtt_ms; /* 当前 probe 的 RTT。 */
|
||||||
|
atomic_llong sync_reply_offset_ms; /* 当前 probe 的 offset。 */
|
||||||
} ClientRuntime;
|
} ClientRuntime;
|
||||||
|
|
||||||
/*
|
typedef struct ClockSyncResult {
|
||||||
* 进程级停止标记:
|
int valid; /* 是否已拿到可用 offset。 */
|
||||||
* - 收到 SIGINT/SIGTERM(例如 Ctrl+C)时置 1
|
int64_t server_minus_client_offset_ms; /* server_time - client_time */
|
||||||
* - 主线程据此触发收尾逻辑,保证线程/连接能优雅退出
|
uint64_t best_rtt_ms; /* 选中样本的 RTT。 */
|
||||||
*/
|
uint32_t sample_count; /* 实际成功样本数。 */
|
||||||
|
} ClockSyncResult;
|
||||||
|
|
||||||
|
/* 信号处理只做最轻量的事情:设置停止标志,由主流程自己收尾。 */
|
||||||
static volatile sig_atomic_t g_stop = 0;
|
static volatile sig_atomic_t g_stop = 0;
|
||||||
|
|
||||||
|
/* SIGINT/SIGTERM 的处理函数:通知发送循环尽快退出。 */
|
||||||
static void on_signal(int signo)
|
static void on_signal(int signo)
|
||||||
{
|
{
|
||||||
(void)signo;
|
(void)signo;
|
||||||
g_stop = 1;
|
g_stop = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 注册 Ctrl+C / kill 的处理逻辑,避免被粗暴中断后缺少收尾日志。 */
|
||||||
static void install_signal_handlers(void)
|
static void install_signal_handlers(void)
|
||||||
{
|
{
|
||||||
struct sigaction sa;
|
struct sigaction sa;
|
||||||
@@ -57,6 +69,7 @@ static void install_signal_handlers(void)
|
|||||||
(void)sigaction(SIGTERM, &sa, NULL);
|
(void)sigaction(SIGTERM, &sa, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 打印客户端命令行帮助。 */
|
||||||
static void usage(const char *prog)
|
static void usage(const char *prog)
|
||||||
{
|
{
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
@@ -66,9 +79,9 @@ static void usage(const char *prog)
|
|||||||
prog);
|
prog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 将字符串协议名转换为内部枚举;非法输入默认回退 TCP。 */
|
||||||
static OmniProtocol parse_proto(const char *s)
|
static OmniProtocol parse_proto(const char *s)
|
||||||
{
|
{
|
||||||
/* 输入非法时回退到 TCP,方便本地默认测试。 */
|
|
||||||
if (!s) return OMNI_PROTO_TCP;
|
if (!s) return OMNI_PROTO_TCP;
|
||||||
if (strcmp(s, "tcp") == 0) return OMNI_PROTO_TCP;
|
if (strcmp(s, "tcp") == 0) return OMNI_PROTO_TCP;
|
||||||
if (strcmp(s, "udp") == 0) return OMNI_PROTO_UDP;
|
if (strcmp(s, "udp") == 0) return OMNI_PROTO_UDP;
|
||||||
@@ -76,17 +89,16 @@ static OmniProtocol parse_proto(const char *s)
|
|||||||
return OMNI_PROTO_TCP;
|
return OMNI_PROTO_TCP;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int send_app_message(OmniContext *ctx,
|
/*
|
||||||
|
* 发送一个完整的业务帧。
|
||||||
|
* 这里统一负责:拼 MsgHeader -> 拷贝 payload -> 调底层 omni_send。
|
||||||
|
*/
|
||||||
|
static int send_app_message_with_timestamp(OmniContext *ctx,
|
||||||
uint32_t type,
|
uint32_t type,
|
||||||
const void *payload,
|
const void *payload,
|
||||||
uint32_t payload_len)
|
uint32_t payload_len,
|
||||||
|
uint64_t timestamp_ms)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
* 统一应用层发包:
|
|
||||||
* 1) 组装业务头(网络字节序)
|
|
||||||
* 2) 拼接 payload
|
|
||||||
* 3) 通过 omni_send 一次发送整帧
|
|
||||||
*/
|
|
||||||
size_t total_len = MSG_HEADER_SIZE + (size_t)payload_len;
|
size_t total_len = MSG_HEADER_SIZE + (size_t)payload_len;
|
||||||
uint8_t *frame = (uint8_t *)malloc(total_len);
|
uint8_t *frame = (uint8_t *)malloc(total_len);
|
||||||
if (!frame) {
|
if (!frame) {
|
||||||
@@ -95,7 +107,8 @@ static int send_app_message(OmniContext *ctx,
|
|||||||
}
|
}
|
||||||
|
|
||||||
MsgHeader hdr;
|
MsgHeader hdr;
|
||||||
omni_msg_header_encode(&hdr, type, payload_len, omni_now_ms());
|
/* 业务层总是按“头 + 载荷”的统一格式发给对端。 */
|
||||||
|
omni_msg_header_encode(&hdr, type, payload_len, timestamp_ms);
|
||||||
memcpy(frame, &hdr, MSG_HEADER_SIZE);
|
memcpy(frame, &hdr, MSG_HEADER_SIZE);
|
||||||
if (payload_len > 0 && payload) {
|
if (payload_len > 0 && payload) {
|
||||||
memcpy(frame + MSG_HEADER_SIZE, payload, payload_len);
|
memcpy(frame + MSG_HEADER_SIZE, payload, payload_len);
|
||||||
@@ -113,16 +126,12 @@ static int send_app_message(OmniContext *ctx,
|
|||||||
return OMNI_OK;
|
return OMNI_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 从接收到的一整帧中拆出应用层头和 payload 指针,并做长度一致性校验。 */
|
||||||
static int decode_app_message(const uint8_t *frame,
|
static int decode_app_message(const uint8_t *frame,
|
||||||
size_t frame_len,
|
size_t frame_len,
|
||||||
MsgHeader *out_hdr,
|
MsgHeader *out_hdr,
|
||||||
const uint8_t **out_payload)
|
const uint8_t **out_payload)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
* 统一应用层解包:
|
|
||||||
* - 至少要有 16B 头
|
|
||||||
* - 头中 len 与总帧长度必须一致,避免越界/脏数据
|
|
||||||
*/
|
|
||||||
if (!frame || frame_len < MSG_HEADER_SIZE || !out_hdr || !out_payload) {
|
if (!frame || frame_len < MSG_HEADER_SIZE || !out_hdr || !out_payload) {
|
||||||
return OMNI_ERR_PARAM;
|
return OMNI_ERR_PARAM;
|
||||||
}
|
}
|
||||||
@@ -139,15 +148,250 @@ static int decode_app_message(const uint8_t *frame,
|
|||||||
return OMNI_OK;
|
return OMNI_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 用四时间戳公式估算 server_time - client_time。 */
|
||||||
|
static int64_t compute_server_minus_client_offset_ms(const TimeSyncReplyMeta *reply,
|
||||||
|
uint64_t client_recv_ts_ms)
|
||||||
|
{
|
||||||
|
int64_t req_leg = (int64_t)reply->server_recv_ts_ms -
|
||||||
|
(int64_t)reply->client_send_ts_ms;
|
||||||
|
int64_t resp_leg = (int64_t)reply->server_send_ts_ms -
|
||||||
|
(int64_t)client_recv_ts_ms;
|
||||||
|
return (req_leg + resp_leg) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 在正式发文件前做几轮时钟探测:
|
||||||
|
* - 主线程发 t0
|
||||||
|
* - 服务端回 t1/t2
|
||||||
|
* - 后台接收线程在 t3 处写回 RTT/offset
|
||||||
|
* - 主线程带超时地等待,保证经由单向 relay 时不会卡死
|
||||||
|
*/
|
||||||
|
static int perform_time_sync(OmniContext *ctx,
|
||||||
|
ClientRuntime *rt,
|
||||||
|
ClockSyncResult *out_result)
|
||||||
|
{
|
||||||
|
uint64_t best_rtt_ms = UINT64_MAX;
|
||||||
|
int64_t best_offset_ms = 0;
|
||||||
|
uint32_t sample_count = 0;
|
||||||
|
|
||||||
|
if (!ctx || !rt || !out_result) {
|
||||||
|
return OMNI_ERR_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(out_result, 0, sizeof(*out_result));
|
||||||
|
|
||||||
|
for (uint32_t probe_id = 1; probe_id <= OMNI_TIME_SYNC_PROBE_COUNT; ++probe_id) {
|
||||||
|
TimeSyncProbeMeta probe_meta;
|
||||||
|
uint64_t client_send_ts_ms = omni_now_ms();
|
||||||
|
int got_reply = 0;
|
||||||
|
|
||||||
|
atomic_store(&rt->sync_reply_ready, 0);
|
||||||
|
atomic_store(&rt->sync_expected_probe_id, probe_id);
|
||||||
|
atomic_store(&rt->sync_expected_client_send_ts_ms, client_send_ts_ms);
|
||||||
|
|
||||||
|
omni_time_sync_probe_meta_encode(&probe_meta, probe_id, client_send_ts_ms);
|
||||||
|
if (send_app_message_with_timestamp(ctx,
|
||||||
|
MSG_TYPE_TIME_SYNC_REQ,
|
||||||
|
&probe_meta,
|
||||||
|
TIME_SYNC_PROBE_META_SIZE,
|
||||||
|
client_send_ts_ms) != OMNI_OK) {
|
||||||
|
logger_log("ERROR", "client",
|
||||||
|
"time_sync_send_failed probe_id=%u",
|
||||||
|
(unsigned)probe_id);
|
||||||
|
return OMNI_ERR_IO;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t waited_ms = 0;
|
||||||
|
waited_ms < OMNI_TIME_SYNC_REPLY_TIMEOUT_MS;
|
||||||
|
waited_ms += 5u) {
|
||||||
|
if (atomic_load(&rt->sync_reply_ready)) {
|
||||||
|
uint64_t rtt_ms = atomic_load(&rt->sync_reply_rtt_ms);
|
||||||
|
int64_t offset_ms = atomic_load(&rt->sync_reply_offset_ms);
|
||||||
|
|
||||||
|
sample_count++;
|
||||||
|
got_reply = 1;
|
||||||
|
logger_on_rtt(rtt_ms);
|
||||||
|
logger_log("INFO", "client",
|
||||||
|
"time_sync_sample probe_id=%u rtt_ms=%llu offset_ms=%lld",
|
||||||
|
(unsigned)probe_id,
|
||||||
|
(unsigned long long)rtt_ms,
|
||||||
|
(long long)offset_ms);
|
||||||
|
if (rtt_ms < best_rtt_ms) {
|
||||||
|
best_rtt_ms = rtt_ms;
|
||||||
|
best_offset_ms = offset_ms;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
usleep(5 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic_store(&rt->sync_expected_probe_id, 0);
|
||||||
|
atomic_store(&rt->sync_expected_client_send_ts_ms, 0);
|
||||||
|
atomic_store(&rt->sync_reply_ready, 0);
|
||||||
|
|
||||||
|
if (!got_reply) {
|
||||||
|
logger_log("WARN", "client",
|
||||||
|
"time_sync_probe_timeout probe_id=%u timeout_ms=%u",
|
||||||
|
(unsigned)probe_id,
|
||||||
|
(unsigned)OMNI_TIME_SYNC_REPLY_TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sample_count == 0 || best_rtt_ms == UINT64_MAX) {
|
||||||
|
logger_log("WARN", "client", "time_sync_no_valid_sample");
|
||||||
|
return OMNI_ERR_IO;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_result->valid = 1;
|
||||||
|
out_result->server_minus_client_offset_ms = best_offset_ms;
|
||||||
|
out_result->best_rtt_ms = best_rtt_ms;
|
||||||
|
out_result->sample_count = sample_count;
|
||||||
|
|
||||||
|
{
|
||||||
|
TimeSyncReportMeta report_meta;
|
||||||
|
uint64_t report_ts_ms = omni_now_ms();
|
||||||
|
|
||||||
|
omni_time_sync_report_meta_encode(&report_meta,
|
||||||
|
best_offset_ms,
|
||||||
|
best_rtt_ms,
|
||||||
|
sample_count);
|
||||||
|
if (send_app_message_with_timestamp(ctx,
|
||||||
|
MSG_TYPE_TIME_SYNC_REPORT,
|
||||||
|
&report_meta,
|
||||||
|
TIME_SYNC_REPORT_META_SIZE,
|
||||||
|
report_ts_ms) != OMNI_OK) {
|
||||||
|
logger_log("ERROR", "client",
|
||||||
|
"time_sync_report_send_failed offset_ms=%lld best_rtt_ms=%llu",
|
||||||
|
(long long)best_offset_ms,
|
||||||
|
(unsigned long long)best_rtt_ms);
|
||||||
|
return OMNI_ERR_IO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger_log("INFO", "client",
|
||||||
|
"time_sync_selected offset_ms=%lld best_rtt_ms=%llu samples=%u",
|
||||||
|
(long long)best_offset_ms,
|
||||||
|
(unsigned long long)best_rtt_ms,
|
||||||
|
(unsigned)sample_count);
|
||||||
|
return OMNI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按需扩容“每秒发送多少片”的统计数组。 */
|
||||||
|
static int ensure_window_capacity(uint64_t **counts, size_t *cap, uint32_t window_id)
|
||||||
|
{
|
||||||
|
size_t need = (size_t)window_id + 1u;
|
||||||
|
size_t new_cap;
|
||||||
|
uint64_t *new_counts;
|
||||||
|
|
||||||
|
if (need <= *cap) {
|
||||||
|
return OMNI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_cap = (*cap == 0) ? 8u : *cap;
|
||||||
|
while (new_cap < need) {
|
||||||
|
new_cap *= 2u;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_counts = (uint64_t *)realloc(*counts, new_cap * sizeof(uint64_t));
|
||||||
|
if (!new_counts) {
|
||||||
|
return OMNI_ERR_GENERIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(new_counts + *cap, 0, (new_cap - *cap) * sizeof(uint64_t));
|
||||||
|
*counts = new_counts;
|
||||||
|
*cap = new_cap;
|
||||||
|
return OMNI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 将窗口分布统计转成 "0:12,1:15,3:9" 这类便于日志输出的字符串。 */
|
||||||
|
static char *format_window_distribution(const uint64_t *counts, uint32_t total_windows)
|
||||||
|
{
|
||||||
|
size_t cap = 256;
|
||||||
|
size_t len = 0;
|
||||||
|
char *buf = (char *)malloc(cap);
|
||||||
|
if (!buf) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
buf[0] = '\0';
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < total_windows; ++i) {
|
||||||
|
char tmp[64];
|
||||||
|
int n;
|
||||||
|
if (counts[i] == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
n = snprintf(tmp, sizeof(tmp), "%s%u:%llu",
|
||||||
|
(len == 0) ? "" : ",",
|
||||||
|
(unsigned)i,
|
||||||
|
(unsigned long long)counts[i]);
|
||||||
|
if (n <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
while (len + (size_t)n + 1 > cap) {
|
||||||
|
char *new_buf;
|
||||||
|
cap *= 2u;
|
||||||
|
new_buf = (char *)realloc(buf, cap);
|
||||||
|
if (!new_buf) {
|
||||||
|
free(buf);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
buf = new_buf;
|
||||||
|
}
|
||||||
|
memcpy(buf + len, tmp, (size_t)n);
|
||||||
|
len += (size_t)n;
|
||||||
|
buf[len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len == 0) {
|
||||||
|
snprintf(buf, cap, "none");
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通过保存/恢复文件指针位置计算总文件大小,不影响后续顺序读取。 */
|
||||||
|
static uint64_t compute_file_size(FILE *fp)
|
||||||
|
{
|
||||||
|
off_t cur = ftello(fp);
|
||||||
|
off_t end;
|
||||||
|
if (cur < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (fseeko(fp, 0, SEEK_END) != 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
end = ftello(fp);
|
||||||
|
(void)fseeko(fp, cur, SEEK_SET);
|
||||||
|
if (end < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (uint64_t)end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 计算百分比时统一处理分母为 0 的情况。 */
|
||||||
|
static double rate_percent(uint64_t numerator, uint64_t denominator)
|
||||||
|
{
|
||||||
|
if (denominator == 0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
return ((double)numerator * 100.0) / (double)denominator;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t saturating_sub_u64(uint64_t total, uint64_t delta)
|
||||||
|
{
|
||||||
|
return (total > delta) ? (total - delta) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 后台接收线程:
|
||||||
|
* - 接收服务端主动下发的命令
|
||||||
|
* - 接收传输结束 ACK,并把结果写入原子变量
|
||||||
|
*/
|
||||||
static void *recv_thread_main(void *arg)
|
static void *recv_thread_main(void *arg)
|
||||||
{
|
{
|
||||||
ClientRuntime *rt = (ClientRuntime *)arg;
|
ClientRuntime *rt = (ClientRuntime *)arg;
|
||||||
uint8_t frame[CLIENT_FRAME_BUF_SIZE];
|
uint8_t frame[CLIENT_FRAME_BUF_SIZE];
|
||||||
|
|
||||||
/*
|
/* 允许主线程在退出时 cancel 本线程。 */
|
||||||
* 显式启用可取消:主线程收尾时通过 pthread_cancel 打断阻塞 recv,
|
|
||||||
* 避免 UDP/KCP 场景下因长时间无回包导致 join 卡住。
|
|
||||||
*/
|
|
||||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||||
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
|
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
|
||||||
|
|
||||||
@@ -158,7 +402,6 @@ static void *recv_thread_main(void *arg)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (n == 0) {
|
if (n == 0) {
|
||||||
/* 0 在不同协议下可能代表“暂时无数据”或“对端关闭”,做短暂退避避免空转。 */
|
|
||||||
usleep(2 * 1000);
|
usleep(2 * 1000);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -172,15 +415,69 @@ static void *recv_thread_main(void *arg)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hdr.type == MSG_TYPE_COMMAND) {
|
if (hdr.type == MSG_TYPE_COMMAND) {
|
||||||
/* COMMAND 约定为 ASCII 文本,做安全截断后打印。 */
|
/* 命令消息只是做演示打印,不参与文件传输状态机。 */
|
||||||
char cmd[2048];
|
char cmd[2048];
|
||||||
size_t cpy = hdr.len < (uint32_t)(sizeof(cmd) - 1) ? hdr.len : (sizeof(cmd) - 1);
|
size_t cpy = hdr.len < (uint32_t)(sizeof(cmd) - 1) ? hdr.len : (sizeof(cmd) - 1);
|
||||||
memcpy(cmd, payload, cpy);
|
memcpy(cmd, payload, cpy);
|
||||||
cmd[cpy] = '\0';
|
cmd[cpy] = '\0';
|
||||||
printf("[server-cmd] %s\n", cmd);
|
printf("[server-cmd] %s\n", cmd);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
} else if (hdr.type == MSG_TYPE_TRANSFER_ACK) {
|
||||||
|
TransferAckMeta ack_meta;
|
||||||
|
uint64_t rtt_ms;
|
||||||
|
|
||||||
|
if (hdr.len < TRANSFER_ACK_META_SIZE) {
|
||||||
|
logger_log("WARN", "client", "short_transfer_ack len=%u", (unsigned)hdr.len);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
omni_transfer_ack_meta_decode((const TransferAckMeta *)payload, &ack_meta);
|
||||||
|
/* 服务端把 FILE_END 的发送时间原样回显回来,因此客户端可直接测一轮 ACK RTT。 */
|
||||||
|
rtt_ms = omni_now_ms() - ack_meta.echoed_end_ts_ms;
|
||||||
|
atomic_store(&rt->ack_received, 1);
|
||||||
|
atomic_store(&rt->ack_rtt_ms, rtt_ms);
|
||||||
|
atomic_store(&rt->ack_bytes_written, ack_meta.bytes_written);
|
||||||
|
atomic_store(&rt->ack_transfer_id, ack_meta.transfer_id);
|
||||||
|
logger_on_rtt(rtt_ms);
|
||||||
|
logger_log("INFO", "client",
|
||||||
|
"transfer_ack transfer_id=%u bytes_written=%llu rtt_ms=%llu",
|
||||||
|
(unsigned)ack_meta.transfer_id,
|
||||||
|
(unsigned long long)ack_meta.bytes_written,
|
||||||
|
(unsigned long long)rtt_ms);
|
||||||
|
} else if (hdr.type == MSG_TYPE_TIME_SYNC_RESP) {
|
||||||
|
TimeSyncReplyMeta reply_meta;
|
||||||
|
uint32_t expected_probe_id;
|
||||||
|
uint64_t expected_client_send_ts_ms;
|
||||||
|
uint64_t client_recv_ts_ms;
|
||||||
|
uint64_t rtt_ms;
|
||||||
|
int64_t offset_ms;
|
||||||
|
|
||||||
|
if (hdr.len < TIME_SYNC_REPLY_META_SIZE) {
|
||||||
|
logger_log("WARN", "client", "short_time_sync_resp len=%u", (unsigned)hdr.len);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
omni_time_sync_reply_meta_decode((const TimeSyncReplyMeta *)payload, &reply_meta);
|
||||||
|
expected_probe_id = atomic_load(&rt->sync_expected_probe_id);
|
||||||
|
expected_client_send_ts_ms = atomic_load(&rt->sync_expected_client_send_ts_ms);
|
||||||
|
if (expected_probe_id == 0 ||
|
||||||
|
reply_meta.probe_id != expected_probe_id ||
|
||||||
|
reply_meta.client_send_ts_ms != expected_client_send_ts_ms) {
|
||||||
|
logger_log("WARN", "client",
|
||||||
|
"unexpected_time_sync_resp probe_id=%u expected_probe=%u",
|
||||||
|
(unsigned)reply_meta.probe_id,
|
||||||
|
(unsigned)expected_probe_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
client_recv_ts_ms = omni_now_ms();
|
||||||
|
rtt_ms = client_recv_ts_ms - reply_meta.client_send_ts_ms;
|
||||||
|
offset_ms = compute_server_minus_client_offset_ms(&reply_meta,
|
||||||
|
client_recv_ts_ms);
|
||||||
|
atomic_store(&rt->sync_reply_rtt_ms, rtt_ms);
|
||||||
|
atomic_store(&rt->sync_reply_offset_ms, offset_ms);
|
||||||
|
atomic_store(&rt->sync_reply_ready, 1);
|
||||||
} else {
|
} else {
|
||||||
/* 客户端当前只消费 COMMAND,其它类型保留日志便于调试。 */
|
|
||||||
logger_log("INFO", "client",
|
logger_log("INFO", "client",
|
||||||
"recv_non_command type=%u len=%u",
|
"recv_non_command type=%u len=%u",
|
||||||
(unsigned)hdr.type, (unsigned)hdr.len);
|
(unsigned)hdr.type, (unsigned)hdr.len);
|
||||||
@@ -191,20 +488,45 @@ static void *recv_thread_main(void *arg)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 客户端主流程:
|
||||||
|
* 1) 解析参数并建立连接
|
||||||
|
* 2) 启动接收线程
|
||||||
|
* 3) 循环读取文件并分片发送
|
||||||
|
* 4) 发送 FILE_END
|
||||||
|
* 5) 等待 ACK 或超时,然后输出汇总
|
||||||
|
*/
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
install_signal_handlers();
|
|
||||||
|
|
||||||
/* 命令行参数默认值。 */
|
|
||||||
const char *proto_str = "tcp";
|
const char *proto_str = "tcp";
|
||||||
const char *server_ip = NULL;
|
const char *server_ip = NULL;
|
||||||
const char *file_path = NULL;
|
const char *file_path = NULL;
|
||||||
|
OmniProtocol proto;
|
||||||
|
FILE *fp = NULL;
|
||||||
|
OmniContext *ctx = NULL;
|
||||||
|
pthread_t recv_tid;
|
||||||
|
ClientRuntime rt;
|
||||||
|
uint8_t *chunk = NULL;
|
||||||
|
uint64_t *window_counts = NULL;
|
||||||
|
size_t window_cap = 0;
|
||||||
|
uint32_t total_windows = 0;
|
||||||
|
uint64_t total_bytes = 0;
|
||||||
|
uint32_t total_chunks = 0;
|
||||||
|
uint32_t transfer_id;
|
||||||
|
uint64_t total_sent = 0;
|
||||||
|
uint64_t offset = 0;
|
||||||
|
uint64_t transfer_start_send_ms = 0;
|
||||||
|
uint64_t file_end_send_ts_ms = 0;
|
||||||
|
ClockSyncResult clock_sync;
|
||||||
int server_port = 0;
|
int server_port = 0;
|
||||||
int bind_port = 0;
|
int bind_port = 0;
|
||||||
unsigned chunk_size = OMNI_DEFAULT_MTU;
|
unsigned chunk_size = OMNI_DEFAULT_MTU;
|
||||||
int wait_seconds = 2;
|
int wait_seconds = 2;
|
||||||
|
|
||||||
int opt;
|
int opt;
|
||||||
|
int exit_code = 0;
|
||||||
|
|
||||||
|
install_signal_handlers();
|
||||||
|
|
||||||
while ((opt = getopt(argc, argv, "p:H:P:f:b:m:w:")) != -1) {
|
while ((opt = getopt(argc, argv, "p:H:P:f:b:m:w:")) != -1) {
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 'p':
|
case 'p':
|
||||||
@@ -239,19 +561,22 @@ int main(int argc, char **argv)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (chunk_size == 0 || chunk_size > 65536u) {
|
if (chunk_size == 0 || chunk_size > 65536u) {
|
||||||
/* 约束 chunk 上限,避免一次申请/发送过大缓冲。 */
|
|
||||||
fprintf(stderr, "invalid chunk size: %u\n", chunk_size);
|
fprintf(stderr, "invalid chunk size: %u\n", chunk_size);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
FILE *fp = fopen(file_path, "rb");
|
|
||||||
|
fp = fopen(file_path, "rb");
|
||||||
if (!fp) {
|
if (!fp) {
|
||||||
perror("fopen");
|
perror("fopen");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
OmniProtocol proto = parse_proto(proto_str);
|
total_bytes = compute_file_size(fp);
|
||||||
/* 客户端角色:对端地址由 -H/-P 指定。 */
|
total_chunks = (chunk_size == 0) ? 0u :
|
||||||
OmniContext *ctx = omni_init(OMNI_ROLE_CLIENT, proto,
|
(uint32_t)((total_bytes + (uint64_t)chunk_size - 1u) / (uint64_t)chunk_size);
|
||||||
|
transfer_id = (uint32_t)((omni_now_ms() ^ (uint64_t)getpid() ^ total_bytes) & 0xffffffffu);
|
||||||
|
proto = parse_proto(proto_str);
|
||||||
|
ctx = omni_init(OMNI_ROLE_CLIENT, proto,
|
||||||
NULL, (uint16_t)bind_port,
|
NULL, (uint16_t)bind_port,
|
||||||
server_ip, (uint16_t)server_port);
|
server_ip, (uint16_t)server_port);
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
@@ -259,46 +584,59 @@ int main(int argc, char **argv)
|
|||||||
fprintf(stderr, "omni_init failed\n");
|
fprintf(stderr, "omni_init failed\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
logger_set_transfer_total(total_bytes);
|
||||||
|
logger_set_progress(0);
|
||||||
|
memset(&clock_sync, 0, sizeof(clock_sync));
|
||||||
|
|
||||||
ClientRuntime rt;
|
/* 接收线程与主线程共享一个 runtime 结构。 */
|
||||||
rt.ctx = ctx;
|
rt.ctx = ctx;
|
||||||
atomic_init(&rt.running, 1);
|
atomic_init(&rt.running, 1);
|
||||||
|
atomic_init(&rt.ack_received, 0);
|
||||||
|
atomic_init(&rt.ack_rtt_ms, 0);
|
||||||
|
atomic_init(&rt.ack_bytes_written, 0);
|
||||||
|
atomic_init(&rt.ack_transfer_id, 0);
|
||||||
|
atomic_init(&rt.sync_expected_probe_id, 0);
|
||||||
|
atomic_init(&rt.sync_expected_client_send_ts_ms, 0);
|
||||||
|
atomic_init(&rt.sync_reply_ready, 0);
|
||||||
|
atomic_init(&rt.sync_reply_rtt_ms, 0);
|
||||||
|
atomic_init(&rt.sync_reply_offset_ms, 0);
|
||||||
|
|
||||||
/* 启动异步接收线程(打印服务端指令)。 */
|
|
||||||
pthread_t recv_tid;
|
|
||||||
if (pthread_create(&recv_tid, NULL, recv_thread_main, &rt) != 0) {
|
if (pthread_create(&recv_tid, NULL, recv_thread_main, &rt) != 0) {
|
||||||
perror("pthread_create");
|
perror("pthread_create");
|
||||||
atomic_store(&rt.running, 0);
|
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
omni_close(ctx);
|
omni_close(ctx);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t *chunk = (uint8_t *)malloc(chunk_size);
|
if (perform_time_sync(ctx, &rt, &clock_sync) != OMNI_OK) {
|
||||||
|
logger_log("WARN", "client",
|
||||||
|
"time_sync_unavailable transfer_will_continue_without_compensated_server_metrics");
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk = (uint8_t *)malloc(chunk_size);
|
||||||
if (!chunk) {
|
if (!chunk) {
|
||||||
logger_log("ERROR", "client", "malloc_chunk_failed size=%u", chunk_size);
|
logger_log("ERROR", "client", "malloc_chunk_failed size=%u", chunk_size);
|
||||||
atomic_store(&rt.running, 0);
|
atomic_store(&rt.running, 0);
|
||||||
pthread_cancel(recv_tid);
|
pthread_cancel(recv_tid);
|
||||||
pthread_join(recv_tid, NULL);
|
pthread_join(recv_tid, NULL);
|
||||||
omni_close(ctx);
|
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
omni_close(ctx);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t total_sent = 0;
|
for (uint32_t seq = 1; atomic_load(&rt.running); ++seq) {
|
||||||
/*
|
uint64_t origin_ts_ms;
|
||||||
* 主发送循环:
|
size_t nread;
|
||||||
* - 每次读取 chunk_size 字节
|
|
||||||
* - 发送 FILE_CHUNK
|
|
||||||
* - EOF 后发送 FILE_END
|
|
||||||
*/
|
|
||||||
while (atomic_load(&rt.running)) {
|
|
||||||
if (g_stop) {
|
if (g_stop) {
|
||||||
logger_log("INFO", "client", "signal_received_stop_sending");
|
logger_log("INFO", "client", "signal_received_stop_sending");
|
||||||
atomic_store(&rt.running, 0);
|
atomic_store(&rt.running, 0);
|
||||||
|
exit_code = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
size_t nread = fread(chunk, 1, chunk_size, fp);
|
|
||||||
|
origin_ts_ms = omni_now_ms();
|
||||||
|
nread = fread(chunk, 1, chunk_size, fp);
|
||||||
if (nread == 0) {
|
if (nread == 0) {
|
||||||
if (feof(fp)) {
|
if (feof(fp)) {
|
||||||
break;
|
break;
|
||||||
@@ -306,53 +644,201 @@ int main(int argc, char **argv)
|
|||||||
if (ferror(fp)) {
|
if (ferror(fp)) {
|
||||||
logger_log("ERROR", "client", "fread_failed");
|
logger_log("ERROR", "client", "fread_failed");
|
||||||
atomic_store(&rt.running, 0);
|
atomic_store(&rt.running, 0);
|
||||||
|
exit_code = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nread > 0) {
|
if (nread > 0) {
|
||||||
int rc = send_app_message(ctx, MSG_TYPE_FILE_CHUNK, chunk, (uint32_t)nread);
|
uint64_t process_t0 = omni_now_ms();
|
||||||
if (rc != OMNI_OK) {
|
uint64_t send_ts_ms = omni_now_ms();
|
||||||
|
uint32_t window_id;
|
||||||
|
size_t payload_len = TRANSFER_CHUNK_META_SIZE + nread;
|
||||||
|
uint8_t *payload = (uint8_t *)malloc(payload_len);
|
||||||
|
TransferChunkMeta meta;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (!payload) {
|
||||||
|
logger_log("ERROR", "client", "malloc_payload_failed len=%zu", payload_len);
|
||||||
atomic_store(&rt.running, 0);
|
atomic_store(&rt.running, 0);
|
||||||
|
exit_code = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (transfer_start_send_ms == 0) {
|
||||||
|
transfer_start_send_ms = send_ts_ms;
|
||||||
|
}
|
||||||
|
/* 以“首次发送时间”为零点,将分片映射到按秒划分的发送窗口。 */
|
||||||
|
window_id = (uint32_t)((send_ts_ms - transfer_start_send_ms) / 1000u);
|
||||||
|
if (ensure_window_capacity(&window_counts, &window_cap, window_id) != OMNI_OK) {
|
||||||
|
free(payload);
|
||||||
|
logger_log("ERROR", "client", "window_counter_alloc_failed window=%u",
|
||||||
|
(unsigned)window_id);
|
||||||
|
atomic_store(&rt.running, 0);
|
||||||
|
exit_code = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
omni_transfer_chunk_meta_encode(&meta,
|
||||||
|
transfer_id,
|
||||||
|
seq,
|
||||||
|
total_chunks,
|
||||||
|
window_id,
|
||||||
|
total_bytes,
|
||||||
|
offset,
|
||||||
|
(uint32_t)nread,
|
||||||
|
origin_ts_ms);
|
||||||
|
/* payload = chunk 元数据 + 实际文件数据。 */
|
||||||
|
memcpy(payload, &meta, TRANSFER_CHUNK_META_SIZE);
|
||||||
|
memcpy(payload + TRANSFER_CHUNK_META_SIZE, chunk, nread);
|
||||||
|
logger_on_processing_latency((double)(omni_now_ms() - process_t0));
|
||||||
|
|
||||||
|
rc = send_app_message_with_timestamp(ctx, MSG_TYPE_FILE_CHUNK,
|
||||||
|
payload, (uint32_t)payload_len,
|
||||||
|
send_ts_ms);
|
||||||
|
free(payload);
|
||||||
|
|
||||||
|
if (rc != OMNI_OK) {
|
||||||
|
atomic_store(&rt.running, 0);
|
||||||
|
exit_code = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
window_counts[window_id]++;
|
||||||
|
if (window_id + 1u > total_windows) {
|
||||||
|
total_windows = window_id + 1u;
|
||||||
|
}
|
||||||
total_sent += nread;
|
total_sent += nread;
|
||||||
|
offset += nread;
|
||||||
|
logger_set_progress(total_sent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (atomic_load(&rt.running)) {
|
if (atomic_load(&rt.running)) {
|
||||||
/* 正常结束时发送 FILE_END,通知服务端落盘完成。 */
|
TransferEndMeta end_meta;
|
||||||
int rc = send_app_message(ctx, MSG_TYPE_FILE_END, NULL, 0);
|
file_end_send_ts_ms = omni_now_ms();
|
||||||
if (rc != OMNI_OK) {
|
/* FILE_END 表示“数据流已经发完”,不是文件内容本身。 */
|
||||||
|
omni_transfer_end_meta_encode(&end_meta,
|
||||||
|
transfer_id,
|
||||||
|
total_chunks,
|
||||||
|
total_bytes,
|
||||||
|
total_windows);
|
||||||
|
if (send_app_message_with_timestamp(ctx, MSG_TYPE_FILE_END,
|
||||||
|
&end_meta, TRANSFER_END_META_SIZE,
|
||||||
|
file_end_send_ts_ms) != OMNI_OK) {
|
||||||
atomic_store(&rt.running, 0);
|
atomic_store(&rt.running, 0);
|
||||||
|
exit_code = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger_log("INFO", "client", "file_transfer_done bytes=%llu",
|
logger_log("INFO", "client",
|
||||||
(unsigned long long)total_sent);
|
"file_transfer_done transfer_id=%u bytes=%llu total_chunks=%u",
|
||||||
free(chunk);
|
(unsigned)transfer_id,
|
||||||
fclose(fp);
|
(unsigned long long)total_sent,
|
||||||
|
(unsigned)total_chunks);
|
||||||
|
|
||||||
/*
|
|
||||||
* 等待模式:
|
|
||||||
* - wait_seconds >= 0: 发送完成后最多等待 N 秒
|
|
||||||
* - wait_seconds < 0 : 常驻模式,直到 Ctrl+C(SIGINT)或连接异常
|
|
||||||
*/
|
|
||||||
if (wait_seconds < 0) {
|
if (wait_seconds < 0) {
|
||||||
|
/* keepalive 模式:发送完成后不主动退出,便于继续观察控制消息。 */
|
||||||
logger_log("INFO", "client", "keepalive_mode=on press_ctrl_c_to_exit");
|
logger_log("INFO", "client", "keepalive_mode=on press_ctrl_c_to_exit");
|
||||||
while (atomic_load(&rt.running) && !g_stop) {
|
while (atomic_load(&rt.running) && !g_stop) {
|
||||||
sleep(1);
|
sleep(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
/* 普通模式:等待一小段时间给服务端回 ACK。 */
|
||||||
for (int i = 0; i < wait_seconds && atomic_load(&rt.running) && !g_stop; ++i) {
|
for (int i = 0; i < wait_seconds && atomic_load(&rt.running) && !g_stop; ++i) {
|
||||||
|
if (atomic_load(&rt.ack_received)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
sleep(1);
|
sleep(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 收尾顺序:先停接收线程,再关闭网络上下文。 */
|
{
|
||||||
|
/* 输出客户端视角的最终汇总,包括发送窗口分布和 ACK 结果。 */
|
||||||
|
OmniStats snapshot = logger_get_snapshot();
|
||||||
|
char *window_dist = format_window_distribution(window_counts, total_windows);
|
||||||
|
uint64_t tcp_original_bytes = saturating_sub_u64(snapshot.tcp_data_bytes_sent,
|
||||||
|
snapshot.tcp_retrans_bytes);
|
||||||
|
uint64_t kcp_original_bytes = saturating_sub_u64(snapshot.kcp_data_bytes_sent,
|
||||||
|
snapshot.kcp_retrans_bytes);
|
||||||
|
double tcp_retrans_rate = rate_percent(snapshot.tcp_retrans_bytes, tcp_original_bytes);
|
||||||
|
double kcp_retrans_rate = rate_percent(snapshot.kcp_retrans_bytes, kcp_original_bytes);
|
||||||
|
uint64_t ack_rtt_ms = atomic_load(&rt.ack_rtt_ms);
|
||||||
|
uint64_t ack_bytes_written = atomic_load(&rt.ack_bytes_written);
|
||||||
|
unsigned ack_received = (unsigned)atomic_load(&rt.ack_received);
|
||||||
|
|
||||||
|
logger_log("INFO", "summary",
|
||||||
|
"event=transfer_summary role=client proto=%s transfer_id=%u "
|
||||||
|
"total_bytes=%llu total_chunks=%u sent_bytes=%llu progress_bytes=%llu "
|
||||||
|
"tx_avg_mbps=%.3f tx_current_mbps=%.3f rx_avg_mbps=%.3f "
|
||||||
|
"processing_avg_ms=%.3f processing_max_ms=%.3f "
|
||||||
|
"queue_avg_ms=%.3f transmission_avg_ms=%.3f propagation_avg_ms=%.3f "
|
||||||
|
"last_rtt_ms=%llu min_rtt_ms=%llu "
|
||||||
|
"send_buffer_avg_pct=%.2f recv_buffer_avg_pct=%.2f "
|
||||||
|
"cwnd_avg=%.2f "
|
||||||
|
"tcp_retrans=%llu tcp_data_segs_out=%llu tcp_original_bytes=%llu "
|
||||||
|
"tcp_retrans_bytes=%llu tcp_retrans_rate_pct=%.2f "
|
||||||
|
"kcp_retrans=%llu kcp_data_segs_out=%llu kcp_original_bytes=%llu "
|
||||||
|
"kcp_retrans_bytes=%llu kcp_retrans_rate_pct=%.2f "
|
||||||
|
"send_windows=%u send_window_distribution=%s "
|
||||||
|
"ack_received=%u ack_rtt_ms=%llu ack_bytes_written=%llu "
|
||||||
|
"clock_sync_ok=%u clock_offset_ms=%lld clock_sync_rtt_ms=%llu clock_sync_samples=%u",
|
||||||
|
proto_str,
|
||||||
|
(unsigned)transfer_id,
|
||||||
|
(unsigned long long)total_bytes,
|
||||||
|
(unsigned)total_chunks,
|
||||||
|
(unsigned long long)total_sent,
|
||||||
|
(unsigned long long)snapshot.progress_bytes,
|
||||||
|
snapshot.tx_avg_mbps,
|
||||||
|
snapshot.tx_current_mbps,
|
||||||
|
snapshot.rx_avg_mbps,
|
||||||
|
(snapshot.processing_delay_ms.count == 0) ? 0.0 :
|
||||||
|
snapshot.processing_delay_ms.sum / (double)snapshot.processing_delay_ms.count,
|
||||||
|
snapshot.processing_delay_ms.max,
|
||||||
|
(snapshot.queue_delay_ms.count == 0) ? 0.0 :
|
||||||
|
snapshot.queue_delay_ms.sum / (double)snapshot.queue_delay_ms.count,
|
||||||
|
(snapshot.transmission_delay_ms.count == 0) ? 0.0 :
|
||||||
|
snapshot.transmission_delay_ms.sum / (double)snapshot.transmission_delay_ms.count,
|
||||||
|
(snapshot.propagation_delay_ms.count == 0) ? 0.0 :
|
||||||
|
snapshot.propagation_delay_ms.sum / (double)snapshot.propagation_delay_ms.count,
|
||||||
|
(unsigned long long)snapshot.last_rtt_ms,
|
||||||
|
(unsigned long long)((snapshot.min_rtt_ms == UINT64_MAX) ? 0 : snapshot.min_rtt_ms),
|
||||||
|
(snapshot.send_buffer_pct.count == 0) ? 0.0 :
|
||||||
|
snapshot.send_buffer_pct.sum / (double)snapshot.send_buffer_pct.count,
|
||||||
|
(snapshot.recv_buffer_pct.count == 0) ? 0.0 :
|
||||||
|
snapshot.recv_buffer_pct.sum / (double)snapshot.recv_buffer_pct.count,
|
||||||
|
(snapshot.cwnd.count == 0) ? 0.0 :
|
||||||
|
snapshot.cwnd.sum / (double)snapshot.cwnd.count,
|
||||||
|
(unsigned long long)snapshot.tcp_retrans,
|
||||||
|
(unsigned long long)snapshot.tcp_data_segs_out,
|
||||||
|
(unsigned long long)tcp_original_bytes,
|
||||||
|
(unsigned long long)snapshot.tcp_retrans_bytes,
|
||||||
|
tcp_retrans_rate,
|
||||||
|
(unsigned long long)snapshot.kcp_retrans,
|
||||||
|
(unsigned long long)snapshot.kcp_data_segs_out,
|
||||||
|
(unsigned long long)kcp_original_bytes,
|
||||||
|
(unsigned long long)snapshot.kcp_retrans_bytes,
|
||||||
|
kcp_retrans_rate,
|
||||||
|
(unsigned)total_windows,
|
||||||
|
window_dist ? window_dist : "alloc_failed",
|
||||||
|
ack_received,
|
||||||
|
(unsigned long long)ack_rtt_ms,
|
||||||
|
(unsigned long long)ack_bytes_written,
|
||||||
|
(unsigned)clock_sync.valid,
|
||||||
|
(long long)clock_sync.server_minus_client_offset_ms,
|
||||||
|
(unsigned long long)clock_sync.best_rtt_ms,
|
||||||
|
(unsigned)clock_sync.sample_count);
|
||||||
|
free(window_dist);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 主线程统一做收尾:停线程、释放缓冲、关闭文件和连接。 */
|
||||||
atomic_store(&rt.running, 0);
|
atomic_store(&rt.running, 0);
|
||||||
pthread_cancel(recv_tid);
|
pthread_cancel(recv_tid);
|
||||||
pthread_join(recv_tid, NULL);
|
pthread_join(recv_tid, NULL);
|
||||||
|
|
||||||
|
free(window_counts);
|
||||||
|
free(chunk);
|
||||||
|
fclose(fp);
|
||||||
omni_close(ctx);
|
omni_close(ctx);
|
||||||
return 0;
|
return exit_code;
|
||||||
}
|
}
|
||||||
|
|||||||
754
src/apps/hub_main.c
Normal file
754
src/apps/hub_main.c
Normal file
@@ -0,0 +1,754 @@
|
|||||||
|
/*
|
||||||
|
* hub_main.c
|
||||||
|
* 云端 hub:维护 client_id -> 连接 的映射,并负责 register / bind / tunnel 路由
|
||||||
|
*
|
||||||
|
* 当前阶段只实现 TCP 控制面:
|
||||||
|
* - 多个 peer 主动连接 hub
|
||||||
|
* - peer 先 REGISTER 自己的逻辑 ID
|
||||||
|
* - peer 可 BIND 默认目标
|
||||||
|
* - peer 发送 TUNNEL 后,hub 根据 dst_id 转发给目标
|
||||||
|
*
|
||||||
|
* 后续文件/视频消息可以直接复用 MSG_TYPE_PEER_TUNNEL 的 inner_type。
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define HUB_MAX_PAYLOAD (PEER_TUNNEL_META_SIZE + 65536u)
|
||||||
|
#define HUB_BACKLOG 64
|
||||||
|
|
||||||
|
typedef struct HubState HubState;
|
||||||
|
|
||||||
|
typedef struct HubClient {
|
||||||
|
HubState *hub;
|
||||||
|
int fd;
|
||||||
|
pthread_t tid;
|
||||||
|
pthread_mutex_t write_mu;
|
||||||
|
atomic_int running;
|
||||||
|
char client_id[OMNI_PEER_ID_SIZE];
|
||||||
|
char bound_peer[OMNI_PEER_ID_SIZE];
|
||||||
|
char remote_ip[64];
|
||||||
|
uint16_t remote_port;
|
||||||
|
struct HubClient *next;
|
||||||
|
} HubClient;
|
||||||
|
|
||||||
|
struct HubState {
|
||||||
|
int listen_fd;
|
||||||
|
atomic_int running;
|
||||||
|
pthread_mutex_t mu;
|
||||||
|
HubClient *clients;
|
||||||
|
};
|
||||||
|
|
||||||
|
static volatile sig_atomic_t g_stop = 0;
|
||||||
|
|
||||||
|
static void on_signal(int signo)
|
||||||
|
{
|
||||||
|
(void)signo;
|
||||||
|
g_stop = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void install_signal_handlers(void)
|
||||||
|
{
|
||||||
|
struct sigaction sa;
|
||||||
|
|
||||||
|
memset(&sa, 0, sizeof(sa));
|
||||||
|
sa.sa_handler = on_signal;
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
(void)sigaction(SIGINT, &sa, NULL);
|
||||||
|
(void)sigaction(SIGTERM, &sa, NULL);
|
||||||
|
(void)signal(SIGPIPE, SIG_IGN);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usage(const char *prog)
|
||||||
|
{
|
||||||
|
fprintf(stderr,
|
||||||
|
"Usage:\n"
|
||||||
|
" %s -P <listen_port> [-b <bind_ip>] [-p tcp]\n",
|
||||||
|
prog);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int peer_id_is_valid(const char *id)
|
||||||
|
{
|
||||||
|
size_t len = 0;
|
||||||
|
|
||||||
|
if (!id || !id[0]) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (len = 0; id[len] != '\0'; ++len) {
|
||||||
|
unsigned char ch = (unsigned char)id[len];
|
||||||
|
|
||||||
|
if (len + 1u >= OMNI_PEER_ID_SIZE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!(isalnum(ch) || ch == '_' || ch == '-' || ch == '.')) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *safe_client_id(const HubClient *client)
|
||||||
|
{
|
||||||
|
if (!client || client->client_id[0] == '\0') {
|
||||||
|
return "unregistered";
|
||||||
|
}
|
||||||
|
return client->client_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t read_n(int fd, void *buf, size_t n)
|
||||||
|
{
|
||||||
|
uint8_t *p = (uint8_t *)buf;
|
||||||
|
size_t done = 0;
|
||||||
|
|
||||||
|
while (done < n) {
|
||||||
|
ssize_t rc = recv(fd, p + done, n - done, 0);
|
||||||
|
if (rc == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (rc < 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
done += (size_t)rc;
|
||||||
|
}
|
||||||
|
return (ssize_t)done;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t write_n(int fd, const void *buf, size_t n)
|
||||||
|
{
|
||||||
|
const uint8_t *p = (const uint8_t *)buf;
|
||||||
|
size_t done = 0;
|
||||||
|
|
||||||
|
while (done < n) {
|
||||||
|
ssize_t rc = send(fd, p + done, n - done, 0);
|
||||||
|
if (rc < 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
done += (size_t)rc;
|
||||||
|
}
|
||||||
|
return (ssize_t)done;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int recv_app_message(int fd, MsgHeader *out_hdr, uint8_t *payload_buf, size_t payload_cap)
|
||||||
|
{
|
||||||
|
MsgHeader net_hdr;
|
||||||
|
ssize_t n;
|
||||||
|
|
||||||
|
if (!out_hdr || !payload_buf) {
|
||||||
|
return OMNI_ERR_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
n = read_n(fd, &net_hdr, MSG_HEADER_SIZE);
|
||||||
|
if (n == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (n != (ssize_t)MSG_HEADER_SIZE) {
|
||||||
|
return OMNI_ERR_IO;
|
||||||
|
}
|
||||||
|
|
||||||
|
omni_msg_header_decode(&net_hdr, out_hdr);
|
||||||
|
if (out_hdr->len > payload_cap) {
|
||||||
|
logger_log("ERROR", "hub", "payload_too_large len=%u cap=%zu",
|
||||||
|
(unsigned)out_hdr->len, payload_cap);
|
||||||
|
return OMNI_ERR_IO;
|
||||||
|
}
|
||||||
|
if (out_hdr->len == 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
n = read_n(fd, payload_buf, out_hdr->len);
|
||||||
|
if (n != (ssize_t)out_hdr->len) {
|
||||||
|
return OMNI_ERR_IO;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger_on_recv(MSG_HEADER_SIZE + out_hdr->len);
|
||||||
|
logger_maybe_print_performance_log("hub_recv");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int send_app_message_locked(HubClient *client,
|
||||||
|
uint32_t type,
|
||||||
|
const void *payload,
|
||||||
|
uint32_t payload_len)
|
||||||
|
{
|
||||||
|
MsgHeader hdr;
|
||||||
|
uint8_t header_buf[MSG_HEADER_SIZE];
|
||||||
|
|
||||||
|
if (!client) {
|
||||||
|
return OMNI_ERR_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
omni_msg_header_encode(&hdr, type, payload_len, omni_now_ms());
|
||||||
|
memcpy(header_buf, &hdr, sizeof(header_buf));
|
||||||
|
|
||||||
|
if (write_n(client->fd, header_buf, sizeof(header_buf)) != (ssize_t)sizeof(header_buf)) {
|
||||||
|
return OMNI_ERR_IO;
|
||||||
|
}
|
||||||
|
if (payload_len > 0 && payload) {
|
||||||
|
if (write_n(client->fd, payload, payload_len) != (ssize_t)payload_len) {
|
||||||
|
return OMNI_ERR_IO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger_on_send(MSG_HEADER_SIZE + payload_len);
|
||||||
|
logger_maybe_print_performance_log("hub_send");
|
||||||
|
return OMNI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int send_app_message(HubClient *client,
|
||||||
|
uint32_t type,
|
||||||
|
const void *payload,
|
||||||
|
uint32_t payload_len)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (!client) {
|
||||||
|
return OMNI_ERR_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&client->write_mu);
|
||||||
|
rc = send_app_message_locked(client, type, payload, payload_len);
|
||||||
|
pthread_mutex_unlock(&client->write_mu);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int send_status_locked(HubClient *client,
|
||||||
|
uint32_t code,
|
||||||
|
const char *self_id,
|
||||||
|
const char *peer_id,
|
||||||
|
const char *detail)
|
||||||
|
{
|
||||||
|
PeerStatusMeta status_meta;
|
||||||
|
|
||||||
|
omni_peer_status_meta_encode(&status_meta, code, self_id, peer_id, detail);
|
||||||
|
return send_app_message_locked(client,
|
||||||
|
MSG_TYPE_PEER_STATUS,
|
||||||
|
&status_meta,
|
||||||
|
PEER_STATUS_META_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int send_status(HubClient *client,
|
||||||
|
uint32_t code,
|
||||||
|
const char *self_id,
|
||||||
|
const char *peer_id,
|
||||||
|
const char *detail)
|
||||||
|
{
|
||||||
|
PeerStatusMeta status_meta;
|
||||||
|
|
||||||
|
omni_peer_status_meta_encode(&status_meta, code, self_id, peer_id, detail);
|
||||||
|
return send_app_message(client,
|
||||||
|
MSG_TYPE_PEER_STATUS,
|
||||||
|
&status_meta,
|
||||||
|
PEER_STATUS_META_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static HubClient *find_client_locked(HubState *hub, const char *client_id)
|
||||||
|
{
|
||||||
|
HubClient *cur;
|
||||||
|
|
||||||
|
for (cur = hub->clients; cur != NULL; cur = cur->next) {
|
||||||
|
if (cur->client_id[0] == '\0') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (strcmp(cur->client_id, client_id) == 0) {
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_client_locked(HubState *hub, HubClient *client)
|
||||||
|
{
|
||||||
|
client->next = hub->clients;
|
||||||
|
hub->clients = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void remove_client_locked(HubState *hub, HubClient *client)
|
||||||
|
{
|
||||||
|
HubClient **pp = &hub->clients;
|
||||||
|
|
||||||
|
while (*pp) {
|
||||||
|
if (*pp == client) {
|
||||||
|
*pp = client->next;
|
||||||
|
client->next = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pp = &(*pp)->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unregister_client(HubClient *client)
|
||||||
|
{
|
||||||
|
HubState *hub;
|
||||||
|
HubClient **notify = NULL;
|
||||||
|
size_t notify_count = 0;
|
||||||
|
size_t notify_cap = 0;
|
||||||
|
HubClient *cur;
|
||||||
|
char departed_id[OMNI_PEER_ID_SIZE];
|
||||||
|
|
||||||
|
if (!client || !client->hub) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hub = client->hub;
|
||||||
|
memset(departed_id, 0, sizeof(departed_id));
|
||||||
|
omni_copy_fixed_ascii(departed_id, sizeof(departed_id), client->client_id);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&hub->mu);
|
||||||
|
remove_client_locked(hub, client);
|
||||||
|
|
||||||
|
if (departed_id[0] != '\0') {
|
||||||
|
for (cur = hub->clients; cur != NULL; cur = cur->next) {
|
||||||
|
if (strcmp(cur->bound_peer, departed_id) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (notify_count == notify_cap) {
|
||||||
|
size_t new_cap = (notify_cap == 0) ? 4u : notify_cap * 2u;
|
||||||
|
HubClient **new_notify =
|
||||||
|
(HubClient **)realloc(notify, new_cap * sizeof(*new_notify));
|
||||||
|
if (!new_notify) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
notify = new_notify;
|
||||||
|
notify_cap = new_cap;
|
||||||
|
}
|
||||||
|
cur->bound_peer[0] = '\0';
|
||||||
|
pthread_mutex_lock(&cur->write_mu);
|
||||||
|
notify[notify_count++] = cur;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&hub->mu);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < notify_count; ++i) {
|
||||||
|
(void)send_status_locked(notify[i],
|
||||||
|
PEER_STATUS_UNBOUND,
|
||||||
|
notify[i]->client_id,
|
||||||
|
departed_id,
|
||||||
|
"peer_offline binding_cleared");
|
||||||
|
pthread_mutex_unlock(¬ify[i]->write_mu);
|
||||||
|
}
|
||||||
|
free(notify);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void close_client(HubClient *client)
|
||||||
|
{
|
||||||
|
if (!client) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&client->write_mu);
|
||||||
|
if (client->fd >= 0) {
|
||||||
|
shutdown(client->fd, SHUT_RDWR);
|
||||||
|
close(client->fd);
|
||||||
|
client->fd = -1;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&client->write_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle_register(HubClient *client, const uint8_t *payload, uint32_t payload_len)
|
||||||
|
{
|
||||||
|
PeerRegisterMeta register_meta;
|
||||||
|
char detail[128];
|
||||||
|
|
||||||
|
logger_log("DEBUG", "hub",
|
||||||
|
"handle_register remote=%s:%u payload_len=%u",
|
||||||
|
client->remote_ip,
|
||||||
|
(unsigned)client->remote_port,
|
||||||
|
(unsigned)payload_len);
|
||||||
|
|
||||||
|
if (payload_len < PEER_REGISTER_META_SIZE) {
|
||||||
|
return send_status(client, PEER_STATUS_ERROR, NULL, NULL, "short_register_payload");
|
||||||
|
}
|
||||||
|
|
||||||
|
omni_peer_register_meta_decode((const PeerRegisterMeta *)payload, ®ister_meta);
|
||||||
|
if (!peer_id_is_valid(register_meta.client_id)) {
|
||||||
|
return send_status(client, PEER_STATUS_ERROR, NULL, NULL, "invalid_client_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&client->hub->mu);
|
||||||
|
if (client->client_id[0] != '\0') {
|
||||||
|
pthread_mutex_unlock(&client->hub->mu);
|
||||||
|
return send_status(client, PEER_STATUS_ERROR, client->client_id, NULL, "already_registered");
|
||||||
|
}
|
||||||
|
if (find_client_locked(client->hub, register_meta.client_id) != NULL) {
|
||||||
|
pthread_mutex_unlock(&client->hub->mu);
|
||||||
|
return send_status(client, PEER_STATUS_ERROR, NULL, register_meta.client_id, "client_id_in_use");
|
||||||
|
}
|
||||||
|
omni_copy_fixed_ascii(client->client_id, sizeof(client->client_id), register_meta.client_id);
|
||||||
|
pthread_mutex_unlock(&client->hub->mu);
|
||||||
|
|
||||||
|
snprintf(detail, sizeof(detail), "registered remote=%s:%u",
|
||||||
|
client->remote_ip, (unsigned)client->remote_port);
|
||||||
|
logger_log("INFO", "hub",
|
||||||
|
"client_registered client_id=%s remote=%s:%u",
|
||||||
|
client->client_id,
|
||||||
|
client->remote_ip,
|
||||||
|
(unsigned)client->remote_port);
|
||||||
|
return send_status(client,
|
||||||
|
PEER_STATUS_REGISTERED,
|
||||||
|
client->client_id,
|
||||||
|
NULL,
|
||||||
|
detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle_bind(HubClient *client, const uint8_t *payload, uint32_t payload_len)
|
||||||
|
{
|
||||||
|
PeerBindMeta bind_meta;
|
||||||
|
HubClient *target;
|
||||||
|
|
||||||
|
logger_log("DEBUG", "hub",
|
||||||
|
"handle_bind client_id=%s payload_len=%u",
|
||||||
|
safe_client_id(client),
|
||||||
|
(unsigned)payload_len);
|
||||||
|
|
||||||
|
if (client->client_id[0] == '\0') {
|
||||||
|
return send_status(client, PEER_STATUS_ERROR, NULL, NULL, "register_first");
|
||||||
|
}
|
||||||
|
if (payload_len < PEER_BIND_META_SIZE) {
|
||||||
|
return send_status(client, PEER_STATUS_ERROR, client->client_id, NULL, "short_bind_payload");
|
||||||
|
}
|
||||||
|
|
||||||
|
omni_peer_bind_meta_decode((const PeerBindMeta *)payload, &bind_meta);
|
||||||
|
if (!peer_id_is_valid(bind_meta.peer_id)) {
|
||||||
|
return send_status(client, PEER_STATUS_ERROR, client->client_id, NULL, "invalid_peer_id");
|
||||||
|
}
|
||||||
|
if (strcmp(bind_meta.peer_id, client->client_id) == 0) {
|
||||||
|
return send_status(client, PEER_STATUS_ERROR, client->client_id, bind_meta.peer_id, "cannot_bind_self");
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&client->hub->mu);
|
||||||
|
target = find_client_locked(client->hub, bind_meta.peer_id);
|
||||||
|
if (!target) {
|
||||||
|
pthread_mutex_unlock(&client->hub->mu);
|
||||||
|
return send_status(client, PEER_STATUS_ERROR, client->client_id, bind_meta.peer_id, "peer_not_online");
|
||||||
|
}
|
||||||
|
omni_copy_fixed_ascii(client->bound_peer, sizeof(client->bound_peer), bind_meta.peer_id);
|
||||||
|
pthread_mutex_unlock(&client->hub->mu);
|
||||||
|
|
||||||
|
logger_log("INFO", "hub",
|
||||||
|
"peer_bound client_id=%s peer_id=%s",
|
||||||
|
client->client_id,
|
||||||
|
bind_meta.peer_id);
|
||||||
|
return send_status(client,
|
||||||
|
PEER_STATUS_BOUND,
|
||||||
|
client->client_id,
|
||||||
|
bind_meta.peer_id,
|
||||||
|
"bind_ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle_tunnel(HubClient *client, const uint8_t *payload, uint32_t payload_len)
|
||||||
|
{
|
||||||
|
PeerTunnelMeta tunnel_meta;
|
||||||
|
HubClient *target = NULL;
|
||||||
|
char effective_dst[OMNI_PEER_ID_SIZE];
|
||||||
|
uint8_t *forward_payload = NULL;
|
||||||
|
uint32_t inner_len;
|
||||||
|
int rc = OMNI_OK;
|
||||||
|
|
||||||
|
logger_log("DEBUG", "hub",
|
||||||
|
"handle_tunnel client_id=%s payload_len=%u",
|
||||||
|
safe_client_id(client),
|
||||||
|
(unsigned)payload_len);
|
||||||
|
|
||||||
|
if (client->client_id[0] == '\0') {
|
||||||
|
return send_status(client, PEER_STATUS_ERROR, NULL, NULL, "register_first");
|
||||||
|
}
|
||||||
|
if (payload_len < PEER_TUNNEL_META_SIZE) {
|
||||||
|
return send_status(client, PEER_STATUS_ERROR, client->client_id, NULL, "short_tunnel_payload");
|
||||||
|
}
|
||||||
|
|
||||||
|
omni_peer_tunnel_meta_decode((const PeerTunnelMeta *)payload, &tunnel_meta);
|
||||||
|
inner_len = payload_len - PEER_TUNNEL_META_SIZE;
|
||||||
|
memset(effective_dst, 0, sizeof(effective_dst));
|
||||||
|
|
||||||
|
pthread_mutex_lock(&client->hub->mu);
|
||||||
|
if (tunnel_meta.dst_id[0] != '\0') {
|
||||||
|
omni_copy_fixed_ascii(effective_dst, sizeof(effective_dst), tunnel_meta.dst_id);
|
||||||
|
} else {
|
||||||
|
omni_copy_fixed_ascii(effective_dst, sizeof(effective_dst), client->bound_peer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effective_dst[0] != '\0') {
|
||||||
|
target = find_client_locked(client->hub, effective_dst);
|
||||||
|
if (target) {
|
||||||
|
pthread_mutex_lock(&target->write_mu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&client->hub->mu);
|
||||||
|
|
||||||
|
if (!peer_id_is_valid(effective_dst)) {
|
||||||
|
return send_status(client, PEER_STATUS_ERROR, client->client_id, NULL, "missing_or_invalid_destination");
|
||||||
|
}
|
||||||
|
if (!target) {
|
||||||
|
return send_status(client, PEER_STATUS_ERROR, client->client_id, effective_dst, "destination_not_online");
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_payload = (uint8_t *)malloc(payload_len);
|
||||||
|
if (!forward_payload) {
|
||||||
|
pthread_mutex_unlock(&target->write_mu);
|
||||||
|
return send_status(client, PEER_STATUS_ERROR, client->client_id, effective_dst, "malloc_forward_payload_failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
PeerTunnelMeta forward_meta;
|
||||||
|
|
||||||
|
omni_peer_tunnel_meta_encode(&forward_meta,
|
||||||
|
client->client_id,
|
||||||
|
effective_dst,
|
||||||
|
tunnel_meta.inner_type);
|
||||||
|
memcpy(forward_payload, &forward_meta, PEER_TUNNEL_META_SIZE);
|
||||||
|
if (inner_len > 0) {
|
||||||
|
memcpy(forward_payload + PEER_TUNNEL_META_SIZE,
|
||||||
|
payload + PEER_TUNNEL_META_SIZE,
|
||||||
|
inner_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = send_app_message_locked(target,
|
||||||
|
MSG_TYPE_PEER_TUNNEL,
|
||||||
|
forward_payload,
|
||||||
|
payload_len);
|
||||||
|
pthread_mutex_unlock(&target->write_mu);
|
||||||
|
free(forward_payload);
|
||||||
|
|
||||||
|
if (rc != OMNI_OK) {
|
||||||
|
logger_log("ERROR", "hub",
|
||||||
|
"forward_failed src_id=%s dst_id=%s inner_type=%u",
|
||||||
|
client->client_id,
|
||||||
|
effective_dst,
|
||||||
|
(unsigned)tunnel_meta.inner_type);
|
||||||
|
return send_status(client, PEER_STATUS_ERROR, client->client_id, effective_dst, "forward_failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger_log("INFO", "hub",
|
||||||
|
"forward_ok src_id=%s dst_id=%s inner_type=%u payload_bytes=%u",
|
||||||
|
client->client_id,
|
||||||
|
effective_dst,
|
||||||
|
(unsigned)tunnel_meta.inner_type,
|
||||||
|
(unsigned)inner_len);
|
||||||
|
return OMNI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *client_thread_main(void *arg)
|
||||||
|
{
|
||||||
|
HubClient *client = (HubClient *)arg;
|
||||||
|
uint8_t payload[HUB_MAX_PAYLOAD];
|
||||||
|
|
||||||
|
while (atomic_load(&client->hub->running) && atomic_load(&client->running)) {
|
||||||
|
MsgHeader hdr;
|
||||||
|
int rc = recv_app_message(client->fd, &hdr, payload, sizeof(payload));
|
||||||
|
|
||||||
|
if (rc == 0) {
|
||||||
|
logger_log("INFO", "hub",
|
||||||
|
"client_closed client_id=%s remote=%s:%u",
|
||||||
|
safe_client_id(client),
|
||||||
|
client->remote_ip,
|
||||||
|
(unsigned)client->remote_port);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (rc < 0) {
|
||||||
|
logger_log("ERROR", "hub",
|
||||||
|
"client_recv_failed client_id=%s remote=%s:%u rc=%d",
|
||||||
|
safe_client_id(client),
|
||||||
|
client->remote_ip,
|
||||||
|
(unsigned)client->remote_port,
|
||||||
|
rc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger_log("DEBUG", "hub",
|
||||||
|
"message_recv client_id=%s type=%u len=%u",
|
||||||
|
safe_client_id(client),
|
||||||
|
(unsigned)hdr.type,
|
||||||
|
(unsigned)hdr.len);
|
||||||
|
|
||||||
|
switch (hdr.type) {
|
||||||
|
case MSG_TYPE_PEER_REGISTER:
|
||||||
|
(void)handle_register(client, payload, hdr.len);
|
||||||
|
break;
|
||||||
|
case MSG_TYPE_PEER_BIND:
|
||||||
|
(void)handle_bind(client, payload, hdr.len);
|
||||||
|
break;
|
||||||
|
case MSG_TYPE_PEER_TUNNEL:
|
||||||
|
(void)handle_tunnel(client, payload, hdr.len);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
(void)send_status(client,
|
||||||
|
PEER_STATUS_ERROR,
|
||||||
|
client->client_id[0] ? client->client_id : NULL,
|
||||||
|
NULL,
|
||||||
|
"unsupported_message_type");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic_store(&client->running, 0);
|
||||||
|
unregister_client(client);
|
||||||
|
close_client(client);
|
||||||
|
pthread_mutex_destroy(&client->write_mu);
|
||||||
|
free(client);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int create_listen_socket(const char *bind_ip, uint16_t port)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
int reuse = 1;
|
||||||
|
struct sockaddr_in addr;
|
||||||
|
|
||||||
|
fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (fd < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
||||||
|
memset(&addr, 0, sizeof(addr));
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_port = htons(port);
|
||||||
|
if (bind_ip && bind_ip[0] != '\0') {
|
||||||
|
if (inet_pton(AF_INET, bind_ip, &addr.sin_addr) != 1) {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (listen(fd, HUB_BACKLOG) != 0) {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
const char *bind_ip = NULL;
|
||||||
|
const char *proto_str = "tcp";
|
||||||
|
int listen_port = 0;
|
||||||
|
int opt;
|
||||||
|
HubState hub;
|
||||||
|
|
||||||
|
while ((opt = getopt(argc, argv, "b:p:P:")) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'b':
|
||||||
|
bind_ip = optarg;
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
proto_str = optarg;
|
||||||
|
break;
|
||||||
|
case 'P':
|
||||||
|
listen_port = atoi(optarg);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
usage(argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listen_port <= 0 || strcmp(proto_str, "tcp") != 0) {
|
||||||
|
usage(argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger_init();
|
||||||
|
install_signal_handlers();
|
||||||
|
|
||||||
|
memset(&hub, 0, sizeof(hub));
|
||||||
|
atomic_init(&hub.running, 1);
|
||||||
|
pthread_mutex_init(&hub.mu, NULL);
|
||||||
|
|
||||||
|
hub.listen_fd = create_listen_socket(bind_ip, (uint16_t)listen_port);
|
||||||
|
if (hub.listen_fd < 0) {
|
||||||
|
perror("hub listen");
|
||||||
|
pthread_mutex_destroy(&hub.mu);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger_log("INFO", "hub", "listening bind_ip=%s port=%u",
|
||||||
|
bind_ip ? bind_ip : "0.0.0.0",
|
||||||
|
(unsigned)listen_port);
|
||||||
|
|
||||||
|
while (atomic_load(&hub.running) && !g_stop) {
|
||||||
|
struct sockaddr_in peer_addr;
|
||||||
|
socklen_t peer_len = sizeof(peer_addr);
|
||||||
|
int cfd = accept(hub.listen_fd, (struct sockaddr *)&peer_addr, &peer_len);
|
||||||
|
|
||||||
|
if (cfd < 0) {
|
||||||
|
if (errno == EINTR && !g_stop) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (g_stop) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
perror("hub accept");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
HubClient *client = (HubClient *)calloc(1, sizeof(*client));
|
||||||
|
if (!client) {
|
||||||
|
close(cfd);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
client->hub = &hub;
|
||||||
|
client->fd = cfd;
|
||||||
|
atomic_init(&client->running, 1);
|
||||||
|
pthread_mutex_init(&client->write_mu, NULL);
|
||||||
|
if (!inet_ntop(AF_INET, &peer_addr.sin_addr, client->remote_ip, sizeof(client->remote_ip))) {
|
||||||
|
omni_copy_fixed_ascii(client->remote_ip, sizeof(client->remote_ip), "unknown");
|
||||||
|
}
|
||||||
|
client->remote_port = ntohs(peer_addr.sin_port);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&hub.mu);
|
||||||
|
add_client_locked(&hub, client);
|
||||||
|
pthread_mutex_unlock(&hub.mu);
|
||||||
|
|
||||||
|
if (pthread_create(&client->tid, NULL, client_thread_main, client) != 0) {
|
||||||
|
perror("hub pthread_create");
|
||||||
|
unregister_client(client);
|
||||||
|
close_client(client);
|
||||||
|
pthread_mutex_destroy(&client->write_mu);
|
||||||
|
free(client);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pthread_detach(client->tid);
|
||||||
|
|
||||||
|
logger_log("INFO", "hub",
|
||||||
|
"client_connected remote=%s:%u",
|
||||||
|
client->remote_ip,
|
||||||
|
(unsigned)client->remote_port);
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic_store(&hub.running, 0);
|
||||||
|
if (hub.listen_fd >= 0) {
|
||||||
|
close(hub.listen_fd);
|
||||||
|
}
|
||||||
|
logger_print_performance_log("final");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
1051
src/apps/peer_main.c
Normal file
1051
src/apps/peer_main.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#define RELAY_BUF_SIZE (MSG_HEADER_SIZE + 65536u)
|
#define RELAY_BUF_SIZE (MSG_HEADER_SIZE + TRANSFER_CHUNK_META_SIZE + 65536u)
|
||||||
|
|
||||||
typedef struct RelayState {
|
typedef struct RelayState {
|
||||||
/* 当前 relay 工作协议。 */
|
/* 当前 relay 工作协议。 */
|
||||||
@@ -53,6 +53,7 @@ static void usage(const char *prog)
|
|||||||
prog);
|
prog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 将命令行协议字符串映射为内部协议枚举。 */
|
||||||
static OmniProtocol parse_proto(const char *s)
|
static OmniProtocol parse_proto(const char *s)
|
||||||
{
|
{
|
||||||
/* 非法输入回退 TCP。 */
|
/* 非法输入回退 TCP。 */
|
||||||
@@ -63,6 +64,7 @@ static OmniProtocol parse_proto(const char *s)
|
|||||||
return OMNI_PROTO_TCP;
|
return OMNI_PROTO_TCP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 在运行期安全地切换下游转发目标。 */
|
||||||
static int relay_set_target(RelayState *st, const char *ip, uint16_t port)
|
static int relay_set_target(RelayState *st, const char *ip, uint16_t port)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@@ -95,6 +97,7 @@ static int relay_set_target(RelayState *st, const char *ip, uint16_t port)
|
|||||||
return OMNI_OK;
|
return OMNI_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 控制线程:解析 stdin 指令并驱动 show / set / quit。 */
|
||||||
static void *control_thread_main(void *arg)
|
static void *control_thread_main(void *arg)
|
||||||
{
|
{
|
||||||
/* 控制线程负责解析 stdin 命令。 */
|
/* 控制线程负责解析 stdin 命令。 */
|
||||||
@@ -153,6 +156,12 @@ static void *control_thread_main(void *arg)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* relay 主流程:
|
||||||
|
* - 上游用 server 角色收数据
|
||||||
|
* - 下游用 client 角色发数据
|
||||||
|
* - 主线程搬运数据,控制线程负责改目标
|
||||||
|
*/
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
/* 命令行参数默认值。 */
|
/* 命令行参数默认值。 */
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server_main.c
|
* server_main.c
|
||||||
* 服务端:接收文件写盘;主线程监听键盘输入并发送 ASCII 指令到客户端
|
* 服务端:接收文件写盘,输出结构化传输统计
|
||||||
*
|
|
||||||
* 线程模型:
|
|
||||||
* - 接收线程:持续收业务帧,写入文件,直到 FILE_END
|
|
||||||
* - 主线程:在交互终端下读取 stdin,发送 COMMAND 给客户端
|
|
||||||
*
|
|
||||||
* 说明:
|
|
||||||
* - 当 stdin 不是 TTY(例如被脚本后台拉起)时,主线程不做交互输入,
|
|
||||||
* 仅等待接收线程完成传输,便于自动化测试稳定运行。
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
@@ -22,23 +14,51 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#define SERVER_FRAME_BUF_SIZE (MSG_HEADER_SIZE + 65536u)
|
#define SERVER_FRAME_BUF_SIZE (MSG_HEADER_SIZE + TRANSFER_CHUNK_META_SIZE + 65536u)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 一次文件传输在服务端视角下的累计状态。
|
||||||
|
* 它主要用来回答三个问题:
|
||||||
|
* 1) 收到了多少唯一分片
|
||||||
|
* 2) 有没有重复/乱序/缺失
|
||||||
|
* 3) 最终写盘了多少字节
|
||||||
|
*/
|
||||||
|
typedef struct TransferState {
|
||||||
|
uint32_t transfer_id; /* 当前传输 ID。 */
|
||||||
|
uint32_t total_chunks; /* 期望收到的总分片数。 */
|
||||||
|
uint32_t total_windows; /* 发送端告诉我们的总窗口数。 */
|
||||||
|
uint64_t total_bytes; /* 期望收到的总字节数。 */
|
||||||
|
uint32_t highest_seq_seen; /* 目前见过的最大序号,用于判断乱序。 */
|
||||||
|
uint32_t unique_chunks; /* 成功去重后收到的分片数。 */
|
||||||
|
uint32_t duplicate_chunks; /* 重复到达的分片数。 */
|
||||||
|
uint32_t out_of_order_chunks; /* 序号回退的分片数,表示存在乱序。 */
|
||||||
|
uint64_t unique_bytes_written; /* 实际写盘的唯一数据量。 */
|
||||||
|
uint8_t *seen; /* 位图/标记数组:某个 seq 是否已收到。 */
|
||||||
|
size_t seen_cap; /* seen 数组容量。 */
|
||||||
|
uint64_t *recv_window_counts; /* 每个时间窗口收到的分片数。 */
|
||||||
|
size_t recv_window_cap; /* recv_window_counts 容量。 */
|
||||||
|
} TransferState;
|
||||||
|
|
||||||
|
typedef struct ClockSyncState {
|
||||||
|
int valid; /* 是否已经拿到客户端确认的 offset。 */
|
||||||
|
int64_t server_minus_client_offset_ms; /* server_time - client_time */
|
||||||
|
uint64_t best_rtt_ms; /* 客户端选中的最优 RTT 样本。 */
|
||||||
|
uint32_t sample_count; /* 参与选择的有效样本数。 */
|
||||||
|
} ClockSyncState;
|
||||||
|
|
||||||
|
/* 服务端运行期上下文:连接、写盘目标、线程状态和传输统计都放在这里。 */
|
||||||
typedef struct ServerRuntime {
|
typedef struct ServerRuntime {
|
||||||
/* 协议抽象层句柄。 */
|
|
||||||
OmniContext *ctx;
|
OmniContext *ctx;
|
||||||
/* 当前运行协议,用于区分 recv 返回 0 的语义(TCP=对端关闭)。 */
|
|
||||||
OmniProtocol proto;
|
OmniProtocol proto;
|
||||||
/* 接收文件写入目标。 */
|
|
||||||
FILE *out_fp;
|
FILE *out_fp;
|
||||||
/* 全局运行标记。 */
|
|
||||||
atomic_int running;
|
atomic_int running;
|
||||||
/* 收到 FILE_END 后置 1。 */
|
|
||||||
atomic_int transfer_done;
|
atomic_int transfer_done;
|
||||||
/* 已成功写入的文件字节数。 */
|
|
||||||
uint64_t bytes_written;
|
uint64_t bytes_written;
|
||||||
|
TransferState transfer;
|
||||||
|
ClockSyncState clock_sync;
|
||||||
} ServerRuntime;
|
} ServerRuntime;
|
||||||
|
|
||||||
|
/* 打印服务端命令行帮助。 */
|
||||||
static void usage(const char *prog)
|
static void usage(const char *prog)
|
||||||
{
|
{
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
@@ -47,9 +67,9 @@ static void usage(const char *prog)
|
|||||||
prog);
|
prog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 将字符串协议名解析为内部枚举,非法值默认回退 TCP。 */
|
||||||
static OmniProtocol parse_proto(const char *s)
|
static OmniProtocol parse_proto(const char *s)
|
||||||
{
|
{
|
||||||
/* 输入不合法时回退到 TCP。 */
|
|
||||||
if (!s) return OMNI_PROTO_TCP;
|
if (!s) return OMNI_PROTO_TCP;
|
||||||
if (strcmp(s, "tcp") == 0) return OMNI_PROTO_TCP;
|
if (strcmp(s, "tcp") == 0) return OMNI_PROTO_TCP;
|
||||||
if (strcmp(s, "udp") == 0) return OMNI_PROTO_UDP;
|
if (strcmp(s, "udp") == 0) return OMNI_PROTO_UDP;
|
||||||
@@ -57,12 +77,13 @@ static OmniProtocol parse_proto(const char *s)
|
|||||||
return OMNI_PROTO_TCP;
|
return OMNI_PROTO_TCP;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int send_app_message(OmniContext *ctx,
|
/* 与客户端对称:负责发送完整业务帧。 */
|
||||||
|
static int send_app_message_with_timestamp(OmniContext *ctx,
|
||||||
uint32_t type,
|
uint32_t type,
|
||||||
const void *payload,
|
const void *payload,
|
||||||
uint32_t payload_len)
|
uint32_t payload_len,
|
||||||
|
uint64_t timestamp_ms)
|
||||||
{
|
{
|
||||||
/* 与客户端保持一致的统一发包函数。 */
|
|
||||||
size_t total_len = MSG_HEADER_SIZE + (size_t)payload_len;
|
size_t total_len = MSG_HEADER_SIZE + (size_t)payload_len;
|
||||||
uint8_t *frame = (uint8_t *)malloc(total_len);
|
uint8_t *frame = (uint8_t *)malloc(total_len);
|
||||||
if (!frame) {
|
if (!frame) {
|
||||||
@@ -71,7 +92,7 @@ static int send_app_message(OmniContext *ctx,
|
|||||||
}
|
}
|
||||||
|
|
||||||
MsgHeader hdr;
|
MsgHeader hdr;
|
||||||
omni_msg_header_encode(&hdr, type, payload_len, omni_now_ms());
|
omni_msg_header_encode(&hdr, type, payload_len, timestamp_ms);
|
||||||
memcpy(frame, &hdr, MSG_HEADER_SIZE);
|
memcpy(frame, &hdr, MSG_HEADER_SIZE);
|
||||||
if (payload_len > 0 && payload) {
|
if (payload_len > 0 && payload) {
|
||||||
memcpy(frame + MSG_HEADER_SIZE, payload, payload_len);
|
memcpy(frame + MSG_HEADER_SIZE, payload, payload_len);
|
||||||
@@ -89,12 +110,12 @@ static int send_app_message(OmniContext *ctx,
|
|||||||
return OMNI_OK;
|
return OMNI_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 与客户端对称:拆包并校验应用层帧长度。 */
|
||||||
static int decode_app_message(const uint8_t *frame,
|
static int decode_app_message(const uint8_t *frame,
|
||||||
size_t frame_len,
|
size_t frame_len,
|
||||||
MsgHeader *out_hdr,
|
MsgHeader *out_hdr,
|
||||||
const uint8_t **out_payload)
|
const uint8_t **out_payload)
|
||||||
{
|
{
|
||||||
/* 与客户端一致的统一解包校验。 */
|
|
||||||
if (!frame || frame_len < MSG_HEADER_SIZE || !out_hdr || !out_payload) {
|
if (!frame || frame_len < MSG_HEADER_SIZE || !out_hdr || !out_payload) {
|
||||||
return OMNI_ERR_PARAM;
|
return OMNI_ERR_PARAM;
|
||||||
}
|
}
|
||||||
@@ -111,12 +132,463 @@ static int decode_app_message(const uint8_t *frame,
|
|||||||
return OMNI_OK;
|
return OMNI_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 用客户端确认的 offset,把 origin_ts_ms 转成服务端时钟系并计算端到端时延。 */
|
||||||
|
static double compute_compensated_end_to_end_ms(const ServerRuntime *rt,
|
||||||
|
uint64_t server_recv_ts_ms,
|
||||||
|
const TransferChunkMeta *meta)
|
||||||
|
{
|
||||||
|
int64_t e2e_ms;
|
||||||
|
|
||||||
|
if (!rt || !meta || !rt->clock_sync.valid) {
|
||||||
|
return -1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
e2e_ms = (int64_t)server_recv_ts_ms -
|
||||||
|
((int64_t)meta->origin_ts_ms + rt->clock_sync.server_minus_client_offset_ms);
|
||||||
|
if (e2e_ms < 0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
return (double)e2e_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 扩容 seen 标记数组,下标直接使用 seq 值。 */
|
||||||
|
static int ensure_seen_capacity(TransferState *st, uint32_t total_chunks)
|
||||||
|
{
|
||||||
|
size_t need = (size_t)total_chunks + 1u;
|
||||||
|
uint8_t *new_seen;
|
||||||
|
|
||||||
|
if (need <= st->seen_cap) {
|
||||||
|
return OMNI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_seen = (uint8_t *)realloc(st->seen, need);
|
||||||
|
if (!new_seen) {
|
||||||
|
return OMNI_ERR_GENERIC;
|
||||||
|
}
|
||||||
|
memset(new_seen + st->seen_cap, 0, need - st->seen_cap);
|
||||||
|
st->seen = new_seen;
|
||||||
|
st->seen_cap = need;
|
||||||
|
return OMNI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 扩容按窗口聚合的接收计数数组。 */
|
||||||
|
static int ensure_window_capacity(uint64_t **counts, size_t *cap, uint32_t window_id)
|
||||||
|
{
|
||||||
|
size_t need = (size_t)window_id + 1u;
|
||||||
|
size_t new_cap;
|
||||||
|
uint64_t *new_counts;
|
||||||
|
|
||||||
|
if (need <= *cap) {
|
||||||
|
return OMNI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_cap = (*cap == 0) ? 8u : *cap;
|
||||||
|
while (new_cap < need) {
|
||||||
|
new_cap *= 2u;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_counts = (uint64_t *)realloc(*counts, new_cap * sizeof(uint64_t));
|
||||||
|
if (!new_counts) {
|
||||||
|
return OMNI_ERR_GENERIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(new_counts + *cap, 0, (new_cap - *cap) * sizeof(uint64_t));
|
||||||
|
*counts = new_counts;
|
||||||
|
*cap = new_cap;
|
||||||
|
return OMNI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 将接收窗口分布转成日志友好的字符串。 */
|
||||||
|
static char *format_window_distribution(const uint64_t *counts, uint32_t total_windows)
|
||||||
|
{
|
||||||
|
size_t cap = 256;
|
||||||
|
size_t len = 0;
|
||||||
|
char *buf = (char *)malloc(cap);
|
||||||
|
if (!buf) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
buf[0] = '\0';
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < total_windows; ++i) {
|
||||||
|
char tmp[64];
|
||||||
|
int n;
|
||||||
|
if (counts[i] == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
n = snprintf(tmp, sizeof(tmp), "%s%u:%llu",
|
||||||
|
(len == 0) ? "" : ",",
|
||||||
|
(unsigned)i,
|
||||||
|
(unsigned long long)counts[i]);
|
||||||
|
if (n <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
while (len + (size_t)n + 1 > cap) {
|
||||||
|
char *new_buf;
|
||||||
|
cap *= 2u;
|
||||||
|
new_buf = (char *)realloc(buf, cap);
|
||||||
|
if (!new_buf) {
|
||||||
|
free(buf);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
buf = new_buf;
|
||||||
|
}
|
||||||
|
memcpy(buf + len, tmp, (size_t)n);
|
||||||
|
len += (size_t)n;
|
||||||
|
buf[len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len == 0) {
|
||||||
|
snprintf(buf, cap, "none");
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t saturating_sub_u64(uint64_t total, uint64_t delta)
|
||||||
|
{
|
||||||
|
return (total > delta) ? (total - delta) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double rate_percent(uint64_t numerator, uint64_t denominator)
|
||||||
|
{
|
||||||
|
if (denominator == 0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
return ((double)numerator * 100.0) / (double)denominator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 根据 seen 位图生成缺失区间字符串,例如 "3-5,9,12-14"。
|
||||||
|
* 同时顺手统计缺失 chunk 数、突发丢包次数和最长突发长度。
|
||||||
|
*/
|
||||||
|
static char *format_missing_ranges(const TransferState *st,
|
||||||
|
uint32_t *out_missing_chunks,
|
||||||
|
uint32_t *out_burst_count,
|
||||||
|
uint32_t *out_max_burst_len)
|
||||||
|
{
|
||||||
|
size_t cap = 256;
|
||||||
|
size_t len = 0;
|
||||||
|
char *buf = (char *)malloc(cap);
|
||||||
|
uint32_t missing_chunks = 0;
|
||||||
|
uint32_t burst_count = 0;
|
||||||
|
uint32_t max_burst_len = 0;
|
||||||
|
|
||||||
|
if (!buf) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
buf[0] = '\0';
|
||||||
|
|
||||||
|
if (st->total_chunks == 0 || !st->seen) {
|
||||||
|
snprintf(buf, cap, "none");
|
||||||
|
if (out_missing_chunks) *out_missing_chunks = 0;
|
||||||
|
if (out_burst_count) *out_burst_count = 0;
|
||||||
|
if (out_max_burst_len) *out_max_burst_len = 0;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t seq = 1; seq <= st->total_chunks; ++seq) {
|
||||||
|
if (st->seen[seq] != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t start = seq;
|
||||||
|
uint32_t end = seq;
|
||||||
|
char tmp[64];
|
||||||
|
int n;
|
||||||
|
|
||||||
|
/* 把连续缺失的 seq 合并成一个区间,便于读日志时看出 burst loss。 */
|
||||||
|
while (end + 1u <= st->total_chunks && st->seen[end + 1u] == 0) {
|
||||||
|
end++;
|
||||||
|
}
|
||||||
|
|
||||||
|
missing_chunks += (end - start + 1u);
|
||||||
|
burst_count++;
|
||||||
|
if ((end - start + 1u) > max_burst_len) {
|
||||||
|
max_burst_len = end - start + 1u;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start == end) {
|
||||||
|
n = snprintf(tmp, sizeof(tmp), "%s%u",
|
||||||
|
(len == 0) ? "" : ",",
|
||||||
|
(unsigned)start);
|
||||||
|
} else {
|
||||||
|
n = snprintf(tmp, sizeof(tmp), "%s%u-%u",
|
||||||
|
(len == 0) ? "" : ",",
|
||||||
|
(unsigned)start,
|
||||||
|
(unsigned)end);
|
||||||
|
}
|
||||||
|
if (n > 0) {
|
||||||
|
while (len + (size_t)n + 1 > cap) {
|
||||||
|
char *new_buf;
|
||||||
|
cap *= 2u;
|
||||||
|
new_buf = (char *)realloc(buf, cap);
|
||||||
|
if (!new_buf) {
|
||||||
|
free(buf);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
buf = new_buf;
|
||||||
|
}
|
||||||
|
memcpy(buf + len, tmp, (size_t)n);
|
||||||
|
len += (size_t)n;
|
||||||
|
buf[len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
seq = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len == 0) {
|
||||||
|
snprintf(buf, cap, "none");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out_missing_chunks) *out_missing_chunks = missing_chunks;
|
||||||
|
if (out_burst_count) *out_burst_count = burst_count;
|
||||||
|
if (out_max_burst_len) *out_max_burst_len = max_burst_len;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 用首个有效分片补齐传输的基础元数据,后续分片只做一致性延续。 */
|
||||||
|
static void transfer_state_init(TransferState *st, const TransferChunkMeta *meta)
|
||||||
|
{
|
||||||
|
if (st->transfer_id == 0) {
|
||||||
|
st->transfer_id = meta->transfer_id;
|
||||||
|
}
|
||||||
|
if (st->total_chunks == 0) {
|
||||||
|
st->total_chunks = meta->total_chunks;
|
||||||
|
}
|
||||||
|
if (st->total_bytes == 0) {
|
||||||
|
st->total_bytes = meta->total_bytes;
|
||||||
|
}
|
||||||
|
if (meta->window_id + 1u > st->total_windows) {
|
||||||
|
st->total_windows = meta->window_id + 1u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 处理一个 FILE_CHUNK:
|
||||||
|
* - 校验元数据
|
||||||
|
* - 去重 / 统计乱序
|
||||||
|
* - 估算时延拆分
|
||||||
|
* - 按 offset 随机写盘
|
||||||
|
*/
|
||||||
|
static int server_record_chunk(ServerRuntime *rt,
|
||||||
|
const MsgHeader *hdr,
|
||||||
|
const uint8_t *payload,
|
||||||
|
uint32_t payload_len)
|
||||||
|
{
|
||||||
|
TransferChunkMeta meta;
|
||||||
|
const uint8_t *chunk_data;
|
||||||
|
uint64_t process_t0;
|
||||||
|
|
||||||
|
(void)hdr;
|
||||||
|
|
||||||
|
if (payload_len < TRANSFER_CHUNK_META_SIZE) {
|
||||||
|
logger_log("ERROR", "server", "short_chunk_payload len=%u", (unsigned)payload_len);
|
||||||
|
return OMNI_ERR_IO;
|
||||||
|
}
|
||||||
|
|
||||||
|
process_t0 = omni_now_ms();
|
||||||
|
omni_transfer_chunk_meta_decode((const TransferChunkMeta *)payload, &meta);
|
||||||
|
if (meta.chunk_bytes > payload_len - TRANSFER_CHUNK_META_SIZE) {
|
||||||
|
logger_log("ERROR", "server",
|
||||||
|
"invalid_chunk_meta transfer_id=%u seq=%u chunk_bytes=%u payload_len=%u",
|
||||||
|
(unsigned)meta.transfer_id,
|
||||||
|
(unsigned)meta.seq,
|
||||||
|
(unsigned)meta.chunk_bytes,
|
||||||
|
(unsigned)payload_len);
|
||||||
|
return OMNI_ERR_IO;
|
||||||
|
}
|
||||||
|
|
||||||
|
transfer_state_init(&rt->transfer, &meta);
|
||||||
|
if (ensure_seen_capacity(&rt->transfer, meta.total_chunks) != OMNI_OK) {
|
||||||
|
logger_log("ERROR", "server", "seen_bitmap_alloc_failed total_chunks=%u",
|
||||||
|
(unsigned)meta.total_chunks);
|
||||||
|
return OMNI_ERR_GENERIC;
|
||||||
|
}
|
||||||
|
if (ensure_window_capacity(&rt->transfer.recv_window_counts,
|
||||||
|
&rt->transfer.recv_window_cap,
|
||||||
|
meta.window_id) != OMNI_OK) {
|
||||||
|
logger_log("ERROR", "server", "recv_window_alloc_failed window=%u",
|
||||||
|
(unsigned)meta.window_id);
|
||||||
|
return OMNI_ERR_GENERIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (meta.seq == 0 || meta.seq > meta.total_chunks) {
|
||||||
|
logger_log("WARN", "server", "invalid_seq transfer_id=%u seq=%u total_chunks=%u",
|
||||||
|
(unsigned)meta.transfer_id,
|
||||||
|
(unsigned)meta.seq,
|
||||||
|
(unsigned)meta.total_chunks);
|
||||||
|
return OMNI_ERR_IO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* seen 位图用于去重:重复片不再写盘,但会计入 duplicate_chunks。 */
|
||||||
|
if (rt->transfer.seen[meta.seq] != 0) {
|
||||||
|
rt->transfer.duplicate_chunks++;
|
||||||
|
return OMNI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
rt->transfer.seen[meta.seq] = 1;
|
||||||
|
rt->transfer.unique_chunks++;
|
||||||
|
rt->transfer.recv_window_counts[meta.window_id]++;
|
||||||
|
/* 若当前序号小于历史最大序号,说明该片到达顺序发生了回退。 */
|
||||||
|
if (meta.seq < rt->transfer.highest_seq_seen) {
|
||||||
|
rt->transfer.out_of_order_chunks++;
|
||||||
|
}
|
||||||
|
if (meta.seq > rt->transfer.highest_seq_seen) {
|
||||||
|
rt->transfer.highest_seq_seen = meta.seq;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
double e2e_ms = compute_compensated_end_to_end_ms(rt, process_t0, &meta);
|
||||||
|
if (e2e_ms >= 0.0) {
|
||||||
|
logger_on_end_to_end_latency(e2e_ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger_set_transfer_total(meta.total_bytes);
|
||||||
|
chunk_data = payload + TRANSFER_CHUNK_META_SIZE;
|
||||||
|
/*
|
||||||
|
* 必须按 offset 写盘,而不是简单 append。
|
||||||
|
* 这样即便 UDP/KCP 发生乱序,输出文件仍能恢复到正确位置。
|
||||||
|
*/
|
||||||
|
if (fseeko(rt->out_fp, (off_t)meta.offset_bytes, SEEK_SET) != 0) {
|
||||||
|
logger_log("ERROR", "server",
|
||||||
|
"fseeko_failed transfer_id=%u seq=%u offset=%llu",
|
||||||
|
(unsigned)meta.transfer_id,
|
||||||
|
(unsigned)meta.seq,
|
||||||
|
(unsigned long long)meta.offset_bytes);
|
||||||
|
return OMNI_ERR_IO;
|
||||||
|
}
|
||||||
|
if (fwrite(chunk_data, 1, meta.chunk_bytes, rt->out_fp) != meta.chunk_bytes) {
|
||||||
|
logger_log("ERROR", "server",
|
||||||
|
"fwrite_failed transfer_id=%u seq=%u chunk_bytes=%u",
|
||||||
|
(unsigned)meta.transfer_id,
|
||||||
|
(unsigned)meta.seq,
|
||||||
|
(unsigned)meta.chunk_bytes);
|
||||||
|
return OMNI_ERR_IO;
|
||||||
|
}
|
||||||
|
logger_on_processing_latency((double)(omni_now_ms() - process_t0));
|
||||||
|
|
||||||
|
rt->transfer.unique_bytes_written += meta.chunk_bytes;
|
||||||
|
rt->bytes_written = rt->transfer.unique_bytes_written;
|
||||||
|
logger_set_progress(rt->bytes_written);
|
||||||
|
return OMNI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 输出服务端视角的最终汇总,包括丢包区间、乱序和时延统计。 */
|
||||||
|
static void server_log_transfer_summary(ServerRuntime *rt)
|
||||||
|
{
|
||||||
|
OmniStats snapshot = logger_get_snapshot();
|
||||||
|
char *missing_ranges = NULL;
|
||||||
|
char *recv_window_dist = NULL;
|
||||||
|
uint32_t missing_chunks = 0;
|
||||||
|
uint32_t burst_count = 0;
|
||||||
|
uint32_t max_burst_len = 0;
|
||||||
|
double loss_rate = 0.0;
|
||||||
|
double tcp_retrans_rate = 0.0;
|
||||||
|
double kcp_retrans_rate = 0.0;
|
||||||
|
uint64_t tcp_original_bytes = saturating_sub_u64(snapshot.tcp_data_bytes_sent,
|
||||||
|
snapshot.tcp_retrans_bytes);
|
||||||
|
uint64_t kcp_original_bytes = saturating_sub_u64(snapshot.kcp_data_bytes_sent,
|
||||||
|
snapshot.kcp_retrans_bytes);
|
||||||
|
|
||||||
|
missing_ranges = format_missing_ranges(&rt->transfer,
|
||||||
|
&missing_chunks,
|
||||||
|
&burst_count,
|
||||||
|
&max_burst_len);
|
||||||
|
recv_window_dist = format_window_distribution(rt->transfer.recv_window_counts,
|
||||||
|
rt->transfer.total_windows);
|
||||||
|
if (rt->transfer.total_chunks > 0) {
|
||||||
|
loss_rate = ((double)missing_chunks * 100.0) / (double)rt->transfer.total_chunks;
|
||||||
|
}
|
||||||
|
tcp_retrans_rate = rate_percent(snapshot.tcp_retrans_bytes, tcp_original_bytes);
|
||||||
|
kcp_retrans_rate = rate_percent(snapshot.kcp_retrans_bytes, kcp_original_bytes);
|
||||||
|
|
||||||
|
logger_log("INFO", "summary",
|
||||||
|
"event=transfer_summary role=server proto=%d transfer_id=%u "
|
||||||
|
"expected_bytes=%llu written_bytes=%llu expected_chunks=%u unique_chunks=%u "
|
||||||
|
"duplicate_chunks=%u out_of_order_chunks=%u "
|
||||||
|
"loss_rate_pct=%.2f missing_chunks=%u missing_ranges=%s "
|
||||||
|
"burst_loss_count=%u max_burst_loss_len=%u recv_windows=%u recv_window_distribution=%s "
|
||||||
|
"rx_avg_mbps=%.3f rx_current_mbps=%.3f "
|
||||||
|
"processing_avg_ms=%.3f queue_avg_ms=%.3f transmission_avg_ms=%.3f "
|
||||||
|
"propagation_avg_ms=%.3f end_to_end_avg_ms=%.3f "
|
||||||
|
"send_buffer_avg_pct=%.2f recv_buffer_avg_pct=%.2f "
|
||||||
|
"cwnd_avg=%.2f "
|
||||||
|
"local_tcp_retrans=%llu local_tcp_data_segs_out=%llu local_tcp_original_bytes=%llu "
|
||||||
|
"local_tcp_retrans_bytes=%llu local_tcp_retrans_rate_pct=%.2f "
|
||||||
|
"local_kcp_retrans=%llu local_kcp_data_segs_out=%llu local_kcp_original_bytes=%llu "
|
||||||
|
"local_kcp_retrans_bytes=%llu local_kcp_retrans_rate_pct=%.2f "
|
||||||
|
"clock_sync_ok=%u clock_offset_ms=%lld clock_sync_rtt_ms=%llu clock_sync_samples=%u",
|
||||||
|
(int)rt->proto,
|
||||||
|
(unsigned)rt->transfer.transfer_id,
|
||||||
|
(unsigned long long)rt->transfer.total_bytes,
|
||||||
|
(unsigned long long)rt->bytes_written,
|
||||||
|
(unsigned)rt->transfer.total_chunks,
|
||||||
|
(unsigned)rt->transfer.unique_chunks,
|
||||||
|
(unsigned)rt->transfer.duplicate_chunks,
|
||||||
|
(unsigned)rt->transfer.out_of_order_chunks,
|
||||||
|
loss_rate,
|
||||||
|
(unsigned)missing_chunks,
|
||||||
|
missing_ranges ? missing_ranges : "alloc_failed",
|
||||||
|
(unsigned)burst_count,
|
||||||
|
(unsigned)max_burst_len,
|
||||||
|
(unsigned)rt->transfer.total_windows,
|
||||||
|
recv_window_dist ? recv_window_dist : "alloc_failed",
|
||||||
|
snapshot.rx_avg_mbps,
|
||||||
|
snapshot.rx_current_mbps,
|
||||||
|
(snapshot.processing_delay_ms.count == 0) ? 0.0 :
|
||||||
|
snapshot.processing_delay_ms.sum / (double)snapshot.processing_delay_ms.count,
|
||||||
|
(snapshot.queue_delay_ms.count == 0) ? 0.0 :
|
||||||
|
snapshot.queue_delay_ms.sum / (double)snapshot.queue_delay_ms.count,
|
||||||
|
(snapshot.transmission_delay_ms.count == 0) ? 0.0 :
|
||||||
|
snapshot.transmission_delay_ms.sum / (double)snapshot.transmission_delay_ms.count,
|
||||||
|
(snapshot.propagation_delay_ms.count == 0) ? 0.0 :
|
||||||
|
snapshot.propagation_delay_ms.sum / (double)snapshot.propagation_delay_ms.count,
|
||||||
|
(snapshot.end_to_end_delay_ms.count == 0) ? 0.0 :
|
||||||
|
snapshot.end_to_end_delay_ms.sum / (double)snapshot.end_to_end_delay_ms.count,
|
||||||
|
(snapshot.send_buffer_pct.count == 0) ? 0.0 :
|
||||||
|
snapshot.send_buffer_pct.sum / (double)snapshot.send_buffer_pct.count,
|
||||||
|
(snapshot.recv_buffer_pct.count == 0) ? 0.0 :
|
||||||
|
snapshot.recv_buffer_pct.sum / (double)snapshot.recv_buffer_pct.count,
|
||||||
|
(snapshot.cwnd.count == 0) ? 0.0 :
|
||||||
|
snapshot.cwnd.sum / (double)snapshot.cwnd.count,
|
||||||
|
(unsigned long long)snapshot.tcp_retrans,
|
||||||
|
(unsigned long long)snapshot.tcp_data_segs_out,
|
||||||
|
(unsigned long long)tcp_original_bytes,
|
||||||
|
(unsigned long long)snapshot.tcp_retrans_bytes,
|
||||||
|
tcp_retrans_rate,
|
||||||
|
(unsigned long long)snapshot.kcp_retrans,
|
||||||
|
(unsigned long long)snapshot.kcp_data_segs_out,
|
||||||
|
(unsigned long long)kcp_original_bytes,
|
||||||
|
(unsigned long long)snapshot.kcp_retrans_bytes,
|
||||||
|
kcp_retrans_rate,
|
||||||
|
(unsigned)rt->clock_sync.valid,
|
||||||
|
(long long)rt->clock_sync.server_minus_client_offset_ms,
|
||||||
|
(unsigned long long)rt->clock_sync.best_rtt_ms,
|
||||||
|
(unsigned)rt->clock_sync.sample_count);
|
||||||
|
|
||||||
|
free(missing_ranges);
|
||||||
|
free(recv_window_dist);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 释放 TransferState 持有的动态内存。 */
|
||||||
|
static void transfer_state_free(TransferState *st)
|
||||||
|
{
|
||||||
|
free(st->seen);
|
||||||
|
free(st->recv_window_counts);
|
||||||
|
memset(st, 0, sizeof(*st));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 服务端接收线程:
|
||||||
|
* - 接收 FILE_CHUNK 并写盘
|
||||||
|
* - 接收 FILE_END 并输出汇总、回 ACK
|
||||||
|
* - 接收 COMMAND 仅记录日志
|
||||||
|
*/
|
||||||
static void *recv_thread_main(void *arg)
|
static void *recv_thread_main(void *arg)
|
||||||
{
|
{
|
||||||
ServerRuntime *rt = (ServerRuntime *)arg;
|
ServerRuntime *rt = (ServerRuntime *)arg;
|
||||||
uint8_t frame[SERVER_FRAME_BUF_SIZE];
|
uint8_t frame[SERVER_FRAME_BUF_SIZE];
|
||||||
|
|
||||||
/* 允许主线程在退出时取消本线程,避免阻塞 recv 导致无法收尾。 */
|
|
||||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||||
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
|
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
|
||||||
|
|
||||||
@@ -127,11 +599,6 @@ static void *recv_thread_main(void *arg)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (n == 0) {
|
if (n == 0) {
|
||||||
/*
|
|
||||||
* recv 返回 0 的语义依赖协议:
|
|
||||||
* - TCP: 对端连接关闭,接收线程可退出
|
|
||||||
* - UDP/KCP: 可能仅表示当前无可读数据,继续等待
|
|
||||||
*/
|
|
||||||
if (rt->proto == OMNI_PROTO_TCP) {
|
if (rt->proto == OMNI_PROTO_TCP) {
|
||||||
logger_log("INFO", "server", "tcp_peer_closed");
|
logger_log("INFO", "server", "tcp_peer_closed");
|
||||||
break;
|
break;
|
||||||
@@ -149,27 +616,112 @@ static void *recv_thread_main(void *arg)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hdr.type == MSG_TYPE_FILE_CHUNK) {
|
if (hdr.type == MSG_TYPE_FILE_CHUNK) {
|
||||||
/* 文件分片:直接按顺序落盘。 */
|
rc = server_record_chunk(rt, &hdr, payload, hdr.len);
|
||||||
size_t nw = fwrite(payload, 1, hdr.len, rt->out_fp);
|
if (rc != OMNI_OK) {
|
||||||
if (nw != hdr.len) {
|
|
||||||
logger_log("ERROR", "server", "fwrite_failed expect=%u got=%zu",
|
|
||||||
(unsigned)hdr.len, nw);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
rt->bytes_written += nw;
|
} else if (hdr.type == MSG_TYPE_TIME_SYNC_REQ) {
|
||||||
|
TimeSyncProbeMeta probe_meta;
|
||||||
|
TimeSyncReplyMeta reply_meta;
|
||||||
|
uint64_t server_recv_ts_ms;
|
||||||
|
uint64_t server_send_ts_ms;
|
||||||
|
|
||||||
|
if (hdr.len < TIME_SYNC_PROBE_META_SIZE) {
|
||||||
|
logger_log("WARN", "server",
|
||||||
|
"short_time_sync_probe len=%u",
|
||||||
|
(unsigned)hdr.len);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
server_recv_ts_ms = omni_now_ms();
|
||||||
|
omni_time_sync_probe_meta_decode((const TimeSyncProbeMeta *)payload, &probe_meta);
|
||||||
|
server_send_ts_ms = omni_now_ms();
|
||||||
|
omni_time_sync_reply_meta_encode(&reply_meta,
|
||||||
|
probe_meta.probe_id,
|
||||||
|
probe_meta.client_send_ts_ms,
|
||||||
|
server_recv_ts_ms,
|
||||||
|
server_send_ts_ms);
|
||||||
|
if (send_app_message_with_timestamp(rt->ctx,
|
||||||
|
MSG_TYPE_TIME_SYNC_RESP,
|
||||||
|
&reply_meta,
|
||||||
|
TIME_SYNC_REPLY_META_SIZE,
|
||||||
|
server_send_ts_ms) != OMNI_OK) {
|
||||||
|
logger_log("ERROR", "server",
|
||||||
|
"time_sync_reply_failed probe_id=%u",
|
||||||
|
(unsigned)probe_meta.probe_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
logger_log("INFO", "server",
|
||||||
|
"time_sync_reply probe_id=%u server_recv_ts_ms=%llu server_send_ts_ms=%llu",
|
||||||
|
(unsigned)probe_meta.probe_id,
|
||||||
|
(unsigned long long)server_recv_ts_ms,
|
||||||
|
(unsigned long long)server_send_ts_ms);
|
||||||
|
continue;
|
||||||
|
} else if (hdr.type == MSG_TYPE_TIME_SYNC_REPORT) {
|
||||||
|
TimeSyncReportMeta report_meta;
|
||||||
|
|
||||||
|
if (hdr.len < TIME_SYNC_REPORT_META_SIZE) {
|
||||||
|
logger_log("WARN", "server",
|
||||||
|
"short_time_sync_report len=%u",
|
||||||
|
(unsigned)hdr.len);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
omni_time_sync_report_meta_decode((const TimeSyncReportMeta *)payload, &report_meta);
|
||||||
|
rt->clock_sync.valid = 1;
|
||||||
|
rt->clock_sync.server_minus_client_offset_ms =
|
||||||
|
report_meta.server_minus_client_offset_ms;
|
||||||
|
rt->clock_sync.best_rtt_ms = report_meta.best_rtt_ms;
|
||||||
|
rt->clock_sync.sample_count = report_meta.sample_count;
|
||||||
|
logger_on_rtt(report_meta.best_rtt_ms);
|
||||||
|
logger_log("INFO", "server",
|
||||||
|
"time_sync_ready offset_ms=%lld best_rtt_ms=%llu samples=%u",
|
||||||
|
(long long)rt->clock_sync.server_minus_client_offset_ms,
|
||||||
|
(unsigned long long)rt->clock_sync.best_rtt_ms,
|
||||||
|
(unsigned)rt->clock_sync.sample_count);
|
||||||
|
continue;
|
||||||
} else if (hdr.type == MSG_TYPE_FILE_END) {
|
} else if (hdr.type == MSG_TYPE_FILE_END) {
|
||||||
/*
|
TransferEndMeta end_meta;
|
||||||
* 文件接收结束:
|
TransferAckMeta ack_meta;
|
||||||
* - 仅置位 transfer_done
|
|
||||||
* - 不退出线程,让服务端在交互模式下继续保持长连接并可下发指令
|
if (hdr.len >= TRANSFER_END_META_SIZE) {
|
||||||
*/
|
/* FILE_END 会补齐整次传输的最终期望规模信息。 */
|
||||||
|
omni_transfer_end_meta_decode((const TransferEndMeta *)payload, &end_meta);
|
||||||
|
if (rt->transfer.transfer_id == 0) {
|
||||||
|
rt->transfer.transfer_id = end_meta.transfer_id;
|
||||||
|
}
|
||||||
|
if (rt->transfer.total_chunks == 0) {
|
||||||
|
rt->transfer.total_chunks = end_meta.total_chunks;
|
||||||
|
}
|
||||||
|
if (rt->transfer.total_bytes == 0) {
|
||||||
|
rt->transfer.total_bytes = end_meta.total_bytes;
|
||||||
|
}
|
||||||
|
if (end_meta.total_windows > rt->transfer.total_windows) {
|
||||||
|
rt->transfer.total_windows = end_meta.total_windows;
|
||||||
|
}
|
||||||
|
logger_set_transfer_total(rt->transfer.total_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 先确保文件内容刷到磁盘,再输出汇总并回 ACK。 */
|
||||||
fflush(rt->out_fp);
|
fflush(rt->out_fp);
|
||||||
|
server_log_transfer_summary(rt);
|
||||||
atomic_store(&rt->transfer_done, 1);
|
atomic_store(&rt->transfer_done, 1);
|
||||||
logger_log("INFO", "server", "file_transfer_end bytes=%llu",
|
logger_log("INFO", "server", "file_transfer_end bytes=%llu",
|
||||||
(unsigned long long)rt->bytes_written);
|
(unsigned long long)rt->bytes_written);
|
||||||
|
|
||||||
|
omni_transfer_ack_meta_encode(&ack_meta,
|
||||||
|
rt->transfer.transfer_id,
|
||||||
|
rt->transfer.total_chunks,
|
||||||
|
rt->transfer.total_bytes,
|
||||||
|
rt->bytes_written,
|
||||||
|
hdr.timestamp);
|
||||||
|
(void)send_app_message_with_timestamp(rt->ctx,
|
||||||
|
MSG_TYPE_TRANSFER_ACK,
|
||||||
|
&ack_meta,
|
||||||
|
TRANSFER_ACK_META_SIZE,
|
||||||
|
omni_now_ms());
|
||||||
continue;
|
continue;
|
||||||
} else if (hdr.type == MSG_TYPE_COMMAND) {
|
} else if (hdr.type == MSG_TYPE_COMMAND) {
|
||||||
/* 当前服务端不处理“来自客户端”的 COMMAND,仅记录日志。 */
|
|
||||||
logger_log("INFO", "server",
|
logger_log("INFO", "server",
|
||||||
"recv_command_from_peer len=%u (ignored)",
|
"recv_command_from_peer len=%u (ignored)",
|
||||||
(unsigned)hdr.len);
|
(unsigned)hdr.len);
|
||||||
@@ -184,15 +736,26 @@ static void *recv_thread_main(void *arg)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 服务端主流程:
|
||||||
|
* 1) 建立监听/接收上下文
|
||||||
|
* 2) 启动后台接收线程
|
||||||
|
* 3) 如果前台是交互终端,则允许输入命令发给客户端
|
||||||
|
* 4) 等待传输完成后统一收尾
|
||||||
|
*/
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
/* 命令行参数默认值。 */
|
|
||||||
const char *proto_str = "tcp";
|
const char *proto_str = "tcp";
|
||||||
const char *bind_ip = NULL;
|
const char *bind_ip = NULL;
|
||||||
const char *output_path = NULL;
|
const char *output_path = NULL;
|
||||||
|
OmniProtocol proto;
|
||||||
|
FILE *out_fp = NULL;
|
||||||
|
OmniContext *ctx = NULL;
|
||||||
|
ServerRuntime rt;
|
||||||
|
pthread_t recv_tid;
|
||||||
int listen_port = 0;
|
int listen_port = 0;
|
||||||
|
|
||||||
int opt;
|
int opt;
|
||||||
|
|
||||||
while ((opt = getopt(argc, argv, "p:b:P:o:")) != -1) {
|
while ((opt = getopt(argc, argv, "p:b:P:o:")) != -1) {
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 'p':
|
case 'p':
|
||||||
@@ -218,15 +781,14 @@ int main(int argc, char **argv)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
FILE *out_fp = fopen(output_path, "wb");
|
out_fp = fopen(output_path, "wb+");
|
||||||
if (!out_fp) {
|
if (!out_fp) {
|
||||||
perror("fopen");
|
perror("fopen");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
OmniProtocol proto = parse_proto(proto_str);
|
proto = parse_proto(proto_str);
|
||||||
/* 服务端角色:仅监听本地端口。 */
|
ctx = omni_init(OMNI_ROLE_SERVER, proto,
|
||||||
OmniContext *ctx = omni_init(OMNI_ROLE_SERVER, proto,
|
|
||||||
bind_ip, (uint16_t)listen_port,
|
bind_ip, (uint16_t)listen_port,
|
||||||
NULL, 0);
|
NULL, 0);
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
@@ -235,16 +797,13 @@ int main(int argc, char **argv)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerRuntime rt;
|
memset(&rt, 0, sizeof(rt));
|
||||||
rt.ctx = ctx;
|
rt.ctx = ctx;
|
||||||
rt.proto = proto;
|
rt.proto = proto;
|
||||||
rt.out_fp = out_fp;
|
rt.out_fp = out_fp;
|
||||||
rt.bytes_written = 0;
|
|
||||||
atomic_init(&rt.running, 1);
|
atomic_init(&rt.running, 1);
|
||||||
atomic_init(&rt.transfer_done, 0);
|
atomic_init(&rt.transfer_done, 0);
|
||||||
|
|
||||||
/* 启动接收线程处理文件写入主流程。 */
|
|
||||||
pthread_t recv_tid;
|
|
||||||
if (pthread_create(&recv_tid, NULL, recv_thread_main, &rt) != 0) {
|
if (pthread_create(&recv_tid, NULL, recv_thread_main, &rt) != 0) {
|
||||||
perror("pthread_create");
|
perror("pthread_create");
|
||||||
omni_close(ctx);
|
omni_close(ctx);
|
||||||
@@ -253,11 +812,7 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isatty(STDIN_FILENO)) {
|
if (isatty(STDIN_FILENO)) {
|
||||||
/*
|
/* 交互模式:允许服务端向客户端发送文本命令。 */
|
||||||
* 交互模式:
|
|
||||||
* - 每次回车读取一行
|
|
||||||
* - 非空行封装为 COMMAND 发送给客户端
|
|
||||||
*/
|
|
||||||
char line[2048];
|
char line[2048];
|
||||||
while (atomic_load(&rt.running)) {
|
while (atomic_load(&rt.running)) {
|
||||||
if (!fgets(line, sizeof(line), stdin)) {
|
if (!fgets(line, sizeof(line), stdin)) {
|
||||||
@@ -273,31 +828,31 @@ int main(int argc, char **argv)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (strcmp(line, "quit") == 0) {
|
if (strcmp(line, "quit") == 0) {
|
||||||
/* 主动退出交互循环。 */
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
int rc = send_app_message(ctx, MSG_TYPE_COMMAND, line, (uint32_t)len);
|
if (send_app_message_with_timestamp(ctx,
|
||||||
if (rc != OMNI_OK) {
|
MSG_TYPE_COMMAND,
|
||||||
|
line,
|
||||||
|
(uint32_t)len,
|
||||||
|
omni_now_ms()) != OMNI_OK) {
|
||||||
logger_log("ERROR", "server", "send_command_failed");
|
logger_log("ERROR", "server", "send_command_failed");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/*
|
/* 非交互模式:例如脚本执行时,只等待传输结束即可。 */
|
||||||
* 非交互模式(如脚本后台):
|
|
||||||
* 只等待接收线程将 transfer_done 置位,避免阻塞在 stdin。
|
|
||||||
*/
|
|
||||||
while (atomic_load(&rt.running) && !atomic_load(&rt.transfer_done)) {
|
while (atomic_load(&rt.running) && !atomic_load(&rt.transfer_done)) {
|
||||||
usleep(100 * 1000);
|
usleep(100 * 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 收尾:取消接收线程 -> join -> 关闭网络 -> 关闭文件。 */
|
/* 主线程统一收尾并释放接收线程持有的资源。 */
|
||||||
atomic_store(&rt.running, 0);
|
atomic_store(&rt.running, 0);
|
||||||
pthread_cancel(recv_tid);
|
pthread_cancel(recv_tid);
|
||||||
pthread_join(recv_tid, NULL);
|
pthread_join(recv_tid, NULL);
|
||||||
omni_close(ctx);
|
omni_close(ctx);
|
||||||
fclose(out_fp);
|
fclose(out_fp);
|
||||||
|
transfer_state_free(&rt.transfer);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* 打印测试程序命令行帮助。 */
|
||||||
static void usage(const char *prog)
|
static void usage(const char *prog)
|
||||||
{
|
{
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
@@ -27,6 +28,7 @@ static void usage(const char *prog)
|
|||||||
prog, prog);
|
prog, prog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 解析协议名,非法输入时默认回退 TCP。 */
|
||||||
static OmniProtocol parse_proto(const char *s)
|
static OmniProtocol parse_proto(const char *s)
|
||||||
{
|
{
|
||||||
if (strcmp(s, "tcp") == 0) return OMNI_PROTO_TCP;
|
if (strcmp(s, "tcp") == 0) return OMNI_PROTO_TCP;
|
||||||
@@ -35,6 +37,10 @@ static OmniProtocol parse_proto(const char *s)
|
|||||||
return OMNI_PROTO_TCP;
|
return OMNI_PROTO_TCP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 测试服务端:
|
||||||
|
* 持续收消息并原样 echo 回去,主要用于验证双向收发路径是否通畅。
|
||||||
|
*/
|
||||||
static void run_server(OmniProtocol proto, uint16_t port)
|
static void run_server(OmniProtocol proto, uint16_t port)
|
||||||
{
|
{
|
||||||
OmniContext *ctx = omni_init(OMNI_ROLE_SERVER, proto,
|
OmniContext *ctx = omni_init(OMNI_ROLE_SERVER, proto,
|
||||||
@@ -56,6 +62,7 @@ static void run_server(OmniProtocol proto, uint16_t port)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (n == 0) {
|
if (n == 0) {
|
||||||
|
/* KCP 在“暂时没拼出完整消息”时可能返回 0,因此这里不能立刻判定连接结束。 */
|
||||||
if (proto == OMNI_PROTO_KCP) {
|
if (proto == OMNI_PROTO_KCP) {
|
||||||
usleep(10 * 1000);
|
usleep(10 * 1000);
|
||||||
continue;
|
continue;
|
||||||
@@ -73,6 +80,10 @@ static void run_server(OmniProtocol proto, uint16_t port)
|
|||||||
omni_close(ctx);
|
omni_close(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 测试客户端:
|
||||||
|
* 连续发 100 条消息,每发一条就等一条 echo,用来观测协议往返行为。
|
||||||
|
*/
|
||||||
static void run_client(OmniProtocol proto, const char *host, uint16_t port)
|
static void run_client(OmniProtocol proto, const char *host, uint16_t port)
|
||||||
{
|
{
|
||||||
if (!host) {
|
if (!host) {
|
||||||
@@ -111,6 +122,7 @@ static void run_client(OmniProtocol proto, const char *host, uint16_t port)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (m == 0) {
|
if (m == 0) {
|
||||||
|
/* 对 KCP 来说,0 更像“此刻没取到完整消息”,而不是 socket 关闭。 */
|
||||||
if (proto == OMNI_PROTO_KCP) {
|
if (proto == OMNI_PROTO_KCP) {
|
||||||
usleep(10 * 1000);
|
usleep(10 * 1000);
|
||||||
--i;
|
--i;
|
||||||
@@ -129,6 +141,7 @@ static void run_client(OmniProtocol proto, const char *host, uint16_t port)
|
|||||||
omni_close(ctx);
|
omni_close(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 程序入口:根据角色参数派发到测试客户端或测试服务端。 */
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
const char *role_str = NULL;
|
const char *role_str = NULL;
|
||||||
|
|||||||
@@ -6,21 +6,31 @@
|
|||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <float.h>
|
||||||
#include <stdarg.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 全局日志/指标状态。
|
||||||
|
* 整个进程共用一份,由 g_mu 串行化保护,避免多个线程同时写统计导致数据撕裂。
|
||||||
|
*/
|
||||||
static OmniStats g_stats;
|
static OmniStats g_stats;
|
||||||
static FILE *g_json_fp = NULL;
|
static FILE *g_json_fp = NULL;
|
||||||
static int g_min_level = 1; /* default INFO */
|
static int g_min_level = 1; /* default INFO */
|
||||||
|
static int g_initialized = 0;
|
||||||
|
static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
/* 对 common.h 的时间接口做一层薄包装,统一 logger 模块内部的调用入口。 */
|
||||||
static uint64_t now_ms(void)
|
static uint64_t now_ms(void)
|
||||||
{
|
{
|
||||||
return omni_now_ms();
|
return omni_now_ms();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 将字符串日志级别映射为可比较的整数,数值越大表示级别越高。 */
|
||||||
static int level_to_int(const char *level)
|
static int level_to_int(const char *level)
|
||||||
{
|
{
|
||||||
if (!level) return 1;
|
if (!level) return 1;
|
||||||
@@ -31,6 +41,10 @@ static int level_to_int(const char *level)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 使用 EWMA(指数加权移动平均)平滑指标曲线。
|
||||||
|
* alpha 越大,越关注最新样本;alpha 越小,越平滑。
|
||||||
|
*/
|
||||||
static void ewma_update(double *avg, double sample, double alpha)
|
static void ewma_update(double *avg, double sample, double alpha)
|
||||||
{
|
{
|
||||||
if (*avg <= 0.0) {
|
if (*avg <= 0.0) {
|
||||||
@@ -40,90 +54,118 @@ static void ewma_update(double *avg, double sample, double alpha)
|
|||||||
*avg = (*avg) * (1.0 - alpha) + sample * alpha;
|
*avg = (*avg) * (1.0 - alpha) + sample * alpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
void logger_init(void)
|
/* 将一个指标摘要结构恢复到“尚未采样”的初始状态。 */
|
||||||
|
static void metric_reset(OmniMetricSummary *metric)
|
||||||
{
|
{
|
||||||
memset(&g_stats, 0, sizeof(g_stats));
|
metric->count = 0;
|
||||||
g_stats.start_ms = now_ms();
|
metric->last = 0.0;
|
||||||
g_stats.last_report_ms = g_stats.start_ms;
|
metric->min = DBL_MAX;
|
||||||
g_stats.send_call_min_ms = UINT64_MAX;
|
metric->max = 0.0;
|
||||||
g_stats.recv_call_min_ms = UINT64_MAX;
|
metric->sum = 0.0;
|
||||||
|
|
||||||
const char *lvl_env = getenv("OMNI_LOG_LEVEL");
|
|
||||||
g_min_level = level_to_int(lvl_env);
|
|
||||||
|
|
||||||
if (!g_json_fp) {
|
|
||||||
g_json_fp = fopen("omni_logs.jsonl", "a");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void logger_on_send(size_t bytes)
|
/* 记录一个样本到 min/max/sum/count/last 这组聚合字段中。 */
|
||||||
|
static void metric_observe(OmniMetricSummary *metric, double sample)
|
||||||
{
|
{
|
||||||
g_stats.bytes_sent += bytes;
|
if (sample < 0.0) return;
|
||||||
g_stats.send_count++;
|
metric->last = sample;
|
||||||
|
if (sample < metric->min) metric->min = sample;
|
||||||
|
if (sample > metric->max) metric->max = sample;
|
||||||
|
metric->sum += sample;
|
||||||
|
metric->count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void logger_on_recv(size_t bytes)
|
/* 计算指标平均值;没有样本时返回 0。 */
|
||||||
|
static double metric_avg(const OmniMetricSummary *metric)
|
||||||
{
|
{
|
||||||
g_stats.bytes_recv += bytes;
|
if (!metric || metric->count == 0) return 0.0;
|
||||||
g_stats.recv_count++;
|
return metric->sum / (double)metric->count;
|
||||||
}
|
}
|
||||||
|
|
||||||
void logger_on_rtt(uint64_t rtt_ms)
|
/* 计算指标最小值;没有样本时返回 0,避免把 DBL_MAX 直接暴露出去。 */
|
||||||
|
static double metric_min(const OmniMetricSummary *metric)
|
||||||
{
|
{
|
||||||
g_stats.last_rtt_ms = rtt_ms;
|
if (!metric || metric->count == 0 || metric->min == DBL_MAX) return 0.0;
|
||||||
if (rtt_ms > g_stats.max_rtt_ms) {
|
return metric->min;
|
||||||
g_stats.max_rtt_ms = rtt_ms;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void logger_on_kcp_retrans(uint64_t delta)
|
/*
|
||||||
|
* 基于当前窗口内流量和累计流量估算“此刻本机可提供的服务速率”。
|
||||||
|
* 优先使用当前窗口速率;若窗口内尚无样本,则退化为全局平均速率。
|
||||||
|
*/
|
||||||
|
static double live_rate_mbps_locked(uint64_t now, int is_send)
|
||||||
{
|
{
|
||||||
g_stats.kcp_retrans += delta;
|
uint64_t window_elapsed_ms = now - g_stats.window_start_ms;
|
||||||
}
|
uint64_t total_elapsed_ms = now - g_stats.start_ms;
|
||||||
|
uint64_t window_bytes = is_send ? g_stats.window_bytes_sent : g_stats.window_bytes_recv;
|
||||||
|
uint64_t total_bytes = is_send ? g_stats.bytes_sent : g_stats.bytes_recv;
|
||||||
|
|
||||||
void logger_on_send_call_latency(uint64_t ms)
|
if (window_elapsed_ms > 0 && window_bytes > 0) {
|
||||||
{
|
return ((double)window_bytes * 8.0) /
|
||||||
g_stats.last_send_call_ms = ms;
|
((double)window_elapsed_ms / 1000.0) /
|
||||||
if (ms < g_stats.send_call_min_ms) g_stats.send_call_min_ms = ms;
|
1000000.0;
|
||||||
if (ms > g_stats.send_call_max_ms) g_stats.send_call_max_ms = ms;
|
|
||||||
ewma_update(&g_stats.send_call_avg_ms, (double)ms, 0.2);
|
|
||||||
}
|
}
|
||||||
|
if (total_elapsed_ms > 0 && total_bytes > 0) {
|
||||||
void logger_on_recv_call_latency(uint64_t ms)
|
return ((double)total_bytes * 8.0) /
|
||||||
{
|
((double)total_elapsed_ms / 1000.0) /
|
||||||
g_stats.last_recv_call_ms = ms;
|
1000000.0;
|
||||||
if (ms < g_stats.recv_call_min_ms) g_stats.recv_call_min_ms = ms;
|
|
||||||
if (ms > g_stats.recv_call_max_ms) g_stats.recv_call_max_ms = ms;
|
|
||||||
ewma_update(&g_stats.recv_call_avg_ms, (double)ms, 0.2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void logger_on_proto_send_latency(uint64_t ms)
|
|
||||||
{
|
|
||||||
ewma_update(&g_stats.proto_send_avg_ms, (double)ms, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void logger_on_proto_recv_latency(uint64_t ms)
|
|
||||||
{
|
|
||||||
ewma_update(&g_stats.proto_recv_avg_ms, (double)ms, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
double logger_calculate_throughput(void)
|
|
||||||
{
|
|
||||||
uint64_t now = now_ms();
|
|
||||||
uint64_t elapsed_ms = now - g_stats.start_ms;
|
|
||||||
if (elapsed_ms == 0) {
|
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
double seconds = (double)elapsed_ms / 1000.0;
|
|
||||||
return (double)(g_stats.bytes_sent + g_stats.bytes_recv) / seconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_timestamp(FILE *fp)
|
/* 用本机当前服务速率把字节数换算成时延估计。 */
|
||||||
|
static double bytes_to_delay_ms_locked(size_t bytes, int is_send)
|
||||||
{
|
{
|
||||||
uint64_t ms = now_ms();
|
uint64_t now = now_ms();
|
||||||
fprintf(fp, "ts=%llu ", (unsigned long long)ms);
|
double rate_mbps = live_rate_mbps_locked(now, is_send);
|
||||||
|
if (rate_mbps <= 0.0) {
|
||||||
|
return -1.0;
|
||||||
|
}
|
||||||
|
return ((double)bytes * 8.0) / (rate_mbps * 1000000.0) * 1000.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 根据“程序启动至今”的累计字节数刷新平均吞吐。
|
||||||
|
* 这个值比较平稳,适合做整段任务的总体表现观察。
|
||||||
|
*/
|
||||||
|
static void refresh_throughput_locked(uint64_t now)
|
||||||
|
{
|
||||||
|
uint64_t elapsed_ms = now - g_stats.start_ms;
|
||||||
|
if (elapsed_ms == 0) {
|
||||||
|
g_stats.tx_avg_mbps = 0.0;
|
||||||
|
g_stats.rx_avg_mbps = 0.0;
|
||||||
|
} else {
|
||||||
|
double seconds = (double)elapsed_ms / 1000.0;
|
||||||
|
g_stats.tx_avg_mbps = ((double)g_stats.bytes_sent * 8.0) / seconds / 1000000.0;
|
||||||
|
g_stats.rx_avg_mbps = ((double)g_stats.bytes_recv * 8.0) / seconds / 1000000.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 刷新当前窗口吞吐。
|
||||||
|
* 这里会把窗口内计数清零,因此必须只在持锁状态下调用。
|
||||||
|
*/
|
||||||
|
static void refresh_window_locked(uint64_t now)
|
||||||
|
{
|
||||||
|
uint64_t window_elapsed_ms = now - g_stats.window_start_ms;
|
||||||
|
if (window_elapsed_ms == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
double seconds = (double)window_elapsed_ms / 1000.0;
|
||||||
|
g_stats.tx_current_mbps = ((double)g_stats.window_bytes_sent * 8.0) / seconds / 1000000.0;
|
||||||
|
g_stats.rx_current_mbps = ((double)g_stats.window_bytes_recv * 8.0) / seconds / 1000000.0;
|
||||||
|
g_stats.window_bytes_sent = 0;
|
||||||
|
g_stats.window_bytes_recv = 0;
|
||||||
|
g_stats.window_start_ms = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 统一打印时间戳前缀,方便 grep 和人眼阅读。 */
|
||||||
|
static void print_timestamp(FILE *fp, uint64_t ts)
|
||||||
|
{
|
||||||
|
fprintf(fp, "ts=%llu ", (unsigned long long)ts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 将日志消息转成适合写入 JSON 的安全字符串。 */
|
||||||
static void json_escape(const char *src, char *dst, size_t dst_sz)
|
static void json_escape(const char *src, char *dst, size_t dst_sz)
|
||||||
{
|
{
|
||||||
size_t j = 0;
|
size_t j = 0;
|
||||||
@@ -148,48 +190,444 @@ static void json_escape(const char *src, char *dst, size_t dst_sz)
|
|||||||
dst[j] = '\0';
|
dst[j] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 初始化全局日志模块。
|
||||||
|
* 这里只做一次性工作:清零统计、读取环境变量日志级别、打开 jsonl 文件。
|
||||||
|
*/
|
||||||
|
void logger_init(void)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
if (!g_initialized) {
|
||||||
|
memset(&g_stats, 0, sizeof(g_stats));
|
||||||
|
g_stats.start_ms = now_ms();
|
||||||
|
g_stats.last_report_ms = g_stats.start_ms;
|
||||||
|
g_stats.window_start_ms = g_stats.start_ms;
|
||||||
|
g_stats.min_rtt_ms = UINT64_MAX;
|
||||||
|
g_stats.send_call_min_ms = UINT64_MAX;
|
||||||
|
g_stats.recv_call_min_ms = UINT64_MAX;
|
||||||
|
metric_reset(&g_stats.processing_delay_ms);
|
||||||
|
metric_reset(&g_stats.queue_delay_ms);
|
||||||
|
metric_reset(&g_stats.transmission_delay_ms);
|
||||||
|
metric_reset(&g_stats.propagation_delay_ms);
|
||||||
|
metric_reset(&g_stats.end_to_end_delay_ms);
|
||||||
|
metric_reset(&g_stats.send_buffer_pct);
|
||||||
|
metric_reset(&g_stats.recv_buffer_pct);
|
||||||
|
metric_reset(&g_stats.cwnd);
|
||||||
|
|
||||||
|
{
|
||||||
|
const char *lvl_env = getenv("OMNI_LOG_LEVEL");
|
||||||
|
g_min_level = level_to_int(lvl_env);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g_json_fp) {
|
||||||
|
g_json_fp = fopen("omni_logs.jsonl", "a");
|
||||||
|
}
|
||||||
|
g_initialized = 1;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 记录一次发送事件,更新累计发送字节和窗口内发送字节。 */
|
||||||
|
void logger_on_send(size_t bytes)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
g_stats.bytes_sent += bytes;
|
||||||
|
g_stats.window_bytes_sent += bytes;
|
||||||
|
g_stats.send_count++;
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 记录一次接收事件,更新累计接收字节和窗口内接收字节。 */
|
||||||
|
void logger_on_recv(size_t bytes)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
g_stats.bytes_recv += bytes;
|
||||||
|
g_stats.window_bytes_recv += bytes;
|
||||||
|
g_stats.recv_count++;
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 更新最近一次 RTT 和历史最大 RTT。 */
|
||||||
|
void logger_on_rtt(uint64_t rtt_ms)
|
||||||
|
{
|
||||||
|
if (rtt_ms == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
g_stats.last_rtt_ms = rtt_ms;
|
||||||
|
if (rtt_ms < g_stats.min_rtt_ms) {
|
||||||
|
g_stats.min_rtt_ms = rtt_ms;
|
||||||
|
}
|
||||||
|
if (rtt_ms > g_stats.max_rtt_ms) {
|
||||||
|
g_stats.max_rtt_ms = rtt_ms;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* 传播时延无法直接观测,这里统一采用 min RTT / 2 作为链路基线估算。
|
||||||
|
* min RTT 更接近“基本无排队”时的往返时间,因此比 last RTT 更稳。
|
||||||
|
*/
|
||||||
|
metric_observe(&g_stats.propagation_delay_ms, (double)g_stats.min_rtt_ms / 2.0);
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TCP 侧上报的是内核维护的累计快照,因此这里统一取较大值覆盖,
|
||||||
|
* 避免重复采样时把同一个累计值反复叠加。
|
||||||
|
*/
|
||||||
|
void logger_on_tcp_transport(uint64_t total_retrans,
|
||||||
|
uint64_t data_segs_out,
|
||||||
|
uint64_t data_bytes_sent,
|
||||||
|
uint64_t retrans_bytes)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
if (total_retrans > g_stats.tcp_retrans) {
|
||||||
|
g_stats.tcp_retrans = total_retrans;
|
||||||
|
}
|
||||||
|
if (data_segs_out > g_stats.tcp_data_segs_out) {
|
||||||
|
g_stats.tcp_data_segs_out = data_segs_out;
|
||||||
|
}
|
||||||
|
if (data_bytes_sent > g_stats.tcp_data_bytes_sent) {
|
||||||
|
g_stats.tcp_data_bytes_sent = data_bytes_sent;
|
||||||
|
}
|
||||||
|
if (retrans_bytes > g_stats.tcp_retrans_bytes) {
|
||||||
|
g_stats.tcp_retrans_bytes = retrans_bytes;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* KCP 侧上报的是本轮首次发送的数据分片,因此这里直接累加。 */
|
||||||
|
void logger_on_kcp_tx(uint64_t segs, uint64_t bytes)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
g_stats.kcp_data_segs_out += segs;
|
||||||
|
g_stats.kcp_data_bytes_sent += bytes;
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* KCP 侧上报的是“本轮新增重传量”,因此这里采用累加。 */
|
||||||
|
void logger_on_kcp_retrans(uint64_t segs, uint64_t bytes)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
g_stats.kcp_retrans += segs;
|
||||||
|
g_stats.kcp_retrans_bytes += bytes;
|
||||||
|
g_stats.kcp_data_segs_out += segs;
|
||||||
|
g_stats.kcp_data_bytes_sent += bytes;
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 记录应用层一次 send 调用的耗时统计。 */
|
||||||
|
void logger_on_send_call_latency(uint64_t ms)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
g_stats.last_send_call_ms = ms;
|
||||||
|
if (ms < g_stats.send_call_min_ms) g_stats.send_call_min_ms = ms;
|
||||||
|
if (ms > g_stats.send_call_max_ms) g_stats.send_call_max_ms = ms;
|
||||||
|
ewma_update(&g_stats.send_call_avg_ms, (double)ms, 0.2);
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 记录应用层一次 recv 调用的耗时统计。 */
|
||||||
|
void logger_on_recv_call_latency(uint64_t ms)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
g_stats.last_recv_call_ms = ms;
|
||||||
|
if (ms < g_stats.recv_call_min_ms) g_stats.recv_call_min_ms = ms;
|
||||||
|
if (ms > g_stats.recv_call_max_ms) g_stats.recv_call_max_ms = ms;
|
||||||
|
ewma_update(&g_stats.recv_call_avg_ms, (double)ms, 0.2);
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 记录协议层发送处理耗时,例如打包、flush、发送等待等。 */
|
||||||
|
void logger_on_proto_send_latency(uint64_t ms)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
ewma_update(&g_stats.proto_send_avg_ms, (double)ms, 0.2);
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 记录协议层接收处理耗时,例如解包、协议状态推进等。 */
|
||||||
|
void logger_on_proto_recv_latency(uint64_t ms)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
ewma_update(&g_stats.proto_recv_avg_ms, (double)ms, 0.2);
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 记录本地处理耗时,例如编码/解码、写盘等 CPU/IO 操作时间。 */
|
||||||
|
void logger_on_processing_latency(double ms)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
metric_observe(&g_stats.processing_delay_ms, ms);
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 记录估算出的排队时延。 */
|
||||||
|
void logger_on_queue_delay_est(double ms)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
metric_observe(&g_stats.queue_delay_ms, ms);
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 记录估算出的传输时延。 */
|
||||||
|
void logger_on_transmission_delay_est(double ms)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
metric_observe(&g_stats.transmission_delay_ms, ms);
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 记录估算出的传播时延。 */
|
||||||
|
void logger_on_propagation_delay_est(double ms)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
metric_observe(&g_stats.propagation_delay_ms, ms);
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 记录端到端总时延。 */
|
||||||
|
void logger_on_end_to_end_latency(double ms)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
metric_observe(&g_stats.end_to_end_delay_ms, ms);
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 记录发送缓冲区内尚未发出的字节量,并按本机发送速率换算排队时延。 */
|
||||||
|
void logger_on_send_queue_bytes(size_t bytes)
|
||||||
|
{
|
||||||
|
double queue_ms;
|
||||||
|
|
||||||
|
if (bytes == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
queue_ms = bytes_to_delay_ms_locked(bytes, 1);
|
||||||
|
if (queue_ms >= 0.0) {
|
||||||
|
metric_observe(&g_stats.queue_delay_ms, queue_ms);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 记录接收缓冲区内已到达但尚未被读取的字节量,并估算接收侧排队时延。 */
|
||||||
|
void logger_on_recv_queue_bytes(size_t bytes)
|
||||||
|
{
|
||||||
|
double queue_ms;
|
||||||
|
|
||||||
|
if (bytes == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
queue_ms = bytes_to_delay_ms_locked(bytes, 0);
|
||||||
|
if (queue_ms >= 0.0) {
|
||||||
|
metric_observe(&g_stats.queue_delay_ms, queue_ms);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按当前发送速率估算“这批字节序列化到链路上需要多久”。 */
|
||||||
|
void logger_on_send_transmission_bytes(size_t bytes)
|
||||||
|
{
|
||||||
|
double transmission_ms;
|
||||||
|
|
||||||
|
if (bytes == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
transmission_ms = bytes_to_delay_ms_locked(bytes, 1);
|
||||||
|
if (transmission_ms >= 0.0) {
|
||||||
|
metric_observe(&g_stats.transmission_delay_ms, transmission_ms);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按当前接收速率估算“这批字节从链路收上来需要多久”。 */
|
||||||
|
void logger_on_recv_transmission_bytes(size_t bytes)
|
||||||
|
{
|
||||||
|
double transmission_ms;
|
||||||
|
|
||||||
|
if (bytes == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
transmission_ms = bytes_to_delay_ms_locked(bytes, 0);
|
||||||
|
if (transmission_ms >= 0.0) {
|
||||||
|
metric_observe(&g_stats.transmission_delay_ms, transmission_ms);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 记录收发缓冲区占用百分比。 */
|
||||||
|
void logger_on_buffer_status(double send_pct, double recv_pct)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
metric_observe(&g_stats.send_buffer_pct, send_pct);
|
||||||
|
metric_observe(&g_stats.recv_buffer_pct, recv_pct);
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 记录拥塞窗口样本。 */
|
||||||
|
void logger_on_cwnd(double cwnd)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
metric_observe(&g_stats.cwnd, cwnd);
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 设置当前任务的总工作量,用于计算进度百分比。 */
|
||||||
|
void logger_set_transfer_total(uint64_t total_bytes)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
g_stats.total_work_bytes = total_bytes;
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 设置当前已完成工作量。 */
|
||||||
|
void logger_set_progress(uint64_t progress_bytes)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
g_stats.progress_bytes = progress_bytes;
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 对外提供一个“当前总吞吐”的便捷计算接口。 */
|
||||||
|
double logger_calculate_throughput(void)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
refresh_throughput_locked(now_ms());
|
||||||
|
{
|
||||||
|
uint64_t total_bytes = g_stats.bytes_sent + g_stats.bytes_recv;
|
||||||
|
uint64_t elapsed_ms = now_ms() - g_stats.start_ms;
|
||||||
|
double throughput = 0.0;
|
||||||
|
if (elapsed_ms > 0) {
|
||||||
|
throughput = (double)total_bytes / ((double)elapsed_ms / 1000.0);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
return throughput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 按需打印性能日志。
|
||||||
|
* 调用方可以高频触发本函数,真正打印由 last_report_ms 节流到大约 1 秒一次。
|
||||||
|
*/
|
||||||
|
void logger_maybe_print_performance_log(const char *tag)
|
||||||
|
{
|
||||||
|
int should_print = 0;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
if (now_ms() - g_stats.last_report_ms >= 1000u) {
|
||||||
|
should_print = 1;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
|
||||||
|
if (should_print) {
|
||||||
|
logger_print_performance_log(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 输出一份完整的性能快照。
|
||||||
|
* 做法是先在锁内刷新并复制 g_stats,然后解锁后执行格式化输出,
|
||||||
|
* 避免长时间持锁阻塞数据面线程。
|
||||||
|
*/
|
||||||
void logger_print_performance_log(const char *tag)
|
void logger_print_performance_log(const char *tag)
|
||||||
{
|
{
|
||||||
uint64_t now = now_ms();
|
uint64_t now;
|
||||||
uint64_t elapsed_ms = now - g_stats.start_ms;
|
OmniStats snapshot;
|
||||||
double thr = logger_calculate_throughput();
|
double progress_pct;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
now = now_ms();
|
||||||
|
refresh_throughput_locked(now);
|
||||||
|
/*
|
||||||
|
* 窗口吞吐每秒刷新一次;
|
||||||
|
* 但如果调用方显式要求 final,则即便不足 1 秒也强制结算一次窗口数据。
|
||||||
|
*/
|
||||||
|
if ((tag && strcmp(tag, "final") == 0) ||
|
||||||
|
(now - g_stats.window_start_ms >= 1000u)) {
|
||||||
|
refresh_window_locked(now);
|
||||||
|
}
|
||||||
|
snapshot = g_stats;
|
||||||
|
snapshot.last_report_ms = now;
|
||||||
|
g_stats.last_report_ms = now;
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
|
||||||
|
progress_pct = 0.0;
|
||||||
|
if (snapshot.total_work_bytes > 0) {
|
||||||
|
progress_pct = ((double)snapshot.progress_bytes * 100.0) /
|
||||||
|
(double)snapshot.total_work_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
/* 人读友好的单行文本日志,适合终端直接观察。 */
|
||||||
FILE *fp = stderr;
|
FILE *fp = stderr;
|
||||||
print_timestamp(fp);
|
print_timestamp(fp, now);
|
||||||
fprintf(fp,
|
fprintf(fp,
|
||||||
"level=INFO component=perf tag=%s "
|
"level=INFO component=perf tag=%s "
|
||||||
"elapsed_ms=%llu bytes_sent=%llu bytes_recv=%llu "
|
"elapsed_ms=%llu bytes_sent=%llu bytes_recv=%llu "
|
||||||
"send_count=%llu recv_count=%llu "
|
"send_count=%llu recv_count=%llu "
|
||||||
"throughput_bytes_per_sec=%.2f "
|
"tx_current_mbps=%.3f rx_current_mbps=%.3f "
|
||||||
|
"tx_avg_mbps=%.3f rx_avg_mbps=%.3f "
|
||||||
|
"progress_bytes=%llu total_work_bytes=%llu progress_pct=%.2f "
|
||||||
"send_call_last_ms=%llu send_call_min_ms=%llu send_call_max_ms=%llu send_call_avg_ms=%.3f "
|
"send_call_last_ms=%llu send_call_min_ms=%llu send_call_max_ms=%llu send_call_avg_ms=%.3f "
|
||||||
"recv_call_last_ms=%llu recv_call_min_ms=%llu recv_call_max_ms=%llu recv_call_avg_ms=%.3f "
|
"recv_call_last_ms=%llu recv_call_min_ms=%llu recv_call_max_ms=%llu recv_call_avg_ms=%.3f "
|
||||||
"proto_send_avg_ms=%.3f proto_recv_avg_ms=%.3f "
|
"proto_send_avg_ms=%.3f proto_recv_avg_ms=%.3f "
|
||||||
"last_rtt_ms=%llu max_rtt_ms=%llu "
|
"processing_avg_ms=%.3f queue_avg_ms=%.3f transmission_avg_ms=%.3f propagation_avg_ms=%.3f end_to_end_avg_ms=%.3f "
|
||||||
"kcp_retrans=%llu\n",
|
"send_buffer_pct=%.2f recv_buffer_pct=%.2f cwnd=%.2f "
|
||||||
|
"last_rtt_ms=%llu min_rtt_ms=%llu max_rtt_ms=%llu "
|
||||||
|
"tcp_retrans=%llu tcp_data_segs_out=%llu tcp_data_bytes_sent=%llu tcp_retrans_bytes=%llu "
|
||||||
|
"kcp_retrans=%llu kcp_data_segs_out=%llu kcp_data_bytes_sent=%llu kcp_retrans_bytes=%llu\n",
|
||||||
tag ? tag : "periodic",
|
tag ? tag : "periodic",
|
||||||
(unsigned long long)elapsed_ms,
|
(unsigned long long)(now - snapshot.start_ms),
|
||||||
(unsigned long long)g_stats.bytes_sent,
|
(unsigned long long)snapshot.bytes_sent,
|
||||||
(unsigned long long)g_stats.bytes_recv,
|
(unsigned long long)snapshot.bytes_recv,
|
||||||
(unsigned long long)g_stats.send_count,
|
(unsigned long long)snapshot.send_count,
|
||||||
(unsigned long long)g_stats.recv_count,
|
(unsigned long long)snapshot.recv_count,
|
||||||
thr,
|
snapshot.tx_current_mbps,
|
||||||
(unsigned long long)g_stats.last_send_call_ms,
|
snapshot.rx_current_mbps,
|
||||||
(unsigned long long)((g_stats.send_call_min_ms == UINT64_MAX) ? 0 : g_stats.send_call_min_ms),
|
snapshot.tx_avg_mbps,
|
||||||
(unsigned long long)g_stats.send_call_max_ms,
|
snapshot.rx_avg_mbps,
|
||||||
g_stats.send_call_avg_ms,
|
(unsigned long long)snapshot.progress_bytes,
|
||||||
(unsigned long long)g_stats.last_recv_call_ms,
|
(unsigned long long)snapshot.total_work_bytes,
|
||||||
(unsigned long long)((g_stats.recv_call_min_ms == UINT64_MAX) ? 0 : g_stats.recv_call_min_ms),
|
progress_pct,
|
||||||
(unsigned long long)g_stats.recv_call_max_ms,
|
(unsigned long long)snapshot.last_send_call_ms,
|
||||||
g_stats.recv_call_avg_ms,
|
(unsigned long long)((snapshot.send_call_min_ms == UINT64_MAX) ? 0 : snapshot.send_call_min_ms),
|
||||||
g_stats.proto_send_avg_ms,
|
(unsigned long long)snapshot.send_call_max_ms,
|
||||||
g_stats.proto_recv_avg_ms,
|
snapshot.send_call_avg_ms,
|
||||||
(unsigned long long)g_stats.last_rtt_ms,
|
(unsigned long long)snapshot.last_recv_call_ms,
|
||||||
(unsigned long long)g_stats.max_rtt_ms,
|
(unsigned long long)((snapshot.recv_call_min_ms == UINT64_MAX) ? 0 : snapshot.recv_call_min_ms),
|
||||||
(unsigned long long)g_stats.kcp_retrans);
|
(unsigned long long)snapshot.recv_call_max_ms,
|
||||||
|
snapshot.recv_call_avg_ms,
|
||||||
g_stats.last_report_ms = now;
|
snapshot.proto_send_avg_ms,
|
||||||
|
snapshot.proto_recv_avg_ms,
|
||||||
|
metric_avg(&snapshot.processing_delay_ms),
|
||||||
|
metric_avg(&snapshot.queue_delay_ms),
|
||||||
|
metric_avg(&snapshot.transmission_delay_ms),
|
||||||
|
metric_avg(&snapshot.propagation_delay_ms),
|
||||||
|
metric_avg(&snapshot.end_to_end_delay_ms),
|
||||||
|
snapshot.send_buffer_pct.last,
|
||||||
|
snapshot.recv_buffer_pct.last,
|
||||||
|
snapshot.cwnd.last,
|
||||||
|
(unsigned long long)snapshot.last_rtt_ms,
|
||||||
|
(unsigned long long)((snapshot.min_rtt_ms == UINT64_MAX) ? 0 : snapshot.min_rtt_ms),
|
||||||
|
(unsigned long long)snapshot.max_rtt_ms,
|
||||||
|
(unsigned long long)snapshot.tcp_retrans,
|
||||||
|
(unsigned long long)snapshot.tcp_data_segs_out,
|
||||||
|
(unsigned long long)snapshot.tcp_data_bytes_sent,
|
||||||
|
(unsigned long long)snapshot.tcp_retrans_bytes,
|
||||||
|
(unsigned long long)snapshot.kcp_retrans,
|
||||||
|
(unsigned long long)snapshot.kcp_data_segs_out,
|
||||||
|
(unsigned long long)snapshot.kcp_data_bytes_sent,
|
||||||
|
(unsigned long long)snapshot.kcp_retrans_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
if (g_json_fp) {
|
if (g_json_fp) {
|
||||||
|
/* JSONL 输出便于后续脚本或可视化工具离线分析。 */
|
||||||
fprintf(g_json_fp,
|
fprintf(g_json_fp,
|
||||||
"{\"ts_ms\":%llu,"
|
"{\"ts_ms\":%llu,"
|
||||||
"\"level\":\"INFO\","
|
"\"level\":\"INFO\","
|
||||||
@@ -200,7 +638,13 @@ void logger_print_performance_log(const char *tag)
|
|||||||
"\"bytes_recv\":%llu,"
|
"\"bytes_recv\":%llu,"
|
||||||
"\"send_count\":%llu,"
|
"\"send_count\":%llu,"
|
||||||
"\"recv_count\":%llu,"
|
"\"recv_count\":%llu,"
|
||||||
"\"throughput_bytes_per_sec\":%.6f,"
|
"\"tx_current_mbps\":%.6f,"
|
||||||
|
"\"rx_current_mbps\":%.6f,"
|
||||||
|
"\"tx_avg_mbps\":%.6f,"
|
||||||
|
"\"rx_avg_mbps\":%.6f,"
|
||||||
|
"\"progress_bytes\":%llu,"
|
||||||
|
"\"total_work_bytes\":%llu,"
|
||||||
|
"\"progress_pct\":%.6f,"
|
||||||
"\"send_call_last_ms\":%llu,"
|
"\"send_call_last_ms\":%llu,"
|
||||||
"\"send_call_min_ms\":%llu,"
|
"\"send_call_min_ms\":%llu,"
|
||||||
"\"send_call_max_ms\":%llu,"
|
"\"send_call_max_ms\":%llu,"
|
||||||
@@ -211,61 +655,136 @@ void logger_print_performance_log(const char *tag)
|
|||||||
"\"recv_call_avg_ms\":%.6f,"
|
"\"recv_call_avg_ms\":%.6f,"
|
||||||
"\"proto_send_avg_ms\":%.6f,"
|
"\"proto_send_avg_ms\":%.6f,"
|
||||||
"\"proto_recv_avg_ms\":%.6f,"
|
"\"proto_recv_avg_ms\":%.6f,"
|
||||||
|
"\"processing_avg_ms\":%.6f,"
|
||||||
|
"\"processing_min_ms\":%.6f,"
|
||||||
|
"\"processing_max_ms\":%.6f,"
|
||||||
|
"\"queue_avg_ms\":%.6f,"
|
||||||
|
"\"queue_min_ms\":%.6f,"
|
||||||
|
"\"queue_max_ms\":%.6f,"
|
||||||
|
"\"transmission_avg_ms\":%.6f,"
|
||||||
|
"\"transmission_min_ms\":%.6f,"
|
||||||
|
"\"transmission_max_ms\":%.6f,"
|
||||||
|
"\"propagation_avg_ms\":%.6f,"
|
||||||
|
"\"propagation_min_ms\":%.6f,"
|
||||||
|
"\"propagation_max_ms\":%.6f,"
|
||||||
|
"\"end_to_end_avg_ms\":%.6f,"
|
||||||
|
"\"end_to_end_min_ms\":%.6f,"
|
||||||
|
"\"end_to_end_max_ms\":%.6f,"
|
||||||
|
"\"send_buffer_pct_last\":%.6f,"
|
||||||
|
"\"send_buffer_pct_avg\":%.6f,"
|
||||||
|
"\"send_buffer_pct_max\":%.6f,"
|
||||||
|
"\"recv_buffer_pct_last\":%.6f,"
|
||||||
|
"\"recv_buffer_pct_avg\":%.6f,"
|
||||||
|
"\"recv_buffer_pct_max\":%.6f,"
|
||||||
|
"\"cwnd_last\":%.6f,"
|
||||||
|
"\"cwnd_avg\":%.6f,"
|
||||||
|
"\"cwnd_max\":%.6f,"
|
||||||
"\"last_rtt_ms\":%llu,"
|
"\"last_rtt_ms\":%llu,"
|
||||||
|
"\"min_rtt_ms\":%llu,"
|
||||||
"\"max_rtt_ms\":%llu,"
|
"\"max_rtt_ms\":%llu,"
|
||||||
"\"kcp_retrans\":%llu}\n",
|
"\"tcp_retrans\":%llu,"
|
||||||
|
"\"tcp_data_segs_out\":%llu,"
|
||||||
|
"\"tcp_data_bytes_sent\":%llu,"
|
||||||
|
"\"tcp_retrans_bytes\":%llu,"
|
||||||
|
"\"kcp_retrans\":%llu,"
|
||||||
|
"\"kcp_data_segs_out\":%llu,"
|
||||||
|
"\"kcp_data_bytes_sent\":%llu,"
|
||||||
|
"\"kcp_retrans_bytes\":%llu}\n",
|
||||||
(unsigned long long)now,
|
(unsigned long long)now,
|
||||||
tag ? tag : "periodic",
|
tag ? tag : "periodic",
|
||||||
(unsigned long long)elapsed_ms,
|
(unsigned long long)(now - snapshot.start_ms),
|
||||||
(unsigned long long)g_stats.bytes_sent,
|
(unsigned long long)snapshot.bytes_sent,
|
||||||
(unsigned long long)g_stats.bytes_recv,
|
(unsigned long long)snapshot.bytes_recv,
|
||||||
(unsigned long long)g_stats.send_count,
|
(unsigned long long)snapshot.send_count,
|
||||||
(unsigned long long)g_stats.recv_count,
|
(unsigned long long)snapshot.recv_count,
|
||||||
thr,
|
snapshot.tx_current_mbps,
|
||||||
(unsigned long long)g_stats.last_send_call_ms,
|
snapshot.rx_current_mbps,
|
||||||
(unsigned long long)((g_stats.send_call_min_ms == UINT64_MAX) ? 0 : g_stats.send_call_min_ms),
|
snapshot.tx_avg_mbps,
|
||||||
(unsigned long long)g_stats.send_call_max_ms,
|
snapshot.rx_avg_mbps,
|
||||||
g_stats.send_call_avg_ms,
|
(unsigned long long)snapshot.progress_bytes,
|
||||||
(unsigned long long)g_stats.last_recv_call_ms,
|
(unsigned long long)snapshot.total_work_bytes,
|
||||||
(unsigned long long)((g_stats.recv_call_min_ms == UINT64_MAX) ? 0 : g_stats.recv_call_min_ms),
|
progress_pct,
|
||||||
(unsigned long long)g_stats.recv_call_max_ms,
|
(unsigned long long)snapshot.last_send_call_ms,
|
||||||
g_stats.recv_call_avg_ms,
|
(unsigned long long)((snapshot.send_call_min_ms == UINT64_MAX) ? 0 : snapshot.send_call_min_ms),
|
||||||
g_stats.proto_send_avg_ms,
|
(unsigned long long)snapshot.send_call_max_ms,
|
||||||
g_stats.proto_recv_avg_ms,
|
snapshot.send_call_avg_ms,
|
||||||
(unsigned long long)g_stats.last_rtt_ms,
|
(unsigned long long)snapshot.last_recv_call_ms,
|
||||||
(unsigned long long)g_stats.max_rtt_ms,
|
(unsigned long long)((snapshot.recv_call_min_ms == UINT64_MAX) ? 0 : snapshot.recv_call_min_ms),
|
||||||
(unsigned long long)g_stats.kcp_retrans);
|
(unsigned long long)snapshot.recv_call_max_ms,
|
||||||
|
snapshot.recv_call_avg_ms,
|
||||||
|
snapshot.proto_send_avg_ms,
|
||||||
|
snapshot.proto_recv_avg_ms,
|
||||||
|
metric_avg(&snapshot.processing_delay_ms),
|
||||||
|
metric_min(&snapshot.processing_delay_ms),
|
||||||
|
snapshot.processing_delay_ms.max,
|
||||||
|
metric_avg(&snapshot.queue_delay_ms),
|
||||||
|
metric_min(&snapshot.queue_delay_ms),
|
||||||
|
snapshot.queue_delay_ms.max,
|
||||||
|
metric_avg(&snapshot.transmission_delay_ms),
|
||||||
|
metric_min(&snapshot.transmission_delay_ms),
|
||||||
|
snapshot.transmission_delay_ms.max,
|
||||||
|
metric_avg(&snapshot.propagation_delay_ms),
|
||||||
|
metric_min(&snapshot.propagation_delay_ms),
|
||||||
|
snapshot.propagation_delay_ms.max,
|
||||||
|
metric_avg(&snapshot.end_to_end_delay_ms),
|
||||||
|
metric_min(&snapshot.end_to_end_delay_ms),
|
||||||
|
snapshot.end_to_end_delay_ms.max,
|
||||||
|
snapshot.send_buffer_pct.last,
|
||||||
|
metric_avg(&snapshot.send_buffer_pct),
|
||||||
|
snapshot.send_buffer_pct.max,
|
||||||
|
snapshot.recv_buffer_pct.last,
|
||||||
|
metric_avg(&snapshot.recv_buffer_pct),
|
||||||
|
snapshot.recv_buffer_pct.max,
|
||||||
|
snapshot.cwnd.last,
|
||||||
|
metric_avg(&snapshot.cwnd),
|
||||||
|
snapshot.cwnd.max,
|
||||||
|
(unsigned long long)snapshot.last_rtt_ms,
|
||||||
|
(unsigned long long)((snapshot.min_rtt_ms == UINT64_MAX) ? 0 : snapshot.min_rtt_ms),
|
||||||
|
(unsigned long long)snapshot.max_rtt_ms,
|
||||||
|
(unsigned long long)snapshot.tcp_retrans,
|
||||||
|
(unsigned long long)snapshot.tcp_data_segs_out,
|
||||||
|
(unsigned long long)snapshot.tcp_data_bytes_sent,
|
||||||
|
(unsigned long long)snapshot.tcp_retrans_bytes,
|
||||||
|
(unsigned long long)snapshot.kcp_retrans,
|
||||||
|
(unsigned long long)snapshot.kcp_data_segs_out,
|
||||||
|
(unsigned long long)snapshot.kcp_data_bytes_sent,
|
||||||
|
(unsigned long long)snapshot.kcp_retrans_bytes);
|
||||||
fflush(g_json_fp);
|
fflush(g_json_fp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 通用结构化日志接口。
|
||||||
|
* 文本日志输出到 stderr,JSON 版本同步追加到 omni_logs.jsonl。
|
||||||
|
*/
|
||||||
void logger_log(const char *level, const char *component,
|
void logger_log(const char *level, const char *component,
|
||||||
const char *fmt, ...)
|
const char *fmt, ...)
|
||||||
{
|
{
|
||||||
const char *lvl = level ? level : "INFO";
|
const char *lvl = level ? level : "INFO";
|
||||||
const char *comp = component ? component : "general";
|
const char *comp = component ? component : "general";
|
||||||
|
char msg_buf[2048];
|
||||||
|
uint64_t ts;
|
||||||
|
|
||||||
|
/* 在格式化消息前先做级别过滤,减少无意义开销。 */
|
||||||
if (level_to_int(lvl) < g_min_level) {
|
if (level_to_int(lvl) < g_min_level) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FILE *fp = stderr;
|
|
||||||
print_timestamp(fp);
|
|
||||||
fprintf(fp, "level=%s component=%s ", lvl, comp);
|
|
||||||
|
|
||||||
char msg_buf[1024];
|
|
||||||
va_list ap;
|
va_list ap;
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
vsnprintf(msg_buf, sizeof(msg_buf), fmt, ap);
|
vsnprintf(msg_buf, sizeof(msg_buf), fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
|
|
||||||
fputs(msg_buf, fp);
|
pthread_mutex_lock(&g_mu);
|
||||||
fputc('\n', fp);
|
ts = now_ms();
|
||||||
|
print_timestamp(stderr, ts);
|
||||||
|
fprintf(stderr, "level=%s component=%s ", lvl, comp);
|
||||||
|
fputs(msg_buf, stderr);
|
||||||
|
fputc('\n', stderr);
|
||||||
|
|
||||||
if (g_json_fp) {
|
if (g_json_fp) {
|
||||||
char esc_buf[2048];
|
char esc_buf[4096];
|
||||||
json_escape(msg_buf, esc_buf, sizeof(esc_buf));
|
json_escape(msg_buf, esc_buf, sizeof(esc_buf));
|
||||||
uint64_t ts = now_ms();
|
|
||||||
fprintf(g_json_fp,
|
fprintf(g_json_fp,
|
||||||
"{\"ts_ms\":%llu,"
|
"{\"ts_ms\":%llu,"
|
||||||
"\"level\":\"%s\","
|
"\"level\":\"%s\","
|
||||||
@@ -277,10 +796,18 @@ void logger_log(const char *level, const char *component,
|
|||||||
esc_buf);
|
esc_buf);
|
||||||
fflush(g_json_fp);
|
fflush(g_json_fp);
|
||||||
}
|
}
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 返回当前统计快照,供 summary 或诊断逻辑读取。 */
|
||||||
OmniStats logger_get_snapshot(void)
|
OmniStats logger_get_snapshot(void)
|
||||||
{
|
{
|
||||||
return g_stats;
|
OmniStats snapshot;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
refresh_throughput_locked(now_ms());
|
||||||
|
snapshot = g_stats;
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,12 +93,11 @@ ssize_t omni_send(OmniContext *ctx, const void *buf, size_t len)
|
|||||||
logger_on_send_call_latency(t1 - t0);
|
logger_on_send_call_latency(t1 - t0);
|
||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
logger_on_send((size_t)n);
|
logger_on_send((size_t)n);
|
||||||
|
logger_on_send_transmission_bytes((size_t)n);
|
||||||
|
logger_maybe_print_performance_log("on_send");
|
||||||
}
|
}
|
||||||
logger_log("DEBUG", "network", "omni_send proto=%d bytes=%zd call_ms=%llu",
|
logger_log("DEBUG", "network", "omni_send proto=%d bytes=%zd call_ms=%llu",
|
||||||
(int)ctx->proto, n, (unsigned long long)(t1 - t0));
|
(int)ctx->proto, n, (unsigned long long)(t1 - t0));
|
||||||
if (n > 0) {
|
|
||||||
logger_print_performance_log("on_send");
|
|
||||||
}
|
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,12 +113,11 @@ ssize_t omni_recv(OmniContext *ctx, void *buf, size_t len)
|
|||||||
logger_on_recv_call_latency(t1 - t0);
|
logger_on_recv_call_latency(t1 - t0);
|
||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
logger_on_recv((size_t)n);
|
logger_on_recv((size_t)n);
|
||||||
|
logger_on_recv_transmission_bytes((size_t)n);
|
||||||
|
logger_maybe_print_performance_log("on_recv");
|
||||||
}
|
}
|
||||||
logger_log("DEBUG", "network", "omni_recv proto=%d bytes=%zd call_ms=%llu",
|
logger_log("DEBUG", "network", "omni_recv proto=%d bytes=%zd call_ms=%llu",
|
||||||
(int)ctx->proto, n, (unsigned long long)(t1 - t0));
|
(int)ctx->proto, n, (unsigned long long)(t1 - t0));
|
||||||
if (n > 0) {
|
|
||||||
logger_print_performance_log("on_recv");
|
|
||||||
}
|
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,75 @@ struct KcpContext {
|
|||||||
struct sockaddr_in peer_addr;
|
struct sockaddr_in peer_addr;
|
||||||
socklen_t peer_len;
|
socklen_t peer_len;
|
||||||
ikcpcb *kcp;
|
ikcpcb *kcp;
|
||||||
uint32_t last_xmit; /* 用于推算重传/发送次数变化 */
|
uint32_t *seg_xmit_seen; /* 按 sn 记录上次采样到的 xmit 值 */
|
||||||
|
size_t seg_xmit_cap;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static int kcp_ensure_seg_track_capacity(struct KcpContext *ctx, uint32_t sn)
|
||||||
|
{
|
||||||
|
size_t need;
|
||||||
|
size_t new_cap;
|
||||||
|
uint32_t *new_seen;
|
||||||
|
|
||||||
|
if (!ctx) return OMNI_ERR_PARAM;
|
||||||
|
|
||||||
|
need = (size_t)sn + 1u;
|
||||||
|
if (need <= ctx->seg_xmit_cap) {
|
||||||
|
return OMNI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_cap = (ctx->seg_xmit_cap == 0) ? 64u : ctx->seg_xmit_cap;
|
||||||
|
while (new_cap < need) {
|
||||||
|
new_cap *= 2u;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_seen = (uint32_t *)realloc(ctx->seg_xmit_seen, new_cap * sizeof(uint32_t));
|
||||||
|
if (!new_seen) {
|
||||||
|
return OMNI_ERR_GENERIC;
|
||||||
|
}
|
||||||
|
memset(new_seen + ctx->seg_xmit_cap, 0,
|
||||||
|
(new_cap - ctx->seg_xmit_cap) * sizeof(uint32_t));
|
||||||
|
ctx->seg_xmit_seen = new_seen;
|
||||||
|
ctx->seg_xmit_cap = new_cap;
|
||||||
|
return OMNI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void kcp_sample_transport_stats(struct KcpContext *ctx)
|
||||||
|
{
|
||||||
|
struct IQUEUEHEAD *p;
|
||||||
|
|
||||||
|
if (!ctx || !ctx->kcp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (p = ctx->kcp->snd_buf.next; p != &ctx->kcp->snd_buf; p = p->next) {
|
||||||
|
struct IKCPSEG *segment = iqueue_entry(p, struct IKCPSEG, node);
|
||||||
|
uint32_t prev_xmit;
|
||||||
|
|
||||||
|
if (!segment || segment->len == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (kcp_ensure_seg_track_capacity(ctx, segment->sn) != OMNI_OK) {
|
||||||
|
logger_log("WARN", "kcp", "seg_track_alloc_failed sn=%u",
|
||||||
|
(unsigned)segment->sn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prev_xmit = ctx->seg_xmit_seen[segment->sn];
|
||||||
|
if (prev_xmit == 0 && segment->xmit > 0) {
|
||||||
|
logger_on_kcp_tx(1u, (uint64_t)segment->len);
|
||||||
|
prev_xmit = 1u;
|
||||||
|
}
|
||||||
|
if (segment->xmit > prev_xmit) {
|
||||||
|
uint32_t delta = segment->xmit - prev_xmit;
|
||||||
|
logger_on_kcp_retrans((uint64_t)delta,
|
||||||
|
(uint64_t)delta * (uint64_t)segment->len);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->seg_xmit_seen[segment->sn] = segment->xmit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int kcp_output(const char *buf, int len, ikcpcb *kcp, void *user)
|
static int kcp_output(const char *buf, int len, ikcpcb *kcp, void *user)
|
||||||
{
|
{
|
||||||
(void)kcp;
|
(void)kcp;
|
||||||
@@ -120,12 +186,36 @@ static void kcp_update_loop(struct KcpContext *ctx)
|
|||||||
ikcp_input(ctx->kcp, buf, (long)n);
|
ikcp_input(ctx->kcp, buf, (long)n);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* KCP 内部状态监控 */
|
/* KCP 内部状态监控:按分片 xmit 变化统计首次发送和真实重传。 */
|
||||||
uint32_t xmit = ctx->kcp->xmit;
|
kcp_sample_transport_stats(ctx);
|
||||||
if (xmit >= ctx->last_xmit) {
|
/* rx_srtt 是 KCP 平滑后的 RTT(单位 ms),大于 0 才说明已经有有效采样值。 */
|
||||||
logger_on_kcp_retrans((uint64_t)(xmit - ctx->last_xmit));
|
if (ctx->kcp->rx_srtt > 0) {
|
||||||
|
logger_on_rtt((uint64_t)ctx->kcp->rx_srtt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* cwnd 是当前拥塞窗口大小,用来观察 KCP 的拥塞控制是否在收缩或增长。 */
|
||||||
|
logger_on_cwnd((double)ctx->kcp->cwnd);
|
||||||
|
|
||||||
|
/* 统计发送/接收窗口的占用百分比,方便观察当前缓冲区压力。 */
|
||||||
|
{
|
||||||
|
double send_pct = 0.0;
|
||||||
|
double recv_pct = 0.0;
|
||||||
|
|
||||||
|
/* ikcp_waitsnd() 表示还在发送路径中的分片数量,用发送窗口大小换算成占用率。 */
|
||||||
|
if (ctx->kcp->snd_wnd > 0) {
|
||||||
|
send_pct = ((double)ikcp_waitsnd(ctx->kcp) * 100.0) / (double)ctx->kcp->snd_wnd;
|
||||||
|
logger_on_send_queue_bytes((size_t)ikcp_waitsnd(ctx->kcp) * (size_t)ctx->kcp->mss);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* nrcv_que 是已经收到、等待应用层取走的数据包数量,用接收窗口换算成占用率。 */
|
||||||
|
if (ctx->kcp->rcv_wnd > 0) {
|
||||||
|
recv_pct = ((double)ctx->kcp->nrcv_que * 100.0) / (double)ctx->kcp->rcv_wnd;
|
||||||
|
logger_on_recv_queue_bytes((size_t)ctx->kcp->nrcv_que * (size_t)ctx->kcp->mss);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 将收发两侧的缓冲区占用情况统一上报给监控模块。 */
|
||||||
|
logger_on_buffer_status(send_pct, recv_pct);
|
||||||
}
|
}
|
||||||
ctx->last_xmit = xmit;
|
|
||||||
|
|
||||||
uint64_t t1 = omni_now_ms();
|
uint64_t t1 = omni_now_ms();
|
||||||
logger_log("DEBUG", "kcp",
|
logger_log("DEBUG", "kcp",
|
||||||
@@ -197,6 +287,7 @@ static void kcp_close(OmniContext *c)
|
|||||||
if (ctx->fd >= 0) {
|
if (ctx->fd >= 0) {
|
||||||
close(ctx->fd);
|
close(ctx->fd);
|
||||||
}
|
}
|
||||||
|
free(ctx->seg_xmit_seen);
|
||||||
free(ctx);
|
free(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,14 +17,90 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <netinet/tcp.h>
|
#include <netinet/tcp.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
|
#include <stddef.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
/* Linux 下 TCP_INFO 定义通常已在 <netinet/tcp.h> 提供,避免引入 <linux/tcp.h> 重定义 */
|
/*
|
||||||
|
* Linux 下 glibc 的 <netinet/tcp.h> 只暴露了 tcp_info 的旧前缀字段,
|
||||||
|
* 这里复制到 tcpi_bytes_retrans 为止的内核布局,用来安全读取扩展快照。
|
||||||
|
*/
|
||||||
|
#ifdef __linux__
|
||||||
|
struct OmniLinuxTcpInfo {
|
||||||
|
uint8_t tcpi_state;
|
||||||
|
uint8_t tcpi_ca_state;
|
||||||
|
uint8_t tcpi_retransmits;
|
||||||
|
uint8_t tcpi_probes;
|
||||||
|
uint8_t tcpi_backoff;
|
||||||
|
uint8_t tcpi_options;
|
||||||
|
uint8_t tcpi_snd_wscale : 4, tcpi_rcv_wscale : 4;
|
||||||
|
uint8_t tcpi_delivery_rate_app_limited : 1, tcpi_fastopen_client_fail : 2;
|
||||||
|
|
||||||
|
uint32_t tcpi_rto;
|
||||||
|
uint32_t tcpi_ato;
|
||||||
|
uint32_t tcpi_snd_mss;
|
||||||
|
uint32_t tcpi_rcv_mss;
|
||||||
|
|
||||||
|
uint32_t tcpi_unacked;
|
||||||
|
uint32_t tcpi_sacked;
|
||||||
|
uint32_t tcpi_lost;
|
||||||
|
uint32_t tcpi_retrans;
|
||||||
|
uint32_t tcpi_fackets;
|
||||||
|
|
||||||
|
uint32_t tcpi_last_data_sent;
|
||||||
|
uint32_t tcpi_last_ack_sent;
|
||||||
|
uint32_t tcpi_last_data_recv;
|
||||||
|
uint32_t tcpi_last_ack_recv;
|
||||||
|
|
||||||
|
uint32_t tcpi_pmtu;
|
||||||
|
uint32_t tcpi_rcv_ssthresh;
|
||||||
|
uint32_t tcpi_rtt;
|
||||||
|
uint32_t tcpi_rttvar;
|
||||||
|
uint32_t tcpi_snd_ssthresh;
|
||||||
|
uint32_t tcpi_snd_cwnd;
|
||||||
|
uint32_t tcpi_advmss;
|
||||||
|
uint32_t tcpi_reordering;
|
||||||
|
|
||||||
|
uint32_t tcpi_rcv_rtt;
|
||||||
|
uint32_t tcpi_rcv_space;
|
||||||
|
|
||||||
|
uint32_t tcpi_total_retrans;
|
||||||
|
|
||||||
|
uint64_t tcpi_pacing_rate;
|
||||||
|
uint64_t tcpi_max_pacing_rate;
|
||||||
|
uint64_t tcpi_bytes_acked;
|
||||||
|
uint64_t tcpi_bytes_received;
|
||||||
|
uint32_t tcpi_segs_out;
|
||||||
|
uint32_t tcpi_segs_in;
|
||||||
|
|
||||||
|
uint32_t tcpi_notsent_bytes;
|
||||||
|
uint32_t tcpi_min_rtt;
|
||||||
|
uint32_t tcpi_data_segs_in;
|
||||||
|
uint32_t tcpi_data_segs_out;
|
||||||
|
|
||||||
|
uint64_t tcpi_delivery_rate;
|
||||||
|
|
||||||
|
uint64_t tcpi_busy_time;
|
||||||
|
uint64_t tcpi_rwnd_limited;
|
||||||
|
uint64_t tcpi_sndbuf_limited;
|
||||||
|
|
||||||
|
uint32_t tcpi_delivered;
|
||||||
|
uint32_t tcpi_delivered_ce;
|
||||||
|
|
||||||
|
uint64_t tcpi_bytes_sent;
|
||||||
|
uint64_t tcpi_bytes_retrans;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int tcp_info_has_field(socklen_t len, size_t field_end)
|
||||||
|
{
|
||||||
|
return (size_t)len >= field_end;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
struct TcpContext {
|
struct TcpContext {
|
||||||
/* 已建立连接的 socket fd(服务端 accept 后或客户端 connect 后)。 */
|
/* 已建立连接的 socket fd(服务端 accept 后或客户端 connect 后)。 */
|
||||||
@@ -32,10 +108,62 @@ struct TcpContext {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
|
static void tcp_sample_socket_buffers(int fd)
|
||||||
|
{
|
||||||
|
/* socket 层配置的发送/接收缓冲区总大小。 */
|
||||||
|
int sndbuf = 0;
|
||||||
|
int rcvbuf = 0;
|
||||||
|
socklen_t optlen = sizeof(int);
|
||||||
|
|
||||||
|
/* 当前内核发送队列/接收队列里还压着的字节数。 */
|
||||||
|
int outq = 0;
|
||||||
|
int inq = 0;
|
||||||
|
|
||||||
|
/* 最终换算成百分比后上报,表示缓冲区占用压力。 */
|
||||||
|
double send_pct = 0.0;
|
||||||
|
double recv_pct = 0.0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SO_SNDBUF 取发送缓冲区容量;
|
||||||
|
* TIOCOUTQ 取当前还没真正发出去的字节数。
|
||||||
|
* 两者相除后得到发送侧缓冲占用率。
|
||||||
|
*/
|
||||||
|
if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, &optlen) == 0 && sndbuf > 0) {
|
||||||
|
#ifdef TIOCOUTQ
|
||||||
|
if (ioctl(fd, TIOCOUTQ, &outq) == 0 && outq >= 0) {
|
||||||
|
send_pct = ((double)outq * 100.0) / (double)sndbuf;
|
||||||
|
logger_on_send_queue_bytes((size_t)outq);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SO_RCVBUF 取接收缓冲区容量;
|
||||||
|
* FIONREAD 取当前已经到达、但应用层还没 read 的字节数。
|
||||||
|
* 两者相除后得到接收侧缓冲占用率。
|
||||||
|
*/
|
||||||
|
optlen = sizeof(int);
|
||||||
|
if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &optlen) == 0 && rcvbuf > 0) {
|
||||||
|
if (ioctl(fd, FIONREAD, &inq) == 0 && inq >= 0) {
|
||||||
|
recv_pct = ((double)inq * 100.0) / (double)rcvbuf;
|
||||||
|
logger_on_recv_queue_bytes((size_t)inq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 将 TCP 收发缓冲区的占用情况统一交给 logger 记录。 */
|
||||||
|
logger_on_buffer_status(send_pct, recv_pct);
|
||||||
|
}
|
||||||
|
|
||||||
static void tcp_log_info(int fd, const char *tag)
|
static void tcp_log_info(int fd, const char *tag)
|
||||||
{
|
{
|
||||||
struct tcp_info ti;
|
struct OmniLinuxTcpInfo ti;
|
||||||
|
uint64_t total_retrans = 0;
|
||||||
|
uint64_t data_segs_out = 0;
|
||||||
|
uint64_t bytes_sent = 0;
|
||||||
|
uint64_t bytes_retrans = 0;
|
||||||
socklen_t len = sizeof(ti);
|
socklen_t len = sizeof(ti);
|
||||||
|
|
||||||
|
memset(&ti, 0, sizeof(ti));
|
||||||
if (getsockopt(fd, IPPROTO_TCP, TCP_INFO, &ti, &len) != 0) {
|
if (getsockopt(fd, IPPROTO_TCP, TCP_INFO, &ti, &len) != 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -44,11 +172,31 @@ static void tcp_log_info(int fd, const char *tag)
|
|||||||
unsigned long long rtt_ms = (unsigned long long)(ti.tcpi_rtt / 1000u);
|
unsigned long long rtt_ms = (unsigned long long)(ti.tcpi_rtt / 1000u);
|
||||||
unsigned long long rttvar_ms = (unsigned long long)(ti.tcpi_rttvar / 1000u);
|
unsigned long long rttvar_ms = (unsigned long long)(ti.tcpi_rttvar / 1000u);
|
||||||
|
|
||||||
|
if (tcp_info_has_field(len, offsetof(struct OmniLinuxTcpInfo, tcpi_total_retrans) +
|
||||||
|
sizeof(ti.tcpi_total_retrans))) {
|
||||||
|
total_retrans = (uint64_t)ti.tcpi_total_retrans;
|
||||||
|
} else {
|
||||||
|
total_retrans = (uint64_t)ti.tcpi_retrans;
|
||||||
|
}
|
||||||
|
if (tcp_info_has_field(len, offsetof(struct OmniLinuxTcpInfo, tcpi_data_segs_out) +
|
||||||
|
sizeof(ti.tcpi_data_segs_out))) {
|
||||||
|
data_segs_out = (uint64_t)ti.tcpi_data_segs_out;
|
||||||
|
}
|
||||||
|
if (tcp_info_has_field(len, offsetof(struct OmniLinuxTcpInfo, tcpi_bytes_sent) +
|
||||||
|
sizeof(ti.tcpi_bytes_sent))) {
|
||||||
|
bytes_sent = (uint64_t)ti.tcpi_bytes_sent;
|
||||||
|
}
|
||||||
|
if (tcp_info_has_field(len, offsetof(struct OmniLinuxTcpInfo, tcpi_bytes_retrans) +
|
||||||
|
sizeof(ti.tcpi_bytes_retrans))) {
|
||||||
|
bytes_retrans = (uint64_t)ti.tcpi_bytes_retrans;
|
||||||
|
}
|
||||||
|
|
||||||
logger_log("INFO", "tcpinfo",
|
logger_log("INFO", "tcpinfo",
|
||||||
"tag=%s state=%u retransmits=%u probes=%u backoff=%u "
|
"tag=%s state=%u retransmits=%u probes=%u backoff=%u "
|
||||||
"rto=%u ato=%u rtt_ms=%llu rttvar_ms=%llu "
|
"rto=%u ato=%u rtt_ms=%llu rttvar_ms=%llu "
|
||||||
"snd_cwnd=%u snd_ssthresh=%u snd_mss=%u rcv_mss=%u "
|
"snd_cwnd=%u snd_ssthresh=%u snd_mss=%u rcv_mss=%u "
|
||||||
"lost=%u retrans=%u fackets=%u "
|
"lost=%u retrans=%u total_retrans=%llu data_segs_out=%llu "
|
||||||
|
"bytes_sent=%llu bytes_retrans=%llu fackets=%u "
|
||||||
"last_data_sent_ms=%u last_data_recv_ms=%u",
|
"last_data_sent_ms=%u last_data_recv_ms=%u",
|
||||||
tag ? tag : "sample",
|
tag ? tag : "sample",
|
||||||
(unsigned)ti.tcpi_state,
|
(unsigned)ti.tcpi_state,
|
||||||
@@ -65,9 +213,18 @@ static void tcp_log_info(int fd, const char *tag)
|
|||||||
(unsigned)ti.tcpi_rcv_mss,
|
(unsigned)ti.tcpi_rcv_mss,
|
||||||
(unsigned)ti.tcpi_lost,
|
(unsigned)ti.tcpi_lost,
|
||||||
(unsigned)ti.tcpi_retrans,
|
(unsigned)ti.tcpi_retrans,
|
||||||
|
(unsigned long long)total_retrans,
|
||||||
|
(unsigned long long)data_segs_out,
|
||||||
|
(unsigned long long)bytes_sent,
|
||||||
|
(unsigned long long)bytes_retrans,
|
||||||
(unsigned)ti.tcpi_fackets,
|
(unsigned)ti.tcpi_fackets,
|
||||||
(unsigned)ti.tcpi_last_data_sent,
|
(unsigned)ti.tcpi_last_data_sent,
|
||||||
(unsigned)ti.tcpi_last_data_recv);
|
(unsigned)ti.tcpi_last_data_recv);
|
||||||
|
|
||||||
|
logger_on_rtt(rtt_ms);
|
||||||
|
logger_on_tcp_transport(total_retrans, data_segs_out, bytes_sent, bytes_retrans);
|
||||||
|
logger_on_cwnd((double)ti.tcpi_snd_cwnd);
|
||||||
|
tcp_sample_socket_buffers(fd);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@@ -22,6 +23,52 @@ struct UdpContext {
|
|||||||
socklen_t peer_len;
|
socklen_t peer_len;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void udp_sample_socket_buffers(int fd)
|
||||||
|
{
|
||||||
|
/* socket 层配置的发送/接收缓冲区总大小。 */
|
||||||
|
int sndbuf = 0;
|
||||||
|
int rcvbuf = 0;
|
||||||
|
socklen_t optlen = sizeof(int);
|
||||||
|
|
||||||
|
/* 当前内核发送队列/接收队列里还压着的字节数。 */
|
||||||
|
int outq = 0;
|
||||||
|
int inq = 0;
|
||||||
|
|
||||||
|
/* 最终换算成百分比后上报,表示缓冲区占用压力。 */
|
||||||
|
double send_pct = 0.0;
|
||||||
|
double recv_pct = 0.0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SO_SNDBUF 取发送缓冲区容量;
|
||||||
|
* TIOCOUTQ 取当前还没从内核发送队列发走的字节数。
|
||||||
|
* 两者相除后得到发送侧缓冲占用率。
|
||||||
|
*/
|
||||||
|
if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, &optlen) == 0 && sndbuf > 0) {
|
||||||
|
#ifdef TIOCOUTQ
|
||||||
|
if (ioctl(fd, TIOCOUTQ, &outq) == 0 && outq >= 0) {
|
||||||
|
send_pct = ((double)outq * 100.0) / (double)sndbuf;
|
||||||
|
logger_on_send_queue_bytes((size_t)outq);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SO_RCVBUF 取接收缓冲区容量;
|
||||||
|
* FIONREAD 取当前已经到达、但应用层还没 recv 的字节数。
|
||||||
|
* 两者相除后得到接收侧缓冲占用率。
|
||||||
|
*/
|
||||||
|
optlen = sizeof(int);
|
||||||
|
if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &optlen) == 0 && rcvbuf > 0) {
|
||||||
|
if (ioctl(fd, FIONREAD, &inq) == 0 && inq >= 0) {
|
||||||
|
recv_pct = ((double)inq * 100.0) / (double)rcvbuf;
|
||||||
|
logger_on_recv_queue_bytes((size_t)inq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 将 UDP 收发缓冲区的占用情况统一交给 logger 记录。 */
|
||||||
|
logger_on_buffer_status(send_pct, recv_pct);
|
||||||
|
}
|
||||||
|
|
||||||
static OmniContext *udp_init(OmniRole role,
|
static OmniContext *udp_init(OmniRole role,
|
||||||
const char *bind_ip,
|
const char *bind_ip,
|
||||||
uint16_t bind_port,
|
uint16_t bind_port,
|
||||||
@@ -82,6 +129,7 @@ static ssize_t udp_send(OmniContext *c, const void *buf, size_t len)
|
|||||||
logger_log("ERROR", "udp", "sendto_failed errno=%d", errno);
|
logger_log("ERROR", "udp", "sendto_failed errno=%d", errno);
|
||||||
return OMNI_ERR_IO;
|
return OMNI_ERR_IO;
|
||||||
}
|
}
|
||||||
|
udp_sample_socket_buffers(ctx->fd);
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +151,7 @@ static ssize_t udp_recv(OmniContext *c, void *buf, size_t len)
|
|||||||
/* 默认更新 peer 为最近一次通信对端,便于“伪长连接” */
|
/* 默认更新 peer 为最近一次通信对端,便于“伪长连接” */
|
||||||
ctx->peer_addr = from;
|
ctx->peer_addr = from;
|
||||||
ctx->peer_len = fromlen;
|
ctx->peer_len = fromlen;
|
||||||
|
udp_sample_socket_buffers(ctx->fd);
|
||||||
|
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user