转自:http://blog.csdn.net/liuzhenwen/article/details/5894279

客户端通信过程 

1.通过SocketConnector同服务器端建立连接 

2.链接建立之后I/O的读写交给了I/O Processor线程,I/O Processor是多线程的 

3.通过I/O Processor读取的数据经过IoFilterChain里所有配置的IoFilter,IoFilter进行消息的过滤,格式的转换,在这个层面可以制定一些自定义的协议 

4.最后IoFilter将数据交给Handler进行业务处理,完成了整个读取的过程 

5.写入过程也是类似,只是刚好倒过来,通过IoSession.write写出数据,然后Handler进行写入的业务处理,处理完成后交给IoFilterChain,进行消息过滤和协议的转换,最后通过I/O Processor将数据写出到socket通道 

IoFilterChain作为消息过滤链 

1.读取的时候是从低级协议到高级协议的过程,一般来说从byte字节逐渐转换成业务对象的过程 

2.写入的时候一般是从业务对象到字节byte的过程 

IoSession贯穿整个通信过程的始终 



整个过程可以用一个图来表现 



消息箭头都是有NioProcessor-N线程发起调用,默认情况下也在NioProcessor-N线程中执行 



类图 

http://mina.apache.org/class-diagrams.html#ClassDiagrams-ProtocolDecoderclassdiagram 



Connector 

作为连接客户端,SocketConector用来和服务器端建立连接,连接成功,创建IoProcessor Thread(不能超过指定的processorCount),Thread由指定的线程池进行管理,IoProcessor 利用NIO框架对IO进行处理,同时创建IoSession。连接的建立是通过Nio的SocketChannel进行。 



NioSocketConnector connector = new NioSocketConnector(processorCount); 

ConnectFuture future = connector.connect(new InetSocketAddress(HOSTNAME, PORT));建立一个I/O通道 



Acceptor 

作为服务器端的连接接受者,SocketAcceptor用来监听端口,同客户端建立连接,连接建立之后的I/O操作全部交给IoProcessor进行处理 

IoAcceptor acceptor = new NioSocketAcceptor(); 

acceptor.bind( new InetSocketAddress(PORT) ); 

Protocol 

利用IoFilter,对消息进行解码和编码,如以下代码通过 MyProtocolEncoder 将java对象转成byte串,通过MyProtocalDecoder 将byte串恢复成java对象

Java代码
  1. connector.getFilterChain().addLast("codec",  new  ProtocolCodecFilter( new MyProtocalFactory()));
  2. ......
  3. public   class  MyProtocalFactory  implements  ProtocolCodecFactory {
  4. ProtocolEncoderAdapter encoder = new  MyProtocolEncoder();
  5. ProtocolDecoder decoder = new  MyProtocalDecoder() ;
  6. public  ProtocolDecoder getDecoder(IoSession session)  throws  Exception {
  7. return  decoder;
  8. }
  9. public  ProtocolEncoder getEncoder(IoSession session)  throws  Exception {
  10. return  encoder;
  11. }
  12. }
  13. ......
  14. public   class  MyProtocalDecoder  extends  ProtocolDecoderAdapter  {
  15. public   void  decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out)
  16. throws  Exception {
  17. int   id  = in.getInt();
  18. int   len = in.getInt();
  19. byte []  dst =  new   byte [len];
  20. in.get(dst);
  21. String name = new  String(dst,"GBK");
  22. Item item = new  Item();
  23. item.setId(id);
  24. item.setName(name);
  25. out.write(item);
  26. }
  27. }
  28. ......
  29. public   class  MyProtocolEncoder  extends  ProtocolEncoderAdapter {
  30. public   void  encode(IoSession session, Object message,
  31. ProtocolEncoderOutput out) throws  Exception {
  32. Item item = (Item)message;
  33. int  byteLen =  8  + item.getName().getBytes("GBK").length ;
  34. IoBuffer buf = IoBuffer.allocate(byteLen);
  35. buf.putInt(item.getId());
  36. buf.putInt(item.getName().getBytes("GBK").length);
  37. buf.put(item.getName().getBytes("GBK"));
  38. buf.flip();
  39. out.write(buf);
  40. }
  41. }
