Apache MINA NIO模型

From: http://2006zhouxinfeng.blog.163.com/blog/static/5024021620115155106736/

 

前一段在网上看到了“淘宝伯岩”([email protected])的一份关于Java NIO 网络编程的讲义《NIO trick and trap——编写高性能Java NIO网络框架》。 其中里面提到了Java NIO在网络编程方面的应用和编程模型,同时也提到了Apache的一个开源网络框架MINA。 正好自己对于NIO对网络编程的应用也不是太熟悉,于是就简单了解了下MINA。本文并不是针对于MINA这个框架如何使用(相关内容可以参见相关文 档),而是整个MINA框架对于NIO的应用。

1、整体架构

首先我们来看一下MINA Server端的整体架构(图片源自MINA官方文档)。 其中的IoService用来负责实际的网络连接,监听用户请求等功能。 对于每一个新的连接,MINA都会创建一个与之对应的Session。 MINA收到或者要发送的数据会经过一系列的Filter。过滤器可以用来对消息进行过滤和规范化。 最后MINA会调用用户实现的一个IoHandler,来处理经过过滤器过滤的消息。 可以看出,对于框架的使用者来说,网络的具体编程是对其透明的,用户只需要实现相应的Handler,并且设置好Filter即可。

2、IoService细节

2.1、继承关系

首先我们来看一下核心几个类的继承关系。

Apache MINA NIO模型_第1张图片

其中IoService为整个框架的核心接口,其代表了网络服务。

IoAcceptorIoService一个子接口,用来代表对用户请求的接受和监听。

SocketAcceptorIoAcceptor的一个子接口,用来代表基于Socket的网络请求。

AbstractIoServiceAbstractIoAcceptor分别是对IoServiceIoAcceptor的一个默认抽象实现。

AbstractPoolingIoAcceptor则是引入了池的概念,建立了处理线程池等。

最后NIOSocketAcceptorAbstractPoolingIoAcceptor的NIO的一个实现。

2.2、整体流程

  • 创建一个NIOSocketAcceptor对象。
  • 调用IoAcceptorbind方法,创建SocketChannel,并绑定到之前的Selector上。
  • 创建一个新的线程,用来监听用户的连接。如果收到用户的连接请求,则为其创建一个Session,并把Session加入到一个Processor中等待处理。
  • 系统中有若干个Processor,每个有自己的线程。在该线程中,也存在一个Selector,用来监听所有该Processor上的Session。如果某个Session有数据可以读取或者写入,则将数据传递给一系列的Filter,并最终调用相应的Handler进行处理。

2.3、具体代码分析

为了简明起见,下面的代码只是整个代码的截取,目的是为了更好的说明问题。完整的代码请详见项目源文件。

2.3.1、构造

我们首先来看一下NioSocketAcceptor的构造函数。

  1. public NioSocketAcceptor() {

  2.     super(new DefaultSocketSessionConfig(), NioProcessor.class);

  3.     ((DefaultSocketSessionConfig) getSessionConfig()).init(this);

  4. }

该构造函数传给了父类构造函数两个参数。首先是一个DefaultSocketSessionConfig的实例,用来表示一个配置对象。之后是NioProcessor.class,用来指明处理对象的类型。

接下来我们来看一下 NioSocketAcceptor的父类 AbstractPollingIoAcceptor<NioSession, ServerSocketChannel>

  1. protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, Class<? extends IoProcessor<NioSession>> processorClass) {

  2.     // 根据之前子类传递过来的processorClass,新建一个SimpleIoProcessorPool对象

  3.     // 并调用另一个构造函数

  4.     this(sessionConfig, null, new SimpleIoProcessorPool<NioSession>(processorClass), true);

  5. }

  6. private AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, Executor executor, IoProcessor<NioSession> processor, boolean createdProcessor) {

  7.     // 调用父类构造函数

  8.     super(sessionConfig, executor);

  9.     // 调用初始化函数

  10.     init();

  11. }

