Linux 进程间通信
介绍一下管道,管道是一种特殊的文件,它通过文件描述符来进行访问和操作
管道的读写操作是阻塞式的,如果没有数据可读,读操作会被阻塞,直到有数据可读;如果管道已满,写操作也会被阻塞,直到有空间可写
无名管道
特点:半双工,用于有亲缘关系的两个进程之间
介绍一下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 秒延时看看这个任务是不是执行完了
】
由上面的例程可以看得出来,管道实际上是通过内核中的缓冲区进行数据传输的。当你创建一个管道时,实际上是创建了一个用于数据传输的缓冲区,而这个缓冲区是由操作系统内核来管理的,而不是由用户进程来直接管理。
消息队列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
#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);// 设置消息类型为 1msg.mtype = 1;// 发送消息到消息队列if (msgsnd(msqid, &msg, sizeof(message) - sizeof(long), 0) == -1) {perror("msgsnd");exit(1);}printf("Message sent successfully.\n");return 0;
}
代码分析:
- 逻辑:首先生成IPC键值,然后利用这个键值去创建消息队列,之后利用msgsnd函数去发消息。
msgget
函数用于创建或获取一个消息队列,并返回消息队列的标识符。它会根据给定的键值创建或获取一个消息队列,并可以指定一些标志位和权限参数来控制消息队列的行为和属性。- sizeof(message) - sizeof(long):在消息队列中,通常第一个字段是消息的类型标识符,它通常是一个
long
类型的整数。这个标识符用于区分不同类型的消息。因此,sizeof(message) - sizeof(long)
计算了除去类型标识符所占用的空间之外,消息体的实际长度
未完待续——————————————