readv函数将文件描述符读取到分散的内存存快中,分散读,writev把分散的数据一并写入文件描述符中,集中写。
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结构数组,该结构体描述一块内存区。count参数是vector数组的长度,就是有所少块内存数据需要从fd中读出或者写到fd。readv和writev成功时返回读出/写入的fd的字节数,失败返回-1,并设置errno,简化版的recvmsg和sendmsg。
web服务器上的集中写:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#define buflen 1024
static const char* status_line[2] = {"200 OK","500 Internal server error"};
int main(int argc,char* argv[])
{
const char* ip = argv[1];
int port = atoi(argv[2]);
const char* file_name = argv[3];
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_port =htons(port) ;
inet_pton(AF_INET,ip,&address.sin_addr);
int sock = socket(AF_INET,SOCK_STREAM,0);
int ret = bind(sock,(struct sockaddr*)&address,sizeof(address));
ret = listen(sock,5);
struct sockaddr_in client;
socklen_t client_addrlength = sizeof(client);
int connfd = accept(sock,(struct sockaddr* )&client,&client_addrlength);
char header_buf[buflen];
char * file_buf;
struct stat file_stat;
bool valid = true;
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){
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+1)<0){valid = false;}
}
else{
valid = false;
}
if(valid){
ret = snprintf(header_buf,buflen-1,"%s %s \r\n","HTTP/1.1",status_line[1]);
len += ret;
ret = snprintf(header_buf+len,buflen-1-len,"Content-Length:%d\r\n",file_stat.st_size);
len+=ret;
ret = snprintf(header_buf+len,buflen-1-len,"%s","\r\n");
struct iovec iv[2];
iv[0].iov_base = header_buf;
iv[1].iov_len = strlen(header_buf);
iv[2].iov_base = file_buf;
iv[3].iov_len = file_stat.st_size;
ret = writev(connfd,iv,2);
}
else{
ret = snprintf(header_buf,buflen-1,"%s %s \r\n","http/1.1",status_line[1]);
len += ret ;
ret = snprintf(header_buf+len, buflen-1-len,"%s","\r\n");
send(connfd,header_buf,strlen(header_buf),0);
}
close(connfd);
delete[] file_buf;
}
close(sock);
return 0;
}
sendfile在俩个文件描述符之间传递数据(在内核中操作),避免了内核缓冲区和用户缓冲区之间的拷贝,效率很高,零拷贝。
#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参数指定文件描述符之间的传输的字节数。sendfile成功时候,返回传输的字节数,失败返回-1并设置errno。另外in_fd必须指向真实的文件,而不能是socket或是管道。而out_fd必须是一个socket。由此可见sendfile是专门为网络传输文件而设计的。
例子:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
int main(int argc,char* argv[])
{
const char* ip = argv[1];
int port = atoi(argv[2]);
const char* file_name = argv[3];
int filefd = open(file_name,O_RDONLY);
struct stat stat_buf;
fstat(filefd,&stat_buf);
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_port = htons(port);
inet_pton(AF_INET,ip,&address.sin_addr);
int sock = socket(PF_INET,SOCK_STREAM,0);
int ret = bind(sock,(struct sockaddr*)&address,sizeof(address));
ret = listen(sock,5);
struct sockaddr_in client;
socklen_t client_addrlength = sizeof(client);
int connfd = accept(sock,(struct sockaddr*)&client,&client_addrlength);
sendfile(connfd,filefd,NULL,stat_buf.st_size);
close(sock);
return 0;
}
6、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_in是一个管道文件描述符,那么off_in参数必须被奢姿为NULL。如果fd_in不似一个管道文件描述符(socket),那么off_in表示从输入数据流的何处开始读取数据。此时,若off_in被设置为null,则表示从输入数据流的当前偏移位置读入;若off_in不为null,则他将指出具体的偏移位置。fd_out/off_out参数的含义与fd_in/off_in相同,不过用于输出数据流。冷参数指定移动数据的长度;
使用splice()时候,fd_in和fd_out必须至少有一个管道文件描述符。splice调用成功时,返回移动字节的数量。
案例:我们使用splice函数实现一个零拷贝的回射服务器,将客户端发送的数据原样返回给客户端。
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <iostream>
using namespace std;
int main(int argc,char* argv[]){
const char* ip =argv[1];
int port = atoi(argv[2]);
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_port = htons(port);
inet_pton(AF_INET,ip,&address.sin_addr);
int sock= socket(AF_INET,SOCK_STREAM,0);
int ret = bind(sock,(struct sockaddr*)&address,sizeof(address));
ret = listen(sock,5);
struct sockaddr_in client ;
socklen_t client_addrlength = sizeof(client);
int conn = accept(sock,(struct sockaddr*)&client,&client_addrlength);
int pipefd[2];
ret = pipe(pipefd);
ret = splice(pipefd[0],NULL,conn,NULL,32768,SPLICE_F_MORE | SPLICE_F_MOVE);
close(conn);
return 0;
}
7、tee()函数。
tee函数用于在俩个管道文件描述符之间复制数据,也是零拷贝操作。不消耗数据,因此源文件描述符上的数据任然可以用于后续的读操作。tee函数的原型如下;
#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。
8、fcntl()函数。
ioctl)比fcntl()更能够执行更多的控制。
#include<fcntl.h>
int fcntl(int fd, int cmd,...);
fd参数是被操作的文件描述符,cmd执行各种类型的操作。根据操作类型的不同,该函数可能还需要第三个可选参数arg。
另外,SIGIO和SIGURG这俩个信号与其他的信号不同,他们必须与某个文件描述符相关联方可使用;