muduo实现的客户端与服务端连接

下面就是利用muduo进行客户端与服务器端的实例:

网络编程关注三个半事件:连接建立、连接断开、消息到达这算三个,还有半个是消息发送完毕(之所以是半个,因为对于低流量的服务,通常不需要关注该事件)

所以,我们利用muduo编写自己的服务器时,需要有一个XXXServer类,在该类中包含一个TcpServer对象和一个EventLoop对象。这个类需要提供3个成员函数OnConnection、OnMessage、OnWriteComplete作为回调函数

#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/InetAddress.h>

#include <boost/bind.hpp>

#include <stdio.h>

using namespace muduo;
using namespace muduo::net;

class TestServer
{
 public:
  TestServer(EventLoop* loop,
             const InetAddress& listenAddr)
    : loop_(loop),
      server_(loop, listenAddr, "TestServer")
  {
	//连接到来和连接断开时的回调函数
    server_.setConnectionCallback(
        boost::bind(&TestServer::onConnection, this, _1));
	//消息到来时的回调函数
    server_.setMessageCallback(
        boost::bind(&TestServer::onMessage, this, _1, _2, _3));
  }

  void start()
  {
	  server_.start();
  }

 private:
  void onConnection(const TcpConnectionPtr& conn)
  {
    if (conn->connected())
    {
      printf("onConnection(): new connection [%s] from %s\n",
             conn->name().c_str(),
             conn->peerAddress().toIpPort().c_str());
    }
    else
    {
      printf("onConnection(): connection [%s] is down\n",
             conn->name().c_str());
    }
  }

  void onMessage(const TcpConnectionPtr& conn,
                 Buffer* buf,
                 Timestamp receiveTime)
  {
	//获取应用层缓冲区的数据
    string msg(buf->retrieveAllAsString());
    printf("onMessage(): received %zd bytes from connection [%s] at %s\n",
           msg.size(),
           conn->name().c_str(),
           receiveTime.toFormattedString().c_str());
    //将数据回射回去
	conn->send(msg);
  }

  EventLoop* loop_;
  TcpServer server_;
};


int main()
{
  printf("main(): pid = %d\n", getpid());

  InetAddress listenAddr(8888);
  EventLoop loop;

  TestServer server(&loop, listenAddr);
  //服务端启动
  server.start();
  //开始事件循环
  loop.loop();
}

分析:

1.server.starrt()也就是server_.start(),此时服务端启动

//该函数可以跨线程调用
//启动线程池
void TcpServer::start()
{
  if (started_.getAndSet(1) == 0)
  {
    threadPool_->start(threadInitCallback_);

    //判断是否处于监听状态
    assert(!acceptor_->listenning());
    loop_->runInLoop(
		//进入&Acceptor::listen
        boost::bind(&Acceptor::listen, get_pointer(acceptor_)));
  }
}

2.执行Acceptor::listen(),此时服务器处于监听状态,并且注册被动连接通道,关注其可读事件

//监听
void Acceptor::listen()
{
  loop_->assertInLoopThread();
  listenning_ = true;
  //监听
  acceptSocket_.listen();
  //关注可读事件
  acceptChannel_.enableReading();
}

3.注册被动连接通道,关注其可读事件

//关注可读事件,把通道注册到EventLoop,从而注册到EventLoop所持有的poll对象中
  void enableReading() { events_ |= kReadEvent; update(); }
void Channel::update()
{
  addedToLoop_ = true;
  //调用loop的updateChannel
  loop_->updateChannel(this);
}

进而调用loop的updateChannel()

void EventLoop::updateChannel(Channel* channel)
{
  assert(channel->ownerLoop() == this);
  assertInLoopThread();
  poller_->updateChannel(channel);
}

进而调用poller(有可能是PollPoller或者EPollPoller)的updateChannel()

