基于EventLoop的tcp服务端 C++实现(一) —— 网络事件模块结构描述

学习github上的项目 flamingo 自己的笔记。
flamingo原作者的csdn是: analogous_love
flamingo是多线程的,但是本人能力有限,只是单线程的还算能理解一点。

自己参照flamingo实现的基于epoll的单线程服务端,git地址:https://gitee.com/storm_zy/StServerFrame
实现了简单的echo功能,很多代码直接拷贝自flamingo。

一、自己当时的想法

  • 自己当时在想如何设计一个通用的Tcp服务端,就是将各个部分模块化,然后只需更改其中的少部分模块(例如业务session)即可
  • 完成完整的服务端。然后就从网上找啊找,最后找到了一个大牛写的一个即时通信类软件 flamingo,从github上一搜就能搜到,然后开始研究其服务端框架。
  • 然后终于感觉对框架稍微懂了一点点,所以就赶紧记录下来。

二、思考服务端框架的过程

  1. 实现一个通用的tcp服务端框架,首先要考虑的是网络通信,说白了就是系统提供的socket等一系列网络层的api的使用。

  2. 由此选用了效率比较高的通知机制 (Linux 下的 epoll 和 Windows 下的 iocp)。因为对iocp还没有研究,所以就用了epoll。

  3. 以上实现的功能可以用 事件模块 来称呼。就命名为 EventModule 吧。EventModule 应该包含的功能:

    1> EventModule在这里就是对 socket 的事件监听,socket下面就用 fd 代替了。
    2> 为fd添加事件   对应 EPOLL_CTL_ADD
    3> 为fd更改事件   对应 EPOLL_CTL_MOD
    4> 为fd删除事件   对应 EPOLL_CTL_DEL
    5> 对外提供获取当前关注的活动的fd及其事件。
    
  4. 考虑通用性,增加一个 Channel 对 fd与EventModule之间的交互封装到 Channel中,这样对EventModule来说,只需对接 Channel即可,Channel再对接具体的
    关注fd的对象(TcpConnection之类)。Channel应该包含的功能:

    1> 关注一个fd。一般关注的fd事件就是读事件和写事件。
    2> 为fd增加读事件。
    3> 为fd增加写事件。
    4> 为fd删除读事件。
    5> 为fd删除写事件。
    6> 对外提供事件处理接口。
    
  5. 有了 EventModule 和 Channel之后,发现还需要一样东西,一样可以执行 EventModule的事件循环的东西,而且需要他来将 EventModule和Channel关联起来。这个东西就叫 EventLoop。

    1> 持有一个EventModule的对象。
    2> Channel可以通过EventLoop将自己关注的fd的事件添加、删除、更改到 EventModule。
    3> 执行 EventModule的事件探测(epoll_wait),获取活动的fd的Channel,调用Channel的事件处理函数。
    

三、三个类对应的伪码实现

EventModule

class CChannel;
	class CEventModule
	{
	public:
		typedef std::vector<CChannel *> ChannelVec;
		CEventModule() : m_hasInit(false) {}
		~CEventModule() {}
	public:
		virtual int UpdateChannel(CChannel *channel) = 0; // 将channel关注的fd添加、修改事件
		virtual void RemoveChannel(CChannel *channel) = 0; // 删除channel关注的fd的事件
		// 对外(EventLoop)提供的获取当前活动的 fd的Channel的函数。
		virtual int GetActiveChannels(int timeoutMs, ChannelVec& retActiveChannels) = 0;
		virtual bool HasChannel(CChannel *channel) = 0;
	
		virtual int Init() { m_hasInit = true; }
		virtual bool HasInit() { return m_hasInit;	}
	protected:
		bool			m_hasInit;
	};
  • UpdateChannel 中判断 channel的状态 是否添加过,添加过则修改事件,没有添加过则是添加事件。事件可以直接通过接口 channel->events();获取。然后通过 epoll_ctl(…);将channel->fd();的事件添加到epoll。
  • RemoveChannel就是将channel->fd(); 通过epoll_ctl(…,EPOLL_CTL_DEL,…)从epoll关注列表中删掉。
  • GetActiveChannels就是执行 epoll_wait(…); 并将当前有事件的fd的channel放到retActiveChannels返回。

