0%

Linux高级I/O函数


常用Linux网络编程相关的高级I/O函数:

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

pipe函数

pipe函数:创建一个管道,以实现进程间的通信。

1
2
3
4
5
6
7
8
9
10
11
#include<unistd.h> 
int pipe(int fd[2]);
/*
作用:创建管道的两端fd[0]和fd[1],向fd[1]写入的数据可以从fd[0]读出。fd[0]只能用于从管道读出数据,fd[1]只能用于向管道写入数据,不可以反过来用。
如果要实现双向传输,需要建立两个管道。
参数:
fd[2]:包含两个int型的数组指针(传出参数)
返回值:
成功返回0,并将一对打开的文件描述符值,填入参数指向的数组;
失败返回-1并设置errno。
*/

默认情况下,一对文件描述符都是阻塞的。如果用read系统调用来读取一个空的管道,则read将被阻塞,直到管道内有数据可读;如果用write系统调用来往一个满的管道中写入数据,则write亦将被阻塞,直到管道有足够多的空闲空间可用。
如果应用程序将fd[0]和fd[1]都设置为非阻塞的,则read和write会有不同的行为。

如果写端fd[1]的引用计数减少到0,表面没有任何进程需要向管道内写入数据,则该管道的读端fd[0]和read操作将返回0,即读取到了文件结束标记EOF。

如果读端fd[0]的引用计数减少到0,表面没有任何进程需要从管道内读取数据,则该管道的写端fd[1]和write操作将失败,并引发SIGPIPE信号。

管道内部传输的数据是字节流。管道容量的大小默认是65536字节。可以使用fcntl函数来修改管道容量。

socket的基础API中有一个socketpair函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<sys/types.h> 
#include<sys/socket.h>

int socketpair(int domain, int type, int protocol, int fd[2]);
/*
作用:创建双向管道,这对文件描述符都是既可读又可写的。
参数:
domain:底层的协议族。只能使用UNIX本地域协议族AF_UNIX,仅能在本地使用双向管道。
type:指定服务类型。
SOCK_STREAM(TCP)、SOCK_UGRAM(UDP)、SOCK_NONBLOCK(非阻塞的)、SOCK_CLOEXEC
protocol:选择协议,通常由前两个参数决定。
设置为0,使用默认协议
fd[2]:指向两个int型整数。
返回值:
成功返回0,并将一对打开的文件描述符值,填入参数指向的数组;
失败返回-1并设置errno。
*/

dup和dup2函数

把标准输入重定向到一个文件,或者把标准输出重定向到一个网络连接。通过用于复制文件描述符的dup或dup2函数实现:

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> 

int dup(int file_descriptor);
/*
作用:复制文件描述符
参数:
file_descriptor:旧的文件描述符
返回值:成功,返回新的文件描述符;失败,返回-1
fd = 3, int fd1 = dup(fd);
fd指向a.txt, fd1也指向a.txt,从空闲的文件描述符表中找一个最小的,作为新的拷贝的文件描述符
*/

int dup2(int file_descriptor_one, int file_descriptor_two);
/*
作用:重定向文件描述符
参数:
file_descriptor_one:旧的文件描述符,必须是一个有效的文件描述符
file_descriptor_two:新的文件描述符,和file_descriptor_one相同,相当于什么都没做
返回值:
成功,返回新的文件描述符;失败,返回-1
file_descriptor_one 指向a.txt,file_descriptor_two 指向b.txt,
调用函数成功后:file_descriptor_two 和b.txt 做close,file_descriptor_two 指向a.txt
*/

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

利用dup函数实现了一个基本的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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 6-1testdup.cpp
#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] );

// 创建socket地址结构体
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 );

// 创建TCP类型的socket
int sock = socket( PF_INET, SOCK_STREAM, 0 );
assert( sock >= 0 );

// 绑定socket
int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) );
assert( ret != -1 );

// 监听socket连接
ret = listen( sock, 5 );
assert( ret != -1 );

