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

C语言通过TCP套接字发送结构体触发段错误问题排查

问题根因

这个段错误和结构体对齐、内存填充没有任何关系,核心问题是直接传输带指针的结构体时,既没有传输指针指向的实际内容,跨进程的指针值本身也是无效的。

  • 你的toto_t结构体里str是char*类型,本质是一个内存地址,不是字符串本身。服务端调用strdup("Helloworld")时,是在自身进程的独立虚拟地址空间里申请了一块内存存储字符串,把这块内存的地址存在了str指针成员里。
  • 你调用send(new_socket, &toto, 14, 0)时,发出去的内容只有两部分:4字节的int值a,加上8字节(64位系统下指针占8字节)的地址值,加起来仅12字节,你写的14字节发送长度甚至还越界读取了结构体后面的非法内存,从头到尾没有把"Helloworld"这个字符串的实际内容发送出去。
  • 客户端接收数据后,toto.a是直接存在结构体里的整数值,所以能正常读出10;但toto.str里存的是服务端进程的虚拟内存地址,这个地址在客户端进程的地址空间里要么没有做内存映射,要么存储的是完全无关的数据,printf尝试访问这个地址读取字符串,自然触发段错误。
  • 你添加的#pragma pack、__attribute__((packed))修饰只能消除结构体内的对齐填充,对指针指向的堆内存中存储的字符串内容没有任何作用,无法解决这个问题。
正确实现方案

网络传输结构体时,绝对不能直接传输持有指针(指向堆/栈其他内存位置)的结构体原始内存,需要做序列化:把结构体里的所有实际内容按约定的字节序、格式拼成连续的字节流发送,接收端收到字节流后再反序列化解析回本地结构体。
针对当前场景,最简单的传输逻辑是:

  1. 传输时先发送字符串长度(即原结构体的a字段),再发送字符串的实际内容,不需要传输指针
  2. 多字节整数要转成网络字节序,避免不同设备大小端差异导致解析错误
  3. 收发逻辑要做循环处理,因为send/read不保证一次调用就能把指定长度的数据全部发送/接收完成

修正后的服务端代码:

#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define PORT 8080

// 循环发送指定长度数据的工具函数
ssize_t send_all(int sockfd, const void *buf, size_t len) {
    size_t total_sent = 0;
    const char *p = (const char *)buf;
    while (total_sent < len) {
        ssize_t sent = send(sockfd, p + total_sent, len - total_sent, 0);
        if (sent <= 0) return sent;
        total_sent += sent;
    }
    return total_sent;
}

int main(int argc, char const* argv[])
{
    int server_fd, new_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    const char *str = "Helloworld";
    int str_len = strlen(str);

    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
 
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);
 
    if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    if ((new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) {
        perror("accept");
        exit(EXIT_FAILURE);
    }

    // 先发送网络字节序的字符串长度
    int net_len = htonl(str_len);
    send_all(new_socket, &net_len, sizeof(net_len));
    // 再发送字符串实际内容
    send_all(new_socket, str, str_len);

    close(new_socket);
    shutdown(server_fd, SHUT_RDWR);
    return 0;
}

修正后的客户端代码:

#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define PORT 8080

// 循环接收指定长度数据的工具函数
ssize_t recv_all(int sockfd, void *buf, size_t len) {
    size_t total_recv = 0;
    char *p = (char *)buf;
    while (total_recv < len) {
        ssize_t r = recv(sockfd, p + total_recv, len - total_recv, 0);
        if (r <= 0) return r;
        total_recv += r;
    }
    return total_recv;
}

int main(int argc, char const* argv[])
{
    int sock, client_fd;
    struct sockaddr_in serv_addr;
    int str_len, net_len;
    char *str;

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("\n Socket creation error \n");
        return -1;
    }
 
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
 
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        printf("\nInvalid address/ Address not supported \n");
        return -1;
    }
 
    if ((client_fd = connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))) < 0) {
        printf("\nConnection Failed \n");
        return -1;
    }

    // 先接收长度,转成本地字节序
    recv_all(sock, &net_len, sizeof(net_len));
    str_len = ntohl(net_len);
    // 申请内存存储字符串,多1字节放字符串结束符'\0'
    str = (char*)malloc(str_len + 1);
    // 接收字符串内容
    recv_all(sock, str, str_len);
    str[str_len] = '\0';

    printf("%d\n%s\n", str_len, str);
    free(str);
 
    close(client_fd);
    return 0;
}

额外注意事项:

  • 不同进程的虚拟地址空间是完全独立的,A进程里的地址值放到B进程里没有任何意义,不要尝试跨网络/跨进程传输指针。
  • 如果结构体里有更复杂的嵌套指针、可变长度数组,都需要按实际内容逐字段序列化,不能直接拷贝结构体原始内存发送。
  • 跨机器传输时除了字节序问题,还要注意两端基本类型的长度差异(比如long类型在32位和64位系统长度不同),简单场景可以自己按约定格式组包,复杂场景可以使用成熟的序列化方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 23:27:29