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服务器上的集中写:
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 | #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是专门为网络传输文件而设计的。
例子:
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 | #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函数实现一个零拷贝的回射服务器,将客户端发送的数据原样返回给客户端。
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 | #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这俩个信号与其他的信号不同,他们必须与某个文件描述符相关联方可使用;
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步