高级I/O函数(1)-writev、readv、sendfile函数

 1.前言

   Linux提供了很多的高级I/O函数,它们在特定的条件下表现出优秀的特性。这里主要讨论的是和网络编程相关的几个。

  用于读写数据的函数,包括writev/readv、sendfile。

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

struct iovec{
void *iov_base;/*内存的起始地址*/
size_t iov_len; /*内存的长度*/
};

/*fd参数是被操作的文件描述符。结构体iovec描述的是一块内存。count参数是vector数组的长度,即有多少块内存数据需要从fd读出或写到fd。*/
返回值:readv和writev在成功时返回读出或写入fd的字节数。失败时则返回-1并设置errno.

例子:

 利用writev函数实现Web服务器解析完一个HTTP请求之后,如果目标文档存在且客户具有读取该文档的权限,那么它就需要发送一个HTTP应答来传输该文件。这个HTTP应答包含1个状态行、多个头部字段、1个空行和文档的内容.前三个部分的内容可能被Web服务器放置在一块内存中,而文档的内容则通常被读入到另一个内存块,我们就可以使用writev函数将它们同时写出.

#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>

#define BUFFER_SIZE 1024
static const char* status_line[2]={"200 OK","500 Internal error"};

int main(int argc,const char* argv[]){
      if(argc<=3){
            printf("usage:%s ip_address port_number\n",argv[0]);
            exit(-1);
      }
      
      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;
      inet_pton(AF_INET,ip,&address.sin_addr);
      address.sin_port=htons(port);
      
      int sockfd=socket(AF_INET,SOCK_STREAM,0);
      assert(sockfd!=-1);
      
      int ret=bind(sockfd,(struct sockaddr*)&address,sizeof(address));
      assert(ret!=-1);
      
      ret=listen(sockfd,5);
      assert(ret!=-1);
      
      struct sockaddr_in client_address;
      bzero(&client_address,sizeof(client_address));
      int connfd=accept(sockfd,(struct sockaddr*)&address,
      
      if(connfd<0){
          printf("errno is:%d\n",errno);
      }
      else{
          //用于保存HTTP应答的状态行、头部字段和一个空行的缓存区
           char header_buf[BUFFER_SIZE];
           memset(header_buf,'\0',BUFFER_SIZE);
           bool valid=true;
           char * file_buf;
           struct stat file_stat;
           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,BUFFER_SIZE)<0){
                            valid=false;
                      }
                 }
                 
                 else{
                     valid=true;
                 }
           }
           
           /*如果目标文件有效,则发送正常的HTTP应答*/
           if(valid){
                  /*下面这部分内容将HTTP应答状态行、"Content-Length"头部字段和一个
                  空行依次加入header_buf中*/
                    int len=0;
                  ret=snprintf(header_buf,BUFFER_SIZE-1,"%s %s\r\n","HTTP/1.1",
                                              status_line[0]);
                  len+=len;
                  ret=snprintf(header_buf+len,BUFFER_SIZE-1-len,"Content-Length:%d\r\n",
                                                  file_stat.st_size);
                  len+=ret;
                  
                  ret=snprintf(header_buf+len,BUFFER_SIZE-1-len,"%s""\r\n");
                  /*利用writev将header_buf和file_buf的内容一并写出*/
                  struct iovec iv[2];
                  iv[0].iov_base=header_buf;
                  iv[0].iov_len=strlen(header_buf);
                  iv[1].iov_base=file_buf;
                  iv[1].iov_len=file_stat.st_size;
                  
                  ret=writev(connfd,iv,2);
            }
            
            else{//如果目标文件无效,则通知客户端服务器发生了"内部错误"
                ret=snprintf(header_buf,BUFFER_SIZE-1,"%s %s\r\n","HTTP/1.1",status_line[1]);
                len+=ret;
                ret=snprintf(header_buf,BUFFER_SIZE-1-len,"%s","\r\n");
                send(connfd,header_buf,strlen(header_buf),0);
            }
               close(connfd);
               delete [] file_buf;                                   
      }
      
      close(sock);
      return 0;
}

 

  sendfile函数

1 #include <sys/sendfile.h>
2  ssize_t sendfile(int out_fd,int in_fd,off_t* offset,size_t count);
3 /*返回值:成功时返回传输的字节数,失败时则返回-1并设置errno.

函数说明:

  •    in_fd参数是待读出内容的文件描述符,out_fd是待写入内容的文件描述符。offset参数指定从读入文件流的哪个位置开始读,如果为空,则使用读入文件流默认的起始位置。count参数指定在文件描述in_fd和out_fd之间的传输的字节数。
  •   in_fd必须指向真实的文件,不能使socket的管道;而out_fd则必须是一个socket。因此可知,sendfile函数几乎是专门为了网络上传输文件而设计的。
  • 下面是利用sendfile函数将服务器上的一个文件传送给客户端
 1 /*****************利用sendfile函数传送文件***************/
 2 
 3 #include <sys/socket.h>
 4 #include <netinet/in.h>
 5 #include <arpa/inet.h>
 6 #include <stdio.h>
 7 #include <stdlib.h>
 8 #include <string.h>
 9 #include <assert.h>
10 #include <errno.h>
11 #include <fcntl.h>
12 #include <sys/sendfile.h>
13 
14 int main(int argc,const char* argv[]){
15      if(argc<=3){
16            printf("usage:%s ip_addresport_number filename\n",argv[0]);
17            return -1;
18      }
19 
20      const char* ip=argv[1];
21      int  port=atoi(argv[2]);
22      
23      int filefd=open(argv[3],O_RDONLY);
24      assert(filefd!=-1);
25     
26      struct stat stat_buf;
27      fstat(filefd,&stat_buf);
28       
29      struct sockaddr_in address;
30      bzero(&address,sizeof(address));
31      address.sin_family=AF_INET;
32      inet_pton(AF_INET,ip,&address.sin_addr);
33      
34      int sock=socket(AF_INET,SOCK_STREAM,0);
35      assert(sock!=-1);
36 
37 
38       int ret=bind(sock,(strucsockaddr*)&address,sizeof(address));
39       assert(ret!=-1);
40      
41       ret=listen(sock,5);
42       assert(ret!=-1);
43 
44       struct sockaddr_in client;
45       socklen_t addrlen;
46       int connfd=accept(sock,(struct sockaddr*)&client,&addrlen);
47       if(connfd<0){
48             printf("errno is:%d \n",errno);
49       }    
50       
51       else{
52            sendfile(connfd,filefd,NULL,stat_buf.st_size);
53            close(connfd);
54        }
55       
56         close(sock);
57         return 0;
58 }

/*代码没有为目标文件分配任何用户空间的缓存,也没有执行读取文件的操作,但同样实现了文件的发送,其效率显然要高得很多.*/

 

posted @ 2014-09-14 23:27  晓风_7  阅读(838)  评论(0)    收藏  举报