基于EventLoop的tcp服务端 C++实现(三) ——tcp连接(TcpConnection)和业务层会话(MySession)

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

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

一、Tcp的连接 TcpConnection

  • 顾名思义,是一个tcp 的socket连接。
  • 上一篇博客中的TcpServer在Listener中接收到新连接后调用OnNewConnection会创建一个TcpConnection添加到TcpServer的列表m_connections中管理。
  • 作为一个连接他应该有以下功能:
1 绑定一个socket,以下简称fd。即当新连接到来的时候,accept的到的fd,用来初始化TcpConnection。
2 当网络事件模块有 读事件 的时候,因该有 handleRead 函数供其回调。
3 写事件回调, handleWrite。
4 错误事件回调,handleError。
5 关闭连接回调,handleClose。
6 读数据缓冲区 m_inBuffer。
7 写数据缓冲区 m_outBuffer。
8 供上层session调用的用来写数据的接口 send(data,len);
9 供上层session设置的回调函数,用来在TcpConnection关闭的时候对session作相应状态设置。

二、伪码实现

CTcpConnection 类设计

class CTcpConnection
{
public:
	CTcpConnection(CEventLoop *loop, int fd);
	~CTcpConnection();
public:
	void				send(const char* buf, int len);
	void				handleRead();
	void				handleWrite();
	void				handleClose();
	void				handleError();
	void				forceClose();
public:
	// called when TcpServer accepts a new connection
	void				connectEstablished();   // should be called only once
	// called when TcpServer has removed me from its map
	void				connectDestroyed();  // should be called only once
public:
	void				addSession(CTcpSession *session);
private:
	enum StateE {kDisconnected, kConnecting, kConnected, kDisconnecting};
	void				setState(StateE state) { m_state = state; }
public:
	int					fd();
	bool				isConnected(void) { return m_state == kConnected; }
public:
	void				setMessageCallBack(const MessageCallBack& cb) { messageCallBack_ = cb; }
	void				setWriteCompleteCallBack(const WriteCompleteCallBack& cb) { writeCompleteCallBack_ = cb; }
	void				setCloseCallBack(const CloseCallBack& cb) { closeCallBack_ = cb; }
private:
	MessageCallBack		messageCallBack_;
	WriteCompleteCallBack writeCompleteCallBack_;
	CloseCallBack		closeCallBack_;
private:
	CSocket				m_fd;
	Buffer				m_inBuffer;
	Buffer				m_outBuffer;
	CChannel			*m_channel;
	CTcpSession			*m_session;
	StateE				m_state;
};

构造和析构函数

CTcpConnection::CTcpConnection(CEventLoop *loop, int fd) : 
	m_fd(fd), m_channel(new CChannel(loop, fd)), m_state(StateE::kConnecting)
{
	m_channel->setReadCallBack(std::bind(&CTcpConnection::handleRead, this));
	m_channel->setWriteCallBack(std::bind(&CTcpConnection::handleWrite, this));
	m_channel->setcloseCallBack(std::bind(&CTcpConnection::handleClose, this));
	m_channel->setErrorCallBack(std::bind(&CTcpConnection::handleError, this));
}

CTcpConnection::~CTcpConnection()
{
	// 有数据没有发完  先发送数据
	if (m_outBuffer.readableBytes() > 0 && m_channel->hasWriteEvent())
		handleWrite();

	// 给上层session设置连接关闭的状态
	if (m_session)
		session->OnConnectDestroyed();
	m_session = nullptr;

	// 清理Channel对象,在Channel对象的析构函数中会从EventModule中删除本连接的相关事件
	delete m_channel;
	m_channel = nullptr;

	// 关闭socket连接
	m_fd.Close();
	setState(StateE::kDisconnected);
}

事件处理函数

void CTcpConnection::handleRead()
{
	int savedErrno = 0;
	int n = m_inBuffer.readFd(m_fd.fd(), &savedErrno);
	
	if (n > 0) {
		if (messageCallBack_)
			messageCallBack_(this, &m_inBuffer);
	}
	else if (n == 0) {
		handleClose();
	}
	else {
		errno = savedErrno;
		handleError();
	}
}

void CTcpConnection::handleWrite()
{
	if (m_outBuffer.readableBytes() > 0) {
		int n = sockets::write(m_fd.fd(), m_outBuffer.peek(), m_outBuffer.readableBytes());
		if (n > 0) {
			m_outBuffer.retrieve(n);
			if (m_outBuffer.readableBytes() == 0) {
				// 写缓冲区数据全部发送完毕,将写事件删除
				m_channel->disableWriting();
				if (writeCompleteCallBack_)
					writeCompleteCallBack_(this);
			}
		}
		else {
			handleClose();
		}
	}
}

void CTcpConnection::handleClose()
{
	if (closeCallBack_)
		closeCallBack_(this);
	setState(StateE::kDisconnecting);
}

void CTcpConnection::handleError()
{
	handleClose();
}

void CTcpConnection::forceClose()
{
	handleClose();
}

供上层session调用的发送数据的接口

void CTcpConnection::send(const char* buf, int len)
{
	m_outBuffer.append(buf, len);
	// 如果当前没有设置本连接的写事件则需要
	if (!m_channel->hasWriteEvent())
		m_channel->enableWriting();
}

连接建立完成接口

void CTcpConnection::connectEstablished()
{
	// 连接建立完成,添加本连接的读事件,等待数据的到来
	m_channel->enableReading();
	setState(StateE::kConnected);
}

三、TcpServer创建连接的过程

OnNewConnection会在Listener(TcpServer监听的fd对象)accept一个新的fd后被调用。

void CTcpServer::OnNewConnection(int fd)
{
	// 创建一个新的连接,其实这里应该判断 m_connections中是否已经包含了本fd的TcpConnection。
	CTcpConnection *conn = new CTcpConnection(m_loop, fd);
	if (!AddConnection(conn)) {
		delete conn;
		return;
	}
	conn->setCloseCallBack(std::bind(&CTcpServer::OnConnectionClose, this, std::placeholders::_1));

	// 将fd设为非阻塞
	CSocket s(fd);
	s.SetNonBlock(true);

	// 调用连接建立的函数,添加连接的读事件
	conn->connectEstablished();
	if (connectionCallBack_)
		connectionCallBack_(conn);
}

四、上层的session

CTcpConnection绑定的是CTcpSession的对象,所以用户的业务会话类MySession应该继承 CTcpSession,而后重写其读函数
TcpServer中有个供上层Server调用的BindSession的函数,就是将TcpConnection和会话session绑定起来。

void CTcpServer::BindSession(CTcpSession *session, CTcpConnection *c)
{
	c->setMessageCallBack(std::bind(&CTcpSession::OnRead, session, std::placeholders::_1, std::placeholders::_2));
	c->setWriteCompleteCallBack(std::bind(&CTcpSession::OnWriteComplete, session, std::placeholders::_1));
	c->addSession(session);
}

1 在这个上面这个函数中,将TcpSession的OnRead设置到 TcpConnection的 messageCallBack_上,在TcpConnection的handleRead函数中在接受数据之后会调用该回调函数来接入用户业务会话层。
2 外层MySession继承 TcpSession,然后重写 OnRead函数,即可将读的逻辑引入到自己的业务层。

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


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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值