欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 资讯 > linux:进程间的通信

linux:进程间的通信

2024/12/23 10:48:34 来源:https://blog.csdn.net/T66656/article/details/141459777  浏览:    关键词:linux:进程间的通信

 IPC   进程间通信  

   IPC(Interprocess Communication,进程间通信)是指在不同进程之间传播或交换信息的一种机制。它允许多个进程在同一操作系统中协同工作,实现数据的共享和同步。IPC主要包括三大类通信方式:古老的通信方式、IPC对象通信以及socket通信。

1. 古老的通信方式

  • 无名管道(PIPE)
    • 是UNIX系统IPC最古老的形式,半双工,即数据只能在一个方向上流动。
    • 它具有固定的读端和写端,通常用于父子进程或兄弟进程之间的通信。
    • 可以看成是一种特殊的文件,对其读写使用普通的read、write函数,但它只存在于内存中,不属于任何文件系统。
  • 有名管道(Named PIPE)
    • 也称为FIFO(First In First Out),它克服了无名管道只能在亲缘关系进程间通信的限制。
    • 有名管道以文件形式存在于文件系统中,有路径名与之关联,可以在无关的进程之间交换数据。
  • 信号(Signal)
    • 是Unix系统中使用的最古老的进程间通信方法之一。
    • 它是异步的,用于通知进程发生了某种事件。
    • 操作系统通过信号来通知进程系统中发生了某种预先规定好的事件,也是用户进程之间通信和同步的一种原始机制。

2. IPC对象通信

  • 消息队列(Message Queue)
    • 消息队列提供了一种从一个进程向另一个进程发送数据块的方法。
    • 每个数据块都有一个类型,接收者进程可以根据类型选择性地接收数据。
    • 消息队列存在于内核中,由消息队列标识符来标识。
  • 共享内存(Shared Memory)
    • 允许两个或多个不相关的进程访问同一块内存区域。
    • 它是所有IPC方式中速度最快的一种,因为数据不需要在客户机和服务器之间复制。
    • 通常需要使用某种形式的同步机制(如信号量)来避免竞态条件。
  • 信号量集(Semaphore Set)
    • 主要用于控制多个进程对共享资源的访问。
    • 它是一种计数器,用于协调多个进程之间的同步。
    • 信号量可以被设置为正数或零,表示可用资源的数量。

3. Socket通信

  • 网络通信
    • Socket是一种特殊的文件描述符,用于表示两个进程间的网络连接(包括TCP和UDP协议)。
    • 它不仅可以在同一台机器上的不同进程间通信,还可以跨网络进行通信。
    • Socket通信包括创建socket、绑定地址和端口、监听连接、接受连接、数据收发以及关闭连接等步骤。

管道

   管道的特性:
    1、管道是 半双工的工作模式(但我们当作单工来使用)
    2、所有的管道都是特殊的文件不支持定位操作。lseek->> fd  fseek ->>FILE* 
    3、管道是特殊文件,读写使用文件IO。fgets,fread,fgetc,
    open,read,write,close;
    
    1,读端存在,一直向管道中去写,超过64k,写会阻塞。
    2,写端是存在的,读管道,如果管道为空的话,读会阻塞。
    
    3.管道破裂:读端关闭,还继续写管道。
    4. read 0 :写端关闭,如果管道没有内容,读到0即:read 0 ;代表通信结束

使用框架:
    创建管道 ==》读写管道 ==》关闭管道

无名管道

1、无名管道 ===》管道的特例 ===>pipe函数
    特性:
    1.1  亲缘关系进程使用
    1.2  有固定的读写端

    流程:
    创建并打开管道: pipe函数
#include <unistd.h>     头文件
int pipe(int pipefd[2]);
功能:创建并打开一个无名管道
参数:pipefd[0] ==>无名管道的固定读端
      pipefd[1] ==>无名管道的固定写端
返回值:成功 0
        失败 -1;

注意事项:
    1、无名管道的架设应该在fork之前进行。
    
无名管道的读写:===》文件IO的读写方式。
    读: read()
    写: write()

关闭管道: close();
pipe 函数的参数是一个整型数组(通常是两个元素的数组),用于存储由系统分配的两个文件描述符。如果调用成功,pipe 函数会将这两个文件描述符存储在提供的数组中,并将数组的第一个元素(pipefd[0])设置为管道的读端,将数组的第二个元素(pipefd[1])设置为管道的写端。然后,pipe 函数返回0以表示成功。如果调用失败,则返回-1,并设置全局变量errno以指示错误原因。

