进程间通信相关应用

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。

IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。

以Linux中的C语言编程为例

一、管道

管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。

1、特点:

它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。

它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。

它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中

2、原型:

使用man 2 pipe 指令查看pipe用法

1 #include <unistd.h>
2 int pipe(int fd[2]); // 返回值:若成功返回0,失败返回-1

 pipe.c 

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>

int main(int argc,char**argv){

        //进程id
        pid_t pid;
        //fd[0]:read  fd[1]:write
        int fd[2];
        //待写入的内容  
        char *content=(char*)malloc(sizeof(char)*24);
        //读取缓冲
        char readBuf[96];

        if(pipe(fd)==-1){
                perror("创建管道失败");
                exit(EXIT_FAILURE);
        }
        //创建子进程
        pid=fork();

        if(pid<0){
                perror("创建子进程失败...\n");
                exit(EXIT_FAILURE);
        }
        //父进程
        if(pid>0){
                printf("父进程延时3s...\n");
                sleep(3);
                printf("这里属于父进程...\n");
                close(fd[0]);
                printf("请录入:");
                //父进程后开始往管道写入数据
                gets(content);
                printf("键盘录入的内容是:%s\n父进程开始写入...\n",content);
                //ssize_t write(int fd, const void *buf, size_t count);         
                write(fd[1],content,strlen(content));
                printf("父进程写入完毕...\n");
                wait();
        }
        //子进程
        if(pid==0){
                printf("这里属于子进程...\n");
                close(fd[1]);
                //ssize_t read(int fd, void *buf, size_t count);
                read(fd[0],readBuf,96);
                printf("子进程读取到的内容是%s\n",readBuf);
                printf("子进程退出...\n");
                exit(0);
        }
                puts("父进程退出...Bye Bye");

        return 0;
}                           

运行结果如👇所示

二、FIFO

FIFO,也称为命名管道,它是一种文件类型

1、特点

FIFO可以在无关的进程之间交换数据,与无名管道不同。

FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。

2、原型

使用man 3 mkfifo 指令查看mkfifo 用法

1 #include <sys/stat.h>
2 // 返回值:成功返回0,出错返回-1
3 int mkfifo(const char *pathname, mode_t mode);

其中的 mode 参数与open函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。

当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:

若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。

若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。

mkfifo_read.c

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>

int main(){

        char readBuf[24];
        if(mkfifo("./file",0600)==-1 && errno!=EEXIST){
                perror("创建失败\n");
        }
        int fd=open("./file",O_RDONLY);
        puts("open file succeeded...");
        while(1){
                int nread=read(fd,readBuf,25);
                if(nread==0){
                        break;
                }

                printf("read %d bytes from file,file content is %s\n",nread,readBuf);

        }


        return 0;

}

mkfifo_write.c

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>

int main(){
        int i;
        int fd=open("./file",O_WRONLY);
        char *content="time to sleep";
        for(i=0;i<5;i++){
                int nwrite=write(fd,content,strlen(content));
                printf("write %d bytes to file\n",nwrite);
                puts("write file succeeded...");
                printf("延时3s...\n");
                sleep(3);
        }
        return 0;

}

运行结果如👇所示

三、消息队列

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。

1、特点

消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级

消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。

消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

2.原型

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);

msgRec.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgBuf{
	long mtype; //message type
	char mtext[64]; //message data
};	

int main(){

	struct msgBuf readBuf;
	//int msgget(key_t key, int msgflg);
	key_t key;
	//key_t ftok(const char *pathname, int proj_id);
	key=ftok(".",'s');	
	printf("key is %#x\n",key);
	int msgId=msgget(key,IPC_CREAT|0777);
	if(msgId==-1){
		perror("create queue failed\n");
	}
	
	//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

       //ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
   
	msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),321,0);
	printf("get from queue: %s\n",readBuf.mtext);
	struct msgBuf sendBuf={888,"message sent from Rec...\n"};
	msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
	//int msgctl(int msqid, int cmd, struct msqid_ds *buf);
	msgctl(msgId,IPC_RMID,NULL);

	return 0;
}

msgSend.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgBuf{
	long mtype; //message type
	char mtext[64]; //message data
};	