//注册或者更新通道
//注册某个文件描述符的一些可读可写事件,更新就是原来关注可读可写,现在只关注可读
void PollPoller::updateChannel(Channel* channel)
{
  //断言是在Loop线程中调用Poller
  Poller::assertInLoopThread();
  LOG_TRACE << "fd = " << channel->fd() << " events = " << channel->events();
  //如果index_<0,位置还不知道,说明是一个新的通道
  if (channel->index() < 0)
  {
    // a new one, add to pollfds_
    assert(channels_.find(channel->fd()) == channels_.end());
    struct pollfd pfd;
    pfd.fd = channel->fd();
    pfd.events = static_cast<short>(channel->events());
    pfd.revents = 0;
    pollfds_.push_back(pfd);
    int idx = static_cast<int>(pollfds_.size())-1;
	//现在是个已经存在的通道,idx不会为-1
    channel->set_index(idx);
	//key是文件描述符,value是channel
    channels_[pfd.fd] = channel;
  }
  //更新一个已经存在的通道
  else
  {
    // update existing one
    assert(channels_.find(channel->fd()) != channels_.end());
    assert(channels_[channel->fd()] == channel);
    int idx = channel->index();
    assert(0 <= idx && idx < static_cast<int>(pollfds_.size()));
    struct pollfd& pfd = pollfds_[idx];
    assert(pfd.fd == channel->fd() || pfd.fd == -channel->fd()-1);
    pfd.fd = channel->fd();
    pfd.events = static_cast<short>(channel->events());
    pfd.revents = 0;
	//将一个通道暂时更新为不关注事件,但不从Poller中移除该通道
    if (channel->isNoneEvent())
    {
      // ignore this pollfd
      //暂时忽略该文件描述符的事件
      pfd.fd = -channel->fd()-1;
    }
  }
}

4.经过上面的步骤,就注册了被动连接通道的可读事件,loop::loop();开始事件循环,此时如果客户端有连接过来,poller_->poll()就会返回这个活跃的通道

//事件循环,该函数不能跨线程使用,只能在创建对象的线程中调用
void EventLoop::loop()
{
  assert(!looping_);
  //断言当前处于创建该对象的线程中
  assertInLoopThread();
  looping_ = true;
  quit_ = false;  // FIXME: what if someone calls quit() before loop() ?
  LOG_TRACE << "EventLoop " << this << " start looping";

  while (!quit_)
  {
    activeChannels_.clear();
	//调用poll返回活动的通道,前一个参数是超时时间
    pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);
    ++iteration_;
    if (Logger::logLevel() <= Logger::TRACE)
    {
      //打印活动通道
      printActiveChannels();
    }
    // TODO sort channel by priority
    eventHandling_ = true;
	//遍历活动通道进行处理
    for (ChannelList::iterator it = activeChannels_.begin();
        it != activeChannels_.end(); ++it)
    {
      //当前的处理通道
      currentActiveChannel_ = *it;
	  //调用handleEvent处理通道
      currentActiveChannel_->handleEvent(pollReturnTime_);
    }
    currentActiveChannel_ = NULL;
    eventHandling_ = false;
	//其它线程或者当前IO线程添加的一些回调任务
	//因为IO线程的设计比较灵活,IO线程也能执行一些IO任务
	//I/O不是很繁忙的时候,IO线程就一直处于阻塞的状态,也就是不工作的状态
	//我们就可以添加一些计算任务,让它来处理
    doPendingFunctors();
  }

5.调用Channel中的handleEvent处理活跃的通道

//当事件到来调用handleEvent处理 
void Channel::handleEvent(Timestamp receiveTime)
{
  boost::shared_ptr<void> guard;
  if (tied_)
  {
    //提升,返回一个shared_ptr对象
    guard = tie_.lock();
    if (guard)
    {
      handleEventWithGuard(receiveTime);
    }
  }
  else
  {
    handleEventWithGuard(receiveTime);
  }
}

void Channel::handleEventWithGuard(Timestamp receiveTime)
{
  eventHandling_ = true;
  LOG_TRACE << reventsToString();
  if ((revents_ & POLLHUP) && !(revents_ & POLLIN))
  {
    if (logHup_)
    {
      LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLHUP";
    }
    if (closeCallback_) closeCallback_();
  }

  //不是合法的文件描述符
  if (revents_ & POLLNVAL)
  {
    LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLNVAL";
  }
 
  //错误的
  if (revents_ & (POLLERR | POLLNVAL))
  {
    if (errorCallback_) errorCallback_();
  }
  //可读事件产生
  if (revents_ & (POLLIN | POLLPRI | POLLRDHUP))
  {
    if (readCallback_) readCallback_(receiveTime);
  }
  //可写事件产生
  if (revents_ & POLLOUT)
  {
    if (writeCallback_) writeCallback_();
  }
  eventHandling_ = false;
}

6.此时是已连接通道中的可读事件发生,调用Acceptor注册的回调函数处理

Acceptor的构造函数中注册的回调函数

  acceptChannel_.setReadCallback(
      boost::bind(&Acceptor::handleRead, this));

