6. 高级I/O函数

第6章 高级I/O函数

本章将讨论其中和网络编程相关的几个,这些函数大致分为三类:

  • 用于创建文件描述符的函数,包括pipe、dup/dup2函数。
  • 用于读写数据的函数,包括readv/writev、sendfile、mmap/munmap、splice和tee函数。
  • 用于控制I/O行为和属性的函数,包括fcntl函数。

1. pipe函数

pipe函数可用于创建一个管道,以实现进程间通信。pipe函数的定义如下:

1
2
#include<unistd.h> 
int pipe(int fd[2]);
pipe函数的参数是一个包含两个int型整数的数组指针。该函数成功时返回0,并将一对打开的文件描述符值填入其参数指向的数组。如果失败,则返回-1并设置errno。

  • 通过pipe函数创建的这两个文件描述符fd[0]和fd[1]分别构成管道的两端,往fd[1]写入的数据可以从fd[0]读出。
  • 并且,fd[0]只能用于从管道读出数据,fd[1]则只能用于往管道写入数据,而不能反过来使用。如果要实现双向的数据传输,就应该使用两个管道。
  • 默认情况下,这一对文件描述符都是阻塞的。此时如果我们用read系统调用来读取一个空的管道,则read将被阻塞,直到管道内有数据可读;
  • 如果我们用write系统调用来往一个满的管道(见后文)中写入数据,则write亦将被阻塞,直到管道有足够多的空闲空间可用。
  • 但如果应用程序将fd[0]和fd[1]都设置为非阻塞的,则read和write会有不同的行为。关于阻塞和非阻塞的讨论,见第8章。
  • 如果管道的写端文件描述符fd[1]的引用计数(见5.7节)减少至0,即没有任何进程需要往管道中写入数据,则针对该管道的读端文件描述符fd[0]的read操作将返回0,即读取到了文件结束标记(End Of File,EOF);
  • 反之,如果管道的读端文件描述符fd[0]的引用计数减少至0,即没有任何进程需要从管道读取数据,则针对该管道的写端文件描述符fd[1]的write操作将失败,并引发SIGPIPE信号。关于SIGPIPE信号,我们将在第10章讨论。

管道内部传输的数据是字节流,这和TCP字节流的概念相同。但二者又有细微的区别。应用层程序能往一个TCP连接中写入多少字节的数据,取决于对方的接收通告窗口的大小和本端的拥塞窗口的大小。而管道本身拥有一个容量限制,它规定如果应用程序不将数据从管道读走的话,该管道最多能被写入多少字节的数据。自Linux 2.6.11内核起,管道容量的大小默认是65536字节。我们可以使用fcntl函数来修改管道容量(见后文)。

此外,socket的基础API中有一个socketpair函数。它能够方便地创建双向管道。其定义如下:

1
2
3
#include<sys/types.h> 
#include<sys/socket.h>
int socketpair(int domain,int type,int protocol,int fd[2]);
socketpair前三个参数的含义与socket系统调用的三个参数完全相同,但domain只能使用UNIX本地域协议族AF_UNIX,因为我们仅能在本地使用这个双向管道。最后一个参数则和pipe系统调用的参数一样,只不过socketpair创建的这对文件描述符都是既可读又可写的。socketpair成功时返回0,失败时返回-1并设置errno。

2. dup函数和dup2函数

有时我们希望把标准输入重定向到一个文件,或者把标准输出重定向到一个网络连接(比如CGI编程)。这可以通过下面的用于复制文件描述符的dup或dup2函数来实现:

1
2
3
#include<unistd.h> 
int dup(int file_descriptor);
int dup2(int file_descriptor_one,int file_descriptor_two);
dup函数创建一个新的文件描述符,该新文件描述符和原有文件描述符file_descriptor指向相同的文件、管道或者网络连接。并且dup返回的文件描述符总是取系统当前可用的最小整数值。dup2和dup类似,不过它将返回第一个不小于file_descriptor_two的整数值。dup和dup2系统调用失败时返回-1并设置errno。

注意通过dup和dup2创建的文件描述符并不继承原文件描述符的属性,比如close-on-exec和non-blocking等。

代码清单6-1利用dup函数实现了一个基本的CGI服务器。

代码清单6-1 CGI服务器原理

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<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>

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 {
close(STDOUT_FILENO);
dup(connfd);
printf("abcd\n");
close(connfd);
}
close(sock);
return 0;
}
在代码清单6-1中,我们先关闭标准输出文件描述符STDOUT_FILENO(其值是1),然后复制socket文件描述符connfd。

