FTP服务器项目

文章目录

    • 1. 项目简介
    • 2. FTP协议和用到指令说明
      • 2.1 FTP协议详解
      • 2.2 指令说明
    • 3. FTP项目的类图分析
      • 3.1 UML
      • 3.2 工厂类XFtpFactory
        • XFtpFactory.h
        • XFtpFactory.cpp
      • 2.2 XFtpTask
        • XFtpTask.h
        • XFtpTask.cpp
      • 2.3 XFtpServerCMD
        • XFtpServerCMD.h
        • XFtpServerCMD.cpp
    • 4. 运行演示
      • FileZilla的配置
      • 运行效果

1. 项目简介

本项目实现一个FTP服务器,通过FTP客户端(如FileZilla)来获取文件列表、下载、上传、删除文件等功能。用到了libevent网络事件库管理socket的连接加入线程池来并发的处理请求,应用创建型模式-工厂模式来管理FTP客户端的请求命令码去注册对象。

  • Libevent的更多内容链接: libevent C++高并发网络编程

  • FTP协议的更多内容链接: FTP协议详解

  • 工厂模式的更多内容链接: C++设计模式 - 创建型模式之工厂模式

  • 项目完整代码git仓库:https://gitee.com/kakagitee1998/ftp_server.git

2. FTP协议和用到指令说明

2.1 FTP协议详解

FTP工作模式

  • 主动模式 PORT
    • 客户端首先和服务器的TCP 21端口建立连接,通过这个通道发送命令,客户端需要接收数据的时候在这个通道上发送
    • PORT命令PORT命令包含了客户端用什么端口接收数据
  • 被动模式 PASV
    • FTP服务器收到Pasv命令后,随机打开一个临时端口用于传送数据
  • 命令通道和数据通道

2.2 指令说明

连接成功 
	* 220 Welcome to XFtpServer (libevent)\r\n  

USER 用户登录
    * USER root\r\n
    * 230 Login successful.\r\n

PWD 获取当前目录
    * PWD\r\n
    * 257 "/" is current directory.

CWD 进入目录
    * CWD test\r\n
    * 250 Directory success changed.

CDUP 返回上传目录
    * CDUP\r\n
    * 250 Directory success changed  
    
PORT 客户端发送数据传送地址和端口
    * PORT 127,0,0,1,70,96\r\n
    * 200 PORT command successful.\r\n
    * 端口计算方法
        PORT n1,n2,n3,n4,n5,n6\r\n
        port = n5*256 + n6 
    
LIST 获取目录
    * LIST\r\n
    * 150 Here comes the directory listing.\r\n 450 file open failed.
    * 数据通道连接
        -rwxrwxrwx 1 root group 64463 Mar 14 09:53 101.jpg\r\n
    	ls -l
    * 226 Transfer complete\r\n
    * 关闭数据通道
    
RETR 下载文件
    * RETR filepath\r\n
    * 150 Transfer start.\r\n 450 file open failed.
    * 数据通道连接 传送文件数据给客户端
    * 226 Transfer complete\r\n
    * 关闭数据通道
            
STOR 上传文件
    * STOR filepath\r\n
    * 125 file OK.\r\n
    * 数据通道 读取上传的文件 客户端发送结束会主动关闭数据通道
    * 226 Transfer complete\r\n 

3. FTP项目的类图分析

3.1 UML

FTP服务器项目_第1张图片

3.2 工厂类XFtpFactory

工厂类XFtpFactory作用是封装对象的创建,分离对象的创建和操作过程,用于批量管理对象的创建过程,便于程序的维护和扩展。

  • GetInstance():通过单例模式创建返回唯一对象;
  • CreateTask():创建XFtpServerCMD对象并将XFtp(USER、PWD、LIST、CWD、CDUP、PORT、RETR、STOR)等指令处理对象注册到工厂类对象中,后续对指令的操作只需要通过命令的名字从容器中取出对象,简化了操作过程,只需要知道令的名字即可。

XFtpFactory.h

#pragma once
#include "XTask.h"

class XFtpFactory
{
public:
    //单例模式创建返回唯一对象
    static XFtpFactory* GetInstance();

    XTask* CreateTask();

private:
    //将构造函数的访问属性设置为 private
    //将构造函数构造声明成私有不使用
    //声明成私有不使用
    XFtpFactory(){}                                 //无参构造
    XFtpFactory(const XFtpFactory&);                //拷贝构造
    XFtpFactory& operator= (const XFtpFactory&);    //赋值运算符重载

    //FTP工厂对象
    static XFtpFactory* pInstance;
};

XFtpFactory.cpp

#include 
#include "XFtpFactory.h"
#include "XFtpServerCMD.h"
#include "XFtpUSER.h"
#include "XFtpPORT.h"
#include "XFtpLIST.h"
#include "XFtpRETR.h"
#include "XFtpSTOR.h"

using namespace std;

//静态成员变量类外初始化
XFtpFactory* XFtpFactory::pInstance = NULL;

