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

754
src/apps/hub_main.c Normal file
View File

@@ -0,0 +1,754 @@
/*
* hub_main.c
* 云端 hub维护 client_id -> 连接 的映射,并负责 register / bind / tunnel 路由
*
* 当前阶段只实现 TCP 控制面:
* - 多个 peer 主动连接 hub
* - peer 先 REGISTER 自己的逻辑 ID
* - peer 可 BIND 默认目标
* - peer 发送 TUNNEL 后hub 根据 dst_id 转发给目标
*
* 后续文件/视频消息可以直接复用 MSG_TYPE_PEER_TUNNEL 的 inner_type。
*/
#include "common.h"
#include "logger.h"
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define HUB_MAX_PAYLOAD (PEER_TUNNEL_META_SIZE + 65536u)
#define HUB_BACKLOG 64
typedef struct HubState HubState;
typedef struct HubClient {
HubState *hub;
int fd;
pthread_t tid;
pthread_mutex_t write_mu;
atomic_int running;
char client_id[OMNI_PEER_ID_SIZE];
char bound_peer[OMNI_PEER_ID_SIZE];
char remote_ip[64];
uint16_t remote_port;
struct HubClient *next;
} HubClient;
struct HubState {
int listen_fd;
atomic_int running;
pthread_mutex_t mu;
HubClient *clients;
};
static volatile sig_atomic_t g_stop = 0;
static void on_signal(int signo)
{
(void)signo;
g_stop = 1;
}
static void install_signal_handlers(void)
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = on_signal;
sigemptyset(&sa.sa_mask);
(void)sigaction(SIGINT, &sa, NULL);
(void)sigaction(SIGTERM, &sa, NULL);
(void)signal(SIGPIPE, SIG_IGN);
}
static void usage(const char *prog)
{
fprintf(stderr,
"Usage:\n"
" %s -P <listen_port> [-b <bind_ip>] [-p tcp]\n",
prog);
}
static int peer_id_is_valid(const char *id)
{
size_t len = 0;
if (!id || !id[0]) {
return 0;
}
for (len = 0; id[len] != '\0'; ++len) {
unsigned char ch = (unsigned char)id[len];
if (len + 1u >= OMNI_PEER_ID_SIZE) {
return 0;
}
if (!(isalnum(ch) || ch == '_' || ch == '-' || ch == '.')) {
return 0;
}
}
return 1;
}
static const char *safe_client_id(const HubClient *client)
{
if (!client || client->client_id[0] == '\0') {
return "unregistered";
}
return client->client_id;
}
static ssize_t read_n(int fd, void *buf, size_t n)
{
uint8_t *p = (uint8_t *)buf;
size_t done = 0;
while (done < n) {
ssize_t rc = recv(fd, p + done, n - done, 0);
if (rc == 0) {
return 0;
}
if (rc < 0) {
if (errno == EINTR) {
continue;
}
return -1;
}
done += (size_t)rc;
}
return (ssize_t)done;
}
static ssize_t write_n(int fd, const void *buf, size_t n)
{
const uint8_t *p = (const uint8_t *)buf;
size_t done = 0;
while (done < n) {
ssize_t rc = send(fd, p + done, n - done, 0);
if (rc < 0) {
if (errno == EINTR) {
continue;
}
return -1;
}
done += (size_t)rc;
}
return (ssize_t)done;
}
static int recv_app_message(int fd, MsgHeader *out_hdr, uint8_t *payload_buf, size_t payload_cap)
{
MsgHeader net_hdr;
ssize_t n;
if (!out_hdr || !payload_buf) {
return OMNI_ERR_PARAM;
}
n = read_n(fd, &net_hdr, MSG_HEADER_SIZE);
if (n == 0) {
return 0;
}
if (n != (ssize_t)MSG_HEADER_SIZE) {
return OMNI_ERR_IO;
}
omni_msg_header_decode(&net_hdr, out_hdr);
if (out_hdr->len > payload_cap) {
logger_log("ERROR", "hub", "payload_too_large len=%u cap=%zu",
(unsigned)out_hdr->len, payload_cap);
return OMNI_ERR_IO;
}
if (out_hdr->len == 0) {
return 1;
}
n = read_n(fd, payload_buf, out_hdr->len);
if (n != (ssize_t)out_hdr->len) {
return OMNI_ERR_IO;
}
logger_on_recv(MSG_HEADER_SIZE + out_hdr->len);
logger_maybe_print_performance_log("hub_recv");
return 1;
}
static int send_app_message_locked(HubClient *client,
uint32_t type,
const void *payload,
uint32_t payload_len)
{
MsgHeader hdr;
uint8_t header_buf[MSG_HEADER_SIZE];
if (!client) {
return OMNI_ERR_PARAM;
}
omni_msg_header_encode(&hdr, type, payload_len, omni_now_ms());
memcpy(header_buf, &hdr, sizeof(header_buf));
if (write_n(client->fd, header_buf, sizeof(header_buf)) != (ssize_t)sizeof(header_buf)) {
return OMNI_ERR_IO;
}
if (payload_len > 0 && payload) {
if (write_n(client->fd, payload, payload_len) != (ssize_t)payload_len) {
return OMNI_ERR_IO;
}
}
logger_on_send(MSG_HEADER_SIZE + payload_len);
logger_maybe_print_performance_log("hub_send");
return OMNI_OK;
}
static int send_app_message(HubClient *client,
uint32_t type,
const void *payload,
uint32_t payload_len)
{
int rc;
if (!client) {
return OMNI_ERR_PARAM;
}
pthread_mutex_lock(&client->write_mu);
rc = send_app_message_locked(client, type, payload, payload_len);
pthread_mutex_unlock(&client->write_mu);
return rc;
}
static int send_status_locked(HubClient *client,
uint32_t code,
const char *self_id,
const char *peer_id,
const char *detail)
{
PeerStatusMeta status_meta;
omni_peer_status_meta_encode(&status_meta, code, self_id, peer_id, detail);
return send_app_message_locked(client,
MSG_TYPE_PEER_STATUS,
&status_meta,
PEER_STATUS_META_SIZE);
}
static int send_status(HubClient *client,
uint32_t code,
const char *self_id,
const char *peer_id,
const char *detail)
{
PeerStatusMeta status_meta;
omni_peer_status_meta_encode(&status_meta, code, self_id, peer_id, detail);
return send_app_message(client,
MSG_TYPE_PEER_STATUS,
&status_meta,
PEER_STATUS_META_SIZE);
}
static HubClient *find_client_locked(HubState *hub, const char *client_id)
{
HubClient *cur;
for (cur = hub->clients; cur != NULL; cur = cur->next) {
if (cur->client_id[0] == '\0') {
continue;
}
if (strcmp(cur->client_id, client_id) == 0) {
return cur;
}
}
return NULL;
}
static void add_client_locked(HubState *hub, HubClient *client)
{
client->next = hub->clients;
hub->clients = client;
}
static void remove_client_locked(HubState *hub, HubClient *client)
{
HubClient **pp = &hub->clients;
while (*pp) {
if (*pp == client) {
*pp = client->next;
client->next = NULL;
return;
}
pp = &(*pp)->next;
}
}
static void unregister_client(HubClient *client)
{
HubState *hub;
HubClient **notify = NULL;
size_t notify_count = 0;
size_t notify_cap = 0;
HubClient *cur;
char departed_id[OMNI_PEER_ID_SIZE];
if (!client || !client->hub) {
return;
}
hub = client->hub;
memset(departed_id, 0, sizeof(departed_id));
omni_copy_fixed_ascii(departed_id, sizeof(departed_id), client->client_id);
pthread_mutex_lock(&hub->mu);
remove_client_locked(hub, client);
if (departed_id[0] != '\0') {
for (cur = hub->clients; cur != NULL; cur = cur->next) {
if (strcmp(cur->bound_peer, departed_id) != 0) {
continue;
}
if (notify_count == notify_cap) {
size_t new_cap = (notify_cap == 0) ? 4u : notify_cap * 2u;
HubClient **new_notify =
(HubClient **)realloc(notify, new_cap * sizeof(*new_notify));
if (!new_notify) {
break;
}
notify = new_notify;
notify_cap = new_cap;
}
cur->bound_peer[0] = '\0';
pthread_mutex_lock(&cur->write_mu);
notify[notify_count++] = cur;
}
}
pthread_mutex_unlock(&hub->mu);
for (size_t i = 0; i < notify_count; ++i) {
(void)send_status_locked(notify[i],
PEER_STATUS_UNBOUND,
notify[i]->client_id,
departed_id,
"peer_offline binding_cleared");
pthread_mutex_unlock(&notify[i]->write_mu);
}
free(notify);
}
static void close_client(HubClient *client)
{
if (!client) {
return;
}
pthread_mutex_lock(&client->write_mu);
if (client->fd >= 0) {
shutdown(client->fd, SHUT_RDWR);
close(client->fd);
client->fd = -1;
}
pthread_mutex_unlock(&client->write_mu);
}
static int handle_register(HubClient *client, const uint8_t *payload, uint32_t payload_len)
{
PeerRegisterMeta register_meta;
char detail[128];
logger_log("DEBUG", "hub",
"handle_register remote=%s:%u payload_len=%u",
client->remote_ip,
(unsigned)client->remote_port,
(unsigned)payload_len);
if (payload_len < PEER_REGISTER_META_SIZE) {
return send_status(client, PEER_STATUS_ERROR, NULL, NULL, "short_register_payload");
}
omni_peer_register_meta_decode((const PeerRegisterMeta *)payload, &register_meta);
if (!peer_id_is_valid(register_meta.client_id)) {
return send_status(client, PEER_STATUS_ERROR, NULL, NULL, "invalid_client_id");
}
pthread_mutex_lock(&client->hub->mu);
if (client->client_id[0] != '\0') {
pthread_mutex_unlock(&client->hub->mu);
return send_status(client, PEER_STATUS_ERROR, client->client_id, NULL, "already_registered");
}
if (find_client_locked(client->hub, register_meta.client_id) != NULL) {
pthread_mutex_unlock(&client->hub->mu);
return send_status(client, PEER_STATUS_ERROR, NULL, register_meta.client_id, "client_id_in_use");
}
omni_copy_fixed_ascii(client->client_id, sizeof(client->client_id), register_meta.client_id);
pthread_mutex_unlock(&client->hub->mu);
snprintf(detail, sizeof(detail), "registered remote=%s:%u",
client->remote_ip, (unsigned)client->remote_port);
logger_log("INFO", "hub",
"client_registered client_id=%s remote=%s:%u",
client->client_id,
client->remote_ip,
(unsigned)client->remote_port);
return send_status(client,
PEER_STATUS_REGISTERED,
client->client_id,
NULL,
detail);
}
static int handle_bind(HubClient *client, const uint8_t *payload, uint32_t payload_len)
{
PeerBindMeta bind_meta;
HubClient *target;
logger_log("DEBUG", "hub",
"handle_bind client_id=%s payload_len=%u",
safe_client_id(client),
(unsigned)payload_len);
if (client->client_id[0] == '\0') {
return send_status(client, PEER_STATUS_ERROR, NULL, NULL, "register_first");
}
if (payload_len < PEER_BIND_META_SIZE) {
return send_status(client, PEER_STATUS_ERROR, client->client_id, NULL, "short_bind_payload");
}
omni_peer_bind_meta_decode((const PeerBindMeta *)payload, &bind_meta);
if (!peer_id_is_valid(bind_meta.peer_id)) {
return send_status(client, PEER_STATUS_ERROR, client->client_id, NULL, "invalid_peer_id");
}
if (strcmp(bind_meta.peer_id, client->client_id) == 0) {
return send_status(client, PEER_STATUS_ERROR, client->client_id, bind_meta.peer_id, "cannot_bind_self");
}
pthread_mutex_lock(&client->hub->mu);
target = find_client_locked(client->hub, bind_meta.peer_id);
if (!target) {
pthread_mutex_unlock(&client->hub->mu);
return send_status(client, PEER_STATUS_ERROR, client->client_id, bind_meta.peer_id, "peer_not_online");
}
omni_copy_fixed_ascii(client->bound_peer, sizeof(client->bound_peer), bind_meta.peer_id);
pthread_mutex_unlock(&client->hub->mu);
logger_log("INFO", "hub",
"peer_bound client_id=%s peer_id=%s",
client->client_id,
bind_meta.peer_id);
return send_status(client,
PEER_STATUS_BOUND,
client->client_id,
bind_meta.peer_id,
"bind_ok");
}
static int handle_tunnel(HubClient *client, const uint8_t *payload, uint32_t payload_len)
{
PeerTunnelMeta tunnel_meta;
HubClient *target = NULL;
char effective_dst[OMNI_PEER_ID_SIZE];
uint8_t *forward_payload = NULL;
uint32_t inner_len;
int rc = OMNI_OK;
logger_log("DEBUG", "hub",
"handle_tunnel client_id=%s payload_len=%u",
safe_client_id(client),
(unsigned)payload_len);
if (client->client_id[0] == '\0') {
return send_status(client, PEER_STATUS_ERROR, NULL, NULL, "register_first");
}
if (payload_len < PEER_TUNNEL_META_SIZE) {
return send_status(client, PEER_STATUS_ERROR, client->client_id, NULL, "short_tunnel_payload");
}
omni_peer_tunnel_meta_decode((const PeerTunnelMeta *)payload, &tunnel_meta);
inner_len = payload_len - PEER_TUNNEL_META_SIZE;
memset(effective_dst, 0, sizeof(effective_dst));
pthread_mutex_lock(&client->hub->mu);
if (tunnel_meta.dst_id[0] != '\0') {
omni_copy_fixed_ascii(effective_dst, sizeof(effective_dst), tunnel_meta.dst_id);
} else {
omni_copy_fixed_ascii(effective_dst, sizeof(effective_dst), client->bound_peer);
}
if (effective_dst[0] != '\0') {
target = find_client_locked(client->hub, effective_dst);
if (target) {
pthread_mutex_lock(&target->write_mu);
}
}
pthread_mutex_unlock(&client->hub->mu);
if (!peer_id_is_valid(effective_dst)) {
return send_status(client, PEER_STATUS_ERROR, client->client_id, NULL, "missing_or_invalid_destination");
}
if (!target) {
return send_status(client, PEER_STATUS_ERROR, client->client_id, effective_dst, "destination_not_online");
}
forward_payload = (uint8_t *)malloc(payload_len);
if (!forward_payload) {
pthread_mutex_unlock(&target->write_mu);
return send_status(client, PEER_STATUS_ERROR, client->client_id, effective_dst, "malloc_forward_payload_failed");
}
{
PeerTunnelMeta forward_meta;
omni_peer_tunnel_meta_encode(&forward_meta,
client->client_id,
effective_dst,
tunnel_meta.inner_type);
memcpy(forward_payload, &forward_meta, PEER_TUNNEL_META_SIZE);
if (inner_len > 0) {
memcpy(forward_payload + PEER_TUNNEL_META_SIZE,
payload + PEER_TUNNEL_META_SIZE,
inner_len);
}
}
rc = send_app_message_locked(target,
MSG_TYPE_PEER_TUNNEL,
forward_payload,
payload_len);
pthread_mutex_unlock(&target->write_mu);
free(forward_payload);
if (rc != OMNI_OK) {
logger_log("ERROR", "hub",
"forward_failed src_id=%s dst_id=%s inner_type=%u",
client->client_id,
effective_dst,
(unsigned)tunnel_meta.inner_type);
return send_status(client, PEER_STATUS_ERROR, client->client_id, effective_dst, "forward_failed");
}
logger_log("INFO", "hub",
"forward_ok src_id=%s dst_id=%s inner_type=%u payload_bytes=%u",
client->client_id,
effective_dst,
(unsigned)tunnel_meta.inner_type,
(unsigned)inner_len);
return OMNI_OK;
}
static void *client_thread_main(void *arg)
{
HubClient *client = (HubClient *)arg;
uint8_t payload[HUB_MAX_PAYLOAD];
while (atomic_load(&client->hub->running) && atomic_load(&client->running)) {
MsgHeader hdr;
int rc = recv_app_message(client->fd, &hdr, payload, sizeof(payload));
if (rc == 0) {
logger_log("INFO", "hub",
"client_closed client_id=%s remote=%s:%u",
safe_client_id(client),
client->remote_ip,
(unsigned)client->remote_port);
break;
}
if (rc < 0) {
logger_log("ERROR", "hub",
"client_recv_failed client_id=%s remote=%s:%u rc=%d",
safe_client_id(client),
client->remote_ip,
(unsigned)client->remote_port,
rc);
break;
}
logger_log("DEBUG", "hub",
"message_recv client_id=%s type=%u len=%u",
safe_client_id(client),
(unsigned)hdr.type,
(unsigned)hdr.len);
switch (hdr.type) {
case MSG_TYPE_PEER_REGISTER:
(void)handle_register(client, payload, hdr.len);
break;
case MSG_TYPE_PEER_BIND:
(void)handle_bind(client, payload, hdr.len);
break;
case MSG_TYPE_PEER_TUNNEL:
(void)handle_tunnel(client, payload, hdr.len);
break;
default:
(void)send_status(client,
PEER_STATUS_ERROR,
client->client_id[0] ? client->client_id : NULL,
NULL,
"unsupported_message_type");
break;
}
}
atomic_store(&client->running, 0);
unregister_client(client);
close_client(client);
pthread_mutex_destroy(&client->write_mu);
free(client);
return NULL;
}
static int create_listen_socket(const char *bind_ip, uint16_t port)
{
int fd;
int reuse = 1;
struct sockaddr_in addr;
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
return -1;
}
(void)setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (bind_ip && bind_ip[0] != '\0') {
if (inet_pton(AF_INET, bind_ip, &addr.sin_addr) != 1) {
close(fd);
return -1;
}
} else {
addr.sin_addr.s_addr = htonl(INADDR_ANY);
}
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
close(fd);
return -1;
}
if (listen(fd, HUB_BACKLOG) != 0) {
close(fd);
return -1;
}
return fd;
}
int main(int argc, char **argv)
{
const char *bind_ip = NULL;
const char *proto_str = "tcp";
int listen_port = 0;
int opt;
HubState hub;
while ((opt = getopt(argc, argv, "b:p:P:")) != -1) {
switch (opt) {
case 'b':
bind_ip = optarg;
break;
case 'p':
proto_str = optarg;
break;
case 'P':
listen_port = atoi(optarg);
break;
default:
usage(argv[0]);
return 1;
}
}
if (listen_port <= 0 || strcmp(proto_str, "tcp") != 0) {
usage(argv[0]);
return 1;
}
logger_init();
install_signal_handlers();
memset(&hub, 0, sizeof(hub));
atomic_init(&hub.running, 1);
pthread_mutex_init(&hub.mu, NULL);
hub.listen_fd = create_listen_socket(bind_ip, (uint16_t)listen_port);
if (hub.listen_fd < 0) {
perror("hub listen");
pthread_mutex_destroy(&hub.mu);
return 1;
}
logger_log("INFO", "hub", "listening bind_ip=%s port=%u",
bind_ip ? bind_ip : "0.0.0.0",
(unsigned)listen_port);
while (atomic_load(&hub.running) && !g_stop) {
struct sockaddr_in peer_addr;
socklen_t peer_len = sizeof(peer_addr);
int cfd = accept(hub.listen_fd, (struct sockaddr *)&peer_addr, &peer_len);
if (cfd < 0) {
if (errno == EINTR && !g_stop) {
continue;
}
if (g_stop) {
break;
}
perror("hub accept");
break;
}
HubClient *client = (HubClient *)calloc(1, sizeof(*client));
if (!client) {
close(cfd);
continue;
}
client->hub = &hub;
client->fd = cfd;
atomic_init(&client->running, 1);
pthread_mutex_init(&client->write_mu, NULL);
if (!inet_ntop(AF_INET, &peer_addr.sin_addr, client->remote_ip, sizeof(client->remote_ip))) {
omni_copy_fixed_ascii(client->remote_ip, sizeof(client->remote_ip), "unknown");
}
client->remote_port = ntohs(peer_addr.sin_port);
pthread_mutex_lock(&hub.mu);
add_client_locked(&hub, client);
pthread_mutex_unlock(&hub.mu);
if (pthread_create(&client->tid, NULL, client_thread_main, client) != 0) {
perror("hub pthread_create");
unregister_client(client);
close_client(client);
pthread_mutex_destroy(&client->write_mu);
free(client);
continue;
}
pthread_detach(client->tid);
logger_log("INFO", "hub",
"client_connected remote=%s:%u",
client->remote_ip,
(unsigned)client->remote_port);
}
atomic_store(&hub.running, 0);
if (hub.listen_fd >= 0) {
close(hub.listen_fd);
}
logger_print_performance_log("final");
return 0;
}