fix:更新客户端功能

This commit is contained in:
nnbcccscdscdsc
2026-03-16 22:28:05 +08:00
parent 7f2f79e672
commit 6c975d9ae3
18 changed files with 8067 additions and 33092 deletions

View File

@@ -17,13 +17,79 @@
#include <sys/types.h>
#include <unistd.h>
struct KcpContext {
int fd;
struct sockaddr_in peer_addr;
socklen_t peer_len;
ikcpcb *kcp;
uint32_t last_xmit; /* 用于推算重传/发送次数变化 */
};
struct KcpContext {
int fd;
struct sockaddr_in peer_addr;
socklen_t peer_len;
ikcpcb *kcp;
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)
{
@@ -120,14 +186,38 @@ static void kcp_update_loop(struct KcpContext *ctx)
ikcp_input(ctx->kcp, buf, (long)n);
}
/* KCP 内部状态监控 */
uint32_t xmit = ctx->kcp->xmit;
if (xmit >= ctx->last_xmit) {
logger_on_kcp_retrans((uint64_t)(xmit - ctx->last_xmit));
}
ctx->last_xmit = xmit;
uint64_t t1 = omni_now_ms();
/* KCP 内部状态监控:按分片 xmit 变化统计首次发送和真实重传。 */
kcp_sample_transport_stats(ctx);
/* rx_srtt 是 KCP 平滑后的 RTT单位 ms大于 0 才说明已经有有效采样值。 */
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);
}
uint64_t t1 = omni_now_ms();
logger_log("DEBUG", "kcp",
"update ms=%llu cwnd=%u ssthresh=%u rmt_wnd=%u snd_wnd=%u rcv_wnd=%u "
"rx_srtt=%u rx_rto=%u nsnd_buf=%u nsnd_que=%u nrcv_buf=%u nrcv_que=%u xmit=%u state=%u",
@@ -191,14 +281,15 @@ 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);
}
if (ctx->kcp) {
ikcp_release(ctx->kcp);
}
if (ctx->fd >= 0) {
close(ctx->fd);
}
free(ctx->seg_xmit_seen);
free(ctx);
}
const struct ProtoVTable KCP_PROTO_VTABLE = {
.init = kcp_init,

View File

@@ -14,44 +14,192 @@
#include "logger.h"
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
/* Linux 下 TCP_INFO 定义通常已在 <netinet/tcp.h> 提供,避免引入 <linux/tcp.h> 重定义 */
#include <errno.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.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 {
/* 已建立连接的 socket fd服务端 accept 后或客户端 connect 后)。 */
int fd;
};
#ifdef __linux__
static void tcp_log_info(int fd, const char *tag)
{
struct tcp_info ti;
socklen_t len = sizeof(ti);
if (getsockopt(fd, IPPROTO_TCP, TCP_INFO, &ti, &len) != 0) {
return;
}
/* 注意tcpi_rtt 单位通常为微秒Linux这里转 ms 仅用于日志观察 */
unsigned long long rtt_ms = (unsigned long long)(ti.tcpi_rtt / 1000u);
unsigned long long rttvar_ms = (unsigned long long)(ti.tcpi_rttvar / 1000u);
logger_log("INFO", "tcpinfo",
"tag=%s state=%u retransmits=%u probes=%u backoff=%u "
"rto=%u ato=%u rtt_ms=%llu rttvar_ms=%llu "
"snd_cwnd=%u snd_ssthresh=%u snd_mss=%u rcv_mss=%u "
"lost=%u retrans=%u fackets=%u "
"last_data_sent_ms=%u last_data_recv_ms=%u",
tag ? tag : "sample",
(unsigned)ti.tcpi_state,
#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)
{
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);
memset(&ti, 0, sizeof(ti));
if (getsockopt(fd, IPPROTO_TCP, TCP_INFO, &ti, &len) != 0) {
return;
}
/* 注意tcpi_rtt 单位通常为微秒Linux这里转 ms 仅用于日志观察 */
unsigned long long rtt_ms = (unsigned long long)(ti.tcpi_rtt / 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",
"tag=%s state=%u retransmits=%u probes=%u backoff=%u "
"rto=%u ato=%u rtt_ms=%llu rttvar_ms=%llu "
"snd_cwnd=%u snd_ssthresh=%u snd_mss=%u rcv_mss=%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",
tag ? tag : "sample",
(unsigned)ti.tcpi_state,
(unsigned)ti.tcpi_retransmits,
(unsigned)ti.tcpi_probes,
(unsigned)ti.tcpi_backoff,
@@ -61,15 +209,24 @@ static void tcp_log_info(int fd, const char *tag)
rttvar_ms,
(unsigned)ti.tcpi_snd_cwnd,
(unsigned)ti.tcpi_snd_ssthresh,
(unsigned)ti.tcpi_snd_mss,
(unsigned)ti.tcpi_rcv_mss,
(unsigned)ti.tcpi_lost,
(unsigned)ti.tcpi_retrans,
(unsigned)ti.tcpi_fackets,
(unsigned)ti.tcpi_last_data_sent,
(unsigned)ti.tcpi_last_data_recv);
}
#endif
(unsigned)ti.tcpi_snd_mss,
(unsigned)ti.tcpi_rcv_mss,
(unsigned)ti.tcpi_lost,
(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_last_data_sent,
(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
static int tcp_set_nodelay(int fd)
{
@@ -277,13 +434,13 @@ static ssize_t tcp_send(OmniContext *c, const void *buf, size_t len)
uint64_t t1 = omni_now_ms();
/* 记录协议层发送耗时,便于后续性能分析。 */
logger_on_proto_send_latency(t1 - t0);
logger_log("DEBUG", "tcp", "send payload_bytes=%zu header_bytes=%zu proto_ms=%llu",
len, (size_t)MSG_HEADER_SIZE, (unsigned long long)(t1 - t0));
#ifdef __linux__
tcp_log_info(ctx->fd, "after_send");
#endif
return (ssize_t)len;
}
logger_log("DEBUG", "tcp", "send payload_bytes=%zu header_bytes=%zu proto_ms=%llu",
len, (size_t)MSG_HEADER_SIZE, (unsigned long long)(t1 - t0));
#ifdef __linux__
tcp_log_info(ctx->fd, "after_send");
#endif
return (ssize_t)len;
}
static ssize_t tcp_recv(OmniContext *c, void *buf, size_t len)
{
@@ -338,11 +495,11 @@ static ssize_t tcp_recv(OmniContext *c, void *buf, size_t len)
(unsigned)host_hdr.type,
(unsigned long long)host_hdr.timestamp,
(unsigned long long)(t1 - t0));
#ifdef __linux__
tcp_log_info(ctx->fd, "after_recv");
#endif
return (ssize_t)payload_len;
}
#ifdef __linux__
tcp_log_info(ctx->fd, "after_recv");
#endif
return (ssize_t)payload_len;
}
static void tcp_close(OmniContext *c)
{

View File

@@ -9,18 +9,65 @@
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
struct UdpContext {
int fd;
struct sockaddr_in peer_addr;
socklen_t peer_len;
};
struct UdpContext {
int fd;
struct sockaddr_in peer_addr;
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,
const char *bind_ip,
@@ -78,12 +125,13 @@ static ssize_t udp_send(OmniContext *c, const void *buf, size_t len)
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;
}
if (n < 0) {
logger_log("ERROR", "udp", "sendto_failed errno=%d", errno);
return OMNI_ERR_IO;
}
udp_sample_socket_buffers(ctx->fd);
return n;
}
static ssize_t udp_recv(OmniContext *c, void *buf, size_t len)
{
@@ -100,12 +148,13 @@ static ssize_t udp_recv(OmniContext *c, void *buf, size_t len)
return OMNI_ERR_IO;
}
/* 默认更新 peer 为最近一次通信对端,便于“伪长连接” */
ctx->peer_addr = from;
ctx->peer_len = fromlen;
return n;
}
/* 默认更新 peer 为最近一次通信对端,便于“伪长连接” */
ctx->peer_addr = from;
ctx->peer_len = fromlen;
udp_sample_socket_buffers(ctx->fd);
return n;
}
static void udp_close(OmniContext *c)
{