linux c++(进程间的通讯方式)
IPC通信的方式有几种
- pipe 管道--最简单
- fifo 有名管道
- mmap 文件映射共享IO --速度最快
- 本地socket 最稳定
- 信号 携带信息量最小
- 共享内存
- 消息队列
- 共享内存 + 信号量 + 消息队列
常见的通信方式:单工,半双工,全双工
管道:半双工通信
优点: 简单
缺点:
只能单向通信,双向通信需要建立两个管道,
只能用于父与子,兄弟间通信,该问题后来使用fifo有名管道解决
管道函数:
int pipe(int pipefd[2]);
pipefd 读写文件描述符, 0 代表读。1 代表写
返回值: 失败返回 1 成功返回 0
使用前提前规划好,那个要读,那个要写,关闭不用的一端
读管道:
写端全部关闭 --read读到0 ,相当于读到文件末尾
写端没有全部关闭
有数据---read 读到数据
没有数据---read 阻塞 fcntl函数可以更改非阻塞
写管道:
读端全部关闭--- ?产生一个信号SIGPIPE,程序一场终止
读端未全部关闭
管道已满 ---write阻塞
管道未满 ---write正常写入
int main()
{
int fd[2];
pipe(fd);
pid_t pid = fork();
if(pid == 0)
{
//son
close(fd[0]);
write(fd[1],"hello",5);
}else if(pid > 0)
{
char buf[12]={0};
close(fd[1]);
read(fd[0],buf,sizeof(buf));
printf("pipe = %s\n",buf);
}
return 0;
}
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
pid_t pid = 0;
if((pid=fork()) < 0)
{
perror("fork");
}else if(pid == 0)
{
// 子进程
mkfifo("my_fifo",0666);
int fd = open("my_fifo",O_RDONLY);
while(1)
{
sleep(1);
char recv[100] = {0};
read(fd,recv,sizeof(recv));
printf("read form my_fifo buf=%s\n",recv);
}
}else{
mkfifo("my_fifo",0666);
int fd = open("my_fifo",O_WRONLY);
while(1)
{
char send[100] = "Hello MiKe";
write(fd,send,strlen(send));
sleep(2);
}
// 父进程
}
return 0;
}
mmap内存映射简单介绍

