一、共享内存(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
未完待续
有疑问的小伙伴可以留言一起交流讨论!!!