📜  五级管道的各种说明(1)

📅  最后修改于: 2023-12-03 15:06:21.276000             🧑  作者: Mango

五级管道的各种说明

五级管道是指在Unix系统中用于输入和输出数据的一种数据传输方式。它可以将一个程序的输出作为另一个程序的输入,从而实现两个程序之间的数据传递。在本文中,我们将介绍五种不同类型的五级管道,它们分别是普通管道、有名管道、环形缓冲区、消息队列和套接字。

普通管道

普通管道也称为匿名管道,是最常用的五级管道类型。它是一种单向的、半双工的数据传输方式,具有以下特点:

  • 只能在两个进程之间传递数据,不能在同一个进程中使用。
  • 基于先进先出的队列结构,保证数据传输的有序性。
  • 只能传递无格式的字节流数据,不能传递具有结构化的数据。

使用普通管道需要调用系统调用pipe()函数,该函数在调用成功时会返回两个文件描述符,分别表示管道的读端和写端。具体使用方法可以参考以下代码片段:

int fd[2];  // 用于保存管道的读端和写端
char buf[1024];  // 用于保存从管道中读取的数据
int ret = pipe(fd);  // 创建管道
if (ret == -1) {
    perror("pipe");
    exit(EXIT_FAILURE);
}
if (fork() == 0) {
    // 子进程
    close(fd[0]);  // 关闭管道的读端
    write(fd[1], "hello world", strlen("hello world"));  // 向管道中写入数据
    close(fd[1]);  // 关闭管道的写端
    exit(EXIT_SUCCESS);
} else {
    // 父进程
    close(fd[1]);  // 关闭管道的写端
    read(fd[0], buf, sizeof(buf));  // 从管道中读取数据
    printf("received data from pipe: %s\n", buf);
    close(fd[0]);  // 关闭管道的读端
    wait(NULL);  // 等待子进程退出
}
有名管道

有名管道也称为FIFO,是一种特殊的管道类型,具有以下特点:

  • 可以在不同的进程之间传递数据,也可以在同一个进程中使用。
  • 基于文件系统的名字空间,可以像普通文件一样使用。
  • 可以传递任意类型的数据,包括结构体和对象。

使用有名管道需要调用系统调用mkfifo()函数,在调用成功时会在文件系统中创建一个FIFO文件。打开FIFO文件时,需要使用标志位O_RDONLYO_WRONLYO_RDWR,表示读、写或读写模式。具体使用方法可以参考以下代码片段:

const char* fifo_path = "/tmp/myfifo";  // 用于保存FIFO文件的路径
char buf[1024];  // 用于保存从FIFO中读取的数据
int ret = mkfifo(fifo_path, 0666);  // 创建FIFO文件
if (ret == -1 && errno != EEXIST) {
    perror("mkfifo");
    exit(EXIT_FAILURE);
}
int fd = open(fifo_path, O_RDONLY);  // 打开FIFO文件,并以只读模式读取数据
if (fd == -1) {
    perror("open");
    exit(EXIT_FAILURE);
}
read(fd, buf, sizeof(buf));  // 从FIFO中读取数据
printf("received data from fifo: %s\n", buf);
close(fd);  // 关闭FIFO文件
环形缓冲区

环形缓冲区是一种基于共享内存的数据传输方式,它具有以下特点:

  • 可以在不同的进程之间传递数据,也可以在同一个进程中使用。
  • 基于环形的队列结构,可以通过循环指针来实现数据的读写操作。
  • 可以传递字节流和结构化的数据,但需要考虑字节对齐和内存对齐的问题。

由于环形缓冲区不是标准的Unix五级管道类型,因此需要使用类似于POSIX共享内存的API来实现。具体使用方法可以参考以下代码片段:

typedef struct {
    int head;
    int tail;
    char buf[1024];
} CircularBuffer;

