Mina-2.0.7源码学习 (1)

关于Mina的技术文章网上很多,这里只是为了驱动自己学习和复习记忆使用,因为自己曾经学习都只是走马观花,雁过无痕。这也算是自己在CSDN上发表的第一篇博客了。

这里对Mina具体是什么就不多介绍,只要知道可以很方便的用它来开发Java客户端服务器程序。下面从一段基于Mina的服务端程序代码开始分析[参考]

<span style="font-family:Microsoft YaHei;"><pre name="code" class="java">public class MinaTimeServer {

    private static final int PORT = 9999;
    // default log4j.properties path is '/src/main/resources'
    private static Logger logger = LoggerFactory.getLogger(MinaTimeServer.class);
    
    public static void main(String[] args) throws IOException {
  
        IoAcceptor acceptor = new NioSocketAcceptor();

        acceptor.getFilterChain().addLast("logger", new LoggingFilter());
        acceptor.getFilterChain().addLast(
                "codec",
                new ProtocolCodecFilter(new TextLineCodecFactory(Charset
                        .forName("UTF-8"))));

        acceptor.setHandler(new TimeServerHandler());

        acceptor.getSessionConfig().setReadBufferSize(2048);
        // every 10 seconds idle will execute method sessionIdle()
        acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 5);

        acceptor.bind(new InetSocketAddress(PORT));
        
        logger.info("server started! ");

    }

}</span>
 
PS:本人是使用Eclipse进行查看,这样既可以看到现有的项目文件目录结构,而且使用“Ctrl + 鼠标左键”很方便查看类的继承关系以及方法变量的定义,还能用debug模式查看运行时每个类的调用顺序。开始使用Notepad++或者Sublime查看源码不是很方便,或许安装插件可以弥补。 

首先从主函数第一行开始:

<span style="font-family:Microsoft YaHei;">	IoAcceptor acceptor = new NioSocketAcceptor();</span>
Ctrl + MouseLeft 点击 NioSocketAcceptor 弹出菜单选择 Open Implementation :

<span style="font-family:Microsoft YaHei;">	public final class NioSocketAcceptor extends AbstractPollingIoAcceptor<NioSession, ServerSocketChannel> implements
			SocketAcceptor {


		private volatile Selector selector;


		public NioSocketAcceptor() {
			super(new DefaultSocketSessionConfig(), NioProcessor.class);
			((DefaultSocketSessionConfig) getSessionConfig()).init(this);
		}
		
		@Override
		protected void init() throws Exception {
			selector = Selector.open();
		}


		@Override
		protected void destroy() throws Exception {
			if (selector != null) {
				selector.close();
			}
		}
		
		// ······
	}</span>
首先,可以看到 NioSocketAcceptor 的父类是 AbstractPollingIoAcceptor, 并且实现了 SocketAcceptor 接口方法。 接口SocketAcceptor extends 接口IoAcceptor extends 接口IoService.
其次,它封装了一个java.nio.channels.Selector类型的私有成员变量,并用volatile修饰,这是java多线程程序中经常会出现的关键字,为了保证并发访问它修饰的变量的原子性、可见性和顺序性。这暗示着每个线程访问一个volatile域时讲读到当前的内存中的值,而不是(有可能)使用一个缓存值。Selector的实例selector在方法init()中初始化,在destroy()中销毁,这两个以及其它与selector相关的一些方法是在父类 AbstractPollingIoAcceptor中定义的抽象方法,最终在父类 AbstractPollingIoAcceptor的某个构造函数(模板方法)中被调用,从而实现了selector变量的初始化。
接着, NioSocketAcceptor的构造函数中调用父类的构造函数,并且传入两个参数,一个是 DefaultSocketSessionConfig类型实例,其中定义了默认的会话参数配置,另外一个参数 NioProcessor.class 看到*.class就可以联想到在某个地方会用到Java Reflection来生成它的实例,为什么不像前一个参数一样直接new一个对象实例呢?想想或许是因为后面我们需要的NioProcessor实例个数不定(CPU核数+1),这里相当于只传入一个名字,后面根据实际需要创建它的对象吧,当然反射的好处不止这些。DefaultSocketSessionConfig类以及它的向上继承体系中相关类和接口都是与Session相关的属性和方法,这里暂时不讨论,NioProcessor类等主要负责处理与会话相关的所有I/O操作,包括加入会话到Processor,flush用户强制刷用户写请求队列中数据,remove删除Processor中会话并释放资源,updateTrafficMask控制会话I/O行为,如只读或只写。

下面接着分析 NioSocketAcceptor的父类:AbstractPollingIoAcceptor 模板类,这里它带两个模板参数 NioSession 和 ServerSocketChannel:

