mina学习笔记四:交互的核心IoSession

原文地址:https://i-blog.csdnimg.cn/blog_migrate/c7542fb690c6d27ceab6cb7f7987f93c.png

 这一章讲的就是IoSession,session这个词做web应用的人应该都是耳熟能详,在mina中IoSession也是起着相同的作用。mina为每个客户端提供了会话session,session是服务器端和客户端的连接持有者,直到连接中断session才会被销毁。

    IoSession不仅是connection的相关信息,同时,服务器端还能通过他存储其他可能需要的数据信息。好,让我们一步一步剖析IoSession那不平凡的一生吧。

1. IoSession在服务器端的创建第一步:accept(processor,handle)

    接着上节说,我们已经知道,NioSocketAcceptor.bind()后,最终会生成一个在自身维护的executor线程池内提交的任务Acceptor(单一线程),该任务不断的轮询判断服务是否关闭、同时等待新的连接接入。当有新的连接接入时:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. Acceptor.run(){  
  2.     ...  
  3.     //selectedHandles()获取一个selector中注册的服务监听的serversocketchannel的迭代器  
  4.     processHandles(selectedHandles());  
  5.     ...  
  6. }  
    创建session、并为其分配指定IoProcessor的秘密都在AbstractPollingIoAcceptor.processHandles()这个方法中。我们接着看:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. private void processHandles(Iterator<ServerSocketChannel> handles) throws Exception {  
  2.            while (handles.hasNext()) {  
  3.                H handle = handles.next();  
  4.                handles.remove();  
  5.   
  6.                // 创建session并将本地的processor处理池当参数传递给session  
  7.     // 注意这里还未分配具体的processor  
  8.                S session = accept(processor, handle);  
  9.   
  10.                if (session == null) {  
  11.                    continue;  
  12.                }  
  13.     // 初始化Session  
  14.                initSession(session, nullnull);  
  15.   
  16.                // 将session分配给具体的processor  
  17.                session.getProcessor().add(session);  
  18.            }  
  19.        }  
    第一步accept(processor,handle)方法,调用子类实现的NioSession accept(...)方法,判断了SelectionKey的有效性后,获得连接的通道SocketChannel ch = handle.accept();最后以此为传参创建IoSession对象return new NioSocketSession(this, processor, ch);我们看下构造函数做了什么(为了方便起见,相关的父类构造函数都写在一块了):
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.    public NioSocketSession(IoService service, IoProcessor<NioSession> processor, SocketChannel channel) {  
  2. //AbstractIoSession(IoService service)  
  3.        this.service = service;  
  4.        this.handler = service.getHandler();  
  5.   
  6.        //保存当前的处理时间信息  
  7.        long currentTime = System.currentTimeMillis();  
  8.        creationTime = currentTime;  
  9.        lastThroughputCalculationTime = currentTime;  
  10.        lastReadTime = currentTime;  
  11.        lastWriteTime = currentTime;  
  12.        lastIdleTimeForBoth = currentTime;  
  13.        lastIdleTimeForRead = currentTime;  
  14.        lastIdleTimeForWrite = currentTime;  
  15.   
  16.        //在关闭时的动作,下面以此展开对IoFuture的探讨。  
  17.        closeFuture.addListener(SCHEDULED_COUNTER_RESETTER);  
  18.   
  19.        //为session创建唯一的ID  
  20.        sessionId = idGenerator.incrementAndGet();  
  21. //END AbstractIoSession(IoService service)  
  22.   
  23. //NioSession(IoProcessor<NioSession> processor, IoService service, Channel channel)  
  24. //即当前session的SocketChannel  
  25. this.channel = channel;  
  26. //这是ioservice中的processor池哦  
  27.        this.processor = processor;  
  28. //这步过滤器链厉害了,后面细说  
  29.        filterChain = new DefaultIoFilterChain(this);  
  30. //END NioSession(IoProcessor<NioSession> processor, IoService service, Channel channel)  
  31.   
  32. //设置各种初始参数  
  33.        config = new SessionConfigImpl();  
  34.        this.config.setAll(service.getSessionConfig());  
  35.    }  
    右边这张图是IoSession的类图结构,比较清晰。
    我们细细的分析一下NioSocketSession在初始化时做了哪些工作,首先持有了sevice及其处理类引用,并设置了一些初始时间信息,然后他在关闭时注册了监听操作closeFuture.addListener(SCHEDULED_COUNTER_RESETTER)。

