使用共享内存实现图片的上传和下载

一、共享内存(SHM)

1、基本概念

        共享内存是效率最高的 IPC,因为他抛弃了内核这个“代理人”,直截了当地将一块裸露的内存放在需要数据传输的进程面前,让他们自己搞,这样的代价是:这些进程必须小心谨慎地操作这块裸露的共享内存,做好诸如同步、互斥等工作,毕竟现在没有人帮他们来管理了,一切都要自己动手。也因为这个原因,共享内存一般不能单独使用,而要配合信号量、互斥锁等协调机制,让各个进程在高效交换数据的同时,不会发生数据践踏、破坏等意外。

        共享内存的思想很朴素,进程与进程之间虚拟内存空间本来相互独立,不能互相访问的,但是可以通过某些方式,使得相同的一块物理内存多次映射到不同的进程虚拟空间之中,这样的效果就相当于多个进程的虚拟内存空间部分重叠在一起,看示意图:

        像上图所示,当进程 P1 向其虚拟内存中的区域 1 写入数据时,进程 2 就能同时在其虚拟内存空间的区域 2 看见这些数据,中间没有经过任何的转发,效率极高。
使用共享内存的一般步骤是:
        1,获取共享内存对象的 ID
        2,将共享内存映射至本进程虚拟内存空间的某个区域
        3,当不再使用时,解除映射关系
        4,当没有进程再需要这块共享内存时,删除它。

2、常用API

二、实现代码

图片的上传:upload.c

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

#define IPC_PATH    "./"   // 指定文件或者路径
#define IPC_MASK    1      // IPC标识符
#define PAGE_SIZE   4096   // 页内存大小

#define READ_ONLY_SIZE  100 // 每一次读取的字节大小

int main(int argc, char const *argv[])
{
    
    // 获取当前未用的一个ipc的key值
    key_t key = ftok(IPC_PATH, IPC_MASK);
    if( key == -1)
    {
        perror("ftok()");
        exit(-1);
    }
    else
    {
        printf("key值为: %x\n", key);
    }

    // 获取共享内存的id
    int shm_id = shmget(key, PAGE_SIZE*247, IPC_CREAT | 0666) ;
    if ( shm_id == -1)
    {
        perror("shmget()");
        exit(-1);

    }


    // 对内存地址进行映射
    char * shm_p = (char * )shmat(shm_id, NULL, 0);
    if( shm_p == (char *)-1)
    {
        perror("shmat()");
        exit(-1);
    }

    // 上传图片
    int file_size;
    struct stat statbuf;
    memset(&statbuf, 0, sizeof(statbuf));
    stat("./1.gif", &statbuf);   // stat函数用于获取文件的相关信息,包括文件大小、修改时间等
    file_size = statbuf.st_size;

    memcpy(shm_p, &file_size, sizeof(int));

    // 打开文件
    FILE * fp = fopen("./1.gif", "r");
    if( fp == NULL)
    {
        perror("fopen()");
        exit(-1);
    }


    int upload_size;  // 上传的文件大小
    char * new_shm_p = shm_p;
    char file_data[READ_ONLY_SIZE];
    for( int upload_num = 0; upload_num < file_size/READ_ONLY_SIZE; upload_num++)
    {
        memset(file_data, 0, READ_ONLY_SIZE);

        //读文件的数据
        int fread_ret = fread(file_data, READ_ONLY_SIZE, 1, fp);
        if( fread_ret < 1)
        {
            if( ferror(fp) )
            {
                perror("fread()");
                exit(-1);
            }

        }


        memcpy( new_shm_p, file_data, READ_ONLY_SIZE);
        new_shm_p += READ_ONLY_SIZE;

        upload_size += READ_ONLY_SIZE;
    }


    //如果有最后一次有剩余的数据,把放进共享内存里面
    int skip_data = file_size % READ_ONLY_SIZE ;   // 获取剩余的字节数
       
    if( skip_data != 0)
    {
        memset(file_data, 0 , READ_ONLY_SIZE);

        // 读文件的数据
        if(fread(file_data, skip_data, 1, fp) < 1)
        {
            if( ferror(fp) )
            {
                perror("fread()");
                exit(-1);
            }
        }

        memcpy(new_shm_p, file_data, skip_data);

        upload_size += skip_data;
    }

    printf("图片上传成功\n");
    printf("上传的文件大小为 %d\n", upload_size);



    if(fclose(fp) == -1)
    {
        perror("fclose()");
        exit(-1);
    }



    return 0;
}

