muduo中TcpServer、TcpConnection执行过程

muduo中的Acceptor类的主要功能是socket、bind、listen,Acceptor用于accept接受TCP连接

一般来说,在上层应用程序中,我们不直接使用Acceptor,而是把它作为TcpServer的成员
 

TcpServer还包含了一个TcpConnection列表这是一个已连接列表。TcpConnection与Acceptor类似,有两个重要的数据成员,Socket与Channel。

所以说,Acceptor用于accept接受TCP连接。Acceptor的数据成员包括Socket、Channel,Acceptor的socket是listening socket(即server socket)。Channel用于观察此socket的readable事件,并回调Acceptor::handleRead(),后者调用acceptor来接受新连接,并回调用户callback。

 

下面分析一个程序的执行步骤:

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

#include <stdio.h>

using namespace muduo;
using namespace muduo::net;

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,
               const char* data,
               ssize_t len)
{
  printf("onMessage(): received %zd bytes from connection [%s]\n",
         len, conn->name().c_str());
}

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

  //构造一个地址对象
  InetAddress listenAddr(8888);
  EventLoop loop;

  TcpServer server(&loop, listenAddr, "TestServer");
  //连接到来的回调函数
  server.setConnectionCallback(onConnection);
  //消息到来的回调函数
  server.setMessageCallback(onMessage);
  //启动
  server.start();

  loop.loop();
}

分析:

程序后面的acceptor_->setNewConnectionCallback(boost::bind(&TcpServer::newConnection, this, _1, _2));这个回调函数的注册对应的是acceptor_,也就是说Acceptor的handleRead()执行后会调用上层的(也就是TcpServer)newConnection函数。

TcpServer server(&loop, listenAddr, "TestServer");

TcpServer::TcpServer(EventLoop* loop,
                     const InetAddress& listenAddr,
                     const string& nameArg,
                     Option option)
  : loop_(CHECK_NOTNULL(loop)),
    ipPort_(listenAddr.toIpPort()),
    name_(nameArg),
    acceptor_(new Acceptor(loop, listenAddr, option == kReusePort)),
    threadPool_(new EventLoopThreadPool(loop, name_)),
    connectionCallback_(defaultConnectionCallback),
    messageCallback_(defaultMessageCallback),
    nextConnId_(1)
{
  //对accptor_设置一个回调函数
  //Acceptor::handleRead函数中会回调用TcpServer::newConnection
  //_1对应的是socket文件描述符,_2对应的是对等方的地址
  acceptor_->setNewConnectionCallback(
      boost::bind(&TcpServer::newConnection, this, _1, _2));
}

 启动,该函数可以跨线程调用,主要就是要执行Acceptor中的listen,也就是监听

server.start();

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

    //判断是否处于监听状态
    assert(!acceptor_->listenning());
    loop_->runInLoop(
		//现在服务器处于监听的状态了
        boost::bind(&Acceptor::listen, get_pointer(acceptor_)));
  }
}

关注监听套接字对应通道的可读事件 

void Acceptor::listen()
{
  loop_->assertInLoopThread();
  listenning_ = true;
  //监听
  acceptSocket_.listen();
  //关注可读事件,TcpConnection所对应的通道加入到Poller关注
  acceptChannel_.enableReading();
}

事件循环

loop.loop();

void EventLoop::loop()
{
    .....
    //遍历活动通道进行处理
    for (ChannelList::iterator it = activeChannels_.begin();
        it != activeChannels_.end(); ++it)
    {
      //当前的处理通道
      currentActiveChannel_ = *it;
	  //调用handleEvent处理通道
      currentActiveChannel_->handleEvent(pollReturnTime_);
    }
    ..... 
  }

当监听到一个连接到来的时候,在TcpServer.cc的构造函数中的setNewConnctionCallback,对Acceptor的acceptor_设置一个回调函数,当一个连接事件到来的时候,Poller的poll函数返回通道,调用了通道的handleEvent(),又调用了Acceptor的handleRead(),在TcpServer中注册的回调函数是TcpServer::newConnection 

