在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线程来处理,Processor线程会监控是否有数据从已经建立的连接中传输到server并进行处理
一个简单的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方法。