Linux高级I/O函数

总览

pipe创建了一个管道,用于进程通信,其是半双工的,数据形式是字节流,默认大小是 2 16 B 2^{16}B 216B,可以使用文件控制函数fcntl来修改。fd[0]是读端,fd[1]是写端

socketpair可以创建一个双向管道来进行通信,只可以使用协议族AF_UNIX。

dup创建一个新的文件描述符,与fd指向同一个文件、管道或socket,特点是创建的fd的id总是可以获取到的最小的fd_id。dup2返回一个大于规定值的文件描述符

readvwritev实现了分散读和集中写。将fd里的内容读到指定的分散的内存里去,而writev将分散在内存里的文件集中写入到fd中去

零拷贝 零拷贝说明的一篇文章

sendfile将一个真实文件发送给socket,实现了零拷贝(不必在用户空间和内核空间之间拷贝)。数据流向是fd_in->fd_out(真实文件->socket)。

mmapmunmap申请一片内存空间,可以作为共享内存来实现进程间通信,也可以将文件映射到其中去,避免了频繁读写的I/O开销。

splice零拷贝的,将fd_in中的数据移动到fd_out去(例如:将socket的数据写入到管道,将管道的数据写入到socket)

tee零拷贝的,在两个文件描述符(管道)间复制数据。注意与splice区别,它不会消耗数据,对fd_in里的数据没影响。

fcntl可以修改文件描述符的属性

管道

pipe

pipe函数用来创建一个管道,用以实现函数间的通信。

#include<unistd.h>
int pipe(int fd[2]);

参数是两个int型整数指针,成功返回0,并将一对打开的文件描述符填入其参数指向的数组。
fd[0]是读端,fd[1]是写端。
管道是半双工的,只可以单向数据传输。默认情况下这一对文件描述符都是阻塞的。调用read函数读取空的管道,将被阻塞直到有足够的数据。write写,没有足够的空间也将被阻塞到有足够写入空间为止。
在这里插入图片描述
管道内部的数据是字节流,默认的容量限制是 2 16 = 65536 B 2^{16} = 65536 B 216=65536B,可以使用fcntl函数修改其大小。

socketpair

可以创建双向管道
int socketpair(int domain, int type, int protocol, int fd[2])
其中前三个参数与socket相同。但是协议族只可以使用AF_UNIX,因为其是本地管道。最后的fd创建了文件描述符都是可读可写的。

dup和dup2

int dup(int fd);
int dup2(int fd_1, int fd_2);

dup创建一个新的新建文件描述符,该文件描述符指向fd的相同文件、管道或网络连接。且dup返回的文件描述符总是取系统可用的最小的文件描述符。
dup2与dup相似,返回第一个不小于fd2的整数值。
调用失败时返回-1

Notice dup和dup2创建的文件描述符不继承原文件描述符的属性,如non-blocking等。

应用例子——CGI服务器

核心函数

sockaddr_in client;
socklen_t client_len = sizeof(client);
int connfd = accept(sock, (sockaddr*)&client, &client_len);
if(connfd >= 0){
	close(STDOUT_FILENO);
	dup(connfd);
	// printf内容将被发送到客户端
	printf("Hello, this is a CGI server");
	close(connfd);
};

STDOUT_FILENO的值是1,是标准输出文件。我们关闭它后,使用了dup复制socket文件描述符,根据dup的特性,总是分配最小可用的文件描述符,所以返回值是1。这样一来服务器的标准输出内容会被直接发送到与客户端相连的socket上。

readv和writev

#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);

readv函数实现了分散读,即将fd中的数据读取到分散的内存中取。
writev函数实现了集中写,即将分散内存中的数据一并写入到fd中去。
在这里插入图片描述

sendfile

只能用于将真实文件传递给一个指向socket的文件描述符
sendflie实现了零拷贝。即在两个文件描述符之间直接传递数据(完全在内核中操作),避免了用户缓冲区和内核缓冲区之间的拷贝。

#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之间传递的字节数。 函数失败返回-1,成功则返回传递的字节数。
in_fd必须是一个指向真实文件的fd,不可以是管道和socket。out_fd必须是一个socket。
example

const char* file_name = "file name";
int filefd = open(file_name, O_RDONLY);
assert(filefd > 0);
struct stat stat_buf;
fstat(filefd, &stat_buf);
/*
新建一个socket->sock
*/
int connfd = connect(sock, (sockaddr*)&client, &client_len);
assert(connfd >= 0);

sendfile(connfd, filefd, NULL, stat_buf.st_size);
close(connfd);

close(sock);
close(filefd);

return 0;

mmap和munmap

mmap可以申请一段内存空间,可以作为进程间通信的共享内存,也可以将文件直接映射到其中。munmap负责释放mmap申请的内存。
mmap相关博客1,写的不错
mmap相关博客2,写的不错

#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控制内存段内容被修改后程序的行为, 按位或可以同时指定多个。MAP_SHARED与MAP_PRIVATE互斥,不可同时指定。
在这里插入图片描述
fd是被映射文件的文件描述符,一般通过open打开,offset可以设置从何处打开。

splice

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

#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_out
调用splice时,fd_in和fd_out至少有一个必须是管道。
在这里插入图片描述

example——反射服务器

将客户端发送的内容发回给客户端

int connfd = accpet(sock, (sockaddr*)&client, &client_len);
assert(connfd >= 0);

int pipefd[2];
re = pipe(pipefd); // create pipe
assert(re != -1);
// 将客户端的数据读取到管道中去
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);

我们没有使用recv/send函数,而是使用了splice函数,因此没有涉及到用户空间和内核空间的数据拷贝,因而很高效。

tee

在两个管道文件描述符之间复制数据,也是零拷贝,不消耗数据,因此源文件描述符上的数据不受影响。

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

数据流向 :fd_in -> fd_out
参数含义与splice相同。调用成功时返回两个文件描述符间传递的数据量。失败传-1。

example

同时输出数据到终端和文件

int filefd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0666);
int pipefd_stdout[2], pipefd_file[2];
pipe(pipefd_stdout[2]);
pipe(pipefd_file);

// 将标准输入内容输入到管道stdout
splice(STDIN_FILENO, NULL, pipefd_stdout[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
// 将管道stdout的输出复制到file的输入端
tee(pipefd_stdout[0], pipefd_file[1], 32768, SPLICE_F_NONBLOCK);
// 将管道file的输出定向到文件描述符上,从而将标准输入的内容写到文件
splice(pipefd_file[0], NULL, filefd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
// 将管道stdout的输出文件定向到标准输出,其内容与上面一样
splice(pipefd_stdout[0], NULL, STDOUT_FILENO, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);

fcntl

是file control的简写,提供了对文件描述符的各种控制。(也可以用ioctl)

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

在这里插入图片描述
在这里插入图片描述

example

将一个文件描述符设置为非阻塞的

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; // 返回旧的,供恢复操作
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值