// 客户端socket地址
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 ); // 关闭标准输出文件描述符(STDOUT_FILENO值为1)
dup( connfd ); // 复制connfd,这里dup返回值实际上是1,即之前关闭的标准输出文件描述符的值
printf( "abcd\n" ); // 服务器输出到标准输出的内容,会直接发送到与客户连接对应的socket上
// 此printf调用的输出将被客户端获得(而不是显示在服务器 程序的终端上)
close( connfd );
}

close( sock ); // 关闭本地连接
return 0;
}

readv和writev函数

readv函数:将数据从文件描述符读到分散的内存块中,分散读。
writev函数:将多块分散的内存数据一并写入文件描述符中,集中写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<sys/uio.h>

ssize_t readv(int fd, const struct iovec* vector, int count);
/*
参数:
fd:目标文件描述符
vector:指向iovec结构数组,该结构体描述一块内存区
count:vector数组的长度,即有多少内存数据需要从fd读出。
返回值:
成功返回读出fd的字节数,失败则返回-1并设置errno,简化版的recvmsg函数
*/

ssize_t writev(int fd, const struct iovec* vector, int count);
/*
参数:
fd:目标文件描述符
vector:指向iovec结构数组,该结构体描述一块内存区
count:vector数组的长度,即有多少内存数据需要写入到fd。
返回值:
成功返回写入fd的字节数,失败则返回-1并设置errno,简化版的sendmsg函数
*/

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// 6-2testwritev.cpp
#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>
#include <sys/uio.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]; // 将目标文件作为程序的第三个参数传入

// 创建socket地址
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 );

// 创建socket文件描述符
int sock = socket( PF_INET, SOCK_STREAM, 0 );
assert( sock >= 0 );

// 绑定socket地址信息
int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) );
assert( ret != -1 );

// 监听socket连接
ret = listen( sock, 5 );
assert( ret != -1 );

// 创建客户端socket地址
struct sockaddr_in client;
socklen_t client_addrlength = sizeof( client );
// 接受socket连接
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; // 记录目标文件是否是有效文件

int len = 0; // 缓存区header_buf目前已经使用了多少字节的空间

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

if( valid ) // 如果目标文件有效,则发送正常的HTTP应答
{
// 将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;
}

sendfile函数

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

1
2
3
4
5
6
7
8
9
10
11
12
#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之间传输的字节数
返回值:
成功返回传输的字节数,失 败则返回-1并设置errno
*/

注: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
62
63
64
65
66
67
68
69
70
71
72
73
// 6-3testsendfile.cpp
#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 );

// 创建socket地址
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 );

// 创建socket文件描述符
int sock = socket( PF_INET, SOCK_STREAM, 0 );
assert( sock >= 0 );

// 命名socket
int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) );
assert( ret != -1 );

// 监听socket连接
ret = listen( sock, 5 );
assert( ret != -1 );

// 客户端socket地址
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发送数据
sendfile( connfd, filefd, NULL, stat_buf.st_size );
close( connfd ); // 关闭客户端连接
}

close( sock ); // 关闭本地连接
return 0;
}

将目标文件作为第3个参数传递给服务器程序,客户telnet到该服务器上即可获得该文件。
与6-2estwritev.cpp相比,6-3testsendfile.cpp没有为目标文件分布任何用户空间的缓存,也没有执行读取文件的操作,同样实现了文件的发送,显然效率更高。

mmap和munmap函数

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

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
#include<sys/mman.h> 

void* mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset);
/*
作用:申请内存空间,以实现进程间共享内存
参数:
start:允许用户使用某个特定的地址作为这段内存的起始地址,设置成NULL,则系统自动分配一个地址
length:指定内存的长度
prot:设置内存段的访问权限,取值有:
PROT_READ,内存段可读
PROT_WRITE,内存段可写
PROT_EXEC,内存段可执行
PROT_NONE,内存段不能被访问
flags:控制内存段内容被修改后程序的行为,见表6-1按位或
(MAP_SHARED和MAP_PRIVATE是互斥的,不能同时指定)
fd:被映射文件对应的文件描述符
offset:设置从文件的何处开始映射
返回值:
成功返回指向目标内存区域的指针,失败则返回 MAP_FAILED((void*)-1)并设置errno
*/

