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(¬ificationEvent_,
getNotificationRecvFD(),
EV_READ | EV_PERSIST,
TNonblockingIOThread::notifyHandler,
this);
// Attach to the base
event_base_set(eventBase_, ¬ificationEvent_);
// Add the event and start up the server
if (-1 == event_add(¬ificationEvent_, 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循环检测事件。
总结一下
现在想想网络服务器那一套也是很简单的,也是一个套路,只是这个套路比较复杂,但是每一步都很有道理,我现在的感觉就是有点通透,好像又有点迷,我想多理几遍,会越来越清楚的。