[java] view
plain
copy

  1. connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new MyProtocalFactory()));
  2. ......
  3. public class MyProtocalFactory implements ProtocolCodecFactory {
  4. ProtocolEncoderAdapter encoder = new MyProtocolEncoder();
  5. ProtocolDecoder decoder = new MyProtocalDecoder() ;
  6. public ProtocolDecoder getDecoder(IoSession session) throws Exception {
  7. return decoder;
  8. }
  9. public ProtocolEncoder getEncoder(IoSession session) throws Exception {
  10. return encoder;
  11. }
  12. }
  13. ......
  14. public class MyProtocalDecoder extends ProtocolDecoderAdapter  {
  15. public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out)
  16. throws Exception {
  17. int  id  = in.getInt();
  18. int  len = in.getInt();
  19. byte[]  dst = new byte[len];
  20. in.get(dst);
  21. String name = new String(dst,"GBK");
  22. Item item = new Item();
  23. item.setId(id);
  24. item.setName(name);
  25. out.write(item);
  26. }
  27. }
  28. ......
  29. public class MyProtocolEncoder extends ProtocolEncoderAdapter {
  30. public void encode(IoSession session, Object message,
  31. ProtocolEncoderOutput out) throws Exception {
  32. Item item = (Item)message;
  33. int byteLen = 8 + item.getName().getBytes("GBK").length ;
  34. IoBuffer buf = IoBuffer.allocate(byteLen);
  35. buf.putInt(item.getId());
  36. buf.putInt(item.getName().getBytes("GBK").length);
  37. buf.put(item.getName().getBytes("GBK"));
  38. buf.flip();
  39. out.write(buf);
  40. }
  41. }

handler 

具体处理事件,事件包括:sessionCreated、sessionOpened、sessionClosed、sessionIdle、exceptionCaught、messageReceived、messageSent。 

connector.setHandler(new MyHandler());MyHandler继承IoHandlerAdapter类或者实现IoHandler接口.事件最终由IoProcessor线程发动调用。 

Processor 

Processor线程主要负责具体的IO操作、filterChain、IoHandler执行。Processor线程的数量默认为cpu数量+1,主要是为了充分利用多核的处理能力。Processor线程的数量可以根据实际情况进行配置。

多个IoSession会被分配到多个Processor线程中,可以理解为一个Processor线程“服务”一个或多个IoSession对象。值得一提的是,N个IoProcessor形成一个处理池(SimpleIoProcessorPoll),分配Processor的时候根据IoSession的id绝对值模N进行分配。

具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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保定具体的processor线程
            session.setAttributeIfAbsent(PROCESSOR, processor);
        }
  
        return
processor;
    }

在默认情况下filterChain、IoHandler的操作都是有IoProcessor顺序执行(执行完一个再执行下一个),如果IoHandler中的业务处理比较耗时,那么Processor线程将阻塞,后续的请求将得不到处理。即同时处理的请求数最多只有N个(N为Processor线程数量)。在高并发的情况下这种模式有待改进。

接着上面的类比,前台(acceptor、connector)接待客户(session)分配给业务员(IoProcessor)进行服务, 业务员对客户进行交流(IO操作、编码、解码等)并执行相应的业务服务(IoHandler)。同理业务员每次只能服务一个客户,其他客户只能等待(假设其他业务员都有自己的客户)。

IoSession 

IoSession是用来保持IoService的上下文,一个IoService在建立Connect之后建立一个IoSession(一个连接一个session),IoSession的生命周期从Connection建立到断开为止 

IoSession做两件事情: 

1.通过IoSession可以获取IoService的所有相关配置对象(持有对IoService,Processor池,SocketChannel,SessionConfig和IoService.IoHandler的引用) 

2.通过IoSession.write 是数据写出的入口 



MINA有3种worker线程 

Acceptor、Connector、I/O processor 线程 

Acceptor Thread 

一般作为服务器端链接的接收线程,实现了接口IoService,线程的数量就是创建SocketAcceptor 的数量 

Connector Thread 

一般作为客户端的请求建立链接线程,实现了接口IoService,维持了一个和服务器端Acceptor的一个链接,线程数量就是创建SocketConnector 的数量 



Mina的SocketAcceptor和SocketConnector均是继承了BaseIoService,是对IoService的两种不同的实现 

I/O processor Thread 

作为I/O真正处理的线程,存在于服务器端和客户端,用来处理I/O的读写操作,线程的数量是可以配置的,默认最大数量是CPU个数+1 

服务器端:在创建SocketAcceptor的时候指定ProcessorCount 

SocketAcceptor acceptor = new SocketAcceptor(Runtime.getRuntime().availableProcessors() + 1, Executors.newCachedThreadPool()); 

客户端:在创建SocketConnector 的时候指定ProcessorCount 

SocketConnector connector = new SocketConnector(Runtime.getRuntime().availableProcessors() + 1, Executors.newCachedThreadPool()); 

I/O Processor Thread,是依附于IoService,类似上面的例子SocketConnector connector = new SocketConnector(Runtime.getRuntime().availableProcessors() + 1, Executors.newCachedThreadPool());是指SocketConnector这个线程允许CPU+1个I/O Processor Thread 

