IPC通信
进程间通信的目的
数据交换:
共享资源:
进程同步:
消息传递:
实现原理:通信进程能够访问相同的内存区域。
主要分为四大类:
- 管道
- System V IPC(消息队列、信号量、共享内存(内存映射) 通过命名空间实现)
- POSIX IPC(消息队列、信号量、共享内存 通过文件实现)
- 套接字
配上代码食用更佳: IPC通信实现
1. 管道
1.1 匿名管道
- 只能在具有血缘关系的进程间通信,父子关系、兄弟关系。
- 半双工通信
- 以字节流方式通信,数据格式由用户自行定义。
- 和fork配合使用
- 存在内存中
优点:
- 常用、简单。
缺点:
- 半双工模式,只能单向流动
- 只能用于具有血缘关系的进程
- 缓冲区大小有限制,默认是4K
- 管道没有名称
- 数据不能重复读。
实现代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <wait.h>
int main()
{
int fd[2]; // 0:读(子) 1:写(父)
pid_t pid;
int ret = pipe(fd);
if (ret == -1)
{
perror("pipe error!");
exit(1);
}
pid = fork(); //有两次返回
if (-1 == pid)
{
perror("create fail....");
exit(1);
}
else if (pid == 0)
{
// 子进程 读
close(fd[1]);
char buf[1024];
printf("child=%d\n", getpid());
ret = read(fd[0], buf, sizeof buf);
write(STDOUT_FILENO, buf, ret);
}
else if (pid > 0)
{
// 父进程 写1
sleep(1);
close(fd[0]);
printf("father=%d\n", getpid());
write(fd[1], "hello pipe\n", 11);
wait(NULL); //回收子进程,避免成为僵尸进程
}
printf("finished:%d\n",getpid());
return 0;
}
1.2命名管道
- 非血缘关系进程间通信
- 提供了一个路径名与管道关联
- 以文件形式存在文件系统
实现代码:
读取端:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#define P_FIFO "/tmp/p_fifo"
int main()
{
char cache[100];
int fd;
memset(cache, 0, sizeof cache); // 初始化内存
if (access(P_FIFO, F_OK) == 0)
{ // 判断管道文件是否存在
execlp("rm", "-f", P_FIFO, NULL); // 删掉
printf("access.\n");
}
if (mkfifo(P_FIFO, 0777) < 0)
{
printf("create named pipe failed.\n");
exit(1);
}
fd = open(P_FIFO, O_RDONLY | O_NONBLOCK); // 非阻塞方式打开
while (1)
{
memset(cache, 0, sizeof cache);
if ((read(fd, cache, 100)) == 0)
{
printf("no data:\n");
}
else{
printf("getdata:%s\n",cache);
}
sleep(1);
}
return 0;
}
发送端
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#define P_FIFO "/tmp/p_fifo"
int main(int argc,char **argv){
int fd;
if(argc<2){
printf("please input the write data.\n");
exit(1);
}
fd = open(P_FIFO,O_WRONLY|O_NONBLOCK); //非阻塞
if(-1 == fd){
printf("open failed!\n");
return 0;
}
else{
printf("open success!");
}
write(fd,argv[1],100);
close(fd);
return 0;
}
2.内存映射
通过mmap映射到内存上
通过munmap释放内存映射
实现原理:
- 在相同的命名空间下
优点:
- 减少I数据的拷贝次数,用内存读写取代I/O读写,大大提高了文件的读写效率。
- 提供进程间的共享内存及相互通信的方式
- 各自修改操作可以直接反映在映射的区域内,从而被对方空间及时捕捉。
缺点
- 空间浪费,以4k为单位。
- 创建、销毁、缺页造成的开销很大。
实现代码:
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
int main()
{
int fd = open("mytest.txt", O_RDWR | O_CREAT, 0644); //这个mytest.txt要有数据
if (fd < 0)
{
perror("open error!");
exit(1);
}
// 申请共享内存
void *p = mmap(NULL, 100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(p == MAP_FAILED){
perror("mmap error");
exit(1);
}
strcpy((char *)p,"abcefg\0");
int ret = munmap(p,100); //释放共享映射
if(-1 ==ret){
perror("munmap error!");
exit(1);
}
close(fd);
return 0;
}
3共享内存
我们先看System V IPC中的共享内存实现,再看POSIX IPC的共享内存实现,它们都是基于内存映射实现的。
3.1 System V IPC共享内存
基于命名空间实现
优缺点同内存映射一样
实现代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#define SHARED_PATH "/tmp/test"
int main(int argc,char** argv){
#if 1
int mode = atoi(argv[1]);
//创建键值
key_t shmkey = ftok(SHARED_PATH,1);
if(-1 == shmkey){
perror("ftok");
return -1;
}
//分配内存段
int shmid = shmget(shmkey,1024,0644|IPC_CREAT);
//通过内存段标识获取所在位置
void* addr = shmat(shmid,NULL,0);
if(addr == (void*)-1){
perror("shmat fail..");
return -1;
}
int i=0;
while(1){
if(0==mode){ //只读
char rbuf[1024]={0};
memcpy(rbuf,addr,1024);
printf("rbuf:%s\n",rbuf);
}
else{
char wbuf[1024]={0};
sprintf(wbuf,"%d",i++);
memset(addr,0,1024); //清空共享内存
memcpy(addr,wbuf,strlen(wbuf));
printf("succsss\n");
sleep(1);
}
}
//删除共享内存
shmctl(shmid,IPC_RMID,NULL);
#endif
return 0;
}
3.2 POSIX IPC共享内存
基于文件实现
实现代码:
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
int main(int argc, char **argv)
{
int mode = atoi(argv[1]);
// 1 打开共享内存文件
int fd = shm_open("/test2", O_RDWR | O_CREAT, 0644);
if (-1 == fd)
{
perror("shm_open error");
return -1;
}
// 2 设置缓冲大小
int ret = ftruncate(fd, 4096);
if (-1 == ret)
{
perror("ftruncate error");
return -1;
}
// 3 映射
void *addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED)
{
perror("mmap error");
return -1;
}
int i = 0;
while (1)
{
if (0 == mode)
{
char rbuf[4096] = {0};
memcpy(rbuf, addr, 4096);
printf("data:%s\n", rbuf);
}
else
{
char sbuf[4696] = {0};
sprintf(sbuf, "%d", i++);
memcpy(addr, sbuf, strlen(sbuf));
sleep(1);
}
}
// 4 解除映射
munmap(addr, 4096);
// 5 删除共享内存
shm_unlink("/test2");
return 0;
}
4 消息队列
下面分别阐述System V 消息队列和POSIX消息队列。
4.1 System V 消息队列
优点:
- 可以实现独立的进程间通信,不受进程的启动和结束顺序影响。
- 实现了并发处理,允许多个进程同时向消息队列中写入和读取消息。
- 通过消息优先级机制,可以优先处理重要的消息。
实现代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
// #define __USE_GNU // struct msgbuf
int main(int argc, char **argv)
{
int mode = atoi(argv[1]);
int mtype = atoi(argv[2]);
char* str = argv[3];
// 生成键值
key_t key = 1234;
// 创建消息队列
int id = msgget(key, 0644 | IPC_CREAT);
printf("id:%d\n", id);
while (1)
{
if (0 == mode)
{ // 读模式
struct msgbuf msg = {0};
int ret = msgrcv(id, &msg, 1024, mtype, 0);
if (-1 == ret)
{
perror("msgrcv");
break;
}
printf("ret:%d,type:%lu,mtext:%s\n", ret, msg.mtype, msg.mtext);
}
else{
struct msgbuf msg = {0};
msg.mtype = mtype;
memcpy(msg.mtext,str,strlen(str));
int ret = msgsnd(id,&msg,strlen(str),0);
if(-1 == ret){
perror("msgsnd");
break;
}
sleep(1);
}
}
msgctl(id,IPC_RMID,0); //删除消息队列
return 0;
}
4.2 POSIX 消息队列
基于文件的消息队列,操作POSIX消息队列像操作文件一样,这样可以大大简化编程难度。
**实现原理:**POSIX消息队列是基于mqueue文件系统实现。底层实现为mqueue inode节点,mqueue inode节点基于红黑树存储消息。
通过 cat /proc/filesystems 查看所有文件系统
mq_notify函数使用注意事项:
- 每个消息队列只能注册一个通知。
- 每个进程只能注册一个通知。
- 消息队列为空且有消息到达时才会发出通知。
- ·进程收到通知后,注册信息失效,需重新注册通知。
- ·通知消息会导致其他进程mq_recevie函数无法收到消息。.
- mq_notify函数sevp设置成NULL可以撤销注册。
实现代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <string.h>
const int MODE_RECEIVE = 0;
const int MODE_SEND = 1;
int main(int argc, char *argv[])
{
int mode = atoi(argv[1]);
if (mode == -1)
{
mq_unlink("/test1");
return 0;
}
struct mq_attr attr;
attr.mq_flags = 0;
attr.mq_maxmsg = 10;
attr.mq_msgsize = 1500;
// 打开消息队列
mqd_t fd = mq_open("/test1", O_RDWR | O_CREAT, 0644,&attr);
if (-1 == fd)
{
perror("mq_open error");
return -1;
}
while ((1))
{
if (MODE_RECEIVE == mode)
{ // 读模式
char rbuf[2048] = {0};
unsigned int priority = 0;
int ret = mq_receive(fd, rbuf, sizeof(rbuf), &priority);
if (-1 == ret)
{
perror("receive error");
break;
}
printf("ret:%d, priority:%u,data:%s\n", ret, priority, rbuf);
}
else
{
#define TEST_STRING "123456789"
int ret = mq_send(fd, TEST_STRING, strlen(TEST_STRING), 1);
if (-1 == ret)
{
perror("send error");
break;
}
sleep(1);
}
}
return 0;
}
进阶:notify的使用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <string.h>
#include <signal.h>
const int MODE_RECEIVE = 0;
const int MODE_SEND = 1;
int do_notify(int fd);
void test_proc(sigval_t val)
{
int fd = val.sival_int;
do_notify(fd);
char rbuf[2048] = {0};
unsigned int priority = 0;
int ret = mq_receive(fd, rbuf, sizeof(rbuf), &priority);
if (-1 == ret)
{
perror("receive error");
return;
}
printf("ret:%d, priority:%u,data:%s\n", ret, priority, rbuf);
}
int do_notify(int fd)
{
struct sigevent ev;
ev.sigev_value.sival_int = fd;
ev.sigev_notify = SIGEV_THREAD;
ev.sigev_notify_function = test_proc;
ev.sigev_notify_attributes = NULL;
int ret = mq_notify(fd, &ev);
if (-1 == ret)
{
perror("mq_notify error");
return -1;
}
return 0;
}
int main(int argc, char *argv[])
{
int mode = atoi(argv[1]);
if (mode == -1)
{
mq_unlink("/test1");
return 0;
}
struct mq_attr attr;
attr.mq_flags = 0;
attr.mq_maxmsg = 10;
attr.mq_msgsize = 1500;
// 打开消息队列
mqd_t fd = mq_open("/test1", O_RDWR | O_CREAT, 0644, &attr);
if (-1 == fd)
{
perror("mq_open error");
return -1;
}
if (MODE_RECEIVE == mode)
{
do_notify(fd);
}
while ((1))
{
if (MODE_RECEIVE == mode)
{ // 读模式
sleep(1);
#if 0
char rbuf[2048] = {0};
unsigned int priority = 0;
int ret = mq_receive(fd, rbuf, sizeof(rbuf), &priority);
if (-1 == ret)
{
perror("receive error");
break;
}
printf("ret:%d, priority:%u,data:%s\n", ret, priority, rbuf);
#endif
}
else
{
#define TEST_STRING "123456789"
int ret = mq_send(fd, TEST_STRING, strlen(TEST_STRING), 1);
if (-1 == ret)
{
perror("send error");
break;
}
sleep(1);
}
}
return 0;
}
5信号量
下面分别阐述System V 信号量和POSIX信号量。
4.1 System V 信号量
**作用:**用于协调多个进程之间的操作。
- P操作:用于申请资源。
- V操作:用于释放资源。
**实现原理:**具有相同IPC命名空间(struct ipc_namespace)的进程能够同时访问IPC命名空间相同的内存空间。
实现代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>
#include <string.h>
union semun
{
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
int sem_init_value(int id, int idx, int value)
{
union semun un;
un.val = value;
int ret = semctl(id, idx, SETVAL, un);
if (-1 == ret)
{
perror("semctl setval");
return -1;
}
printf("sem init value:%d\n", value);
return 0;
}
int sem_get_value(int id, int idx)
{
union semun un;
int ret = semctl(id, idx, GETVAL, un); // 返回信号量值
if (-1 == ret)
{
perror("semctl setval");
return -1;
}
printf("sem get value:%d\n", ret);
return 0;
}
int sem_p(int id)
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1;
buf.sem_flg |= SEM_UNDO;
int ret = semop(id, &buf, 1); // 第三个参数是数组长度
if (-1 == ret)
{
perror("sem w");
return -1;
}
return 0;
}
int sem_w(int id)
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 0;
buf.sem_flg |= SEM_UNDO;
int ret = semop(id, &buf, 1); // 第三个参数是数组长度
if (-1 == ret)
{
perror("sem v");
return -1;
}
return 0;
}
int sem_v(int id)
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1;
buf.sem_flg |= SEM_UNDO;
int ret = semop(id, &buf, 1); // 第三个参数是数组长度
if (-1 == ret)
{
perror("sem p");
return -1;
}
return 0;
}
int main(int argc, char **argv)
{
int op = atoi(argv[1]);
int val = atoi(argv[2]);
// 创建键值
key_t key = ftok("/tmp/test", 1);
if (-1 == key)
{
perror("ftok");
return -1;
}
// 创建一个信号量
int id = semget(key, 5, 0644 | IPC_CREAT);
printf("id:%d\n", id);
// 初始化
if (val >= 0)
sem_init_value(id, 0, val);
sem_get_value(id, 0);
if (-1 == op)
{ // 申请资源
sem_p(id);
}
else if (0 == op)
{
sem_w(id);
}
else
{
sem_v(id);
}
sem_get_value(id, 0);
// pause();
sleep(5);
union semun un;
semctl(id,0,IPC_RMID,un);
return 0;
}
**进阶:**和共享内存结合,使得进程之间同步
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/sem.h>
#define SHARED_PATH "/tmp/test"
union semun
{
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
int sem_init_value(int id, int idx, int value)
{
union semun un;
un.val = value;
int ret = semctl(id, idx, SETVAL, un);
if (-1 == ret)
{
perror("semctl setval");
return -1;
}
printf("sem init value:%d\n", value);
return 0;
}
int sem_get_value(int id, int idx)
{
union semun un;
int ret = semctl(id, idx, GETVAL, un); // 返回信号量值
if (-1 == ret)
{
perror("semctl setval");
return -1;
}
printf("sem get value:%d\n", ret);
return 0;
}
int sem_p(int id)
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1;
buf.sem_flg |= SEM_UNDO;
int ret = semop(id, &buf, 1); // 第三个参数是数组长度
if (-1 == ret)
{
perror("sem w");
return -1;
}
return 0;
}
int sem_w(int id)
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 0;
buf.sem_flg |= SEM_UNDO;
int ret = semop(id, &buf, 1); // 第三个参数是数组长度
if (-1 == ret)
{
perror("sem v");
return -1;
}
return 0;
}
int sem_v(int id)
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1;
buf.sem_flg |= SEM_UNDO;
int ret = semop(id, &buf, 1); // 第三个参数是数组长度
if (-1 == ret)
{
perror("sem p");
return -1;
}
return 0;
}
// 和信号量结合进行同步测试
int main(int argc, char **argv)
{
int mode = atoi(argv[1]);
int val = atoi(argv[2]);
#if 1
// 创建键值
key_t semkey = ftok("/tmp/test", 2);
if (-1 == semkey)
{
perror("ftok");
return -1;
}
// 创建一个信号量
int semid = semget(semkey, 5, 0644 | IPC_CREAT);
printf("semid:%d\n", semid);
// 初始化
if (val >= 0)
sem_init_value(semid, 0, val);
sem_get_value(semid, 0);
#endif
#if 1
// 创建键值
key_t shmkey = ftok(SHARED_PATH, 1);
if (-1 == shmkey)
{
perror("ftok");
return -1;
}
// 分配内存段
int shmid = shmget(shmkey, 1024, 0644 | IPC_CREAT);
// 通过内存段标识获取所在位置
void *addr = shmat(shmid, NULL, 0);
if (addr == (void *)-1)
{
perror("shmat fail..");
return -1;
}
int i = 0;
while (1)
{
if (0 == mode)
{ // 只读
sem_p(semid); //P操作
char rbuf[1024] = {0};
memcpy(rbuf, addr, 1024);
printf("rbuf:%s\n", rbuf);
}
else // 写进程
{
char wbuf[1024] = {0};
sprintf(wbuf, "%d", i++);
memset(addr, 0, 1024); // 清空共享内存
memcpy(addr, wbuf, strlen(wbuf));
printf("succsss\n");
sem_v(semid); //V操作
sleep(1);
}
}
// 删除共享内存
shmctl(shmid, IPC_RMID, NULL);
#endif
return 0;
}
4.2 POSIX 信号量
有名信号量用于进程间通信;无名信号量用于线程间同步。
**实现原理:**是由tmpfs文件系统和mmap内存映射共同实现。
实现代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <semaphore.h>
int main(int argc, char *argv[])
{
int mode = atoi(argv[1]);
sem_t *sem = sem_open("/test", O_RDWR | O_CREAT, 0644, 0);
if (SEM_FAILED == sem)
{
perror("sem_open erro");
return -1;
}
while (1)
{
if (0 == mode) //申请资源
{
sem_wait(sem);
printf("消费....\n");
}
else
{
sleep(1);
sem_post(sem);
printf("生产...\n");
}
}
sem_close(sem);
sem_unlink("/tmp");
return 0;
}
6 本地套接字
- AF_UNIX或AF_LOCAL
优点
- 简单、更强大(相比于上面的两种方式)
- 性能更好
服务端实现代码:
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <sys/un.h>
#define EXIT_FAILURE 1
#define UNIXSTR_PATH "/tmp/unix.str"
#define LISTENQ 5
#define BUFFER_SIZE 256
int main(void)
{
int lfd;
int cfd;
socklen_t len;
struct sockaddr_un servAddr;
struct sockaddr_un cliendAddr;
lfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (-1 == lfd)
{
perror("create lfd fail...");
exit(EXIT_FAILURE);
}
unlink(UNIXSTR_PATH);
bzero(&servAddr, sizeof(servAddr));
servAddr.sun_family = AF_LOCAL;
strcpy(servAddr.sun_path, UNIXSTR_PATH);
if (-1 == bind(lfd, (struct sockaddr *)&servAddr, sizeof(servAddr)))
{
perror("bind failed...");
exit(EXIT_FAILURE);
}
listen(lfd, LISTENQ);
printf("server started success\n");
len = sizeof(cliendAddr);
cfd = accept(lfd, (struct sockaddr *)&cliendAddr, &len);
if (-1 == cfd)
{
perror("accept fail..");
exit(EXIT_FAILURE);
}
char buf[BUFFER_SIZE];
while (1)
{
bzero(buf, sizeof buf);
if (read(cfd, buf, BUFFER_SIZE) == 0)
break;
printf("recv:%s", buf);
}
close(lfd);
close(cfd);
unlink(UNIXSTR_PATH);
return 0;
}
客户端代码实现:
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <sys/un.h>
#define EXIT_FAILURE 1
#define UNIXSTR_PATH "/tmp/unix.str"
#define LISTENQ 5
#define BUFFER_SIZE 256
int main(void)
{
int cfd;
struct sockaddr_un servAddr;
cfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (-1 == cfd)
{
perror("create cfd fail...");
exit(EXIT_FAILURE);
}
bzero(&servAddr, sizeof(servAddr));
servAddr.sun_family = AF_LOCAL;
strcpy(servAddr.sun_path, UNIXSTR_PATH);
int ret = connect(cfd, (struct sockaddr *)&servAddr, sizeof(servAddr));
if (-1 == ret)
{
perror("connect fail..");
exit(EXIT_FAILURE);
}
char buf[BUFFER_SIZE];
while (1)
{
bzero(buf, sizeof buf);
printf(">>");
if (fgets(buf, BUFFER_SIZE, stdin) == NULL)
{
break;
}
write(cfd, buf, strlen(buf));
}
close(cfd);
return 0;
}