Linux零拷贝函数SendFile应用

天涯浪子 提交于 2019-12-07 09:42:05

项目需要,使用linux零拷贝函数SendFile来传输文件。

传统的read/write方式进行网络文件传输的方式,要经过四次copy操作:

硬盘 >> kernel buffer >> user buffer >> kernel socket buffer >> 协议栈

而sendfile() 就是用来简化上面步骤提升性能的。sendfile() 不但能减少切换次数而且还能减少拷贝次数。

硬盘 >> kernel buffer (快速拷贝到kernel socket buffer) >> 协议栈

更加具体的资料请参考:linux的sendfile()系统调用  Linux kernel 的 sendfile 是如何提高性能的

这里提供一种SendFile的具体应用,供大家使用SendFile时参考。程序主要实现了文件下载:

  1. client与server建立tcp连接。
  2. client告知server,我要下载哪个文件。
  3. server将文件传输给client。

其中实现了两种server,一种是通过SendFile发送文件,一种是通过普通的Socket方式发送文件。具体代码如下:

client.cpp:client端,文件接收端

//Client端

#include <arpa/inet.h>  
#include <netinet/in.h>  
#include <sys/types.h> 
#include <sys/socket.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h>
  
#define BUFFER_SIZE 1024 
#define FILE_NAME_MAX_SIZE 256 
  