图片的下载:download.c

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

#define IPC_PATH    "./"   // 指定文件或者路径
#define IPC_MASK    1      // IPC标识符
#define PAGE_SIZE   4096   // 页内存大小

#define WRITE_ONLY_SIZE  20 // 每一次读取的字节大小

int main(int argc, char const *argv[])
{
    

    key_t key = ftok(IPC_PATH,IPC_MASK);
    if(key == -1)
    {
        perror("ftok ... ");
        return -1;
    }

    int shm_id = shmget(key,PAGE_SIZE*247,IPC_CREAT | 0666);
    if(shm_id == -1)
    {
        perror("shmget ... ");
        return -1;
    }

    char * shm_p = (char *)shmat(shm_id,NULL,0);
    if(shm_p == (char *)-1)
    {
        perror("shmat ... ");
        return -1;
    }

    char * new_shm_p = shm_p;
    FILE * fp = fopen("./img/new.gif","w");
    if(fp == NULL)
    {
        perror("fopen ... ");
        return -1;
    }

    //先从共享内存前4个字节中获取要下载的文件的字节大小
    int download_file_size;

    memcpy(&download_file_size,new_shm_p,sizeof(int));
    new_shm_p+= sizeof(int);
    printf("即将下载文件大小:%d\n",download_file_size);


    char file_data[WRITE_ONLY_SIZE ]; 
    int download_size = 0 ;     // 下载的文件大小
    
    for(int download_num = 0; download_num < download_file_size/WRITE_ONLY_SIZE ; download_num++)
    {
        memset(file_data, 0, WRITE_ONLY_SIZE );

        // 从共享文件中获取数据
        memcpy(file_data, new_shm_p, WRITE_ONLY_SIZE );

        // 将文件数据写入新的文件中
        int write_ret = fwrite(file_data, WRITE_ONLY_SIZE , 1, fp );
        if( write_ret < 1)
        {
            if( ferror(fp) )
            {
                perror("write()");
                exit(-1);
            }
        }

        new_shm_p += WRITE_ONLY_SIZE ;

        download_size += WRITE_ONLY_SIZE  ;
    }


    //如果有最后一次有剩余的数据,把放进共享内存里面
    int skip_data = download_file_size % WRITE_ONLY_SIZE  ;   // 获取剩余的字节数
    if( skip_data != 0 )
    {
        memset( file_data, 0, WRITE_ONLY_SIZE  );


        // 从共享文件中获取数据
        memcpy(file_data, new_shm_p, WRITE_ONLY_SIZE );

        // 将文件数据写入新的文件中
        if( fwrite(file_data , skip_data, 1, fp ) < 1)
        {
            if( ferror(fp) )
            {
                perror("fwrite()");
                exit(-1);
            }
        }

        download_size += skip_data ;
    }


    printf("下载文件完成, 下载的文件大小为:%d \n", download_size);


    if(fclose(fp) == -1)
    { 
        perror("fclose()");
        exit(-1);
    }

    // 删除共享内存
    if(shmctl(shm_id, IPC_RMID, NULL) == -1)
    {
        perror("shmctl()");
        exit(-1);
    }

    return 0;
}

三、效果展示

查看共享内存的文件大小:

ipcs -m

未完待续


有疑问的小伙伴可以留言一起交流讨论!!!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值