• Linux学习笔记9


    Linux 进程间通信

    介绍一下管道,管道是一种特殊的文件,它通过文件描述符来进行访问和操作

    管道的读写操作是阻塞式的,如果没有数据可读,读操作会被阻塞,直到有数据可读;如果管道已满,写操作也会被阻塞,直到有空间可写

    无名管道

    特点:半双工,用于有亲缘关系的两个进程之间

    介绍一下pipe 函数  int pipe(int pipefd[2])
    参数pipefd[0]:用于读管道。
    参数pipefd[1]:用于写管道。

    使用逻辑:先建立一个fd[2]数组,将fd[]传入pipe函数去创建管道,之后创建进程通过返回值判断是子进程还是父进程,如果是子进程像读取数据,则要将fd[0]给close,父进程同理

    1. int main() {
    2. int pfd[2];
    3. char buf[30];
    4. pid_t pid;
    5. // 创建管道
    6. if (pipe(pfd) == -1) {
    7. printf("Error: Failed to create pipe.\n");
    8. return 1;
    9. }
    10. // 创建子进程
    11. pid = fork();
    12. if (pid == -1) {
    13. printf("Error: Failed to create child process.\n");
    14. return 1;
    15. }
    16. if (pid == 0) {
    17. // 子进程从管道中读取数据
    18. close(pfd[1]);
    19. read(pfd[0], buf, sizeof(buf));
    20. printf("Child: Received message: %s\n", buf);
    21. close(pfd[0]);
    22. } else {
    23. // 父进程向管道中写入数据
    24. close(pfd[0]);
    25. write(pfd[1], "Hello, world!", 14);
    26. close(pfd[1]);
    27. }
    28. return 0;
    29. }

    有名管道

    刚刚说的无名管道是适用于有亲缘关系的进程之间的,但是有名管道是可以用于无关系的进程之间的

    有名管道fifo 给文件系统提供一个路径,这个路径和管道关联,只要知道这个管道路径,就可以进行文件访问,fifo 是指先进先出,也就是先写入的数据,先读出来

    使用mkfifo函数创建有名管道,指定一个路径名。格式如下

    int mkfifo(const char *pathname, mode_t mode);
    参数*pathname:路径名,管道名称。
    参数mode:管道的权限。

    例如,mkfifo("/path/to/fifo", 0666)将创建一个路径为/path/to/fifo的有名管道。

    例程如下:

    1. int main() {
    2. const char *fifoPath = "/path/to/fifo";
    3. int fd;
    4. mkfifo(fifoPath, 0666);// 创建有名管道
    5. // 打开管道并进行通信
    6. fd = open(fifoPath, O_WRONLY);
    7. write(fd, "Hello, world!", 14);
    8. close(fd);
    9. fd = open(fifoPath, O_RDONLY);
    10. char buf[15];
    11. read(fd, buf, sizeof(buf));
    12. printf("Received message: %s\n", buf);
    13. close(fd);
    14. // 删除有名管道
    15. unlink(fifoPath);
    16. return 0;
    17. }

    从这个例程可以看出来 :unlink等于删除有名管道,我们先去用open函数去获取 管道 读/写的句柄,之后再执行读写

    有名管道的例程2 writepipe.c

    1. int main()
    2. {
    3. const char *fifo_name="my_fifo";
    4. char *file1="data.txt";
    5. int pipe_fd=-1;
    6. int data_fd=-1;
    7. int res=0;
    8. const int open_mode=O_WRONLY;
    9. int bytes_sent=0;
    10. char buffer[PIPE_BUF+1];
    11. if(access(fifo_name,F_OK)==-1)
    12. {
    13. res=mkfifo(fifo_name,0777);
    14. if(res !=0)
    15. {
    16. fprintf(stderr,"Could not create fifo %s\n",fifo_name);
    17. exit(EXIT_FAILURE);
    18. }
    19. }
    20. printf("Process %d opening FIFO O_WRONLY\n",getpid());
    21. pipe_fd=open(fifo_name,open_mode);
    22. data_fd=open(fifo1,O_RDONLY);
    23. printf("Process %d RESULT %d\n",getpid(),pipe_fd);
    24. if(pipe_fd != -1){
    25. int bytes_read=0;
    26. bytes_read=read(data_fd,buffer,PIPE_BUF);
    27. buffer[bytes_read]="\0";
    28. while(bytes_read>0)
    29. {res = write(pipe_fd,buffer, bytes_read);
    30. if(res == -1)
    31. {fprintf(stderr,"Write error on pipe\n");exit(EXIT_FAILURE);}
    32. //累加写的字节数,并继续读取数据
    33. bytes_sent += res;
    34. bytes_read =read(data_fd,buffer,PIPE_BUF);
    35. buffer[bytes_read]='\0';
    36. }
    37. close(pipe_fd);
    38. close(data_fd);
    39. }
    40. else
    41. exit(EXIT_FAILURE);
    42. printf("finish");
    43. exit(EXIT_SUCCESS);}

    代码思路:这段程序的作用是将一个数据文件(data.txt)的内容写入到一个FIFO文件(my_fifo)中

    首先if语句里是常用的检查函数access,用于检查当前进程对文件的访问权限,其声明一般是      int access(const char *path, int mode);               我们代码里的mode是“F_OK”表示文件是否存在,如果管道文件不存在,则新建管道文件

    之后分别用open函数去返回pipe和data的句柄,方便后续读写使用

    如果管道句柄存在,利用read函数将会把data文件里的内容以PIPE_BUF形式读到缓冲区,因为read返回的是读取数据的数量,所以我们可以在读到buffer里的最后一位处赋值“\0”表示结束

    后面的同理,注意PIPE_BUF 是一个宏,表示系统中管道(或命名管道)的缓冲区大小的上限

    之后编译运行测试。

    一个小tips:在Shell命令中,& 符号表示将一个进程放入后台运行。具体来说,当你在终端中执行一个命令时,如果你在命令的末尾加上 & 符号,这个命令所启动的进程就会在后台运行,而不会阻塞当前的终端会话

    可以用Linux命令“job”去查看运行的任务,使用linux 命令“jobs;sleep 5;jobs”可以看着5 秒延时看看这个任务是不是执行完了

    由上面的例程可以看得出来,管道实际上是通过内核中的缓冲区进行数据传输的。当你创建一个管道时,实际上是创建了一个用于数据传输的缓冲区,而这个缓冲区是由操作系统内核来管理的,而不是由用户进程来直接管理。

    消息队列msg

    消息队列是一种进程间通信的机制,用于在不同的进程之间传递数据。消息队列允许一个进程向另一个进程发送数据,而无需直接共享内存或使用文件等外部数据存储方式。

    在消息队列中,数据被组织成消息,每个消息都有一个标识符和一个内容。进程可以通过指定标识符来发送和接收特定类型的消息。通常,消息队列是先进先出(FIFO)的,即最先发送的消息会最先被接收。

    这些消息可以被异步地发送和接收,即发送进程可以继续执行而不必等待接收进程处理消息

    消息队列主要有两个函数msgrcv 和msgsnd,一个接收一个发送。

    函数int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
    参数msqid:消息队列的标识码。
    参数*msgp:指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用
    户可定义的通用结构。
    参数msgsz:消息的长短。
    参数msgflg:标志位。

    函数ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
    参数msqid:消息队列的标识码。
    参数*msgp:指向消息缓冲区的指针。
    参数msgsz:消息的长短

    我们还需要了解一下下面这些知识:

    1.key 参数是一个键值,用于唯一地标识一个消息队列,这个键可以是一个由程序员指定的常量值,也可以是通过调用 ftok 函数生成的 IPC 键值。

    2.在使用System V IPC机制(如消息队列、信号量和共享内存)时,有一个重要的概念就是 IPC 键(IPC Key)。IPC键Inter-Process Communication Key)是通过ftok函数生成的唯一标识符,用于在进程间通信中标识特定的资源,比如消息队列

    IPC键是用于标识和访问IPC资源的一种机制,它使得不同的进程可以通过共享的IPC键来访问同一个IPC资源,从而实现进程间的通信和共享数据。

    3.ftok 函数是用于生成一个唯一的 IPC 键值的函数

    定义:  key_t ftok(const char *pathname, int proj_id);

    ftok 函数接受两个参数:

    ①pathname:一个指向路径名的指针,它是用于生成 IPC 键值的文件的路径。

    ②proj_id:一个整数,用作项目标识符。

    例如:key = ftok(".", 'a');————这里的 pathname 参数是 “.”,表示当前目录;参数是 'a',是一个用户定义的项目标识符

    【注释:这个“proj_id”参数有一些注意事项,ftok函数会根据根据我们输入的项目标识符去生成IPC值,所以我们要注意尽量用不同的proj_id参数去标识】

    例程msgsend

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <sys/types.h>
    4. #include <sys/ipc.h>
    5. #include <sys/msg.h>
    6. #define MSGSZ 128
    7. // 定义消息结构体
    8. typedef struct msgbuf {
    9. long mtype;
    10. char mtext[MSGSZ];
    11. } message;
    12. int main() {
    13. int msqid;
    14. key_t key;
    15. message msg;
    16. // 生成唯一的 IPC 键值
    17. key = ftok(".", 'a');
    18. if (key == -1) {
    19. perror("ftok");
    20. exit(1);
    21. }
    22. // 创建或获取消息队列
    23. msqid = msgget(key, IPC_CREAT | 0666);
    24. if (msqid == -1) {
    25. perror("msgget");
    26. exit(1);
    27. }
    28. printf("Enter message to send: ");
    29. fgets(msg.mtext, MSGSZ, stdin);
    30. // 设置消息类型为 1
    31. msg.mtype = 1;
    32. // 发送消息到消息队列
    33. if (msgsnd(msqid, &msg, sizeof(message) - sizeof(long), 0) == -1) {
    34. perror("msgsnd");
    35. exit(1);
    36. }
    37. printf("Message sent successfully.\n");
    38. return 0;
    39. }

    代码分析:

    • 逻辑:首先生成IPC键值,然后利用这个键值去创建消息队列,之后利用msgsnd函数去发消息。
    • msgget 函数用于创建或获取一个消息队列,并返回消息队列的标识符。它会根据给定的键值创建或获取一个消息队列,并可以指定一些标志位和权限参数来控制消息队列的行为和属性。
    • sizeof(message) - sizeof(long):在消息队列中,通常第一个字段是消息的类型标识符,它通常是一个 long 类型的整数。这个标识符用于区分不同类型的消息。因此,sizeof(message) - sizeof(long) 计算了除去类型标识符所占用的空间之外,消息体的实际长度

    未完待续——————————————

  • 相关阅读:
    Python爬虫实战-批量爬取下载网易云音乐
    51.Python-web框架-Django开始第一个应用的增删改查
    离散化(超详细)
    解决ios17无法复制的问题
    未来的户外LED视频墙将怎么发展
    C++ vector使用方法
    windows打包软件-Inno Setup
    Python机器视觉--OpenCV入门--视频的加载,录制
    Vue.js+Node.js全栈开发教程:Vue.js数据冻结
    Python 中的闭包和自由变量
  • 原文地址:https://blog.csdn.net/weixin_62514989/article/details/139446764