因为dup总是返回系统中最小的可用文件描述符,所以它的返回值实际上一是之前关闭的标准输出文件描述符的值。

这样一来,服务器输出到标准输出的内容(这里是“abcd”)就会直接发送到与客户连接对应的socket上,因此printf调用的输出将被客户端获得(而不是显示在服务器程序的终端上)。

这就是CGI服务器的基本工作原理。

3. readv函数和writev函数

readv函数将数据从文件描述符读到分散的内存块中,即分散读;writev函数则将多块分散的内存数据一并写入文件描述符中,即集中写。它们的定义如下:

1
2
3
#include<sys/uio.h> 
ssize_t readv(int fd,const struct iovec*vector,int count);
ssize_t writev(int fd,const struct iovec*vector,int count);

  • fd参数是被操作的目标文件描述符。
  • vector参数的类型是iovec结构数组。我们在第5章讨论过结构体iovec,该结构体描述一块内存区。
  • count参数是vector数组的长度,即有多少块内存数据需要从fd读出或写到fd。
  • readv和writev在成功时返回读出/写入fd的字节数,失败则返回-1并设置errno。它们相当于简化版的recvmsg和sendmsg函数。

考虑第4章讨论过的Web服务器。当Web服务器解析完一个HTTP请求之后,如果目标文档存在且客户具有读取该文档的权限,那么它就需要发送一个HTTP应答来传输该文档。这个HTTP应答包含1个状态行、多个头部字段、1个空行和文档的内容。其中,前3部分的内容可能被Web服务器放置在一块内存中,而文档的内容则通常被读入到另外一块单独的内存中(通过read函数或mmap函数)。我们并不需要把这两部分内容拼接到一起再发送,而是可以使用writev函数将它们同时写出,如代码清单6-2所示。

代码清单6-2 Web服务器上的集中写

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#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/stat.h>
#include<sys/types.h>
#include<fcntl.h>

#define BUFFER_SIZE 1024
/*定义两种HTTP状态码和状态信息*/
static const char*status_line[2]={"200 OK","500 Internal server error"};

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];
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 {
/*用于保存HTTP应答的状态行、头部字段和一个空行的缓存区*/
char header_buf[BUFFER_SIZE];
memset(header_buf,'\0',BUFFER_SIZE);
/*用于存放目标文件内容的应用程序缓存*/
char*file_buf;
/*用于获取目标文件的属性,比如是否为目录,文件大小等*/
struct stat file_stat;
/*记录目标文件是否是有效文件*/
bool valid=true;
/*缓存区header_buf目前已经使用了多少字节的空间*/
int len=0;
if(stat(file_name,&file_stat)<0)/*目标文件不存在*/ {
valid=false;
} else {
if(S_ISDIR(file_stat.st_mode))/*目标文件是一个目录*/ {
valid=false;
} else if(file_stat.st_mode&S_IROTH)/*当前用户有读取目标文件的权限*/ {
/*动态分配缓存区file_buf,并指定其大小为目标文件的大小file_stat.st_size
加1,然后将目标文件读入缓存区file_buf中*/
int fd=open(file_name,O_RDONLY);
file_buf=new char[file_stat.st_size+1];
memset(file_buf,'\0',file_stat.st_size+1);
if(read(fd,file_buf,file_stat.st_size)<0) {
valid=false;
}
} else {
valid=false;
}

}
/*如果目标文件有效,则发送正常的HTTP应答*/
if(valid) {
/*下面这部分内容将HTTP应答的状态行、“Content-Length”头部字段和一个空行依
次加入header_buf中*/
ret=snprintf(header_buf,BUFFER_SIZE-1,"%s%s\r\n","HTTP/1.1",status_line[0]);
len+=ret;
ret=snprintf(header_buf+len,BUFFER_SIZE-1-len,"Content-Length:%d\r\n",file_stat.st_size);
len+=ret;
ret=snprintf(header_buf+len,BUFFER_SIZE-1-len,"%s","\r\n");
/*利用writev将header_buf和file_buf的内容一并写出*/
struct iovec iv[2];
iv[0].iov_base=header_buf;
iv[0].iov_len=strlen(header_buf);
iv[1].iov_base=file_buf;
iv[1].iov_len=file_stat.st_size;
ret=writev(connfd,iv,2);
} else/*如果目标文件无效,则通知客户端服务器发生了“内部错误”*/ {
ret=snprintf(header_buf,BUFFER_SIZE-1,"%s%s\r\n","HTTP/1.1",status_line[1]);
len+=ret;
ret=snprintf(header_buf+len,BUFFER_SIZE-1-len,"%s","\r\n");
send(connfd,header_buf,strlen(header_buf),0);
}
close(connfd);
delete[]file_buf;
}
close(sock);
return 0;
}

