在我的GitHub中上传了关于高性能图像处理服务器的网络库方面的代码,网址为:https://github.com/zk3326312/ZK_ImageServer/tree/master/net,整个框架为reactors in threads的框架,总体来说就是由一个主reactors作为接受器,当有新的client连接上时将连接挂载在sub reactors上,总体框架为:


下面就以一个echo服务器为例详细的说明一下一个client从连接到处理再到关闭的过程在代码上是怎么反映的:

      首先,我先说明下一个reactor是怎么实现的,我的reactor的实现参照的是陈硕大大的one loop per thread思想,即一个线程一个循环,在代码中,我的一个eventloop就是一个reactor,每个eventloop里面持有一个Epoll类对象,每个eventloop的主循环(调用eventloop.loop()启动主循环)里面调用Epoll.process()处理已经就绪的fd(Epoll里注册在epoll_wait()里的fd)。

      然后,再从整体布局来对代码进行说明,首先main函数里新建了一个echoServer对象,一个eventLoop对象(这里称之为baseLoop),这个baseLoop就是main Reactor,他的主要任务是处理accepter中的逻辑,acceptor会通过持有baseLoop的指针的方式持有这个reactor,acceptor与baseLoop的交互方式我会在下面详细说明。echoServer中会持有一个TcpServer的对象,TcpServer是一个封装好的整个服务器处理逻辑的类,echoSever要做的就是向TcpServer中注册onConnection(连接建立时需执行的任务)回调函数和onMessage(消息处理任务)回调函数。一个TcpServer中持有了一个acceptor(用来处理连接过程),一个eventLoopThreadPool(这个eventLoop线程池就是sub reactor,使用线程池的方式可以有效的减少频繁创建新线程和新对象的开销),TcpServer的主要任务就是协调acceptor接受到的新连接,将其以轮询的方式挂载到eventLoopThreadPool中去,为实现这个功能,TcpServer实现了一个newConnection()函数,并将其以回调的方式注册到了acceptor中,当acceptor接受到新的连接后就会调用这个函数进行处理了。

      这里再详细的说明一下acceptor和baseLoop是怎样互相进行交互的,这里用到了实现的一个Channel类,每一个channel只对应一个eventLoop(channel会以指针的方式持有eventLoop),每一个channel也只对应一个fd(一个channel处理一个文件描述符,这里即连接),acceptor里面会持有一个channel,并且实现读写回调函数功能再注册到channel类中,这个channel的功能就是用来与eventLoop交互,channel中实现了enableRead(),enableWrite()等控制函数,以enableRead()为例,每当channel调用enableRead(),channel所持有的eventLoop就会对channel进行更新,将channel持有的文件描述符fd,以及channel的指针以及事件(EPOLL_IN,EPOLL_OUT等)以struct epoll_event的方式注册到eventLoop中持有的Epoll中,每当fd就绪,eventLoop就会在主循环中调用Epoll.process()函数,Epoll.process()中又会调用到Channel.handleEvent()函数,通过Channel.handleEvent(),就调用到注册到channel里的回调了。不仅是acceptor,后面会提到的TcpConnection(挂载在sub reactor上,用来处理连接建立之后的IO事件等)与其持有的eventLoop也是这样交互的,这就是典型的reactor模式的实现。整个过程大致如下图:


      然后接着上面继续详细的介绍下在acceptor连接建立好之后,系统又是怎样运作的:当连接建立好之后,acceptor会调用到newConnection()这个回调函数,这个函数的功能就是将建立好的连接fd(这里称之为connfd)挂载到sub reactor(即eventLoopThreadPool)中,那么是如何挂载的呢?这里就用到了我刚才提到的TcpConnection类,这个类的作用很简单,就是为已成功建立连接的fd约束一个eventLoop,一个channel,然后向channel中注册各种读写回调函数(TcpConnection中的读写回调函数就是在echo中实现的onConneciton(读),onMessage(消息处理,写),这样就可以将业务逻辑完全抽象到最外层,如果需要功能修改,就只需修改onConnection和onMessage,这就是reactor的好处),这就跟acceptor是一样的,都是向channel中注册读写回调,然后注册到所属的eventLoop中的Epoll,然后fd就绪后调用相应的回调函数处理。

      整个代码的功能模块如下图所示,至此,服务器的网络功能模块就基本完成了,还需注意的一点是,epoll这种IO复用方式需要配合非阻塞IO使用,那么非阻塞IO下的数据的读取和写入就需要通过一个buffer来进行缓冲,不然会出现数据读不完或没有完全发送的情况,在我的代码中实现了一个NetBuffer类,功能很简单,就是按长度读取和根据读写的长度来调整读写的位置等,这里就不赘述了。


      

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