From: http://2006zhouxinfeng.blog.163.com/blog/static/5024021620115155106736/
前一段在网上看到了“淘宝伯岩”([email protected])的一份关于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为整个框架的核心接口,其代表了网络服务。
IoAcceptor为IoService一个子接口,用来代表对用户请求的接受和监听。
SocketAcceptor为IoAcceptor的一个子接口,用来代表基于Socket的网络请求。
而AbstractIoService和AbstractIoAcceptor分别是对IoService和IoAcceptor的一个默认抽象实现。
AbstractPoolingIoAcceptor则是引入了池的概念,建立了处理线程池等。
最后NIOSocketAcceptor是AbstractPoolingIoAcceptor的NIO的一个实现。
2.2、整体流程
- 创建一个NIOSocketAcceptor对象。
- 调用IoAcceptor的bind方法,创建SocketChannel,并绑定到之前的Selector上。
- 创建一个新的线程,用来监听用户的连接。如果收到用户的连接请求,则为其创建一个Session,并把Session加入到一个Processor中等待处理。
- 系统中有若干个Processor,每个有自己的线程。在该线程中,也存在一个Selector,用来监听所有该Processor上的Session。如果某个Session有数据可以读取或者写入,则将数据传递给一系列的Filter,并最终调用相应的Handler进行处理。
2.3、具体代码分析
为了简明起见,下面的代码只是整个代码的截取,目的是为了更好的说明问题。完整的代码请详见项目源文件。
2.3.1、构造
我们首先来看一下NioSocketAcceptor的构造函数。
-
public NioSocketAcceptor() {
-
super(new DefaultSocketSessionConfig(), NioProcessor.class);
-
((DefaultSocketSessionConfig) getSessionConfig()).init(this);
-
}
该构造函数传给了父类构造函数两个参数。首先是一个DefaultSocketSessionConfig的实例,用来表示一个配置对象。之后是NioProcessor.class,用来指明处理对象的类型。
接下来我们来看一下 NioSocketAcceptor的父类 AbstractPollingIoAcceptor<NioSession, ServerSocketChannel>:
-
protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, Class<? extends IoProcessor<NioSession>> processorClass) {
-
// 根据之前子类传递过来的processorClass,新建一个SimpleIoProcessorPool对象
-
// 并调用另一个构造函数
-
this(sessionConfig, null, new SimpleIoProcessorPool<NioSession>(processorClass), true);
-
}
-
private AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, Executor executor, IoProcessor<NioSession> processor, boolean createdProcessor) {
-
// 调用父类构造函数
-
super(sessionConfig, executor);
-
// 调用初始化函数
-
init();
-
}
其中的init函数的实现在 NioSocketAcceptor中,代码如下:
-
@Override
-
protected void init() throws Exception {
-
// 创建一个Selector对象
-
selector = Selector.open();
-
}
其中的SimpleIoProcessorPool<NioSession>的构造函数如下:
-
public SimpleIoProcessorPool(Class<? extends IoProcessor<NioSession>> processorType) {
-
// 用默认参数调用另一构造函数
-
this(processorType, null, DEFAULT_SIZE);
-
}
-
public SimpleIoProcessorPool(Class<? extends IoProcessor<NioSession>> processorType, Executor executor, int size) {
-
// 若executor为null则新建,不然则使用传入的对象。
-
createdExecutor = (executor == null);
-
if (createdExecutor) {
-
this.executor = Executors.newCachedThreadPool();
-
} else {
-
this.executor = executor;
-
}
-
// 新建一个IoProcessor池
-
pool = new IoProcessor[size];
-
// 根据传入的processorType,利用Java的反射机制来创建对象,填填满对象池
-
Constructor<? extends IoProcessor<S>> processorConstructor = processorType.getConstructor(ExecutorService.class);
-
for(int i = 0; i < size; i++) {
-
pool[i] = processorConstructor.newInstance(this.executor);
-
}
-
}
从以上代码可以看出, SimpleIoProcessorPool<NioSession>根据传入的IoProcessor的类型(这里为NioProcessor),创建了一个IoProcessor池,和一个Executor线程池,用来进行网络IO的读写请求。
那我们就来看一下 NioProcessor的具体创建过程
-
public NioProcessor(Executor executor) {
-
super(executor);
-
this.selector = Selector.open();
-
}
可以看出,在NioProcessor创建的过程中,还创建了一个Selector对象,用来之后监听网络IO的读写请求。并把传进来的Executor对象传递给父类进行保存。
之后我们再来看一下 AbstractPollingIoAcceptor<NioSession, ServerSocketChannel>的父类 AbstractIoAcceptor
-
protected AbstractIoAcceptor(IoSessionConfig sessionConfig, Executor executor) {
-
super(sessionConfig, executor);
-
}
可以看出该构造函数没有进行额外的工作,而是直接调用父类。那我们就来看一下他的父类 AbstractIoService
-
protected AbstractIoService(IoSessionConfig sessionConfig, Executor executor) {
-
// 保存配置对象
-
this.sessionConfig = sessionConfig;
-
// 若为提供Executor对象,则新建一个。
-
if (executor == null) {
-
this.executor = Executors.newCachedThreadPool();
-
createdExecutor = true;
-
} else {
-
this.executor = executor;
-
createdExecutor = false;
-
}
-
}
至此, NIOSocketAcceptor对象的创建也就到此结束。在整个创建过程中,我们创建了一个 DefaultSocketSessionConfig实例,用来表示一些配置选项。创建了一个Selector对象, 和一个Executor线程池,用来负责监听用户的请求(详见后文)。创建了一个 SimpleIoProcessorPool<NioSession>对象,其中包括一组 NioProcessor对象和一个Executor线程池对象,每个NioProcessor对象中还包括一个Selector对象,用来负责网络IO的读写处理(详见后文)。
2.3.2、bind
我们接着来看一下IoAcceptor中声明的bind方法。其现实在AbstractIoAcceptor中。代码如下:
-
public final void bind(Iterable<? extends SocketAddress> localAddresses) throws IOException {
-
// 检查地址的类型,并加入到localAddressCopy中
-
List<SocketAddress> localAddressesCopy = new ArrayList<SocketAddress>();
-
for (SocketAddress a : localAddresses) {
-
checkAddressType(a);
-
localAddressesCopy.add(a);
-
}
-
// 调用bindInternal函数,返回成功绑定的地址。
-
Set<SocketAddress> addresses = bindInternal(localAddressesCopy);
-
synchronized (boundAddresses) {
-
boundAddresses.addAll(addresses);
-
}
-
}
下面我们来看一下bindInternal的实现,该函数的实现在AbstractPollingIoAcceptor<NioSession, SocketChannel>中。
-
@Override
-
protected final Set<SocketAddress> bindInternal(List<? extends SocketAddress> localAddresses) throws Exception {
-
// 创建一个AcceptorOperationFuture对象,该对象的执行结果可以异步的通知
-
AcceptorOperationFuture request = new AcceptorOperationFuture(localAddresses);
-
// 将上文创建的对象加入队列中,等待其他线程处理
-
registerQueue.add(request);
-
// 启动Acceptor,进行地址的绑定
-
startupAcceptor();
-
// 唤起之前阻塞的Selector对象(详见下文)
-
wakeup();
-
// 等待绑定完成
-
request.awaitUninterruptibly();
-
// 返回结果
-
Set<SocketAddress> newLocalAddresses = new HashSet<SocketAddress>();
-
for (H handle : boundHandles.values()) {
-
newLocalAddresses.add(localAddress(handle));
-
}
-
return newLocalAddresses;
-
}
可以看出,在该函数中,首先创建了一个 AcceptorOperationFuture对象,并把其加入一个队列中,等待其他线程处理。该对象类似Java中自带的Future<?>对象,代表一个异步执行任务的结果。 自后调用了startupAcceptor来进行连接的监听和绑定。下面我们就来看一下startupAcceptor函数的具体实现。
-
private void startupAcceptor() {
-
// 如果acceptorRef没个赋值,则新建一个Acceptor对象进行赋值,并执行该acceptor。
-
Acceptor acceptor = acceptorRef.get();
-
if (acceptor == null) {
-
acceptor = new Acceptor();
-
if (acceptorRef.compareAndSet(null, acceptor)) {
-
executeWorker(acceptor);
-
}
-
}
从代码可以看出,该函数进行了一个原子操作。如果acceptorRef没被赋值,则新建对象进行赋值,并调用executeWroker函数。其中Acceptor实现了Runnable接口,而executorWorker函数的主要功能就是把该acceptor交给之前创建的Executor来执行。下面我们来看一下Acceptor的run函数,看一下它的具体执行过程。
-
public void run() {
-
int nHandles = 0;
-
while (selectable) {
-
// 等待用户连接请求
-
int selected = select();
-
// 注册新的地址绑定
-
nHandles += registerHandles();
-
// 没有绑定的地址,可以退出
-
if (nHandles == 0) {
-
acceptorRef.set(null);
-
if (registerQueue.isEmpty() && cancelQueue.isEmpty()) {
-
break;
-
}
-
if (!acceptorRef.compareAndSet(null, this)) {
-
break;
-
}
-
}
-
// 处理用户连接请求
-
if (selected > 0) {
-
processHandles(selectedHandles());
-
}
-
// 检查是否有unbind请求.
-
nHandles -= unregisterHandles();
-
}
-
}
其中的select()函数的实现在 NIOSocketAcceptor类中:
-
@Override
-
protected int select() throws Exception {
-
return selector.select();
-
}
可以看出,select函数在之前创建的Selector上调用select函数。等待绑定在该Selector上的Channel状态就绪。由于在第一次调用时,该Selector上并没有绑定任何Channel,所以该函数会永远阻塞住。这也就是为什么在之前的 bindInternal中,在调用 startAcceptor后,需要马上调用wakeup函数。该函数的实现在 NIOSocketAcceptor类中:
-
@Override
-
protected void wakeup() {
-
selector.wakeup();
-
}
该函数会调用Selector的wakeup函数,用来唤醒阻塞的select函数。
接下来我们来看一下registerHandlers函数,该函数的作用是用来检查是否有地址绑定的请求。并进行绑定。(unRegisterHandles与之相似,过程相反,这里略去不提。)
-
private int registerHandles() {
-
for (;;) {
-
// 从队列中获得绑定任务
-
AcceptorOperationFuture future = registerQueue.poll();
-
// 队列为空,直接退出,返回0
-
if (future == null) {
-
return 0;
-
}
-
Map<SocketAddress, ServerSocketChannel> newHandles = new ConcurrentHashMap<SocketAddress, ServerSocketChannel>();
-
// 获得需要绑定的地址
-
List<SocketAddress> localAddresses = future.getLocalAddresses();
-
// 一次绑定
-
for (SocketAddress a : localAddresses) {
-
// 为每个地址创建ServerSocketChannel对象
-
ServerSocketChannel handle = open(a);
-
newHandles.put(localAddress(handle), handle);
-
}
-
// 更新已绑定的连接
-
boundHandles.putAll(newHandles);
-
// 通知 AcceptorOperationFuture任务已经完成
-
// 这样之前调用awaitUninterruptibly()阻塞的线程将继续执行。
-
future.setDone();
-
// 返回已绑定的个数
-
return newHandles.size();
-
}
-
}
该函数的作用是检查是否有绑定的请求,然后为每个地址建立一个连接,并绑定。其中创建连接调用的是open函数。该函数的实现在 NIOSocketAcceptor类中:
-
@Override
-
protected ServerSocketChannel open(SocketAddress localAddress) throws Exception {
-
// 创建一个新的ServerSocketChannel
-
ServerSocketChannel channel = ServerSocketChannel.open();
-
// 设置为非阻塞
-
channel.configureBlocking(false);
-
// 绑定地址
-
ServerSocket socket = channel.socket();
-
socket.bind(localAddress, getBacklog());
-
// 在Selector对象中注册该Channel。
-
channel.register(selector, SelectionKey.OP_ACCEPT);
-
return channel;
-
}
我们现在再把注意力移回之前的Acceptor的run函数。如果之前的select函数是正常返回,而不是被wakeup,那么说明有用户的连接请求。接下来就会执行processHandles函数。其实现如下:
-
private void processHandles(Iterator<ServerSocketChannel> handles) throws Exception {
-
while (handles.hasNext()) {
-
ServerSocketChannel handle = handles.next();
-
handles.remove();
-
// 接受用户的请求,并创Session。
-
NioSession session = accept(processor, handle);
-
// 初始化Session
-
initSession(session, null, null);
-
// 把session添加到一个Processor的处理队列中,等待IO读写处理
-
session.getProcessor().add(session);
-
}
-
}
下面我们来看一下accept函数。该函数接收两个参数,一个是有连接请求的ServerSocketChannel,一个是之前创建的SimpleIoProcessorPool<NioSession>对象。
该函数的实现在NIOSocketAcceptor类中:
- @Override
- protected NioSession accept(IoProcessor<NioSession> processor, ServerSocketChannel handle) throws Exception {
- // 获得用户连接
- SocketChannel ch = handle.accept();
- // 创建Session
- return new NioSocketSession(this, processor, ch);
- }
之后我们来看一下Session在Processor上的注册过程。及SimpleIoProcessorPool<NioSession>的add函数:
-
public final void add(S session) {
-
// 从Processor池中获得一个Processor(NioProcessor),并注册session.
-
getProcessor(session).add(session);
-
}
-
private IoProcessor<S> getProcessor(S session) {
-
// 如果该session已经注册一个Processor,则返回该Processor
-
// 如果不存在,则从池中随机选择一个,并绑定到该session上
-
IoProcessor<S> processor = (IoProcessor<S>) session.getAttribute(PROCESSOR);
-
if(processor == null) {
-
processor = pool[Math.abs((int) session.getId()) % pool.length];
-
session.setAttributeIfAbsent(PROCESSOR, processor);
-
}
-
return processor;
-
}
接下来我们来看一下NioProcessor中的add函数。整个过程和之前监听用户连接请求的过程相似。故我们只关注其中不同的地方
-
public final void add(S session) {
-
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));
-
}
-
}
-
wakeup();
-
}
-
-
public void run() {
-
int nSessions = 0;
-
for (;;) {
-
int selected = select(SELECT_TIMEOUT);
-
// 处理新的session的注册
-
nSessions += handleNewSessions();
-
if (selected > 0) {
-
process();
-
}
-
// 刷新排队的写请求
-
flush(currentTime);
-
nSessions -= removeSessions();
-
if (nSessions == 0) {
-
processorRef.set(null);
-
if (newSessions.isEmpty() && isSelectorEmpty()) {
-
break;
-
}
-
if (!processorRef.compareAndSet(null, this)) {
-
break;
-
}
-
}
- }
接下来我们来看一下其中的 handleNewSessions函数
-
private int handleNewSessions() {
-
int addedSessions = 0;
-
for (S session = newSessions.poll(); session != null; session = newSessions.poll()) {
-
if (addNow(session)) {
-
addedSessions++;
-
}
-
}
-
return addedSessions;
-
}
-
private boolean addNow(S session) {
-
boolean registered = false;
-
// 注册session
-
init(session);
-
registered = true;
-
// 构建FilterChain
-
IoFilterChainBuilder chainBuilder = session.getService().getFilterChainBuilder();
-
chainBuilder.buildFilterChain(session.getFilterChain());
-
return registered;
-
}
-
@Override
-
protected void init(NioSession session) throws Exception {
-
// 把Session对应的SocketChannel注册在该session对应的processor的Selector。
-
SelectableChannel ch = (SelectableChannel) session.getChannel();
-
ch.configureBlocking(false);
-
session.setSelectionKey(ch.register(selector, SelectionKey.OP_READ,session));
-
}
接下来我们再来看一下process函数。该函数负责数据的读写,并把数据传递给FilterChain,并最终调用用户的IoHandler。
-
private void process() throws Exception {
-
for (Iterator<S> i = selectedSessions(); i.hasNext();) {
-
S session = i.next();
-
process(session);
-
i.remove();
-
}
-
}
-
private void process(S session) {
-
// 读请求
-
if (isReadable(session) && !session.isReadSuspended()) {
-
read(session);
-
}
-
// 写请求
-
if (isWritable(session) && !session.isWriteSuspended()) {
-
if (session.setScheduledForFlush(true)) {
-
// 加入写刷新队列
-
flushingSessions.add(session);
-
}
-
}
-
}
-
-
private void read(S session) {
-
// 从SocketChannel中读数据
-
// 这里略去
-
// 如果读到数据
-
if (readBytes > 0) {
-
// 把数据传给FilterChain
-
IoFilterChain filterChain = session.getFilterChain();
-
filterChain.fireMessageReceived(buf);
-
}
-
}
最后是
flush函数,用来刷新写请求的数据。这里暂时略去。
因为我已经写不动了,累死了。哪天在另开文章吧。