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这俩个信号与其他的信号不同,他们必须与某个文件描述符相关联方可使用;