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视为字符串结束符,引发数据截断或解析错误:
- 缓存端:
snprintf用%s处理二进制数据时,会在第一个\0处停止读取,导致写入共享内存的内容不全,后续填充的是初始化的0而非原始文件数据。 - 代理端:
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
相关产品推荐
相关产品推荐

