阻塞和非阻塞概念
如果是阻塞,管道中没有数据,read会一直等待,直到有数据才会继续运行,否则一 直等待。
如果是非阻塞,read函数运行时,会先看一下管道中是否有数据,如果有数据,则正常运行读取数据,如果管道中没有数据,则read函数会立即返回,继续下面的代码运行。
通过fcntl函数设置文件的阻塞特性
功能 | 函数 |
---|---|
设置为阻塞: | fcntl(fd, F_SETFL, 0); |
设置为非阻塞: | fcntl(fd, F_SETFL, O_NONBLOCK); |
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd_pipe[2];
char buf[] = "hello world";
pid_t pid;
if (pipe(fd_pipe) < 0)
{
perror("fail to pipe");
exit(1);
}
pid = fork();
if (pid < 0)
{
perror("fail to fork");
exit(0);
}
if (pid == 0)
{
while(1)
{
sleep(5);
write(fd_pipe[1], buf, strlen(buf));
}
}
else
{
//将fd_pipe[0]设置为阻塞
//fcntl(fd_pipe[0], F_SETFL, 0);
//将fd_pipe[0]设置为非阻塞
fcntl(fd_pipe[0], F_SETFL, O_NONBLOCK);
while(1)
{
memset(buf, 0, sizeof(buf));
read(fd_pipe[0], buf, sizeof(buf));
printf("buf=[%s]\n", buf);
sleep(1);
}
}
return 0;
}
文件描述符概述
文件描述符是非负整数,是文件的标识。 用户使用文件描述符(file descriptor)来访问文件。
利用open打开一个文件时,内核会返回一个文件描述符。 每个进程都有一张文件描述符的表,进程刚被创建时,标准输入、标准输出、标准错误输出设备文件被打开,对应的文件描述符0、1、2 记录在表中。
在进程中打开其他文件时,系统会返回文件描述符表中最小可用的文件描述符,并将此文件 描述符记录在表中。
注意: Linux中一个进程最多只能打开NR_OPEN_DEFAULT (即1024)个文件,故当文件不再使用时应及时调用close函数关闭文件。
例子
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
//在进程中打开其他文件时,
//系统会返回文件描述符表中最小可用的文件描述符,
//并将此文件描述符记录在进程的文件描述符表中。
close(0);
int fd1, fd2, fd3;
fd1 = open("file.txt", O_RDONLY | O_CREAT, 0664);
fd2 = open("file.txt", O_RDONLY | O_CREAT, 0664);
fd3 = open("file.txt", O_RDONLY | O_CREAT, 0664);
printf("fd1 = %d\n", fd1);
printf("fd2 = %d\n", fd2);
printf("fd3 = %d\n", fd3);
return 0;
}
文件描述符的复制
dup函数
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(void)
{
//通过dup函数复制一个文件描述符
int fd;
//dup执行后给返回值文件描述符分配的值是文件描述符表中最小可用的文件描述符
fd = dup(1);
printf("fd = %d\n", fd);
//由于通过dup函数将1这个文件描述符复制了一份为fd,所以fd现在就相当于1,所以写数据就是想终端写入数据
write(fd, "nihao beijing\n", strlen("nihao beijing\n"));
return 0;
}
实现输出重定向的功能
如果需要实现输出重定向的功能
首先像printf函数是操作文件描述符1所对应的文件,默认是操作终端,只要能够把1对应标识的文件改变,
就可以实现输出重定向。所以实现创建好文件对应的文件描述符之后,将1文件描述符关闭,接着通过dup
函数复制的新的文件描述符就是1,这样printf函数对1操作,就写到了文件中。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
//如果需要实现输出重定向的功能
//首先像printf函数是操作文件描述符1所对应的文件,
//默认是操作终端,只要能够把1对应标识的文件改变,就可以实现输出重定向
//所以实现创建好文件对应的文件描述符之后,将1文件描述符关闭,接着通过dup
//函数复制的新的文件描述符就是1,这样printf函数对1操作,就写到了文件中
int fd_file;
fd_file = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);
if(fd_file == -1)
{
perror("fail to open");
exit(1);
}
close(1);
int fd = dup(fd_file);
printf("hello world\n");
printf("fd = %d\n", fd);
return 0;
}
实现输出重定向后,还想标准输出
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd1;
int fd2;
fd2 = dup(1);
printf("new:fd2 = %d\n",fd2);
fd1 = open("test.txt", O_RDWR | O_CREAT, 0664);
close(1);
int fd3 = dup(fd1);
printf("hello world\n");
printf("fd = %d\n", fd3);
close(1);
int fd4 = dup(fd2);
printf("nihao beijing\n");
printf("fd = %d\n", fd4);
return 0;
}
有名管道
命名管道(FIFO)和管道(pipe)基本相同,但也有一些显著的不同, 其特点是:
1、半双工,数据在同一时刻只能在一个方向上流动。
2、写入FIFO中的数据遵循先入先出的规则。
3、FIFO所传送的数据是无格式的,这要求FIFO的读出方与写入方必须事先约定好数据的格 式,如多少字节算一个消息等。
4、FIFO在文件系统中作为一个特殊的文件而存在并且在文件系统中可见,所以有名管道可 以实现不相关进程间通信,但FIFO中的内容却存放在内存中。
5、管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。
6、从FIFO读数据是一次性操作,数据一旦被读,它就从FIFO中被抛弃,释放空间以便写更 多的数据。
7、当使用FIFO的进程退出后,FIFO文件将继续保存在文件系统中以便以后使用。
8、FIFO有名字,不相关的进程可以通过打开命名管道进行通信。
有名管道的创建
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
//有名管道创建
int main()
{
if((mkfifo("./file",0600)==-1)&&errno!=EEXIST)
{
printf("mkfifo failued\n");
perror("why");
}else if(errno==EEXIST)
{
printf("file exist\n");
}
else{
printf("suscceess\n");
}
return 0;
}
有名管道的基本读写操作
由于有名管道在本地创建了一个管道文件,所以系统调用的IO函数基本都可以对有名管道进行操作, 但是不能使用lseek修改管道文件的偏移量。
注意:有名管道创建的本地的文件只是起到标识作用,真正有名管道实现进程间通信还是在内核空间开辟内存,所以本地产生的文件只是一个标识,没有其他作用,对本地管道文件的 操作实质就是对内核空间的操作。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#define FIFONAME "fifo_file"
int main(int argc, char const *argv[])
{
//通过mkfifo函数创建有名管道
if(mkfifo(FIFONAME, 0664) == -1)
{
if(errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
//对有名管道进行操作
//管道后写入的数据会保存在之前写入数据的后面,不会替换
//如果管道中没有数据了,读操作会阻塞
//通过open函数打开管道文件并得到文件描述符
int fd;
fd = open(FIFONAME, O_RDWR);
if(fd == -1)
{
perror("fail to open");
exit(1);
}
//通过write函数向管道中写入数据
if(write(fd, "hello world", strlen("hello world")) == -1)
{
perror("fail to write");
exit(1);
}
write(fd, "nihao beijing", strlen("nihao beijing"));
//通过read函数读取管道中的数据
char buf[32] = "";
if(read(fd, buf, sizeof(buf)) == -1)
{
perror("fail to read");
exit(1);
}
printf("buf = [%s]\n", buf);
if(read(fd, buf, sizeof(buf)) == -1)
{
perror("fail to read");
exit(1);
}
printf("buf = [%s]\n", buf);
//使用close函数关闭文件描述符
close(fd);
return 0;
}
有名管道实现进程间通信
由于有名管道在本地创建了一个管道文件,所以不相关的进程间也可以实现通信。
send
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
//如果没有创建有名管道,则创建有名管道
//为了实现两个进程都可以收发数据,所以需要创建两个有名管道
if(mkfifo("myfifo1", 0664) == -1)
{
if(errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
if(mkfifo("myfifo2", 0664) == -1)
{
if(errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
//打开两个有名管道并得到文件描述符
int fd_w, fd_r;
if((fd_w = open("myfifo1", O_WRONLY)) == -1)
{
perror("fail to open");
exit(1);
}
if((fd_r = open("myfifo2", O_RDONLY)) == -1)
{
perror("fail to open");
exit(1);
}
char buf[128] = "";
ssize_t bytes;
while(1)
{
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
//send进程负责将数据写入myfifo1,接着从myfifo2中读取数据
if((bytes = write(fd_w, buf, sizeof(buf))) == -1)
{
perror("fail to write");
exit(1);
}
if((bytes = read(fd_r, buf, sizeof(buf))) == -1)
{
perror("fail to read");
exit(1);
}
printf("from recv: %s\n", buf);
}
return 0;
}
** recv**
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
//如果没有创建有名管道,则创建有名管道
//为了实现两个进程都可以收发数据,所以需要创建两个有名管道
if(mkfifo("myfifo1", 0664) == -1)
{
if(errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
if(mkfifo("myfifo2", 0664) == -1)
{
if(errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
//打开两个有名管道并得到文件描述符
int fd_w, fd_r;
if((fd_r = open("myfifo1", O_RDONLY)) == -1)
{
perror("fail to open");
exit(1);
}
if((fd_w = open("myfifo2", O_WRONLY)) == -1)
{
perror("fail to open");
exit(1);
}
char buf[128] = "";
ssize_t bytes;
while(1)
{
if((bytes = read(fd_r, buf, sizeof(buf))) == -1)
{
perror("fail to read");
exit(1);
}
printf("from send: %s\n", buf);
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
write(fd_w, buf, sizeof(buf));
}
return 0;
}
有名管道的读写规律(阻塞)
读写端都存在,只读不写
如果原本管道中有数据,则正常读取。
如果管道中没有数据,则read函数会阻塞等待。
读写端都存在,只写不读
当有名管道的缓冲区写满后,write函数会发生阻塞。
默认有名管道的缓冲区为64K字节。
在一个进程中,只有读端,没有写端
在一个进程中,只有读端,没有写端。
会在open函数的位置阻塞。
在一个进程中,只有写端,没有读端
在一个进程中,只有写端,没有读端。
会在open函数的位置阻塞。
一个进程只读,一个进程只写
只要保证有名管道的读写端都存在,不管是几个进程,都不会再open这阻塞了。
如果一个进程只读,一个进程只写,都运行后,如果关闭写端,读端read会返回0。
如果一个进程只读,一个进程只写,都运行后,如果关闭读端,写端会立即产生 SIGPIPE信号,默认的处理方式是退出进程。