int main(){

	struct msgBuf sendBuf={321,"message sent from Send...\n"};
	struct msgBuf readBuf;
	//int msgget(key_t key, int msgflg);
	key_t key;
	//key_t ftok(const char *pathname, int proj_id);
	key=ftok(".",'s');	
	printf("key is %#x\n",key);
	int msgId=msgget(key,IPC_CREAT|0777);
	if(msgId==-1){
		perror("create queue failed\n");
	}
	
	//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
	msgsnd(msgId,&sendBuf,sizeof(sendBuf.mtext),0);
	puts("send message over...");

       //ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
   
	msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
	printf("get from queue: %s\n",readBuf.mtext);
    //int msgctl(int msqid, int cmd, struct msqid_ds *buf);
	msgctl(msgId,IPC_RMID,NULL);
	
	return 0;
}

执行rec(接收端),创建了一个消息队列,键值为0X7305f93,此时rec一直在监听,随后执行send(发送端),接收到了send的讯息,两者互相通讯。

Send:message sent from send... (send message over...)

Rec:get from queue:message sent from send...(message sent from Rec...)

Send:get from queue:message sent from Rec...

运行结果如👇所示

四、共享内存

共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。

1、特点

共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。

因为多个进程可以同时操作,所以需要进行同步。

信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

2、原型

#include <sys/shm.h>
2 // 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
3 int shmget(key_t key, size_t size, int flag);
4 // 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
5 void *shmat(int shm_id, const void *addr, int flag);
6 // 断开与共享内存的连接:成功返回0,失败返回-1
7 int shmdt(void *addr); 
8 // 控制共享内存的相关信息:成功返回0,失败返回-1
9 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

当用shmget函数创建一段共享内存时,必须指定其 size;而如果引用一个已存在的共享内存,则将 size 指定为0 。

当一段共享内存被创建以后,它并不能被任何进程访问。必须使用shmat函数连接该共享内存到当前进程的地址空间,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。

shmdt函数是用来断开shmat建立的连接的。注意,这并不是从系统中删除该共享内存,只是当前进程不能再访问该共享内存而已。

shmctl函数可以对共享内存执行多种操作,根据参数 cmd 执行相应的操作。常用的是IPC_RMID(从系统中删除该共享内存)。

shmwrite.c

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
int main(){

        int shmid;
        char *shmaddr;
        key_t key;
        key=ftok(".",1);
        //int shmget(key_t key, size_t size, int shmflg);
        shmid=shmget(key,1024*4,IPC_CREAT|0666);
        if(shmid==-1){
                perror("shmget failed...\n");
                exit(-1);
        }
        shmaddr=shmat(shmid,0,0);
        puts("shmat finished...");
        strcpy(shmaddr,"hermaniu");
        sleep(3);
        // int shmdt(const void *shmaddr);
        shmdt(shmaddr);
        // int shmctl(int shmid, int cmd, struct shmid_ds *buf);
        shmctl(shmid,IPC_RMID,NULL);
        puts("quit...");

        return 0;

}

shmread.c  

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int main(){

        int shmid;
        char *shmaddr;
        key_t key;
        key=ftok(".",1);
        //int shmget(key_t key, size_t size, int shmflg);
        shmid=shmget(key,1024*4,0);
        if(shmid==-1){
                perror("shmget failed...\n");
                exit(-1);
        }
        shmaddr=shmat(shmid,0,0);
        puts("shmat finished...");
        printf("content: %s\n",shmaddr);
        // int shmdt(const void *shmaddr);
        shmdt(shmaddr);
        puts("quit...");

        return 0;

}

运行结果如👇所示

五、信号(signal) 

signal1.c  

#include <signal.h>
#include <stdio.h>
       //typedef void (*sighandler_t)(int);
       //sighandler_t signal(int signum, sighandler_t handler);

//处理函数:处理接收到的信号 并作出行为
void handler(int signum){
        printf("get signum =%d\n",signum);
        puts("won't be killed...\n");
}

int main(){

        //ctrl+C 就会触发SIGINT 信号
        signal(SIGINT,handler);
        //类似于kill -9 pid  该处的重写不生效
        signal(SIGKILL,handler);
        while(1);
        return 0;
}


                            

