項目須要,使用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時參考。程序主要實現了文件下載:函數
其中實現了兩種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; }