为项目开发socket服务程序,一开始使用的是普通的IO机制,socket服务端监听线程每收到一个链接,即启动一个线程来收发处理链接报文,在连接数较少时,此种处理方式的线程数不多,数据量也不大,带来的CPU和资源的负荷较小,程序还能稳定的工作。但是随着链接的增大,数据量的增多,简单的IO机制的服务程序无论从性能还是稳定性上都不能满足系统的需要。在这个时候开始接触NIO机制,用来作为服务程序的框架,于是接触了MINA框架。
MINA框架的使用比较简单,在绑定套接字前,只需要定义部分参数、编解码处理器及过滤器即可,简单示例如下:
1 IoAcceptor acceptor = new NioSocketAcceptor(); 2 acceptor.getFilterChain().addLast("logger", new LoggingFilter()); 3 acceptor.getFilterChain().addLast("codec",new ProtocolCodecFilter(new DemoCodecFactory())); 4 acceptor.setHandler(new DemoServerHandler()); 5 acceptor.getSessionConfig().setReadBufferSize(13); 6 acceptor.getSessionConfig().setIdleTime(IdleStatus.READER_IDLE, 60); 7 acceptor.bind(new InetSocketAddress(PORT));
此次主要学习MINA的NIO机制,所以只涉及到上述代码中的第一行及最后一行作为入口。
IoAcceptor acceptor = new NioSocketAcceptor();
代码中使用的是NioSocketAcceptor的无参构造函数,查看NioSocketAcceptor的源代码,可以看到
public NioSocketAcceptor() { super(new DefaultSocketSessionConfig(), NioProcessor.class); ((DefaultSocketSessionConfig) getSessionConfig()).init(this); }
调用的是父类AbstractPollingIoAcceptor中的
protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, Class<? extends IoProcessor<S>> processorClass) { this(sessionConfig, null, new SimpleIoProcessorPool<S>(processorClass), true, null); }
此处需要注意的是new SimpleIoProcessorPool<S>(processorClass)这个参数。SimpleIoProcessorPool既继承了IoProcessor接口同时又是IoProcessor的一个集合。IoProcessor即是报文读取处理的类,这一块我们下面介绍。也就是说定义无参Accepter时自动构建了一个处理器的池子,通过下面的代码
public SimpleIoProcessorPool(Class<? extends IoProcessor<S>> processorType) { this(processorType, null, DEFAULT_SIZE, null); }
我们可以看出来,处理器池的默认大小DEFAULT_SIZE是Runtime.getRuntime().availableProcessors() + 1,也就是CPU个数+1。通过初始化的代码我们可以看出来,Accepter初始化时构建了一个处理器池,然后我们看一下Accepter和Processor是怎么工作的。
public final void bind(Iterable<? extends SocketAddress> localAddresses)
通过上述绑定套接字的方法,可以看出MINA可以监听多个端口,忽略掉验证的内容,查看内部实现我们可以看到:
private class Acceptor implements Runnable { public void run() { ... 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) { // We have some connection request, let's process // them here. 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); } } } ... }
private int registerHandles() { for (;;) { ...... try { // Process all the addresses for (SocketAddress a : localAddresses) { H handle = open(a); newHandles.put(localAddress(handle), handle); } ...... }
@Override protected ServerSocketChannel open(SocketAddress localAddress) throws Exception { // Creates the listening ServerSocket ServerSocketChannel channel = null; if (selectorProvider != null) { channel = selectorProvider.openServerSocketChannel(); } else { channel = ServerSocketChannel.open(); } boolean success = false; try { // This is a non blocking socket channel channel.configureBlocking(false); // Configure the server socket, ServerSocket socket = channel.socket(); // Set the reuseAddress flag accordingly with the setting socket.setReuseAddress(isReuseAddress()); // and bind. try { socket.bind(localAddress, getBacklog()); } catch (IOException ioe) { // Add some info regarding the address we try to bind to the // message String newMessage = "Error while binding on " + localAddress + "\n" + "original message : " + ioe.getMessage(); Exception e = new IOException(newMessage); e.initCause(ioe.getCause()); // And close the channel channel.close(); throw e; } // Register the channel within the selector for ACCEPT event channel.register(selector, SelectionKey.OP_ACCEPT); success = true; } finally { if (!success) { close(channel); } } return channel; }
直到此时,我们发现Accepter就是将各个channel的SelectionKey.OP_ACCEPT事件注册到了Accepter的selector上。
下一步我们就是分析链接成功后是如何进行数据读取的,当然了上面我们提到过,数据读取处理是通过Processor来完成的。我们还是回到Accepter的处理逻辑中,请注意下面的代码:
if (selected > 0) { // We have some connection request, let's process // them here. processHandles(selectedHandles()); }
我们看一下是怎么处理的:
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); } }
accept(processor, handle)为新连接创建了一个Session,Session中保存了该链接的相关信息,生命周期将伴随整个链接的处理过程。通过session.getProcessor()我们可以发现,session中已经包含了Processor的信息,我们看看是怎么为该session分配的Processor。需要注意的是,此时的参数IoProcessor<NioSession> processor仍然是我们初始化时的Processor池。通过类SimpleIoProcessorPool中的方法getProcessor我们可以看到
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; }
Processor池最终通过简单的算法为我们提供了唯一的一个IoProcessor对象,最后我们来看一下IoProcessor的add方法做了什么操作
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内部线程类中,我们可以看到这一句:nSessions += handleNewSessions();,最终我们可以看到在addNew的init方法中完成了读事件的Selector注册
@Override protected void init(NioSession session) throws Exception { SelectableChannel ch = (SelectableChannel) session.getChannel(); ch.configureBlocking(false); session.setSelectionKey(ch.register(selector, SelectionKey.OP_READ, session)); }
至此,我们捋顺了socket从建立连接到监听读数据的NIO结构,下面的章节我们将针对数据的读取及处理来进行学习。