SIGKILL  该信号是无法被捕获的,也就是说进程无法执行信号处理程序(handler函数),会直接发送默认行为,也就是直接退出.这也就是为何kill -9 pid一定能杀死程序的原因. 故这也造成了进程被结束前无法清理或者关闭资源等行为,这样是有弊端的

signal2.c

#include <sys/types.h>
#include <signal.h>
#include <stdio.h>


int main(int argc,char**argv){

        int signum;
        int pid;
        signum=atoi(argv[1]);
        pid=atoi(argv[2]);
        printf("signum=%d , pid=%d\n",signum,pid);

        //int kill(pid_t pid, int sig); 发送信号
        kill(pid,signum);
        printf("send signla succeeded~\n");

        return 0;
}

运行结果如👇所示

signal3.c

#include <sys/types.h>
#include <signal.h>
#include <stdio.h>


int main(int argc,char**argv){

        int signum;
        int pid;
        char cmd[24]={0};
        signum=atoi(argv[1]);
        pid=atoi(argv[2]);
        printf("signum=%d , pid=%d\n",signum,pid);
        //将格式化后的字符串输出到str中
        //int sprintf(char *str, const char *format, ...)
        sprintf(cmd,"kill -%d %d",signum,pid);
        system(cmd);
        printf("send signal via system method succeeded~\n");

        return 0;
}

运行结果如👇所示

六、信号量

信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

1、特点

  1.         信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
  2.         信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
  3.         每次对信号量的 PV 操作不仅 限于对信号量值加 1 或减 1,而且可以加减任意正整数。
  4.         支持信号量组。

2、原型

        最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。

        Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。

1 #include <sys/sem.h>
2 // 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
3 int semget(key_t key, int num_sems, int sem_flags);
4 // 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
5 int semop(int semid, struct sembuf semoparray[], size_t numops);  
6 // 控制信号量的相关信息
7 int semctl(int semid, int sem_num, int cmd, ...);

sem.c 

#include <stdio.h>
#include <sys/types.h> 
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <stdlib.h>


//int semop(int semid, struct sembuf *sops, unsigned nsops);
void sem_p(int semid){
	struct sembuf sem;

           /* Code to set semid omitted */

        sem.sem_num = 0;        /* Operate on semaphore 0 */
        sem.sem_op = -1;         /* Wait for value to equal 0 */
        sem.sem_flg = SEM_UNDO;
        semop(semid,&sem,1);
	puts("sem_p...");
}

void sem_v(int semid){
	struct sembuf sem;

           /* Code to set semid omitted */

        sem.sem_num = 0;        /* Operate on semaphore 0 */
        sem.sem_op = 1;         /* Wait for value to equal 0 */
        sem.sem_flg = SEM_UNDO;
        semop(semid,&sem,1);
	puts("sem_v...");
}


  
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 main(){

	key_t key;
	pid_t pid;
	key=ftok(".",24);
	int semid;
      	//int semget(key_t key, int nsems, int semflg);
	semid=semget(key,1,IPC_CREAT|0666); //创建一个信号量
	
	union semun initSem;
    //初始为无锁状态
	initSem.val=0;
 	//int semctl(int semid, int semnum, int cmd, ...);
	semctl(semid,0,SETVAL,initSem);//0代表第一个信号量,SETVAL 设置操作,初始化
	pid=fork();
	if(pid>0){
        //父进程处于等待
		sem_p(semid);
		puts("this is in Father...");
		sem_v(semid);
	}	
	else if(pid ==0){
		puts("this is in Child...");
        //子进程释放锁,父进程方可执行
		sem_v(semid);
	}else{

		perror("fork failed...\n");
		exit(-1);
	}


	return 0;

}

运行结果如👇所示

有点类似多线程的那味~ 初始时,initSem.val=0,即使父进程先执行,也会因为信号量中value状态不是1而等待,直到子进程执行完,释放锁,val=1才执行(父进程)中的代码。       

至此,进程间通信(IPC)的内容告一段落,大致了解了多种方式的使用方法,写了一点小demo加深日后的理解。接下去进行线程的学习。                                                                                           

                                                       2023-09-12  23:00:00   Herman

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值