进入handleRead()

void Acceptor::handleRead()
{
  loop_->assertInLoopThread();
  //准备一个地址,接受对等方地址
  InetAddress peerAddr;
  //FIXME loop until no more
  //得到了一个连接
  int connfd = acceptSocket_.accept(&peerAddr);
  if (connfd >= 0)
  {
    // string hostport = peerAddr.toIpPort();
    // LOG_TRACE << "Accepts of " << hostport;
    //如果上层定义了回调函数,则执行
    if (newConnectionCallback_)
    {
      //传递套接字以及对等方的地址
      newConnectionCallback_(connfd, peerAddr);
    }
    else
    {
      sockets::close(connfd);
    }
  }
  //失败
  else
  {
    LOG_SYSERR << "in Acceptor::handleRead";
    // Read the section named "The special problem of
    // accept()ing when you can't" in libev's doc.
    // By Marc Lehmann, author of libev.
    if (errno == EMFILE)
    {
      //把这个空闲的文件描述符关闭掉,腾出一个位置
      ::close(idleFd_);
      idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL);
	  //接受完以后关闭掉
      ::close(idleFd_);
      idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
    }
  }
}

7.调用TcpServer中注册的回调函数,进行相应可读事件的处理

TcpServer的构造函数中注册的回调函数

//对accptor_设置一个回调函数
  //Acceptor::handleRead函数中会回调用TcpServer::newConnection
  //_1对应的是socket文件描述符,_2对应的是对等方的地址
  acceptor_->setNewConnectionCallback(
      boost::bind(&TcpServer::newConnection, this, _1, _2));

进入newConnection(),此时就创建了一个连接对象

//一个新的连接之后
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
  loop_->assertInLoopThread();
  //按照轮叫的方式选择一个EventLoop	
  EventLoop* ioLoop = threadPool_->getNextLoop();
  char buf[64];
  snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_);
  ++nextConnId_;
  //连接名称
  string connName = name_ + buf;

  LOG_INFO << "TcpServer::newConnection [" << name_
           << "] - new connection [" << connName
           << "] from " << peerAddr.toIpPort();
  //构造本地地址
  InetAddress localAddr(sockets::getLocalAddr(sockfd));
  // FIXME poll with zero timeout to double confirm the new connection
  // FIXME use make_shared if necessary
  //创建一个连接对象
  TcpConnectionPtr conn(new TcpConnection(ioLoop,
                                          connName,
                                          sockfd,
                                          localAddr,
                                          peerAddr));
  //将连接对象放到一个map容器中
  connections_[connName] = conn;
  对这个连接对象设置回调函数
  conn->setConnectionCallback(connectionCallback_);
  conn->setMessageCallback(messageCallback_);
  conn->setWriteCompleteCallback(writeCompleteCallback_);
  conn->setCloseCallback(
      boost::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe
  //不能够在当前线程中调用,应该让ioLoop所属的IO线程调用这个连接
  ioLoop->runInLoop(boost::bind(&TcpConnection::connectEstablished, conn));
}

8.进入connectEstablished,因为有新的连接的建立,准备将已连接通道加入到Poller,关注其可读事件

//当连接到来的时候
void TcpConnection::connectEstablished()
{
  loop_->assertInLoopThread();
  assert(state_ == kConnecting);
  setState(kConnected);
  //tie的参数是shared_ptr,shared_from_this()获得一个自身的share_ptr对象
  
  channel_->tie(shared_from_this());
  //TcpConnection所对应的通道加入到Poller关注
  channel_->enableReading();

  //这是用户的回调函数
  connectionCallback_(shared_from_this());
}

9.注册已连接通道,关注其可读事件

代码和步骤3的代码一样

10.经过上面的步骤,就注册了已连接通道,关注了可读事件,loop::loop()一直在循环,此时如果客户端有发消息过来,poller_->poll()就会返回这个活跃的通道

代码和步骤4的代码一样

11.调用Channel中的handleEvent处理活跃的通道

代码和步骤5的代码一样

12.此时是已连接通道中的可读事件发生,调用TcpConnection注册的回调函数处理

TcpConnection的构造函数中注册的回调函数

//通道可读事件到来的时候,回调tcpConnection::handleRead,_1是事件发生时间
  channel_->setReadCallback(
      boost::bind(&TcpConnection::handleRead, this, _1));

