[muduo网络库]——EchoServer之消息读取(剖析muduo网络库核心部分、设计思想)

上一节我们剖析到了连接是如何建立[muduo网络库]——EchoServer之连接建立(剖析muduo网络库核心部分、设计思想)的,建立连接以后,线程都处于阻塞在poller_->pollepoll_wait上,主线程等待新连接的到来,子线程此时也在监听以连接用户的状态,也就是消息的到来。
消息到来以后,线程是如何处理的呢?那么这一节我们来一步一步的剖析一下这个过程。

消息读取

  • 在上一节我们提到,当mainLoop接受新连接请求之后,实例化了一个Acceptor对象,并将这个Tcp连接封装成TcpConnection对象,并使用TcpConnectionPtr智能指针来管理它。TcpConnection对象在构造函数中,主要就是封装了连接套接字的fd、连接套接字的channel_,以及注册了相关的回调方法在对应的channel_内,其中尤其要关注:如何关闭连接的回调 conn->shutdown
    conn->setCloseCallback(
        std::bind(&TcpServer::removeConnection,this,std::placeholders::_1)
    );
  • 当TcpConnection对象建立完毕之后,mainLoop的Acceptor会将这个TcpConnection对象中的channel_通过轮询算法注册到某一个SubLoop中,每一个Channel封装着一个fd及fd感兴趣的事件和事件监听器监听到该fd实际发生的事件,开始监听Channel_上的可读事件。
  • 当新接收的连接有消息到来时,对应的子线程Poller就会检测到该连接对应的Channel_可读,于是就会解除阻塞,去执行该channel上的可读事件,即TcpConnection::handleRead()
void TcpConnection::handleRead(TimeStamp receiveTime)
{
    int savedErrno = 0;
    ssize_t n = inputBuffer_.readFd(channel_->fd(),&savedErrno);
    if(n > 0)
    {
        //已建立连接的用户,有可读事件发生了,调用用户传入的回调操作onMessage
        //shared_from_this()获取了当前TcpConnection对象的智能指针
        messageCallback_(shared_from_this(),&inputBuffer_,receiveTime);
    }
    else if(n==0) //客户端断开
    {
        handleClose();
    } 
    else
    {
        errno = savedErrno;
        LOG_ERROR("TcpConnection::hanleRead");
        handleError();
    }

}
  1. 先定义了一个savedErrno,并且如果在后续数据的读取拷贝过程中发生错误,将错误信息保存在savedErrno中,并调用TcpConnection::handleError( )来处理。
  2. TcpConnection::handleRead( )函数首先调用inputBuffer_.readFd(channel_->fd(),&savedErrno),该函数底层调用系统的readv( ),将Tcp接收缓冲区数据拷贝到用户定义的缓冲区inputBuffer_。如果在读取拷贝的过程中发生了什么错误,这个错误信息就会保存在savedErrno中。
  3. 当读取的数据大于0时,那么会接着调用messageCallback_处理函数。对于messageCallback_,是调用用户传入的回调操作onMessage
server_.setMessageCallback(
                std::bind(&EchoServer::onMessage,this,
                    std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)
            );
  • readFd( )返回值等于0,说明客户端连接关闭,这时候应该调用TcpConnection::handleClose( )来处理连接关闭事件

发送消息

  • 在用户定义的onMessage
void onMessage(const TcpConnectionPtr &conn,
                Buffer *buf,
                TimeStamp time)
        {
             //用户定义的可读事件处理函数:当一个Tcp连接发生了可读事件就把它这个接收到的消息原封不动的还回去
            std::string msg = buf->retrieveAllAsString();
            conn->send(msg);
            conn->shutdown(); //关闭写端EPOLLHUP=> closeCallback
        }
  • 我们可以看出,在获得对方接收的消息后,调用conn->send(msg),实际上是调用TcpConnection::send()
void TcpConnection::send(const std::string &buf) //直接引用buffer
{
    if(state_ == kConnected)
    {
        if(loop_->isInLoopThread())
        {
            sendInLoop(buf.c_str(),buf.size());
        }
        else
        {
            loop_->runInLoop(std::bind(&TcpConnection::sendInLoop
                                , this
                                , buf.c_str()
                                , buf.size()
            ));
        }
    }
}
  • 接着内部调用了TcpConnection::sendInLoop()