1.1 IoFuture

    我们已经很多次看到IoFuture,有必要来详细了解一下IoFuture的作用。那用过Executor的同学都知道,submit()提交Callback的任务后将返回一个Future表示这个任务的操作结果,这里的IoFuture的作用是类似的,他代表一次相关于IoSession的操作的操作结果,同时,还可以在他完成时获取定义相关的操作。我们看下他的子类结构图

图4.1 IoFuture子类结构图

    由图中可见,对session相关的操作例如关闭、连接、写、读等操作,IoFuture全都有子类去支持。session的操作可以很方便的通过IoFuture查看处理的结果。我们看下IoFuture接口的API清单:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. addListener(IoFutureListener<?>)  
  2. await()  
  3. await(long)  
  4. await(long, TimeUnit)  
  5. awaitUninterruptibly()  
  6. awaitUninterruptibly(long)  
  7. awaitUninterruptibly(long, TimeUnit)  
  8. getSession()  
  9. isDone()  
  10. removeListener(IoFutureListener<?>)  
    从清单我们了解到:1.IoFuture支持添加操作完成的监听器,2.IoFuture支持可中断、可设置超时时间的阻塞式操作。3.isDone()则返回操作的完成状态。
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public interface IoFutureListener<F extends IoFuture> extends EventListener {  
  2.     void operationComplete(F future);  
  3. }  

    在他的直接实现子类DefaultIoFuture里面,我们看一下该类很主要一个实现方法setValue(Object newValue),所有其他子类都会调用该方法以完成具体的操作:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public void setValue(Object newValue) {  
  2.     synchronized (lock) {  
  3.         // 已经完成则直接返回  
  4.         if (ready) {  
  5.             return;  
  6.         }  
  7.  //设置结果  
  8.         result = newValue;  
  9.  //设置完成状态  
  10.         ready = true;  
  11.  //唤醒所有等待线程  
  12.         if (waiters > 0) {  
  13.             lock.notifyAll();  
  14.         }  
  15.     }  
  16. 知监听器执行完成操作  
  17.     notifyListeners();  
  18. }  
DefaultIoFuture中个还有一个检查死锁的方法,大家有兴趣可以看下是怎么判断死锁的:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. private void checkDeadLock() {  
  2.     // Only read / write / connect / write future can cause dead lock.   
  3.     if (!(this instanceof CloseFuture || this instanceof WriteFuture || this instanceof ReadFuture || this instanceof ConnectFuture)) {  
  4.         return;  
  5.     }  
  6.   
  7.     // Get the current thread stackTrace.   
  8.     // Using Thread.currentThread().getStackTrace() is the best solution,  
  9.     // even if slightly less efficient than doing a new Exception().getStackTrace(),  
  10.     // as internally, it does exactly the same thing. The advantage of using  
  11.     // this solution is that we may benefit some improvement with some  
  12.     // future versions of Java.  
  13.     StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();  
  14.   
  15.     // Simple and quick check.  
  16.     for (StackTraceElement s : stackTrace) {  
  17.         if (AbstractPollingIoProcessor.class.getName().equals(s.getClassName())) {  
  18.             IllegalStateException e = new IllegalStateException("t");  
  19.             e.getStackTrace();  
  20.             throw new IllegalStateException("DEAD LOCK: " + IoFuture.class.getSimpleName()  
  21.                     + ".await() was invoked from an I/O processor thread.  " + "Please use "  
  22.                     + IoFutureListener.class.getSimpleName() + " or configure a proper thread model alternatively.");  
  23.         }  
  24.     }  
  25.   
  26.     // And then more precisely.  
  27.     for (StackTraceElement s : stackTrace) {  
  28.         try {  
  29.             Class<?> cls = DefaultIoFuture.class.getClassLoader().loadClass(s.getClassName());  
  30.             if (IoProcessor.class.isAssignableFrom(cls)) {  
  31.                 throw new IllegalStateException("DEAD LOCK: " + IoFuture.class.getSimpleName()  
  32.                         + ".await() was invoked from an I/O processor thread.  " + "Please use "  
  33.                         + IoFutureListener.class.getSimpleName()  
  34.                         + " or configure a proper thread model alternatively.");  
  35.             }  
  36.         } catch (Exception cnfe) {  
  37.             // Ignore  
  38.         }  
  39.     }  
  40. }  

