介绍一下管道,管道是一种特殊的文件,它通过文件描述符来进行访问和操作
管道的读写操作是阻塞式的,如果没有数据可读,读操作会被阻塞,直到有数据可读;如果管道已满,写操作也会被阻塞,直到有空间可写
特点:半双工,用于有亲缘关系的两个进程之间
介绍一下pipe 函数 int pipe(int pipefd[2])
参数pipefd[0]:用于读管道。
参数pipefd[1]:用于写管道。
使用逻辑:先建立一个fd[2]数组,将fd[]传入pipe函数去创建管道,之后创建进程通过返回值判断是子进程还是父进程,如果是子进程像读取数据,则要将fd[0]给close,父进程同理
- int main() {
- int pfd[2];
- char buf[30];
- pid_t pid;
-
- // 创建管道
- if (pipe(pfd) == -1) {
- printf("Error: Failed to create pipe.\n");
- return 1;
- }
-
- // 创建子进程
- pid = fork();
-
- if (pid == -1) {
- printf("Error: Failed to create child process.\n");
- return 1;
- }
-
- if (pid == 0) {
- // 子进程从管道中读取数据
- close(pfd[1]);
- read(pfd[0], buf, sizeof(buf));
- printf("Child: Received message: %s\n", buf);
- close(pfd[0]);
- } else {
- // 父进程向管道中写入数据
- close(pfd[0]);
- write(pfd[1], "Hello, world!", 14);
- close(pfd[1]);
- }
-
- return 0;
- }
刚刚说的无名管道是适用于有亲缘关系的进程之间的,但是有名管道是可以用于无关系的进程之间的
有名管道fifo 给文件系统提供一个路径,这个路径和管道关联,只要知道这个管道路径,就可以进行文件访问,fifo 是指先进先出,也就是先写入的数据,先读出来
使用mkfifo
函数创建有名管道,指定一个路径名。格式如下
int mkfifo(const char *pathname, mode_t mode);
参数*pathname:路径名,管道名称。
参数mode:管道的权限。
例如,mkfifo("/path/to/fifo", 0666)
将创建一个路径为/path/to/fifo
的有名管道。
例程如下:
- int main() {
- const char *fifoPath = "/path/to/fifo";
- int fd;
- mkfifo(fifoPath, 0666);// 创建有名管道
- // 打开管道并进行通信
- fd = open(fifoPath, O_WRONLY);
- write(fd, "Hello, world!", 14);
- close(fd);
- fd = open(fifoPath, O_RDONLY);
- char buf[15];
- read(fd, buf, sizeof(buf));
- printf("Received message: %s\n", buf);
- close(fd);
- // 删除有名管道
- unlink(fifoPath);
- return 0;
- }
从这个例程可以看出来 :unlink等于删除有名管道,我们先去用open函数去获取 管道 读/写的句柄,之后再执行读写
有名管道的例程2 writepipe.c
- int main()
- {
- const char *fifo_name="my_fifo";
- char *file1="data.txt";
- int pipe_fd=-1;
- int data_fd=-1;
- int res=0;
- const int open_mode=O_WRONLY;
- int bytes_sent=0;
- char buffer[PIPE_BUF+1];
- if(access(fifo_name,F_OK)==-1)
- {
- res=mkfifo(fifo_name,0777);
- if(res !=0)
- {
- fprintf(stderr,"Could not create fifo %s\n",fifo_name);
- exit(EXIT_FAILURE);
- }
- }
- printf("Process %d opening FIFO O_WRONLY\n",getpid());
- pipe_fd=open(fifo_name,open_mode);
- data_fd=open(fifo1,O_RDONLY);
- printf("Process %d RESULT %d\n",getpid(),pipe_fd);
-
- if(pipe_fd != -1){
- int bytes_read=0;
- bytes_read=read(data_fd,buffer,PIPE_BUF);
- buffer[bytes_read]="\0";
- while(bytes_read>0)
- {res = write(pipe_fd,buffer, bytes_read);
- if(res == -1)
- {fprintf(stderr,"Write error on pipe\n");exit(EXIT_FAILURE);}
- //累加写的字节数,并继续读取数据
- bytes_sent += res;
- bytes_read =read(data_fd,buffer,PIPE_BUF);
- buffer[bytes_read]='\0';
- }
- close(pipe_fd);
- close(data_fd);
- }
- else
- exit(EXIT_FAILURE);
- printf("finish");
- 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 秒延时看看这个任务是不是执行完了
】
由上面的例程可以看得出来,管道实际上是通过内核中的缓冲区进行数据传输的。当你创建一个管道时,实际上是创建了一个用于数据传输的缓冲区,而这个缓冲区是由操作系统内核来管理的,而不是由用户进程来直接管理。
消息队列是一种进程间通信的机制,用于在不同的进程之间传递数据。消息队列允许一个进程向另一个进程发送数据,而无需直接共享内存或使用文件等外部数据存储方式。
在消息队列中,数据被组织成消息,每个消息都有一个标识符和一个内容。进程可以通过指定标识符来发送和接收特定类型的消息。通常,消息队列是先进先出(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
- #include <stdio.h>
- #include <stdlib.h>
- #include <sys/types.h>
- #include <sys/ipc.h>
- #include <sys/msg.h>
-
- #define MSGSZ 128
-
- // 定义消息结构体
- typedef struct msgbuf {
- long mtype;
- char mtext[MSGSZ];
- } message;
-
- int main() {
- int msqid;
- key_t key;
- message msg;
-
- // 生成唯一的 IPC 键值
- key = ftok(".", 'a');
- if (key == -1) {
- perror("ftok");
- exit(1);
- }
-
- // 创建或获取消息队列
- msqid = msgget(key, IPC_CREAT | 0666);
- if (msqid == -1) {
- perror("msgget");
- exit(1);
- }
-
- printf("Enter message to send: ");
- fgets(msg.mtext, MSGSZ, stdin);
-
- // 设置消息类型为 1
- msg.mtype = 1;
-
- // 发送消息到消息队列
- if (msgsnd(msqid, &msg, sizeof(message) - sizeof(long), 0) == -1) {
- perror("msgsnd");
- exit(1);
- }
-
- printf("Message sent successfully.\n");
-
- return 0;
- }
代码分析:
msgget
函数用于创建或获取一个消息队列,并返回消息队列的标识符。它会根据给定的键值创建或获取一个消息队列,并可以指定一些标志位和权限参数来控制消息队列的行为和属性。long
类型的整数。这个标识符用于区分不同类型的消息。因此,sizeof(message) - sizeof(long)
计算了除去类型标识符所占用的空间之外,消息体的实际长度未完待续——————————————