muduo_net代码剖析之TcpServer

引言

  1. 上文,我们知道类Acceptor主要功能socket、bind、listen,并调用注册的回调函数来处理新到的连接。
  2. 一般来说,在上层应用程序中,很少直接使用Acceptor作为服务器程序,而是把Acceptor作为TcpServer的成员属性

先看下TcpServer连接建立/处理的时序图

muduo_net代码剖析之TcpServer_第1张图片
1:当loop()函数监听到通道acceptChannel_有事件到来,即listen套接字可读时
2:acceptChannel_->handleEvent()
3:Acceptor::handleRead()
4:handleRead()函数中又调用了accept()接收客户端的请求
5:连接建立后,会调用注册的回调函数newConnection()
6:在回调newConnection()函数中,使用返回的sockfd先创建channel_,再用sockfd+channel_构造一个TcpConnection对象用于与client通信(channel_是TcpConnection的成员变量,监听读写事件用于通信)
7:调用了TcpConnection::connectEstablished()函数,在connectEstablished函数中,又回调了用户注册的connCb函数

channel_->enableReading(); //关注读事件
connectionCallback_(shared_from_this()); //执行回调函数connectionCallback_

1、源码简单分析

  1. 成员变量
  typedef std::map<string, TcpConnectionPtr> ConnectionMap;

  EventLoop* loop_;  //the acceptor loop,即mainEVentLoop
  const string ipPort_; //端口
  const string name_;   //服务器的名字
  std::unique_ptr<Acceptor> acceptor_; //功能:socket、bind、listen
  std::shared_ptr<EventLoopThreadPool> threadPool_; //一个线程池
 
  //回调函数 
  ConnectionCallback connectionCallback_; //连接到来
  MessageCallback messageCallback_; //消息到来
  WriteCompleteCallback writeCompleteCallback_;
  ThreadInitCallback threadInitCallback_;
      
  AtomicInt32 started_;

  int nextConnId_; //
  ConnectionMap connections_; //哈希表,保存着每一个客户端连接

EventLoop* loop_是mainEventLoop
std::shared_ptr threadPool_;线程池,一个mainEventLoop+多个subEventLoop
std::unique_ptr acceptor_;用于接收新的连接
typedef std::map ConnectionMap;类型是一个哈希表,它的每一个元素,即就表示一个client连接,其中:key是string类型的字符串,它一定是唯一的,用于标识每一个TcpConnectionPtr。当然,新的连接建立后,将会添加到ConnectionMap中,并且由key唯一标识该连接。

  1. public函数接口
//构造函数、析构函数
 TcpServer(EventLoop* loop,
           const InetAddress& listenAddr,
           const string& nameArg,
           Option option = kNoReusePort);
 ~TcpServer();  // force out-line dtor, for std::unique_ptr members.

//获得成员变量的值
 const string& ipPort() const { return ipPort_; } //端口
 const string& name() const { return name_; } //服务名称
 EventLoop* getLoop() const { return loop_; } //mainEventLoop

//set成员变量的值
 //设置subEventLoop的个数
 void setThreadNum(int numThreads);
 {
   assert(0 <= numThreads);
   threadPool_->setThreadNum(numThreads);
 }
 
//set成员变量(回调函数)的值
 void setConnectionCallback(const ConnectionCallback& cb)
 { connectionCallback_ = cb; }
 void setMessageCallback(const MessageCallback& cb)
 { messageCallback_ = cb; }
 void setWriteCompleteCallback(const WriteCompleteCallback& cb)
 { writeCompleteCallback_ = cb; }

//开启所有的EventLoop && 开始listen客户端的连接请求
 void start();
 {
   if (started_.getAndSet(1) == 0) 
   {
    //EventLoopThreadPool::start(___)
     //1.创建numThreads个线程,每个子线程都创建一个事件循环subEventLoop
     //2.每个subEventLoop都开启事件循环,即调用loop()
     threadPool_->start(threadInitCallback_);

     //3.执行Acceptor::listen()
     assert(!acceptor_->listenning()); 
     loop_->runInLoop(  
         std::bind(&Acceptor::listen, get_pointer(acceptor_)));
     //get_pointer:返回原生指针
   }
 }

