从Thrift服务框架思考服务器框架-真的很有收获

Thrift框架

配图一张,主程序的流程图:
在这里插入图片描述

底层的(I/O)模块:负责实际的数据传输,比如Socket、文件、压缩数据流等的传输。

TTransport(负则传输的模块,就是底层I/O的实现):每一种传输方式都对应一个该模块,比如TSocket负则Socket通信,负责传输的对象就是Message。

TProtocol:这个就是协议模块,因为对Message的传输需要统一,否则就乱了,也就是对Message进行序列化的模块,常见协议入json、xml等。

TServer:这个模块主要负责TClient的请求,然后将请求转发给Processor处理,以便在高并发情况下快速处理请求。

Processor:主要负责处理请求,也可以转发RPC请求,剩下的就是处理请求的逻辑,然后做出响应,负责向Message中写入数据或者读出数据处理,Message就是通信的桥梁,中间对象。

TServer:

主要是负则TClient的请求,开启主线程循环处理事件,调用事件发生的回调函数,使用libevent作为事件驱动引擎,搞懂libevent,点这里:插入链接https://blog.csdn.net/Dachao0707/article/details/103911435

void TNonblockingServer::serve() {

  if (ioThreads_.empty())//ioThReads_是一个线程容器
    registerEvents(nullptr);

  // Run the primary (listener) IO thread loop in our main thread; this will
  // only return when the server is shutting down.
  ioThreads_[0]->run();

  // Ensure all threads are finished before exiting serve()
  for (uint32_t i = 0; i < ioThreads_.size(); ++i) {
    ioThreads_[i]->join();
    GlobalOutput.printf("TNonblocking: join done for IO thread #%d", i);
  }
}

服务器的启动流程其实很简单,第一步,判断线程容器是否为空,为空就注册事件(稍后再说),然后启动线程(稍后说),最后退出server就把所有的线程回收掉。则会个流程再正常不过,一般网络服务器架构,客户端架构基本都是这么设计的。

首先ioThreads_:这个是TNonblockingServer(非阻塞服务器,见名知意)里的一个io线程容器,可以理解为,它里面就是服务器对象管理线程的一个容器对象。里面存储的是这样的TNonblockingIOThread(非阻塞的线程)智能指针,我们前面说了,server是通过libevent事件库事件驱动的,那么事件就应该和线程建立关系,在这个线程对象中:

/// pointer to eventbase to be used for looping
  event_base* eventBase_;//事件处理框架

  /// Set to true if this class is responsible for freeing the event base
  /// memory.
  bool ownEventBase_;

  /// Used with eventBase_ for connection events (only in listener thread)
  struct event serverEvent_;//处理连接事件,监听线程也就是主线程关注的事件

  /// Used with eventBase_ for task completion notification
  struct event notificationEvent_;//这个就是收消息的事件

看到这里就非常清晰了,知道libevent就知道,事件处理需要一个时间处理框架(作用就是用来管理多个事件的,一个线程不可能只管理一个事件的),也就是event_base* eventBase_;,这是libevent的内容可以看:https://blog.csdn.net/Dachao0707/article/details/103911435

**tips:**其实对于事件这个东西,这里主要说网络的事件,还有其他的事件这里先不说,理解起来就是这样的:我们的操作系统是应用程序的爹,所有其它的程序,不管在不在同一个计算机上,想要和程序通信,都需要先经过操作系统,然后由操作系统来统一把控,因为它是爹(其实就是各种设备都是它来掌控的),所以比如有人要连解一个应用程序,也就是server的时候,操作系统那就会收到一个I/O信号,通过句柄管理,然后事件就会绑定一个句柄,操作系统会通过句柄告诉你,这个句柄有变化,好的,这就是事件发生了,说了这么多,我有时候也会晕,多理解几次就好了,比如这里这个i/o处理线程向事件处理框架注册了连接事件,那么有客户端连接的时候,操作系统就会通过那个文件描述符,通知libevnet引擎,libevent会去调用事件的回调函数,这就是事件通知的原理,想想也挺简单的。

上面的tips很重要,不可能一下理解,多看多想自然就有感觉了,然后我们再来看线程对象,**原来每一个线程就是处理各种i/o事件的,i/o事件就是连接事件和读写事件,**在服务器和客户端这种网络通信中,底层所干的事情就是这样的,处理连解以及处理读写事件,至于怎么处理,那就是衍生出的复杂的业务逻辑,再怎么变,底层不会变。有一种通透的感觉,真香。

**再来看thrift的服务器主流程,**我这里都可以猜一下,本来说的就是server主要负责处理client的请求然后把请求转发给Processor,你看很简单吧,一个主线程处理请求,并不会真的负责处理复杂的逻辑,逻辑都是给Processor做的。

那么现在的Server目标就很明确的:主线程先对事件搭建起来,就是libevent的流程,然后去循环监听事件就可以了。

注册事件:

