本文共 3423 字,大约阅读时间需要 11 分钟。
在前一篇文章中,我们探讨了匿名管道在进程间通信中的应用及其局限性。匿名管道虽然简单,但其存在一个显著的缺陷:所有进程都必须由一个共同的祖先进程启动,这使得不相关进程之间的数据交换显得不够方便。为了解决这一问题,命名管道(FIFO,First-In-First-Out)提供了一种更灵活的通信方式。
命名管道也被称为FIFO文件。它是一种特殊类型的文件,通过文件名与用户空间中的进程进行交互。与匿名管道不同,命名管道的存在依赖于文件系统中的实际文件名,这种设计使得它的操作更加统一且便捷。
由于Linux将所有事物视为文件,命名管道的使用也因此变得与文件操作高度一致。开发者可以像处理普通文件一样处理命名管道,这大大提高了使用的便利性。
创建命名管道可以通过两种函数中的任意一种实现:mkfifo 或 mknod。两者的共同点都是创建一个FIFO文件,但具体实现方式有所不同。
mkfifo 函数:该函数接受文件名和模式参数,直接创建一个FIFO文件。其调用示例如下:
#include#include int mkfifo(const char *filename, mode_t mode);
mknod 函数:该函数同样用于创建FIFO文件,但参数的组合略有不同。建议优先使用 mkfifo,因为其功能更为明确。
创建FIFO文件时,需注意文件的读写权限。通常情况下,可设置为777权限,以保证不同进程间的读写操作不会受到权限限制。
要使用命名管道,必须首先打开它。与普通文件一样,FIFO文件可以通过open系统调用进行访问。需要注意以下几点:
打开模式:FIFO文件不能以读写模式(O_RDWR)打开,因为这种情况下行为未明确定义。通常情况下,FIFO文件应以只读(O_RDONLY)或只写(O_WRONLY)模式打开。
非阻塞与阻塞:在打开FIFO文件时,可以选择是否设置非阻塞标志(O_NONBLOCK)。如果设置,则open调用将立即返回,无论FIFO是否有其他进程在进行写操作。
例如,以下是打开FIFO文件的几种常见方式:
// 只读模式,阻塞open(const char *path, O_RDONLY);// 只读模式,非阻塞open(const char *path, O_RDONLY | O_NONBLOCK);// 只写模式,阻塞open(const char *path, O_WRONLY);// 只写模式,非阻塞open(const char *path, O_WRONLY | O_NONBLOCK);
为了更好地理解FIFO的使用效果,可以通过一个实际例子来说明。以下是一个简单的进程间通信案例:
fifowrite.c:创建FIFO文件并向其中写入数据。fiforead.c:从FIFO文件中读取数据并保存到目标文件。代码示例如下:
// fifowrite.c#include#include #include #include #include #include #include #include int main() { const char *fifo_name = "/tmp/my_fifo"; int pipe_fd = -1; int data_fd = -1; int res = 0; const int open_mode = O_WRONLY; // 创建FIFO文件 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); } } // 打开FIFO文件 pipe_fd = open(fifo_name, open_mode); data_fd = open("Data.txt", O_RDONLY); if (pipe_fd != -1) { char buffer[PIPE_BUF + 1]; int bytes_read = 0; while (true) { bytes_read = read(data_fd, buffer, PIPE_BUF); if (bytes_read == 0) { break; } buffer[bytes_read] = '\0'; res = write(pipe_fd, buffer, bytes_read); if (res == -1) { fprintf(stderr, "Write error on pipe\n"); exit(EXIT_FAILURE); } close(pipe_fd); close(data_fd); break; } } exit(EXIT_SUCCESS);}
// fiforead.c#include#include #include #include #include #include #include int main() { const char *fifo_name = "/tmp/my_fifo"; int pipe_fd = -1; int data_fd = -1; int bytes_read = 0; int bytes_write = 0; // 打开FIFO文件 pipe_fd = open(fifo_name, O_RDONLY); data_fd = open("DataFormFIFO.txt", O_WRONLY | O_CREAT, 0644); if (pipe_fd != -1) { char buffer[PIPE_BUF + 1]; memset(buffer, '\0', sizeof(buffer)); do { bytes_read = read(pipe_fd, buffer, PIPE_BUF); if (bytes_read == 0) { break; } bytes_write = write(data_fd, buffer, bytes_read); if (bytes_write == -1) { fprintf(stderr, "Write error to file\n"); exit(EXIT_FAILURE); } } while (bytes_read > 0); close(pipe_fd); close(data_fd); } else { exit(EXIT_FAILURE); } printf("Process %d finished, %d bytes read\n", getpid(), bytes_read); exit(EXIT_SUCCESS);}
在实际应用中,多个进程可能同时向同一个FIFO文件进行写操作。为了确保数据传递的原子性,系统规定:每次写入操作的数据量不得超过PIPE_BUF字节。这样可以避免数据块的交错,确保每次写入的数据不会与后续写入的数据混合在一起。
与匿名管道相比,命名管道的优势在于通信进程之间没有必然的父子关系。它们可以独立地启动并通过同一个FIFO文件进行通信。然而,匿名管道的通信进程之间必须有一个共同的祖先进程,这在某些场景下可能带来不便。
总之,命名管道为进程间通信提供了一种更加灵活和安全的解决方案。通过合理配置和正确编写代码,我们可以充分发挥其优势,实现高效的进程间通信。
转载地址:http://uekfk.baihongyu.com/