/* * common.h * 全局公共定义:消息头、错误码、通用宏 * * 这个头文件承担两类职责: * 1) 定义跨模块共享的线协议结构,保证 client / server / relay 的编码口径一致。 * 2) 提供与协议无关的基础工具,例如时间戳和 64 位字节序转换。 */ #ifndef OMNISOCKET_COMMON_H #define OMNISOCKET_COMMON_H #include #include #include #include #include /* * 统一的 16 字节应用层消息头。 * 注意:这不是 TCP/KCP/UDP 的传输层头,而是项目自己定义的“业务帧头”。 */ typedef struct MsgHeader { uint32_t type; /* 消息类型:文件块/控制指令等 */ uint32_t len; /* 后续负载长度(字节数) */ uint64_t timestamp; /* 发送时间戳(毫秒) */ } MsgHeader; #define MSG_HEADER_SIZE (sizeof(MsgHeader)) /* 16 字节 */ /* 应用层消息类型约定。 */ enum { MSG_TYPE_FILE_CHUNK = 1, //文件数据分片(TransferChunkMeta + 实际文件字节) MSG_TYPE_FILE_END = 2, //文件传输结束通知 MSG_TYPE_COMMAND = 3, //控制指令(服务端发起+客户端响应) 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 /* * 文件分片元数据: * - 用于进度统计、端到端延迟估算、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 { OMNI_OK = 0, OMNI_ERR_GENERIC = -1, OMNI_ERR_PARAM = -2, OMNI_ERR_IO = -3, OMNI_ERR_TIMEOUT = -4 }; /* 获取当前单调时间(毫秒),避免系统时间回拨影响延迟统计。 */ static inline uint64_t omni_now_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t)ts.tv_sec * 1000u + (uint64_t)(ts.tv_nsec / 1000000u); } /* * 手写 64 位字节翻转工具。 * 之所以单独实现,是因为标准库广泛提供 htonl/ntohl,但 64 位版本并非所有平台都统一。 */ static inline uint64_t omni_bswap64(uint64_t x) { return ((x & 0x00000000000000FFull) << 56) | ((x & 0x000000000000FF00ull) << 40) | ((x & 0x0000000000FF0000ull) << 24) | ((x & 0x00000000FF000000ull) << 8) | ((x & 0x000000FF00000000ull) >> 8) | ((x & 0x0000FF0000000000ull) >> 24) | ((x & 0x00FF000000000000ull) >> 40) | ((x & 0xFF00000000000000ull) >> 56); } /* 将主机字节序的 64 位整数编码成网络字节序。 */ static inline uint64_t omni_htonll(uint64_t x) { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ return omni_bswap64(x); #else return x; #endif } /* 64 位网络字节序转回主机字节序;实现上与 htonll 对称。 */ static inline uint64_t omni_ntohll(uint64_t 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, uint32_t type, uint32_t len, uint64_t timestamp_ms) { out_hdr->type = htonl(type); out_hdr->len = htonl(len); out_hdr->timestamp = omni_htonll(timestamp_ms); } /* 将网络帧头解码回本机可直接使用的主机字节序结构。 */ static inline void omni_msg_header_decode(const MsgHeader *net_hdr, MsgHeader *host_hdr) { host_hdr->type = ntohl(net_hdr->type); host_hdr->len = ntohl(net_hdr->len); host_hdr->timestamp = omni_ntohll(net_hdr->timestamp); } /* 编码单个文件分片的元数据,发送端在每个 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 */