项目需要,使用linux零拷贝函数SendFile来传输文件。
传统的read/write方式进行网络文件传输的方式,要经过四次copy操作:
硬盘 >> kernel buffer >> user buffer >> kernel socket buffer >> 协议栈
而sendfile() 就是用来简化上面步骤提升性能的。sendfile() 不但能减少切换次数而且还能减少拷贝次数。
硬盘 >> kernel buffer (快速拷贝到kernel socket buffer) >> 协议栈
更加具体的资料请参考: 、
这里提供一种SendFile的具体应用,供大家使用SendFile时参考。程序主要实现了文件下载:
- client与server建立tcp连接。
- client告知server,我要下载哪个文件。
- server将文件传输给client。
其中实现了两种server,一种是通过SendFile发送文件,一种是通过普通的Socket方式发送文件。具体代码如下:
client.cpp:client端,文件接收端
//Client端#include#include #include #include #include #include #include #include #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#include #include #include #include #include #include #include #include #include #include #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#include #include #include #include #include #include #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; }