项目需要,使用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时参考。程序主要实现了文件下载:
- client与server建立tcp连接。
- client告知server,我要下载哪个文件。
- 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;
}
来源:oschina
链接:https://my.oschina.net/u/154563/blog/813171