C语言实现进程间通信
在没有学习进程间通信的时候,两个进程可以通过文件进程通信。但是在使用文件通信的时候谁先执行谁后执行无法确定,所以这种通信方式有问题。在linux系统中的进程间通信方式有7种。
原始的通信方式:无名管道 有名管道 信号
system V的系统上引入如下三种方式:消息队列 共享内存 信号灯
通过套接字进行本地进程间通信:BSD
一、原始的通信方式
1.无名管道
①无名管道的原理
无名管道只能用于具备亲缘关系的进程间通信,无名管道是一个半双工的通信方式。
单工: A----->B
半双工:在同一时间点内只能一端发另外一端收 A------>B B----->A
全双工:在同一时刻内可以双向发送和接收 A<------->B
管道最大是64K,无名管道不能使用lseek定位光标。
②无名管道读写端特点
读端存在,写管道 :有多少写多少,直到管道写满(64K)
读端不存在,写管道:读端不存在写管道没有意义,管道破裂,操作系统给进程发送一个SIGPIPE,杀死进程
写端存在,读管道 :有多少读多少,如果管道里没有数据,读阻塞等待
写端不存在,读管道:有多少读多少,如果管道里没有数据,立即ansh
③用到的函数
int pipe(int pipefd[2]);
功能:创建一个无名管道
#include <unistd.h>
参数:
@pipefd:返回的是读写段的文件描述符
返回值:成功返回0,失败返回-1置位错误码
示例:
#include <head.h>
#include <sys/wait.h>int main(int argc, char const *argv[])
{int pipefd[2];char buf[1024] = {0};pid_t pid;//1.创建管道if(pipe(pipefd) == -1)PRINT_ERR("pipe create error");//2.创建进程pid = fork();if(pid == -1){PRINT_ERR("fork error");}else if(pid == 0 ){close(pipefd[0]); //关闭读取端//1.子进程while(1){fgets(buf,sizeof(buf),stdin);buf[strlen(buf)-1]='\0';write(pipefd[1],buf,strlen(buf));if(strncmp(buf,"quit",4)==0)break;}printf("子进程退出了\n");close(pipefd[1]);exit(EXIT_SUCCESS);}else{close(pipefd[1]); //关闭写端口//2.父进程while(1){memset(buf,0,sizeof(buf)); //bzeroread(pipefd[0],buf,sizeof(buf));if(strncmp(buf,"quit",4)==0)break;printf("parent buf = %s\n",buf);}printf("父进程退出了\n");close(pipefd[0]);wait(NULL);}return 0;
}
//head.h#ifndef __HEAD_H__
#define __HEAD_H__#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>#define PRINT_ERR(msg) do{ \perror(msg);\return -1;\}while(0)#endif
————————————————
版权声明:本文为CSDN博主「zhangts318」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhangts318/article/details/123973935
2.有名管道
有名管道可以用于任意进程间的通讯,不一定是亲缘关系的进程。有名管道的大小也是64K,也不能使用lseek函数。
①有名管道通信的特点
读不存在写管道:在打开的位置阻塞
写不存在读管道:在打开的位置阻塞
先存在读,然后关掉读,写管道:管道破裂,收到SIGPIPE信号
先存在写,然后关掉写,读管道:读端立即返回
如果读写都正常存在:有多少写多少,直到写满64K为止,有多少读多少,没有数据就阻塞
②用到的函数
int mkfifo(const char *pathname, mode_t mode);
功能:创建一个有名管道
#include <sys/types.h>
#include <sys/stat.h>
参数:
@pathname:路径及有名管道的文件
管道文件不是在磁盘上存储的,管道文件是在内存上存储的。
管道文件的大小永远都是0,不管有没有向里面写数据,只起到标识作用
@mode:管道文件的权限
返回值:成功返回0,失败返回-1置位错误码
//当文件创建成功之后,可以通过open read write close进行通信
示例:
//创建管道
#include <head.h>
#define FIFO "./fifo1"
int main(int argc, char const *argv[])
{char buf[20] = {0};if (mkfifo(FIFO, 0664) == -1)PRINT_ERR("make fifo error");while (1){fgets(buf, sizeof(buf), stdin);buf[strlen(buf) - 1] = '\0';if (strncmp(buf, "quit", 4) == 0)break;}printf("销毁管道文件..\n");system("rm ./fifo1");return 0;
}
//发送方
#include <head.h>
#define FIFO "./fifo1"
int main(int argc, char const *argv[])
{int fd;char buf[1024] = {0};if((fd = open(FIFO,O_WRONLY))==-1){PRINT_ERR("open error");}while(1){fgets(buf, sizeof(buf), stdin);buf[strlen(buf) - 1] = '\0';write(fd,buf,strlen(buf));if (strncmp(buf, "quit", 4) == 0)break;}close(fd);return 0;
}
//接收方
#include <head.h>
#include <strings.h>
#define FIFO "./fifo1"
int main(int argc, char const *argv[])
{int fd;char buf[1024] = {0};if((fd = open(FIFO,O_RDONLY))==-1){PRINT_ERR("open error");}while(1){bzero(buf,sizeof(buf));read(fd,buf,sizeof(buf));if (strncmp(buf, "quit", 4) == 0)break;printf("buf = %s\n",buf);}close(fd);return 0;
}
3.信号
信号是linux内核中一种软件模拟硬件中断的机制,如果没有linux内核就没有信号,但是会存在中断。用户可以给进程发信号,进程可以给进程发信号,内核可以给进程发信号。进程收到信号的处理方式默认,忽略,捕捉。
①查看Linux中的信号:kill -l
常用的信号:
注:在所有的信号中只有SIGKILL和SIGSTOP不能被捕捉也不能被忽略,其他的信号如果是默认处理的,大部分都是杀死进程。
②用到的函数:signal/kill/raise/alarm
typedef void (*sighandler_t)(int); //信号处理函数的原型
void handle(int signo)
{
//信号处理函数的原型
}
sighandler_t signal(int signum, sighandler_t handler);
功能:注册信号处理函数
#include <signal.h>
参数:
@signum:信号号
@handler:处理函数
SIG_IGN :忽略
SIG_DFL :默认
handle :捕捉
返回值:成功返回handler,失败返回SIG_ERR,置位错误码
示例:
#include <head.h>
#include <signal.h>
void handle(int signo)
{if (signo == SIGINT){printf("收到了ctrl+c\n");}
}
int main(int argc, char const *argv[])
{// 1.默认处理,杀死程序// if(SIG_ERR == signal(SIGINT,SIG_DFL))// PRINT_ERR("signal error");// 2.忽略处理,ctrl+c没有任何反应// if (SIG_ERR == signal(SIGINT, SIG_IGN))// PRINT_ERR("signal error");// 3.捕捉if (SIG_ERR == signal(SIGINT, handle))PRINT_ERR("signal error");while (1);return 0;
}
int kill(pid_t pid, int sig);
功能:给指定的进程发信号
#include <sys/types.h>
#include <signal.h>
参数:
@pid:进程号
pid>0:给指定pid进程发信号
pid=0: 给当前进程组内的进程发信号
pid = -1 :给所有的进程发信号(必须有权限)
pid <-1 :将pid取反,将信号发给取反之后对应的进程组
@sig:信号号
返回值:成功返回0,失败返回-1置位错误码
int raise(int sig);
功能:给当前进程发信号
#include <sys/types.h>
#include <signal.h>
参数:
@sig:信号号
返回值:成功返回0,失败返回非0
示例:
#include <head.h>
#include <sys/wait.h>
#include <signal.h>
void handle(int signo)
{printf("get SIGUSR1 signal from child\n");waitpid(-1,NULL,WNOHANG);raise(SIGKILL);
}
int main(int argc, char const *argv[])
{pid_t pid;pid = fork();if(pid < 0){PRINT_ERR("fork error");}else if(pid ==0){sleep(5);printf("子进程退出了\n");kill(getppid(),SIGUSR1);}else{signal(SIGUSR1,handle);while(1);}return 0;
}
闹钟信号alarm:
unsigned int alarm(unsigned int seconds);
功能:在seconds后发送闹钟信号
#include <unistd.h>
参数:
@seconds:倒计时的秒钟
如果在倒计时到0前又重新给seconds赋值,重新计数seconds秒
如果第一次就是seconds赋值为0,就不会发SIGALRM信号了
返回值:返回上一次倒计时为0前的剩余的秒钟,如果是0表示之前没有调闹钟的函数
示例:
#include <head.h>
#include <signal.h>
void handle(int signo)
{printf("自动出牌了=t\n");alarm(4); //如果第一次都不输入任何的字符,可以在自动出牌后开启下一次计时
}
int main(int argc, char const *argv[])
{if (SIG_ERR == signal(SIGALRM, handle))PRINT_ERR("signal error");alarm(4);//倒计时+kill(getpid(),SIGALRM)char ch;while(1){ch = getchar();getchar();printf("手动出牌是=%c\n",ch);alarm(4);}return 0;
}
二、system V IPC进程间通讯
消息队列 共享内存 信号量
查看相关的命令:ipcs 查看所有的信息(消息队列,共享内存,信号量)
ipcs -q 消息队列
ipcs -m 共享内存
ipcs -s 信号量(信号灯)(semphore)
删除相关的命令: ipcrm -q/-m/-s ID 删除
1.消息队列
①消息队列的原理
②用到的函数:ftok/msgget/msgsnd/msgrcv/msgctl
头文件均为:#include <sys/ipc.h>
#include <sys/msg.h>
key_t ftok(const char *pathname, int proj_id);
功能:获得key值
参数:
@pathname 已经存在的文件路径
@proj_id 获取这个整数的低8bit (key = proj_id,inode,st_dev)
返回值:成功返回 key值,失败返回-1
第二种方法:将key值指定为IPC_PRIVATE ,当IPC对象在亲缘关系进程通信的时候
int msgget(key_t key, int msgflg);
功能:创建IPC对象
参数:
@key IPC_PRIVATE 或 ftok
@msgflg IPC_CREAT | 0666 或 IPC_CREAT | IPC_EXCL | 0666 (判断IPC对象是否存在)
返回值:成功返回ID,失败返回-1
注意:如果对应key值的IPC对象不存在,则创建,如果存在,直接返回IPC对象的ID
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:发送消息
参数:
@msqid 消息队列ID
@msgp 需要发送的消息存放的地址
@msgsz 消息正文的大小
@msgflg 0:阻塞的方式发送 IPC_NOWAIT:非阻塞方式调用
返回值:成功返回0,失败返回-1 (队列的大小限制MSGMNB 16384)
消息结构体定义:
typedef struct{
long msg_type; //消息类型必须在第一个位置,
char mtxt[1024];
...
}msg_t;
正文大小:sizeof(msg_t) - sizeof(long)
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:接收消息
参数:
@msqid 消息队列ID
@msgp 存放接收到的消息
@msgsz 正文大小
@msgtyp 消息类型 , 0: 总是从消息队列中提取第一个消息
@msgflg 0:阻塞的方式接收 IPC_NOWAIT:非阻塞方式调用
返回值:成功返回 接收消息正文大小,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:删除消息队列
参数:
@msgqid 消息队列
@cmd IPC_RMID(删除消息队列) IPC_SET(设置消息队列的属性信息) IPC_STAT(获取消息队列属性信息)
@buf 存放消息队列属性
返回值:成功返回0,失败返回-1
示例:
//发送方
#include <head.h>
#include <sys/ipc.h>
#include <sys/msg.h>
typedef struct
{long msg_type; //消息类型必须在第一个位置,char name[20];int age;char sex;
} msg_t;
int main(int argc, char const *argv[])
{key_t key;int msgid;// 1.获取keyif ((key = ftok("/home/linux", 'y')) == -1)PRINT_ERR("ftok error");// 2获取消息队列if ((msgid = msgget(key, IPC_CREAT | 0664)) == -1)PRINT_ERR("msg get error");msg_t m1 = {1, "zhangsan", 50, 'm'};msgsnd(msgid, &m1, sizeof(msg_t) - sizeof(long), 0);msg_t m2 = {2, "lisi", 20, 'm'};msgsnd(msgid, &m2, sizeof(msg_t) - sizeof(long), 0);msg_t m3 = {3, "wangwu", 18, 'w'};msgsnd(msgid, &m3, sizeof(msg_t) - sizeof(long), 0);return 0;
}
//接收方
#include <head.h>
#include <sys/ipc.h>
#include <sys/msg.h>
typedef struct
{long msg_type; //消息类型必须在第一个位置,char name[20];int age;char sex;
} msg_t;
int main(int argc, char const *argv[])
{key_t key;int msgid;// 1.获取keyif ((key = ftok("/home/linux", 'y')) == -1)PRINT_ERR("ftok error");// 2获取消息队列if ((msgid = msgget(key, IPC_CREAT | 0664)) == -1)PRINT_ERR("msg get error");msg_t msg;while(1){bzero(&msg,sizeof(msg_t));msgrcv(msgid,&msg,sizeof(msg_t)-sizeof(long),2,0);printf("name =%s,age = %d,sex = %c\n",msg.name,msg.age,msg.sex);}return 0;
}
2.共享内存
①共享内存内部原理
②用到的函数:shmget/shmat/shmdt/shmctl
头文件均为:#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
功能:获取共享内存段的ID,创建共享内存
参数:
@key IPC_PRIVATE 或 ftok()
@size 申请的共享内存段大小 [4k的倍数]
@shmflg IPC_CREAT | 0666 或 IPC_CREAT | IPC_EXCL
返回值:成功返回ID,失败返回-1
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:映射共享内存到用户空间
参数:
@shmid 共享内存段ID
@shmaddr NULL:系统自动完成映射
@shmflg SHM_RDONLY:只读 0:读写
返回值:成功返回映射后的地址,失败返回(void *)-1
int shmdt(const void *shmaddr);
功能:撤销映射
参数:
@shmaddr 共享内存映射的地址
注意:当一个进程结束的时候,它映射共享内存,会自动撤销映射
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//shmctl(shmid,IPC_RMID,NULL);
功能:根据命令控制共享内存,删除IPC对象
参数:
@shmid 共享内存段的ID
@cmd IPC_STAT[获取属性],IPC_SET[设置属性],IPC_RMID[删除IPC对象]
@buf 保存属性
返回值:成功返回0,失败返回 -1
注意:当我们调用shmctl删除共享内存的时候,并不会立即删除。只有当共享内存映射次数为0,才会删除共享内存对象
示例:
//发送方
#include <head.h>
#include <sys/ipc.h>
#include <sys/shm.h>int main(int argc, char const *argv[])
{key_t key;int shmid;char * addr;// 1.获取keyif ((key = ftok("/home/linux", 'y')) == -1)PRINT_ERR("ftok error");// 2获取共享内存if((shmid = shmget(key,4096,IPC_CREAT|0664))==-1)PRINT_ERR("shm get error");addr = shmat(shmid,NULL,0);if(addr == (void *)-1){fprintf(stderr,"shm at error");return -1;}while(1){fgets(addr,4096,stdin);addr[strlen(addr)-1]='\0'; }//撤销映射shmdt(addr);}
//接收方
#include <head.h>
#include <sys/ipc.h>
#include <sys/shm.h>int main(int argc, char const *argv[])
{key_t key;int shmid;char * addr;// 1.获取keyif ((key = ftok("/home/linux", 'y')) == -1)PRINT_ERR("ftok error");// 2获取共享内存if((shmid = shmget(key,4096,IPC_CREAT|0664))==-1)PRINT_ERR("shm get error");addr = shmat(shmid,NULL,0);if(addr == (void *)-1){fprintf(stderr,"shm at error");return -1;}while(1){sleep(3);printf("addr = %s\n",addr);}//撤销映射shmdt(addr);
}
3.进程的同步:信号量(信号灯)
POSIX 线程中的同步用的是无名信号量
进程间的同步使用的是IPC 对象[信号灯集]
信号灯集:信号灯集合,每一个信号灯都可以用来表示一类资源,其值表示资源的个数
用到的函数:sema_init/P/V(自行封装的,源代码如下)
//源代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <head.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 init_sem_value(int sem_id,int sem_num,int value)
{union semun sem_val;sem_val.val = value;if(semctl(sem_id,sem_num,SETVAL,sem_val) < 0){PRINT_ERR("semctl error");}return 0;
}
int sema_init(int num)
{key_t key;int semid;// 1.获取keyif ((key = ftok("/home", 'u')) == -1)PRINT_ERR("ftok error");// 2获取共享内存if((semid = semget(key,num,IPC_CREAT|0664))==-1)PRINT_ERR("semget error");init_sem_value(semid,0,0);init_sem_value(semid,1,1);return semid;
}
//P申请资源-1
int P(int sem_id,int sem_num)
{struct sembuf sem;sem.sem_num = sem_num;sem.sem_op = -1;sem.sem_flg = 0;if(semop(sem_id,&sem,1) < 0){PRINT_ERR("semp error");}
}
//V操作释放资源+1
int V(int sem_id,int sem_num)
{struct sembuf sem;sem.sem_num = sem_num;sem.sem_op = 1;sem.sem_flg = 0;if(semop(sem_id,&sem,1) < 0){PRINT_ERR("semv error");}
}
//函数声明
#ifndef __SEM_H__
#define __SEM_H__
int sema_init(int num);
int P(int sem_id,int sem_num);
int V(int sem_id,int sem_num);#endif
示例:
//发送方
#include <head.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "sem.h"
int main(int argc, char const *argv[])
{key_t key;int shmid;int semid;char * addr;//0.初始化信号灯semid = sema_init(2);// 1.获取keyif ((key = ftok("/home/linux", 'y')) == -1)PRINT_ERR("ftok error");// 2获取共享内存if((shmid = shmget(key,4096,IPC_CREAT|0664))==-1)PRINT_ERR("shm get error");addr = shmat(shmid,NULL,0);if(addr == (void *)-1){fprintf(stderr,"shm at error");return -1;}while(1){P(semid,1);fgets(addr,4096,stdin);addr[strlen(addr)-1]='\0';V(semid,0);}//撤销映射shmdt(addr);}
//接收方
#include <head.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "sem.h"
int main(int argc, char const *argv[])
{key_t key;int shmid,semid;char * addr;semid = sema_init(2);// 1.获取keyif ((key = ftok("/home/linux", 'y')) == -1)PRINT_ERR("ftok error");// 2获取共享内存if((shmid = shmget(key,4096,IPC_CREAT|0664))==-1)PRINT_ERR("shm get error");addr = shmat(shmid,NULL,0);if(addr == (void *)-1){fprintf(stderr,"shm at error");return -1;}while(1){P(semid,0);printf("addr = %s\n",addr);V(semid,1);}//撤销映射shmdt(addr);}
C语言实现进程间通信相关推荐
- 进程双向通信c语言代码,进程间通信——管道(示例代码)
进程间通信方式主要分为管道.SystemV IPC. POSIX IPC三大类,管道作为进程间通信的一大重要方式,平时应用当中十分广泛.于是这里就先简单整理了一些关于管道的用法和注意事项. 匿名管道 ...
- 智能电视验收测试软件,验收测试
验收测试是基于用户要求和功能处理的正式测试.它确定软件是否符合指定的要求和用户要求.它是作为一种黑盒测试进行的,其中涉及测试系统接受程度所需的用户数量.这是软件测试的第四级和最后一级. 但是,该软件已 ...
- python写一个聊天程序_python实现一个简单的网络聊天程序
一.Linux Socket 1.Linux Socke基本上就是BSD Socket(伯克利套接字) 伯克利套接字的应用编程接口(API)是采用C语言的进程间通信的库,经常用在计算机网络间的通信.B ...
- Socket编程(C语言实现)—— AF_INET(典型的TCP/IP四层模型的通信过程),AF_UNIX(本地进程间通信)
1.AF_INET域与Socket通信 其是典型的TCP/IP四层模型的通信过程. (1)接收方与发送方依赖IP和port来标识,即,将本地socket绑定到对应的IP端口上: (2)发送数据时指定对 ...
- c语言进程间通信架构,构建微服务之:微服务架构中的进程间通信
这是使用微服务架构构建应用系列的第三篇文章.第一篇文章介绍了微服务架构模式并讨论了使用微服务的优势和劣势 :第二篇文章介绍了应用的客户端如何通过API网关作为中介实现服务间的通信:在这篇文章中我们将看 ...
- Python|线程和进程|阻塞|非阻塞|同步|异步|生成器和协程|资源竞争|进程间通信|aiohttp库|daemon属性值详解|语言基础50课:学习(11)
文章目录 系列目录 原项目地址 第34课:Python中的并发编程-1 线程和进程 多线程编程 使用 Thread 类创建线程对象 继承 Thread 类自定义线程 使用线程池 守护线程 资源竞争 G ...
- linux 下C语言编程(2)——进程的创建,挂起,解挂,进程间通信
在 linux 下利用C语言实现进程的创建,挂起和解挂操作 #include <stdio.h> #include <sys/stat.h> #include <sys/ ...
- Socket编程(C语言实现)——UDP协议(进程间通信AF_UNIX)的流式(SOCK_STREAM)+报式(SOCK_DGRAM)传输【循环监听】
Socket编程 目前较为流行的网络编程模型是客户机/服务器通信模式 客户进程向服务器进程发出要求某种服务的请求,服务器进程响应该请求.如图所示,通常,一个服务器进程会同时为多个客户端进程服务,图中服 ...
- c语言程序实现进程的管道通信,C 进程间通信--命名管道通信代码实现及其原理图示...
在将这个题目之前大家需要了解几个概念: 进程: 我们可以先看进程的定义:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础: ...
最新文章
- 用circlize包绘制circos-plot
- Shell编程—【01】shell中常用的字符串操作
- quick sort
- mate40pro什么时候用鸿蒙,mate40Pro什么时候可以用鸿蒙
- 160 - 50 DueList.5
- etag java_你知道HTTP协议的ETag是干什么的吗?
- 自动驾驶——传感器的配置参数
- TCP\IP协议实践:wireshark抓包分析之链路层与网络层
- python爬取京东图书_Python 3实战爬虫之爬取京东图书的图片详解
- Windows Server 2008 R2 WSUS服务器的详细配置和部署
- Classic Shell 4.2.4 中文版已经发布
- 关于PHP程序员技术职业生涯规划 2017年3月5日韩 天峰
- 三步骤快速开发 iOS资讯类App
- 哈工大人工智能暑期课实践项目——手写体识别四则运算(项目计划)
- Windows下动态内存分配方式http://whx.tzgt.gov.cn/newOperate/html/7/71/711/3938.html
- PCIE-XPDMA-Simple DMA传输笔记
- 基于STM32F767的FreeRTOS的移植
- slowfast代码实现和论文理解
- 瀑布式开发和迭代式开发
- 学习并掌握结构化写作方法,提高写作能力 ——结构化写作学习笔记(2)