Skip to content

LV020-有名管道

本文主要是进程通信——管道的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

一、有名管道

有名管道主要用于非亲缘的两个进程互相通信

1. 有名管道创建

1.1 mkfifo()

1.1.1 函数说明

linux下可以使用man 3 mkfifo命令查看该函数的帮助手册。

c
/* 需包含的头文件 */
#include <sys/types.h>
#include <sys/stat.h>

/* 函数声明 */
int mkfifo(const char *pathname, mode_t mode);

函数说明】该函数用于创建一个有名管道,创建的有名管道文件也可以称为FIFO文件。

函数参数

  • pathnameconst char *类型,有名管道文件存放的路径(包括管道名称),需要注意的是,这里不要放在共享目录下。
  • modemode_t类型,有名管道文件权限,为8进制表示法。新建的有名管道文件权限会受到umask 值影响,实际权限是mode - umaks

Tips:什么是 umask?

在类unix系统中,umask是确定掩码设置的命令,该掩码用来设定文件或目录的初始权限。umask确定了文件创建时的初始权限:

c
文件或目录的初始权限 = 文件或目录的最大默认权限 - umask权限

文件初始默认权限为0666,目录为0777,若用户umask0002,则新创建的文件或目录在没有指定的情况下默认权限分别为06640775)。

Linux下,我们可以使用umask命令来查看当前用户默认的umask值,同时也可以在umask命令后面跟上需要设置的umask值来重新设置umask

返回值int类型,成功返回0,失败返回-1,并设置errno

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <sys/types.h>
#include <sys/stat.h>

/* 至少应该有的语句 */
mkfifo("/home/hk/MyFifo", 0666);

注意事项

(1)文件路径不要定在虚拟机中LinuxWindows的共享目录中。

(2)该文件必须不存在。

1.1.2 使用实例

暂无。

2. 有名管道打开

2.1 open()

2.1.1 函数说明

linux下可以使用man 2 open命令查看该函数的帮助手册。

c
/* 需包含的头文件 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/* 函数声明 */
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

函数说明】该函数。

函数参数

  • pathnameconst char *类型,表示需要被打开的文件名(可包括路径名)。
  • flagsint类型,表示打开文件所采用的操作。

(1)flag 常量常见可取的值

O_RDONLY只读方式打开文件。这三个参数互斥
O_WRONLY可写方式打开文件。
O_RDWR 读写方式打开文件。
O_CREAT 如果该文件不存在,就创建一个新的文件,并用第三的参数为其设置权限。
O_EXCL 如果使用O_CREAT时文件存在,则可返回错误消息。这一参数可测试文件是否存在。
O_NOCTTY使用本参数时,如文件为终端,那么终端不可以作为调用open()系统调用的那个进程的控制终端。
O_TRUNC 如文件已经存在,那么打开文件时先删除文件中原有数据。
O_APPEND以添加方式打开文件,所以对文件的写操作都在文件的末尾进行。
O_NONBLOCK以非阻塞的方式打开文件。

【注意事项】前三个参数必须指定,且只能指定一个,后边的几个可以与前边搭配使用。

(2)flag常量与标准I/O文件打开权限关系、

r O_RDONLY
r+O_RDWR
w O_WRONLY | O_CREAT | O_TRUNC, 0664
w+O_RDWR | O_CREAT | O_TRUNC, 0664
a O_WRONLY | O_CREAT | O_APPEND, 0664
a+O_RDWR | O_CREAT | O_APPEND, 0664
- `mode`:`mode_t`类型,表示被打开文件的存取权限,为`8`进制表示法。此参数**只有在建立新文件时有效**。新建文件时的权限会受到`umask` 值影响,实际权限是`mode - umaks`。

