muduo : TcpConnection
来源:互联网 发布:数据挖掘用什么软件 编辑:程序博客网 时间:2024/05/14 02:49
引用
前面学习了TcpServer的实现,TcpServer对每个连接都会新建一个TcpConnection(使用shared_ptr管理)。接下来学习一下TcpConnection的设计细节。
连接状态
muduo对于一个连接的从生到死进行了状态的定义,类似一个状态机。
enum States { kDisconnected, kConnecting, kConnected, kDisconnecting };
分别代表:已经断开、初始状态、已连接、正在断开
成员变量
private EventLoop* loop_; // 神一样的存在,EventLoop string name_; States state_; // FIXME: use atomic variable // we don't expose those classes to client. boost::scoped_ptr<Socket> socket_; // 已连接套接字 boost::scoped_ptr<Channel> channel_; // 已连接套接字对应的Channel InetAddress localAddr_; InetAddress peerAddr_; ConnectionCallback connectionCallback_; MessageCallback messageCallback_; WriteCompleteCallback writeCompleteCallback_; ConnectionCallback closeCallback_; Buffer inputBuffer_; // 输入缓冲区 // MutexLock mutex_; Buffer outputBuffer_; // 输出缓冲区 boost::any context_; // 存放上下文
先理解上面的 loop_
, socket_
, channel_
好了,不明白请翻阅前几篇文章。
构造/析构函数
TcpConnection::TcpConnection(EventLoop* loop, const string& name__, int sockfd, const InetAddress& localAddr, const InetAddress& peerAddr) : loop_(CHECK_NOTNULL(loop)), name_(name__), state_(kConnecting), // 初始状态为kConnecting socket_(new Socket(sockfd)), // RAII管理已连接套接字 channel_(new Channel(loop, sockfd)), // 使用Channel管理套接字上的读写 localAddr_(localAddr), peerAddr_(peerAddr){ // 设置一些回调函数(好的,这很muduo) // 在已连接套接字可读时,调用TcpConnection::handleRead,进而调用用户设置的回调函数messageCallback_ channel_->setReadCallback( boost::bind(&TcpConnection::handleRead, this, _1)); channel_->setWriteCallback( boost::bind(&TcpConnection::handleWrite, this)); channel_->setCloseCallback( boost::bind(&TcpConnection::handleClose, this)); channel_->setErrorCallback( boost::bind(&TcpConnection::handleError, this)); LOG_DEBUG << "TcpConnection::ctor[" << name_ << "] at " << this << " fd=" << sockfd;}TcpConnection::~TcpConnection(){ LOG_DEBUG << "TcpConnection::dtor[" << name_ << "] at " << this << " fd=" << channel_->fd();}
构造函数在初始化列表中对socket、channel等进行了初始化,在函数体中设置了回调函数。
TcpConnection::handleRead
void TcpConnection::handleRead(Timestamp receiveTime){ loop_->assertInLoopThread(); int savedErrno; ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno); if (n > 0) { // 调用回调函数,使用shared_from_this()得到自身的shared_ptr, 延长了该对象的生命期,保证了它的生命期长过messageCallback_函数,messageCallback_能安全的使用它。 messageCallback_(shared_from_this(), &inputBuffer_, receiveTime); } else if (n == 0) { handleClose(); } else { // check savedErrno }}
前面提到了,在已连接套接字可读时,调用TcpConnection::handleRead
,进而调用用户设置的回调函数messageCallback_
连接的断开
这是muduo提到的三个半事件
中的另一个事件,即处理连接的断开。这个过程比较绕,是这样事儿的,图中的X
表示TcpConnection通常在此析构:
为什么看上去这么绕?
主要是由EventLoop::loop的结构造成的,再次贴出事件循环的主体:
void EventLoop::loop(){ // ... while (!quit_) { activeChannels_.clear(); // 1. 通过poller获取就绪的channel,放到activeChannels_中,poller会将发生的事件类型填写到channel的revents_中,供Channel::handleEvent使用 pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_); ++iteration_; eventHandling_ = true; for (ChannelList::iterator it = activeChannels_.begin(); it != activeChannels_.end(); ++it) { currentActiveChannel_ = *it; // 2. 调用channel的事件处理函数handleEvent,根据poller设置的发生的事件类型,调用相应的用户回调函数 currentActiveChannel_->handleEvent(pollReturnTime_); } currentActiveChannel_ = NULL; eventHandling_ = false; // 3. 处理其他函数,由runInLoop加入到线程的任务容器 doPendingFunctors(); }//...}
一个对象在被使用时,必然是不能被析构的。这里的currentActiveChannel->handleEvent
在执行时必须保证currentActiveChannel
不会被析构,一般小的程序当然不会有这么显而易见的错误。不过对于稍微复杂点的程序是需要考虑的。
TcpConnection是拥有一个Channel的,它负责管理该Channel的生命周期,但是却必须把Channel的裸指针暴露给EventLoop(确切的讲是暴露给Poller),因为Poller需要对Channel的事件进行管理(添加、修改、删除)。
此外,TcpConnection是在TcpServer中创建的,但是连接断开事件是被TcpConnection得知的。TcpConnection需要将TcpServer中存放自己shared_ptr的容器中的对应响删除掉,然后才能析构。为了在TcpServer进行erase时不会将TcpConnection析构,需要使用一定的手段(shared_from_this
)
而且上面讲到了,TcpConnection的Channel的裸指针暴露给了EventLoop的Poller。根据上图的调用链,不能在使用Channel时将TcpConnection析构,因为TcpConnection析构时,Channel也会被析构,这就造成正在使用一个对象时,它确被析构了,将导致严重的问题。所以muduo使用queueInLoop
使得TcpConnection的析构点位于使用完channel之后的位置(见EventLoop::loop)。
看来,对于对象的生命期管理有时还是很负责。
下面会继续进行分析。
TcpConnection::handleClose
void TcpConnection::handleClose(){ loop_->assertInLoopThread(); // we don't close fd, leave it to dtor, so we can find leaks easily. setState(kDisconnected); // 设置状态为kDisconnected,表示已断开 channel_->disableAll(); // 移除注册的事件,使用epoll时是EPOLL_CTL_DEL TcpConnectionPtr guardThis(shared_from_this()); // 延长本对象的生命周期,引用计数为2 // 调用用户回调函数 connectionCallback_(guardThis); // 参数为shared_ptr,保证了 connectionCallback_能安全的使用本对象 // 调用TcpServer::removeConnection // must be the last line closeCallback_(guardThis);}
连接断开时,会调用TcpConnection::handleClose
;接着调用用户回调connectionCallback_
;最后调用closeCallback_
,即TcpServer::removeConnection
(TcpServer创建TcpConnection时设置的)
TcpServer::removeConnection
void TcpServer::removeConnection(const TcpConnectionPtr& conn){ loop_->runInLoop(boost::bind(&TcpServer::removeConnectionInLoop, this, conn));}void TcpServer::removeConnectionInLoop(const TcpConnectionPtr& conn){ loop_->assertInLoopThread(); LOG_INFO << "TcpServer::removeConnectionInLoop [" << name_ << "] - connection " << conn->name(); // 根据conn的name,从map容器中删除,此时引用计数会减1。erase之前引用计数为2(由前面的shared_from_this()保证),所以执行完erase,引用计数变为1 size_t n = connections_.erase(conn->name()); assert(n == 1); // 然后调用conn->connectDestroyed EventLoop* ioLoop = conn->getLoop(); ioLoop->queueInLoop( boost::bind(&TcpConnection::connectDestroyed, conn)); // bind延长了conn的生命期,connectDestroyed完成后,TcpConnection被析构。 // FIXME wake up ?}
TcpServer先将该conn从map容器中删除,因为erase之前使用了shared_from_this
,所以erase之前引用计数为2,那么erase之后引用计数将变为1。
如果没用shared_from_this
,仅仅传递了一个裸指针过来,erase之后引用计数变为0,那么该TcpConnection会被析构!这意味着TcpConnection的Channel也会被析构,可是你现在正在使用该Channel啊(结合上图看),怎么能在使用某个对象的时候把它析构呢,这是严重的错误。所以muduo使用shared_ptr管理TcpConnection,避免了上述问题。
最后queueInLoop
就是将TcpConnection::connectDestroyed
函数移动到EventLoop中执行,执行位置就是在Channel->handleEvent
之后,此时可以安全的析构TcpConnection。(这么做的原因见前面)
注意上面最后的boost::bind,它让TcpConnection的生命期长到调用connectDestroyed的时刻。在connectDestroyed
执行完之后,TcpConnection才被析构。
TcpConnection::connectDestroyed
void TcpConnection::connectDestroyed(){ loop_->assertInLoopThread(); if (state_ == kConnected) { setState(kDisconnected); channel_->disableAll(); connectionCallback_(shared_from_this()); } // 将EventLoop.Poller中的该channel从容器中删除 loop_->removeChannel(get_pointer(channel_));}
TcpConnection::connectDestroyed
是该对象析构前调用的最后一个成员函数,它会通知用户连接已经断开。
我已经用了洪荒之力来理解TcpConnection,实在是太绕了。
- muduo : TcpConnection
- muduo Tcpconnection类
- muduo::TcpConnection分析
- muduo : TcpConnection's Read Buffer
- muduo : TcpConnection's Write Buffer
- muduo源码解析之TcpConnection
- muduo库的TcpServer和TcpConnection用法
- muduo源码分析之TcpConnection发送数据
- Muduo之TcpConnection源码分析笔记
- muduo源码学习(21)-TcpSever/TcpConnection
- 从epoll构建muduo-5 加入Acceptor和TcpConnection
- 从epoll构建muduo-5 加入Acceptor和TcpConnection
- muduo网络库学习笔记(12):TcpServer和TcpConnection类
- muduo库TcpConnection对send、shutdown、SIGPIPE的处理
- muduo中TcpConnection里IO事件的处理
- muduo网络库学习之EventLoop(五):TcpConnection生存期管理(连接关闭)
- muduo网络库学习之EventLoop(六):TcpConnection::send()、shutdown()、handleRead()、handleWrite()
- muduo源码分析--事件回调层次是怎么传递的Tcpserver Channel TcpConnection
- 【Android】【版本差异】启动模式
- ViewPager无限轮播,下方展示小点
- HDU-5807 Keep In Touch
- Nim博弈
- java.lang.ClassCastException: org.apache.catalina.connector.RequestFacade cannot be
- muduo : TcpConnection
- Android 网络开源库-Retrofit(一)简单介绍
- Ubuntu apt命令
- CSS-伪类、元素
- 呱呱财经视频社区V5.6无插件官方版
- 13、耶稣有13个门徒,其中有一个就是出卖耶稣的叛徒,请用排除法找出这位叛徒:13人围坐一圈,从第一个开始报号:1,2,3,1,2,3……,凡是报到“3”就退出圈子,最后留在圈内的人就是出卖耶稣的叛
- [C#] winform中的DataGridView的列宽设置(自动调整列宽)
- hive建表
- Nxlog——日志采集神器简介