在mina中,IoService提供基础的I/O服务、管理IoSession,它是Mina框架中最重要的一部分,大部分基础的I/O操作都是在IoService的实现类中被处理的
图一
IoService有如下几个功能:
1、通信管理:在server端和client端处理数据的传输
2、管理过滤器链:管理过滤器链
3、调用Handler:当有新消息到来时调用Handler
4、统计管理:更新发送的消息数,发送的字节数等等
5、listeners管理:管理监听器
6、管理sessions:创建和删除session,空闲检测
本系列文章会按顺序通过源码逐个分析这些功能是如何实现的
server端IoService的类结构如下:
图二
通信管理
mina的工作原理如下:
图三
在服务端,bind一个端口后,会创建一个Acceptor线程负责监听是否有新的连接到来,新的连接到来之后封装成IoSession交给Processor线程来处理
一个简单的TCPServer如下所示:
public class MinaServer { private static Logger logger = LoggerFactory.getLogger(MinaServer.class); public static void main(String[] args) { NioSocketAcceptor acceptor = new NioSocketAcceptor(); //1 acceptor.getSessionConfig().setBothIdleTime(10000); //2 acceptor.getSessionConfig().setMaxReadBufferSize(2048); //3 acceptor.setHandler(new ServerHandler()); //4 acceptor.getFilterChain().addLast("codec", //5 new ProtocolCodecFilter( new TextLineCodecFactory( Charset.forName("UTF-8"), LineDelimiter.WINDOWS.getValue(), LineDelimiter.WINDOWS.getValue()))); try { acceptor.bind(new InetSocketAddress("127.0.0.1", 8866));//6 } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } logger.debug("MinaServer 启动 {}","success"); } }本篇文章主要讲解上述代码中的第1步和第6步,mina都做了哪些工作,其余部分会有专题进行讲解,先用一张顺序图来说明mina在第1步和第6步做了哪些工作,顺序图如下:
图四
首先创建一个NioSocketAcceptor对象
NioSocketAcceptor acceptor = new NioSocketAcceptor(); //1在创建NioSocketAcceptor对象的时候先创建一个父类对象AbstractPollingIoAcceptor并传入两个参数,第一个参数是一个DefaultSocketSessionConfig类,mina所有的可配置信息都在这个对象中,我们通过acceptor.getSessionConfig()方法来进行配置,getSessionConfig()方法获得的对象就是现在创建的这个DefaultSocketSessionConfig对象。
public NioSocketAcceptor() { super(new DefaultSocketSessionConfig(), NioProcessor.class); ((DefaultSocketSessionConfig) getSessionConfig()).init(this); }
创建父类对象时用到了子类对象NioSocketAcceptor传进来的NioProcess.class参数,该参数是一个Class对象,AbstractPollingIoAcceptor根据该参数创建一个SimpleIoProcessorPool对象
protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, Class<? extends IoProcessor<S>> processorClass) { this(sessionConfig, null, new SimpleIoProcessorPool<S>(processorClass), true, null); }
SimpleIoProcess是IoProcessor的实现类,该类的主要功能是创建一个IoProcessor数组,该数组的大小为所在机器的处理器数量+1,如代码1处所示,在代码2处通过反射创建IoProcessor实例,在代码2以下又创建了固定数量的IoProcessor实例。
processor线程主要负责具体的IO读写操作和IoFilterChain、IoHandler的逻辑,建立多个processor可以充分利用多核的处理能力,图三中的多个processor就是在这里创建的
public SimpleIoProcessorPool(Class<? extends IoProcessor<S>> processorType, Executor executor, int size, SelectorProvider selectorProvider) { if (processorType == null) { throw new IllegalArgumentException("processorType"); } if (size <= 0) { throw new IllegalArgumentException("size: " + size + " (expected: positive integer)"); } // Create the executor if none is provided createdExecutor = (executor == null); if (createdExecutor) { this.executor = Executors.newCachedThreadPool(); // Set a default reject handler ((ThreadPoolExecutor) this.executor).setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); } else { this.executor = executor; } pool = new IoProcessor[size]; //1)创建IoProcess数组 boolean success = false; Constructor<? extends IoProcessor<S>> processorConstructor = null; boolean usesExecutorArg = true; try { // We create at least one processor try { try { processorConstructor = processorType.getConstructor(ExecutorService.class);//2) 创建IoProcessor pool[0] = processorConstructor.newInstance(this.executor); } catch (NoSuchMethodException e1) { // To the next step... try { if(selectorProvider==null) { processorConstructor = processorType.getConstructor(Executor.class); pool[0] = processorConstructor.newInstance(this.executor); } else { processorConstructor = processorType.getConstructor(Executor.class, SelectorProvider.class); pool[0] = processorConstructor.newInstance(this.executor,selectorProvider); } } catch (NoSuchMethodException e2) { // To the next step... try { processorConstructor = processorType.getConstructor(); usesExecutorArg = false; pool[0] = processorConstructor.newInstance(); } catch (NoSuchMethodException e3) { // To the next step... } } } } catch (RuntimeException re) { LOGGER.error("Cannot create an IoProcessor :{}", re.getMessage()); throw re; } catch (Exception e) { String msg = "Failed to create a new instance of " + processorType.getName() + ":" + e.getMessage(); LOGGER.error(msg, e); throw new RuntimeIoException(msg, e); } if (processorConstructor == null) { // Raise an exception if no proper constructor is found. String msg = String.valueOf(processorType) + " must have a public constructor with one " + ExecutorService.class.getSimpleName() + " parameter, a public constructor with one " + Executor.class.getSimpleName() + " parameter or a public default constructor."; LOGGER.error(msg); throw new IllegalArgumentException(msg); } // Constructor found now use it for all subsequent instantiations for (int i = 1; i < pool.length; i++) { try { if (usesExecutorArg) { if(selectorProvider==null) { pool[i] = processorConstructor.newInstance(this.executor); } else { pool[i] = processorConstructor.newInstance(this.executor, selectorProvider); } } else { pool[i] = processorConstructor.newInstance(); } } catch (Exception e) { // Won't happen because it has been done previously } } success = true; } finally { if (!success) { dispose(); } } }
创建完SimpleIoProcessorPool实例后在代码1处赋值给AbstractPollingIoAcceptor的processor属性
private AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, Executor executor, IoProcessor<S> processor, boolean createdProcessor, SelectorProvider selectorProvider) { super(sessionConfig, executor); if (processor == null) { throw new IllegalArgumentException("processor"); } this.processor = processor;//1 this.createdProcessor = createdProcessor; try { // Initialize the selector init(selectorProvider); // The selector is now ready, we can switch the // flag to true so that incoming connection can be accepted selectable = true; } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeIoException("Failed to initialize.", e); } finally { if (!selectable) { try { destroy(); } catch (Exception e) { ExceptionMonitor.getInstance().exceptionCaught(e); } } } }
创建完AbstractPollingIoAcceptor对象后会依次初始化父类对象AbstractIoAcceptor、AbstractIoService如图4所示
至此,NioSocketAcceptor对象就已经创建完毕了,创建完NioSocketAcceptor后一般要设置过滤器***Filter,处理器***Handler(这些在后面的文章中会介绍),绑定监听端口,本篇文章重点介绍mina在绑定端口时为我们做了哪些工作。
绑定端口的过程如图4所示,在第2.1.1.1步调用了startupAcceptor方法,具体代码如下,在代码1处创建了acceptor对象并在代码2处将acceptor交由线程池执行逻辑,这个线程池是在创建NioSocketAcceptor时创建的
private void startupAcceptor() throws InterruptedException { // If the acceptor is not ready, clear the queues // TODO : they should already be clean : do we have to do that ? if (!selectable) { registerQueue.clear(); cancelQueue.clear(); } // start the acceptor if not already started Acceptor acceptor = acceptorRef.get(); if (acceptor == null) { lock.acquire(); acceptor = new Acceptor(); //① if (acceptorRef.compareAndSet(null, acceptor)) { executeWorker(acceptor); //② } else { lock.release(); } } }Acceptor类的代码如下,while循环中的循环变量selectable在创建NioSocketAcceptor对象时被赋值selectable=true;所以while循环会一直进行,该类的功能就是判断是否有新的连接到来,如果有新的连接到来就调用processHandles方法将新的连接封装成IoSession,在代码1处将连接封装成了一个Session。
private class Acceptor implements Runnable { public void run() { assert (acceptorRef.get() == this); int nHandles = 0; // Release the lock lock.release(); while (selectable) { try { // Detect if we have some keys ready to be processed // The select() will be woke up if some new connection // have occurred, or if the selector has been explicitly // woke up int selected = select(); // this actually sets the selector to OP_ACCEPT, // and binds to the port on which this class will // listen on nHandles += registerHandles(); // Now, if the number of registred handles is 0, we can // quit the loop: we don't have any socket listening // for incoming connection. if (nHandles == 0) { acceptorRef.set(null); if (registerQueue.isEmpty() && cancelQueue.isEmpty()) { assert (acceptorRef.get() != this); break; } if (!acceptorRef.compareAndSet(null, this)) { assert (acceptorRef.get() != this); break; } assert (acceptorRef.get() == this); } if (selected > 0) {//判断是否有新的连接到来 processHandles(selectedHandles()); } // check to see if any cancellation request has been made. nHandles -= unregisterHandles(); } catch (ClosedSelectorException cse) { // If the selector has been closed, we can exit the loop ExceptionMonitor.getInstance().exceptionCaught(cse); break; } catch (Exception e) { ExceptionMonitor.getInstance().exceptionCaught(e); try { Thread.sleep(1000); } catch (InterruptedException e1) { ExceptionMonitor.getInstance().exceptionCaught(e1); } } } // Cleanup all the processors, and shutdown the acceptor. if (selectable && isDisposing()) { selectable = false; try { if (createdProcessor) { processor.dispose(); } } finally { try { synchronized (disposalLock) { if (isDisposing()) { destroy(); } } } catch (Exception e) { ExceptionMonitor.getInstance().exceptionCaught(e); } finally { disposalFuture.setDone(); } } } } /** * This method will process new sessions for the Worker class. All * keys that have had their status updates as per the Selector.selectedKeys() * method will be processed here. Only keys that are ready to accept * connections are handled here. * <p/> * Session objects are created by making new instances of SocketSessionImpl * and passing the session object to the SocketIoProcessor class. */ @SuppressWarnings("unchecked") private void processHandles(Iterator<H> handles) throws Exception { while (handles.hasNext()) { H handle = handles.next(); handles.remove(); // Associates a new created connection to a processor, // and get back a session S session = accept(processor, handle);//① if (session == null) { continue; } initSession(session, null, null); // add the session to the SocketIoProcessor session.getProcessor().add(session);//② } } }让我们看看是怎么将连接封装成session的,实际上就是创建了一个NioSocketSession对象,创建对象时将IoService对象、IoProcessor对象、SocketChannel对象传入作为NioSocketSession类的属性,从图3中可以看到,Acceptor线程将连接封装成IoSession后交给了IoProcessor线程来处理,还记得在创建NioSocketAcceptor对象时初始化了processor属性,并为该属性赋值了一个SimpleIoProcessorPool吗,在代码2处通过session.getProcessor()方法可以拿到SimpleIoProcessorPool对象的引用,让我们看看SimpleIoProcessorPool对象的add方法是怎么实现的
@Override protected NioSession accept(IoProcessor<NioSession> processor, ServerSocketChannel handle) throws Exception { SelectionKey key = null; if (handle != null) { key = handle.keyFor(selector); } if ((key == null) || (!key.isValid()) || (!key.isAcceptable())) { return null; } // accept the connection from the client SocketChannel ch = handle.accept(); if (ch == null) { return null; } return new NioSocketSession(this, processor, ch);//① }add方法的实现如下:
public final void add(S session) { getProcessor(session).add(session); }
private IoProcessor<S> getProcessor(S session) { IoProcessor<S> processor = (IoProcessor<S>) session.getAttribute(PROCESSOR); if (processor == null) { if (disposed || disposing) { throw new IllegalStateException("A disposed processor cannot be accessed."); } processor = pool[Math.abs((int) session.getId()) % pool.length]; if (processor == null) { throw new IllegalStateException("A disposed processor cannot be accessed."); } session.setAttributeIfAbsent(PROCESSOR, processor); } return processor; }又调用了一次getProcessor方法,这个getProcessor方法和之前的getProcessor方法完全不是一回事,之前的getProcessor方法是为了拿到SimpleIoProcessorPool对象,这个getProcessor方法是SimpleIoProcessorPool对象的一个方法,该方法的作用是根据sessionId在选出一个processor,然后调用add方法,在add方法的代码1处调用了startupProcessor方法,在该方法中,创建了一个Processor对象,并将该对象放到线程池中启动
public final void add(S session) { if (disposed || disposing) { throw new IllegalStateException("Already disposed."); } // Adds the session to the newSession queue and starts the worker newSessions.add(session); startupProcessor();//① }
private void startupProcessor() { Processor processor = processorRef.get(); if (processor == null) { processor = new Processor(); if (processorRef.compareAndSet(null, processor)) { executor.execute(new NamePreservingRunnable(processor, threadName)); } } // Just stop the select() and start it again, so that the processor // can be activated immediately. wakeup(); }Processor主要负责具体的IO读写操作和执行后面的IoFilterChain和IoHandler逻辑
Processor类的实现如下:
private class Processor implements Runnable { public void run() { assert (processorRef.get() == this); int nSessions = 0; lastIdleCheckTime = System.currentTimeMillis(); for (;;) { try { // This select has a timeout so that we can manage // idle session when we get out of the select every // second. (note : this is a hack to avoid creating // a dedicated thread). long t0 = System.currentTimeMillis(); int selected = select(SELECT_TIMEOUT); long t1 = System.currentTimeMillis(); long delta = (t1 - t0); if (!wakeupCalled.getAndSet(false) && (selected == 0) && (delta < 100)) { // Last chance : the select() may have been // interrupted because we have had an closed channel. if (isBrokenConnection()) { LOG.warn("Broken connection"); } else { LOG.warn("Create a new selector. Selected is 0, delta = " + (t1 - t0)); // Ok, we are hit by the nasty epoll // spinning. // Basically, there is a race condition // which causes a closing file descriptor not to be // considered as available as a selected channel, // but // it stopped the select. The next time we will // call select(), it will exit immediately for the // same // reason, and do so forever, consuming 100% // CPU. // We have to destroy the selector, and // register all the socket on a new one. registerNewSelector(); } } // Manage newly created session first nSessions += handleNewSessions();//① updateTrafficMask(); // Now, if we have had some incoming or outgoing events, // deal with them if (selected > 0) { // LOG.debug("Processing ..."); // This log hurts one of // the MDCFilter test... process();//② } // Write the pending requests long currentTime = System.currentTimeMillis(); flush(currentTime); // And manage removed sessions nSessions -= removeSessions(); // Last, not least, send Idle events to the idle sessions notifyIdleSessions(currentTime); // Get a chance to exit the infinite loop if there are no // more sessions on this Processor if (nSessions == 0) { processorRef.set(null); if (newSessions.isEmpty() && isSelectorEmpty()) { // newSessions.add() precedes startupProcessor assert (processorRef.get() != this); break; } assert (processorRef.get() != this); if (!processorRef.compareAndSet(null, this)) { // startupProcessor won race, so must exit processor assert (processorRef.get() != this); break; } assert (processorRef.get() == this); } // Disconnect all sessions immediately if disposal has been // requested so that we exit this loop eventually. if (isDisposing()) { boolean hasKeys = false; for (Iterator<S> i = allSessions(); i.hasNext();) { IoSession session = i.next(); if (session.isActive()) { scheduleRemove((S)session); hasKeys = true; } } if (hasKeys) { wakeup(); } } } catch (ClosedSelectorException cse) { // If the selector has been closed, we can exit the loop // But first, dump a stack trace ExceptionMonitor.getInstance().exceptionCaught(cse); break; } catch (Exception e) { ExceptionMonitor.getInstance().exceptionCaught(e); try { Thread.sleep(1000); } catch (InterruptedException e1) { ExceptionMonitor.getInstance().exceptionCaught(e1); } } } try { synchronized (disposalLock) { if (disposing) { doDispose(); } } } catch (Exception e) { ExceptionMonitor.getInstance().exceptionCaught(e); } finally { disposalFuture.setValue(true); } } }在代码1处将客户端创建的新连接以事件的形式通过过滤器链传给IoHandler的SessionCreated和SessionOpend方法,如果不是新的连接而是消息到达,则会走到代码2处,该方法会将消息以事件的形式通过过滤器传递给IoHandler的MessageReceived方法。
IoService的通信管理功能到此结束,IoService的其他功能会在接下来的文章中进行讲解