2、源码实现

  1. 构造函数
    (1) 首先创建一个TcpServer对象,在的创建过程中,首先new出来自己的核心组件(Acceptor,loop,connectionMap,threadPoll)
    (2) 之后Acceptor注册一个新连接到来时的TcpServer::newConnection回调函数
TcpServer::TcpServer(EventLoop* loop,
                     const InetAddress& listenAddr,
                     const string& nameArg,
                     Option option)
  : loop_(CHECK_NOTNULL(loop)), //检查loop不是空指针
    ipPort_(listenAddr.toIpPort()),
    name_(nameArg),
    //创建acceptor,用于listen、accept
    acceptor_(new Acceptor(loop, listenAddr, option == kReusePort)),
    //线程池,多个事件循环mainEventLoop+subEventLoop
    threadPool_(new EventLoopThreadPool(loop, name_)),
    connectionCallback_(defaultConnectionCallback),
    messageCallback_(defaultMessageCallback),
    nextConnId_(1)
{
//当一个新的连接到来时后,执行回调函数TcpServer::newConnection
  //_1 文件描述符    _2 对等方的地址
  acceptor_->setNewConnectionCallback(
      std::bind(&TcpServer::newConnection, this, _1, _2));
}
  1. 回调函数TcpServer::newConnection主要作用
    ①当一个新的连接到来时,根据Acceptor创建的可连接描述符和客户的地址,创建一个Connection对象
    ②并且将这个对象加入到TcpServer的ConnectionMap中,由TcpServer来管理上述新建conn对象。
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
  loop_->assertInLoopThread();
  //1.按照RR方式,查找ioLoop
  EventLoop* ioLoop = threadPool_->getNextLoop();
  
  // 生成唯一的name
  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));
  
  //2.在ioLoop上,创建一个TcpConnection对象
  TcpConnectionPtr conn(new TcpConnection(ioLoop,
                                          connName,
                                          sockfd,
                                          localAddr,
                                          peerAddr));
										  
  //3.将这个对象加入到ConnectionMap哈希表中
  connections_[connName] = conn; 
  
  //4.使用TcpServer类的成员变量(回调函数),给TcpConnection的成员变量赋值
  conn->setConnectionCallback(connectionCallback_);
  conn->setMessageCallback(messageCallback_);
  conn->setWriteCompleteCallback(writeCompleteCallback_);
  conn->setCloseCallback(std::bind(&TcpServer::removeConnection, this, _1)); 
  
  //5.调用conn->connectEstablished()
  ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
}

void TcpConnection::connectEstablished()
{
  loop_->assertInLoopThread();
  assert(state_ == kConnecting);
  setState(kConnected);
  channel_->tie(shared_from_this());
  channel_->enableReading(); //关注读事件

  //执行回调函数connectionCallback_
  connectionCallback_(shared_from_this());
}

3、网络编程的三个半事件

  1. 连接建立:onConnection
  2. 连接断开
  3. 消息到达:onMessage
  4. 消息发送完毕(对于低流量的服务来说,通常不需要关注该事件):onWriteComplete

4、编写一个XXX服务器类的通用方法

① 提供一个XXXServer类
② 在该类中包含一个TcpServer对象
③ 实现三个半事件,即:只需要bind并重写onConnection、onMessage、onWriteComplete函数

5、五个简单TCP协议

代码存放在muduo\examples\simple下

  1. echo—回显服务器
  2. discard—服务器丢弃所有收到的数据
  3. daytime—服务器accept连接之后,以字符串形式发送当前时间,然后主动断开连接
  4. time—服务器accept连接之后,以二进制形式发送当前时间(从Epoch到现在的秒数),然后主动断开连接;我们需要一个客户端程序来把收到的时间转换为字符串
  5. chargen—服务端accept连接之后,不停的发送测试数据

示例代码

echo-server回显服务器

该代码,它实际上是一个discard服务。但目前它永远不会关闭socket,即永远走不到else分支,在遇到对方断开连接的时候会陷入busy-loop。该问题留在后面章节解决

