目录
模块设计
TcpServer模块其实就是可以提供给用户使用和设置各种方法的接口模块了,在TcpServer模块中,我们需要管理所有的连接,所有的EventLoop,所有的线程以及用户设置的回调函数和用户是否启动超时销毁。
在这个模块中,我们需要一个监听套接字,也就是一个Acceptor模块
需要一个EventLoop对象,这个EventLoop对象是在主线程中执行的,是主从Reactor模型的主Reactor线程,未来主要负责获取新连接,也就是我们前面所说的_base_loop
然后还需要一个线程池对象EventLoopThreadPool,她负责管理从属线程以及分配EventLoop。
还需要一个容器来管理所有的Connection的基础计数。
最后还需要维护一个_conn_id ,后续新连接到来之后,分配id就是通过这个变量来进行的。
同时,在TcpServer中还需要保存一个变量用于表示是否开启了超时连接释放以及超时的时间,以及保存用户设置的四个回调函数。
当然服务器需要一个端口,我们也需要保存。
那么他的成员就是以下几个:
class TcpServer
{
private:
uint16_t _port; //服务器端口号
EventLoop _base_loop; //主 EventLoop
Acceptor _acceptor; //监听套接字
EventLoopThreadPool _pool; //线程池
uint64_t _conn_id; //分配id
std::unordered_map<uint64_t,PtrConnection> _conns;
int _timeout; //超时时间
bool _enable_inactive_release; //是否开启非活跃超时释放
//四个回调函数
using ConnectCallBack = std::function<void(const PtrConnection&)>;
using MessageCallBack = std::function<void(const PtrConnection&,Buffer*)>;
using EventCallBack = std::function<void(const PtrConnection&)>;
using CloseCallBack = std::function<void(const PtrConnection&)>;
ConnectCallBack _connect_cb;
MessageCallBack _message_cb;
EventCallBack _event_cb;
CloseCallBack _close_cb;
那么这个模块需要提供哪些方法?
首先,创建新连接以及添加管理的方法必须提供给Acceptor模块。以及释放基础shared_ptr的方法需要提供给新的Connection对象
其次需要提供一个接口用于设置线程数量。然后还需要提供四个设置回调函数的接口
最后就是超时管理的启动和取消。
我们既然设计了时间轮模块,还可以给用户提供一个接口用于添加用户的定时任务。
最后就是开启服务器的接口了。
private:
void NewConnectionCallBack(int newfd); //创建Conenction对象
void SvrCloseCallBack(); //释放基础计数
public:
TcpServer(uint16_t port);
void SetThreadCount(size_t count); //设置线程数
void SetConnectCallBack(const ConnectCallBack& cb);
void SetMessageCallBack(const MessageCallBack& cb);
void SetEventCallBack(const EventCallBack& cb);
void SetCloseCallBack(const CloseCallBack& cb);
void EnableInactiveRelease(int delay = 30); //启动超时销毁
void DisenableInactiveRelease(); //取消超时销毁
void AddTimerTask(const std::function<void()>& task,int delay); //用于添加用户的定时任务
void Start(); //启动服务器
模块实现
首先是构造函数,需要传入一个端口号进来。
TcpServer(uint16_t port):_port(port),_base_loop(),_acceptor(&_base_loop,_port),_pool(&_base_loop),_conn_id(0),_timeout(30),_enable_inactive_release(false)
{
//设置监听套接字的读方法
_acceptor.SetReadCallBack(std::bind(&TcpServer::NewConnectionCallBack,this,std::placeholders::_1));
}
设置线程数其实就是调用EventLoopThreadPool的接口
void SetThreadCount(size_t count){_pool.SetThreadCount(count);} //设置线程数
然后就是启动和取消超时销毁
void EnableInactiveRelease(int delay = 30) {_enable_inactive_release = true; _timeout = delay;} //启动超时销毁
void DisenableInactiveRelease(){_enable_inactive_release = false;} //取消超时销毁
然后就是设置用户的定时任务,由于这里其实也会有线程安全问题,所以我们也需要将操作放到主线程中执行。
void AddTimerTaskInLoop(const std::function<void()>& task,int delay)
{
_base_loop.AddTimerTask(_conn_id++,delay,task);
}
void AddTimerTask(const std::function<void()>& task,int delay){_base_loop.RunInLoop(std::bind(&TcpServer::AddTimerTask,this,task,delay));} //用于添加用户的定时任务
启动服务器其实就是启动主Reactor线程和启动线程池,以及启动Acceptor。
注意这里的顺序,一定是最后再启动_base_loop
void Start() //启动服务器
{
_pool.Start();
_acceptor.Listen();
_base_loop.Start();
}
剩下的就是两个私有接口了。
首先创建新连接,其实就把我们之前的测试的函数代码拿过来就行了:
之前的代码:
void CreateConnection(int fd) //模拟TcpServer提供给Acceptor模块的创建Connection的回调
{
PtrConnection pt (new Connection(auto_id,fd,&loop));
_conns.insert(std::make_pair(auto_id,pt)); //添加管理
auto_id++; //后续可能会有线程安全,我们需要互斥以及同步机制
//那么接下来就是设置一些属性
//1 设置用户的处理函数,设置上下文
pt->SetMessageCallBack(Message);
pt->SetCloseCallBack(Close);
pt->SetConnectCallBack(Connect);
pt->SetEventCallBack(Event);
Any a; //后续也会在TcpServer中管理
pt->SetContext(a);
//2 设置非活跃超时销毁
pt->EnableInactiveRelease(5);
//3 设置服务器的关闭连接函数
//其实就是删除基础计数的shared_ptr
pt->SetSvrCloseCallBack(ServerClose);
//4 开始通信
pt->Established();
}
修改之后:
void NewConnectionCallBack(int newfd) //创建Conenction对象
{
PtrConnection pt(new Connection(_conn_id,newfd,_pool.GetEventLoop()));
_conns.insert(std::make_pair(_conn_id,pt));
_conn_id++;
//判断是否启动非活跃销毁
if(_enable_inactive_release) pt->EnableInactiveRelease(_timeout);
//设置回调方法
pt->SetConnectCallBack(_connect_cb);
pt->SetMessageCallBack(_message_cb);
pt->SetEventCallBack(_event_cb);
pt->SetCloseCallBack(_close_cb);
pt->SetSvrCloseCallBack(std::bind(&TcpServer::SvrCloseCallBack,this,std::placeholders::_1));
//启动该通信
pt->Established();
}
然后就是删除基础计数的接口,注意,创建新连接的接口未来一定是在我们的_base_loop中执行的,因为他是Acceptor的回调函数,未来会在_base_loop中获取到就绪事件然后执行。但是释放资源这个接口不一定是在_base_loop中执行,所以我们需要考虑线程安全问题,把他放在_base_loop中执行。
void SvrCloseCallBackInLoop(const PtrConnection& conn) //释放基础计数
{
_conns.erase(conn->Id());
}
void SvrCloseCallBack(const PtrConnection& conn) //释放基础计数
{
_base_loop.RunInLoop(std::bind(&TcpServer::SvrCloseCallBackInLoop,this,conn));
}
那么我们的TcpServer模块也就完成了,目前这份代码编译是没有问题的。
最后补充一点就是,以前我们讲过的SIGPIPE信号,当对端关闭的时候,那么我们再去写入的时候就会收到这个信号,导致程序退出。
所以我们需要忽略掉这个信号。一种很简单的方式:
class IGNORE_SIGPIPE{
public:
IGNORE_SIGPIPE(){signal(SIGPIPE,SIG_IGN);}
}
static IGNORE_SIGPIPE;
我们直接定义一个本文件的静态对象来实现,未来我们的头文件会在包含的地方展开
同时由于我们会定义对象,那么为了防止未来重复包含头文件导致对象重定义,我们需要在头文件的开始和末尾加上条件编译
#ifndef __MY__MUDUO__
#define __MY__MUDUO__
头文件
#endif
那么我们的服务器的所有模块就设计完了,下一篇文章再来进行测试