• Linux系统编程_进程间通信第1天:IPC、无名管道pipe和命名管道mkfifo(半双工)、消息队列msgget(全双工)


    1. 进程间通信概述(427.1)

    在这里插入图片描述

    2. 管道通信原理(428.2)(半双工)

    • 进程间的五种通信方式介绍
      • https://blog.csdn.net/wh_sjc/article/details/70283843
    • 进程间通信(IPC,InterProcess Communication):在不同进程之间传播或交换信息(*面试会问)
      • IPC的方式通常有管道(无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。

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

    2.1 特点

    1. 它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
    2. 它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
    3. 它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

    2.2 原型

    #include 
    int pipe(int fd[2]);    // 返回值:若成功返回0,失败返回-1
    
    • 1
    • 2
    • 当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。如下图:
    • 要关闭管道只需将这两个文件描述符关闭即可。
      在这里插入图片描述

    2.3 例子

    • 单个进程中的管道几乎没有任何用处。所以,通常调用 pipe 的进程接着调用 fork,这样就创建了父进程与子进程之间的 IPC 通道。如下图所示:
    • 若要数据流从父进程流向子进程,则关闭父进程的读端(fd[0])与子进程的写端(fd[1]);反之,则可以使数据流从子进程流向父进程。

    在这里插入图片描述

    #include
    #include
      
    int main(){
        int fd[2];  // 两个文件描述符
        pid_t pid;
        char buff[20];
      
        if(pipe(fd) < 0)  // 创建管道
            printf("Create Pipe Error!\n");
     
        if((pid = fork()) < 0)  // 创建子进程
            printf("Fork Error!\n");
        else if(pid > 0){  // 父进程
    		close(fd[0]);  // 关闭读端
    		write(fd[1],"hello world\n",12);
    	}else{			   //子进程
    		close(fd[1]);  // 关闭写端
    		read(fd[0],buff,20);
    		printf("%s",buff);
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

    3. 管道编程实战(429.3)(无名管道)(管道只能用open 不能fopen)

    • IPC/demo1.c(无名管道,父进程向子进程写入字符串)
    #include 
    #include //标准c库 pipe fork
    #include 
    #include //标准c库 exit
    #include 
    #include //wait
    
    int main(){
    	int fd[2];
    	pid_t pid;
    	char buf[128];
    //	int pipe(int pipefd[2]);
    	if(pipe(fd) == -1){   //创建管道
    		printf("creat pipe failed\n");
    	}
    	pid = fork();         //创建子进程
    	
    	if(pid<0){
    		printf("creat child failed\n");
    	}
    	else if(pid > 0){     //父进程
    		sleep(3);		  //休眠确保子进程先运行,到read时阻塞,3s后父进程往下走
    		printf("this is father\n");
    		close(fd[0]); //关闭读端
    		write(fd[1],"hello from father",strlen("hello form father"));
    		wait(NULL);
    	}else{		      //子进程
    		printf("this is child\n");
    		close(fd[1]); //关闭写端
    		read(fd[0],buf,128);
    		printf("read from father: %s\n",buf);
    		exit(0);
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    在这里插入图片描述

    4. 创建命名管道(4)

    FIFO,也称为命名管道,它是一种文件类型(半双工)

    4.1 特点

    • FIFO 可以在无关的进程之间交换数据,与无名管道不同。
    • FIFO 有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。

    4.2 原型

    #include 
    // 返回值:成功返回0,出错返回-1
    int mkfifo(const char *pathname, mode_t mode);
    
    • 1
    • 2
    • 3
    • 其中的 mode 参数与 open函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。
    • 当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:
      • 若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。
      • 若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。

    4.3 例子

    • FIFO 的通信方式类似于在进程中使用文件来传输数据,只不过 FIFO 类型文件同时具有管道的特性。在数据读出时,FIFO 管道中同时清除数据,并且“先进先出”。下面的例子演示了使用 FIFO 进行 IPC 的过程:
    • IPC/demo2.c(仅创建)
    #include 
    #include //mkfifo的
            
    int main(){
    	//int mkfifo(const char *pathname, mode_t mode);
    	mkfifo("./file",0600);//创建管道,权限为可读可写
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • IPC/demo3.c(简单调试:文件已存在,错误)
    #include 
    #include 
    #include 
           
    int main(){
    	//int mkfifo(const char *pathname, mode_t mode);
    	int ret = mkfifo("./file",0600);
    	if(ret == 0){
    		printf("mkfifo suscceess\n");
    	}
    	if(ret == -1){
    		printf("mkfifo failed\n");
    		perror("why");
    	}		
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

    • IPC/demo4.c(效果同demo3)
    #include 
    #include 
    #include 
    #include
           
    int main(){
    //int mkfifo(const char *pathname, mode_t mode);
    	if( (mkfifo("./file",0600) == -1) && errno==EEXIST){
    		printf("mkfifo failed\n");
    		perror("why");
    	}
    	else{
    		if(errno= =EEXIST){
    			printf("file exists\n");
    		}else
    			printf("mkfifo suscceess\n");
    	}		
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述

    • IPC/demo5.c(创建管道,且只有在错误不是 EEXIST 时才打印错误消息)
    #include 
    #include 
    #include 
    #include
           
    int main(){
    //int mkfifo(const char *pathname, mode_t mode);
    	if( (mkfifo("./file",0600) == -1) && errno!=EEXIST){
    		//只有在错误不是 EEXIST 时才打印错误消息
    		perror("mkfifo failed");
    	}
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    5. 命名管道的数据通信编程实现(430.5)

    • IPC/demo6.c(没有指定O_NONBLOCK(默认),只读 open 会阻塞到某个其他进程为写而打开此 FIFO)
    #include 
    #include 
    #include 
    #include
    #include 
           
    int main(){
    //int mkfifo(const char *pathname, mode_t mode);
    	if( (mkfifo("./file",0600) == -1) && errno!=EEXIST){
    		printf("mkfifo failuer\n");
    		perror("why");
    	}
    
    	int fd = open("./file",O_RDONLY);//若没有指定O_NONBLOCK(默认)
    	printf("open success\n");//只读 open 会阻塞到某个其他进程为写而打开此 FIFO
    	//close(fd);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • IPC/writedemo6.c(没有指定O_NONBLOCK(默认),只写 open 会阻塞到某个其他进程为读而打开它)
    #include 
    #include 
    #include 
    #include
    #include 
    
    int main(){
    	int fd = open("./file",O_WRONLY);//若没有指定O_NONBLOCK(默认)
    	printf("write open success\n");//只写 open 会阻塞到某个其他进程为读而打开它
    	//close(fd);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • IPC/read.c(读5次)
    #include 
    #include 
    #include 
    #include 
    #include  // O_WRONLY open
    #include //strlen
    #include //write
    //int mkfifo(const char *pathname, mode_t mode);
    int main(){
    	char buf[30] = {0};
    	//int nread = 0;
    	int cnt = 0;
    	if( (mkfifo("./file",0600) == -1) && errno!=EEXIST){//创建fifo管道
    		printf("mkfifo failed\n");
    		perror("why");
    	}
    
    	int fd = open("./file",O_RDONLY);
    	printf("open success\n");
    	
    	while(1){
    		int nread = read(fd,buf,30);//读内容
    		printf("read %d byte from fifo,context:%s\n",nread,buf);
    		cnt++;
    		if(cnt == 5){
    			break;	
    		}
    	}
    	close(fd);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • IPC/write.c(写5次)
    #include 
    #include 
    #include 
    #include 
    #include  // O_WRONLY open
    #include //strlen
    #include //write
    //int mkfifo(const char *pathname, mode_t mode);
    int main(){
    	int cnt = 0;
    	char *str = "I'm Message from fifo_write";
    
    	int fd = open("./file",O_WRONLY);
    	printf("write open success\n");
    	
    	while(1){
    		write(fd, str, strlen(str));//写内容
    		cnt++;
    		sleep(1);
    		if(cnt == 5){
    			break;	
    		}
    	}
    	close(fd);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在这里插入图片描述

    6. 消息队列的通信原理(6)(全双工)

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

    特点

    1. 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
    2. 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
    3. 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
      在这里插入图片描述

    7. 消息队列相关api(431.7)

    在这里插入图片描述
    在这里插入图片描述

    原型

    #include 
    // 创建或打开消息队列:成功返回队列ID,失败返回-1
    int msgget(key_t key, int flag);//通过key(非负数 索引值)找到对应消息队列,打开队列的方式//返回值是消息队列的标识符
    // 添加消息:成功返回0,失败返回-1
    int msgsnd(int msqid, const void *ptr, size_t size, int flag);//flag为0是默认阻塞的方式添加消息
    // 读取消息:成功返回消息数据的长度,失败返回-1
    int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);//long type消息类型,用于找到对应消息,flag为0是默认阻塞的方式读取消息
    // 控制消息队列:成功返回0,失败返回-1
    int msgctl(int msqid, int cmd, struct msqid_ds *buf);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 在以下两种情况下,msgget将创建一个新的消息队列:
      • 如果没有与键值 key 相对应的消息队列,并且 flag 中包含了 IPC_CREAT 标志位。
      • key 参数为 IPC_PRIVATE。
    • 函数 msgrcv 在读取消息队列时,type 参数有下面几种情况:
      • type == 0,返回队列中的第一个消息;
      • type > 0,返回队列中消息类型为 type 的第一个消息;
      • type < 0,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。
    • 可以看出,type 值非 0 时用于以非先进先出次序读消息。也可以把 type 看做优先级的权值。(其他的参数解释,请自行Google之)
      在这里插入图片描述

    8. 消息队列编程收发数据(432.8)

    在这里插入图片描述

    • IPC/msgGet.c
    #include 
    #include 
    #include 
    #include 
    #include 
    //       int msgget(key_t key, int msgflg);
    //       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);
    struct msgbuf{
    	long mtype;       /* message type, must be > 0 */
        char mtext[256];    /* message data */
    };
    
    int main(){
    	struct msgbuf readBuf;	
    
    	int msgId = msgget(0x1234,IPC_CREAT|0777);//创建/打开消息队列,通过key找到对应消息队列,可读可写可执行
    	if(msgId == -1 ){
    		printf("get que failed\n");//创建/打开失败
    	}
    	
    	msgrcv(msgId, &readBuf,sizeof(readBuf.mtext),888,0);//读取消息	
    	printf("read from que:%s\n",readBuf.mtext);
    
        struct msgbuf sendBuf = {988,"thank you for reach"};
        msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//添加消息
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • IPC/msgSend.c
    #include 
    #include 
    #include 
    #include 
    #include 
    //       int msgget(key_t key, int msgflg);
    //       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);
    struct msgbuf {
                   
    	long mtype;       /* message type, must be > 0 */
        char mtext[256];    /* message data */
    };
    
    int main(){
    	struct msgbuf sendBuf = {888,"this is message from quen"};	
    	
        int msgId = msgget(0x1234, IPC_CREAT|0777);//创建/打开消息队列,通过key找到对应消息队列,可读可写可执行
    
    	if(msgId == -1 ){
    		printf("get que failed\n");
    	}
    	
    	msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//添加消息
    	printf("send over\n");
    	
    	struct msgbuf readBuf;
        msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),988,0);//读取消息
    	printf("reaturn from get:%s\n",readBuf.mtext);
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    9. 键值生成及消息队列移除(9)

    • https://baike.baidu.com/item/ftok
      在这里插入图片描述
    • IPC/msgGet.c(除了添加了key,其他和8. msgGet.c相同)
    #include 
    #include 
    #include 
    #include 
    #include 
    //       int msgget(key_t key, int msgflg);
    //       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);
    //int msgctl(int msqid, int cmd, struct msqid_ds *buf);
    struct msgbuf{
    	long mtype;       /* message type, must be > 0 */
        char mtext[256];    /* message data */
    };
    
    int main(){
    	struct msgbuf readBuf;	
    
    	key_t key;
    	key = ftok(".",'m');
    	printf("key=%x\n",key);
    
    	int msgId = msgget(key,IPC_CREAT|0777);//创建/打开消息队列,通过key找到对应消息队列,可读可写可执行
    	if(msgId == -1 ){
    		printf("get que failuer\n");
    	}
    	
    	//memset(&readBuf,0,sizeof(struct msgbuf));
    	msgrcv(msgId, &readBuf,sizeof(readBuf.mtext),888,0);//读取消息	
    	printf("read from que:%s\n",readBuf.mtext);
    
        struct msgbuf sendBuf = {988,"thank you for reach"};
        msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//添加消息
    
    	msgctl(msgId,IPC_RMID,NULL);//控制消息队列,将消息队列从内核中删除
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • IPC/msgSend.c(get和send的key要一致)
    #include 
    #include 
    #include 
    #include 
    #include 
    //       int msgget(key_t key, int msgflg);
    //       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);
    //int msgctl(int msqid, int cmd, struct msqid_ds *buf);
    struct msgbuf {
                   
    	long mtype;       /* message type, must be > 0 */
        char mtext[256];    /* message data */
    };
    
    int main(){
    	struct msgbuf sendBuf = {888,"this is message from quen"};	
    		
    	key_t key;
        key = ftok(".",'m');
        printf("key=%x\n",key);
    
        int msgId = msgget(key, IPC_CREAT|0777);//创建/打开消息队列,通过key找到对应消息队列,可读可写可执行
    
    	if(msgId == -1 ){
    		printf("get que failuer\n");
    	}
    	
    	msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//添加消息
    	printf("send over\n");
    	
    	struct msgbuf readBuf;
    	//memset(&readBuf,0,sizeof(struct msgbuf));
        msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),988,0);//读取消息
    	printf("reaturn from get:%s\n",readBuf.mtext);
    	
    	msgctl(msgId,IPC_RMID,NULL);//控制消息队列,将消息队列从内核中删除
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
  • 相关阅读:
    如何设计账户余额的数据准确性?
    0068 IO流
    30个必会python技巧
    SQL基础
    关于蓝牙人员定位的几个重要问题
    无代码开发添加数据入门教程
    Cas 80750-24-9,去硫代生物素 N-羟基琥珀酰亚胺,淡黄色固体
    【信号处理】基于EEG脑电信号的自闭症预测典型方法实现
    如何下载修复xinput1_3.dll并避免常见错误 ,详解多种实用解决方法
    华为云交换数据空间 EDS:“可信、可控、可证”能力实现你的数据你做主
  • 原文地址:https://blog.csdn.net/Jaci133/article/details/133896580