本项目实现一个FTP服务器,通过FTP客户端(如FileZilla)来获取文件列表、下载、上传、删除文件等功能
。用到了libevent网络事件库管理socket的连接
、加入线程池来并发的处理请求
,应用创建型模式-工厂模式
来管理FTP客户端的请求命令码去注册对象。
Libevent的更多内容链接: libevent C++高并发网络编程
FTP协议的更多内容链接: FTP协议详解
工厂模式的更多内容链接: C++设计模式 - 创建型模式之工厂模式
项目完整代码git仓库:https://gitee.com/kakagitee1998/ftp_server.git
FTP工作模式
连接成功
* 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
工厂类XFtpFactory作用是封装对象的创建,分离对象的创建和操作过程,用于批量管理对象的创建过程,便于程序的维护和扩展。
#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;
};
#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;
}
继承XTask
任务类,其他命令任务处理都继承它,并且实现他的虚函数。
#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;
};
#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);
}
接收客户端的命令分发,根据命令从工厂中得到对应的处理对象,std::map< std::string, XFtpTask*> calls
,执行Parse()处理相应FTP客户端。
#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;
};
#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;
}
}
协议选择:FTP-文件传输协议
登录类型:匿名
传输模式:主动
字符集:UTF8