/dev/zero 可以随意映射
addr: 传NULL
length: 映射区的长度
prot:
PROT_READ 可读
PROT_WRITE 可写
flags:
MAP_SHARED 共享的,对内存的修改会影响原文件 MAP_SHARED|MAP_ANONYMOUS(或者MAP_ANON)匿名映射这两个宏有些unix系统没有
MAP_PRIVATE 私有的
fd: 文件描述符,open打开一个文件,`文件必须有大小`
offset: 偏移量0或着4K的整数倍
返回值:
成功 返回可用的内vu你首地址
失败 返回MAP_FAILED
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
释放映射区
addr: 传mmap的返回值
leng: mmap创建的长度
返回值:
成功 0
失败 -1
int munmap(void *addr, size_t length);
int main()
{
int *mem = (int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
if(mem == MAP_FAILED)
{
perror("man err");
return -1;
}
pid_t pid = 0;
pid=fork();
if(pid == 0)
{
//son
*mem = 101;
printf("child,*mem = %d\n",*mem);
sleep(3);
printf("child,*mem = %d\n",*mem);
}else if(pid > 0)
{
sleep(1);
*mem=199;
printf("father,*mem = %d\n",*mem);
*mem = 1000;
printf("father,*mem = %d\n",*mem);
wait(NULL);
}
munmap(mem,4);
return 0;
}
+ 1.如果更改mem变量的地址,释放的时候munmap,传入mem还能成功吗?
- 不能
+ 2.如果对mem越界操作会怎么样?
- 文件的大小对应社区操作有影响,尽量避免
+ 3.如果文件偏移量随便填个数会怎么样?
- offset必须是4K的整数倍
+ 4.如果文件描述符先关闭,对mmap映射有没有影响?
- 没有影响
+ 5.open的时候,可以新创建一个文件来创建映射区吗?
- 不可以用大小为0的文件,给文件设置大小ftruncate(fd,offset);
+ 6.open文件选择O_WRONLY,可以吗?
- 不可以,没有权限
+ 7.当选择MAP_SHARED的时候,open文件选择O_RDONLY,prot可以选择PROT_READ|PROT_WRITE吗?
- 不可以,没有权限,SHARED的时候映射区的权限 <= open文件的权限
+ 8.mmap什么情况下会报错?
- 以上情况都会报错
成功执行时,mmap()返回被映射区的指针。失败时,mmap()返回MAP_FAILED[其值为(void *)-1],
error被设为以下的某个值:
1 EACCES:访问出错
2 EAGAIN:文件已被锁定,或者太多的内存已被锁定
3 EBADF:fd不是有效的文件描述词
4 EINVAL:一个或者多个参数无效
5 ENFILE:已达到系统对打开文件的限制
6 ENODEV:指定文件所在的文件系统不支持内存映射
7 ENOMEM:内存不足,或者进程已超出最大内存映射数量
8 EPERM:权能不足,操作不允许
9 ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志
10 SIGSEGV:试着向只读区写入
11 SIGBUS:试着访问不属于进程的内存区
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main()
{
pid_t pid = 0;
if((pid=fork()) < 0)
{
perror("fork");
}else if(pid == 0)
{
// 创建共享内存
int shmid = shmget(100,0,0);
// 和当前进程关联
void* ptr = shmat(shmid,NULL,0);
while(1)
{
printf("child = %s\n",(char*)ptr);
sleep(1);
}
// 解除关联
shmdt(ptr);
// 删除共享内存
shmctl(shmid,IPC_RMID,NULL);
// 子进程
}else{
// 创建共享内存
int shmid = shmget(100,4096,IPC_CREAT|0664);
// 和当前进程关联
void* ptr = shmat(shmid,NULL,0);
while(1)
{
std::string str= "这是一块共享内存";
memcpy(ptr,str.c_str(),str.size());
sleep(2);
}
// 解除关联
shmdt(ptr);
// 删除共享内存
shmctl(shmid,IPC_RMID,NULL);
// 父进程
}
return 0;
}
1 #include <sys/msg.h>
2 // 创建或打开消息队列:成功返回队列ID,失败返回-1
3 int msgget(key_t key, int flag);
4 // 添加消息:成功返回0,失败返回-1
5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);
6 // 读取消息:成功返回消息数据的长度,失败返回-1
7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
8 // 控制消息队列:成功返回0,失败返回-1
9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);
在以下两种情况下,msgget将创建一个新的消息队列:
1.如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志位。
2.key参数为IPC_PRIVATE。
函数msgrcv在读取消息队列时,type参数有下面几种情况:
type == 0,返回队列中的第一个消息;
type > 0,返回队列中消息类型为 type 的第一个消息;
type < 0,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。
可以看出,type值非 0 时用于以非先进先出次序读消息。也可以把 type 看做优先级的权值
#include <iostream>
#include <unistd.h>
#include <sys/msg.h>
const std::string FILE_MSG = "/dev/null";
// 消息结构
struct msg_form
{
long mtype;
char mtext[256];
};
int main()
{
pid_t pid = 0;
if((pid=fork()) < 0)
{
perror("fork");
}else if(pid == 0)
{
int msqid;
key_t key;
struct msg_form msg;
// 获取key值
if((key = ftok(FILE_MSG.c_str(),'z')) < 0)
{
perror("ftok error");
exit(1);
}
// 打开消息队列
if((msqid = msgget(key,IPC_CREAT|0777)) == -1)
{
perror("msgget error");
exit(1);
}
// 打印消息队列ID及进程ID
printf("MyClient msqid is: %d\n",msqid);
printf("MyClient pid is: %d\n",getpid());
// 添加消息,类型为888
for(;;)
{
msg.mtype = 888;
sprintf(msg.mtext,"hello,I'm client %d",getpid());
msgsnd(msqid,&msg,sizeof(msg.mtext),0);
msgrcv(msqid,&msg,256,999,0);
printf("Client: receive msg.mtype is: %d\n",msg.mtype);
printf("Client: receive msg.mtype is: %s\n",msg.mtext);
sleep(2);
}
// 子进程
}else{
int msqid;
key_t key;
struct msg_form msg;
// 获取key值
if((key = ftok(FILE_MSG.c_str(),'z')) < 0)
{
perror("ftok error");
exit(1);
}
// 创建消息队列
if((msqid = msgget(key,IPC_CREAT|0777)) == -1)
{
perror("msgget error");
exit(1);
}
// 打印消息队列ID及进程ID
printf("MyServer msqid is: %d\n",msqid);
printf("MyServer pid is: %d\n",getpid());
// 循环读取消息
for (;;)
{
msgrcv(msqid,&msg,256,888,0);
printf("Server: receive msg.mtext is: %s\n",msg.mtext);
printf("Server: receive msg.mtype is: %d\n",msg.mtype);
msg.mtype = 999;
sprintf(msg.mtext,"hello,I'm server %d",getpid());
msgsnd(msqid,&msg,sizeof(msg.mtext),0);
}
// 父进程
}
return 0;
}
共享内存
- 共享内存用来传递数据;
- 信号量用来同步;
- 消息队列用来 在客户端修改了共享内存后 通知服务器读取。
server.cpp
#include <iostream>
#include <unistd.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
const std::string __MSG_FILE__ = "/dev/null";
sem_t blank,xfull;
struct msg_form{
long mtype;
char mtext;
};
int main()
{
struct msg_form msg; // 消息队列用于通知对方更新了共享内存
// 获取key值
key_t key;
if((key = ftok(__MSG_FILE__.c_str(),'z')) < 0)
{
perror("ftok error");
exit(1);
}
// 创建共享内存
int shmid;
if((shmid = shmget(key,4096,IPC_CREAT|0664)) == -1)
{
perror("ftok error");
exit(1);
}
// 和当前进程关联
char* shm;
shm = (char*)shmat(shmid,0,0);
if((int)*shm == -1)
{
perror("Attach Shared Memory Error");
exit(1);
}
// 创建消息队列
int msqid;
if((msqid = msgget(key,IPC_CREAT|0777)) == -1)
{
perror("msgget error");
exit(1);
}
// 创建信号量
sem_init(&blank,0,1);
sem_init(&xfull,0,0);
// 读数据
while(1)
{
sem_wait(&blank);
msgrcv(msqid,&msg,1,888,0);
sem_post(&xfull);
if(msg.mtext == 'q')
break;
else if(msg.mtext == 'r')
{
sem_wait(&xfull);
printf("%s\n",shm);
sem_post(&blank);
}
}
// 解除关联
shmdt(shm);
// 删除共享内存,消息队列
shmctl(shmid,IPC_RMID,NULL);
msgctl(msqid,IPC_RMID,NULL);
sem_destroy(&blank);
sem_destroy(&xfull);
return 0;
}
client.cpp
#include <iostream>
#include <unistd.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
const std::string __MSG_FILE__ = "/dev/null";
sem_t blank,xfull;
struct msg_form{
long mtype;
char mtext;
};
int main()
{
struct msg_form msg; // 消息队列用于通知对方更新了共享内存
int flag = 1; // while循环条件
// 获取key值
key_t key;
if((key = ftok(__MSG_FILE__.c_str(),'z')) < 0)
{
perror("ftok error");
exit(1);
}
// 创建共享内存
int shmid;
if((shmid = shmget(key,4096,0)) == -1)
{
perror("shmget error");
exit(1);
}
// 和当前进程关联
char* shm;
shm = (char*)shmat(shmid,0,0);
if((int)*shm == -1)
{
perror("Attach Shared Memory Error");
exit(1);
}
// 创建消息队列
int msqid;
if((msqid = msgget(key,0)) == -1)
{
perror("msgget error");
exit(1);
}
// 创建信号量
sem_init(&blank,0,111);
sem_init(&xfull,0,0);
// 写数据
printf("***************************************\n");
printf("* IPC *\n");
printf("* Input r to send data to server. *\n");
printf("* Input q to quit. *\n");
printf("***************************************\n");
// 读数据
while(flag)
{
char c;
printf("Please input command: ");
scanf("%c",&c);
switch (c) {
case 'r':
printf("Data to send: \n");
sem_wait(&blank);
scanf("%s",shm);
sem_post(&xfull);
// 清空标准输入缓冲区
while((c = getchar())!='\n' && c!=EOF);
msg.mtype = 888;
msg.mtext = 'r';
sem_wait(&xfull);
msgsnd(msqid,&msg,sizeof(msg.mtext),0);
sem_post(&blank);
break;
case 'q':
msg.mtype = 888;
msg.mtext = 'q';
msgsnd(msqid,&msg,sizeof(msg.mtext),0);
flag = 0;
break;
default:
printf("Wrong input!\n");
while((c=getchar())!='\n'&&c!=EOF);
}
}
// 解除关联
shmdt(shm);
// 删除共享内存,消息队列
shmctl(shmid,IPC_RMID,NULL);
msgctl(msqid,IPC_RMID,NULL);
sem_destroy(&blank);
sem_destroy(&xfull);
return 0;
}
注意:当scanf()输入字符或字符串时,缓冲区中遗留下了\n,所以每次输入操作后都需要清空标准输入的缓冲区。但是由于 gcc 编译器不支持fflush(stdin)(它只是标准C的扩展),所以我们使用了替代方案:
while((c=getchar())!='\n' && c!=EOF);
浙公网安备 33010602011771号