其中的init函数的实现在 NioSocketAcceptor中,代码如下:

  1. @Override

  2. protected void init() throws Exception {

  3.     // 创建一个Selector对象

  4.     selector = Selector.open();

  5. }

其中的SimpleIoProcessorPool<NioSession>的构造函数如下:

  1. public SimpleIoProcessorPool(Class<? extends IoProcessor<NioSession>> processorType) {

  2.     // 用默认参数调用另一构造函数

  3.     this(processorType, null, DEFAULT_SIZE);

  4. }

  5. public SimpleIoProcessorPool(Class<? extends IoProcessor<NioSession>> processorType, Executor executor, int size) {

  6.     // 若executor为null则新建,不然则使用传入的对象。

  7.     createdExecutor = (executor == null);

  8.     if (createdExecutor) {

  9.         this.executor = Executors.newCachedThreadPool();

  10.     } else {

  11.         this.executor = executor;

  12.     }

  13.     // 新建一个IoProcessor池

  14.     pool = new IoProcessor[size];

  15.     // 根据传入的processorType,利用Java的反射机制来创建对象,填填满对象池

  16.     Constructor<? extends IoProcessor<S>> processorConstructor  = processorType.getConstructor(ExecutorService.class);

  17.     for(int i = 0; i < size; i++) {

  18.         pool[i] = processorConstructor.newInstance(this.executor);

  19.     }

  20. }

从以上代码可以看出, SimpleIoProcessorPool<NioSession>根据传入的IoProcessor的类型(这里为NioProcessor),创建了一个IoProcessor池,和一个Executor线程池,用来进行网络IO的读写请求。

那我们就来看一下 NioProcessor的具体创建过程

  1. public NioProcessor(Executor executor) {

  2.     super(executor);

  3.     this.selector = Selector.open();

  4. }

可以看出,在NioProcessor创建的过程中,还创建了一个Selector对象,用来之后监听网络IO的读写请求。并把传进来的Executor对象传递给父类进行保存。

之后我们再来看一下  AbstractPollingIoAcceptor<NioSession, ServerSocketChannel>的父类 AbstractIoAcceptor

  1. protected AbstractIoAcceptor(IoSessionConfig sessionConfig, Executor executor) {

  2.     super(sessionConfig, executor);

  3. }

可以看出该构造函数没有进行额外的工作,而是直接调用父类。那我们就来看一下他的父类 AbstractIoService

  1. protected AbstractIoService(IoSessionConfig sessionConfig, Executor executor) {

  2.     // 保存配置对象

  3.     this.sessionConfig = sessionConfig;

  4.     // 若为提供Executor对象,则新建一个。

  5.     if (executor == null) {

  6.         this.executor = Executors.newCachedThreadPool();

  7.         createdExecutor = true;

  8.     } else {

  9.     this.executor = executor;

  10.     createdExecutor = false;

  11.     }

  12. }

至此, NIOSocketAcceptor对象的创建也就到此结束。在整个创建过程中,我们创建了一个 DefaultSocketSessionConfig实例,用来表示一些配置选项。创建了一个Selector对象, 和一个Executor线程池,用来负责监听用户的请求(详见后文)。创建了一个 SimpleIoProcessorPool<NioSession>对象,其中包括一组 NioProcessor对象和一个Executor线程池对象,每个NioProcessor对象中还包括一个Selector对象,用来负责网络IO的读写处理(详见后文)。

2.3.2、bind

我们接着来看一下IoAcceptor中声明的bind方法。其现实在AbstractIoAcceptor中。代码如下:

  1. public final void bind(Iterable<? extends SocketAddress> localAddresses) throws IOException {

  2.     // 检查地址的类型,并加入到localAddressCopy中

  3.     List<SocketAddress> localAddressesCopy = new ArrayList<SocketAddress>();

  4.     for (SocketAddress a : localAddresses) {

  5.         checkAddressType(a);

  6.         localAddressesCopy.add(a);

  7.     }

  8.     // 调用bindInternal函数,返回成功绑定的地址。

  9.     Set<SocketAddress> addresses = bindInternal(localAddressesCopy);

  10.     synchronized (boundAddresses) {

  11.             boundAddresses.addAll(addresses);

  12.     }

  13. }

