进程间通讯
- 管道,信号量,消息队列,共享内存,socket(网路部分)
通讯条件
- 至少两个以上进程,进程间必须有公共访问的资源。
管道文件通讯
- 普通文件的读写文件偏移量是共享的,管道的读写文件偏移量是分开的。
- 管道文件主要用于进程间通信,存储文件内容直接在内存上。
有名管道
- 有名管道是半双工通讯,所谓半双工通讯就是在同一时刻只能有一个进程读一个进程写。
创建有名管道方式
mkfifo filename // 命令形式创建有名管道
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(char*path,int flag); // 函数形式创建有名管道
// flag:文件权限
- 有名管道在磁盘上会有一个文件标识(inode节点),但是文件存储不在磁盘上在内存上。
- 有名管道通讯,内存缓冲区中的数据没有区分,读取数据和写入数据的次数以及读取和写入的大小都没有关系,因此有名管道只适合两个进程不需要数据区分的通讯。
代码实现两个进程通过有名管道通讯
向管道写数据 – write.cpp
#include <iostream>
#include <string>
using namespace std;
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h>
int main()
{
// 创建管道文件
int ret = mkfifo("comm", 664);
// 只读方式打开
int fd = open("comm", O_WRONLY);
assert(fd != -1);
for (;;)
{
string str;
cout << "input data:";
cin >> str;
if (str == "end")
break;
ret = write(fd, str.c_str(), str.size());
assert(ret != -1);
}
close(fd); // 关闭管道
return 0;
}
读管道的数据 – read.cpp
#include <iostream>
using namespace std;
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h>
int main1()
{
// 只读方式打开
int fd = open("comm", O_WRONLY);
assert(fd != -1);
for (;;)
{
char str[128] = { 0 };
if (str == "end")
break;
int ret = read(fd, str, 128);
if (ret <= 0)
break;
cout << "Read Date :" << str << endl;
}
close(fd); // 关闭管道
return 0;
}
无名管道
- 无名管道也是半双工通讯,无名管道不存在inode节点(不存在管道文件),其实是直接在内存上开辟一块内存空间。
- 无名管道利用的是父子进程共享调用fork之前打开的文件描述符。
无名管道的创建方式
#include <unistd.h>
int pipe(int fd[2]);
- fd[0]:指向创建的无名管道的读端。
- fd[1]:指向创建的无名管道的写端。
代码实现父子进程通过无名管道通信
#include <iostream>
#include <string>
using namespace std;
#include <unistd.h>
#include <assert.h>
// 父进程写数据,子进程读数据
int main()
{
int fd[2]; // 保存无名管道文件描述符
int pip = pipe(fd); // 创建无名管道
int n = fork();
if (n == 0)
{
// 子进程,关闭写端
close(fd[1]);
for (;;)
{
char str[128] = { 0 };
int ret = read(fd[0],str, 128);
if (ret <= 0)
break;
cout << "Read Data:" << str << endl;
}
close(fd[0]);
}
else
{
// 父进程,关闭读数据
close(fd[0]);
for (;;)
{
string str;
cout << "Write Data:";
cin >> str;
if (str == "end")
{
break;
}
int ret = write(fd[1], str.c_str(), str.size());
assert(ret != -1);
}
close(fd[1]);
}
return 0;
}
消息队列
-
消息队列利用进程共享1G的内存空间的提点
-
示意图
-
消息由连部分组成,消息类型+消息数据,该结构体需要用户使用时自己定义。
struct mymesg // 消息结构体
{
long mtype; // 消息类型
char mtext[512]; // 消息数据
};
- 消息队列是按照条算的,每次发送的数据都互不影响,和管道相反。
消息队列相关函数
#include <sys/msg.h>
int msgget(key_t key,int falg);
- 创建一个新队列或者打开一个现存队列,成功返回队列ID,失败返回-1。
- flag -> IPC_PRIVATE:总是创建一个新队列。
- flag -> IPC_CREAT:创建或者获取一个队列。
int msgsnd(int mspid,const void*ptr,int nbytes,int flag); // 发送
- ptr:指向一个消息结构体,结构体存放消息类型。
- flag : 一般设置为0。
- nbytes :mtext中存储的消息长度。
int msgrcv(int mspid,void*ptr,int nbytes,long type,int flag); // 接收
-
nbytes:一般是小于512字节的,如果大于512字节需要设置flag。
-
type:读取数据的类型。
-
消息类型 说明 type ==0 消息队列第一个消息 type > 0 消息队列第type个消息 type < 0 返回消息队列中类型值小于或等于type绝对值的消息。如果这种消息由若干,返回类型值最小的。
-
-
flag :IPC_NOWAIT设置非阻塞
int msgctl(int mspid,int cmd,struct mspid_ds*buf);
- 将 cmd 赋值为IPC_RMID 删除消失队列,立即生效。
消息队列相关命令
ipcs -q // 查看消息队列
ipcrm -q ID // 删除指定消息队列
使用消息队列实现进程通讯
进程write往消息队列中写数据
#include <iostream>
#include <string>
using namespace std;
#include <unistd.h>
#include <sys/msg.h>
#include <assert.h>
#include <string.h>
struct mymesg // 消息结构体
{
long type;
char mtext[512];
};
int main(int argc,char*argv[])
{
if (argc < 2)
return -1;
mymesg msg;
memset(&msg, 0, sizeof(msg));
sscanf(argv[1], "%d", &msg.type);
strcpy(msg.mtext, argv[2]);
int ret = msgget((key_t)123, IPC_CREAT | 0664); // 创建一个消息队列,ID123
assert(ret != -1);
msgsnd(ret, &msg, strlen(msg.mtext), 0);
return 0;
}
// ./write 1000 hello
// 使用main函数参数,1000类型 hello数据
进程read从消息队列中读数据
#include <iostream>
using namespace std;
#include <unistd.h>
#include <sys/msg.h>
#include <assert.h>
#include <string.h>
struct mymesg // 消息结构体
{
long type;
char mtext[512];
};
int main(int argc,char*argv[])
{
if (argc < 2)
return -1;
int ret = msgget((key_t)123, IPC_CREAT | 0664);
assert(ret != -1);
mymesg msg;
memset(&msg, 0, sizeof(msg));
int type = 0;
sscanf(argv[1], "%d", &type);
msgrcv(ret, &msg, 511, type, 0);
cout << msg.type << ":" << msg.mtext << endl;
return 0;
}
信号量
- 进程同步控制的一种方式,信号量类似于一个计数器。
- 所谓进程同步就是按照顺序执行,A执行完成后B才能执行。
信号量相关函数
#include <sys/sem.h>
int semget(key_t key,int nsems,int flag);
- 创建或者获取信号量集,成功返回信号量集ID,失败返回-1。
- nsems是在创建信号量集的时候指定信号量个数的,如果是从现有的获取信号量直接给0。
- flag :IPC_CREAT。
struct sembuf
{
unsigned short sem_num;
short sem_op;
short sem_flg;
};
int semop(int semid, sembuf semoparray[],int nops);
- nopes指定结构体数组中操作信号量的个数。
- sem_op 正数:进程释放占用资源,负数:进程获取资源。
- flag :IPC_NOWAIT:为获取直接退出,默认是挂起等待。
int semctl(int semid,int semnum,int cmd, /* union semun arg*/ );
- 信号量初始化函数,semnum 指定操作信号量集中的那个信号量,类似数组。
- cmd :SETVAL(设置信号量),IPC_RMID(删除信号量集,立即生效)。
命令操作
ipcs -s // 查看信号量集
ipcrm -s ID // 删除信号量集
代码
对信号量函数进行封装
#include <sys/sem.h>
#include <assert.h>
union Semun
{
int val;
};
int sem_init(int key, int nsems, int*semval)
{
// 尝试获取信号量
int semid = semget((key_t)key, 0, 0664);
if (semid != -1)
return semid;
// 获取失败就是创建信号量
semid = semget((key_t)key, nsems, IPC_CREAT | 0664);
assert(semid != -1);
for (int i = 0; i < nsems; ++i)
{
Semun sem;
sem.val = semval[i];
semctl(semid, i, SETVAL, sem);
}
return semid;
}
// 对semid集合中的num个信号进行p操作,获取资源
void sem_p(int semid, int *sems, int num)
{
sembuf buf[num];
for (int i = 0; i < num; ++i)
{
buf[i].sem_num = sems[i];
buf[i].sem_op = -1;
buf[i].sem_flg = SEM_UNDO;
}
semop(semid, buf, num);
}
// 对semid集合中的num个信号进行v操作,释放资源
void sem_v(int semid, int*sems, int num)
{
sembuf buf[num];
for (int i = 0; i < num; ++i)
{
buf[i].sem_num = sems[i];
buf[i].sem_op = 1;
buf[i].sem_flg = SEM_UNDO;
}
semop(semid, buf, num);
}
// 删除信号量
void sem_del(int semid)
{
if (semctl(semid, 0, IPC_RMID) == -1)
throw "sem RMID Failed";
}
共享内存
- 共享内存是进程间通讯速度最快的。
- 共享内存允许两个或者多个进程共享物理内存的同一块区域,即共享内存会成为进程用户空间的一部分。
- 共享内存作为用户内存的一部分,是可以直接进行物理映射到物理内存,就相当于多个进程可以映射到同一块物理内存上。
- 共享内存通讯,进程不需要自己开辟一块buff,然后在将buff数据写入进程通讯的共享内存中,而是直接写入共享内存,减少了内存的拷贝的开销。
- 共享内存是不受内核控制的,意味着需要通过同步方式使得进程不会同时访问共享内存这块临界资源。
共享内存相关函数
#include <sys/shm.h>
#include <sys/types.h>
int shmget(key_t key,size_t size,int flag);
- 创建一个新的共享内存或者获取已有内存ID
- size 是创建共享内存的大小,因为内核是以整页的形式分配共享内存的,因此size会被提升为最近系统分页大小的整数倍,但是size以外的内存是不可用的。
- flag : IPC_CREAT | 0664。
void* shmat(int shmid,const void* shmaddr,int flag);
- 将shmid标识的共享内存附加到调用进程的虚拟地址空间中。
- shmaddr:一般默认为NULL,这样内核就会自己指定一个合适的位置开辟内存。
- flag = 0:对内存即可读也可写。
int shmdt(const void* shmaddr);
- 将该段内存调离出进程的虚拟地址空间,断开映射关系,但是共享内存还在。
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- cmd -> IPC_RMID ,从系统内存删除共享内存,因为每个共享内存都有一个计数器,因此只有计数器减为0时,共享内存才会真正释放。
共享内存实现进程通讯
向共享内存写入数据进程
#include <iostream>
#include <string>
using namespace std;
#include "sem.h"
#include <sys/shm.h>
#include <assert.h>
#include <string.h>
int main()
{
int sem[] = { 0,1 };
int semid = sem_init(1, 2, sem);
assert(semid != -1);
int shmid = shmget(2, 128, IPC_CREAT | 0664); // 创建共享内存
assert(shmid != -1);
// 映射内存
char *ptr = (char*)shmat(shmid, NULL, 0);
for (;;)
{
sem_p(semid, (sem+1), 1); // 对sem[1]进行p操作
cout << "imput Data:";
fgets(ptr, 127, stdin); // 从标准输入获取数据
sem_v(semid, sem, 1); // 对sem[0]进行v操作
if (!strncmp(ptr, "end", 3))
break;
}
shmdt(ptr);
return 0;
}
从共享内存读取数据进程
#include <iostream>
using namespace std;
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include "sem.h"
#include <sys/shm.h>
int main()
{
int sem[] = { 0,1 };
int semid = sem_init(1, 2, sem);
assert(semid != -1);
int shmid = shmget(2, 128, IPC_CREAT | 0664); // 获取共享内存
assert(shmid != -1);
char *ptr = (char*)shmat(shmid, NULL, 0);
for (;;)
{
sem_p(semid, sem, 1); // 对sem[0]进行p操作
if (!strncmp(ptr, "end", 3))
break;
cout << "Data:" << ptr << endl;
sem_v(semid, (sem+1), 1); // 对sem[1]进行v操作
}
shmdt(ptr);
return 0;
}
第一次整理进程间通讯,如有错误,请指出!!!