验证: read 0 :写端关闭,如果管道没有内容,读到0即:read 0 ;代表通信结束

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>              /* Obtain O_* constant definitions */
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{int pipefd[2]={0};int ret = pipe(pipefd);if(-1 == ret){perror("pipe");return 1;}pid_t pid = fork();if(pid>0){//需要决定读写方向close(pipefd[0]);sleep(3);char buf[50]="hello,child";write(pipefd[1],buf,strlen(buf));}else if(0 == pid){close(pipefd[1]);while(1){char buf[50]={0};int rd_ret = read(pipefd[0],buf,sizeof(buf)); //后台进程if(rd_ret<=0){break;}printf("father :%s\n",buf);}printf("ipc end\n");}else {perror("fork");return 1;}return 0;
}

这段代码中只创建了一个管道。管道是通过pipe()函数创建的,它返回一个包含两个文件描述符的数组:pipefd[0]pipefd[1]。这两个文件描述符分别代表管道的读端和写端。

  • pipefd[0]:管道的读端,用于读取数据。
  • pipefd[1]:管道的写端,用于向管道写入数据。

在代码中,fork()调用创建了一个新的子进程。子进程和父进程都会继承这个管道的文件描述符。但是,根据fork()的返回值,父进程和子进程分别关闭了它们不需要的管道端:

  • 父进程(pid > 0):关闭了读端pipefd[0],保留了写端pipefd[1],用于向管道写入数据。
  • 子进程(pid == 0):关闭了写端pipefd[1],保留了读端pipefd[0],用于从管道读取数据。

父进程在等待3秒后向管道写入字符串"hello,child",然后子进程不断地从管道读取数据,直到没有更多的数据可读。

因此,虽然父进程和子进程都有管道的文件描述符,但它们操作的是同一个管道的两端。整个程序中只存在一个管道,而不是两个独立的管道。

验证如下问题:
1、父子进程是否都有fd[0] fd[1],
   如果在单一进程中写fd[1]能否直接从fd[0]中读到。

   可以,写fd[1]可以从fd[0]读

2、管道的数据存储方式是什么样的
   数据是否一直保留?
    栈, 先进后出
   队列形式存储 读数据会剪切取走数据不会保留
   先进先出

3、管道的数据容量是多少,有没有上限值。
    操作系统的建议值: 512* 8 = 4k
    代码测试实际值:   65536byte= 64k

4、管道的同步效果如何验证?读写同步验证。
    读端关闭能不能写? 不可以 ===>SIGPIPE 异常终止 
    写端关闭能不能读? 可以,取决于pipe有没有内容,===>read返回值为0 不阻塞

    结论:读写端必须同时存在,才能进行
          管道的读写。
5、固定的读写端是否就不能互换?
    能否写fd[0] 能否读fd[1]?   不可以,是固定读写端。

练习:在父子进程间传输文件内容。父进程读取/home/linux/1.png文件的内容,并通过管道发送给子进程。子进程则接收这些内容,并将其写入到2.png文件中。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>int main(int argc, char *argv[])
{int pipefd[2]={0};int ret = pipe(pipefd);if(-1 == ret){perror("pipe");return 1;}pid_t pid = fork();if(pid>0){close(pipefd[0]);int fd = open("/home/linux/1.png",O_RDONLY);if(-1 == fd){perror("father open");exit(1);}while(1){char buf[4096]={0};int rd_ret = read(fd,buf,sizeof(buf));if(rd_ret<=0){break;}write(pipefd[1],buf,rd_ret);}close(fd); close(pipefd[1]);}else if(0 == pid){close(pipefd[1]);int fd = open("2.png",O_WRONLY|O_TRUNC|O_CREAT,0666);if(-1 == fd){perror("child open");exit(1);}while(1) {char buf[4096]={0};int rd_ret = read(pipefd[0],buf,sizeof(buf));if(rd_ret<=0){break;}write(fd,buf,rd_ret);}close(fd);close(pipefd[0]);}else {perror("fork");return 1;}return 0;
}

有名管道

有名管道===》fifo ==》有文件名称的管道。
                      文件系统中可见

框架:
    创建有名管道 ==》打开有名管道 ==》读写管道
    ==》关闭管道  ==》卸载有名管道

1、创建:mkfifo
#include <sys/types.h>
#include <sys/stat.h>
 remove();

int mkfifo(const char *pathname, mode_t mode);
功能:在指定的pathname路径+名称下创建一个权限为
      mode的有名管道文件。
参数:pathname要创建的有名管道路径+名称
      mode  8进制文件权限。
返回值:成功 0
        失败  -1;

2、打开有名管道 open
    注意:该函数使用的时候要注意打开方式,
    因为管道是半双工模式,所有打开方式直接决定
    当前进程的读写方式。
    一般只有如下方式:
    int fd-read = open("./fifo",O_RDONLY); ==>fd 是固定读端
    int fd-write = open("./fifo",O_WRONLY); ==>fd 是固定写端
    不能是 O_RDWR 方式打开文件。
    不能有 O_CREAT 选项,因为创建管道有指定的mkfifo函数


3、管道的读写: 文件IO

    读: read(fd-read,buff,sizeof(buff));
    写: write(fd-write,buff,sizeof(buff));

4、关闭管道:
        close(fd);

5、卸载管道:remove();
        int unlink(const char *pathname);
        功能:将指定的pathname管道文件卸载,同时
              从文件系统中删除。
        参数: ptahtname 要卸载的有名管道 
        返回值:成功 0
                失败  -1;
练习:1.实现fifo a,b 进程 实时聊天。可以连续收发。#quit,双方都退出。

写端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>void* th1(void* arg)
{int  fd =*(int*)arg; while(1) {printf("to B:");char buf[128]={0};fgets(buf,sizeof(buf),stdin);//#quitif(0 == strcmp(buf,"#quit\n")){exit(0);}write(fd,buf,strlen(buf));}return NULL;}
void* th2(void* arg)
{int  fd =*(int*)arg; while(1) {char buf[128]={0};int ret = read(fd,buf,sizeof(buf));if(0==strcmp(buf,"#quit\n") || ret<=0){exit(0);}printf("from B:%s",buf);fflush(stdout);}return NULL;}int main(int argc, char *argv[])
{int ret = mkfifo("myfifo1",0666);if(-1 == ret){if( EEXIST!= errno ){perror("mkfifo");return 1;}}ret = mkfifo("myfifo2",0666);if(-1 == ret){if( EEXIST!= errno ){perror("mkfifo");return 1;}}int fd_w = open("myfifo1",O_WRONLY);if(-1 == fd_w){perror("open");return 1;}int fd_r = open("myfifo2",O_RDONLY);if(-1 == fd_r){perror("open");return 1;}pthread_t tid1,tid2;pthread_create(&tid1,NULL,th1,&fd_w);pthread_create(&tid2,NULL,th2,&fd_r);pthread_join(tid1,NULL);pthread_join(tid2,NULL);return 0;
}

读端: 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>void* th1(void* arg)
{int  fd =*(int*)arg; while(1) {printf("to A:");char buf[128]={0};fgets(buf,sizeof(buf),stdin);//#quitif(0 == strcmp(buf,"#quit\n")){exit(0);}write(fd,buf,strlen(buf));}return NULL;}
void* th2(void* arg)
{int  fd =*(int*)arg; while(1) {char buf[128]={0};int ret = read(fd,buf,sizeof(buf));if(0==strcmp(buf,"#quit\n") || ret<=0){exit(0);}printf("from A:%s",buf);fflush(stdout);}return NULL;}int main(int argc, char *argv[])
{int ret = mkfifo("myfifo1",0666);if(-1 == ret){if( EEXIST!= errno ){perror("mkfifo");return 1;}}ret = mkfifo("myfifo2",0666);if(-1 == ret){if( EEXIST!= errno ){perror("mkfifo");return 1;}}int fd_r = open("myfifo1",O_RDONLY);if(-1 == fd_r){perror("open");return 1;}int fd_w = open("myfifo2",O_WRONLY);if(-1 == fd_w){perror("open");return 1;}pthread_t tid1,tid2;pthread_create(&tid1,NULL,th1,&fd_w);pthread_create(&tid2,NULL,th2,&fd_r);pthread_join(tid1,NULL);pthread_join(tid2,NULL);return 0;
}

 多个有名管道,开的顺序要一致;

 

练习2:实现pipe实现查字典。 1.可以循环查找 2.找到显示意思,没找到显示没找到 3.#quit 都退出。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>              /* Obtain O_* constant definitions */
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define MAX 19662
int main(int argc, char *argv[])
{int pipefd[2]={0};int ret = pipe(pipefd);if(-1 == ret){perror("pipe");return 1;}pid_t pid = fork();if(pid>0){close(pipefd[0]);int fd = open("/home/linux/dict.txt",O_RDONLY);if(-1 == fd){perror("open");exit(1);}while(1){char buf[4096]={0};int rd_ret = read(fd,buf,sizeof(buf));if(rd_ret==0){lseek(fd,0,SEEK_SET);continue;}write(pipefd[1],buf,rd_ret);}}else if(0 == pid){close(pipefd[1]);FILE*fp = fdopen(pipefd[0],"r");while(1){char want_word[50]={0};printf("want word:");fgets(want_word,sizeof(want_word),stdin);want_word[strlen(want_word)-1]='\0';if(0 == strcmp(want_word,"#quit")){break;}int num = 0;while(1){char linebuf[512]={0};fgets(linebuf,sizeof(linebuf),fp);char* arg[2]={NULL};arg[0]=strtok(linebuf," ");arg[1]=strtok(NULL,"\r");if(0 == strcmp(want_word,arg[0])){printf("%s:%s\n",arg[0],arg[1]);break;}num++;if(num>=MAX){printf("cant find %s\n",want_word);break;}}}}else {perror("fork");return 1;}return 0;
}

代码中用管道破裂使父进程结束

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com