int fd = shm_open("/myshm", O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);  // 创建共享内存
ftruncate(fd, sizeof(CircularBuffer));  // 设置共享内存大小
CircularBuffer* cb = (CircularBuffer*)mmap(NULL, sizeof(CircularBuffer), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);  // 映射共享内存
close(fd);  // 关闭共享内存文件引用
cb->head = 0;
cb->tail = 0;
strcpy(cb->buf, "hello world");  // 向环形缓冲区中写入数据
cb->head += strlen("hello world");  // 更新头指针
char buf[1024];
int len = cb->tail - cb->head;  // 计算待读取数据的长度
memcpy(buf, cb->buf + cb->head, len);  // 从环形缓冲区中读取数据
printf("received data from shared memory: %s\n", buf);
cb->tail += len;  // 更新尾指针
munmap(cb, sizeof(CircularBuffer));  // 解除共享内存的映射
shm_unlink("/myshm");  // 删除共享内存
消息队列

消息队列是一种基于消息的数据传输方式,它具有以下特点:

  • 可以在不同的进程之间传递数据,但不能在同一个进程中使用。
  • 基于一对多的通信模式,可以通过消息队列ID来访问消息队列。
  • 可以传递任意类型的数据,但需要提前定义消息体的格式。

使用消息队列需要调用系统调用msgget()函数来创建或打开消息队列,调用msgsnd()函数向消息队列中写入数据,调用msgrcv()函数从消息队列中读取数据,调用msgctl()函数进行消息队列的控制操作。具体使用方法可以参考以下代码片段:

typedef struct {
    long mtype;  // 消息类型(必须大于0)
    char mtext[1024];  // 消息内容(可以为任意类型的数据)
} Message;

key_t key = ftok("/tmp", 'A');  // 用于生成消息队列的key
int msqid = msgget(key, IPC_CREAT|IPC_EXCL|0666);  // 创建消息队列
if (msqid == -1 && errno == EEXIST) {
    msqid = msgget(key, 0666);  // 打开已经存在的消息队列
}
if (msqid == -1) {
    perror("msgget");
    exit(EXIT_FAILURE);
}
Message msg;
msg.mtype = 1;  // 消息类型必须大于0
strcpy(msg.mtext, "hello world");  // 设置消息内容
if (msgsnd(msqid, (void*)&msg, sizeof(Message)-sizeof(long), 0) == -1) {  // 向消息队列中写入数据
    perror("msgsnd");
    exit(EXIT_FAILURE);
}
if (msgrcv(msqid, (void*)&msg, sizeof(Message)-sizeof(long), 1, 0) == -1) {  // 从消息队列中读取数据
    perror("msgrcv");
    exit(EXIT_FAILURE);
}
printf("received data from message queue: %s\n", msg.mtext);
msgctl(msqid, IPC_RMID, NULL);  // 删除消息队列
套接字

套接字是一种抽象的接口,用于将Unix五级管道拓展到网络通信中,它具有以下特点:

  • 可以在不同的主机之间传递数据。
  • 基于通用的Socket接口,可以通过TCP或UDP协议进行通信。
  • 可以传递任意类型的数据,但需要考虑网络字节序和数据大小端问题。

使用套接字需要调用系统调用socket()函数创建Socket,调用bind()函数将Socket绑定到指定的IP地址和端口号,调用listen()函数监听Socket,调用accept()函数接受客户端的连接请求,调用connect()函数主动发起连接请求,调用send()函数向对端发送数据,调用recv()函数从对端接收数据,调用close()函数关闭套接字。具体使用方法可以参考以下代码片段:

int sockfd = socket(AF_INET, SOCK_STREAM, 0);  // 创建Socket
if (sockfd == -1) {
    perror("socket");
    exit(EXIT_FAILURE);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;  // 使用IPv4地址
addr.sin_addr.s_addr = inet_addr("127.0.0.1");  // 设置IP地址
addr.sin_port = htons(12345);  // 设置端口号
if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {  // 发起连接请求
    perror("connect");
    exit(EXIT_FAILURE);
}
if (send(sockfd, "hello world", strlen("hello world"), 0) == -1) {  // 向对端发送数据
    perror("send");
    exit(EXIT_FAILURE);
}
char buf[1024];
if (recv(sockfd, buf, sizeof(buf), 0) == -1) {  // 从对端接收数据
    perror("recv");
    exit(EXIT_FAILURE);
}
printf("received data from socket: %s\n", buf);
close(sockfd);  // 关闭Socket