进入handleRead(),此时就将socket中的数据接收到了缓冲区中,如果应用层注册了回调函数,则执行。

//当一个消息到来的时候,调用handleRead
void TcpConnection::handleRead(Timestamp receiveTime)
{
  loop_->assertInLoopThread();
  int savedErrno = 0;
  //将数据接受到inputBuffer_这个缓冲区中
  ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);
  if (n > 0)
  {
    //shared_from_this()的作用是转为shared_ptr
    messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
  }
  //处理连接断开
  else if (n == 0)
  {
    //返回为0的话,处理连接断开
    handleClose();
  }
  //否则的话处理错误
  else
  {
    errno = savedErrno;
    LOG_SYSERR << "TcpConnection::handleRead";
    handleError();
  }
}

调用inputBuffer_.readFd(channel_->fd,&savedErrno);

//从套接字读取数据,然后添加到缓冲区中
//结合栈上的空间,避免内存使用过大,提高内存使用率
//如果有5000个连接,每个连接就分配64K+64K的缓冲区的话,将占用640M内存
//而大多数时候,这些缓冲区使用率很低
ssize_t Buffer::readFd(int fd, int* savedErrno)
{
  // saved an ioctl()/FIONREAD call to tell how much to read
  //栈上的空间
  char extrabuf[65536];
  struct iovec vec[2];
  const size_t writable = writableBytes();
  //第一块缓冲区
  vec[0].iov_base = begin()+writerIndex_;
  vec[0].iov_len = writable;
  //第二块缓冲区,这是栈上的空间
  vec[1].iov_base = extrabuf;
  vec[1].iov_len = sizeof extrabuf;
  // when there is enough space in this buffer, don't read into extrabuf.
  // when extrabuf is used, we read 128k-1 bytes at most.
  const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1;
  const ssize_t n = sockets::readv(fd, vec, iovcnt);
  if (n < 0)
  {
    *savedErrno = errno;
  }
  //第一块缓冲区足够容纳
  else if (implicit_cast<size_t>(n) <= writable)
  {
    writerIndex_ += n;
  }
  //当前缓冲区不够容纳,因而数据被接受到了第二块缓冲区extrabuf,将其append至buffer
  else
  {
    writerIndex_ = buffer_.size();
    append(extrabuf, n - writable);
  }
  // if (n == writable + sizeof extrabuf)
  // {
  //   goto line_30;
  // }
  return n;
}
13.执行应用层中消息到达回调函数
  void onMessage(const TcpConnectionPtr& conn,
                 Buffer* buf,
                 Timestamp receiveTime)
  {
	//获取应用层缓冲区的数据
    string msg(buf->retrieveAllAsString());
    printf("onMessage(): received %zd bytes from connection [%s] at %s\n",
           msg.size(),
           conn->name().c_str(),
           receiveTime.toFormattedString().c_str());
    //将数据回射回去
	conn->send(msg);
  }

  EventLoop* loop_;
  TcpServer server_;
};

14.现在服务端已经收到了数据,而且还要执行conn->send(),转到send()函数

//线程安全,可以跨线程调用
void TcpConnection::send(const StringPiece& message)
{
  if (state_ == kConnected)
  {
    if (loop_->isInLoopThread())
    {
      sendInLoop(message);
    }
    else
    {
      loop_->runInLoop(
          boost::bind(&TcpConnection::sendInLoop,
                      this,     // FIXME
                      message.as_string()));
                    //std::forward<string>(message)));
    }
  }
}

转入sendInLoop()

如果已连接通道没有关注可写事件并且发送缓冲区没有数据,直接write,此时有两种情况,如果把数据都写入了内核缓冲区,那么很方便,结束。如果发送了一部分,内核缓冲区满了,则需要将未发送到内核缓冲区的数据加入应用层缓冲区,因为应用层缓冲区有数据了,此时就需要关注已连接通道的可写事件了。