代码清单6-2中,我们省略了HTTP请求的接收及解析,因为现在关注的重点是HTTP应答的发送。我们直接将目标文件作为第3个参数传递给服务器程序,客户telnet到该服务器上即可获得该文件。关于HTTP请求的解析,我们将在第8章给出相关代码。

4. sendfile函数

sendfile函数在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这被称为零拷贝。sendfile函数的定义如下:

1
2
#include<sys/sendfile.h> 
ssize_t sendfile(int out_fd,int in_fd,off_t*offset,size_t count);

  • in_fd参数是待读出内容的文件描述符,
  • out_fd参数是待写入内容的文件描述符。
  • offset参数指定从读入文件流的哪个位置开始读,如果不为空,则使用读入文件流默认的起始位置。
  • count参数指定在文件描述符in_fd和out_fd之间传输的字节数。
  • sendfile成功时返回传输的字节数,失败则返回-1并设置errno。
  • 该函数的man手册明确指出,in_fd必须是一个支持类似mmap函数的文件描述符,即它必须指向真实的文件,不能是socket和管道;而out_fd则必须是一个socket。
  • 由此可见,sendfile几乎是专门为在网络上传输文件而设计的。下面的代码清单6-3利用sendfile函数将服务器上的一个文件传送给客户端。

代码清单6-3 用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
#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;
}
代码清单6-3中,我们将目标文件作为第3个参数传递给服务器程序,客户telnet到该服务器上即可获得该文件。相比代码清单6-2,代码清单6-3没有为目标文件分配任何用户空间的缓存,也没有执行读取文件的操作,但同样实现了文件的发送,其效率显然要高得多。

5. mmap函数和munmap函数

mmap函数用于申请一段内存空间。我们可以将这段内存作为进程间通信的共享内存,也可以将文件直接映射到其中。munmap函数则释放由mmap创建的这段内存空间。它们的定义如下:

1
2
3
#include<sys/mman.h> 
void* mmap(void*start,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void*start,size_t length);

  • start参数允许用户使用某个特定的地址作为这段内存的起始地址。如果它被设置成NULL,则系统自动分配一个地址。

  • length参数指定内存段的长度。

  • prot参数用来设置内存段的访问权限。它可以取以下几个值的按位或:

    • PROT_READ,内存段可读。
    • PROT_WRITE,内存段可写。
    • PROT_EXEC,内存段可执行。
    • PROT_NONE,内存段不能被访问。
  • flags参数控制内存段内容被修改后程序的行为。它可以被设置为表6-1中的某些值(这里仅列出了常用的值)的按位或(其中MAP_SHARED和MAP_PRIVATE是互斥的,不能同时指定)。

  • fd参数是被映射文件对应的文件描述符。它一般通过open系统调用获得。
  • offset参数设置从文件的何处开始映射(对于不需要读入整个文件的情况)。
  • mmap函数成功时返回指向目标内存区域的指针,失败则返回MAP_FAILED((void*)-1)并设置errno。
  • munmap函数成功时返回0,失败则返回-1并设置errno。

将在第13章进一步讨论如何利用mmap函数实现进程间共享内存。

6. splice函数

splice函数用于在两个文件描述符之间移动数据,也是零拷贝操作。splice函数的定义如下:

1
2
#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参数则控制数据如何移动,它可以被设置为表6-2中的某些值的按位或。

使用splice函数时,fd_in和fd_out必须至少有一个是管道文件描述符。splice函数调用成功时返回移动字节的数量。它可能返回0,表示没有数据需要移动,这发生在从管道中读取数据(fd_in是管道文件描述符)而该管道没有被写入任何数据时。splice函数失败时返回-1并设置errno。常见的errno如表6-3所示。

下面我们使用splice函数来实现一个零拷贝的回射服务器,它将客户端发送的数据原样返回给客户端,具体实现如代码清单6-4所示。

代码清单6-4 使用splice函数实现的回射服务器

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
#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);/*创建管道*/
/*将connfd上流入的客户数据定向到管道中*/
ret=splice(connfd,NULL,pipefd[1],NULL,32768,SPLICE_F_MORE|SPLICE_
F_MOVE);
assert(ret!=-1);
/*将管道的输出定向到connfd客户连接文件描述符*/
ret=splice(pipefd[0],NULL,connfd,NULL,32768,SPLICE_F_MORE|SPLICE_
F_MOVE);
assert(ret!=-1);
close(connfd);
}
close(sock);
return 0;
}
我们通过splice函数将客户端的内容读入到pipefd[1]中,然后再使用splice函数从pipefd[0]中读出该内容到客户端,从而实现了简单高效的回射服务。整个过程未执行recv/send操作,因此也未涉及用户空间和内核空间之间的数据拷贝。

