java nio 系列学习之四

 

前一段在网上看到了“淘宝伯岩”(boyan@taobao.com)的一份关于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、继承关系

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

其中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函数,用来刷新写请求的数据。这里暂时略去。
因为我已经写不动了,累死了。哪天在另开文章吧。
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值