You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现跨网络客户端通信?C/S架构消息应用扩展咨询

跨网络C/S消息应用扩展实现指南(基于getaddrinfo)

先搞懂getaddrinfo到底干嘛用

getaddrinfo是标准库中用于地址解析的核心函数,替代了老旧的gethostbyname:

  • 它能把域名/主机名(比如your-server.com)和端口号转换成可直接用于Socket连接的地址结构体
  • 自动兼容IPv4和IPv6,无需手动区分协议版本
  • 返回一个addrinfo结构体链表,每个节点对应一个可用的网络地址
  • 成功返回0,失败返回错误码,可用gai_strerror(rv)获取可读错误信息

服务器端修改(核心:绑定公网可访问地址)

你原来的IP_ADDRESS "local"只能绑定LAN内地址,跨网需要让服务器监听所有网卡,或者绑定公网IP。用getaddrinfo实现的代码如下:

#define PORT "8080" // 改成字符串,因为getaddrinfo接受字符串格式端口
#define MAX_SIZE 1024

int main() {
    struct addrinfo hints, *servinfo, *p;
    int rv, sockfd;

    // 初始化hints,指定我们需要的地址类型
    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;       // 自动适配IPv4/IPv6
    hints.ai_socktype = SOCK_STREAM;   // 用TCP协议(如果是UDP改SOCK_DGRAM)
    hints.ai_flags = AI_PASSIVE;       // 服务器模式:绑定到本机所有可用网卡

    // 解析地址:NULL表示绑定本机所有地址,PORT是监听端口
    if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo错误: %s\n", gai_strerror(rv));
        exit(1);
    }

    // 遍历addrinfo链表,创建并绑定Socket
    for(p = servinfo; p != NULL; p = p->ai_next) {
        // 创建Socket
        if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
            perror("服务器: 创建Socket失败");
            continue;
        }

        // 设置端口复用,避免重启服务时端口占用报错
        int yes = 1;
        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
            perror("setsockopt错误");
            exit(1);
        }

        // 绑定Socket到地址
        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("服务器: 绑定地址失败");
            continue;
        }

        break; // 找到可用地址并绑定成功,跳出循环
    }

    // 检查是否绑定成功
    if (p == NULL) {
        fprintf(stderr, "服务器: 所有地址绑定失败\n");
        exit(1);
    }

    freeaddrinfo(servinfo); // 用完必须释放链表内存,避免内存泄漏

    // 后续监听、接受连接的逻辑和你原来的LAN版本一致
    listen(sockfd, 10);
    // ...
}

服务器网络层面要求:

  1. 服务器需要有公网IP,或者在NAT(家用路由器)后设置端口映射:把外部网络的8080端口转发到服务器内网IP的8080端口
  2. 关闭服务器防火墙对8080端口的限制

客户端修改(核心:解析服务器公网地址)

原来的IP_ADDRESS "local"只能连LAN内服务器,跨网需要指定服务器的公网域名或IP,用getaddrinfo自动完成DNS解析:

#define PORT "8080"
#define MAX_SIZE 1024

int main(int argc, char *argv[]) {
    // 可以从命令行传入服务器地址,比如./client your-server.com
    if (argc != 2) {
        fprintf(stderr, "用法: %s <服务器域名/IP>\n", argv[0]);
        exit(1);
    }
    const char* server_addr = argv[1];

    struct addrinfo hints, *servinfo, *p;
    int rv, sockfd;

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    // 解析服务器地址和端口
    if ((rv = getaddrinfo(server_addr, PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo错误: %s\n", gai_strerror(rv));
        exit(1);
    }

    // 遍历链表尝试连接服务器
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
            perror("客户端: 创建Socket失败");
            continue;
        }

        if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("客户端: 连接服务器失败");
            continue;
        }

        break;
    }

    if (p == NULL) {
        fprintf(stderr, "客户端: 无法连接到服务器\n");
        exit(1);
    }

    freeaddrinfo(servinfo);

    // 后续发送接收消息的逻辑和原来一致
    // ...
}

额外提示

  • 如果你的公网IP是动态的(家用宽带常见),可以用DDNS服务绑定一个固定域名,客户端直接用这个域名即可,无需手动更新IP
  • getaddrinfo返回的链表必须用freeaddrinfo释放,否则会造成内存泄漏
  • 测试时可以先用公网IP直接连接,确认网络连通后再切换到域名测试DNS解析

内容的提问来源于stack exchange,提问作者fatgladiator1712

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 15:42:48