使用共享内存传输文件

使用共享内存传输文件

功能已经完成了,传小文本没有问题,一旦传输超过自定义的共享内存大小的文件就会出现数据不全的问题。例如传输一张图片,接收到的文件大小比原文件小很多。

代码:

头文件: ipcs_shm.h

#ifndef __IPCS_SHM_H__
#define __IPCS_SHM_H__

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <unistd.h>

#define PATHNAME "." // 路径名
#define PROID 10     // 低8位整数

#define SZ_SHM 1024

#endif

发送文件:send_process.c

#include "ipcs_shm.h"

int sendfile(void *addr, char *argv[])
{
	FILE *fp_read = NULL;
	size_t rbytes;

	fp_read = fopen(argv[1], "r");
	if (fp_read == NULL)
	{
		perror("Error fread fopen() ");
		exit(EXIT_FAILURE);
	}

	while (1)// 循环读取文件数据到共享内存
	{
		rbytes = fread(addr, sizeof(char), SZ_SHM, fp_read);
		if (rbytes > 0)
		{
			while (strlen(addr) != 0)// 如果共享内存数据未被取走,则读操作暂停1秒
			{
				fprintf(stdout, "wirte process busy...\n");
				fflush(NULL);

				sleep(1);
			}
		}
		else
			break;
	}

	fclose(fp_read);

	return 0;
}

int main(int argc, char *argv[])
{
	if (argc != 2)
	{
		fprintf(stderr, "Usage : %s <dest_pathname> .\n", argv[0]);
		return -1;
	}

	key_t key;
	int shmid;
	void *addr = NULL;
	int ret = 0;

	key = ftok(PATHNAME, PROID); // 申请消息队列的msqid
	if (key == -1)
	{
		perror("fotk(): ");
		exit(EXIT_FAILURE);
	}

	shmid = shmget(key, SZ_SHM, IPC_CREAT | 0666); // 创建共享内存id
	if (shmid == -1)
	{
		perror("shmid(): ");
		exit(EXIT_FAILURE);
	}

	printf("shmid = %d\n", shmid);

	addr = shmat(shmid, NULL, 0); // 映射共享内存
	if (addr == (void *)-1)
	{
		perror("[ERROR] shmat(): ");
		exit(EXIT_FAILURE);
	}

	sendfile(addr, argv);

	shmdt(addr); // 解除映射

	fprintf(stdout, "read file over.\n");
	fflush(NULL);

	return 0;
}

接收文件:rcv_process.c

#include "ipcs_shm.h"

int waitcount = 0;

void rsvfile(void *addr, char *argv[])
{
	FILE *fp_write = NULL;
	size_t wbytes;

	fp_write = fopen(argv[1], "w");
	if (NULL == fp_write)
	{
		perror("Error write fopen() ");
		exit(EXIT_FAILURE);
	}

	// 循环写操作
	while (1)
	{
		// 将共享内存的数据写入到文件 fp_write中
		wbytes = fwrite(addr, sizeof(char), strlen(addr), fp_write);
		if (0 > wbytes)
		{
			perror("[ERROR] fwrite() ");
			exit(EXIT_FAILURE);
		}

		// 连续3次没有读取到数据则结束 写操作
		while (0 == wbytes)
		{
			if (3 == ++waitcount)
			{
				fprintf(stdout, "wirte process close.\n");
				fflush(NULL);

				fclose(fp_write);
				return ;
			}

			fprintf(stdout, "wirte waiting...\n");
			fflush(NULL);

			sleep(1);

			wbytes = fwrite(addr, sizeof(char), strlen(addr), fp_write);
		}

		// 每次写操作完成后,清空共享内存
		memset(addr, 0, SZ_SHM);

		waitcount = 0;
	}

}

int main(int argc, char *argv[])
{
	if (2 != argc)
	{
		fprintf(stderr, "Usage : %s <dest_pathname> .\n", argv[0]);
		return -1;
	}

	key_t key;
	int shmid;
	void *addr = NULL;
	int ret = 0;

	key = ftok(PATHNAME, PROID); // 申请消息队列的msqid
	if (key == -1)
	{
		perror("fotk(): ");
		exit(EXIT_FAILURE);
	}

	shmid = shmget(key, SZ_SHM, IPC_CREAT | 0666); // 创建共享内存id
	if (shmid == -1)
	{
		perror("shmid(): ");
		exit(EXIT_FAILURE);
	}

	printf("shmid = %d\n", shmid);

	addr = shmat(shmid, NULL, 0); // 映射共享内存
	if ((void *)-1 == addr)
	{
		perror("[ERROR] shmat(): ");
		exit(EXIT_FAILURE);
	}

	rsvfile(addr, argv); // 文件接收

	shmdt(addr); // 解除映射

	ret = shmctl(shmid, IPC_RMID, NULL); // 销毁共享内存
	if (ret == -1)
	{
		perror("[ERROR] shmctl(): ");
		exit(EXIT_FAILURE);
	}

	return 0;
}


正在回答

登陆购买课程后可参与讨论,去登陆