#include 

#include 
#include 
#include 
#include 

#include 

#include 
#include 

using namespace muduo;
using namespace muduo::net;

int numThreads = 0;

class EchoServer
{
public:
  EchoServer(EventLoop* loop, const InetAddress& listenAddr)
    : loop_(loop),
      server_(loop, listenAddr, "EchoServer")
  {
    server_.setConnectionCallback(
        std::bind(&EchoServer::onConnection, this, _1));
    server_.setMessageCallback(
        std::bind(&EchoServer::onMessage, this, _1, _2, _3));
    server_.setThreadNum(numThreads);
  }

  void start()
  {
    server_.start();
  }
private:
  void onConnection(const TcpConnectionPtr& conn)
  {
    LOG_TRACE << conn->peerAddress().toIpPort() << " -> "
        << conn->localAddress().toIpPort() << " is "
        << (conn->connected() ? "UP" : "DOWN");
    LOG_INFO << conn->getTcpInfoString();

    conn->send("hello\n");
  }

  void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time)
  {
	//接收readable中所有的数据,转换成string
    string msg(buf->retrieveAllAsString()); 
    LOG_TRACE << conn->name() << " recv " << msg.size() << " bytes at " << time.toString();
	
	//根据msg的内容,执行相应的动作
    if (msg == "exit\n") 
    {
      conn->send("bye\n");
      conn->shutdown();
    }
    if (msg == "quit\n") 
    {
      loop_->quit();
    }
    conn->send(msg); //将msg回射回去
  }

  EventLoop* loop_;
  TcpServer server_;
};

int main(int argc, char* argv[])
{
  LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid();
  LOG_INFO << "sizeof TcpConnection = " << sizeof(TcpConnection);
  if (argc > 1)
  {
    numThreads = atoi(argv[1]);
  }
  bool ipv6 = argc > 2;
  EventLoop loop;
  InetAddress listenAddr(2000, false, ipv6);
  EchoServer server(&loop, listenAddr);

  server.start();

  loop.loop();
}
chargen-server

客户端连接上之后,服务器将不断的给客户端发送大流量的数据,服务器并每隔3秒统计一次吞吐量

chargen.h头文件

#ifndef MUDUO_EXAMPLES_SIMPLE_CHARGEN_CHARGEN_H
#define MUDUO_EXAMPLES_SIMPLE_CHARGEN_CHARGEN_H

#include 

// RFC 864
class ChargenServer
{
 public:
  ChargenServer(muduo::net::EventLoop* loop,
                const muduo::net::InetAddress& listenAddr,
                bool print = false);

  void start();

 private:
  void onConnection(const muduo::net::TcpConnectionPtr& conn);

  void onMessage(const muduo::net::TcpConnectionPtr& conn,
                 muduo::net::Buffer* buf,
                 muduo::Timestamp time);

  void onWriteComplete(const muduo::net::TcpConnectionPtr& conn);
  void printThroughput();

  muduo::net::TcpServer server_;

  muduo::string message_;
  int64_t transferred_;
  muduo::Timestamp startTime_;
};

#endif  // MUDUO_EXAMPLES_SIMPLE_CHARGEN_CHARGEN_H

chargen.c实现文件

#include "chargen.h"

#include 
#include 

#include 

using namespace muduo;
using namespace muduo::net;