/*
* 函数名: XFtpFactory::GetInstance
* 作用:   单例模式创建返回唯一对象
*/
XFtpFactory* XFtpFactory::GetInstance()
{
    //当需要使用对象时,访问instance 的值
    //空值:创建对象,并用instance 标记
    //非空值: 返回instance 标记的对象
    if( pInstance == NULL )
    {
        pInstance = new XFtpFactory();
    }
    
    return pInstance;
}

XTask* XFtpFactory::CreateTask()
{
    XFtpServerCMD* x = new XFtpServerCMD();

    //注册ftp消息处理对象
    x->Reg("USER", new XFtpUSER());

    XFtpLIST* list = new XFtpLIST();
    x->Reg("PWD", list);
    x->Reg("LIST", list);
    x->Reg("CWD", list);
    x->Reg("CDUP", list);
    
    x->Reg("PORT", new XFtpPORT());

    x->Reg("RETR", new XFtpRETR());
	x->Reg("STOR", new XFtpSTOR());

 	return x;
}

2.2 XFtpTask

继承XTask任务类,其他命令任务处理都继承它,并且实现他的虚函数。

XFtpTask.h

#pragma once
#include "XTask.h"
#include 

class XFtpTask : public XTask
{
public:
    std::string curDir  = "/home/kaka/linux/libevent/code/14.tev_ftp_server";
    std::string rootDir = "";

    /*PORT 数据通道的IP和端口*/
    std::string ip = "";
    int port = 0;

    //命令通道
	XFtpTask* cmdTask = 0;
    
    //解析协议
    virtual void Parse(std::string type, std::string msg){}

    //回复cmd消息
    void ResCMD(std::string msg);

    //用来发送建立了连接的数据通道
    void Send(std::string data);
    void Send(const char* data, int datasize);

    //连接数据通道
    void ConnectPORT();
    void Close();

    virtual void Read(struct bufferevent* bev) {}
    virtual void Write(struct bufferevent* bev) {}
    virtual void Event(struct bufferevent* bev, short what) {}
    void SetCallback(struct bufferevent* bev);
    bool Init() { return true; } //继承于基类的纯虚函数需要实现
protected:
    static void ReadCB(struct bufferevent* bev, void* arg);
    static void WriteCB(struct bufferevent* bev, void* arg);
    static void EventCB(struct bufferevent* bev, short what, void* arg);

    //命令bev
    struct bufferevent* bev = NULL;
    FILE* fp = 0;
};

XFtpTask.cpp

#include 
#include "XFtpTask.h"
#include 
#include 
#include 
#include 

using namespace std;

//用来发送建立了连接的数据通道
void XFtpTask::Send(std::string data)
{
    Send(data.c_str(), data.size());
}
void XFtpTask::Send(const char* data, int datasize)
{
    if(!bev)return;

    bufferevent_write(bev, data, datasize);
}


//连接数据通道
void XFtpTask::ConnectPORT()
{
    if(ip.empty() || port<=0 || !base)
    {
        cout << "ConnectPORT failed ip or port or base is null" << endl;
		return;
    }
    Close();

    bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
    sockaddr_in sin;
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
	sin.sin_port = htons(port);
    evutil_inet_pton(AF_INET, ip.c_str(), &sin.sin_addr.s_addr);

    //设置回调和权限
	SetCallback(bev);

    //添加超时 
	timeval rt = { 60,0 };
	bufferevent_set_timeouts(bev, &rt, 0);
	bufferevent_socket_connect(bev, (sockaddr*)&sin, sizeof(sin));
}

void XFtpTask::Close()
{
    if(bev)
    {
        bufferevent_free(bev);
        bev = NULL;
    }

    if(fp)
    {
        fclose(fp);
        fp = NULL;
    }
}


//回复cmd消息
void XFtpTask::ResCMD(std::string msg)
{
    if(!cmdTask || !cmdTask->bev)return;
    cout << "ResCMD:" << msg << endl;
    if(msg[msg.size() - 1] != '\n')
        msg += "\r\n";
    bufferevent_write(cmdTask->bev, msg.c_str(), msg.size());
}

void XFtpTask::SetCallback(struct bufferevent* bev)
{
    bufferevent_setcb(bev, ReadCB, WriteCB ,EventCB, this);
    bufferevent_enable(bev, EV_READ | EV_WRITE);
}

void XFtpTask::ReadCB(struct bufferevent* bev, void* arg)
{
    XFtpTask *t = (XFtpTask*)arg;
    t->Read(bev);
}

void XFtpTask::WriteCB(struct bufferevent* bev, void* arg)
{
    XFtpTask *t = (XFtpTask*)arg;
    t->Write(bev);
}

void XFtpTask::EventCB(struct bufferevent* bev, short what, void* arg)
{
    XFtpTask *t = (XFtpTask*)arg;
    t->Event(bev, what);
}

2.3 XFtpServerCMD

接收客户端的命令分发,根据命令从工厂中得到对应的处理对象,std::map< std::string, XFtpTask*> calls,执行Parse()处理相应FTP客户端。

