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

C语言中通过SHM传输后JPG文件损坏问题排查求助

多线程代理缓存系统JPG文件损坏问题排查

问题背景

开发多线程架构的代理与缓存应用:客户端(黑盒)将请求发送至代理,代理通过消息队列向缓存查询文件,数据传输采用POSIX共享内存通道。缓存未找到文件时向共享内存写入-1;找到则发送数据块大小|数据格式的拼接内容,代理据此向客户端返回响应及数据。

问题现象

传输的数据量、文件大小、收发数据块均完全匹配,打印的内容看似一致,但客户端写入的JPG文件打开时显示损坏,排查多日未找到原因。

代理处理函数代码

ssize_t proxy_worker_handler(custom_struct *ctx, const char *path, void *arg)
{
    int segment_to_lock_into = *(int *)arg % GLOB_NSEGMENTS;

    size_t filesize = 0;

    mq_send(COMMAND_CHANNEL_ID, path, BUFSIZE, 1);

    data_channel current_channel = GLOB_DATA_CHANNEL_SEGMENTS[segment_to_lock_into];

    // 代理获取该共享内存段的使用权
    sem_wait(current_channel.proxy_sem);

    // 等待写入信号,准备读取数据
    sem_wait(current_channel.reader_sem);

    // 判断文件是否存在并返回对应头部
    if (strncmp(current_channel.data, "-1", strlen("-1")) == 0)
    {
        gfs_sendheader(ctx, GF_FILE_NOT_FOUND, 0);
        sem_post(current_channel.writer_sem);
    }
    else
    {
        // 获取文件大小
        filesize = (size_t)atoi(current_channel.data);
        gfs_sendheader(ctx, GF_OK, filesize);
        sem_post(current_channel.writer_sem);

        // 接收数据块
        int curr_received = 0;
        int delimiter, data_len;
        int padding_size = 10;
        char *data_str;
        char data_len_str[padding_size];
        while (curr_received < (int)filesize)
        {
            sem_wait(current_channel.reader_sem);

            data_str = "";
            memset(data_len_str, 0, padding_size);

            // 分割共享内存中的数据,提取大小和内容
            data_str = strstr(current_channel.data, "|") + 1;
            delimiter = (data_str - 1) - current_channel.data;
            for (int i = 0; i < delimiter; i++)
                data_len_str[i] = current_channel.data[i];
            data_len = atoi(data_len_str);

            printf("%s", data_str);

            // 发送数据至客户端
            curr_received += data_len;
            gfs_send(ctx, data_str, (size_t)data_len);

            sem_post(current_channel.writer_sem);
        }
        printf("%d of %zu\n", curr_received, filesize);
    }

    // 释放共享内存段供其他代理工作线程使用
    sem_post(current_channel.proxy_sem);

    return filesize;
}

缓存工作线程代码

void *cache_worker(void *arg)
{
    char *path;
    int fd;
    struct stat st;
    char filesize[21];
    size_t batch_read, start_point;
    int segment_to_lock_into = *(int *)arg % GLOB_NSEGMENTS;

    free(arg);

    data_channel current_channel = GLOB_DATA_CHANNEL_SEGMENTS[segment_to_lock_into];

    while (1)
    {

        // 从队列中获取请求路径
        pthread_mutex_lock(&queue_lock);

        while (steque_isempty(&queue) == 1)
            pthread_cond_wait(&queue_cond, &queue_lock);
        path = steque_pop(&queue);

        pthread_mutex_unlock(&queue_lock);

        fd = simplecache_get(path);
        sleep(cache_delay);

        free(path);

        // 缓存获取该共享内存段的使用权
        sem_wait(current_channel.cache_sem);

        // 判断文件是否存在
        if (fd == CACHE_FAILURE)
        {
            sem_wait(current_channel.writer_sem);

            // 写入-1并通知代理读取
            memset(current_channel.data, 0, GLOB_SEGSIZE);
            memcpy(current_channel.data, "-1", strlen("-1"));
            sem_post(current_channel.reader_sem);
            close(fd);
        }
        else
        {
            // 先向代理发送文件大小
            sem_wait(current_channel.writer_sem);

            fstat(fd, &st);
            snprintf(filesize, 21, "%ld", st.st_size);

            memset(current_channel.data, 0, GLOB_SEGSIZE);
            memcpy(current_channel.data, filesize, 21);

            sem_post(current_channel.reader_sem);

            // 读取文件并发送数据块
            int padding_size = 10;
            char data[GLOB_SEGSIZE];
            char to_send[GLOB_SEGSIZE];
            batch_read = 0, start_point = 0;
            while ((int)(batch_read = pread(fd, data, (size_t)(GLOB_SEGSIZE - padding_size), start_point)) > 0)
            {
                sem_wait(current_channel.writer_sem);

                memset(to_send, 0, GLOB_SEGSIZE);
                memset(current_channel.data, 0, GLOB_SEGSIZE);

                snprintf(to_send, GLOB_SEGSIZE, "%zu|%s", batch_read, data);

                memcpy(current_channel.data, to_send, (size_t)GLOB_SEGSIZE);

                start_point += batch_read;

                printf("%s", data);

                sem_post(current_channel.reader_sem);
            }
            printf("%zu of %s\n", start_point, filesize);
        }

        // 释放共享内存段供其他缓存工作线程使用
        sem_post(current_channel.cache_sem);
    }
    return NULL;
}

问题分析与修复方案

核心问题:二进制数据被当作字符串处理导致损坏

JPG是二进制文件,内容中可能包含\0(空字符),但代码中使用的snprintf、strstr等字符串操作函数会将\0视为字符串结束符,引发数据截断或解析错误:

  1. 缓存端:snprintf用%s处理二进制数据时,会在第一个\0处停止读取,导致写入共享内存的内容不全,后续填充的是初始化的0而非原始文件数据。
  2. 代理端:strstr查找分隔符时,若数据块中包含|字符会导致分隔位置判断错误;同时data_str被当作字符串处理,遇到\0就停止,发送给客户端的数据不完整。

修复方案

1. 缓存端:改用二进制安全的数据格式

放弃字符串拼接,用固定长度的头部存储数据块大小,直接写入二进制数据:

// 替换原snprintf和memcpy代码块
// 写入数据块大小(用size_t类型的字节长度作为头部)
size_t *size_ptr = (size_t *)current_channel.data;
*size_ptr = batch_read;
// 写入二进制数据,从头部之后的位置开始
memcpy(current_channel.data + sizeof(size_t), data, batch_read);

2. 代理端:对应修改数据解析逻辑

直接读取固定长度的头部获取数据块大小,再读取对应长度的二进制数据:

// 替换原strstr和数据解析代码块
size_t *data_len_ptr = (size_t *)current_channel.data;
data_len = *data_len_ptr;
char *data_str = current_channel.data + sizeof(size_t);

// 直接发送二进制数据,无需字符串处理
curr_received += data_len;
gfs_send(ctx, data_str, (size_t)data_len);

3. 额外注意事项

  • 确保共享内存大小足够容纳头部(sizeof(size_t))加最大数据块长度。
  • 调试时不要用printf("%s", data)打印二进制数据,改用十六进制格式:for (int i=0; i<data_len; i++) printf("%02x ", data[i]);。
  • 确认gfs_send函数能正确处理二进制数据,不会因\0截断内容。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 11:35:17