- 用于创建文件描述符函数
- 用于读写数据函数
- 用于控制IO行为和属性的函数
6.1 pipe函数
- 用于创建管道,实现进程之间的通信。
- pipe函数定义
#include <unistd.h>
//成功返回0,失败返回-1并设置errno
int pipe(int fd[2]);
- 往
fd[1]
写入的数据可以从fd[0]
读出。 fd[1]
只能用于数据写入。fd[0]
只能用于数据读出。- 用
read
读取一个空的管道,则read会阻塞,直到管道有可读。 - 用
write
写入一个满的管道,则write会阻塞,直到管道有空闲可写。 - 如果写端
fd[1]
的引用计数减少至0,也就是都关闭了,那么读端的read操作会返回0,也就是读到末尾。 - 同理如果读端的
fd[0]
的引用计数都减少到0,也就是都关闭了,那么写端的write操作会失败,并引发SIGPIPE信号。 - 管道大小有容量限制,默认65536字节,可以用fcntl函数修改容量。
- 有一个双向管道:domain只能使用UNIX本地协议族AF_UNIX,所以socketpair只能在本地使用,不过创造的fd一端都是可读可写的。
#include <sys/types.h>
#include <sys/socket.h>
//成功返回0,失败返回-1设置errno
int socketpair(int domain, int type, int protocol, int fd[2]);
6.2 dup函数和dup2函数
- 可以将标准输入重定向到一个文件,或者标准输出重定向到一个网络连接,可以使用复制文件描述符的dup和dup2
#include <unistd.h>
int dup(int file_descriptor);
int dup2(int file_descriptor_one, int file_descriptor_two);
- dup函数创建一个新的文件描述符,该文件描述符相当于原有文件描述符file_descriptor的一个副本,指向相同的文件、管道或者网络连接,但是并不继承原有文件描述符的属性。
- dup2类似,会返回一个不小于file_descriptor_two的整数值,常常表现为把file_descriptor_two覆盖掉,使之指向file_descriptor_one的文件。
- 所以可以用这个来改变原来文件描述符的指向。
- 下边的例子是打开了一个1.txt文件,并改变标准输出的文件描述符的指向,使其输出到1.txt文件夹中。
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
int main(){
int fd = open("1.txt", O_RDWR|O_TRUNC|O_CREAT);
//因为标准输出的文件描述符最小,是1,所以dup肯定就取到1了。
close(STDOUT_FILENO);
dup(fd);
printf("123456\n");
return 0;
}
/*1.txt内容
123456
*/
使用dup2
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
int main(){
int fd = open("1.txt", O_RDWR|O_TRUNC|O_CREAT);
dup2(fd, STDOUT_FILENO);
printf("123456\n");
return 0;
}
/*1.txt内容
123456
*/
6.3 readv函数和writev函数
- 这个函数和之前的TCP、UDP通用数据读写的recvmsg和sendmsg有点类似,都是分散读和集中写,不同的是后者只能用于socket的读写,而且经过简化的。
#include <sys/uio.h>
//成功返回读写的字节数,失败返回-1并设置errno
ssize_t readv(int fd, const struct iovec* vector, int count); //分散读
ssize_t writev(int fd, const struct iovec* vector, int count); //集中写
struct iovec{
void *iov_base; //内存块起始地址
size_t iov_len; //内存块长度
};
- fd是目标文件描述符,vector是iovec结构数组,count数组长度。
- 例子,创建一个端口,发送指定的文件,文件和HTTP头部分别用集中写执行。
//使用writev分散写,写头部文件和给定的目标文件
#include "../create_sockfd.h"
#include "../filecheck.h"
#include "stdio.h"
#include "stdlib.h"
#include "errno.h"
#include <stdio.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* fileName = argv[3];
createSockfd sockfd(argv[1], atoi(argv[2]));
assert(sockfd.bindSockfd() != -1);
assert(sockfd.listenfd(5) != -1);
sockfd.acceptfd();
if(sockfd.connfd < 0) {
printf("Accept error: %d\n", errno);
} else {
//保存头部信息
char header_buf[BUFFER_SIZE];
memset(header_buf, '\0', BUFFER_SIZE);
//记录header用了多少空间
int len = 0;
//用于存放目标文件的缓存
char *fileBuf;
//存放文件信息
struct stat file_stat;
if(fileCheck(fileName, &file_stat, &fileBuf)){
int 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, "Content-Length: %d\r\n", int(file_stat.st_size));
len += ret;
ret = snprintf(header_buf + len, BUFFER_SIZE - 1 - len, "%s", "\r\n");
len += ret;
struct iovec iv[2];
iv[0].iov_base = header_buf;
iv[0].iov_len = strlen(header_buf);
iv[1].iov_base = fileBuf;
iv[1].iov_len = file_stat.st_size;
writev(sockfd.connfd, iv, 2);
delete [] fileBuf;
} else {
int 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(sockfd.connfd, header_buf, strlen(header_buf), 0);
}
}
return 0;
}
- 下边是filecheck.h的内容,负责判断文件是否存在,存在的话读取文件到fileBuf,不存在返回false;
//检查给定的名字是否是文件而不是文件夹,是否有全新进行读取
#include <sys/stat.h>
#include <fcntl.h>
#include "stdio.h"
bool fileCheck(const char *filename, struct stat *file_stat, char **fileBuf) {
//小于0代表目标文件不存在。
if(stat(filename, file_stat) < 0){
printf("filename is not exist.\n");
return false;
} else {
//判断目标文件是不是目录
if(S_ISDIR(file_stat->st_mode)) {
printf("filename is dir.\n");
return false;
} else if (!(file_stat->st_mode & S_IROTH)) { //有无读取权限
printf("filename meiyouquanxian.\n");
return false;
}
}
int fd = open(filename, O_RDONLY);
*fileBuf = new char [file_stat->st_size + 1];
memset(*fileBuf, '\0', file_stat->st_size + 1);
//读取失败
if(read(fd, *fileBuf, file_stat->st_size) < 0){
delete [] *fileBuf;
printf("filename read error.\n");
return false;
}
return true;
}
- 如果传指针到函数中,在函数中重新new申请空间,那么改变的只是指针的形参,并不会改变原来的指针的指向,如果想要改变,那就传入指针的指针。
- 测试
$testwritev 127.0.0.1 12345 1.txt
$telnet 127.0.0.1 12345
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
HTTP/1.1 200 OK
Content-Length: 7
123456
Connection closed by foreign host.
- readv的例子
- 把分散读和标准输入连接在一起
- 输出
$ ./testreadv
12
Read bytes: 2
First message: 1
Second message: 2
#include <stdio.h>
#include <sys/uio.h>
#include <string.h>
#define BUF_SIZE 2
int main(int argc, const char * argv[]) {
struct iovec vec[2];
char buf1[BUF_SIZE];
char buf2[BUF_SIZE];
memset(buf1, '\0', BUF_SIZE);
memset(buf2, '\0', BUF_SIZE);
int str_len;
vec[0].iov_base = buf1;
vec[0].iov_len = BUF_SIZE-1;
vec[1].iov_base = buf2;
vec[1].iov_len = BUF_SIZE-1;
//把数据放到多个缓冲中储存
str_len = readv(0, vec, 2); //2是从标准输入接收数据
printf("Read bytes: %d \n", str_len);
printf("First message: %s \n", buf1);
printf("Second message: %s \n", buf2);
return 0;
}
6.4 sendfile函数
- sendfile函数在两个文件描述符之间直接传递数据,内核中操作,避免内核缓冲区和用户缓冲区之间的数据拷贝,效率高。
#include <sys/sendfile.h>
//成功返回传输的字节数,失败返回-1并设置errno
ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count);
- in_fd参数是待读出内容的文件描述符
- out_fd参数待写入内容的文件描述符
- offset参数指定从读入文件流的哪个位置开始读,空,使用默认起始位置
- cout参数指定in_fd和out_fd之间传输的字节数。
-
注意in_fd必须是一个正式的文件,不能是socket和管道,out_fd必须是一个socket
- 例如,使用sendfile传递文件,用telnet测试
#include "../create_sockfd.h"
#include "../filecheck.h"
#include "stdio.h"
#include "stdlib.h"
#include "assert.h"
#include "errno.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;
}
createSockfd sockfd(argv[1], atoi(argv[2]));
assert(sockfd.bindSockfd() != -1);
assert(sockfd.listenfd(5) != -1);
sockfd.acceptfd();
if(sockfd.connfd < 0){
printf("accept error: %d\n", errno);
} else {
struct stat file_stat;
int fd = fileCheck(argv[3], &file_stat);
//使用sendfile
sendfile(sockfd.connfd, fd, NULL, file_stat.st_size);
close(fd);
}
return 0;
}
///放在filecheck.h中的重载函数,不读,直接获取fd
int fileCheck(const char *filename, struct stat *file_stat) {
int fd = open(filename, O_RDONLY);
//打开失败
if (fd < 0)
{
close(fd);
return fd;
}
fstat(fd, file_stat);
return fd;
}
- telnet测试
zsz@ubuntu:~$ telnet 127.0.0.1 12355
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
123456
Connection closed by foreign host.
mmap函数和munmap函数
- 这两个函数是一对,mmap用于申请一段内存空间,这段内存可以用于进程间通信的共享内存,也可以直接将文件映射过去,munmap用于释放这段空间。
#include <sys/mman.h>
//成功返回指向目标区域的指针,失败返回MAP_FAILED((void*) -1),并设置errno
void* mmap(void *start, size_t length, int port, int flags, int fd, off_t offset);
//成功返回0,失败返回-1并设置errno
int munmap(void *start, size_t length);
- start 参数允许用户使用某一个特定的地址作为这段内存的其实地址,如果是设置为NULL,则系统自动分配。
- length指定这段内存的长度
- port指定内存段的访问权限,
- flags参数控制内存段内容被修改后程序的行为
- fd是被映射文件的文件描述符,一般通过open系统调用获取
- offset设置文件从何处来时映射,对于不需要读取整个文件的情况非常好用
6.6 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_in是一个管道文件描述符则off_in必须是NULL,off_in表示输入数据流从何处开始读取数据。
- fd_out和off_out含义类似,用于输出数据里。
- len指定移动数据的长度。
- flags参数控制数据,可以是下表参数的按位或
-
使用splice时,fd_in和fd_out至少有一个是管道文件描述符。
- splice成功时候返回移动的字节数量,失败时返回-1并设置errno
- splice的常见errno
- 使用splice实现的高效回射服务
...
int pipefd[2];
int ret = pipe(pipefd);
//从socket中读取
ret = splice(connfd, NULL, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
//回射
ret = splice(pipefd[0], NULL, connfd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
...
6.7 tee函数
- tee函数在两个管道文件描述符之间复制数据,不消耗数据,而splice从管道中读取数据,也就是消耗数据。
#include <fcntl.h>
ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags);
- 参数含义与splice相同,但是fd_in和fd_out必须都是管道文件描述符
- tee成功时返回两个文件描述符之间复制的数据数量,字节数,失败返回-1并设置errno。
6.8 fcntl函数
- 提供了对文件描述符的各种操作
#include <fcntl.h>
//失败返回-1并设置errno
int fcntl(int fd, int cmd, ...);
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;
}