sendfile | 传说中的零拷贝(主要用于网络中文件传输)

sendfile函数

  • sendfile函数简介
    • sendfile简单小例子
      • 用sendfile函数简单模拟文件下载

sendfile函数简介

  • sendfile函数:sendfile函数是在两个文件描述符中直接传递数据(完全在内核中操作),从而避免了用户和内核之间的数据拷贝,所以效率很高,也被称之为零拷贝。
  • sendfile函数用法
    • 头文件:#include
    • 用法:ssize_t sendfile(int out_fd,int in_fd,off_t * offset,size_t count)
      • out_fd:待写入内容的文件描述符,一般为accept的返回值
      • in_fd:待读出内容的文件描述符,一般为open的返回值
      • offset:指定从读入文件流的哪个位置开始读,如果为空,则使用读入文件流的默认位置,一般设置为NULL
      • count:两文件描述符之间的字节数,一般给struct stat结构体的一个变量,在struct stat中可以设置文件描述符属性

注意:in_fd规定指向真实的文件,不能是socket等管道文件描述符,一般使open返回值,而out_fd则是socket描述符

  • sendfile的优点:是专门为网络上传输文件而设计的函数,效率高

sendfile简单小例子

//打开文件
int file_fd = open("./wyg.txt",O_RDWR | O_CREAT,664);
if(file_fd < 0)
{
     perror("open");
     return -1;
}
//设置file_fd文件描述符属性
struct stat stat_buf;
fstat(file_fd,&stat_buf);
//把目标文件传递给服务器
int ret = sendfile(newts.GetFd(),file_fd,NULL,stat_buf.st_size);
if(ret < 0)
{
     perror("sendfile");
     return -1;
}

用sendfile函数简单模拟文件下载

实现功能:客户端连接服务器,会自动下载服务器的文件
主要实现:其实就是基于tcp套接字编程 + sendfile函数来实现的,传输效率高
1、服务器

  • 创建套接字
  • 绑定地址信息
  • 监听
  • 获取连接
  • 打开文件
  • 设置打开的文件描述符属性
  • 把目标文件通过sendfile传递给服务器
  • 关闭套接字

2、客户端

  • 创建套接字
  • 发起连接
  • 接收文件
  • 关闭套接字

3、总代码实现如下

tcp_svr.hpp

#include
#include
#include
#include
#include
#include
#include

class Tcpsvr
{
    public:
        Tcpsvr()
        {
            _sockfd = -1;
        }
        ~Tcpsvr()
        {}
        bool Createsocket()//创建套接字
        {
            _sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
            if(_sockfd < 0)
            {
                perror("socket");
                return false;
            }
            return true;
        }
        bool Bind(const std::string& ip,uint16_t port)//绑定地址信息
        {
            struct sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port);
            addr.sin_addr.s_addr = inet_addr(ip.c_str());
            int ret = bind(_sockfd,(struct sockaddr*)&addr,sizeof(addr));
            if(ret < 0)
            {
                perror("bind");
                return false;
            }
            return true;
        }
        bool Listen(int backlog = 5)//监听
        {
            int ret = listen(_sockfd,backlog);
            if(ret < 0)
            {
                perror("listen");
                return false;
            }
            return true;
        }
        bool Accept(Tcpsvr *newts,struct sockaddr_in* peeraddr)//获取连接
        {
            socklen_t addrlen = sizeof(struct sockaddr_in);
            int newfd = accept(_sockfd,(struct sockaddr*)peeraddr,&addrlen);
            if(newfd < 0)
            {
                perror("accept");
                return false;
            }
            newts->_sockfd = newfd;
            return true;
        }
        bool Connect(const std::string& ip,uint16_t port)//发起连接
        {
            struct sockaddr_in dest_addr;
            dest_addr.sin_family = AF_INET;
            dest_addr.sin_port = htons(port);
            dest_addr.sin_addr.s_addr = inet_addr(ip.c_str());
            int ret = connect(_sockfd,(struct sockaddr*)&dest_addr,sizeof(dest_addr));
            if(ret < 0)
            {
                perror("connect");
                return false;
            }
            return true;
        }
        bool Recv(std::string* data)//接收数据
        {
            char buf[1024] = {0};
            int recv_size = recv(_sockfd,buf,sizeof(buf) - 1,0);
            if(recv_size < 0)
            {
                perror("recv");
                return false;
            }
            else if(recv_size == 0)
            {
                //对端关闭了连接
                printf("peer shutdown connect\n");
                return false;                             
            }
            data->assign(buf,recv_size);
            return true;
        }
        int GetFd()//获取套接字描述符
        {
            return _sockfd;
        }
        void Close()//关闭套接字
        {
            close(_sockfd);
        }
    private:
        int _sockfd;
};

服务器svr.cpp

#include "tcp_svr.hpp"
#include 
#include 
#include 
#include 
#include 
#define CHECK_RET(p) if(p == false){return 0;}//判断封装接口是否调用成功,失败直接返回
int main()
{
    Tcpsvr tp;
    //1、创建套接字
    CHECK_RET(tp.Createsocket());
    //2、绑定地址信息
    CHECK_RET(tp.Bind("192.168.163.129",18888));
    //3、监听
    CHECK_RET(tp.Listen());
    Tcpsvr newts;
    while(1)
    {
        //4、获取连接
        struct sockaddr_in cli_addr;
        CHECK_RET(tp.Accept(&newts,&cli_addr));
        //5、打开文件
        int file_fd = open("./wyg.txt",O_RDWR | O_CREAT,664);
        if(file_fd < 0)
        {
            perror("open");
            return -1;
        }
		//6、//设置file_fd文件描述符属性
        struct stat stat_buf;
        fstat(file_fd,&stat_buf);
		//7、//把目标文件传递给服务器
        int ret = sendfile(newts.GetFd(),file_fd,NULL,stat_buf.st_size);
        if(ret < 0)
        {
            perror("sendfile");
            return -1;
        }
    }
    //8、关闭套接字
    tp.Close();
    newts.Close();
    return 0;
}

客户端cli.cpp

#include "tcp_svr.hpp"
#define CHECK_RET(p) if(p == false){return 0;}//判断封装的接口是否调用成功,失败直接返回
int main()
{
    Tcpsvr tp;
    //1、创建套接字
    CHECK_RET(tp.Createsocket());
    //2、发起连接
    CHECK_RET(tp.Connect("192.168.163.129",18888));
	//3、接收
    std::string buf;
    tp.Recv(&buf);
    printf("%s",buf.c_str());
    //4、关闭套接字
    tp.Close();
    return 0;
}

makefile

all:cli svr
cli:cli.cpp
	g++ $^ -o $@
svr:svr.cpp
	g++ $^ -o $@

wyg.txt

hello world

1、我在要传输的文件wyg.txt就写了hello world这一句话,其实是可以实现大文件传输的,感兴趣的可以传大文件试一下,也可以把程序改成并发的,这样传的更快
2、我在让客户端接收的时候只是让文件内容打印出来了,其实是可以写入到另外一个文件的

你可能感兴趣的:(Linux)