学习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++ 技术,包括基础、自己遇到的问题解决过程。
当然如果关注并留言问题的话,我们力所能及的话会帮你解决并回复哟。我们和你一样,是正在成长的程序员,我们也会分享自己的成长路上的感想,希望可以和你一起努力成长。