ChargenServer::ChargenServer(EventLoop* loop,
                             const InetAddress& listenAddr,
                             bool print)
  : server_(loop, listenAddr, "ChargenServer"),
    transferred_(0),
    startTime_(Timestamp::now())
{
  server_.setConnectionCallback(
      std::bind(&ChargenServer::onConnection, this, _1));
  server_.setMessageCallback(
      std::bind(&ChargenServer::onMessage, this, _1, _2, _3));
  server_.setWriteCompleteCallback(
      std::bind(&ChargenServer::onWriteComplete, this, _1));
	  
  //生成数据:每行72个字符,总共94行
  string line;
  for (int i = 33; i < 127; ++i)  
  {
    line.push_back(char(i)); //line: 33,34,35,... ...,126
  }
  line += line; //line: 33,34,35,... ...,126,33,34,35,... ...,126

  for (size_t i = 0; i < 127-33; ++i)
  {
	//line.substr(i, 72),表示substr获得字符串line中,从第i位开始的长度为72的字符串	
    message_ += line.substr(i, 72) + '\n'; 
  }
/*
message_:类型string
  33,34,35,... ...,126,\n
  34,35,36,... ...,33,\n
  35,36,37,... ...,34,\n
  ...
  ...
  126,33,34,... ...,125,\n
  */
  
  if (print) //每隔3s调用printThroughput函数打印吞吐量
  {
    loop->runEvery(3.0, std::bind(&ChargenServer::printThroughput, this));
  }
}

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

void ChargenServer::onConnection(const TcpConnectionPtr& conn)
{
  LOG_INFO << "ChargenServer - " << conn->peerAddress().toIpPort() << " -> "
           << conn->localAddress().toIpPort() << " is "
           << (conn->connected() ? "UP" : "DOWN");
  if (conn->connected())
  {
    conn->setTcpNoDelay(true);
    conn->send(message_);
  }
}

//整块message_数据发送完毕后,会回调onWriteComplete函数
void ChargenServer::onWriteComplete(const TcpConnectionPtr& conn)
{
  transferred_ += message_.size();
  conn->send(message_); //继续调用send发送message_
}

void ChargenServer::printThroughput() //计算并打印吞吐量
{
  Timestamp endTime = Timestamp::now();
  double time = timeDifference(endTime, startTime_);
  printf("%4.3f MiB/s\n", static_cast<double>(transferred_)/time/1024/1024);
  transferred_ = 0;
  startTime_ = endTime;
}

void ChargenServer::onMessage(const TcpConnectionPtr& conn,
                              Buffer* buf,
                              Timestamp time)
{
  string msg(buf->retrieveAllAsString());
  LOG_INFO << conn->name() << " discards " << msg.size()
           << " bytes received at " << time.toString();
}

main.c主程序文件

#include "chargen.h"

#include 
#include 

#include 

using namespace muduo;
using namespace muduo::net;

int main()
{
  LOG_INFO << "pid = " << getpid();
  EventLoop loop;
  InetAddress listenAddr(2019);
  ChargenServer server(&loop, listenAddr, true);
  server.start();
  loop.loop();
}

chargenclient.c
仅仅是将服务器发来的数据,调用buf->retrieveAll()取走,并没有打印(因为打印会降低吞吐量)

#include 
#include 
#include 
#include 

#include 

#include 
#include 

using namespace muduo;
using namespace muduo::net;

class ChargenClient : noncopyable
{
 public:
  ChargenClient(EventLoop* loop, const InetAddress& listenAddr)
    : loop_(loop),
      client_(loop, listenAddr, "ChargenClient")
  {
    client_.setConnectionCallback(
        std::bind(&ChargenClient::onConnection, this, _1));
    client_.setMessageCallback(
        std::bind(&ChargenClient::onMessage, this, _1, _2, _3));
    // client_.enableRetry();
  }

  void connect()
  {
    client_.connect();
  }

 private:
  void onConnection(const TcpConnectionPtr& conn)
  {
    LOG_INFO << conn->localAddress().toIpPort() << " -> "
             << conn->peerAddress().toIpPort() << " is "
             << (conn->connected() ? "UP" : "DOWN");

    if (!conn->connected())
      loop_->quit();
  }

  void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp receiveTime)
  {
    buf->retrieveAll();
  }

  EventLoop* loop_;
  TcpClient client_;
};

int main(int argc, char* argv[])
{
  LOG_INFO << "pid = " << getpid();
  if (argc > 1)
  {
    EventLoop loop;
    InetAddress serverAddr(argv[1], 2019);

    ChargenClient chargenClient(&loop, serverAddr);
    chargenClient.connect();
    loop.loop();
  }
  else
  {
    printf("Usage: %s host_ip\n", argv[0]);
  }
}

你可能感兴趣的:(Muduo库源码剖析)