7. tee函数

tee函数在两个管道文件描述符之间复制数据,也是零拷贝操作。它不消耗数据,因此源文件描述符上的数据仍然可以用于后续的读操作。tee函数的原型如下:

1
2
#include<fcntl.h> 
ssize_t tee(int fd_in,int fd_out,size_t len,unsigned int flags);

  • 该函数的参数的含义与splice相同(但fd_in和fd_out必须都是管道文件描述符)。
  • tee函数成功时返回在两个文件描述符之间复制的数据数量(字节数)。
  • 返回0表示没有复制任何数据。tee失败时返回-1并设置errno。

代码清单6-5利用tee函数和splice函数,实现了Linux下tee程序(同时输出数据到终端和文件的程序,不要和tee函数混淆)的基本功能。

代码清单6-5 同时输出数据到终端和文件的程序

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
//filename:tee.cpp 
#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);
/*将标准输入内容输入管道pipefd_stdout*/
ret=splice(STDIN_FILENO,NULL,pipefd_stdout[1],NULL,32768,SPLICE_
F_MORE|SPLICE_F_MOVE);
assert(ret!=-1);
/*将管道pipefd_stdout的输出复制到管道pipefd_file的输入端*/
ret=tee(pipefd_stdout[0],pipefd_file[1],32768,SPLICE_F_NONBLOCK)
;
assert(ret!=-1);
/*将管道pipefd_file的输出定向到文件描述符filefd上,从而将标准输入的内容写
入文件*/
ret=splice(pipefd_file[0],NULL,filefd,NULL,32768,SPLICE_F_MORE|SPLICE_F_MOVE);
assert(ret!=-1);
/*将管道pipefd_stdout的输出定向到标准输出,其内容和写入文件的内容完全一致
*/
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;

}

8. fcntl函数

fcntl函数,正如其名字(file control)描述的那样,提供了对文件描述符的各种控制操作。另外一个常见的控制文件描述符属性和行为的系统调用是ioctl,而且ioctl比fcntl能够执行更多的控制。但是,对于控制文件描述符常用的属性和行为,fcntl函数是由POSIX规范指定的首选方法。所以本书仅讨论fcntl函数。fcntl函数的定义如下:

1
2
#include<fcntl.h> 
int fcntl(int fd,int cmd,…);

  • fd参数是被操作的文件描述符,
  • cmd参数指定执行何种类型的操作。
  • 根据操作类型的不同,该函数可能还需要第三个可选参数arg。fcntl函数支持的常用操作及其参数如表6-4所示。

  • fcntl函数成功时的返回值如表6-4最后一列所示,失败则返回-1并设置errno。

  • 在网络编程中,fcntl函数通常用来将一个文件描述符设置为非阻塞的,如代码清单6-6所示。

代码清单6-6 将文件描述符设置为非阻塞的

1
2
3
4
5
6
7
int setnonblocking(int fd) { 
int old_option=fcntl(fd,F_GETFL);/*获取文件描述符旧的状态标志*/
int new_option=old_option|O_NONBLOCK;/*设置非阻塞标志*/
fcntl(fd,F_SETFL,new_option);
return old_option;/*返回文件描述符旧的状态标志,以便*/
/*日后恢复该状态标志*/
}

  • 此外,SIGIO和SIGURG这两个信号与其他Linux信号不同,它们必须与某个文件描述符相关联方可使用:
    • 当被关联的文件描述符可读或可写时,系统将触发SIGIO信号;
    • 当被关联的文件描述符(而且必须是一个socket)上有带外数据可读时,系统将触发SIGURG信号。
  • 将信号和文件描述符关联的方法,就是使用fcntl函数为目标文件描述符指定宿主进程或进程组,那么被指定的宿主进程或进程组将捕获这两个信号。
  • 使用SIGIO时,还需要利用fcntl设置其O_ASYNC标志(异步I/O标志,不过SIGIO信号模型并非真正意义上的异步I/O模型,见第8章)。关于信号SIGURG的更多内容,我们将在第10章讨论。

6. 高级I/O函数
http://binbo-zappy.github.io/2024/12/16/Linux高性能服务器编程-游双/6-高级IO函数/
作者
Binbo
发布于
2024年12月16日
许可协议