NioProcessor虽然是多线程,但是对与一个连接的时候业务处理只会使用一个线程进行处理(Processor线程对于一个客户端连接只使用一个线程NioProcessor-n)如果handler的业务比较耗时,会导致NioProcessor线程堵塞 ,在2个客户端同时连接上来的时候会创建第2个(前提是第1个NioProcessor正在忙),创建的最大数量由Acceptor构造方法的时候指定。如果:一个客户端连接同服务器端有很多通信,并且I/O的开销不大,但是Handler处理的业务时间比较长,那么需要采用独立的线程模式,在
FilterChain的最后增加一个ExecutorFitler : 

acceptor.getFilterChain().addLast(&quot;threadPool&quot;, new ExecutorFilter(Executors.newCachedThreadPool())); 

这样可以保证processor和handler的线程是分开的,否则:客户端发送3个消息,而服务器对于每个消息要处理10s左右,那么这3个消息是被串行处理,在处理第一个消息的时候,后面的消息将被堵塞,同样反过来客户端也有同样的问题。

Mina 线程模型

接上文,上文提到“若IoHandler中的业务处理比较耗时,将阻塞当前Processor线程”,那么可以通过添加线程池处理这个问题。

Mina提供了一个很好的扩展点,可以在FilterChain添加线程池,让“下面”的业务处理过程并行起来。如图:

第一种模式:Mina默认的模式,IoProcessor全程服务。(One by one)

第二种模式:在FilterChain中加入ThreadPoolFilter,此时的处理流程变为Processor线程读取完数据后,执行IoFilterChain的逻辑。当执行到Thread Pool Filter的时候,该Filter会将后续的处理流程封装到一个Runnable对象中,并交由Filter自身的线程池来执行,而Processor线程则能立即返回来处理下一个IO请求。这样如果后面的IoFilter或IoHandler中有阻塞操作,只会引起Filter线程池里的线程阻塞,而不会阻塞住Processor线程,从而提高了服务器的处理能力。Mina提供了Thread
Pool Filter的一个实现:ExecutorFilter。(本段内容为转载)

第三种模式:在合适的环节添加多个线程池,这种适合于FilterChain、IoHandler过程中存在多个计算密集型的任务。一般不需要使用,加大了代码复杂程度。

接着上面的类比,如果公司业务发展很好,客户增多,那么一个业务员(Processor)将很难及时服务到众多客户。于是公司决定业务员只负责与客户交流,具体的业务操作交由专门的业务服务团队(ThreadPool)。大家意会一下即可!

客户端Porcessor堵塞测试情况: 

1.以下代码在建立连接后连续发送了5个消息(item)

Java代码
  1. ConnectFuture future = connector.connect( new  InetSocketAddress(HOSTNAME, PORT));
  2. future.awaitUninterruptibly();
  3. session = future.getSession();
  4. Item item = new  Item();
  5. item.setId(12345 );
  6. item.setName(&quot;hi&quot;);
  7. session.write(item);
  8. session.write(item);
  9. session.write(item);
  10. session.write(item);
  11. session.write(item);
[java] view
plain
copy

  1. ConnectFuture future = connector.connect(new InetSocketAddress(HOSTNAME, PORT));
  2. future.awaitUninterruptibly();
  3. session = future.getSession();
  4. Item item = new Item();
  5. item.setId(12345);
  6. item.setName(&quot;hi&quot;);
  7. session.write(item);
  8. session.write(item);
  9. session.write(item);
  10. session.write(item);
  11. session.write(item);

2.在handle的messageSent方法进行了延时处理,延时3秒

Java代码
  1. public   void  messageSent(IoSession session, Object message)  throws  Exception {
  2. Thread.sleep(3000 );
  3. System.out.println(message);
  4. }
[java] view
plain
copy

  1. public void messageSent(IoSession session, Object message) throws Exception {
  2. Thread.sleep(3000);
  3. System.out.println(message);
  4. }

3.测试结果 

5个消息是串行发送,都由同一个IoPorcessor线程处理

Java代码
  1. session.write(item);
  2. session.write(item);
  3. session.write(item);
  4. session.write(item);
  5. session.write(item);
[java] view
plain
copy

  1. session.write(item);
  2. session.write(item);
  3. session.write(item);
  4. session.write(item);
  5. session.write(item);

服务器端每隔3秒收到一个消息。因为调用是由IoProcessor触发,而一个connector只会使用一个IoProcessor线程 



4.增加ExecutorFilter,ExecutorFilter保证在处理handler的时候是独立线程 

connector.getFilterChain().addLast(&quot;threadPool&quot;, new ExecutorFilter(Executors.newCachedThreadPool())); 

5.测试结果 

4个session.wirte变成了并行处理,服务器端同时收到了5条消息