下面我们来看一下bindInternal的实现,该函数的实现在AbstractPollingIoAcceptor<NioSession, SocketChannel>中。

  1. @Override

  2. protected final Set<SocketAddress> bindInternal(List<? extends SocketAddress> localAddresses) throws Exception {

  3.     // 创建一个AcceptorOperationFuture对象,该对象的执行结果可以异步的通知

  4.     AcceptorOperationFuture request = new AcceptorOperationFuture(localAddresses);

  5.     // 将上文创建的对象加入队列中,等待其他线程处理

  6.     registerQueue.add(request);

  7.     // 启动Acceptor,进行地址的绑定

  8.      startupAcceptor();

  9.     // 唤起之前阻塞的Selector对象(详见下文)

  10.     wakeup();

  11.     // 等待绑定完成

  12.     request.awaitUninterruptibly();

  13.     // 返回结果

  14.     Set<SocketAddress> newLocalAddresses = new HashSet<SocketAddress>();

  15.     for (H handle : boundHandles.values()) {

  16.         newLocalAddresses.add(localAddress(handle));

  17.     }

  18.     return newLocalAddresses;

  19. }

可以看出,在该函数中,首先创建了一个 AcceptorOperationFuture对象,并把其加入一个队列中,等待其他线程处理。该对象类似Java中自带的Future<?>对象,代表一个异步执行任务的结果。 自后调用了startupAcceptor来进行连接的监听和绑定。下面我们就来看一下startupAcceptor函数的具体实现。

  1. private void startupAcceptor() {

  2.     // 如果acceptorRef没个赋值,则新建一个Acceptor对象进行赋值,并执行该acceptor。

  3.     Acceptor acceptor = acceptorRef.get();

  4.     if (acceptor == null) {

  5.     acceptor = new Acceptor();

  6.     if (acceptorRef.compareAndSet(null, acceptor)) {

  7.         executeWorker(acceptor);

  8.     }

  9. }

从代码可以看出,该函数进行了一个原子操作。如果acceptorRef没被赋值,则新建对象进行赋值,并调用executeWroker函数。其中Acceptor实现了Runnable接口,而executorWorker函数的主要功能就是把该acceptor交给之前创建的Executor来执行。下面我们来看一下Acceptorrun函数,看一下它的具体执行过程。

  1. public void run() {

  2.     int nHandles = 0;

  3.     while (selectable) {

  4.         // 等待用户连接请求

  5.         int selected = select();

  6.         // 注册新的地址绑定

  7.         nHandles += registerHandles();

  8.         // 没有绑定的地址,可以退出

  9.         if (nHandles == 0) {

  10.             acceptorRef.set(null);

  11.             if (registerQueue.isEmpty() && cancelQueue.isEmpty()) {

  12.                 break;

  13.            }

  14.            if (!acceptorRef.compareAndSet(null, this)) {

  15.                break;

  16.            }

  17.         }

  18.         // 处理用户连接请求

  19.         if (selected > 0) {

  20.              processHandles(selectedHandles());

  21.         }

  22.        // 检查是否有unbind请求.

  23.         nHandles -= unregisterHandles();

  24.     }

  25. }

其中的select()函数的实现在 NIOSocketAcceptor类中:

  1. @Override

  2. protected int select() throws Exception {

  3.     return selector.select();

  4. }

可以看出,select函数在之前创建的Selector上调用select函数。等待绑定在该Selector上的Channel状态就绪。由于在第一次调用时,该Selector上并没有绑定任何Channel,所以该函数会永远阻塞住。这也就是为什么在之前的 bindInternal中,在调用 startAcceptor后,需要马上调用wakeup函数。该函数的实现在 NIOSocketAcceptor类中:

  1. @Override

  2. protected void wakeup() {

  3.     selector.wakeup();

  4. }

该函数会调用Selectorwakeup函数,用来唤醒阻塞的select函数。

接下来我们来看一下registerHandlers函数,该函数的作用是用来检查是否有地址绑定的请求。并进行绑定。(unRegisterHandles与之相似,过程相反,这里略去不提。)

  1. private int registerHandles() {

  2.     for (;;) {

  3.         // 从队列中获得绑定任务

  4.         AcceptorOperationFuture future = registerQueue.poll();

  5.         // 队列为空,直接退出,返回0

  6.         if (future == null) {

  7.            return 0;

  8.         }

  9.         Map<SocketAddress, ServerSocketChannel> newHandles = new ConcurrentHashMap<SocketAddress, ServerSocketChannel>();

  10.         // 获得需要绑定的地址

  11.         List<SocketAddress> localAddresses = future.getLocalAddresses();

  12.         // 一次绑定

  13.         for (SocketAddress a : localAddresses) {

  14.             // 为每个地址创建ServerSocketChannel对象

  15.             ServerSocketChannel handle = open(a);

  16.             newHandles.put(localAddress(handle), handle);

  17.         }

  18.         // 更新已绑定的连接

  19.         boundHandles.putAll(newHandles);

  20.         // 通知 AcceptorOperationFuture任务已经完成

  21.         // 这样之前调用awaitUninterruptibly()阻塞的线程将继续执行。

  22.         future.setDone();

  23.         // 返回已绑定的个数

  24.         return newHandles.size();

  25.     }

  26. }

该函数的作用是检查是否有绑定的请求,然后为每个地址建立一个连接,并绑定。其中创建连接调用的是open函数。该函数的实现在 NIOSocketAcceptor类中:

  1. @Override

  2. protected ServerSocketChannel open(SocketAddress localAddress) throws Exception {

  3.       // 创建一个新的ServerSocketChannel

  4.      ServerSocketChannel channel = ServerSocketChannel.open();

  5.     // 设置为非阻塞

  6.     channel.configureBlocking(false);

  7.     // 绑定地址

  8.     ServerSocket socket = channel.socket();

  9.     socket.bind(localAddress, getBacklog());

  10.     // 在Selector对象中注册该Channel。

  11.     channel.register(selector, SelectionKey.OP_ACCEPT);

  12.     return channel;

  13. }

我们现在再把注意力移回之前的Acceptorrun函数。如果之前的select函数是正常返回,而不是被wakeup,那么说明有用户的连接请求。接下来就会执行processHandles函数。其实现如下:

  1. private void processHandles(Iterator<ServerSocketChannel> handles) throws Exception {

  2.    while (handles.hasNext()) {

  3.         ServerSocketChannel handle = handles.next();

  4.         handles.remove();

  5.         // 接受用户的请求,并创Session。

  6.         NioSession session = accept(processor, handle);

  7.         // 初始化Session

  8.        initSession(session, null, null);

  9.        // 把session添加到一个Processor的处理队列中,等待IO读写处理

  10.        session.getProcessor().add(session);

  11.     }

  12. }

下面我们来看一下accept函数。该函数接收两个参数,一个是有连接请求的ServerSocketChannel,一个是之前创建的SimpleIoProcessorPool<NioSession>对象。

该函数的实现在NIOSocketAcceptor类中:

  1. @Override
  2. protected NioSession accept(IoProcessor<NioSession> processor, ServerSocketChannel handle) throws Exception {
  3.     // 获得用户连接
  4.     SocketChannel ch = handle.accept();
  5.     // 创建Session
  6.     return new NioSocketSession(this, processor, ch);
  7. }

之后我们来看一下Session在Processor上的注册过程。及SimpleIoProcessorPool<NioSession>的add函数:

  1. public final void add(S session) {

  2.     // 从Processor池中获得一个Processor(NioProcessor),并注册session.

  3.     getProcessor(session).add(session);

  4. }

  5. private IoProcessor<S> getProcessor(S session) {

  6.     // 如果该session已经注册一个Processor,则返回该Processor

  7.     // 如果不存在,则从池中随机选择一个,并绑定到该session上

  8.     IoProcessor<S> processor = (IoProcessor<S>) session.getAttribute(PROCESSOR);

  9.     if(processor == null) {

  10.         processor = pool[Math.abs((int) session.getId()) % pool.length];

  11.         session.setAttributeIfAbsent(PROCESSOR, processor);

  12.     }

  13.      return processor;

  14. }

接下来我们来看一下NioProcessor中的add函数。整个过程和之前监听用户连接请求的过程相似。故我们只关注其中不同的地方

  1. public final void add(S session)  {

  2.     newSessions.add(session); // 加入请求队列

  3.     startupProcessor();

  4. }


  5. private void startupProcessor() {

  6.     Processor processor = processorRef.get();

  7.     if (processor == null) {

  8.         processor = new Processor();

  9.         if (processorRef.compareAndSet(null, processor)) {

  10.             executor.execute(new NamePreservingRunnable(processor, threadName));

  11.         }

  12.      }

  13.      wakeup();

  14. }


  15. public void run() {

  16.     int nSessions = 0;

  17.     for (;;) {

  18.         int selected = select(SELECT_TIMEOUT);

  19.         // 处理新的session的注册

  20.         nSessions += handleNewSessions();

  21.         if (selected > 0) {

  22.             process();

  23.         }

  24.         // 刷新排队的写请求

  25.         flush(currentTime);

  26.         nSessions -= removeSessions();

  27.         if (nSessions == 0) {

  28.             processorRef.set(null);

  29.             if (newSessions.isEmpty() && isSelectorEmpty()) {

  30.                 break;

  31.             }

  32.             if (!processorRef.compareAndSet(null, this)) {

  33.                 break;

  34.             }

  35.        }

  36. }

接下来我们来看一下其中的 handleNewSessions函数

  1. private int handleNewSessions() {

  2.     int addedSessions = 0;

  3.     for (S session = newSessions.poll(); session != null; session = newSessions.poll()) {

  4.         if (addNow(session)) {

  5.             addedSessions++;

  6.         }

  7.     }

  8.     return addedSessions;

  9. }

  10. private boolean addNow(S session) {

  11.     boolean registered = false;

  12.     // 注册session

  13.     init(session);

  14.     registered = true;

  15.     // 构建FilterChain

  16.     IoFilterChainBuilder chainBuilder = session.getService().getFilterChainBuilder();

  17.     chainBuilder.buildFilterChain(session.getFilterChain());

  18.     return registered;

  19. }

  20. @Override

  21. protected void init(NioSession session) throws Exception {

  22.     // 把Session对应的SocketChannel注册在该session对应的processor的Selector。

  23.     SelectableChannel ch = (SelectableChannel) session.getChannel();

  24.     ch.configureBlocking(false);

  25.     session.setSelectionKey(ch.register(selector, SelectionKey.OP_READ,session));

  26. }

接下来我们再来看一下process函数。该函数负责数据的读写,并把数据传递给FilterChain,并最终调用用户的IoHandler
  1. private void process() throws Exception {

  2.     for (Iterator<S> i = selectedSessions(); i.hasNext();) {

  3.         S session = i.next();

  4.         process(session);

  5.         i.remove();

  6.     }

  7. }

  8. private void process(S session) {

  9.     // 读请求

  10.     if (isReadable(session) && !session.isReadSuspended()) {

  11.         read(session);

  12.     }

  13.     // 写请求

  14.     if (isWritable(session) && !session.isWriteSuspended()) {

  15.         if (session.setScheduledForFlush(true)) {

  16.            // 加入写刷新队列

  17.            flushingSessions.add(session);

  18.         }

  19.     }

  20. }


  21. private void read(S session) {

  22. // 从SocketChannel中读数据

  23. // 这里略去

  24. // 如果读到数据

  25. if (readBytes > 0) {

  26. // 把数据传给FilterChain

  27. IoFilterChain filterChain = session.getFilterChain();

  28. filterChain.fireMessageReceived(buf);

  29. }

  30. }

最后是 flush函数,用来刷新写请求的数据。这里暂时略去。
因为我已经写不动了,累死了。哪天在另开文章吧。

你可能感兴趣的:(Apache MINA NIO模型)