所以先执行的Acceptor中的handleRead(),然后再执行TcpServer中的newConnection()

在handleRead()中得到已连接套接字,并传递给TcpServer的函数newConnection()

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);
    }
  }

 在函数newConnection()中创建连接对象并放入map容器进行管理,然后调用TcpConnection中的connectEstablished,进行连接的管理

//一个新的连接之后
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));
}

在这个函数中关注已连接通道的可读事件,此时是已连接状态,回调用户注册的连接建立函数。这个是在TcpServer的newConnection函数中注册的。 

//当连接到来的时候
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());
}

上面这个程序在连接到来的时候,将TcpConnection所对应的通道加入到Poller关注,此时,如果有可读事件发生,就会由handleEvent()->handleRead(),就可以处理连接通道中的可读事件了

读走数据并且执行回调函数messageCallback_(),这个函数是由用户注册到TcpServer,然后由TcpServer.cc中的newConnection()函数又注册到TcpConnection中。

//当一个消息到来的时候,调用handleRead
void TcpConnection::handleRead(Timestamp receiveTime)
{
  loop_->assertInLoopThread();
  int savedErrno = 0;
  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)
  {
    handleClose();
  }
  else
  {
    errno = savedErrno;
    LOG_SYSERR << "TcpConnection::handleRead";
    handleError();
  }
}

一个服务器维护一个已连接列表,当一个连接断开的时候,TcpConnection中的通道处于活跃状态,EventLopp的事件循环返回这个活跃的通道,并且调用handleEvent函数来处理,回调TcpConnection的handleRead函数,在这个函数中,又调用了read,这个时候read返回为0,又调用了handleClose函数,这个函数中又回调了TcpConnection的removeConnection,erase将这个连接对象从列表中移除。按照正常的思路,我们还应该将这个对象销毁掉,但是在这里我们不能立即销毁这个连接对象,如果销毁了这个对象,TcpConnection所包含的Channel对象也就跟着销毁了。而当前正在调用这个Channel对象的handleEvent函数,而这个Channel对象又销毁了,就会出现coredump。因而这个不能销毁TcpConnection对象,也就是说TcpConnection对象的生存期应该长于HandleEvent函数,如果做到这一点,可以利用shared_ptr来管理TcpConnection对象。

当连接到来,创建一个TcpConnection对象,立刻用shared_ptr来管理,这时候引用计数为1。

在Channel中维护一个weak_ptr(tie_),将这个shared_ptr对象赋值给tie_,因为是弱引用,所以引用计数不会加1。

当连接关闭,调用了Channel的handleEvent函数,在这个函数中,将tie_提升,得到一个shared_ptr对象,此时引用计数为2。

erase从列表中移除,引用计数减1,还剩1,因而TcpConnection对象不会销毁。

此时,会调用queueInLoop将connectDestroyed放到EventLoop的functors中,这个时候引用计数加1,变为2。handleEvent函数返回之后,它所提升的那个shared_ptr对象销毁了,引用计数减1,变为1。boost::function调用用户的函数connCb,引用计数减1变为0。TcpConnection对象就销毁了。

