高级I/O函数 《Linux高性能服务器编程》在第六章讲解了很多Linux提供了很多高级I/O函数,在这里做个笔记。这一章主要内容包括:pipe函数、dup
和dup2
函数、readv函数和writev函数、sendfile函数、mmap函数和munmap函数、splice函数、tee函数和fcntl函数。
管道 pipe函数 pipe
函数可用于创建一个管道(匿名),以实现进程之间的通讯(主要感觉是父子进程之间)。
1 2 #include <unistd.h> int pipe (int pipefd[2 ]) ;
pipefd
是传出参数,它包含两个文件描述符。我们就是使用这两个文件描述符进行进程之间的通讯。
pipe
函数调用成功返回0,如果失败返回-1,并且设置errno
。
pipe
函数创建的管道是单向通讯,其中pipefd[0]
只能读,pipefd[1]
只能写。并且默认情况下,这一对文件描述符都是阻塞的,但是也可以进行修改,变成非阻塞的。
pipe
函数创建的管道内部传输的数据是字节流,管道本身有一个容量限制 ,最多能写下65536
个字节,但是这个大小可以使用fcntl
进行修改。
如果管道的写端文件描述符pipefd[1]
的引用计数 减少至0,即没有任何进程需要往管道中写人数据,则针对该管道的读端文件描述符 pipefd[0]
的read
操作将返回0,即读取到了文件结束标记(End Of File,EOF);反之,如果管道的读端文件描述符pipefd[0]
的引用计数减少至0,即没有任何进程需要从管道读取数据,则针对该管道的写端文件描述符pipefd[1]
的write
操作将失败,并引发SIGPIPE
信号。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <unistd.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <sys/wait.h> void sys_err (const char *str) { perror(str); exit (1 ); } int main (int argc, char const *argv[]) { pid_t pid; char buf[1024 ]; int fd[2 ]; char *p = "test for pipe\n" ; if (pipe(fd) == -1 ) sys_err("pipe" ); pid = fork(); if (pid < 0 ) { sys_err("fork err" ); } else if (pid == 0 ) { close(fd[1 ]); int len = read(fd[0 ], buf, sizeof (buf)); write(STDOUT_FILENO, buf, len); close(fd[0 ]); } else { close(fd[0 ]); write(fd[1 ], p, strlen (p)); wait(NULL ); close(fd[1 ]); } return 0 ; }
socketpair函数 socketpair
函数能创建双向管道(一对套接字),并且似乎只能用于本地通讯。
1 2 3 4 #include <sys/types.h> #include <sys/socket.h> int socketpair (int domain, int type, int protocol, int sv[2 ]) ;
socketpair
前三个参数和socket
一样。
domain
是协议族类型,但是因为是本地通讯所以只能是AF_UNIX
type
参数指定服务类型。服务类型主要有SOCK_STREAM
服务(流服务)和SOCK_UGRAM
(数据报)服务。
protocol
参数设置具体的协议。但是在前两个参数确定的情况下,这个参数的值基本上唯一的,所有几乎在所有情况下,我们都把这个值设置为0,表示使用默认协议。
sv[2]
则是传出参数,和上面的pipe
相同,里面包含着两个通讯用的文件描述符,只不过这两个文件描述符是既可以读又可以写的。
socketpair
函数调用成功返回0,如果失败返回-1,并且设置errno
。
socketpair
用法比较简单,和上面pipe
有一些类似
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <sys/wait.h> #include <unistd.h> #include <string.h> void sys_err (const char *str) { perror(str); exit (1 ); } int main (int argc, char const *argv[]) { pid_t pid; int fd[2 ]; char buf[1024 ]; if (socketpair(AF_UNIX, SOCK_STREAM, 0 , fd)) sys_err("socketpair error" ); pid = fork(); if (pid < 0 ) { sys_err("fork err" ); } else if (pid == 0 ) { int len = read(fd[0 ], buf, sizeof (buf)); write(STDOUT_FILENO, buf, len); char *p = "child test for socketpair\n" ; write(fd[0 ], p, strlen (p)); } else { char *p = "parent test for socketpair\n" ; write(fd[1 ], p, strlen (p)); int len = read(fd[1 ], buf, sizeof (buf)); write(STDOUT_FILENO, buf, len); wait(NULL ); close(fd[0 ]); close(fd[1 ]); } return 0 ; }
mkfifo函数 1 2 3 4 #include <sys/types.h> #include <sys/stat.h> int mkfifo (const char *pathname, mode_t mode) ;
mkfifo
会创建一个fifo
类型的文件,然后两个进程可任意通过open
的方式打开这个文件进行进程间的通讯。
pathname
表示文件名,mode
指定了文件的读写权限。
函数成功调用返回0失败返回-1,并且设置errno
。
下面的代码就是创建一个名为mytestfifo的fifo文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/stat.h> #include <errno.h> #include <pthread.h> void sys_err (const char *str) { perror(str); exit (1 ); } int main (int argc, char *argv[]) { int ret = mkfifo("mytestfifo" , 0664 ); if (ret == -1 ) sys_err("mkfifo error" ); return 0 ; }
可以看到这个文件类型前面有个p
管道文件读取或者写入和普通文件相同,我们都可以使用open
对其进行操作但是需要的是注意两点:
1.程序不能以O_RDWR模式打开FIFO文件进行读写操作 ,而其行为也未明确定义,因为如一个管道以读/写方式打开,进程就会读回自己的输出,同时我们通常使用FIFO只是为了单向的数据传递。
2.打开FIFO文件通常有四种方式,
1 2 3 4 open(const char *pathname, O_RDONLY); open(const char *pathname, O_RDONLY | O_NONBLOCK); open(const char *pathname, O_WRONLY); open(const char *pathname, O_WRONLY | O_NONBLOCK);
O_NONBLOCK
表示阻塞。所以
O_RDONLY
:open将会调用阻塞,除非有另外一个进程以写的方式打开同一个FIFO,否则一直等待。
O_WRONLY
:open将会调用阻塞,除非有另外一个进程以读的方式打开同一个FIFO,否则一直等待。
O_RDONLY|O_NONBLOCK
:如果此时没有其他进程以写的方式打开FIFO,此时open也会成功返回,此时FIFO被读打开,而不会返回错误。
O_WRONLY|O_NONBLOCK
:立即返回,如果此时没有其他进程以读的方式打开,open会失败打开,此时FIFO没有被打开,返回-1。
例子:
fifo_w.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <stdio.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <fcntl.h> #include <stdlib.h> #include <cstring> void sys_err (char const *str) { perror(str); exit (-1 ); } int main (int argc, char *argv[]) { int fd; char buf[4096 ]; if (argc < 2 ) { printf ("Enter like this: ./a.out fifoname\n" ); return -1 ; } fd = open(argv[1 ], O_WRONLY); if (fd < 0 ) sys_err("open fifo error\n" ); for (int i = 0 ; i < 5 ; i++) { sprintf (buf, "hello world %d\n" , i); write(fd, buf, strlen (buf)); } close(fd); return 0 ; }
fifo_r.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <stdio.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> void sys_err (char const *str) { perror(str); exit (1 ); } int main (int argc, char *argv[]) { int fd, len; char buf[4096 ]; if (argc < 2 ) { printf ("./a.out fifoname\n" ); return -1 ; } fd = open(argv[1 ], O_RDONLY); if (fd < 0 ) sys_err("open" ); while (1 ) { len = read(fd, buf, sizeof (buf)); write(STDOUT_FILENO, buf, len); } close(fd); return 0 ; }
FIFO
文件会存在进程之间通讯的问题。比如多个进程对FIFO
进行写,但是只有一个FIFO
进行读取时写入的数据块会不会发生交错?
为了解决这个问题,系统规定: 在一个以O_WRONLY
(即阻塞方式)打开的FIFO
中, 如果写入的数据长度小于等待PIPE_BUF
,那么或者写入全部字节,或者一个字节都不写入。
所以所有的写请求都是发往一个阻塞的FIFO的,并且每个写记请求的数据长度小于等于PIPE_BUF
字节,系统就可以确保数据决不会交错在一起。
其中PIPE_BUF
是FIFO
的长度,它在头文件limits
.h中被定义。在linux或其他类UNIX系统中,它的值通常是4096字节。
参考:
dup和dup2函数 dup
和dup2
用于复制文件描述符,通常用于重定向。
1 2 3 4 #include <unistd.h> int dup (int oldfd) ;int dup2 (int oldfd, int newfd) ;
dup
函数创建一个新的文件描述符,该新文件描述符和原有文件描述符oldfd
指向相同的文件、管道或者网络连接。并且dup返回的文件描述符总是取系统当前可用的最小整数值。
dup2
和dup
类似,不过它将返回第一个不小于newfd
的整数值的文件描述符,并且newfd
这个文件描述符也将会指向oldfd
指向的文件,原来的newfd
指向的文件将会被关闭(除非newfd
和oldfd
相同)。
dup
和dup2
系统调用失败时返回-1并设置errno
,成功就返回新的文件描述符。
注意: 通过dup和dup2创建的文件描述符并不继承原文件描述符的属性 ,比如close-on-exec和non-blocking 等
dup
简单,输入oldfd
直接返回复制的文件描述符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <unistd.h> #include <stdio.h> #include <sys/stat.h> #include <fcntl.h> #include <assert.h> int main (int argc, char const *argv[]) { int fd = open("text.txt" , O_RDWR | O_CREAT, 0666 ); assert(fd != -1 ); printf ("fd = %d\n" , fd); int fd2 = dup(fd); printf ("fd2 = %d\n" , fd2); char str[] = "hello " ; write(fd, str, sizeof (str)); char str2[] = "world\n" ; write(fd2, str2, sizeof (str2)); close(fd); close(fd2); return 0 ; }
dup2
感觉复杂一些,其实dup2
忽略第二个参数,功能是和dup
一样的,除此之外dup2
加了一个将返回第一个不小于newfd
的整数值的文件描述符的功能,并且newfd
也将指向oldfd
指向的文件。
下面的代码调用dup2
,文件描述符fd2原来指向”text2.txt”文件的,调用dup2
后,fd2改为指向”text.txt”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <unistd.h> #include <stdio.h> #include <sys/stat.h> #include <fcntl.h> #include <assert.h> int main (int argc, char const *argv[]) { int fd1 = open("text.txt" , O_RDWR | O_CREAT, 0666 ); int fd2 = open("text2.txt" , O_RDWR | O_CREAT, 0666 ); assert(fd1 != -1 ); assert(fd2 != -1 ); printf ("fd1 = %d, fd2 = %d\n" , fd1, fd2); int fd3 = dup2(fd1, fd2); printf ("fd1 = %d,fd2 = %d,fd3 = %d\n" , fd1, fd2, fd3); char str[] = "hello " ; write(fd1, str, sizeof (str)); char str2[] = "world\n" ; write(fd2, str2, sizeof (str2)); char str2[] = " hello world\n" ; write(fd3, str2, sizeof (str2)); close(fd1); close(fd2); close(fd3); return 0 ; }
readv函数和writev函数 readv
函数将数据从文件描述符读到分散的内存块中,即分散读;
writev
函数则将多块分散的内存数据一并写人文件描述符中,即集中写。它们的定义如下:
1 2 3 4 #include <sys/uio.h> ssize_t readv (int fd, const struct iovec *iov, int iovcnt) ;ssize_t writev (int fd, const struct iovec *iov, int iovcnt) ;
fd
被操作的目标文件描述符。
iov
是iovec
类型的数组,在recvmsg
和sendmsg
中接触过。
iovcnt
是iov
数组的长度。
iovec
结构体封装了一块内存的起始位置和长度。
1 2 3 4 struct iovec { void *iov_base; size_t iov_len; };
readv
和 writev
在成功时返回读出/写入fd
的字节数,失败则返回-1并设置errno。
readv
和writev
是个非常有用的函数。比如:当Web服务器解析完一个HTTP请求之后,如果目标文档存在且客户具有读取该文档的权限,那么它就需要发送一个HTTP应答来传输该文档。这个HTTP应答包含1个状态行、多个头部字段、1个空行和文档的内容。其中,前3部分的内容可能被Web服务器放置在一块内存中,而文档的内容则通常被读入到另外一块单独的内存中(通过read函数或mmap函数)。我们并不需要把这两部分内容拼接到一起再发送,而是可以使用writev函数将它们同时写出。
举一个man手册上writev
函数的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <sys/uio.h> #include <string.h> #include <unistd.h> int main (int argc, char const *argv[]) { char *str0 = "hello " ; char *str1 = "world\n" ; struct iovec iov [2]; ssize_t nwritten; iov[0 ].iov_base = str0; iov[0 ].iov_len = strlen (str0); iov[1 ].iov_base = str1; iov[1 ].iov_len = strlen (str1); nwritten = writev(STDOUT_FILENO, iov, 2 ); return 0 ; }
sendfile函数 sendfile
函数在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这被称为零拷贝。sendfile
函数的定义如下:
1 2 #include <sys/sendfile.h> ssize_t sendfile (int out_fd, int in_fd, off_t *offset, size_t count) ;
out_fd
参数是待写入内容的文件描述符
in_fd
参数是待读取内容的文件描述符
offset
参数是指从读入文件流的哪个位置开始读,如果为空,则使用读入文件流默认的起始位置
count
参数指定在文件描述符in_fd
和out_fd
之间传输的字节数
sendfile
成功时返回传输的字节数,失败则返回-1并设置errno
。
该函数的man手册明确指出,in_fd
必须是一个支持类似mmap
函数的文件描述符,即它必须指向真实的文件,不能是socket和管道。而out_fd
则必须是一个socket。由此可见,sendfile
几乎是专门为在网络上传输文件而设计的。
用了一个书上的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/sendfile.h> int main (int argc, char *argv[]) { if (argc <= 3 ) { printf ("usage: %s ip_address port_number filename\n" , basename(argv[0 ])); return 1 ; } const char *ip = argv[1 ]; int port = atoi(argv[2 ]); const char *file_name = argv[3 ]; int filefd = open(file_name, O_RDONLY); assert(filefd > 0 ); struct stat stat_buf ; fstat(filefd, &stat_buf); struct sockaddr_in address ; bzero(&address, sizeof (address)); address.sin_family = AF_INET; inet_pton(AF_INET, ip, &address.sin_addr); address.sin_port = htons(port); int sock = socket(PF_INET, SOCK_STREAM, 0 ); assert(sock >= 0 ); int ret = bind(sock, (struct sockaddr *)&address, sizeof (address)); assert(ret != -1 ); ret = listen(sock, 5 ); assert(ret != -1 ); struct sockaddr_in client ; socklen_t client_addrlength = sizeof (client); int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength); if (connfd < 0 ) { printf ("errno is: %d\n" , errno); } else { sendfile(connfd, filefd, NULL , stat_buf.st_size); close(connfd); } close(sock); return 0 ; }
mmap函数和munmap函数 mmap
函数用于申请一段内存空间。可以将这段内存作为进程间通讯的共享内存,也可以将文件直接映射其中。munmap
函数用于释放mmap
创建的内存空间。
1 2 3 4 #include <sys/mman.h> void *mmap (void *addr, size_t length, int prot, int flags,int fd, off_t offset) ;int munmap (void *addr, size_t length) ;
addr
参数运行用户使用某个特定的地址作为这段内存的起始地址。如果它被设置为NULL,则系统会自动分配一个地址。
length
参数指定内存段的长度
prot
参数用来设置内存段的访问权限,可以下面几个值
PROT_EXEC
,内存段可执行
PROT_READ
,内存段可读
PROT_WRITE
,内存段可写
PROT_NONE
,内存段不能被访问
flags
参数控制内存段内容被修改后程序的行为。它常用的取值如下:
fd
参数是被映射文件对应的文件描述符。它一般通过open系统调用获得。
offset
参数设置从文件的何处开始映射。
mmap
函数成功时返回指向目标内存区域的指针,失败则返回MAP_FAILED((void*)-1)
并设置errno
。
munmap
函数成功时返回0,失败则返回-1并设置errno
。
splice函数 splice
用于在两个文件描述符之间移动数据,是零拷贝操作。看了man
手册,发现这个splice
函数跟pipe管道关系不浅。
1 2 3 #include <fcntl.h> ssize_t splice (int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags) ;
fd_in
参数是待输人数据的文件描述符。如果fd_in
是一个管道文件描述符,那么 off_in
参数必须被设置为NULL。如果fd_in
不是一个管道文件描述符(比如 socket),那么off_in
表示从输入数据流的何处开始读取数据。此时,若off_in
被设置为NULL,则表示从输入数据流的当前偏移位置读入;若off_in
不为NULL,则它将指出具体的偏移位置。
fd_out/off_out
参数的含义与fd_in/off_in
相同,不过用于输出数据流。
len
参数指定移动数据的长度
flags
参数则控制数据如何移动,它可以被设置为下表中的某些值的按位或。
使用splice
函数时,fd_in
和fd_out
必须至少有一个是管道文件描述符。
splice
函数调用成功时返回移动字节的数量。它可能返回0,表示没有数据需要移动,这发生在从管道中读取数据(fd_in
是管道文件描述符)而该管道没有被写入任何数据时。splice
函数失败时返回-1并设置errno
。常见的errno
如下表所示。
下面用了一个书中的例子,实现一个零拷贝的回射服务器,它将客户端发送的信息通过splice
从pipefd[1]
写入管道,再使用splice
从pipefd[0]
向客户端写东西,从而实现零拷贝的回射服务器(整个过程没有使用read
或者write
操作)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <fcntl.h> int main (int argc, char *argv[]) { if (argc <= 2 ) { printf ("usage: %s ip_address port_number\n" , basename(argv[0 ])); return 1 ; } const char *ip = argv[1 ]; int port = atoi(argv[2 ]); struct sockaddr_in address ; bzero(&address, sizeof (address)); address.sin_family = AF_INET; inet_pton(AF_INET, ip, &address.sin_addr); address.sin_port = htons(port); int sock = socket(PF_INET, SOCK_STREAM, 0 ); assert(sock >= 0 ); int ret = bind(sock, (struct sockaddr *)&address, sizeof (address)); assert(ret != -1 ); ret = listen(sock, 5 ); assert(ret != -1 ); struct sockaddr_in client ; socklen_t client_addrlength = sizeof (client); int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength); if (connfd < 0 ) { printf ("errno is: %d\n" , errno); } else { int pipefd[2 ]; assert(ret != -1 ); ret = pipe(pipefd); ret = splice(connfd, NULL , pipefd[1 ], NULL , 32768 , SPLICE_F_MORE | SPLICE_F_MOVE); assert(ret != -1 ); ret = splice(pipefd[0 ], NULL , connfd, NULL , 32768 , SPLICE_F_MORE | SPLICE_F_MOVE); assert(ret != -1 ); close(connfd); } close(sock); return 0 ; }
tee函数 tee
函数在两个管道描述符之间复制数据,也是零拷贝操作。它不消耗数据,因此源文件描述符上的数据仍然可以用于后续操作。
1 2 3 #include <fcntl.h> ssize_t tee (int fd_in, int fd_out, size_t len, unsigned int flags) ;
fd_in
和fd_out
是文件描述符,但是必须是管道文件描述符
len
参数指定移动数据的长度
flags
参数则控制数据如何移动,它可以被设置为下表中的某些值的按位或,它的参数其实和splice
函数相同。
tee
函数成功时返回在两个文件描述符之间复制的数据数量(字节数)。返回0表示没有复制任何数据。tee
失败时返回-1并设置errno
。
书中代码利用tee
函数和splice
函数,实现了Linux 下tee
程序(同时输出数据到终端和文件的程序,不要和tee
函数混淆)的基本功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <assert.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <fcntl.h> int main (int argc, char *argv[]) { if (argc != 2 ) { printf ("usage: %s <file>\n" , argv[0 ]); return 1 ; } int filefd = open(argv[1 ], O_CREAT | O_WRONLY | O_TRUNC, 0666 ); assert(filefd > 0 ); int pipefd_stdout[2 ]; int ret = pipe(pipefd_stdout); assert(ret != -1 ); int pipefd_file[2 ]; ret = pipe(pipefd_file); assert(ret != -1 ); ret = splice(STDIN_FILENO, NULL , pipefd_stdout[1 ], NULL , 32768 , SPLICE_F_MORE | SPLICE_F_MOVE); assert(ret != -1 ); ret = tee(pipefd_stdout[0 ], pipefd_file[1 ], 32768 , SPLICE_F_NONBLOCK); assert(ret != -1 ); ret = splice(pipefd_file[0 ], NULL , filefd, NULL , 32768 , SPLICE_F_MORE | SPLICE_F_MOVE); assert(ret != -1 ); ret = splice(pipefd_stdout[0 ], NULL , STDOUT_FILENO, NULL , 32768 , SPLICE_F_MORE | SPLICE_F_MOVE); assert(ret != -1 ); close(filefd); close(pipefd_stdout[0 ]); close(pipefd_stdout[1 ]); close(pipefd_file[0 ]); close(pipefd_file[1 ]); return 0 ; }
fcntl函数 fcntl
函数提供了对文件描述符的各种控制操作。
1 2 3 4 #include <unistd.h> #include <fcntl.h> int fcntl (int fd, int cmd, ... ) ;
fd
参数是被操作的文件描述符,cmd
参数指定执行何种类型的操作。根据操作类型的不同,该函数可能还需要第三个可选参数 arg
。fcntl
函数支持的常用操作及其参数如下表所示。
fcntl
函数成功时的返回值如表中最后一列所示,失败则返回-1并设置errno
。
在网络编程中,fcntl
函数通常用来将一个文件描述符设置为非阻塞的。
比如:终端文件默认是阻塞读的,这里用 fcntl 将其更改为非阻塞读
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define MSG_TRY "try again\n" int main (void ) { char buf[10 ]; int flags, n; flags = fcntl(STDIN_FILENO, F_GETFL); if (flags == -1 ) { perror("fcntl error" ); exit (1 ); } flags |= O_NONBLOCK; int ret = fcntl(STDIN_FILENO, F_SETFL, flags); if (ret == -1 ) { perror("fcntl error" ); exit (1 ); } while (true ) { n = read(STDIN_FILENO, buf, 10 ); if (n < 0 ) { if (errno != EAGAIN) { perror("read /dev/tty" ); exit (1 ); } sleep(3 ); write(STDOUT_FILENO, MSG_TRY, strlen (MSG_TRY)); continue ; } write(STDOUT_FILENO, buf, n); } return 0 ; }