void TNonblockingServer::registerEvents(event_base* user_event_base) {
  userEventBase_ = user_event_base;//这个就是服务器对象的时间处理框架

  // init listen socket
  if (serverSocket_ == THRIFT_INVALID_SOCKET)
    createAndListenOnSocket();//这个是创建监听端口,主要就是开启监听,获取fd的一些操作,把信息都和传输对象绑定起来,可以理解为对监听套接字的初始化。

  // set up the IO threads
  assert(ioThreads_.empty());//初始化线程计数
  if (!numIOThreads_) {
    numIOThreads_ = DEFAULT_IO_THREADS;
  }
  // User-provided event-base doesn't works for multi-threaded servers
  assert(numIOThreads_ == 1 || !userEventBase_);//事件框架这时候还是空的,在debug中报错定位,在release版本中不会产生任何代码,证明调用的时候应该传入空事件框架

  for (uint32_t id = 0; id < numIOThreads_; ++id) {
    // the first IO thread also does the listening on server socket
     //初始化监听的套接字,如果为主线程就是前面的server维护的fd
    THRIFT_SOCKET listenFd = (id == 0 ? serverSocket_ : THRIFT_INVALID_SOCKET);
	//创建主线程对象并加入到线程容器中
    shared_ptr<TNonblockingIOThread> thread(
        //需要指定线程的调度优先级是否为高优先级
        new TNonblockingIOThread(this, id, listenFd, useHighPriorityIOThreads_));
    ioThreads_.push_back(thread);
  }

  // Notify handler of the preServe event
  if (eventHandler_) {
    eventHandler_->preServe();
  }

  // Start all of our helper IO threads. Note that the threads run forever,
  // only terminating if stop() is called.
  assert(ioThreads_.size() == numIOThreads_);
  assert(ioThreads_.size() > 0);

  GlobalOutput.printf("TNonblockingServer: Serving with %d io threads.",
                      ioThreads_.size());

  // Launch all the secondary IO threads in separate threads
  if (ioThreads_.size() > 1) {//如果线程多的话,开启他们
    ioThreadFactory_.reset(new ThreadFactory(
        false // detached
        ));

    assert(ioThreadFactory_.get());

    // intentionally starting at thread 1, not 0
    for (uint32_t i = 1; i < ioThreads_.size(); ++i) {
      shared_ptr<Thread> thread = ioThreadFactory_->newThread(ioThreads_[i]);
      ioThreads_[i]->setThread(thread);
      thread->start();
    }
  }

  // Register the events for the primary (listener) IO thread
  ioThreads_[0]->registerEvents();//下面这一步就是注册线程的事件了
}

看看这个函数吧:

/**
 * Register the core libevent events onto the proper base.
 */
void TNonblockingIOThread::registerEvents() {
  threadId_ = Thread::get_current();

  assert(eventBase_ == nullptr);
  eventBase_ = getServer()->getUserEventBase();//就是哪到server的事件处理框架
  if (eventBase_ == nullptr) {//为其创建事件处理框架,就是libevent的第一步,接下来肯定有创建事件和添加事件
    eventBase_ = event_base_new();
    ownEventBase_ = true;
  }

  // Print some libevent stats
  if (number_ == 0) {
    GlobalOutput.printf("TNonblockingServer: using libevent %s method %s",
                        event_get_version(),
                        event_base_get_method(eventBase_));
  }

  if (listenSocket_ != THRIFT_INVALID_SOCKET) {//这里因为TNonblockingServer声明了 friend class TNonblockingIOThread;,所以可以使用私有属性
    // Register the server event
    event_set(&serverEvent_,
              listenSocket_,
              EV_READ | EV_PERSIST,
              TNonblockingIOThread::listenHandler,//回调函数
              server_);
    event_base_set(eventBase_, &serverEvent_);//将事件注册到event_base上

    // Add the event and start up the server
    if (-1 == event_add(&serverEvent_, nullptr)) {//添加事件
      throw TException(
          "TNonblockingServer::serve(): "
          "event_add() failed on server listen event");
    }
    GlobalOutput.printf("TNonblocking: IO thread #%d registered for listen.", number_);
  }

  createNotificationPipe();//这个是创建一个处理任务事件通知的通道

  // Create an event to be notified when a task finishes
  event_set(&notificationEvent_,
            getNotificationRecvFD(),
            EV_READ | EV_PERSIST,
            TNonblockingIOThread::notifyHandler,
            this);

  // Attach to the base
  event_base_set(eventBase_, &notificationEvent_);

  // Add the event and start up the server
  if (-1 == event_add(&notificationEvent_, nullptr)) {
    throw TException(
        "TNonblockingServer::serve(): "
        "event_add() failed on task-done notification event");
  }
  GlobalOutput.printf("TNonblocking: IO thread #%d registered for notify.", number_);
}

其实到这里就很简单了,这就是libevent那一套,仔细看看代码很好理解。这段代码就干了三件事

  • 为主线程创建一个时间处理框架也就是event_base。
  • 注册并添加server主要处理的事件,就是连接事件,也就是为监听套接字fd创建一个事件来处理连解,并设置回调函数。
  • 就是为任务完成消息创建了一个任务完成通知的通道,因为主线程只负责处理连接和转发请求,所以当这些请求处理完成后可以通知主线程,任务完成,实现交互。其实这一步就是创建了一个可读的事件,并让主线程关注这个事件,只不过是为任务完成通知非配得事件。

**tips:**再强调一下,对于事件驱动就是一种处理I/O的机制,当有I/O事件出现时,如果对事件进行了关注,就会触发事件的回调函数,而触发就是通过文件描述符或者socket就是一个句柄来关联的,因为操作系统通过它告诉你事件触发了,所以事件通知就是这么干:

  • 一个事件需要有一个句柄,句柄就是操作系统和应用程序的桥梁。
  • 事件需要有回调函数,当事件触发,去回调用来处理事件的。

所以掌握了这个就很简单了,一个事件就先把这两个给设置了,然后,其他的再说,再把事件注册并添加到处理框架上就行了,后续就是通过loop循环检测事件。

总结一下

现在想想网络服务器那一套也是很简单的,也是一个套路,只是这个套路比较复杂,但是每一步都很有道理,我现在的感觉就是有点通透,好像又有点迷,我想多理几遍,会越来越清楚的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值