int main(int argc, char* argv[]) 
{ 
  //用法
  if(argc<4)
  {
	  printf("Usage: ./client serverIP serverPort reqFileName [bufferSize=1024]\n");
	  return 0;
  }
  
  char* serverIP = argv[1];
  int serverPort = atoi(argv[2]);
  char* reqFileName = argv[3];
  
  int bufferSize = BUFFER_SIZE;
  if(argc>=5)
  {
	  bufferSize = atoi(argv[4]);
  }

  // 创建socket,若成功,返回socket描述符 
  int client_socket_fd = socket(AF_INET, SOCK_STREAM, 0); 
  if(client_socket_fd < 0) 
  { 
    perror("Create Socket Failed:"); 
    exit(1); 
  } 
  
  // 声明一个服务器端的socket地址结构,并用服务器那边的IP地址及端口对其进行初始化,用于后面的连接 
  struct sockaddr_in server_addr; 
  memset(&server_addr, 0, sizeof(server_addr)); 
  server_addr.sin_family = AF_INET; 
  if(inet_pton(AF_INET, serverIP, &server_addr.sin_addr) == 0) 
  { 
    perror("Server IP Address Error:"); 
    exit(1); 
  } 
  server_addr.sin_port = htons(serverPort); 
  
  // 向服务器发起连接,连接成功后client_socket_fd代表了客户端和服务器的一个socket连接 
  if(connect(client_socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) 
  { 
    perror("Can Not Connect To Server IP:"); 
    exit(0); 
  } 
  
  char buffer[bufferSize]; 
  memset(buffer, 0, bufferSize); 
  int strLength = strlen(reqFileName);
  if(strLength > bufferSize) strLength = bufferSize;
  strncpy(buffer, reqFileName, strLength); 
  
  // 打开文件,准备写入
  char saveFileName[FILE_NAME_MAX_SIZE];
  sprintf(saveFileName, "recv/%s", reqFileName);
  FILE *fp = fopen(saveFileName, "w"); 
  if(NULL == fp) 
  { 
    printf("File:\t%s Can Not Open To Write\n", saveFileName); 
    exit(1); 
  } 
  
  // 向服务器发送buffer中的数据 
  if(send(client_socket_fd, buffer, strLength, 0) < 0) 
  { 
    perror("Send File Name Failed:"); 
    fclose(fp); 
    exit(1); 
  } 
  
  // 从服务器接收数据到buffer中 
  // 每接收一段数据,便将其写入文件中,循环直到文件接收完并写完为止   
  memset(buffer, 0, bufferSize);  
  int recvLength = 0; 
  int writeLength = 0;
  //while((recvLength = recv(client_socket_fd, buffer, bufferSize, 0)) > 0) 
  while((recvLength = recv(client_socket_fd, buffer, bufferSize, MSG_WAITALL)) > 0) 
  {
    //printf("recv %d bytes:%s\n", recvLength, buffer); 
	
    writeLength = fwrite(buffer, sizeof(char), recvLength, fp);
    if( writeLength < recvLength) 
    { 
      printf("File:\t%s Write Failed\n", saveFileName); 
      break; 
    } 
    
    memset(buffer, 0, bufferSize); 
  }
  
  // 接收成功后,关闭文件,关闭socket 
  printf("Receive File:\t%s From Server IP Successful!\n", saveFileName); 
  fclose(fp); 
  close(client_socket_fd); 
  return 0; 
}

server1.cpp:通过SendFile发送文件

//Server端(通过SendFile发送文件)

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

#define BUFFER_SIZE 1024  
#define LENGTH_OF_LISTEN_QUEUE 20  
#define FILE_NAME_MAX_SIZE 256 
  
int main(int argc, char* argv[]) 
{ 
  //用法
  if(argc<2)
  {
	  printf("Usage: ./server1 serverPort [bufferSize=1024]\n");
	  return 0;
  }
  
  int serverPort = atoi(argv[1]);
  
  int bufferSize = BUFFER_SIZE;
  if(argc>=3)
  {
	  bufferSize = atoi(argv[2]);
  }

  // 声明并初始化一个服务器端的socket地址结构 
  struct sockaddr_in server_addr; 
  bzero(&server_addr, sizeof(server_addr)); 
  server_addr.sin_family = AF_INET; 
  server_addr.sin_addr.s_addr = htons(INADDR_ANY); 
  server_addr.sin_port = htons(serverPort); 
  
  // 创建socket,若成功,返回socket描述符 
  int server_socket_fd = socket(PF_INET, SOCK_STREAM, 0); 
  if(server_socket_fd < 0) 
  { 
    perror("Create Socket Failed:"); 
    exit(1); 
  } 
  int opt = 1; 
  setsockopt(server_socket_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 
  
  // 绑定socket和socket地址结构 
  if(-1 == (bind(server_socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)))) 
  { 
    perror("Server Bind Failed:"); 
    exit(1); 
  } 
    
  // socket监听 
  if(-1 == (listen(server_socket_fd, LENGTH_OF_LISTEN_QUEUE))) 
  { 
    perror("Server Listen Failed:"); 
    exit(1); 
  } 
  
  while(1) 
  { 
    // 定义客户端的socket地址结构 
    struct sockaddr_in client_addr; 
    socklen_t client_addr_length = sizeof(client_addr); 
  
    // 接受连接请求,返回一个新的socket(描述符),这个新socket用于同连接的客户端通信 
    // accept函数会把连接到的客户端信息写到client_addr中 
    int new_server_socket_fd = accept(server_socket_fd, (struct sockaddr*)&client_addr, &client_addr_length); 
    if(new_server_socket_fd < 0) 
    { 
      perror("Server Accept Failed:"); 
      break; 
    } 
  
    // recv函数接收数据到缓冲区buffer中 
    char file_name[FILE_NAME_MAX_SIZE]; 
    bzero(file_name, FILE_NAME_MAX_SIZE); 
    if(recv(new_server_socket_fd, file_name, FILE_NAME_MAX_SIZE, 0) < 0) 
    { 
      perror("Server Recieve Data Failed:"); 
      break; 
    } 
    printf("%s\n", file_name); 
  
    // 打开文件并读取文件数据 
    int fd = open(file_name, O_RDONLY);
    if(fd == -1) 
    { 
	  printf("File:%s Not Found\n", file_name); 
    }	
    else
    {
		struct stat stat_buf;
		fstat(fd, &stat_buf);

		off_t offset = 0;
		ssize_t rc = sendfile (new_server_socket_fd, fd, &offset, stat_buf.st_size);
	  
		if (rc == -1) {
			fprintf(stderr, "error from sendfile: %s\n", strerror(errno));
			exit(1);
		}
		if (rc != stat_buf.st_size) {
			fprintf(stderr, "incomplete transfer from sendfile: %ld of %ld bytes\n", rc, stat_buf.st_size);
			exit(1);
		}

		close(fd);
		printf("File:%s Transfer Successful!\n", file_name); 
    } 
    // 关闭与客户端的连接 
    close(new_server_socket_fd); 
  } 
  // 关闭监听用的socket 
  close(server_socket_fd); 
  return 0; 
}

server2.cpp:通过普通的Socket方式发送文件

//Server端(通过Socket发送文件)

#include <netinet/in.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <stdio.h>
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h>

#define BUFFER_SIZE 1024  
#define LENGTH_OF_LISTEN_QUEUE 20  
#define FILE_NAME_MAX_SIZE 256 
  
int main(int argc, char* argv[]) 
{ 
  //用法
  if(argc<2)
  {
	  printf("Usage: ./server2 serverPort [bufferSize=1024]\n");
	  return 0;
  }
  
  int serverPort = atoi(argv[1]);
  
  int bufferSize = BUFFER_SIZE;
  if(argc>=3)
  {
	  bufferSize = atoi(argv[2]);
  }

  // 声明并初始化一个服务器端的socket地址结构 
  struct sockaddr_in server_addr; 
  bzero(&server_addr, sizeof(server_addr)); 
  server_addr.sin_family = AF_INET; 
  server_addr.sin_addr.s_addr = htons(INADDR_ANY); 
  server_addr.sin_port = htons(serverPort); 
  
  // 创建socket,若成功,返回socket描述符 
  int server_socket_fd = socket(PF_INET, SOCK_STREAM, 0); 
  if(server_socket_fd < 0) 
  { 
    perror("Create Socket Failed:"); 
    exit(1); 
  } 
  int opt = 1; 
  setsockopt(server_socket_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 
  
  // 绑定socket和socket地址结构 
  if(-1 == (bind(server_socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)))) 
  { 
    perror("Server Bind Failed:"); 
    exit(1); 
  } 
    
  // socket监听 
  if(-1 == (listen(server_socket_fd, LENGTH_OF_LISTEN_QUEUE))) 
  { 
    perror("Server Listen Failed:"); 
    exit(1); 
  } 
  
  while(1) 
  { 
    // 定义客户端的socket地址结构 
    struct sockaddr_in client_addr; 
    socklen_t client_addr_length = sizeof(client_addr); 
  
    // 接受连接请求,返回一个新的socket(描述符),这个新socket用于同连接的客户端通信 
    // accept函数会把连接到的客户端信息写到client_addr中 
    int new_server_socket_fd = accept(server_socket_fd, (struct sockaddr*)&client_addr, &client_addr_length); 
    if(new_server_socket_fd < 0) 
    { 
      perror("Server Accept Failed:"); 
      break; 
    } 
  
    // recv函数接收数据到缓冲区buffer中 
    char file_name[FILE_NAME_MAX_SIZE]; 
    bzero(file_name, FILE_NAME_MAX_SIZE); 
    if(recv(new_server_socket_fd, file_name, FILE_NAME_MAX_SIZE, 0) < 0) 
    { 
      perror("Server Recieve Data Failed:"); 
      break; 
    } 
    printf("%s\n", file_name); 
  
    // 打开文件并读取文件数据 
	char buffer[bufferSize]; 
    FILE *fp = fopen(file_name, "r"); 
    if(NULL == fp) 
    { 
      printf("File:%s Not Found\n", file_name); 
    } 
    else
    { 
      bzero(buffer, bufferSize); 
      int readLength = 0;
	  int sendLength = 0;
      // 每读取一段数据,便将其发送给客户端,循环直到文件读完为止 
      while((readLength = fread(buffer, sizeof(char), bufferSize, fp)) > 0) 
      { 
		sendLength = send(new_server_socket_fd, buffer, readLength, 0);
        if( sendLength < readLength) 
        { 
          printf("Send File:%s Failed. read %d bytes, send %d bytes/n", file_name, readLength, sendLength); 
          break; 
        } 
		//printf("read %d bytes, send %d bytes:%s\n", readLength, sendLength, buffer); 
        bzero(buffer, bufferSize); 
      } 
  
      // 关闭文件 
      fclose(fp); 
      printf("File:%s Transfer Successful!\n", file_name); 
    } 
    // 关闭与客户端的连接
	//sleep(1); //等待协议栈缓冲区中的数据发送完毕
    close(new_server_socket_fd); 
  } 
  // 关闭监听用的socket 
  close(server_socket_fd); 
  return 0; 
}

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!