void TcpConnection::sendInLoop(const void* data, size_t len)
{
  loop_->assertInLoopThread();
  ssize_t nwrote = 0;
  size_t remaining = len;
  bool faultError = false;
  if (state_ == kDisconnected)
  {
    LOG_WARN << "disconnected, give up writing";
    return;
  }
  // if no thing in output queue, try writing directly
  //通道没有关注可写事件并且发送缓冲区没有数据,直接write
  if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0)
  {
    //channel_->fd()是已连接套接字
    nwrote = sockets::write(channel_->fd(), data, len);
    if (nwrote >= 0)
    {
      remaining = len - nwrote;
	  //写完了,回调writeCompleteCallback_
      if (remaining == 0 && writeCompleteCallback_)
      {
        loop_->queueInLoop(boost::bind(writeCompleteCallback_, shared_from_this()));
      }
    }
    else // nwrote < 0
    {
      nwrote = 0;
      if (errno != EWOULDBLOCK)
      {
        LOG_SYSERR << "TcpConnection::sendInLoop";
        if (errno == EPIPE || errno == ECONNRESET) // FIXME: any others?
        {
          faultError = true;
        }
      }
    }
  }

  assert(remaining <= len);
  //没有错误,并且还有未写完的数据(说明内核缓冲区满,要将未写完的数据添加到output buffer中)
  if (!faultError && remaining > 0)
  {
    size_t oldLen = outputBuffer_.readableBytes();
    if (oldLen + remaining >= highWaterMark_
        && oldLen < highWaterMark_
        && highWaterMarkCallback_)
    {
      loop_->queueInLoop(boost::bind(highWaterMarkCallback_, shared_from_this(), oldLen + remaining));
    }
	//将未发送完的数据添加到outputBuffer_
	//这个时候outputBuffer_有数据了,需要关注POLLOUT事件
    outputBuffer_.append(static_cast<const char*>(data)+nwrote, remaining);
    if (!channel_->isWriting())
    {
      //关注POLLOUT事件
      //POLLOUT事件触发的时候,回调handleWrite()
      channel_->enableWriting();
    }
  }
}

15.已连接通道关注POLLOUT(可写)事件

void enableWriting() { events_ |= kWriteEvent; update(); }

然后重复步骤3的代码,不同的是,已连接通道早已注册,现在是要改变其关注的事件(加上可写事件)

16.此时,如果内核缓冲区有了空余,loop->loop()返回已连接通道

调用Channel中的handleEvent处理活跃的通道

代码和步骤5的代码一样

17.此时是已连接通道中的可写事件发生,调用TcpConnection注册的回调函数处理

TcpConnection的构造函数中注册的回调函数

  //通道可写事件到来的时候,回调tcpConnection::handleWrite,_1是事件发生时间
  channel_->setWriteCallback(
      boost::bind(&TcpConnection::handleWrite, this));
进入handleWrite(),此时内核缓冲区中有空间了,可以将应用层缓冲区中的数据写入内核缓冲区了,此时有两种情况,内核缓冲区中的空余空间可以容纳应用层缓冲区中的数据了,那么将数据写入内核缓冲区后,就清空应用层发送缓冲区中的数据,并且停止已连接通道的可写事件。第二种情况是内核缓冲区还是只能容纳一部分,应用层缓冲区中还有数据,那么还需要继续关注可写通道中的POLLOUT事件,直到应用层缓冲区中的数据都发往内核缓冲区,结束。
//内核发送缓冲区有空间了,回调该函数,也就是POLLOUT事件触发了
void TcpConnection::handleWrite()
{
  loop_->assertInLoopThread();
  if (channel_->isWriting())
  {
    //将outputBuffer_缓冲区中的数据写入,不一定保证把数据都写入
    ssize_t n = sockets::write(channel_->fd(),
                               outputBuffer_.peek(),
                               outputBuffer_.readableBytes());
    if (n > 0)
    {
      outputBuffer_.retrieve(n);
	  //发送缓冲区已清空
      if (outputBuffer_.readableBytes() == 0)
      {
        //停止关注可写事件,以免出现busy loop
        channel_->disableWriting();
        if (writeCompleteCallback_)
        {
          //应用层发送缓冲区已被清空,就会调用writeCompleteCallback_
          loop_->queueInLoop(boost::bind(writeCompleteCallback_, shared_from_this()));
        }
	//发送缓冲区已清空并且连接状态是kDisconnecting,要关闭连接
        if (state_ == kDisconnecting)
        {
          //关闭连接
          shutdownInLoop();
        }
      }
    }
    else
    {
      LOG_SYSERR << "TcpConnection::handleWrite";
      // if (state_ == kDisconnecting)
      // {
      //   shutdownInLoop();
      // }
    }
  }
  else
  {
    LOG_TRACE << "Connection fd = " << channel_->fd()
              << " is down, no more writing";
  }
}

18.至此,整个过程就结束





猜你喜欢

转载自blog.csdn.net/wk_bjut_edu_cn/article/details/80936772