XFtpServerCMD.h

#pragma once

#include "XFtpTask.h"
#include 

class XFtpServerCMD : public XFtpTask
{
public:
    //初始化任务
    virtual bool Init();

    virtual void Read(struct bufferevent* bev);
    virtual void Write(struct bufferevent* bev);
    virtual void Event(struct bufferevent* bev, short what);

    //注册命令处理对象 不需要考虑线程安全,调用时还未分发到线程
    void Reg(std::string , XFtpTask* call);

    XFtpServerCMD();
    ~XFtpServerCMD();

private:
    //注册命令集合
    std::map<std::string, XFtpTask*> calls;

    //用来做空间清理
	std::map< XFtpTask*, int>calls_del;
};

XFtpServerCMD.cpp

#include "XFtpServerCMD.h"
#include 
#include 
#include 
#include 
#include 

using namespace std;

//注册命令处理对象 不需要考虑线程安全,调用时还未分发到线程
void XFtpServerCMD::Reg(std::string cmd, XFtpTask* call)
{
    if (!call)
	{
		cout << "XFtpServerCMD::Reg call is null " << endl;
		return;
	}
	if (cmd.empty())
	{
		cout << "XFtpServerCMD::Reg cmd is null " << endl;
		return;
	}

	//已经注册的是否覆盖 不覆盖,提示错误
    if(calls.find(cmd) != calls.end())
    {   
        cout << cmd << " is alreay register" << endl;
		return;
    }

    calls[cmd] = call;
    //用来做空间清理
	calls_del[call] = 0;
}


/*
* 函数名: Read
* 作用:   读事件回调函数
*/
void XFtpServerCMD::Read(struct bufferevent *bev)
{
    char data[1024] = {0};

    for (;;)
    {
        int len = bufferevent_read(bev, data, sizeof(data)-1);
        if(len <= 0)break;
        data[len] = '\0';
        cout << "Recv CMD:"<<data;
		//分发到处理对象

        //分析出类型 USER anonymous'
        string type = "";
        for (int i = 0; i < len; i++)
        {
            if(data[i] == ' ' || data[i] == '\r')
                break;

            type += data[i];
        }
        cout << "type is [" << type<<"]" << endl;
        
        //查找组成的命令
        if(calls.find(type) != calls.end()) //查找到
        {   
            XFtpTask* t = calls[type];
            t->cmdTask = this;  //用来处理回复命令和目录
            t->ip = ip;
            t->port = port;
            t->base = base;
            t->Parse(type, data);
            if(type == "PORT")
            {
                ip = t->ip;
                port = t->port;
            }
        }
        else
        {
            string msg = "200 OK\r\n";
			bufferevent_write(bev, msg.c_str(), msg.size());
        }
    }
}

/*
* 函数名: Write
* 作用:   写事件回调函数
*/
void XFtpServerCMD::Write(struct bufferevent *bev)
{

}

/*
* 函数名: Event
* 作用:   超时事件回调函数
* 解释:   客户端超时未发请求,断开连接退出任务
*/
void XFtpServerCMD::Event(struct bufferevent *bev, short what)
{
    //如果对方网络断掉,或者机器死机有可能收不到BEV_EVENT_EOF数据
    if(what & (BEV_EVENT_EOF | BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT))
    {
        cout << "BEV_EVENT_EOF | BEV_EVENT_ERROR |BEV_EVENT_TIMEOUT" << endl;
		bufferevent_free(bev);
		delete this;
    }
}


/*
* 函数名: XFtpServerCMD::Init
* 作用:   初始化任务
* 解释:   初始化任务,注册当前socket的读事件和超时事件,绑定回调函数。
*/
bool XFtpServerCMD::Init()
{
    cout << "XFtpServerCMD::Init() sock:" << sock << endl;
	//监听socket bufferevent
	// base socket
    bufferevent* bev = bufferevent_socket_new(base, sock, BEV_OPT_CLOSE_ON_FREE);
    if (!bev)
	{
		delete this;
		return false;
	}

    this->bev = bev;
    this->SetCallback(bev);

    //添加超时
    timeval rt = {10, 0};       //10秒
    bufferevent_set_timeouts(bev, &rt, 0);  //设置读超时回调函数

    //FTP连接成功,首先回欢迎消息
    string msg = "220 Welcome to libevent XFtpServer\r\n";
    bufferevent_write(bev, msg.c_str(), msg.size());

	return true;
}

XFtpServerCMD::XFtpServerCMD()
{

}

XFtpServerCMD::~XFtpServerCMD()
{
    Close();
	for (auto ptr = calls_del.begin(); ptr != calls_del.end(); ptr++)
	{
		ptr->first->Close();
		delete ptr->first;
	}
}

4. 运行演示

FileZilla的配置

协议选择:FTP-文件传输协议
登录类型:匿名
传输模式:主动
字符集:UTF8

FTP服务器项目_第2张图片

运行效果

FTP服务器项目_第3张图片

你可能感兴趣的:(libevent,C++高并发网络编程,服务器,网络,运维)