1回答
  1. 主要问题在于strlen 函数计算共享内存中二进制数据的长度是不准确的,具体解决方案如下:

       (a) . 在申请共享内存空间时,需要多申请4个字节用于共享每次写入共享内存的数据长度

       (b). 在从共享内存中读取数据时,需要先从共享内存前4个字节读取写入的长度,在根据实际写入到共享内存的长度来读取数据

  下面是经过修改的代码,目前测试了图片大小相同,具体内容还需要你进一步测试一下

#include "ipcs_shm.h"
 
int waitcount = 0;
 
void rsvfile(void *addr, char *argv[])
{
    FILE *fp_write = NULL;
    size_t wbytes,length = 0;
 
    fp_write = fopen(argv[1], "w");
    if (NULL == fp_write)
    {
        perror("Error write fopen() ");
        exit(EXIT_FAILURE);
    }
 
    // 循环写操作
    while (1)
    {
		memcpy(&length,addr,4);// 从共享内存中获取长度
        // 将共享内存的数据写入到文件 fp_write中
        wbytes = fwrite(addr + 4, sizeof(char),length, fp_write);//根据写入的长度将数据写入到共享内存中
        if (0 > wbytes)
        {
            perror("[ERROR] fwrite() ");
            exit(EXIT_FAILURE);
        }
		
		memset(addr,'\0',4);//读取完成后将数据清零
        // 连续3次没有读取到数据则结束 写操作
        while (0 == wbytes)
        {
            if (3 == ++waitcount)
            {
                fprintf(stdout, "wirte process close.\n");
                fflush(NULL);
 
                fclose(fp_write);
                return ;
            }
 
            fprintf(stdout, "wirte waiting...\n");
            fflush(NULL);
 
            sleep(1);
 
			memcpy(&length,addr,4);//获取长度
			
           	wbytes = fwrite(addr, sizeof(char), length, fp_write);
			memset(addr,'\0',4);//将长度清零
        }
 
        // 每次写操作完成后,清空共享内存
        memset(addr, 0, SZ_SHM + 4);
 
        waitcount = 0;
    }
 
}
 
int main(int argc, char *argv[])
{
    if (2 != argc)
    {
        fprintf(stderr, "Usage : %s <dest_pathname> .\n", argv[0]);
        return -1;
    }
 
    key_t key;
    int shmid;
    void *addr = NULL;
    int ret = 0;
 
    key = ftok(PATHNAME, PROID); 
    if (key == -1)
    {
        perror("fotk(): ");
        exit(EXIT_FAILURE);
    }
 
    shmid = shmget(key, SZ_SHM + 4, IPC_CREAT | 0666); // 创建共享内存id
    if (shmid == -1)
    {
        perror("shmid(): ");
        exit(EXIT_FAILURE);
    }
 
    printf("shmid = %d\n", shmid);
 
    addr = shmat(shmid, NULL, 0); // 映射共享内存
    if ((void *)-1 == addr)
    {
        perror("[ERROR] shmat(): ");
        exit(EXIT_FAILURE);
    }
 
    rsvfile(addr, argv); // 文件接收
 
    shmdt(addr); // 解除映射
 
    ret = shmctl(shmid, IPC_RMID, NULL); // 销毁共享内存
    if (ret == -1)
    {
        perror("[ERROR] shmctl(): ");
        exit(EXIT_FAILURE);
    }
 
    return 0;
}
#include "ipcs_shm.h"


int sendfile(void *addr, char *argv[])
{
    FILE *fp_read = NULL;
    size_t rbytes;
 
    fp_read = fopen(argv[1], "r");
    if (fp_read == NULL)
    {
        perror("Error fread fopen() ");
        exit(EXIT_FAILURE);
    }
 
    while (1)// 循环读取文件数据到共享内存
    {
		
        rbytes = fread(addr + 4, sizeof(char), SZ_SHM, fp_read);//将文件的数据写入到共享内存中
        if (rbytes > 0)
        {
			memcpy(addr,&rbytes,4);//将长度写入到共享内存的前4个字节
			memcpy(&rbytes,addr,4);//重新读取长度
			while(rbytes != 0)	
            //while (strlen(addr) != 0)// 如果共享内存数据未被取走,则读操作暂停1秒
            {
				memcpy(&rbytes,addr,4);//每次重新读取长度

                fprintf(stdout, "wirte process busy...\n");
                fflush(NULL);
 
                sleep(1);
            }
        }
        else
            break;
    }
 
    fclose(fp_read);
 
    return 0;
}
 
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        fprintf(stderr, "Usage : %s <dest_pathname> .\n", argv[0]);
        return -1;
    }
 
    key_t key;
    int shmid;
    void *addr = NULL;
    int ret = 0;
 
    key = ftok(PATHNAME, PROID); // 申请消息队列的msqid
    if (key == -1)
    {
        perror("fotk(): ");
        exit(EXIT_FAILURE);
    }
 
    shmid = shmget(key, SZ_SHM + 4, IPC_CREAT | 0666); // 创建共享内存id
    if (shmid == -1)
    {
        perror("shmid(): ");
        exit(EXIT_FAILURE);
    }
 
    printf("shmid = %d\n", shmid);
 
    addr = shmat(shmid, NULL, 0); // 映射共享内存
    if (addr == (void *)-1)
    {
        perror("[ERROR] shmat(): ");
        exit(EXIT_FAILURE);
    }
 
    sendfile(addr, argv);
 
    shmdt(addr); // 解除映射
 
    fprintf(stdout, "read file over.\n");
    fflush(NULL);
 
}