### 回答1: Linux多线程服务端编程是指使用Muduo C网络在Linux操作系统进行多线程的服务端编程。Muduo C网络是一个基于事件驱动的网络,采用了Reactor模式,并且在底层使用了epoll来实现高效的I/O复用。 使用Muduo C网络进行多线程服务端编程有以下几个步骤: 1. 引入Muduo C网络:首先需要下载并引入Muduo C网络的源代码,然后在编写代码时包含相应的头文件。 2. 创建并初始化EventLoop:首先需要创建一个EventLoop对象,它用于接收和分发事件。通过初始化函数进行初始化,并在主线程调用它的loop()函数来运行事件循环。 3. 创建TcpServer:然后创建一个TcpServer对象,它负责监听客户端的连接,并管理多个TcpConnection对象。通过设置回调函数,可以在特定事件发生时处理相应的逻辑。 4. 创建多个EventLoopThread:为了提高并发性能,可以创建多个EventLoopThread对象,每个对象负责一个EventLoop,从而实现多线程处理客户端的连接和请求。 5. 处理事件:在回调函数处理特定事件,例如有新的连接到来时会调用onConnection()函数,可以在该函数进行一些初始化操作。当有数据到来时会调用onMessage()函数,可以在该函数处理接收和发送数据的逻辑。 6. 运行服务端:在主线程调用TcpServer的start()函数来运行服务端,等待客户端的连接和请求。 总的来说,使用Muduo C网络进行Linux多线程服务端编程可以更好地利用多核处理器的性能优势。每个线程负责处理特定事件,通过事件驱动模式实现高效的网络编程。这样可以提高服务器的并发能力,提高系统的整体性能。 ### 回答2: Linux多线程服务端编程是指在Linux平台上使用多线程的方式来编写网络服务器程序。而使用muduo C网络是一种常见的方法,它提供了高效的网络编程接口,可以简化多线程服务器的开发过程muduo C网络基于Reactor模式,利用多线程实现了高并发的网络通信。在使用muduo C进行多线程服务端编程时,我们可以按照以下步骤进行: 1. 引入muduo:首先需要导入muduo C网络的头文件,并链接对应的文件,以供程序调用。 2. 创建线程池:利用muduo C的ThreadPool类创建一个线程池,用于管理和调度处理网络请求的多个线程。 3. 创建TcpServer对象:使用muduo CTcpServer类创建一个服务器对象,监听指定的端口,并设置好Acceptor、TcpConnectionCallback等相关回调函数。 4. 定义业务逻辑:根据具体的业务需求,编写处理网络请求的业务逻辑代码,如接收客户端的请求、处理请求、发送响应等。 5. 注册业务逻辑函数:将定义好的业务逻辑函数注册到TcpServer对象,以便在处理网络请求时调用。 6. 启动服务器:调用TcpServer对象的start函数,启动服务器,开始监听端口并接收客户端请求。 7. 处理网络请求:当有客户端连接到服务器时,muduo C会自动分配一个线程去处理该连接,执行注册的业务逻辑函数来处理网络请求。 8. 释放资源:在程序结束时,需要调用相应的函数来释放使用的资源,如关闭服务器、销毁线程池等。 通过使用muduo C网络,我们可以简化多线程服务端编程的过程,提高服务器的并发处理能力。因为muduo C网络已经实现了底层的网络通信细节,我们只需要专注于编写业务逻辑代码,从而减少开发的工作量。同时,muduo C的多线程模型可以有效地提高服务器的并发性能,满足高并发网络服务的需求。 ### 回答3: Linux多线程服务端编程是指在Linux操作系统上开发多线程的服务器应用程序。使用muduo C网络有助于简化开发过程,提供高效的网络通信能力。 muduo C网络是一个基于Reactor模式的网络,适用于C++语言,由Douglas Schmidt的ACE网络演化而来。它提供了高度并发的网络编程能力,封装了许多底层细节,使得开发者能够更加专注于业务逻辑的实现。 在开发过程,首先需要创建一个muduo C的EventLoop对象来管理事件循环。然后,可以利用TcpServer类来创建服务器并监听指定的端口。当有新的客户端请求到达时,muduo C会自动调用用户定义的回调函数处理请求。 在处理请求时,可以使用muduo C提供的ThreadPool来创建多个工作线程。这些工作线程将负责处理具体的业务逻辑。通过将工作任务分配给不同的线程,可以充分利用多核服务器的计算资源,提高服务器的处理能力。 在具体的业务逻辑,可以使用muduo C提供的Buffer类来处理网络数据。Buffer类提供了高效的数据读写操作,可以方便地进行数据解析与封装。 此外,muduo C还提供了TimerQueue类来处理定时任务,可以用于实现定时事件的调度与管理。这对于一些需要定期执行的任务非常有用,如心跳检测、定时备份等。 总之,使用muduo C网络可以简化Linux多线程服务端编程的开发过程,提供高效的并发能力。通过合理地利用多线程和其他的相关组件,可以实现高性能、稳定可靠的网络服务端应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值