MINA2.0原理的更多相关文章

  1. mina2.0 spring

    Apache MINA是一个网络应用程序框架,它可以帮助用户开发的高性能.高扩展性的网络应用程序.它提供了一个抽象的事件驱动的异步API在不同传输如TCP/IP和UDP/IP通过java NIO. A ...

  2. Android进阶:七、Retrofit2.0原理解析之最简流程【下】

    紧接上文Android进阶:七.Retrofit2.0原理解析之最简流程[上] 一.请求参数整理 我们定义的接口已经被实现,但是我们还是不知道我们注解的请求方式,参数类型等是如何发起网络请求的呢? 这 ...

  3. OAuth2.0 原理流程及其单点登录和权限控制

    2018年07月26日 07:21:58 kefeng-wang 阅读数:5468更多 所属专栏: Java微服务构架   版权声明:[自由转载-非商用-非衍生-保持署名]-转载请标明作者和出处. h ...

  4. MINA2.0用户手册中文版

    MINA2.0用户手册中文版--第一章 MINA2.0入门 MINA2.0用户手册中文版--第二章 第一节 MINA应用程序架构 MINA2.0用户手册中文版--第二章 第二节 TCP服务端实例 MI ...

  5. apache mina2.0源码解析(一)

    apache mina是一个基于java nio的网络通信框架,为TCP UDP ARP等协议提供了一致的编程模型:其源码结构展示了优秀的设计案例,可以为我们的编程事业提供参考. 依照惯例,首先搭建a ...

  6. OAuth2.0原理与实现

    弄懂了原理流程,才可以搭建出来.更重要的是,可以根据原理流程自定义搭建,甚至可以完全自己实现一套,最后运行效果和原理和这个对得上就成功了,不要总期待标准答案! 首先参考两篇博客: 阮一峰的博客以及张开 ...

  7. HTTP 2.0 原理详细分析

    HTTP 2.0是在SPDY(An experimental protocol for a faster web, The Chromium Projects)基础上形成的下一代互联网通信协议.HTT ...

  8. Vue2.0原理-模板解析

    下面这段代码,vue内部做了什么操作?我去源码里面找找看 new Vue({ el: '#app' }) 入口 vue 的入口文件在 src/core/instance/index.js, 里面一进来 ...

  9. Struts1.2,struts2.0原理分析

    struts1原理: 1.首先我们表单提交到action 2.进入到web.xml 3.web.xml拦截*.do 4.交给ActionServlet 5.找到path属性,获得url 6.找到nam ...

随机推荐

  1. php中mysqli 处理查询结果集的几个方法

    最近对php查询mysql处理结果集的几个方法不太明白的地方查阅了资料,在此整理记下 Php使用mysqli_result类处理结果集有以下几种方法 fetch_all() 抓取所有的结果行并且以关联 ...

  2. Git中的fetch和pull

    http://blog.haohtml.com/archives/12674 Git中从远程的分支获取最新的版本到本地有这样2个命令: 1. git fetch:相当于是从远程获取最新版本到本地,不会 ...

  3. git push用法和常见问题分析

    在使用git 处理对android的修改的过程之中总结的.但不完善 Git push $ git push origin test:master         // 提交本地test分支作为远程的m ...

  4. App 性能分析

    关键因素: ---- Instrument 性能分析神器 1. 启动时间 应用启动时间长短对用户第一次体验至关重要,同时系统对应用的启动.恢复等状态的运行时间也有严格的要求,在应用超时的情况下系统会直 ...

  5. JS单元测试框架:QUnit

    QUnit:jQuery的单元测试框架,但不仅限于jQuery(从这个工具不需要引用jquery.js可以看出) index.html <!-- 官网 http://qunitjs.com/ - ...

  6. hdu 3062

    2-SAT的入门题: 网上说这个算法最好的入门教材是:伍昱的<由对称性解2-SAT问题>的ppt和赵爽的论文<2-SAT 解法浅析>: 看了一下伍昱的ppt,很好理解! 而这道 ...

  7. Hibernate save或者persist 后获取主键ID

    一个自增长ID的对象被save或者persist后,会返回其主键ID: Department department = new Department(); department.setName(&qu ...

  8. RTSP

    相关博客: RTSP 很详细的英文文档 RTSP交互命令简介及过程参数描述   RTSP协议 http://blog.csdn.net/andyweike/article/details/621071 ...

  9. [转贴]sizeof 和strlen的区别

    1. 编译时计算运算符sizeof,可用类型或变量做参数,计算占用内存的大小.sizeof后若是类型必须加括弧,若是变量名可不加括弧.sizeof(x)可用来定义数组维数.如:printf(" ...

  10. perl 监控mysql 事务和锁

    use DBI; use Net::SMTP; use HTTP::Date qw(time2iso str2time time2iso time2isoz); # mail_user should ...