1.2 IoSession的过滤器链

    IoSession是有状态的,它包括:
  • Connected :IoSession已被创建且生效。
  • Idle :会话在一定时间内没有处理任何请求,它包括下面三个子状态:
    • Idle for read : 一段时间内没有接受数据。
    • Idle for write : 一段时间内没有发送数据。
    • Idle for both : 一段时间内没有产生接受或发送数据的操作。
  • Closing : IoSession正在关闭中。
  • Closed : session已经被关闭。

图4.2 IoSession状态变化图

    每个状态都会引起一系列的过滤器的对应的操作,从这句代码中我们了解到,每个IoSession都有他自己的过滤器链,即我们可以为每个session定制过滤器链。
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. filterChain = new DefaultIoFilterChain(this);  
    IoSession会先继承IoService的过滤器链,过滤器链将放在下一节统一分析。


2. IoSession在服务器端的创建第二步:initSession(session, null, null)

    这一步initSession(session, null, null);主要的有两段可配置的属性,一个就是存储attribute属性键值对的Map,默认是ConcurrentHashMap;另一个就是发送队列,默认是ConcurrentLinkedQueue。若要实现自己的数据结构,需要重写接口IoSessionDataStructureFactory或继承DefaultIoSessionDataStructureFactory。
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. try {  
  2.     ((AbstractIoSession) session).setAttributeMap(session.getService().getSessionDataStructureFactory()  
  3.             .getAttributeMap(session));  
  4. catch (IoSessionInitializationException e) {  
  5.     throw e;  
  6. catch (Exception e) {  
  7.     throw new IoSessionInitializationException("Failed to initialize an attributeMap.", e);  
  8. }  
  9.   
  10. try {  
  11.     ((AbstractIoSession) session).setWriteRequestQueue(session.getService().getSessionDataStructureFactory()  
  12.             .getWriteRequestQueue(session));  
  13. catch (IoSessionInitializationException e) {  
  14.     throw e;  
  15. catch (Exception e) {  
  16.     throw new IoSessionInitializationException("Failed to initialize a writeRequestQueue.", e);  
  17. }  

3. IoSession在服务器端的创建第三步:session.getProcessor().add(session);

    这一步就是为我们新生成的IoSession在IoService的Processor池中指定一个Processor处理。分配的方式就是取模分配,完成后在分配Processor中增加该IoSession的引用。SimpleIoProcessorPool中的代码如下:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.    public final void add(S session) {  
  2. //请注意这里是两步,第一步getProcessor(session)在下面段代码,创建(若没有)并返回Processor  
  3. //第二步在该Processor中add(session),这里就是调用AbstractPollingIoProcessor.add(),后面有更多描述  
  4.        getProcessor(session).add(session);  
  5.    }    
 
 
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. private IoProcessor<S> getProcessor(S session) {  
  2.     IoProcessor<S> processor = (IoProcessor<S>) session.getAttribute(PROCESSOR);  
  3.   
  4.     if (processor == null) {  
  5.         if (disposed || disposing) {  
  6.             throw new IllegalStateException("A disposed processor cannot be accessed.");  
  7.         }  
  8.   
  9.         processor = pool[Math.abs((int) session.getId()) % pool.length];  
  10.   
  11.         if (processor == null) {  
  12.             throw new IllegalStateException("A disposed processor cannot be accessed.");  
  13.         }  
  14.   
  15.         session.setAttributeIfAbsent(PROCESSOR, processor);  
  16.     }  
  17.   
  18.     return processor;  
  19. }  

4. SimpleIoProcessorPool、IoProcess和NioSocketAcceptor之间的关系

    上节我们留了个悬念,SimpleIoProcessorPool、IoProcess和IoServices之间的到底是关系呢?我们从SimpleIoProcessorPool初始化池说起:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. for (int i = 1; i < pool.length; i++) {  
  2.     ...  
  3.     pool[i] = processorConstructor.newInstance(this.executor);  
  4.     ...  
  5. }  
    这段代码将生成的线程池当做构造传参传递给所有的NioProcess,即数组池中所有NioProcess都是共享一个Executro线程池的(newCashedThreadPool)。AbstractPollingIoProcessor.add()方法做了这些工作:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.   public final void add(S session) {  
  2.       if (disposed || disposing) {  
  3.           throw new IllegalStateException("Already disposed.");  
  4.       }  
  5.   
  6.       // 将session添加到ConcurrentLinkedQueue的队列中,并启动Processor 的worker线程  
  7.       newSessions.add(session);  
  8. //启动Processor线程  
  9. tartupProcessor();   
  10.   }  
     我们很有必要具体看一下Processor线程做了什么: 
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. private class Processor implements Runnable {  
  2.     public void run() {  
  3.  ...  
  4.         for (;;) {  
  5.             try {  
  6.                 // 监控空闲时间  
  7.                 long t0 = System.currentTimeMillis();  
  8.                 int selected = select(1000);  
  9.                 long t1 = System.currentTimeMillis();  
  10.                 long delta = (t1 - t0);  
  11.   
  12.                 ...  
  13.                 // 注册新的session连接,并初始化session信息,如过滤器链,  
  14.   // 通知service的监听器  
  15.                 nSessions += handleNewSessions();  
  16.   ...  
  17.                 // 处理新的请求  
  18.                 if (selected > 0) {  
  19.                     process();  
  20.                 }  
  21.   ...  
  22.                 // 移除  
  23.                 nSessions -= removeSessions();  
  24.   
  25.                 // 通知空闲监听器  
  26.                 notifyIdleSessions(currentTime);  
  27.   
  28.                 ...  
  29.   //关闭  
  30.                 if (isDisposing()) {  
  31.                     for (Iterator<S> i = allSessions(); i.hasNext();) {  
  32.                         scheduleRemove(i.next());  
  33.                     }  
  34.   
  35.                     wakeup();  
  36.                 }  
  37.             } catch ...  
  38.         }  
  39.  ...  
  40.     }  
  41. }  
    其中,handlesNewSessions()做了如下工作:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.     //在selector上注册感兴趣的相应集合读操作  
  2.         SelectableChannel ch = (SelectableChannel) session.getChannel();  
  3.         ch.configureBlocking(false);  
  4.         session.setSelectionKey(ch.register(selector, SelectionKey.OP_READ, session));  
  5.   
  6.    //将service的过滤器链设置在session上  
  7.         IoFilterChainBuilder chainBuilder = session.getService().getFilterChainBuilder();  
  8.         chainBuilder.buildFilterChain(session.getFilterChain());  
  9.     //回调监听器  
  10.         IoServiceListenerSupport listeners = ((AbstractIoService) session.getService()).getListeners();  
  11.         listeners.fireSessionCreated(session);  
    至此SimpleIoProcessorPool、IoProcess和IoServices的关系就清晰了。
  • SimpleIoProcessorPool维护一组IoProcess[] ,每个IoProcess都在线程池中绑定了工作线程,该工作线程开启一个selector并监听session交互OP_READ。
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public NioProcessor(Executor executor) {  
  2.     super(executor);  
  3.   
  4.     try {  
  5.         selector = Selector.open();  
  6.     } catch (IOException e) {  
  7.         throw new RuntimeIoException("Failed to open a selector.", e);  
  8.     }  
  9. }  
  • NioSocketAcceptor持有一个pool的引用
  • NioSocketAcceptor自身也有个单线程的executor,用于监听新的session的链接OP_ACCEPT。
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. NioSocketAcceptor.init() throws Exception {  
  2.     selector = Selector.open();  
  3. }  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值