From 7dc00165d9f8075718dc05b750a6e897e182eb47 Mon Sep 17 00:00:00 2001 From: meiqi <976161896@qq.com> Date: Fri, 13 Mar 2026 15:22:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9D=E5=A7=8B=E5=8C=96=20OmniSocke?= =?UTF-8?q?t=20=E7=9B=AE=E5=BD=95=E7=BB=93=E6=9E=84=E4=B8=8E=E5=BA=95?= =?UTF-8?q?=E5=B1=82=E7=BD=91=E7=BB=9C=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 16 + ikcp.o | Bin 0 -> 22576 bytes include/common.h | 40 ++ include/kcp/ikcp.h | 416 ++++++++++++ include/logger.h | 58 ++ include/network.h | 78 +++ kcp_impl.o | Bin 0 -> 6320 bytes logger.o | Bin 0 -> 5792 bytes network.o | Bin 0 -> 4240 bytes src/core/logger.c | 118 ++++ src/core/network.c | 123 ++++ src/protocols/ikcp.c | 1306 ++++++++++++++++++++++++++++++++++++++ src/protocols/kcp_impl.c | 171 +++++ src/protocols/tcp_impl.c | 255 ++++++++ src/protocols/udp_impl.c | 126 ++++ tcp_impl.o | Bin 0 -> 7912 bytes udp_impl.o | Bin 0 -> 4728 bytes 17 files changed, 2707 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 ikcp.o create mode 100644 include/common.h create mode 100644 include/kcp/ikcp.h create mode 100644 include/logger.h create mode 100644 include/network.h create mode 100644 kcp_impl.o create mode 100644 logger.o create mode 100644 network.o create mode 100644 src/core/logger.c create mode 100644 src/core/network.c create mode 100644 src/protocols/ikcp.c create mode 100644 src/protocols/kcp_impl.c create mode 100644 src/protocols/tcp_impl.c create mode 100644 src/protocols/udp_impl.c create mode 100644 tcp_impl.o create mode 100644 udp_impl.o diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..c4a06f5 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "OmniSocket", + "type": "lldb", + "request": "launch", + "program": "${workspaceRoot}/build", + "args": [], + "cwd": "${workspaceRoot}" + } + ] +} \ No newline at end of file diff --git a/ikcp.o b/ikcp.o new file mode 100644 index 0000000000000000000000000000000000000000..43654d2b10679857136946cbf2efc1fa12a9e843 GIT binary patch literal 22576 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 literal 0 HcmV?d00001 diff --git a/include/common.h b/include/common.h new file mode 100644 index 0000000..2d804f9 --- /dev/null +++ b/include/common.h @@ -0,0 +1,40 @@ +/* + * common.h + * 全局公共定义:消息头、错误码、通用宏 + */ + +#ifndef OMNISOCKET_COMMON_H +#define OMNISOCKET_COMMON_H + +#include +#include + +/* 统一的 16 字节消息头(解决 TCP 粘包用) */ +typedef struct MsgHeader { + uint32_t magic; /* 固定魔数,用于快速校验 */ + uint32_t length; /* 后续负载长度(字节数) */ + uint64_t seq; /* 序列号或会话内消息 ID */ +} MsgHeader; + +#define MSG_HEADER_SIZE (sizeof(MsgHeader)) /* 16 字节 */ +#define MSG_MAGIC 0x4F4D4E49u /* 'OMNI' */ + +/* 通用错误码(负数返回表示出错) */ +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); +} + +#endif /* OMNISOCKET_COMMON_H */ + diff --git a/include/kcp/ikcp.h b/include/kcp/ikcp.h new file mode 100644 index 0000000..e525105 --- /dev/null +++ b/include/kcp/ikcp.h @@ -0,0 +1,416 @@ +//===================================================================== +// +// KCP - A Better ARQ Protocol Implementation +// skywind3000 (at) gmail.com, 2010-2011 +// +// Features: +// + Average RTT reduce 30% - 40% vs traditional ARQ like tcp. +// + Maximum RTT reduce three times vs tcp. +// + Lightweight, distributed as a single source file. +// +//===================================================================== +#ifndef __IKCP_H__ +#define __IKCP_H__ + +#include +#include +#include + + +//===================================================================== +// 32BIT INTEGER DEFINITION +//===================================================================== +#ifndef __INTEGER_32_BITS__ +#define __INTEGER_32_BITS__ +#if defined(_WIN64) || defined(WIN64) || defined(__amd64__) || \ + defined(__x86_64) || defined(__x86_64__) || defined(_M_IA64) || \ + defined(_M_AMD64) + typedef unsigned int ISTDUINT32; + typedef int ISTDINT32; +#elif defined(_WIN32) || defined(WIN32) || defined(__i386__) || \ + defined(__i386) || defined(_M_X86) + typedef unsigned long ISTDUINT32; + typedef long ISTDINT32; +#elif defined(__MACOS__) + typedef UInt32 ISTDUINT32; + typedef SInt32 ISTDINT32; +#elif defined(__APPLE__) && defined(__MACH__) + #include + typedef u_int32_t ISTDUINT32; + typedef int32_t ISTDINT32; +#elif defined(__BEOS__) + #include + typedef u_int32_t ISTDUINT32; + typedef int32_t ISTDINT32; +#elif (defined(_MSC_VER) || defined(__BORLANDC__)) && (!defined(__MSDOS__)) + typedef unsigned __int32 ISTDUINT32; + typedef __int32 ISTDINT32; +#elif defined(__GNUC__) + #include + typedef uint32_t ISTDUINT32; + typedef int32_t ISTDINT32; +#else + typedef unsigned long ISTDUINT32; + typedef long ISTDINT32; +#endif +#endif + + +//===================================================================== +// Integer Definition +//===================================================================== +#ifndef __IINT8_DEFINED +#define __IINT8_DEFINED +typedef char IINT8; +#endif + +#ifndef __IUINT8_DEFINED +#define __IUINT8_DEFINED +typedef unsigned char IUINT8; +#endif + +#ifndef __IUINT16_DEFINED +#define __IUINT16_DEFINED +typedef unsigned short IUINT16; +#endif + +#ifndef __IINT16_DEFINED +#define __IINT16_DEFINED +typedef short IINT16; +#endif + +#ifndef __IINT32_DEFINED +#define __IINT32_DEFINED +typedef ISTDINT32 IINT32; +#endif + +#ifndef __IUINT32_DEFINED +#define __IUINT32_DEFINED +typedef ISTDUINT32 IUINT32; +#endif + +#ifndef __IINT64_DEFINED +#define __IINT64_DEFINED +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef __int64 IINT64; +#else +typedef long long IINT64; +#endif +#endif + +#ifndef __IUINT64_DEFINED +#define __IUINT64_DEFINED +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef unsigned __int64 IUINT64; +#else +typedef unsigned long long IUINT64; +#endif +#endif + +#ifndef INLINE +#if defined(__GNUC__) + +#if (__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1)) +#define INLINE __inline__ __attribute__((always_inline)) +#else +#define INLINE __inline__ +#endif + +#elif (defined(_MSC_VER) || defined(__BORLANDC__) || defined(__WATCOMC__)) +#define INLINE __inline +#else +#define INLINE +#endif +#endif + +#if (!defined(__cplusplus)) && (!defined(inline)) +#define inline INLINE +#endif + + +//===================================================================== +// QUEUE DEFINITION +//===================================================================== +#ifndef __IQUEUE_DEF__ +#define __IQUEUE_DEF__ + +struct IQUEUEHEAD { + struct IQUEUEHEAD *next, *prev; +}; + +typedef struct IQUEUEHEAD iqueue_head; + + +//--------------------------------------------------------------------- +// queue init +//--------------------------------------------------------------------- +#define IQUEUE_HEAD_INIT(name) { &(name), &(name) } +#define IQUEUE_HEAD(name) \ + struct IQUEUEHEAD name = IQUEUE_HEAD_INIT(name) + +#define IQUEUE_INIT(ptr) ( \ + (ptr)->next = (ptr), (ptr)->prev = (ptr)) + +#define IOFFSETOF(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) + +#define ICONTAINEROF(ptr, type, member) ( \ + (type*)( ((char*)((type*)ptr)) - IOFFSETOF(type, member)) ) + +#define IQUEUE_ENTRY(ptr, type, member) ICONTAINEROF(ptr, type, member) + + +//--------------------------------------------------------------------- +// queue operation +//--------------------------------------------------------------------- +#define IQUEUE_ADD(node, head) ( \ + (node)->prev = (head), (node)->next = (head)->next, \ + (head)->next->prev = (node), (head)->next = (node)) + +#define IQUEUE_ADD_TAIL(node, head) ( \ + (node)->prev = (head)->prev, (node)->next = (head), \ + (head)->prev->next = (node), (head)->prev = (node)) + +#define IQUEUE_DEL_BETWEEN(p, n) ((n)->prev = (p), (p)->next = (n)) + +#define IQUEUE_DEL(entry) (\ + (entry)->next->prev = (entry)->prev, \ + (entry)->prev->next = (entry)->next, \ + (entry)->next = 0, (entry)->prev = 0) + +#define IQUEUE_DEL_INIT(entry) do { \ + IQUEUE_DEL(entry); IQUEUE_INIT(entry); } while (0) + +#define IQUEUE_IS_EMPTY(entry) ((entry) == (entry)->next) + +#define iqueue_init IQUEUE_INIT +#define iqueue_entry IQUEUE_ENTRY +#define iqueue_add IQUEUE_ADD +#define iqueue_add_tail IQUEUE_ADD_TAIL +#define iqueue_del IQUEUE_DEL +#define iqueue_del_init IQUEUE_DEL_INIT +#define iqueue_is_empty IQUEUE_IS_EMPTY + +#define IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) \ + for ((iterator) = iqueue_entry((head)->next, TYPE, MEMBER); \ + &((iterator)->MEMBER) != (head); \ + (iterator) = iqueue_entry((iterator)->MEMBER.next, TYPE, MEMBER)) + +#define iqueue_foreach(iterator, head, TYPE, MEMBER) \ + IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) + +#define iqueue_foreach_entry(pos, head) \ + for( (pos) = (head)->next; (pos) != (head) ; (pos) = (pos)->next ) + + +#define __iqueue_splice(list, head) do { \ + iqueue_head *first = (list)->next, *last = (list)->prev; \ + iqueue_head *at = (head)->next; \ + (first)->prev = (head), (head)->next = (first); \ + (last)->next = (at), (at)->prev = (last); } while (0) + +#define iqueue_splice(list, head) do { \ + if (!iqueue_is_empty(list)) __iqueue_splice(list, head); } while (0) + +#define iqueue_splice_init(list, head) do { \ + iqueue_splice(list, head); iqueue_init(list); } while (0) + + +#ifdef _MSC_VER +#pragma warning(disable:4311) +#pragma warning(disable:4312) +#pragma warning(disable:4996) +#endif + +#endif + + +//--------------------------------------------------------------------- +// BYTE ORDER & ALIGNMENT +//--------------------------------------------------------------------- +#ifndef IWORDS_BIG_ENDIAN + #ifdef _BIG_ENDIAN_ + #if _BIG_ENDIAN_ + #define IWORDS_BIG_ENDIAN 1 + #endif + #endif + #ifndef IWORDS_BIG_ENDIAN + #if defined(__hppa__) || \ + defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \ + (defined(__MIPS__) && defined(__MIPSEB__)) || \ + defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \ + defined(__sparc__) || defined(__powerpc__) || \ + defined(__mc68000__) || defined(__s390x__) || defined(__s390__) + #define IWORDS_BIG_ENDIAN 1 + #endif + #endif + #ifndef IWORDS_BIG_ENDIAN + #define IWORDS_BIG_ENDIAN 0 + #endif +#endif + +#ifndef IWORDS_MUST_ALIGN + #if defined(__i386__) || defined(__i386) || defined(_i386_) + #define IWORDS_MUST_ALIGN 0 + #elif defined(_M_IX86) || defined(_X86_) || defined(__x86_64__) + #define IWORDS_MUST_ALIGN 0 + #elif defined(__amd64) || defined(__amd64__) + #define IWORDS_MUST_ALIGN 0 + #else + #define IWORDS_MUST_ALIGN 1 + #endif +#endif + + +//===================================================================== +// SEGMENT +//===================================================================== +struct IKCPSEG +{ + struct IQUEUEHEAD node; + IUINT32 conv; + IUINT32 cmd; + IUINT32 frg; + IUINT32 wnd; + IUINT32 ts; + IUINT32 sn; + IUINT32 una; + IUINT32 len; + IUINT32 resendts; + IUINT32 rto; + IUINT32 fastack; + IUINT32 xmit; + char data[1]; +}; + + +//--------------------------------------------------------------------- +// IKCPCB +//--------------------------------------------------------------------- +struct IKCPCB +{ + IUINT32 conv, mtu, mss, state; + IUINT32 snd_una, snd_nxt, rcv_nxt; + IUINT32 ts_recent, ts_lastack, ssthresh; + IINT32 rx_rttval, rx_srtt, rx_rto, rx_minrto; + IUINT32 snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe; + IUINT32 current, interval, ts_flush, xmit; + IUINT32 nrcv_buf, nsnd_buf; + IUINT32 nrcv_que, nsnd_que; + IUINT32 nodelay, updated; + IUINT32 ts_probe, probe_wait; + IUINT32 dead_link, incr; + struct IQUEUEHEAD snd_queue; + struct IQUEUEHEAD rcv_queue; + struct IQUEUEHEAD snd_buf; + struct IQUEUEHEAD rcv_buf; + IUINT32 *acklist; + IUINT32 ackcount; + IUINT32 ackblock; + void *user; + char *buffer; + int fastresend; + int fastlimit; + int nocwnd, stream; + int logmask; + int (*output)(const char *buf, int len, struct IKCPCB *kcp, void *user); + void (*writelog)(const char *log, struct IKCPCB *kcp, void *user); +}; + + +typedef struct IKCPCB ikcpcb; + +#define IKCP_LOG_OUTPUT 1 +#define IKCP_LOG_INPUT 2 +#define IKCP_LOG_SEND 4 +#define IKCP_LOG_RECV 8 +#define IKCP_LOG_IN_DATA 16 +#define IKCP_LOG_IN_ACK 32 +#define IKCP_LOG_IN_PROBE 64 +#define IKCP_LOG_IN_WINS 128 +#define IKCP_LOG_OUT_DATA 256 +#define IKCP_LOG_OUT_ACK 512 +#define IKCP_LOG_OUT_PROBE 1024 +#define IKCP_LOG_OUT_WINS 2048 + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------- +// interface +//--------------------------------------------------------------------- + +// create a new kcp control object, 'conv' must equal in two endpoint +// from the same connection. 'user' will be passed to the output callback +// output callback can be setup like this: 'kcp->output = my_udp_output' +ikcpcb* ikcp_create(IUINT32 conv, void *user); + +// release kcp control object +void ikcp_release(ikcpcb *kcp); + +// set output callback, which will be invoked by kcp +void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len, + ikcpcb *kcp, void *user)); + +// user/upper level recv: returns size, returns below zero for EAGAIN +int ikcp_recv(ikcpcb *kcp, char *buffer, int len); + +// user/upper level send, returns below zero for error +int ikcp_send(ikcpcb *kcp, const char *buffer, int len); + +// update state (call it repeatedly, every 10ms-100ms), or you can ask +// ikcp_check when to call it again (without ikcp_input/_send calling). +// 'current' - current timestamp in millisec. +void ikcp_update(ikcpcb *kcp, IUINT32 current); + +// Determine when should you invoke ikcp_update: +// returns when you should invoke ikcp_update in millisec, if there +// is no ikcp_input/_send calling. you can call ikcp_update in that +// time, instead of call update repeatly. +// Important to reduce unnacessary ikcp_update invoking. use it to +// schedule ikcp_update (eg. implementing an epoll-like mechanism, +// or optimize ikcp_update when handling massive kcp connections) +IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current); + +// when you received a low level packet (eg. UDP packet), call it +int ikcp_input(ikcpcb *kcp, const char *data, long size); + +// flush pending data +void ikcp_flush(ikcpcb *kcp); + +// check the size of next message in the recv queue +int ikcp_peeksize(const ikcpcb *kcp); + +// change MTU size, default is 1400 +int ikcp_setmtu(ikcpcb *kcp, int mtu); + +// set maximum window size: sndwnd=32, rcvwnd=32 by default +int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd); + +// get how many packet is waiting to be sent +int ikcp_waitsnd(const ikcpcb *kcp); + +// fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) +// nodelay: 0:disable(default), 1:enable +// interval: internal update timer interval in millisec, default is 100ms +// resend: 0:disable fast resend(default), 1:enable fast resend +// nc: 0:normal congestion control(default), 1:disable congestion control +int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc); + + +void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...); + +// setup allocator +void ikcp_allocator(void* (*new_malloc)(size_t), void (*new_free)(void*)); + +// read conv +IUINT32 ikcp_getconv(const void *ptr); + + +#ifdef __cplusplus +} +#endif + +#endif + + diff --git a/include/logger.h b/include/logger.h new file mode 100644 index 0000000..a8ea804 --- /dev/null +++ b/include/logger.h @@ -0,0 +1,58 @@ +/* + * logger.h + * 日志与性能统计接口 + */ + +#ifndef OMNISOCKET_LOGGER_H +#define OMNISOCKET_LOGGER_H + +#include +#include + +/* 通过该结构体收集全局统计信息 */ +typedef struct OmniStats { + uint64_t start_ms; /* 起始时间(毫秒) */ + uint64_t last_report_ms; /* 上一次打印日志时间 */ + + uint64_t bytes_sent; /* 发送总字节数 */ + uint64_t bytes_recv; /* 接收总字节数 */ + + uint64_t send_count; /* 调用 omni_send 次数 */ + uint64_t recv_count; /* 调用 omni_recv 次数 */ + + uint64_t last_rtt_ms; /* 最近一次 RTT */ + uint64_t max_rtt_ms; /* 最大 RTT */ + + uint64_t tcp_retrans; /* 预留:TCP 重传统计(如可从内核获取) */ + uint64_t udp_retrans; /* UDP 上层重传次数 */ + uint64_t kcp_retrans; /* KCP 内部重传次数(可从 ikcp 统计) */ +} OmniStats; + +/* 初始化统计模块,在程序启动时调用一次 */ +void logger_init(void); + +/* 记录一次发送/接收 */ +void logger_on_send(size_t bytes); +void logger_on_recv(size_t bytes); + +/* 记录一次 RTT(由上层在合适时机调用) */ +void logger_on_rtt(uint64_t rtt_ms); + +/* 记录 KCP 重传次数变化(可在 KCP 更新循环中调用) */ +void logger_on_kcp_retrans(uint64_t delta); + +/* 计算当前吞吐量(返回:字节/秒) */ +double logger_calculate_throughput(void); + +/* 打印一条结构化性能日志(例如每隔若干秒调用) */ +void logger_print_performance_log(const char *tag); + +/* 结构化通用日志(key=value 形式) */ +void logger_log(const char *level, const char *component, + const char *fmt, ...); + +/* 获取内部统计快照(线程不安全,仅调试用) */ +OmniStats logger_get_snapshot(void); + +#endif /* OMNISOCKET_LOGGER_H */ + diff --git a/include/network.h b/include/network.h new file mode 100644 index 0000000..f4c332c --- /dev/null +++ b/include/network.h @@ -0,0 +1,78 @@ +/* + * network.h + * 统一的协议抽象层,对上层暴露 omni_* 接口 + * + * 支持 TCP / UDP / KCP 三种协议,通过命令行参数切换: + * -t 使用 TCP + * -u 使用 UDP + * -k 使用 KCP + */ + +#ifndef OMNISOCKET_NETWORK_H +#define OMNISOCKET_NETWORK_H + +#include +#include +#include /* for ssize_t */ + +/* 协议类型 */ +typedef enum { + OMNI_PROTO_TCP = 0, + OMNI_PROTO_UDP = 1, + OMNI_PROTO_KCP = 2 +} OmniProtocol; + +/* 角色:客户端 / 服务端 */ +typedef enum { + OMNI_ROLE_CLIENT = 0, + OMNI_ROLE_SERVER = 1 +} OmniRole; + +/* 统一上下文句柄(对上层不透明) */ +typedef struct OmniContext OmniContext; + +/* 协议实现函数表(由底层协议模块提供) */ +struct ProtoVTable { + OmniContext *(*init)(OmniRole role, + const char *bind_ip, + uint16_t bind_port, + const char *peer_ip, + uint16_t peer_port); + ssize_t (*send)(OmniContext *ctx, const void *buf, size_t len); + ssize_t (*recv)(OmniContext *ctx, void *buf, size_t len); + void (*close)(OmniContext *ctx); +}; + +/* + * 创建并初始化一个网络上下文。 + * + * 参数: + * role - 客户端或服务端 + * proto - 协议类型(TCP/UDP/KCP) + * bind_ip - 服务器监听或客户端本地绑定 IP(可为 NULL 表示 INADDR_ANY) + * bind_port - 监听端口或本地端口(0 表示让系统分配) + * peer_ip - 对端 IP(客户端连接或服务端应答的默认地址,可为 NULL) + * peer_port - 对端端口 + * + * 返回: + * 成功:上下文指针 + * 失败:NULL + */ +OmniContext *omni_init(OmniRole role, + OmniProtocol proto, + const char *bind_ip, + uint16_t bind_port, + const char *peer_ip, + uint16_t peer_port); + +/* 发送数据(阻塞直到全部发送或出错) */ +ssize_t omni_send(OmniContext *ctx, const void *buf, size_t len); + +/* 接收数据(阻塞直到读到至少 1 字节或出错/关闭) */ +ssize_t omni_recv(OmniContext *ctx, void *buf, size_t len); + +/* 关闭并释放上下文 */ +void omni_close(OmniContext *ctx); + +#endif /* OMNISOCKET_NETWORK_H */ + diff --git a/kcp_impl.o b/kcp_impl.o new file mode 100644 index 0000000000000000000000000000000000000000..c0883c522c950fe5937d93ea83354533ae048f7b GIT binary patch literal 6320 zcmbuDeQX>@6~M4bOb+o;>2kT-a}M9# z9(Q}xuIe`QaMjUrB?uW+6rm^zl@St+ikdi9n_d#Bb|e(J`~jAzz@UO$9Dxx*Tn*)T zZ+6~gyuKv~jI=j1zc=q=-p;;V`&ncEp2mg-My-K$vg6p zc=2i=&YkA%_k(~7qta34C1Z|@x8Fij^SGQl0FsOIO*rhC=aawQdFEHo{bp-77t{P7 zf8rOGZo|=q%4hil7rB_>4U60fSK|Cro26NMl{9nt(%)L|SyE2hxfq!jh4;7!&vMZ- z13qTJJt!KB4&RI&FAX(2#(O-5!bJys7v{Uf<$a=Z4xov zYyIZWz&_r;$u@Ke7u-{KX=2KlYhYQePSUF*s+E7R${m!$wJBD%31PKUFglhI_GlHH1IzhCl8JAI{&5 z*xiEKQ=!e63>-S)Y0(BLI39{@b>GtbBqWIN*I- z!2c{S-TuM9s#Wkw;Uevtz$fh-9*xO$@N*ug`hv-0Nb!eg>)D=D?&ep6h4FB^D_O$%$mge33*ly@J zp_>cIbJ%EkNTw0$w_BemBsdCeq2c)DODu0@;SZhH@e8B~liGI%e`Mz8sXr}|mLP+P;+^=n#=LowL~QoOKz4S%{e zwmsk2yY1{vtenSER^DXr8yk!an6{lvn=BH^+fkUu zjth1`a z&?sd_VVjS4_eJ{pdk^(SzHq2(_kIHs_(_#Ue7rvb7kui;HmGr|F~l!ue1pc{(fCG< zQw_u~Mf5>-{;1Xy|EQWTdib4c{-Ns8Mk`!orx(rzHspBzh!1Ielg6LdxE|+s6sLIZ z1|4iKdpO1UBM+xIU(@XCamo*5u!nKp1{cNozP2816JOFez9Wfm!T_P6o!j9e-l6e3 zH2$c@_5B)(V~4hO!iBBZ!)d=?@^IR3LUFXA?>DP)TuX62t#LgMCp50-;dPDcd6?0- zo`-en!vf>McOCiNrZ}phY#&_M?(=ZkZ@Y)nes^j1_5JSExW3=dYh2&&vl{;x#6fYM z(D+>%Kcn%j8o#1A?du7UVf&qj$6d@8Je>CXcMpG2t#8C+LBmep?_C<#>%)M?w}B4r z>lKaTvqii`eJ7y!9*y6vaozr~#y_sDe^GJD!*@Z3?IjPV{hsu2y1%b#_VxX?t5*-k z8Gwu8FKYY~8vnk=_4DwG#`W_6sa9C+#{xBr+Px(6t4E?vmrMI&3AJid?DSy;|S!4Rr zKP!Em;h6M@e-#F}bVdGr_D}JX{&O%un_m4kwXi}9Rqxfuzn|*mZ%E~DT`pmll@=9$9Z4E6N`_%Q+PTW(kT_3Hlz DGZ9cN literal 0 HcmV?d00001 diff --git a/logger.o b/logger.o new file mode 100644 index 0000000000000000000000000000000000000000..817727490fddd39680c775bbacaccfb72898bee5 GIT binary patch literal 5792 zcmb_feQaA-6~9ipHeJ^`E!*hSGFe)K>Wx}y8>Z1U!=ZC~hmx(d7=qT$b?q0?}L zxul4cu*|S0*CGJ{B_-h~`O`3o)QRbGZEt&=yLZfJCgrNzujxB@%)>H`Rci%nn z>y0Drj~(gx-1|G9_uTXDef~ylaDPW2AeaK;KGB*nDn#fbt#MQhqhf0W#Rt{PC6RRXf}jZO@Q8uIzq`hN2JM_=EvPr8@of8LVk z-`|AM`R1o(_=0q2Wnf-bt^NFxk0)4)i3`Uy#0LyZ9KZTxOjAF`us2j`Gty9 zyFoGclCo&LM0(Qou&Nd*Hq)|}?qpL|Z&7yDSW^aO4U6e|KqDWL6$QCIr+Z_~(mILY z9{xe%_;qLx8gu`{QezFN*IA7>R5j!Ca(Z%Jlvj@g->qMrq$u;UQlD6d>4FU2S-*Ol zbT7zP-kP2Z$h8+3zOWD+t}g_;*P|{A!AH(71UFKCTqm!VRH_f|>XBHpwS#CIDtx06 z_R=QLae65Inu;jhUrYDWa|o_>a;9ZE@cF}J-AmS&8(06U7CqZq^sKk&E6Qp_22a(g zD03`V^`RitA2zkdVD)2&FzBsnu&P22asfZ-$$G$~r%9!}ES%Mp=;fESQ^A8Uk z@wMkS-^NQR-E*5&Gyk|-u`^wY)kd)lw=%9+ZGzZibte=I$EJeep_yPfel{3B`b;ov z)q~;uY%pA&3x+4>eQ#_A*;2w6U$~J2%n*ph(K+HBPs`4c!_-7{)X*E@Z9VU@+a=xME)$fL+VJLODE0C1 z_GKvb=kPtt&?upjUn2M5K`-`EdNC;JW#p9ldvdvQNQ_%{wwTVQv_{T4VdeTCcyRwi zp;R$HUMyGzr=N62LQXQ%-%|=%x#W1sN+;qu9e>*iyr@q}$TcCz5*bEC*u z1FtZ` z3~K?|t)i!!9vo5!9*m0)A?m>B{!w(Z=7Fyr5`l^Cz?PfVbj{E^wv})^ps(O(U_KvI$>mi$TJL@UXE?uo@N zaJJ93?Ek3c+x+r}Tk>mK{;Xf#@k6Dsm9VWinTYPi5Bs-0`w&5UwftSb{J`nX=~W$n zf);!n(>h1M*+=K$0OTQprnLOKG+vSqOm}v;UL3&f2xhCqKNj1`I&nz0p-$Be*9&k$ zbolhuUK4gJJprFSJ}W@jYv{9{+IuBIPpPX=&pdX!*56Hy2U4{p9-#+)Y0_Q-+TlZl zZ=$$U+CcRbjlV=sJN;vXLtlLg(OC5m@uJ4xBMxHx6pf*Or!Jl9DZ)-`Tu-l3;mKF9<93%U-fX9#<31^qX){!#s+a{Rxqp#Sd`aP&Ch{tPcYk?J!H za>Y!>vLh)`%onnWLh-9~WQk0o>Bu^%WG+?CB^@i#I(b>BVvS(?G#IBIf+t%ZimsLv$U@$(^`72(Par5L#>E_ z<6U9=3_v1mC$@<3rvMT`|0a4E{{cWEQ~wzcUWWhF!1eZtmkk^-K2G0HSR|Drdykp>8w+3QA2`8#0F z!1*4gD^(Hu;qQQuf%A7y)WG>WAZ_4$-#=#He1D5b>15tX9w+YD9v|hAP5*Twjy2(k zNIK~xMdWy?BqDavo6tXsJpJntkwVe2BJ^Kihq}e}JX0t~#_b~ArOrvRfhTJ;F=8j_ zzW*>NL{6}z)rPaEo)7ANV6k34PsJ7*p;ey7ex{NQ-eNE4{&`P z08(_?h|9PcAN>!Xru|uM&)>&vAL4=LJBh<5`o&Ld-(>{k{8{%?G=NGk1DZdr3ySY) zKe0RJpCKWS{d%vO8={4#?jgc)ewz7T(e@#2$IQ(C%OrqL)4r+g&l&+udy8z_?f>Jr zB#A=W9{Z2)H@4^fZxq}k5?XndM HP5b`?qZ%QU literal 0 HcmV?d00001 diff --git a/network.o b/network.o new file mode 100644 index 0000000000000000000000000000000000000000..782c1c73a347e9e77148750241e4bdf81147c3a5 GIT binary patch literal 4240 zcmc&%U1%It6h52uC$(v^ErP9;vIs4$*fB|q)z--7XPYS|O<1z^Mcr;TlXmUyOq`u* zo7&WrpfC(d5g$aP=!+Bt9|TRGY-+`%5;WD92tkBmiN=RieFz#7&zZSrcD(FDtPdWT zx#xc8|DJpA?7oudJX{rt08<2>hSH2tfas0Q8P!QmJCmJGV!Yk? zYQRZMIn(%_oFjcVG2=|kU0hjNQJut`IyyWDR;}tH>w@~~xP5>Wu8SC|^LfC18E_L` zV9%S!{rnhK{n=J@evQia1n7JLoiBjh6xglG*KPEG=`6UZ87Jy9vrf+%;#4;^r#d|~ zlx?S5Q$IF5SwEJVQr)g`)wQSH#B7VR=uEV%+`{OK7=01!_`fR0i-OC8Ie=H%{IeqP zA_k6PG>m9Zxm~m97T3Eb+2Y)7aVA&4VCv`>UR5{4ZEH}SVXVyl5gUhYsUg!Ndpb4eG@BGPZDjh$WpQ@lDV_dU3nZ+t(^B$8@k97pT)%ieDk=yMQf5?1(e& z+*)m?$l#s}h|(@ATL3K#)%-sc5XBDoJI2aTb-eNif{7d7O0C!>0II^;R+9E?4Ft}_ z%D2Hv1}hz``h4Wl%^S4Aps3}wtd%}(W%}}3dLWa_YyGi7)3A)@o&BI|))~V*1&L&` zI|{A-{K5l!{rVCB`m4^LF;~pWv+}Gd}#fL7yj-x540vIQ( z8Xuzj{Q)B9k`Or?jcl)7Uvmj>pob8rhp2;IiMr@(Rc)KrT|fn#lI$tMk;dr8VtMsP z5z-*}b0kE%PA`^MUG*H0{5;`EmE`aGVtARxz~1x|5Hfk7RMx=V6&a^_b(t^AM#QT^ zxL2z%uJ%?%O*#D!5hwi-vBA9FA@*qneLkW}_Mb-?dfR%^J<0Cg z?)0(Vme$S$q}nCm8CfGZqmVaFoWS!(2by_=#03BYrlysSBP$qsnoeiw5QYSFCVfFR zZxl6_44OIJN)Kx0fMFIgdR9wQ6pwcM1D!W-7cJqJhNv-wuS1^kgCTr<2!Bc7bY?e% z@XG=x|0H8SS0wyFG5=V?9~Sd#Vk95;;lqArLO7if#(xnw$K^WzkZ_K>Ea4n?qu76P z+=9T_f2*)RBjN1-qJ*>mH!AplCxq9cZ?5Yb$xpwC`@Mv7+}jfVzR+I~xLntHDhP@2 zI(#_pTLPznWFO!|bXmeV?iC65zk&dt2waZaEY2a}bY3}bO5hy#CIv-GOE|~PN%#$4 z9Zm~ej{Ae8e@E!w3F&V^jPv;|gopbQ6LyrRDA7aW63+MNaS7*pZbZWQK6_8X`Q8Gh zI8?wN6*yX^Kb~aH)bbgHem;QGpRqEa^c9Psn1(;e8ij(Uqf9p}O~G&O-lCPso)YsD zx~<@^4ZJg~A+#Y++R5~QnJH)@lm9lv(44j)-7R5?i^*SNomnQfD^FoNa@5OsfcVS| zee{u3VNZ7g*$Nowp9|40d!aj@t{x71Ce9HDC z!ruEw7IGllYbYz%{}>%gq_h42)PMROu|4;{Tx>FVLTt!MQ82lqlw^DIDfb?sK=C_o XQhLejw;4s{?B5po)9<1%$@YH(BvU(d literal 0 HcmV?d00001 diff --git a/src/core/logger.c b/src/core/logger.c new file mode 100644 index 0000000..676d478 --- /dev/null +++ b/src/core/logger.c @@ -0,0 +1,118 @@ +/* + * logger.c + * 性能统计与结构化日志实现 + */ + +#include "logger.h" +#include "common.h" + +#include +#include +#include + +static OmniStats g_stats; + +static uint64_t now_ms(void) +{ + return omni_now_ms(); +} + +void logger_init(void) +{ + memset(&g_stats, 0, sizeof(g_stats)); + g_stats.start_ms = now_ms(); + g_stats.last_report_ms = g_stats.start_ms; +} + +void logger_on_send(size_t bytes) +{ + g_stats.bytes_sent += bytes; + g_stats.send_count++; +} + +void logger_on_recv(size_t bytes) +{ + g_stats.bytes_recv += bytes; + g_stats.recv_count++; +} + +void logger_on_rtt(uint64_t rtt_ms) +{ + g_stats.last_rtt_ms = rtt_ms; + if (rtt_ms > g_stats.max_rtt_ms) { + g_stats.max_rtt_ms = rtt_ms; + } +} + +void logger_on_kcp_retrans(uint64_t delta) +{ + g_stats.kcp_retrans += delta; +} + +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; + } + double seconds = (double)elapsed_ms / 1000.0; + return (double)(g_stats.bytes_sent + g_stats.bytes_recv) / seconds; +} + +static void print_timestamp(FILE *fp) +{ + uint64_t ms = now_ms(); + fprintf(fp, "ts=%llu ", (unsigned long long)ms); +} + +void logger_print_performance_log(const char *tag) +{ + uint64_t now = now_ms(); + uint64_t elapsed_ms = now - g_stats.start_ms; + double thr = logger_calculate_throughput(); + + FILE *fp = stderr; + print_timestamp(fp); + fprintf(fp, + "level=INFO component=perf tag=%s " + "elapsed_ms=%llu bytes_sent=%llu bytes_recv=%llu " + "send_count=%llu recv_count=%llu " + "throughput_bytes_per_sec=%.2f " + "last_rtt_ms=%llu max_rtt_ms=%llu " + "kcp_retrans=%llu\n", + tag ? tag : "periodic", + (unsigned long long)elapsed_ms, + (unsigned long long)g_stats.bytes_sent, + (unsigned long long)g_stats.bytes_recv, + (unsigned long long)g_stats.send_count, + (unsigned long long)g_stats.recv_count, + thr, + (unsigned long long)g_stats.last_rtt_ms, + (unsigned long long)g_stats.max_rtt_ms, + (unsigned long long)g_stats.kcp_retrans); + + g_stats.last_report_ms = now; +} + +void logger_log(const char *level, const char *component, + const char *fmt, ...) +{ + FILE *fp = stderr; + print_timestamp(fp); + fprintf(fp, "level=%s component=%s ", level ? level : "INFO", + component ? component : "general"); + + va_list ap; + va_start(ap, fmt); + vfprintf(fp, fmt, ap); + va_end(ap); + + fputc('\n', fp); +} + +OmniStats logger_get_snapshot(void) +{ + return g_stats; +} + diff --git a/src/core/network.c b/src/core/network.c new file mode 100644 index 0000000..e7493a0 --- /dev/null +++ b/src/core/network.c @@ -0,0 +1,123 @@ +/* + * network.c + * 协议分发与工厂模式实现 + */ + +#include "network.h" +#include "common.h" +#include "logger.h" + +#include + +/* TCP / UDP / KCP 在各自的实现文件中提供以下符号 */ +extern const struct ProtoVTable TCP_PROTO_VTABLE; +extern const struct ProtoVTable UDP_PROTO_VTABLE; +extern const struct ProtoVTable KCP_PROTO_VTABLE; + +struct OmniContext { + OmniProtocol proto; + OmniRole role; + const struct ProtoVTable *vt; + void *impl; /* 由具体协议实现使用的内部指针 */ +}; + +/* 工厂:根据协议类型选择对应实现 */ +static const struct ProtoVTable *select_vtable(OmniProtocol proto) +{ + switch (proto) { + case OMNI_PROTO_TCP: + return &TCP_PROTO_VTABLE; + case OMNI_PROTO_UDP: + return &UDP_PROTO_VTABLE; + case OMNI_PROTO_KCP: + return &KCP_PROTO_VTABLE; + default: + return NULL; + } +} + +OmniContext *omni_init(OmniRole role, + OmniProtocol proto, + const char *bind_ip, + uint16_t bind_port, + const char *peer_ip, + uint16_t peer_port) +{ + logger_init(); + + const struct ProtoVTable *vt = select_vtable(proto); + if (!vt || !vt->init) { + logger_log("ERROR", "network", "select_vtable_failed proto=%d", + (int)proto); + return NULL; + } + + OmniContext *ctx = (OmniContext *)calloc(1, sizeof(OmniContext)); + if (!ctx) { + logger_log("ERROR", "network", "calloc_OmniContext_failed"); + return NULL; + } + + ctx->proto = proto; + ctx->role = role; + ctx->vt = vt; + + OmniContext *impl_ctx = vt->init(role, bind_ip, bind_port, peer_ip, peer_port); + if (!impl_ctx) { + logger_log("ERROR", "network", "proto_init_failed proto=%d", (int)proto); + free(ctx); + return NULL; + } + + /* 简化:直接让 impl 指针等于协议实现返回的上下文 */ + ctx->impl = impl_ctx; + + logger_log("INFO", "network", + "omni_init_success proto=%d role=%d bind_port=%u peer_port=%u", + (int)proto, (int)role, + (unsigned)bind_port, + (unsigned)peer_port); + + return ctx; +} + +ssize_t omni_send(OmniContext *ctx, const void *buf, size_t len) +{ + if (!ctx || !ctx->vt || !ctx->vt->send) { + return OMNI_ERR_PARAM; + } + + ssize_t n = ctx->vt->send((OmniContext *)ctx->impl, buf, len); + if (n > 0) { + logger_on_send((size_t)n); + } + logger_log("DEBUG", "network", "omni_send proto=%d bytes=%zd", + (int)ctx->proto, n); + return n; +} + +ssize_t omni_recv(OmniContext *ctx, void *buf, size_t len) +{ + if (!ctx || !ctx->vt || !ctx->vt->recv) { + return OMNI_ERR_PARAM; + } + + ssize_t n = ctx->vt->recv((OmniContext *)ctx->impl, buf, len); + if (n > 0) { + logger_on_recv((size_t)n); + } + logger_log("DEBUG", "network", "omni_recv proto=%d bytes=%zd", + (int)ctx->proto, n); + return n; +} + +void omni_close(OmniContext *ctx) +{ + if (!ctx) return; + if (ctx->vt && ctx->vt->close && ctx->impl) { + ctx->vt->close((OmniContext *)ctx->impl); + } + logger_print_performance_log("final"); + free(ctx); +} + diff --git a/src/protocols/ikcp.c b/src/protocols/ikcp.c new file mode 100644 index 0000000..6127f40 --- /dev/null +++ b/src/protocols/ikcp.c @@ -0,0 +1,1306 @@ +//===================================================================== +// +// KCP - A Better ARQ Protocol Implementation +// skywind3000 (at) gmail.com, 2010-2011 +// +// Features: +// + Average RTT reduce 30% - 40% vs traditional ARQ like tcp. +// + Maximum RTT reduce three times vs tcp. +// + Lightweight, distributed as a single source file. +// +//===================================================================== +#include "kcp/ikcp.h" + +#include +#include +#include +#include +#include + +#define IKCP_FASTACK_CONSERVE + +//===================================================================== +// KCP BASIC +//===================================================================== +const IUINT32 IKCP_RTO_NDL = 30; // no delay min rto +const IUINT32 IKCP_RTO_MIN = 100; // normal min rto +const IUINT32 IKCP_RTO_DEF = 200; +const IUINT32 IKCP_RTO_MAX = 60000; +const IUINT32 IKCP_CMD_PUSH = 81; // cmd: push data +const IUINT32 IKCP_CMD_ACK = 82; // cmd: ack +const IUINT32 IKCP_CMD_WASK = 83; // cmd: window probe (ask) +const IUINT32 IKCP_CMD_WINS = 84; // cmd: window size (tell) +const IUINT32 IKCP_ASK_SEND = 1; // need to send IKCP_CMD_WASK +const IUINT32 IKCP_ASK_TELL = 2; // need to send IKCP_CMD_WINS +const IUINT32 IKCP_WND_SND = 32; +const IUINT32 IKCP_WND_RCV = 128; // must >= max fragment size +const IUINT32 IKCP_MTU_DEF = 1400; +const IUINT32 IKCP_ACK_FAST = 3; +const IUINT32 IKCP_INTERVAL = 100; +const IUINT32 IKCP_OVERHEAD = 24; +const IUINT32 IKCP_DEADLINK = 20; +const IUINT32 IKCP_THRESH_INIT = 2; +const IUINT32 IKCP_THRESH_MIN = 2; +const IUINT32 IKCP_PROBE_INIT = 7000; // 7 secs to probe window size +const IUINT32 IKCP_PROBE_LIMIT = 120000; // up to 120 secs to probe window +const IUINT32 IKCP_FASTACK_LIMIT = 5; // max times to trigger fastack + + +//--------------------------------------------------------------------- +// encode / decode +//--------------------------------------------------------------------- + +/* encode 8 bits unsigned int */ +static inline char *ikcp_encode8u(char *p, unsigned char c) +{ + *(unsigned char*)p++ = c; + return p; +} + +/* decode 8 bits unsigned int */ +static inline const char *ikcp_decode8u(const char *p, unsigned char *c) +{ + *c = *(unsigned char*)p++; + return p; +} + +/* encode 16 bits unsigned int (lsb) */ +static inline char *ikcp_encode16u(char *p, unsigned short w) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *(unsigned char*)(p + 0) = (w & 255); + *(unsigned char*)(p + 1) = (w >> 8); +#else + memcpy(p, &w, 2); +#endif + p += 2; + return p; +} + +/* decode 16 bits unsigned int (lsb) */ +static inline const char *ikcp_decode16u(const char *p, unsigned short *w) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *w = *(const unsigned char*)(p + 1); + *w = *(const unsigned char*)(p + 0) + (*w << 8); +#else + memcpy(w, p, 2); +#endif + p += 2; + return p; +} + +/* encode 32 bits unsigned int (lsb) */ +static inline char *ikcp_encode32u(char *p, IUINT32 l) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *(unsigned char*)(p + 0) = (unsigned char)((l >> 0) & 0xff); + *(unsigned char*)(p + 1) = (unsigned char)((l >> 8) & 0xff); + *(unsigned char*)(p + 2) = (unsigned char)((l >> 16) & 0xff); + *(unsigned char*)(p + 3) = (unsigned char)((l >> 24) & 0xff); +#else + memcpy(p, &l, 4); +#endif + p += 4; + return p; +} + +/* decode 32 bits unsigned int (lsb) */ +static inline const char *ikcp_decode32u(const char *p, IUINT32 *l) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *l = *(const unsigned char*)(p + 3); + *l = *(const unsigned char*)(p + 2) + (*l << 8); + *l = *(const unsigned char*)(p + 1) + (*l << 8); + *l = *(const unsigned char*)(p + 0) + (*l << 8); +#else + memcpy(l, p, 4); +#endif + p += 4; + return p; +} + +static inline IUINT32 _imin_(IUINT32 a, IUINT32 b) { + return a <= b ? a : b; +} + +static inline IUINT32 _imax_(IUINT32 a, IUINT32 b) { + return a >= b ? a : b; +} + +static inline IUINT32 _ibound_(IUINT32 lower, IUINT32 middle, IUINT32 upper) +{ + return _imin_(_imax_(lower, middle), upper); +} + +static inline long _itimediff(IUINT32 later, IUINT32 earlier) +{ + return ((IINT32)(later - earlier)); +} + +//--------------------------------------------------------------------- +// manage segment +//--------------------------------------------------------------------- +typedef struct IKCPSEG IKCPSEG; + +static void* (*ikcp_malloc_hook)(size_t) = NULL; +static void (*ikcp_free_hook)(void *) = NULL; + +// internal malloc +static void* ikcp_malloc(size_t size) { + if (ikcp_malloc_hook) + return ikcp_malloc_hook(size); + return malloc(size); +} + +// internal free +static void ikcp_free(void *ptr) { + if (ikcp_free_hook) { + ikcp_free_hook(ptr); + } else { + free(ptr); + } +} + +// redefine allocator +void ikcp_allocator(void* (*new_malloc)(size_t), void (*new_free)(void*)) +{ + ikcp_malloc_hook = new_malloc; + ikcp_free_hook = new_free; +} + +// allocate a new kcp segment +static IKCPSEG* ikcp_segment_new(ikcpcb *kcp, int size) +{ + return (IKCPSEG*)ikcp_malloc(sizeof(IKCPSEG) + size); +} + +// delete a segment +static void ikcp_segment_delete(ikcpcb *kcp, IKCPSEG *seg) +{ + ikcp_free(seg); +} + +// write log +void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...) +{ + char buffer[1024]; + va_list argptr; + if ((mask & kcp->logmask) == 0 || kcp->writelog == 0) return; + va_start(argptr, fmt); + vsprintf(buffer, fmt, argptr); + va_end(argptr); + kcp->writelog(buffer, kcp, kcp->user); +} + +// check log mask +static int ikcp_canlog(const ikcpcb *kcp, int mask) +{ + if ((mask & kcp->logmask) == 0 || kcp->writelog == NULL) return 0; + return 1; +} + +// output segment +static int ikcp_output(ikcpcb *kcp, const void *data, int size) +{ + assert(kcp); + assert(kcp->output); + if (ikcp_canlog(kcp, IKCP_LOG_OUTPUT)) { + ikcp_log(kcp, IKCP_LOG_OUTPUT, "[RO] %ld bytes", (long)size); + } + if (size == 0) return 0; + return kcp->output((const char*)data, size, kcp, kcp->user); +} + +// output queue +void ikcp_qprint(const char *name, const struct IQUEUEHEAD *head) +{ +#if 0 + const struct IQUEUEHEAD *p; + printf("<%s>: [", name); + for (p = head->next; p != head; p = p->next) { + const IKCPSEG *seg = iqueue_entry(p, const IKCPSEG, node); + printf("(%lu %d)", (unsigned long)seg->sn, (int)(seg->ts % 10000)); + if (p->next != head) printf(","); + } + printf("]\n"); +#endif +} + + +//--------------------------------------------------------------------- +// create a new kcpcb +//--------------------------------------------------------------------- +ikcpcb* ikcp_create(IUINT32 conv, void *user) +{ + ikcpcb *kcp = (ikcpcb*)ikcp_malloc(sizeof(struct IKCPCB)); + if (kcp == NULL) return NULL; + kcp->conv = conv; + kcp->user = user; + kcp->snd_una = 0; + kcp->snd_nxt = 0; + kcp->rcv_nxt = 0; + kcp->ts_recent = 0; + kcp->ts_lastack = 0; + kcp->ts_probe = 0; + kcp->probe_wait = 0; + kcp->snd_wnd = IKCP_WND_SND; + kcp->rcv_wnd = IKCP_WND_RCV; + kcp->rmt_wnd = IKCP_WND_RCV; + kcp->cwnd = 0; + kcp->incr = 0; + kcp->probe = 0; + kcp->mtu = IKCP_MTU_DEF; + kcp->mss = kcp->mtu - IKCP_OVERHEAD; + kcp->stream = 0; + + kcp->buffer = (char*)ikcp_malloc((kcp->mtu + IKCP_OVERHEAD) * 3); + if (kcp->buffer == NULL) { + ikcp_free(kcp); + return NULL; + } + + iqueue_init(&kcp->snd_queue); + iqueue_init(&kcp->rcv_queue); + iqueue_init(&kcp->snd_buf); + iqueue_init(&kcp->rcv_buf); + kcp->nrcv_buf = 0; + kcp->nsnd_buf = 0; + kcp->nrcv_que = 0; + kcp->nsnd_que = 0; + kcp->state = 0; + kcp->acklist = NULL; + kcp->ackblock = 0; + kcp->ackcount = 0; + kcp->rx_srtt = 0; + kcp->rx_rttval = 0; + kcp->rx_rto = IKCP_RTO_DEF; + kcp->rx_minrto = IKCP_RTO_MIN; + kcp->current = 0; + kcp->interval = IKCP_INTERVAL; + kcp->ts_flush = IKCP_INTERVAL; + kcp->nodelay = 0; + kcp->updated = 0; + kcp->logmask = 0; + kcp->ssthresh = IKCP_THRESH_INIT; + kcp->fastresend = 0; + kcp->fastlimit = IKCP_FASTACK_LIMIT; + kcp->nocwnd = 0; + kcp->xmit = 0; + kcp->dead_link = IKCP_DEADLINK; + kcp->output = NULL; + kcp->writelog = NULL; + + return kcp; +} + + +//--------------------------------------------------------------------- +// release a new kcpcb +//--------------------------------------------------------------------- +void ikcp_release(ikcpcb *kcp) +{ + assert(kcp); + if (kcp) { + IKCPSEG *seg; + while (!iqueue_is_empty(&kcp->snd_buf)) { + seg = iqueue_entry(kcp->snd_buf.next, IKCPSEG, node); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + while (!iqueue_is_empty(&kcp->rcv_buf)) { + seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + while (!iqueue_is_empty(&kcp->snd_queue)) { + seg = iqueue_entry(kcp->snd_queue.next, IKCPSEG, node); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + while (!iqueue_is_empty(&kcp->rcv_queue)) { + seg = iqueue_entry(kcp->rcv_queue.next, IKCPSEG, node); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + if (kcp->buffer) { + ikcp_free(kcp->buffer); + } + if (kcp->acklist) { + ikcp_free(kcp->acklist); + } + + kcp->nrcv_buf = 0; + kcp->nsnd_buf = 0; + kcp->nrcv_que = 0; + kcp->nsnd_que = 0; + kcp->ackcount = 0; + kcp->buffer = NULL; + kcp->acklist = NULL; + ikcp_free(kcp); + } +} + + +//--------------------------------------------------------------------- +// set output callback, which will be invoked by kcp +//--------------------------------------------------------------------- +void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len, + ikcpcb *kcp, void *user)) +{ + kcp->output = output; +} + + +//--------------------------------------------------------------------- +// user/upper level recv: returns size, returns below zero for EAGAIN +//--------------------------------------------------------------------- +int ikcp_recv(ikcpcb *kcp, char *buffer, int len) +{ + struct IQUEUEHEAD *p; + int ispeek = (len < 0)? 1 : 0; + int peeksize; + int recover = 0; + IKCPSEG *seg; + assert(kcp); + + if (iqueue_is_empty(&kcp->rcv_queue)) + return -1; + + if (len < 0) len = -len; + + peeksize = ikcp_peeksize(kcp); + + if (peeksize < 0) + return -2; + + if (peeksize > len) + return -3; + + if (kcp->nrcv_que >= kcp->rcv_wnd) + recover = 1; + + // merge fragment + for (len = 0, p = kcp->rcv_queue.next; p != &kcp->rcv_queue; ) { + int fragment; + seg = iqueue_entry(p, IKCPSEG, node); + p = p->next; + + if (buffer) { + memcpy(buffer, seg->data, seg->len); + buffer += seg->len; + } + + len += seg->len; + fragment = seg->frg; + + if (ikcp_canlog(kcp, IKCP_LOG_RECV)) { + ikcp_log(kcp, IKCP_LOG_RECV, "recv sn=%lu", (unsigned long)seg->sn); + } + + if (ispeek == 0) { + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + kcp->nrcv_que--; + } + + if (fragment == 0) + break; + } + + assert(len == peeksize); + + // move available data from rcv_buf -> rcv_queue + while (! iqueue_is_empty(&kcp->rcv_buf)) { + seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); + if (seg->sn == kcp->rcv_nxt && kcp->nrcv_que < kcp->rcv_wnd) { + iqueue_del(&seg->node); + kcp->nrcv_buf--; + iqueue_add_tail(&seg->node, &kcp->rcv_queue); + kcp->nrcv_que++; + kcp->rcv_nxt++; + } else { + break; + } + } + + // fast recover + if (kcp->nrcv_que < kcp->rcv_wnd && recover) { + // ready to send back IKCP_CMD_WINS in ikcp_flush + // tell remote my window size + kcp->probe |= IKCP_ASK_TELL; + } + + return len; +} + + +//--------------------------------------------------------------------- +// peek data size +//--------------------------------------------------------------------- +int ikcp_peeksize(const ikcpcb *kcp) +{ + struct IQUEUEHEAD *p; + IKCPSEG *seg; + int length = 0; + + assert(kcp); + + if (iqueue_is_empty(&kcp->rcv_queue)) return -1; + + seg = iqueue_entry(kcp->rcv_queue.next, IKCPSEG, node); + if (seg->frg == 0) return seg->len; + + if (kcp->nrcv_que < seg->frg + 1) return -1; + + for (p = kcp->rcv_queue.next; p != &kcp->rcv_queue; p = p->next) { + seg = iqueue_entry(p, IKCPSEG, node); + length += seg->len; + if (seg->frg == 0) break; + } + + return length; +} + + +//--------------------------------------------------------------------- +// user/upper level send, returns below zero for error +//--------------------------------------------------------------------- +int ikcp_send(ikcpcb *kcp, const char *buffer, int len) +{ + IKCPSEG *seg; + int count, i; + int sent = 0; + + assert(kcp->mss > 0); + if (len < 0) return -1; + + // append to previous segment in streaming mode (if possible) + if (kcp->stream != 0) { + if (!iqueue_is_empty(&kcp->snd_queue)) { + IKCPSEG *old = iqueue_entry(kcp->snd_queue.prev, IKCPSEG, node); + if (old->len < kcp->mss) { + int capacity = kcp->mss - old->len; + int extend = (len < capacity)? len : capacity; + seg = ikcp_segment_new(kcp, old->len + extend); + assert(seg); + if (seg == NULL) { + return -2; + } + iqueue_add_tail(&seg->node, &kcp->snd_queue); + memcpy(seg->data, old->data, old->len); + if (buffer) { + memcpy(seg->data + old->len, buffer, extend); + buffer += extend; + } + seg->len = old->len + extend; + seg->frg = 0; + len -= extend; + iqueue_del_init(&old->node); + ikcp_segment_delete(kcp, old); + sent = extend; + } + } + if (len <= 0) { + return sent; + } + } + + if (len <= (int)kcp->mss) count = 1; + else count = (len + kcp->mss - 1) / kcp->mss; + + if (count >= (int)IKCP_WND_RCV) { + if (kcp->stream != 0 && sent > 0) + return sent; + return -2; + } + + if (count == 0) count = 1; + + // fragment + for (i = 0; i < count; i++) { + int size = len > (int)kcp->mss ? (int)kcp->mss : len; + seg = ikcp_segment_new(kcp, size); + assert(seg); + if (seg == NULL) { + return -2; + } + if (buffer && len > 0) { + memcpy(seg->data, buffer, size); + } + seg->len = size; + seg->frg = (kcp->stream == 0)? (count - i - 1) : 0; + iqueue_init(&seg->node); + iqueue_add_tail(&seg->node, &kcp->snd_queue); + kcp->nsnd_que++; + if (buffer) { + buffer += size; + } + len -= size; + sent += size; + } + + return sent; +} + + +//--------------------------------------------------------------------- +// parse ack +//--------------------------------------------------------------------- +static void ikcp_update_ack(ikcpcb *kcp, IINT32 rtt) +{ + IINT32 rto = 0; + if (kcp->rx_srtt == 0) { + kcp->rx_srtt = rtt; + kcp->rx_rttval = rtt / 2; + } else { + long delta = rtt - kcp->rx_srtt; + if (delta < 0) delta = -delta; + kcp->rx_rttval = (3 * kcp->rx_rttval + delta) / 4; + kcp->rx_srtt = (7 * kcp->rx_srtt + rtt) / 8; + if (kcp->rx_srtt < 1) kcp->rx_srtt = 1; + } + rto = kcp->rx_srtt + _imax_(kcp->interval, 4 * kcp->rx_rttval); + kcp->rx_rto = _ibound_(kcp->rx_minrto, rto, IKCP_RTO_MAX); +} + +static void ikcp_shrink_buf(ikcpcb *kcp) +{ + struct IQUEUEHEAD *p = kcp->snd_buf.next; + if (p != &kcp->snd_buf) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + kcp->snd_una = seg->sn; + } else { + kcp->snd_una = kcp->snd_nxt; + } +} + +static void ikcp_parse_ack(ikcpcb *kcp, IUINT32 sn) +{ + struct IQUEUEHEAD *p, *next; + + if (_itimediff(sn, kcp->snd_una) < 0 || _itimediff(sn, kcp->snd_nxt) >= 0) + return; + + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + next = p->next; + if (sn == seg->sn) { + iqueue_del(p); + ikcp_segment_delete(kcp, seg); + kcp->nsnd_buf--; + break; + } + if (_itimediff(sn, seg->sn) < 0) { + break; + } + } +} + +static void ikcp_parse_una(ikcpcb *kcp, IUINT32 una) +{ + struct IQUEUEHEAD *p, *next; + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + next = p->next; + if (_itimediff(una, seg->sn) > 0) { + iqueue_del(p); + ikcp_segment_delete(kcp, seg); + kcp->nsnd_buf--; + } else { + break; + } + } +} + +static void ikcp_parse_fastack(ikcpcb *kcp, IUINT32 sn, IUINT32 ts) +{ + struct IQUEUEHEAD *p, *next; + + if (_itimediff(sn, kcp->snd_una) < 0 || _itimediff(sn, kcp->snd_nxt) >= 0) + return; + + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + next = p->next; + if (_itimediff(sn, seg->sn) < 0) { + break; + } + else if (sn != seg->sn) { + #ifndef IKCP_FASTACK_CONSERVE + seg->fastack++; + #else + if (_itimediff(ts, seg->ts) >= 0) + seg->fastack++; + #endif + } + } +} + + +//--------------------------------------------------------------------- +// ack append +//--------------------------------------------------------------------- +static void ikcp_ack_push(ikcpcb *kcp, IUINT32 sn, IUINT32 ts) +{ + IUINT32 newsize = kcp->ackcount + 1; + IUINT32 *ptr; + + if (newsize > kcp->ackblock) { + IUINT32 *acklist; + IUINT32 newblock; + + for (newblock = 8; newblock < newsize; newblock <<= 1); + acklist = (IUINT32*)ikcp_malloc(newblock * sizeof(IUINT32) * 2); + + if (acklist == NULL) { + assert(acklist != NULL); + abort(); + } + + if (kcp->acklist != NULL) { + IUINT32 x; + for (x = 0; x < kcp->ackcount; x++) { + acklist[x * 2 + 0] = kcp->acklist[x * 2 + 0]; + acklist[x * 2 + 1] = kcp->acklist[x * 2 + 1]; + } + ikcp_free(kcp->acklist); + } + + kcp->acklist = acklist; + kcp->ackblock = newblock; + } + + ptr = &kcp->acklist[kcp->ackcount * 2]; + ptr[0] = sn; + ptr[1] = ts; + kcp->ackcount++; +} + +static void ikcp_ack_get(const ikcpcb *kcp, int p, IUINT32 *sn, IUINT32 *ts) +{ + if (sn) sn[0] = kcp->acklist[p * 2 + 0]; + if (ts) ts[0] = kcp->acklist[p * 2 + 1]; +} + + +//--------------------------------------------------------------------- +// parse data +//--------------------------------------------------------------------- +void ikcp_parse_data(ikcpcb *kcp, IKCPSEG *newseg) +{ + struct IQUEUEHEAD *p, *prev; + IUINT32 sn = newseg->sn; + int repeat = 0; + + if (_itimediff(sn, kcp->rcv_nxt + kcp->rcv_wnd) >= 0 || + _itimediff(sn, kcp->rcv_nxt) < 0) { + ikcp_segment_delete(kcp, newseg); + return; + } + + for (p = kcp->rcv_buf.prev; p != &kcp->rcv_buf; p = prev) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + prev = p->prev; + if (seg->sn == sn) { + repeat = 1; + break; + } + if (_itimediff(sn, seg->sn) > 0) { + break; + } + } + + if (repeat == 0) { + iqueue_init(&newseg->node); + iqueue_add(&newseg->node, p); + kcp->nrcv_buf++; + } else { + ikcp_segment_delete(kcp, newseg); + } + +#if 0 + ikcp_qprint("rcvbuf", &kcp->rcv_buf); + printf("rcv_nxt=%lu\n", kcp->rcv_nxt); +#endif + + // move available data from rcv_buf -> rcv_queue + while (! iqueue_is_empty(&kcp->rcv_buf)) { + IKCPSEG *seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); + if (seg->sn == kcp->rcv_nxt && kcp->nrcv_que < kcp->rcv_wnd) { + iqueue_del(&seg->node); + kcp->nrcv_buf--; + iqueue_add_tail(&seg->node, &kcp->rcv_queue); + kcp->nrcv_que++; + kcp->rcv_nxt++; + } else { + break; + } + } + +#if 0 + ikcp_qprint("queue", &kcp->rcv_queue); + printf("rcv_nxt=%lu\n", kcp->rcv_nxt); +#endif + +#if 1 +// printf("snd(buf=%d, queue=%d)\n", kcp->nsnd_buf, kcp->nsnd_que); +// printf("rcv(buf=%d, queue=%d)\n", kcp->nrcv_buf, kcp->nrcv_que); +#endif +} + + +//--------------------------------------------------------------------- +// input data +//--------------------------------------------------------------------- +int ikcp_input(ikcpcb *kcp, const char *data, long size) +{ + IUINT32 prev_una = kcp->snd_una; + IUINT32 maxack = 0, latest_ts = 0; + int flag = 0; + + if (ikcp_canlog(kcp, IKCP_LOG_INPUT)) { + ikcp_log(kcp, IKCP_LOG_INPUT, "[RI] %d bytes", (int)size); + } + + if (data == NULL || (int)size < (int)IKCP_OVERHEAD) return -1; + + while (1) { + IUINT32 ts, sn, len, una, conv; + IUINT16 wnd; + IUINT8 cmd, frg; + IKCPSEG *seg; + + if (size < (int)IKCP_OVERHEAD) break; + + data = ikcp_decode32u(data, &conv); + if (conv != kcp->conv) return -1; + + data = ikcp_decode8u(data, &cmd); + data = ikcp_decode8u(data, &frg); + data = ikcp_decode16u(data, &wnd); + data = ikcp_decode32u(data, &ts); + data = ikcp_decode32u(data, &sn); + data = ikcp_decode32u(data, &una); + data = ikcp_decode32u(data, &len); + + size -= IKCP_OVERHEAD; + + if ((long)size < (long)len || (int)len < 0) return -2; + + if (cmd != IKCP_CMD_PUSH && cmd != IKCP_CMD_ACK && + cmd != IKCP_CMD_WASK && cmd != IKCP_CMD_WINS) + return -3; + + kcp->rmt_wnd = wnd; + ikcp_parse_una(kcp, una); + ikcp_shrink_buf(kcp); + + if (cmd == IKCP_CMD_ACK) { + if (_itimediff(kcp->current, ts) >= 0) { + ikcp_update_ack(kcp, _itimediff(kcp->current, ts)); + } + ikcp_parse_ack(kcp, sn); + ikcp_shrink_buf(kcp); + if (flag == 0) { + flag = 1; + maxack = sn; + latest_ts = ts; + } else { + if (_itimediff(sn, maxack) > 0) { + #ifndef IKCP_FASTACK_CONSERVE + maxack = sn; + latest_ts = ts; + #else + if (_itimediff(ts, latest_ts) > 0) { + maxack = sn; + latest_ts = ts; + } + #endif + } + } + if (ikcp_canlog(kcp, IKCP_LOG_IN_ACK)) { + ikcp_log(kcp, IKCP_LOG_IN_ACK, + "input ack: sn=%lu rtt=%ld rto=%ld", (unsigned long)sn, + (long)_itimediff(kcp->current, ts), + (long)kcp->rx_rto); + } + } + else if (cmd == IKCP_CMD_PUSH) { + if (ikcp_canlog(kcp, IKCP_LOG_IN_DATA)) { + ikcp_log(kcp, IKCP_LOG_IN_DATA, + "input psh: sn=%lu ts=%lu", (unsigned long)sn, (unsigned long)ts); + } + if (_itimediff(sn, kcp->rcv_nxt + kcp->rcv_wnd) < 0) { + ikcp_ack_push(kcp, sn, ts); + if (_itimediff(sn, kcp->rcv_nxt) >= 0) { + seg = ikcp_segment_new(kcp, len); + seg->conv = conv; + seg->cmd = cmd; + seg->frg = frg; + seg->wnd = wnd; + seg->ts = ts; + seg->sn = sn; + seg->una = una; + seg->len = len; + + if (len > 0) { + memcpy(seg->data, data, len); + } + + ikcp_parse_data(kcp, seg); + } + } + } + else if (cmd == IKCP_CMD_WASK) { + // ready to send back IKCP_CMD_WINS in ikcp_flush + // tell remote my window size + kcp->probe |= IKCP_ASK_TELL; + if (ikcp_canlog(kcp, IKCP_LOG_IN_PROBE)) { + ikcp_log(kcp, IKCP_LOG_IN_PROBE, "input probe"); + } + } + else if (cmd == IKCP_CMD_WINS) { + // do nothing + if (ikcp_canlog(kcp, IKCP_LOG_IN_WINS)) { + ikcp_log(kcp, IKCP_LOG_IN_WINS, + "input wins: %lu", (unsigned long)(wnd)); + } + } + else { + return -3; + } + + data += len; + size -= len; + } + + if (flag != 0) { + ikcp_parse_fastack(kcp, maxack, latest_ts); + } + + if (_itimediff(kcp->snd_una, prev_una) > 0) { + if (kcp->cwnd < kcp->rmt_wnd) { + IUINT32 mss = kcp->mss; + if (kcp->cwnd < kcp->ssthresh) { + kcp->cwnd++; + kcp->incr += mss; + } else { + if (kcp->incr < mss) kcp->incr = mss; + kcp->incr += (mss * mss) / kcp->incr + (mss / 16); + if ((kcp->cwnd + 1) * mss <= kcp->incr) { + #if 1 + kcp->cwnd = (kcp->incr + mss - 1) / ((mss > 0)? mss : 1); + #else + kcp->cwnd++; + #endif + } + } + if (kcp->cwnd > kcp->rmt_wnd) { + kcp->cwnd = kcp->rmt_wnd; + kcp->incr = kcp->rmt_wnd * mss; + } + } + } + + return 0; +} + + +//--------------------------------------------------------------------- +// ikcp_encode_seg +//--------------------------------------------------------------------- +static char *ikcp_encode_seg(char *ptr, const IKCPSEG *seg) +{ + ptr = ikcp_encode32u(ptr, seg->conv); + ptr = ikcp_encode8u(ptr, (IUINT8)seg->cmd); + ptr = ikcp_encode8u(ptr, (IUINT8)seg->frg); + ptr = ikcp_encode16u(ptr, (IUINT16)seg->wnd); + ptr = ikcp_encode32u(ptr, seg->ts); + ptr = ikcp_encode32u(ptr, seg->sn); + ptr = ikcp_encode32u(ptr, seg->una); + ptr = ikcp_encode32u(ptr, seg->len); + return ptr; +} + +static int ikcp_wnd_unused(const ikcpcb *kcp) +{ + if (kcp->nrcv_que < kcp->rcv_wnd) { + return kcp->rcv_wnd - kcp->nrcv_que; + } + return 0; +} + + +//--------------------------------------------------------------------- +// ikcp_flush +//--------------------------------------------------------------------- +void ikcp_flush(ikcpcb *kcp) +{ + IUINT32 current = kcp->current; + char *buffer = kcp->buffer; + char *ptr = buffer; + int count, size, i; + IUINT32 resent, cwnd; + IUINT32 rtomin; + struct IQUEUEHEAD *p; + int change = 0; + int lost = 0; + IKCPSEG seg; + + // 'ikcp_update' haven't been called. + if (kcp->updated == 0) return; + + seg.conv = kcp->conv; + seg.cmd = IKCP_CMD_ACK; + seg.frg = 0; + seg.wnd = ikcp_wnd_unused(kcp); + seg.una = kcp->rcv_nxt; + seg.len = 0; + seg.sn = 0; + seg.ts = 0; + + // flush acknowledges + count = kcp->ackcount; + for (i = 0; i < count; i++) { + size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { + ikcp_output(kcp, buffer, size); + ptr = buffer; + } + ikcp_ack_get(kcp, i, &seg.sn, &seg.ts); + ptr = ikcp_encode_seg(ptr, &seg); + } + + kcp->ackcount = 0; + + // probe window size (if remote window size equals zero) + if (kcp->rmt_wnd == 0) { + if (kcp->probe_wait == 0) { + kcp->probe_wait = IKCP_PROBE_INIT; + kcp->ts_probe = kcp->current + kcp->probe_wait; + } + else { + if (_itimediff(kcp->current, kcp->ts_probe) >= 0) { + if (kcp->probe_wait < IKCP_PROBE_INIT) + kcp->probe_wait = IKCP_PROBE_INIT; + kcp->probe_wait += kcp->probe_wait / 2; + if (kcp->probe_wait > IKCP_PROBE_LIMIT) + kcp->probe_wait = IKCP_PROBE_LIMIT; + kcp->ts_probe = kcp->current + kcp->probe_wait; + kcp->probe |= IKCP_ASK_SEND; + } + } + } else { + kcp->ts_probe = 0; + kcp->probe_wait = 0; + } + + // flush window probing commands + if (kcp->probe & IKCP_ASK_SEND) { + seg.cmd = IKCP_CMD_WASK; + size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { + ikcp_output(kcp, buffer, size); + ptr = buffer; + } + ptr = ikcp_encode_seg(ptr, &seg); + } + + // flush window probing commands + if (kcp->probe & IKCP_ASK_TELL) { + seg.cmd = IKCP_CMD_WINS; + size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { + ikcp_output(kcp, buffer, size); + ptr = buffer; + } + ptr = ikcp_encode_seg(ptr, &seg); + } + + kcp->probe = 0; + + // calculate window size + cwnd = _imin_(kcp->snd_wnd, kcp->rmt_wnd); + if (kcp->nocwnd == 0) cwnd = _imin_(kcp->cwnd, cwnd); + + // move data from snd_queue to snd_buf + while (_itimediff(kcp->snd_nxt, kcp->snd_una + cwnd) < 0) { + IKCPSEG *newseg; + if (iqueue_is_empty(&kcp->snd_queue)) break; + + newseg = iqueue_entry(kcp->snd_queue.next, IKCPSEG, node); + + iqueue_del(&newseg->node); + iqueue_add_tail(&newseg->node, &kcp->snd_buf); + kcp->nsnd_que--; + kcp->nsnd_buf++; + + newseg->conv = kcp->conv; + newseg->cmd = IKCP_CMD_PUSH; + newseg->wnd = seg.wnd; + newseg->ts = current; + newseg->sn = kcp->snd_nxt++; + newseg->una = kcp->rcv_nxt; + newseg->resendts = current; + newseg->rto = kcp->rx_rto; + newseg->fastack = 0; + newseg->xmit = 0; + } + + // calculate resent + resent = (kcp->fastresend > 0)? (IUINT32)kcp->fastresend : 0xffffffff; + rtomin = (kcp->nodelay == 0)? (kcp->rx_rto >> 3) : 0; + + // flush data segments + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = p->next) { + IKCPSEG *segment = iqueue_entry(p, IKCPSEG, node); + int needsend = 0; + if (segment->xmit == 0) { + needsend = 1; + segment->xmit++; + segment->rto = kcp->rx_rto; + segment->resendts = current + segment->rto + rtomin; + } + else if (_itimediff(current, segment->resendts) >= 0) { + needsend = 1; + segment->xmit++; + kcp->xmit++; + if (kcp->nodelay == 0) { + segment->rto += _imax_(segment->rto, (IUINT32)kcp->rx_rto); + } else { + IINT32 step = (kcp->nodelay < 2)? + ((IINT32)(segment->rto)) : kcp->rx_rto; + segment->rto += step / 2; + } + segment->resendts = current + segment->rto; + lost = 1; + } + else if (segment->fastack >= resent) { + if ((int)segment->xmit <= kcp->fastlimit || + kcp->fastlimit <= 0) { + needsend = 1; + segment->xmit++; + segment->fastack = 0; + segment->resendts = current + segment->rto; + change++; + } + } + + if (needsend) { + int need; + segment->ts = current; + segment->wnd = seg.wnd; + segment->una = kcp->rcv_nxt; + + size = (int)(ptr - buffer); + need = IKCP_OVERHEAD + segment->len; + + if (size + need > (int)kcp->mtu) { + ikcp_output(kcp, buffer, size); + ptr = buffer; + } + + ptr = ikcp_encode_seg(ptr, segment); + + if (segment->len > 0) { + memcpy(ptr, segment->data, segment->len); + ptr += segment->len; + } + + if (segment->xmit >= kcp->dead_link) { + kcp->state = (IUINT32)-1; + } + } + } + + // flash remain segments + size = (int)(ptr - buffer); + if (size > 0) { + ikcp_output(kcp, buffer, size); + } + + // update ssthresh + if (change) { + IUINT32 inflight = kcp->snd_nxt - kcp->snd_una; + kcp->ssthresh = inflight / 2; + if (kcp->ssthresh < IKCP_THRESH_MIN) + kcp->ssthresh = IKCP_THRESH_MIN; + kcp->cwnd = kcp->ssthresh + resent; + kcp->incr = kcp->cwnd * kcp->mss; + } + + if (lost) { + kcp->ssthresh = cwnd / 2; + if (kcp->ssthresh < IKCP_THRESH_MIN) + kcp->ssthresh = IKCP_THRESH_MIN; + kcp->cwnd = 1; + kcp->incr = kcp->mss; + } + + if (kcp->cwnd < 1) { + kcp->cwnd = 1; + kcp->incr = kcp->mss; + } +} + + +//--------------------------------------------------------------------- +// update state (call it repeatedly, every 10ms-100ms), or you can ask +// ikcp_check when to call it again (without ikcp_input/_send calling). +// 'current' - current timestamp in millisec. +//--------------------------------------------------------------------- +void ikcp_update(ikcpcb *kcp, IUINT32 current) +{ + IINT32 slap; + + kcp->current = current; + + if (kcp->updated == 0) { + kcp->updated = 1; + kcp->ts_flush = kcp->current; + } + + slap = _itimediff(kcp->current, kcp->ts_flush); + + if (slap >= 10000 || slap < -10000) { + kcp->ts_flush = kcp->current; + slap = 0; + } + + if (slap >= 0) { + kcp->ts_flush += kcp->interval; + if (_itimediff(kcp->current, kcp->ts_flush) >= 0) { + kcp->ts_flush = kcp->current + kcp->interval; + } + ikcp_flush(kcp); + } +} + + +//--------------------------------------------------------------------- +// Determine when should you invoke ikcp_update: +// returns when you should invoke ikcp_update in millisec, if there +// is no ikcp_input/_send calling. you can call ikcp_update in that +// time, instead of call update repeatly. +// Important to reduce unnacessary ikcp_update invoking. use it to +// schedule ikcp_update (eg. implementing an epoll-like mechanism, +// or optimize ikcp_update when handling massive kcp connections) +//--------------------------------------------------------------------- +IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current) +{ + IUINT32 ts_flush = kcp->ts_flush; + IINT32 tm_flush = 0x7fffffff; + IINT32 tm_packet = 0x7fffffff; + IUINT32 minimal = 0; + struct IQUEUEHEAD *p; + + if (kcp->updated == 0) { + return current; + } + + if (_itimediff(current, ts_flush) >= 10000 || + _itimediff(current, ts_flush) < -10000) { + ts_flush = current; + } + + if (_itimediff(current, ts_flush) >= 0) { + return current; + } + + tm_flush = _itimediff(ts_flush, current); + + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = p->next) { + const IKCPSEG *seg = iqueue_entry(p, const IKCPSEG, node); + IINT32 diff = _itimediff(seg->resendts, current); + if (diff <= 0) { + return current; + } + if (diff < tm_packet) tm_packet = diff; + } + + minimal = (IUINT32)(tm_packet < tm_flush ? tm_packet : tm_flush); + if (minimal >= kcp->interval) minimal = kcp->interval; + + return current + minimal; +} + + + +int ikcp_setmtu(ikcpcb *kcp, int mtu) +{ + char *buffer; + if (mtu < 50 || mtu < (int)IKCP_OVERHEAD) + return -1; + buffer = (char*)ikcp_malloc((mtu + IKCP_OVERHEAD) * 3); + if (buffer == NULL) + return -2; + kcp->mtu = mtu; + kcp->mss = kcp->mtu - IKCP_OVERHEAD; + ikcp_free(kcp->buffer); + kcp->buffer = buffer; + return 0; +} + +int ikcp_interval(ikcpcb *kcp, int interval) +{ + if (interval > 5000) interval = 5000; + else if (interval < 10) interval = 10; + kcp->interval = interval; + return 0; +} + +int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc) +{ + if (nodelay >= 0) { + kcp->nodelay = nodelay; + if (nodelay) { + kcp->rx_minrto = IKCP_RTO_NDL; + } + else { + kcp->rx_minrto = IKCP_RTO_MIN; + } + } + if (interval >= 0) { + if (interval > 5000) interval = 5000; + else if (interval < 10) interval = 10; + kcp->interval = interval; + } + if (resend >= 0) { + kcp->fastresend = resend; + } + if (nc >= 0) { + kcp->nocwnd = nc; + } + return 0; +} + + +int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd) +{ + if (kcp) { + if (sndwnd > 0) { + kcp->snd_wnd = sndwnd; + } + if (rcvwnd > 0) { // must >= max fragment size + kcp->rcv_wnd = _imax_(rcvwnd, IKCP_WND_RCV); + } + } + return 0; +} + +int ikcp_waitsnd(const ikcpcb *kcp) +{ + return kcp->nsnd_buf + kcp->nsnd_que; +} + + +// read conv +IUINT32 ikcp_getconv(const void *ptr) +{ + IUINT32 conv; + ikcp_decode32u((const char*)ptr, &conv); + return conv; +} + + diff --git a/src/protocols/kcp_impl.c b/src/protocols/kcp_impl.c new file mode 100644 index 0000000..6b254eb --- /dev/null +++ b/src/protocols/kcp_impl.c @@ -0,0 +1,171 @@ +/* + * kcp_impl.c + * 基于 UDP 的 KCP 可靠传输实现 + */ + +#include "common.h" +#include "network.h" +#include "logger.h" +#include "kcp/ikcp.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +struct KcpContext { + int fd; + struct sockaddr_in peer_addr; + socklen_t peer_len; + ikcpcb *kcp; +}; + +static int kcp_output(const char *buf, int len, ikcpcb *kcp, void *user) +{ + (void)kcp; + struct KcpContext *ctx = (struct KcpContext *)user; + ssize_t n = sendto(ctx->fd, buf, (size_t)len, 0, + (struct sockaddr *)&ctx->peer_addr, ctx->peer_len); + if (n < 0) { + logger_log("ERROR", "kcp", "sendto_failed errno=%d", errno); + return OMNI_ERR_IO; + } + return 0; +} + +static OmniContext *kcp_init(OmniRole role, + const char *bind_ip, + uint16_t bind_port, + const char *peer_ip, + uint16_t peer_port) +{ + (void)role; + + struct KcpContext *ctx = (struct KcpContext *)calloc(1, sizeof(*ctx)); + if (!ctx) return NULL; + + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + logger_log("ERROR", "kcp", "socket_failed errno=%d", errno); + free(ctx); + return NULL; + } + + if (bind_port != 0) { + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(bind_port); + addr.sin_addr.s_addr = bind_ip ? inet_addr(bind_ip) : INADDR_ANY; + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + logger_log("ERROR", "kcp", "bind_failed errno=%d", errno); + close(fd); + free(ctx); + return NULL; + } + } + + memset(&ctx->peer_addr, 0, sizeof(ctx->peer_addr)); + ctx->peer_addr.sin_family = AF_INET; + ctx->peer_addr.sin_port = htons(peer_port); + ctx->peer_addr.sin_addr.s_addr = peer_ip ? inet_addr(peer_ip) : INADDR_ANY; + ctx->peer_len = sizeof(ctx->peer_addr); + + ctx->fd = fd; + + /* conv 可简单使用端口号 */ + IUINT32 conv = (IUINT32)peer_port; + ikcpcb *kcp = ikcp_create(conv, ctx); + if (!kcp) { + logger_log("ERROR", "kcp", "ikcp_create_failed"); + close(fd); + free(ctx); + return NULL; + } + + ctx->kcp = kcp; + + ikcp_setoutput(kcp, kcp_output); + ikcp_nodelay(kcp, 1, 10, 2, 1); + ikcp_wndsize(kcp, 128, 128); + + logger_log("INFO", "kcp", + "init bind_port=%u peer_ip=%s peer_port=%u", + (unsigned)bind_port, + peer_ip ? peer_ip : "NULL", + (unsigned)peer_port); + + return (OmniContext *)ctx; +} + +static void kcp_update_loop(struct KcpContext *ctx) +{ + IUINT32 current = (IUINT32)omni_now_ms(); + ikcp_update(ctx->kcp, current); + + char buf[1500]; + struct sockaddr_in from; + socklen_t fromlen = sizeof(from); + ssize_t n = recvfrom(ctx->fd, buf, sizeof(buf), MSG_DONTWAIT, + (struct sockaddr *)&from, &fromlen); + if (n > 0) { + ctx->peer_addr = from; + ctx->peer_len = fromlen; + ikcp_input(ctx->kcp, buf, (long)n); + } +} + +static ssize_t kcp_send(OmniContext *c, const void *buf, size_t len) +{ + struct KcpContext *ctx = (struct KcpContext *)c; + if (!ctx || !ctx->kcp) return OMNI_ERR_PARAM; + + int rc = ikcp_send(ctx->kcp, (const char *)buf, (int)len); + if (rc < 0) { + logger_log("ERROR", "kcp", "ikcp_send_failed rc=%d", rc); + return OMNI_ERR_IO; + } + + /* 驱动一次 flush */ + kcp_update_loop(ctx); + return (ssize_t)len; +} + +static ssize_t kcp_recv(OmniContext *c, void *buf, size_t len) +{ + struct KcpContext *ctx = (struct KcpContext *)c; + if (!ctx || !ctx->kcp) return OMNI_ERR_PARAM; + + kcp_update_loop(ctx); + + int n = ikcp_recv(ctx->kcp, (char *)buf, (int)len); + if (n < 0) { + return 0; /* 暂无数据 */ + } + return (ssize_t)n; +} + +static void kcp_close(OmniContext *c) +{ + struct KcpContext *ctx = (struct KcpContext *)c; + if (!ctx) return; + if (ctx->kcp) { + ikcp_release(ctx->kcp); + } + if (ctx->fd >= 0) { + close(ctx->fd); + } + free(ctx); +} + +const struct ProtoVTable KCP_PROTO_VTABLE = { + .init = kcp_init, + .send = kcp_send, + .recv = kcp_recv, + .close = kcp_close, +}; + diff --git a/src/protocols/tcp_impl.c b/src/protocols/tcp_impl.c new file mode 100644 index 0000000..3c7f60f --- /dev/null +++ b/src/protocols/tcp_impl.c @@ -0,0 +1,255 @@ +/* + * tcp_impl.c + * TCP 协议实现,带 16 字节包头解决粘包 + */ + +#include "common.h" +#include "network.h" +#include "logger.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct TcpContext { + int fd; +}; + +static int tcp_set_nodelay(int fd) +{ + int flag = 1; + return setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag)); +} + +static int tcp_set_reuseaddr(int fd) +{ + int flag = 1; + return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)); +} + +static int tcp_bind_and_listen(struct TcpContext *ctx, + const char *bind_ip, + uint16_t bind_port) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + logger_log("ERROR", "tcp", "socket_failed errno=%d", errno); + return -1; + } + + tcp_set_reuseaddr(fd); + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(bind_port); + addr.sin_addr.s_addr = bind_ip ? inet_addr(bind_ip) : INADDR_ANY; + + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + logger_log("ERROR", "tcp", "bind_failed errno=%d", errno); + close(fd); + return -1; + } + + if (listen(fd, 1) < 0) { + logger_log("ERROR", "tcp", "listen_failed errno=%d", errno); + close(fd); + return -1; + } + + logger_log("INFO", "tcp", "listening port=%u", (unsigned)bind_port); + + /* 简化:阻塞接受一个客户端,之后用于长连接 */ + int cfd = accept(fd, NULL, NULL); + if (cfd < 0) { + logger_log("ERROR", "tcp", "accept_failed errno=%d", errno); + close(fd); + return -1; + } + + close(fd); + tcp_set_nodelay(cfd); + + ctx->fd = cfd; + return 0; +} + +static int tcp_connect_peer(struct TcpContext *ctx, + const char *peer_ip, + uint16_t peer_port) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + logger_log("ERROR", "tcp", "socket_failed errno=%d", errno); + return -1; + } + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(peer_port); + addr.sin_addr.s_addr = inet_addr(peer_ip); + + if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + logger_log("ERROR", "tcp", "connect_failed errno=%d", errno); + close(fd); + return -1; + } + + tcp_set_nodelay(fd); + ctx->fd = fd; + logger_log("INFO", "tcp", "connected peer_ip=%s peer_port=%u", + peer_ip, (unsigned)peer_port); + return 0; +} + +static ssize_t tcp_read_n(int fd, void *buf, size_t n) +{ + size_t off = 0; + char *p = (char *)buf; + while (off < n) { + ssize_t r = read(fd, p + off, n - off); + if (r < 0) { + if (errno == EINTR) continue; + return -1; + } + if (r == 0) { + return off; /* 对端关闭 */ + } + off += (size_t)r; + } + return (ssize_t)off; +} + +static ssize_t tcp_write_n(int fd, const void *buf, size_t n) +{ + size_t off = 0; + const char *p = (const char *)buf; + while (off < n) { + ssize_t r = write(fd, p + off, n - off); + if (r < 0) { + if (errno == EINTR) continue; + return -1; + } + off += (size_t)r; + } + return (ssize_t)off; +} + +static OmniContext *tcp_init(OmniRole role, + const char *bind_ip, + uint16_t bind_port, + const char *peer_ip, + uint16_t peer_port) +{ + struct TcpContext *ctx = (struct TcpContext *)calloc(1, sizeof(*ctx)); + if (!ctx) { + return NULL; + } + + int rc; + if (role == OMNI_ROLE_SERVER) { + rc = tcp_bind_and_listen(ctx, bind_ip, bind_port); + } else { + if (!peer_ip || peer_port == 0) { + logger_log("ERROR", "tcp", "client_requires_peer_ip_port"); + free(ctx); + return NULL; + } + rc = tcp_connect_peer(ctx, peer_ip, peer_port); + } + + if (rc != 0) { + free(ctx); + return NULL; + } + + return (OmniContext *)ctx; +} + +static ssize_t tcp_send(OmniContext *c, const void *buf, size_t len) +{ + struct TcpContext *ctx = (struct TcpContext *)c; + if (!ctx || ctx->fd < 0) return OMNI_ERR_PARAM; + + MsgHeader hdr; + hdr.magic = htonl(MSG_MAGIC); + hdr.length = htonl((uint32_t)len); + hdr.seq = 0; /* 如有需要,上层可扩展维护序列号 */ + + uint8_t header_buf[MSG_HEADER_SIZE]; + memcpy(header_buf, &hdr, MSG_HEADER_SIZE); + + ssize_t n1 = tcp_write_n(ctx->fd, header_buf, MSG_HEADER_SIZE); + if (n1 != (ssize_t)MSG_HEADER_SIZE) { + return OMNI_ERR_IO; + } + + ssize_t n2 = tcp_write_n(ctx->fd, buf, len); + if (n2 != (ssize_t)len) { + return OMNI_ERR_IO; + } + + return (ssize_t)len; +} + +static ssize_t tcp_recv(OmniContext *c, void *buf, size_t len) +{ + struct TcpContext *ctx = (struct TcpContext *)c; + if (!ctx || ctx->fd < 0) return OMNI_ERR_PARAM; + + uint8_t header_buf[MSG_HEADER_SIZE]; + ssize_t n1 = tcp_read_n(ctx->fd, header_buf, MSG_HEADER_SIZE); + if (n1 <= 0) { + return n1; /* 0 表示对端关闭,负数为错误 */ + } + if (n1 != (ssize_t)MSG_HEADER_SIZE) { + return OMNI_ERR_IO; + } + + MsgHeader hdr; + memcpy(&hdr, header_buf, MSG_HEADER_SIZE); + if (ntohl(hdr.magic) != MSG_MAGIC) { + logger_log("ERROR", "tcp", "invalid_magic"); + return OMNI_ERR_IO; + } + + uint32_t payload_len = ntohl(hdr.length); + if (payload_len > len) { + logger_log("ERROR", "tcp", "buffer_too_small payload=%u buf_len=%zu", + payload_len, len); + /* 简化:这里返回错误,实际可考虑丢弃或扩展缓冲区 */ + return OMNI_ERR_PARAM; + } + + ssize_t n2 = tcp_read_n(ctx->fd, buf, payload_len); + if (n2 != (ssize_t)payload_len) { + return OMNI_ERR_IO; + } + + return (ssize_t)payload_len; +} + +static void tcp_close(OmniContext *c) +{ + struct TcpContext *ctx = (struct TcpContext *)c; + if (!ctx) return; + if (ctx->fd >= 0) { + close(ctx->fd); + } + free(ctx); +} + +const struct ProtoVTable TCP_PROTO_VTABLE = { + .init = tcp_init, + .send = tcp_send, + .recv = tcp_recv, + .close = tcp_close, +}; + diff --git a/src/protocols/udp_impl.c b/src/protocols/udp_impl.c new file mode 100644 index 0000000..452f918 --- /dev/null +++ b/src/protocols/udp_impl.c @@ -0,0 +1,126 @@ +/* + * udp_impl.c + * UDP 协议实现(无连接,基础 sendto/recvfrom) + */ + +#include "common.h" +#include "network.h" +#include "logger.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +struct UdpContext { + int fd; + struct sockaddr_in peer_addr; + socklen_t peer_len; +}; + +static OmniContext *udp_init(OmniRole role, + const char *bind_ip, + uint16_t bind_port, + const char *peer_ip, + uint16_t peer_port) +{ + (void)role; + + struct UdpContext *ctx = (struct UdpContext *)calloc(1, sizeof(*ctx)); + if (!ctx) return NULL; + + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + logger_log("ERROR", "udp", "socket_failed errno=%d", errno); + free(ctx); + return NULL; + } + + if (bind_port != 0) { + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(bind_port); + addr.sin_addr.s_addr = bind_ip ? inet_addr(bind_ip) : INADDR_ANY; + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + logger_log("ERROR", "udp", "bind_failed errno=%d", errno); + close(fd); + free(ctx); + return NULL; + } + } + + memset(&ctx->peer_addr, 0, sizeof(ctx->peer_addr)); + ctx->peer_addr.sin_family = AF_INET; + ctx->peer_addr.sin_port = htons(peer_port); + ctx->peer_addr.sin_addr.s_addr = peer_ip ? inet_addr(peer_ip) : INADDR_ANY; + ctx->peer_len = sizeof(ctx->peer_addr); + + ctx->fd = fd; + + logger_log("INFO", "udp", + "init bind_port=%u peer_ip=%s peer_port=%u", + (unsigned)bind_port, + peer_ip ? peer_ip : "NULL", + (unsigned)peer_port); + + return (OmniContext *)ctx; +} + +static ssize_t udp_send(OmniContext *c, const void *buf, size_t len) +{ + struct UdpContext *ctx = (struct UdpContext *)c; + if (!ctx || ctx->fd < 0) return OMNI_ERR_PARAM; + + ssize_t n = sendto(ctx->fd, buf, len, 0, + (struct sockaddr *)&ctx->peer_addr, ctx->peer_len); + if (n < 0) { + logger_log("ERROR", "udp", "sendto_failed errno=%d", errno); + return OMNI_ERR_IO; + } + return n; +} + +static ssize_t udp_recv(OmniContext *c, void *buf, size_t len) +{ + struct UdpContext *ctx = (struct UdpContext *)c; + if (!ctx || ctx->fd < 0) return OMNI_ERR_PARAM; + + struct sockaddr_in from; + socklen_t fromlen = sizeof(from); + ssize_t n = recvfrom(ctx->fd, buf, len, 0, + (struct sockaddr *)&from, &fromlen); + if (n < 0) { + if (errno == EINTR) return 0; + logger_log("ERROR", "udp", "recvfrom_failed errno=%d", errno); + return OMNI_ERR_IO; + } + + /* 默认更新 peer 为最近一次通信对端,便于“伪长连接” */ + ctx->peer_addr = from; + ctx->peer_len = fromlen; + + return n; +} + +static void udp_close(OmniContext *c) +{ + struct UdpContext *ctx = (struct UdpContext *)c; + if (!ctx) return; + if (ctx->fd >= 0) { + close(ctx->fd); + } + free(ctx); +} + +const struct ProtoVTable UDP_PROTO_VTABLE = { + .init = udp_init, + .send = udp_send, + .recv = udp_recv, + .close = udp_close, +}; + diff --git a/tcp_impl.o b/tcp_impl.o new file mode 100644 index 0000000000000000000000000000000000000000..79355db31f1bcf2cd69bb35167e9df66e86b4730 GIT binary patch literal 7912 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