Channel

class CEventLoop;
class CChannel
{
public:
	typedef std::function<void()> EventCallback;
	
	CChannel(CEventLoop *loop, int fd);
	~CChannel();
public:
	void				handleEvents();
	bool				update();
public:
	void				setReadCallBack(const EventCallback& cb) { readCallBack_ = cb; }
	void				setWriteCallBack(const EventCallback& cb) { writeCallBack_ = cb; }
	void				setcloseCallBack(const EventCallback& cb) { closeCallBack_ = cb; }
	void				setErrorCallBack(const EventCallback& cb) { errorCallBack_ = cb; }
	void				setEvents(int events) { m_events = events; }
	void				setRevents(int revents) { m_revents = revents; }
	void				setState(int state) { m_state = state; }

	int					events(void) { return m_events; }
	int					revents(void) { return m_revents; }
	int					fd(void) { return m_fd; }
	int					state(void) { return m_state; }
public:
	bool				enableReading();
	bool				disableReading();
	bool 				enableWriting();
	bool				disableWriting();
	bool				hasWriteEvent();
	void				removeAllEvent();
private:
	EventCallback		readCallBack_;
	EventCallback		writeCallBack_;
	EventCallback		closeCallBack_;
	EventCallback		errorCallBack_;
private:
	static const int            kNoneEvent;
	static const int            kReadEvent;
	static const int            kWriteEvent;

	const int			m_fd;
	int					m_events;			// 本channel关注的事件
	int					m_revents;			// 当前从 EventModule 收到的等待处理的事件  recved events
	CEventLoop			*m_loop;			
	int					m_state;			// -1 没有被加入到 CEventModule 中, 1 已经加上了  2 已经删除了
};
  • enable/disable那几个函数是从EventModule添加/删除对应的事件。
  • handleEvents 是对外(EventLoop)提供的事件处理函数。
  • 几个CallBack函数是对应 更上层的事件处理函数。会在handleEvents中根据收到不同的事件进行相应调用。

EventLoop

class CEventLoop;
class CChannel;
class CEventModule;
class CEventLoop
{
public:
	CEventLoop();
	~CEventLoop();
public:
	bool					updateChannel(CChannel * channel);
	void					removeChannel(CChannel * channel);
	void					loop();
	void					stop();
public:
	void					setEventModule(CEventModule *eventModule) { m_eventModule = eventModule; }
private:
	typedef std::vector<CChannel *> ChannelVec;
	CEventModule			*m_eventModule;
	ChannelVec				m_activeChannels;
	CChannel				*m_curActiveChannel;
	bool					m_stoped;
};
  • loop函数是调用 EventModule->GetActiveChannels(),并将返回的有事件的channel列表遍历,并调用channel->handelEvent()进行事件处理。
  • updateChannel/removeChannel是提供给 Channel进行增加/删除事件的接口。

四、示例 伪码

int main()
{
	CEventLoop mainLoop;
	// 假设自己实现了一个 继承自 CEventModule 的事件模块类 CEpollModule
	CEventModule *emd = new CEpollModule(&mainLoop); // 创建事件模块
	emd->Init();
	mainLoop.setEventModule(emd);  // 将事件模块添加到 EventLoop

	int fd = 创建一个socket;
	CChannel *c = new CChannel(&mainLoop, fd);  // 实际应该是 TcpConnection 关联一个 Channel
	c->enableWrite();  // 将fd事件添加到EventModule
	mainLoop.loop();   // 开始事件循环
	return 0;
}

*以上只是伪码,示例大概怎样使用。


欢迎关注 [懒人漫说] 公众号,分享Java、Android、C/C++ 技术,包括基础、自己遇到的问题解决过程。
在这里插入图片描述
当然如果关注并留言问题的话,我们力所能及的话会帮你解决并回复哟。我们和你一样,是正在成长的程序员,我们也会分享自己的成长路上的感想,希望可以和你一起努力成长。

发布了26 篇原创文章 · 获赞 35 · 访问量 13万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览