int munmap(void* start,size_t length);
/*
作用:释放空间
参数:参考mmap
返回值:
成功时返回0,失败则返回-1并设置errno。

*/

image-20220725234309852

splice函数

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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;
off_in:从输入数据流的何处开始读取数据,设置为NULL表示从输入数据流的当前偏移位置读入
fd_out:输出数据流的文件描述符
off_out:输出数据流的偏移量
len:指定移动数据的长度
flags:控制数据如何移动,它可以被设置为表6-2中的某些值的按位或
返回值:
成功时返回移动字节的数量。可能返回0,表示没有数据需要移动(从管道中读取数据(fd_in是管道文件描述 符)而该管道没有被写入任何数据);
失败返回-1并设置 errno
*/

使用splice函数时,fd_in和fd_out必须至少有一个是管道文件描述符。

image-20220725234325910

image-20220725234520263

使用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
54
55
56
57
58
59
60
61
62
63
64
65
// 6-4testsplice.cpp
#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上流入的客户数据定向到管道中,splice函数将客户端的内容读入到pipefd[1]中
ret = splice( connfd, NULL, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );
assert( ret != -1 );

// 将管道的输出定向到connfd客户连接文件描述符,splice函数从pipefd[0]中读出该内容到客户端
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操作,因此也未涉及用户空间和内核空间之间的数据拷贝。

tee函数

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

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<fcntl.h> 
ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags);
/*
参数:
fd_in:待输入数据的管道文件描述符
fd_out:管道文件描述符
len:指定移动数据的长度
flags:控制数据如何移动
返回值:
成功时返回在两个文件描述符之间复制的数据数量(字节数)。
返回0表示没有复制任何数据。
失败时返回-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
45
46
47
48
49
50
51
52
53
54
55
// 6-5testtee.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 );

//close( STDIN_FILENO );
// dup2( pipefd_stdout[1], STDIN_FILENO );
//write( pipefd_stdout[1], "abc\n", 4 );

// 将标准输入内容输入管道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;
}

fcntl函数

fcntl函数:提供了对文件描述符的各种控制操作。对于控制文件描述符常用的属性和行为,fcntl函数是由POSIX规范指定的首选方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<fcntl.h> 
int fcntl(int fd, int cmd,…);
/*
作用:获取或修改文件的属性
参数:
fd:需要操作的文件描述符
cmd:对文件描述符进行如何操作
- F_DUPFD:复制第一个参数文件描述符fd,得到一个新的文件描述符(返回值)
int ret = fnctl(fd, F_DUPFD);
- F_GETFL:获取指定的文件描述符的文件状态flag(与open函数传递的文件权限flag是相同的)
- F_SETFL:设置文件描述符的文件状态flag
必选项:O_RONLY, O_WRONLY, O_RDWR 不可以被修改(文件访问、创建权限)
可选项:O_APPEND, O_NONBLOCK
O_APPEND 表示追加数据
O_NONBLOCK 设置成非阻塞

*/

在网络编程中,fcntl函数通常用来将一个文件描述符设置为非阻塞的。

将文件描述符设置成非阻塞的:

1
2
3
4
5
6
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; /*返回文件描述符旧的状态标志,以便日后恢复该状态标志*/
}

image-20220726152218986

image-20220726152238201

SIGIO和SIGURG这两个信号与其他Linux信号不同,它们必须与某个文件描述符相关联方可使用:当被关联的文件描述符可读或可写时,系统将触发SIGIO信号;当被关联的文件描述符(而且必须是一个socket)上有带外数据可读时,系统将触发SIGURG信号。
使用SIGIO时,还需要利用fcntl设置其O_ASYNC标志(异步I/O标志,不过SIGIO信号模型并非真正意义上的异步I/O模型)。