void TcpConnection::sendInLoop(const void* data, size_t len)
{
    ssize_t nwrote = 0; // 已经发送的数据长度
    size_t remaining = len; //未发送的数据
    bool faultError = false; //记录是否产生错误

    //之前调用过connection的shutdown 不能在发送了
    if(state_ == kDisconnected)
    {
        LOG_ERROR("disconnected,give up writing!");
        return ;
    }

    //channel 第一次开始写数据,且缓冲区没有待发送数据
    if(!channel_->isWriting() && outputBuffer_.readableBytes() == 0)
    {
        nwrote = ::write(channel_->fd(),data,len);
        if(nwrote >= 0)
        {
            remaining = len - nwrote;
            if(remaining == 0 && writeCompleteCallback_)
            {
                //一次性数据全部发送完成,就不要再给channel设置epollout事件了
                loop_->queueInLoop(
                    std::bind(writeCompleteCallback_,shared_from_this()));
            }
        }
        else
        {
            nwrote = 0;
            if(errno != EWOULDBLOCK) //用于非阻塞模式,不需要重新读或者写
            {
                LOG_ERROR("TcpConnection::sendInLoop");
                if(errno == EPIPE || errno == ECONNRESET) //SIGPIPE RESET
                {
                    faultError = true;
                }
            }
        }
    }
    
    if(!faultError && remaining > 0) 
    {
        //目前发送缓冲区剩余的待发送数据的长度
        size_t oldlen = outputBuffer_.readableBytes();
        if(oldlen + remaining >= highWaterMark_ 
            && oldlen < highWaterMark_
            && highWaterMark_)
            {
                loop_->queueInLoop(
                    std::bind(highWaterMarkCallback_,shared_from_this(),oldlen + remaining)
                );
            }
            outputBuffer_.append((char*)data + nwrote,remaining);
            if(!channel_->isWriting())
            {
                channel_->enableWriting(); //注册channel写事件,否则poller不会向channel通知epollout
            }
    }
}
  1. 先判断outputBuffer_中是否还有数据需要发送;
  2. 如果没有,则直接把要发送的数据msg调用系统的write()发送出去,如果一次性发送完成了,调用writeCompleteCallback_
  3. 接着1,如果有,说明当前这一次write ,并没有把数据全发送出去,剩余的数据需要保存到缓冲区outputBuffer_当中,给channel注册epollout事件,poller发现tcp发送缓冲区有空间,会通知相应的socket-channel,调用相应的writeCallback()回调方法,也就是调用TcpConnection::handleWrite(),把发送缓冲区中数据全部发送出去
void TcpConnection::handleWrite()
{
    if(channel_->isWriting())
    {
        int savedErrno = 0;
        ssize_t n = outputBuffer_.writeFd(channel_->fd(),&savedErrno);
        if(n > 0)
        {
            outputBuffer_.retrieve(n); //处理了n个
            if(outputBuffer_.readableBytes() == 0) //发送完成
            {
                channel_->disableWriting(); //不可写了
                if(writeCompleteCallback_)
                {
                    //唤醒loop对应的thread线程,执行回调
                    loop_->queueInLoop(
                        std::bind(writeCompleteCallback_,shared_from_this())
                    );
                }
                if(state_ == kDisconnecting)
                {
                    shutdownInLoop();// 在当前loop中删除TcpConnection
                }
            }
        }
        else
        {
            LOG_ERROR("TcpConnection::handleWrite");
        }
    }
    else
    {
        LOG_ERROR("TcpConnection fd=%d is down, no more writing \n",channel_->fd());
    }
}
  • 接着调用Buffer::writeFd,实际上,内部也是调用了系统的write()函数,将数据写入到Tcp发送缓冲区,如果Tcp发送缓冲区还没法容纳剩余的未发送数据,就继续保持可写事件的监听。
  • 当数据全部拷贝到Tcp发送缓冲区之后,就会调用用户自定义的写完后的事件处理函数,并且移除该TcpConnection在事件监听器上的可写事件。为什么是可写事件在另一篇博客中剖析过了[muduo网络库]——muduo库TcpConnection类,万字总结(剖析muduo网络库核心部分、设计思想)

代码地址:https://github.com/Cheeron955/mymuduo/tree/master

好了~ 有关于使用muduo库搭建EchoServer之消息读取就分析到这里了,整个Tcp编程还剩最后的消息断开部分,下一节我们来分析它的内在联系,还是想说一点,文章中可能存在分析不到位或者有误的地方,希望路过的大佬们能够不吝赐教,我们下一节见~~
  • 28
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值