Linux零拷貝函數SendFile應用

項目須要,使用linux零拷貝函數SendFile來傳輸文件。html

傳統的read/write方式進行網絡文件傳輸的方式,要通過四次copy操做:linux

硬盤 >> kernel buffer >> user buffer >> kernel socket buffer >> 協議棧服務器

而sendfile() 就是用來簡化上面步驟提高性能的。sendfile() 不但能減小切換次數並且還能減小拷貝次數。網絡

硬盤 >> kernel buffer (快速拷貝到kernel socket buffer) >> 協議棧socket

更加具體的資料請參考:linux的sendfile()系統調用  Linux kernel 的 sendfile 是如何提升性能的tcp

這裏提供一種SendFile的具體應用,供你們使用SendFile時參考。程序主要實現了文件下載:函數

  1. client與server創建tcp鏈接。
  2. client告知server,我要下載哪一個文件。
  3. server將文件傳輸給client。

其中實現了兩種server,一種是經過SendFile發送文件,一種是經過普通的Socket方式發送文件。具體代碼以下:性能

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

//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發送文件spa

//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; 
}
相關文章
相關標籤/搜索