返回值int类型,成功时返回文件描述符(非负整数);出错时返回EOF(一般是-1)。

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/* 至少应该有的语句 */
int fd;/* 接收文件描述符 */
/* 以只写方式打开文件file.txt。如果文件不存在则创建,如果文件存在则清空 */
fd = open("file.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);

注意事项】该函数可以打开设备文件,但是不能创建设备文件

2.2 使用说明

此函数是文件IO中的相关函数,在对有名管道文件进行操作时,需要注意以下几点:

(1)程序不能以O_RDWR(读写)模式打开FIFO文件进行读写操作,而其行为也未明确定义,因为如一个管道以读/写方式打开,进程可以读回自己的输出,同时我们通常使用FIFO只是为了单向的数据传递。

(2)open函数的打开方式还可以有以下情况的搭配:

c
open(const char *path, O_RDONLY);             //1
open(const char *path, O_RDONLY | O_NONBLOCK);//2
open(const char *path, O_WRONLY);             //3
open(const char *path, O_WRONLY | O_NONBLOCK);//4

第二个参数中的选项O_NONBLOCK,选项O_NONBLOCK表示非阻塞,加上这个选项后,表示open调用是非阻塞的,如果没有这个选项,则表示open调用是阻塞的。

(3)对于以只读方式(O_RDONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_RDONLY),除非有一个进程以写方式打开同一个FIFO,否则它不会返回;如果open调用是非阻塞的的(即第二个参数为O_RDONLY | O_NONBLOCK),则即使没有其他进程以写方式打开同一个FIFO文件,open调用将成功并立即返回。

(4)对于以只写方式(O_WRONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_WRONLY),open调用将被阻塞,直到有一个进程以只读方式打开同一个FIFO文件为止;如果open调用是非阻塞的(即第二个参数为O_WRONLY | O_NONBLOCK),open总会立即返回,但如果没有其他进程以只读方式打开同一个FIFO文件,open调用将返回-1,并且FIFO也不会被打开。

(5)据完整性,如果有多个进程写同一个管道,使用O_WRONLY方式打开管道,如果写入的数据长度小于等于PIPE_BUF(4K),或者写入全部字节,或者一个字节都不写入,这样系统就可以确保数据决不会交错在一起。

3. 有名管道读写

3.1 读写函数

先说一下读写吧,有名管道的读写与无名管道一样,当打开之后,可以通过read函数读取管道中的数据,可以使用write函数来写入数据。

c
/* 需包含的头文件 */
#include <unistd.h>
/* 函数声明 */
ssize_t read(int fildes, void *buf, size_t nbyte);        /* 数据读取 */
ssize_t write(int fildes, const void *buf, size_t nbyte); /* 数据写入 */

3.2 使用实例

下边的实例实现了两个进程的通信。

3.2.1 fifo_read.c

【说明】对于以只读方式(O_RDONLY)打开的FIFO文件:如果open调用是阻塞的(即第二个参数为O_RDONLY),除非有一个进程以写方式打开同一个FIFO,否则它不会返回;如果open调用是非阻塞的的(即第二个参数为O_RDONLY | O_NONBLOCK),则即使没有其他进程以写方式打开同一个FIFO文件,open调用将成功并立即返回。

c
/* 头文件 */
#include <stdio.h>  /* perror fgets */
#include <unistd.h> /* write */
#include <string.h> /* strlen */

#include <sys/types.h>/* mkfifo open */
#include <sys/stat.h> /* mkfifo open */

#include <fcntl.h>    /* open */
#include <stdlib.h>   /* exit */

/* 主函数 */
int main(int argc, char *argv[])
{
	int ret;
	int fd;
	char buff[32];

	fd = open("/home/hk/MyFifo", O_RDONLY);
	if(fd < 0)
	{
		perror("open");
		return -1;
	}
	printf("after open MyFifo!\n");
	while(1)
	{
		memset(buff, 0, 32);
		ret = read(fd, buff, 32);
		if(ret > 0)
			printf("read fifo=%s\n",buff);
		else if(ret == 0)
			exit(0);
	}
	return 0;
}
3.2.2 fifo_write.c

【说明】对于以只写方式(O_WRONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_WRONLY),open调用将被阻塞,直到有一个进程以只读方式打开同一个FIFO文件为止;如果open调用是非阻塞的(即第二个参数为O_WRONLY | O_NONBLOCK),open总会立即返回,但如果没有其他进程以只读方式打开同一个FIFO文件,open调用将返回-1,并且FIFO也不会被打开。

c
/* 头文件 */
#include <stdio.h>  /* perror fgets */
#include <unistd.h> /* write */
#include <string.h> /* strlen */

#include <sys/types.h>/* mkfifo open */
#include <sys/stat.h> /* mkfifo open */

#include <fcntl.h>    /* open */

/* 主函数 */
int main(int argc, char *argv[])
{
	int ret;
	int fd;
	char buff[32];
	/* 1. 创建有名管道文件 */
	ret = mkfifo("/home/hk/MyFifo", 0666);/* 注意不要创建在共享目录 */
	if(ret < 0)
	{
		/* 若文件已存在,则打印出错信息即可,程序不需要结束 */
		perror("mkfifo");
	}
	/* 2. 使用文件IO打开有名管道文件 */
	fd = open("/home/hk/MyFifo", O_WRONLY|O_NONBLOCK);
	if(fd < 0)
	{
		perror("open");
		return -1;
	}
	printf("after open MyFifo!\n");
	while(1)
	{
		fgets(buff, 32, stdin);
		write(fd, buff, strlen(buff));
	}
	return 0;
}
3.2.3 Makefile
makefile
CC = gcc

DEBUG = -g -O2 -Wall
CFLAGS += $(DEBUG)

# 所有.c文件去掉后缀
TARGET_LIST = ${patsubst %.c, %, ${wildcard *.c}} 

all : $(TARGET_LIST)

%.o : %.c
	$(CC) $(CFLAGS) -c $< -o $@

.PHONY: all clean clean_o
clean : clean_o
	@rm -vf $(TARGET_LIST) 
	
clean_o :
	@rm -vf *.o
3.2.4 测试效果

在终端执行以下命令编译程序:

shell
make # 编译程序,将会生成两个可执行程序 
./fifo_write # 一个终端向管道写入数据
./fifo_read  # 在另一个终端读取管道数据

然后,两个终端都会阻塞等待另一个进程打开有名管到文件,之后我们在运行写入管道可执行程序的终端中输入数据,便可以在另一个终端中收到了。

【注意事项】需要注意的是,尽量不要设置不阻塞,否则现象并不明显,很可能会出现以下问题:

shell
open: No such device or address