<span style="font-family:Microsoft YaHei;">	public abstract class AbstractPollingIoAcceptor<S extends AbstractIoSession, H> extends AbstractIoAcceptor {
		/** A lock used to protect the selector to be waked up before it's created */
		private final Semaphore lock = new Semaphore(1);	
		
		private final IoProcessor<S> processor;
		private final boolean createdProcessor;
		
		private final Queue<AcceptorOperationFuture> registerQueue = new ConcurrentLinkedQueue<AcceptorOperationFuture>();
		private final Queue<AcceptorOperationFuture> cancelQueue = new ConcurrentLinkedQueue<AcceptorOperationFuture>();
		
		private final Map<SocketAddress, H> boundHandles = Collections.synchronizedMap(new HashMap<SocketAddress, H>());
		private final ServiceOperationFuture disposalFuture = new ServiceOperationFuture();
		
		/** A flag set when the acceptor has been created and initialized */
		private volatile boolean selectable;

		/** The thread responsible of accepting incoming requests */
		private AtomicReference<Acceptor> acceptorRef = new AtomicReference<Acceptor>();

		protected boolean reuseAddress = false;
		
		/**
		 * Define the number of socket that can wait to be accepted. Default
		 * to 50 (as in the SocketServer default).
		 */
		protected int backlog = 50;

		protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, Class<? extends IoProcessor<S>> processorClass) {
			this(sessionConfig, null, new SimpleIoProcessorPool<S>(processorClass), true);
		}
		
		@Override
		protected final Set<SocketAddress> bindInternal(List<? extends SocketAddress> localAddresses) throws Exception {

			//******
			
		}
		
		/**
		 * This method is called by the doBind() and doUnbind()
		 * methods.  If the acceptor is null, the acceptor object will
		 * be created and kicked off by the executor.  If the acceptor
		 * object is null, probably already created and this class
		 * is now working, then nothing will happen and the method
		 * will just return.
		 */
		private void startupAcceptor() throws InterruptedException {
			//******
		}
		
		/**
		 * This class is called by the startupAcceptor() method and is
		 * placed into a NamePreservingRunnable class.
		 * It's a thread accepting incoming connections from clients.
		 * The loop is stopped when all the bound handlers are unbound.
		 */
		private class Acceptor implements Runnable {
		
			//******
			
		}
		
		//······
	}</span>
它有一个信号量变量,根据注释可知,该信号量用于防止在selector被创建之前就调用wakeup方法。selector.wakeup()主要是为了唤醒阻塞在selector.select选择上的线程,让该线程及时去处理其他事情,例如注册channel,改变interestOps、判断超时等等,selector.select()的选择过程是阻塞的,即如果感兴趣的事件一个都没有发生,就会阻塞等待直到至少有一个发生为止。

  • select()阻塞到至少有一个通道在你注册的事件上就绪了。
  • select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)。
  • selectNow()不会阻塞,不管什么通道就绪都立刻返回(如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回0)。
这里有几个方法讲解下,构造方法AbstractPollingIoAcceptor(),普通方法bindInternal()、startupAcceptor()以及内部Runnable子类 Acceptor。先看看构造方法:
<span style="font-family:Microsoft YaHei;">		protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, Class<? extends IoProcessor<S>> processorClass) {
			this(sessionConfig, null, new SimpleIoProcessorPool<S>(processorClass), true);
		}</span>
SimpleIoProcessorPool 从名字上看出它是一个Processor的池,在其内部会根据参数选择是否创建默认的Executor实例:

<span style="font-family:Microsoft YaHei;">        // 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;
        }</span>
然后在SimpleIoProcessorPool的构造函数中用Java反射机制以及上面executor作为参数构造NioProcessor实例

<span style="font-family:Microsoft YaHei;">        Constructor<? extends IoProcessor<S>> processorConstructor = null;
        processorConstructor = processorType.getConstructor(ExecutorService.class);
        pool[0] = processorConstructor.newInstance(this.executor);</span>
这里SimpleIoProcessorPool中有一个Executor,它主要作为Processor的线程执行者,后面会讲到Processor是专门用来处理Session上的I/O读写操作的。还有一个Executor在抽象类 AbstractIoService 中,它主要作为 Acceptor或者Connector线程的执行者,Acceptor顾名思义就是专门接收连接请求的线程,Connector同理。可以看出,这也是与平时的Java Nio的不同之处,平时都是使用一个Selector, 不区分IoService和IoProcess. 另外,IoProcess还负责调用注册在其上的 IoFilter,经过FilterChain之后调用IoHandler,所以Mina结构中:

  • IoService该接口主要负责套接字的建立,拥有自己的 Selector(NioSocketAcceptor中)和 Executor, 监听是否有连接建立。
  • IoProcessor接口也有自己的Executor和Selector(NioProcessor中), 负责通道上数据的I/O操作。
  • IoService的Acceptor线程接受连接请求创建会话IoSession并与某个IoProcessor类型的Processor绑定,这个Processor就负责处理IoSession中channel的I/O操作


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值