下面是我写的参考答案

include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/sem.h>
#include <signal.h>

#define PATHNAME "."

struct file_mem{
    
    int magic_no;/*数据块的编号*/
    int size;/*数据块的大小*/
    char buffer[1024];/*数据块缓冲区*/
};

/*共享内存创建标志*/
int g_shm_created = 1;

/*
 *获取共享内存key
 * 
 * */
key_t getkey(void)
{
    return ftok(PATHNAME,1);
}

/*
 *创建共享内存
 * 
 *返回struct file_mem 指针
 * */
struct file_mem *open_shm(void)
{
    key_t key;
    int shm;

    key = getkey();
    shm = shmget(key,sizeof(struct file_mem),0666);
    if (shm < 0){
        shm = shmget(key,sizeof(struct file_mem),0666|IPC_CREAT);   
        if (shm < 0){
            return NULL;
        }
        /*创建标志设置为共享内存的id*/
        g_shm_created = shm;
    }
    /*进程映射到共享内存中*/
    return (struct file_mem *)shmat(shm,NULL,0);
}

/*
 *解除映射,并删除共享内存 
 * */
void close_shm(struct file_mem *p)
{
    shmdt(p);
    if (g_shm_created)
        shmctl(g_shm_created,IPC_RMID,NULL);
        
}

/*等待文件传输
 *
 * */
void wait_for_file(void)
{
    struct file_mem *p = open_shm();    
    if (p != NULL){
        int times = 0;
        /*last_no 记录最后一次数据块的编号*/
        int last_no = p->magic_no;

        /*当last_no 等于数据块编号时,则表示没有开始传输*/
        while(last_no == p->magic_no)
        {
            usleep(100000);
        }
        printf("[Server is online!]\n");

        /*当last_no 与p->magic_no编号不一致时,赋值给last_no*/
        last_no = p->magic_no;
        while(1){

            /*当last_no与数据块编号不相等时,更新last_no*/
            if (last_no != p->magic_no){
                times = 0;
                last_no = p->magic_no;
            }else{
                times++;    
                if (times > 1000){
                    printf("[Server is down.]\n");
                    break;
                }
            }
            
            /*当size >0 则表示有数据*/
            if (p->size > 0){
                p->buffer[p->size] = 0;/*将数据块尾添加'\0'*/
                printf(p->buffer);/*打印到stdout*/
                fflush(stdout);
                p->size = 0;/*将数据块的大小置为0,等待下一个数据块*/
            
            }else{
                usleep(1000);/*没有数据则睡眠*/
            }
        }

        close_shm(p);
    }
}

int main(int argc,char *argv[])
{
    struct file_mem *p;
    FILE *fp;
    size_t reads;

    if (argc < 2){
        wait_for_file();
        exit(0);
    }
    if ((fp = fopen(argv[1],"r+")) == NULL){
        perror("fopen failed.\n");
        exit(0);
    }
    p = open_shm();/*创建共享内存并映射到共享内存上*/
    if (p != NULL){
        p->size = 0;/*将size设置为0*/
        while(!feof(fp)){
            p->magic_no++;/*数据块编号加1*/

            /*将size = 0时,在将数据写入到共享内存中*/
            if (p->size == 0){
                /*数据缓冲区添加'\0'*/
                p->buffer[sizeof(p->buffer) -1] = 0;
                /*从文件中读取数据到buffer中,并留一个位置给'\0'*/
                reads = fread(p->buffer,1,sizeof(p->buffer) - 1,fp);
                if(reads > 0){
                    p->size = reads;/*读取成功,则更新size的值*/
                }
            
            }
        }

        printf("send over.\n");
        close_shm(p);
    }
    fclose(fp);
}


  • 慕小白0101 提问者 #1

    无名老师,您的代码中,函数close_shm()的 if (g_shm_created)这个判断条件是无法用来判断g_shm_created这个shmid的共享内存有没有被释放销毁的。两个进程都执行函数close_shm(),其中接收数据的进程会返回一个errno。

    https://img1.sycdn.imooc.com//climg/6469e86a0996cba611930422.jpg


    2023-05-21 17:46:24
  • 慕小白0101 提问者 #2

    我搞错了。。。无名老师代码是正确的

    2023-05-21 17:48:03
  • 慕小白0101 提问者 #3

    无名老师,请问你的代码为什么能让发送端的进程的g_shm_created这个全局变量的值变为1啊?

    2023-05-21 18:15:26
问题已解决,确定采纳
还有疑问,暂不采纳

恭喜解决一个难题,获得1积分~

来为老师/同学的回答评分吧

0 星
请稍等 ...
意见反馈 帮助中心 APP下载
官方微信

在线咨询

领取优惠

免费试听

领取大纲

扫描二维码,添加
你的专属老师