哎,各种各样杂七杂八的事情。。。好久没有看代码了,其实要搞明白一个与IO相关的框架,最好的办法就是把它的I/0的读写两个过程搞清楚。。。例如在netty中,如果能将eventLoop的运行原理搞清楚,然后摸清楚整个I/O读写两个过程,那么也就差不太多了。。。。

这次来看看ZeroMQ(java)中如何来处理I/O的,先来看看一个类型的定义,IOObject类型,这个类型应该扮演的是工具类的形象,前面看过在ZeroMQ中所谓的IO线程的定义,那么IOObject就是用于直接与IO线程交互的,或者说的更直接的一点就是它是与IO线程里的poller对象交互的。。。

那么先来看看IOObject的类图吧:

这张图应该将IOObject与IOThread以及Poller之间的关系表现的很清楚了吧。。。。IOObject实现了IPollEvents接口,那么也就代表它可以响应IO事件。。。不过其实它并不直接实现这些IO事件,而是将其委托给内部的一个IPollEvents对象。。只不过是做了一层代理而已。。。

好了,接下来来看看IOObject的代码吧,先来看看它的属性申明:

  1. private Poller poller;   //poller对象
  2. private IPollEvents handler;   //用于执行事件回调的handler

这个poller就是从IO线程里面获取过来的,handler就是刚刚提到的事件回调的处理对象。。。IOObject不过是对其进行了一层包装而已。。。

那么接下来来看看重要的方法定义:

  1. //在将一个IO对象加入到一个IO线程的时候,要注意确定当前IO对象之前没有加入到任何IO线程或者已经从别的IO线程上面退下来了
  2. //将当前这个IO对象加入到IO线程上面去,说白了主要是获取这个IO线程的poller对象
  3. public void plug(IOThread io_thread_) {
  4. assert (io_thread_ != null);
  5. assert (poller == null);
  6. poller = io_thread_.get_poller ();      //获取这个线程的poller对象
  7. }

这个方法用于将当前这个IO对象加入到一个IO线程,其实主要的是要获取这个IO线程的Poller对象。。好了,接下来再来看看如何注册channel以及事件吧:

  1. //在poller里面移除channel
  2. public final void rm_fd(SelectableChannel handle) {
  3. poller.rm_fd(handle);
  4. }
  5. //给这个channel注册读取的事件
  6. public final void set_pollin (SelectableChannel handle_) {
  7. poller.set_pollin (handle_);
  8. }
  9. //在这个channel上面注册写事件
  10. public final void set_pollout (SelectableChannel handle_) {
  11. poller.set_pollout (handle_);
  12. }
  13. //注册链接事件
  14. public final void set_pollconnect(SelectableChannel handle) {
  15. poller.set_pollconnect(handle);
  16. }
  17. //注册accept事件
  18. public final void set_pollaccept(SelectableChannel handle) {
  19. poller.set_pollaccept(handle);
  20. }
  21. //取消读取事件的注册
  22. public final void reset_pollin(SelectableChannel handle) {
  23. poller.reset_pollin (handle);
  24. }
  25. //取消写事件的注册
  26. public final void reset_pollout(SelectableChannel handle) {
  27. poller.reset_pollout (handle);
  28. }

这部分代码应该很简单吧,而且应该对IOObject的用处比较的清楚了,然后至于说IOObject对象如何响应in_event什么的,前面已经说过了,其实是委托给了handler对象来处理。。。好啦,IOObject的分析就算差不多了。。接下来来看看StreamEngine类型的实现吧,还是先来看看它初略的类图吧:

其实觉得看一个类的类图,基本上就能看出这个类的很多情况,好了,不说闲话了,来看看它的属性的定义吧:

  1. private static final int GREETING_SIZE = ;   //问候msg的大小,12个字节  (10字节的头,1字节的版本,1字节的socket类型)
  2. //  True iff we are registered with an I/O poller.
  3. private boolean io_enabled;   //如果是true的话,表示当前已经注册到了poller上面去
  4. private SocketChannel handle;   //真正底层用于通信的socketChannel
  5. private ByteBuffer inbuf;  //接收数据的buf
  6. private int insize;   //记录接收的数据的大小
  7. private DecoderBase decoder;  //decoder
  8. private Transfer outbuf;   //outbuf
  9. private int outsize;   //outbuf的大小
  10. private EncoderBase encoder;  //encoder
  11. //  When true, we are still trying to determine whether
  12. //  the peer is using versioned protocol, and if so, which
  13. //  version.  When false, normal message flow has started.
  14. private boolean handshaking;  //是否是在握手中,当值为false的时候代表握手已经完成了
  15. //  The receive buffer holding the greeting message
  16. //  that we are receiving from the peer.
  17. private final ByteBuffer greeting;  //用于接收问候msg的buf
  18. //  The send buffer holding the greeting message
  19. //  that we are sending to the peer.
  20. private final ByteBuffer greeting_output_buffer;  //用于发送问候msg的buf
  21. private SessionBase session;    //所属的session
  22. private Options options;  //选项配置
  23. // String representation of endpoint
  24. private String endpoint;   //这里一般是地址信息
  25. private boolean plugged;   //是否已经加入了
  26. private boolean terminating;  //是否已经停止了
  27. // Socket
  28. private SocketBase socket;  //所属的socket
  29. private IOObject io_object;    //拥有的IO对象

这里面有很多重要的属性,例如handler是SocketChannel类型的,可以知道它才是实际上底层用于通信的,然后又inbuf以及outbuf,这两个东西是干嘛用的应该一眼就看出来了吧,然后还有encoder和decoder,呵呵,可以猜到,读取到的数据先要经过decoder的处理才提交给上层,发送出去的数据也会通过encoder处理成二进制再发送出去。。。然后还有一个io_objcet对象。。。

接下来来看看构造方法吧:

  1. //构造函数,第一个参数是底层的channel,
  2. public StreamEngine (SocketChannel fd_, final Options options_, final String endpoint_)
  3. {
  4. handle = fd_;
  5. inbuf = null;
  6. insize = ;
  7. io_enabled = false;
  8. outbuf = null;
  9. outsize = ;
  10. handshaking = true;  //初始化为ture,表示还没有完成握手
  11. session = null;
  12. options = options_;
  13. plugged = false;
  14. terminating = false;
  15. endpoint = endpoint_;
  16. socket = null;
  17. greeting = ByteBuffer.allocate (GREETING_SIZE);  //创建用于接收问候msg的buf
  18. greeting_output_buffer = ByteBuffer.allocate (GREETING_SIZE);   //创建用于发送握手信息的buf
  19. encoder = null;
  20. decoder = null;
  21. try {
  22. Utils.unblock_socket (handle);  //将底层的channel设置为非阻塞的
  23. if (options.sndbuf != ) {  //设置底层的socket的发送缓冲大小
  24. handle.socket().setSendBufferSize((int)options.sndbuf);
  25. }
  26. if (options.rcvbuf != ) {  //设置底层的socket的接收缓冲大小
  27. handle.socket().setReceiveBufferSize((int)options.rcvbuf);
  28. }
  29. } catch (IOException e) {
  30. throw new ZError.IOException(e);
  31. }
  32. }

这个比较有意思的就是将channel设置为了非阻塞的模式,然后设置了底层socket的发送以及接受缓冲的大小。。其余的就没啥意思了。。。

  1. //将当前engine加入到IO线程以及session,其实这里最主要的事情是将channel注册到poller上面去
  2. public void plug (IOThread io_thread_,
  3. SessionBase session_)  {
  4. assert (!plugged);
  5. plugged = true;  //标志位
  6. //  Connect to session object.
  7. assert (session == null);
  8. assert (session_ != null);
  9. session = session_;    //当前所属的session
  10. socket = session.get_soket ();  //获取所属的scoekt,这个是ZMQ的socket
  11. io_object = new IOObject(null);  //创建IO对象,
  12. io_object.set_handler(this);  //设置IO对象的事件回调
  13. //  Connect to I/O threads poller object.
  14. io_object.plug (io_thread_);  // 将IO对象搞到这个IO线程上面去,其实最主要的就是获取这个IO线程的poller对象
  15. io_object.add_fd (handle);   //将底层的channel加入
  16. io_enabled = true; //表示已经加入了
  17. //  Send the 'length' and 'flags' fields of the identity message.
  18. //  The 'length' field is encoded in the long format.
  19. //设置发送的问候msg的信息
  20. greeting_output_buffer.put ((byte) );
  21. greeting_output_buffer.put ((byte) ) {  //如果inbuf里面没有数据需要处理
  22. //  Retrieve the buffer and read as much data as possible.
  23. //  Note that buffer can be arbitrarily large. However, we assume
  24. //  the underlying TCP layer has fixed buffer size and thus the
  25. //  number of bytes read will be always limited.
  26. inbuf = decoder.get_buffer ();  //从解码器里面获取buf,用于写入读取的数据,因为在已经设置了底层socket的TCP接收缓冲区的大小
  27. insize = read (inbuf);  //用于将发送过来的数据写到buf中去,并记录大小
  28. inbuf.flip();  //这里准备从buf里面读取数据了
  29. //  Check whether the peer has closed the connection.
  30. if (insize == -) {  //如果是-1的话,表示底层的socket连接已经出现了问题
  31. insize = ;
  32. disconnection = true;
  33. }
  34. }
  35. //  Push the data to the decoder.
  36. int processed = decoder.process_buffer (inbuf, insize);  //解析这些读取到的数据
  37. if (processed == -) {
  38. disconnection = true;
  39. } else {
  40. //  Stop polling for input if we got stuck.
  41. if (processed < insize)  //如果处理的数据居然还没有读到的数据多,那么取消读取事件的注册
  42. io_object.reset_pollin (handle);
  43. //  Adjust the buffer.
  44. insize -= processed;  //还剩下没有处理的数据的大小
  45. }
  46. //  Flush all messages the decoder may have produced.
  47. session.flush ();  //将decoder解析出来的数据交给session
  48. //  An input error has occurred. If the last decoded message
  49. //  has already been accepted, we terminate the engine immediately.
  50. //  Otherwise, we stop waiting for socket events and postpone
  51. //  the termination until after the message is accepted.
  52. if (disconnection) {   //表示已经断开了连接,那么需要处理一下
  53. if (decoder.stalled ()) {
  54. io_object.rm_fd (handle);
  55. io_enabled = false;
  56. } else
  57. error ();
  58. }
  59. }
  60. //表示可以写数据了
  61. public void out_event ()   {
  62. //  If write buffer is empty, try to read new data from the encoder.
  63. if (outsize == ) {  //需要写的数据量为0
  64. //  Even when we stop polling as soon as there is no
  65. //  data to send, the poller may invoke out_event one
  66. //  more time due to 'speculative write' optimisation.
  67. if (encoder == null) {
  68. assert (handshaking);
  69. return;
  70. }
  71. outbuf = encoder.get_data (null);  //从encoder里面获取数据
  72. outsize = outbuf.remaining();
  73. //  If there is no data to send, stop polling for output.
  74. if (outbuf.remaining() == ) {   //如果确实没有数据要写,那么取消写事件的注册
  75. io_object.reset_pollout (handle);
  76. // when we use custom encoder, we might want to close
  77. if (encoder.is_error()) {
  78. error();
  79. }
  80. return;
  81. }
  82. }
  83. //  If there are any data to write in write buffer, write as much as
  84. //  possible to the socket. Note that amount of data to write can be
  85. //  arbitratily large. However, we assume that underlying TCP layer has
  86. //  limited transmission buffer and thus the actual number of bytes
  87. //  written should be reasonably modest.
  88. int nbytes = write (outbuf);  //写数据
  89. //  IO error has occurred. We stop waiting for output events.
  90. //  The engine is not terminated until we detect input error;
  91. //  this is necessary to prevent losing incomming messages.
  92. if (nbytes == -) {  //如果-1,那么表示底层用到的socket其实已经出现了问题
  93. io_object.reset_pollout (handle);  //取消写事件的注册
  94. if (terminating)
  95. terminate ();
  96. return;
  97. }
  98. outsize -= nbytes;  //这里更新需要写的数据的数量
  99. //  If we are still handshaking and there are no data
  100. //  to send, stop polling for output.
  101. if (handshaking)
  102. if (outsize == )
  103. io_object.reset_pollout (handle);
  104. // when we use custom encoder, we might want to close after sending a response
  105. if (outsize == ) {
  106. if (encoder != null && encoder.is_error ()) {
  107. error();
  108. return;
  109. }
  110. if (terminating)
  111. terminate ();
  112. }
  113. }

这两个方法是用于相应IO事件的,前面提到的IOObject将IO事件其实委托给了内部的handler来处理,其实这个handler对象就是SteamEngine对象,也就是底层的channel有数据可以读写的时候,将会用上面的两个方法来处理。这里就可以看到读写事件最原始的处理流程了,而且也看到了encoder以及decoder的用处。。。这里代码应该还算是比较的简单,由于这部分还涉及到与上层的session对象之间的交互,这个还要等到以后来分析。。。

好了,那么到这里ZeroMQ中IO的处理流程也就算是有了基本的了解了。。。。

ZeroMQ(java)中对IO的封装(StreamEngine)的更多相关文章

  1. ZeroMQ(java)中的数据流SessionBase与SocketBase

    前面的文章中已经比较的清楚了ZeroMQ(java)中如何在底层处理IO, 通过StreamEngine对象来维护SelectableChannel对象以及IO的事件回调,然后通过Poller对象来维 ...

  2. ZeroMQ(JAVA)中的数据流,SessionBase与SocketBase

    前面的文章中已经比较的清楚了ZeroMQ(java)中如何在底层处理IO, 通过StreamEngine对象来维护SelectableChannel对象以及IO的事件回调,然后通过Poller对象来维 ...

  3. java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET

    java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET 亲,“社区之星”已经一周岁了!      社区福利快来领取免费参加MDCC大会机会哦    Tag功能介绍—我们 ...

  4. 深入理解Java中的IO

    深入理解Java中的IO 引言:     对程序语言的设计者来说,创建一个好的输入/输出(I/O)系统是一项艰难的任务 < Thinking in Java >   本文的目录视图如下: ...

  5. Java中的IO流,Input和Output的用法,字节流和字符流的区别

    Java中的IO流:就是内存与设备之间的输入和输出操作就成为IO操作,也就是IO流.内存中的数据持久化到设备上-------->输出(Output).把 硬盘上的数据读取到内存中,这种操作 成为 ...

  6. Java中的IO流(五)

    上一篇<Java中的IO流(四)>记录了一下Properties类,此类不属于IO流,它属于集合框架.接下来说一下IO流中的其它流 一,打印流PrintStream PrintStream ...

  7. Java中的IO流(三)

    上一篇<Java中的IO流(二)>把学习Java的字符流以及转换流作了一下记录,从本篇开始将把IO流中对文件或文件夹操作的对象File类的学习进行一下记录. 一,File类的构造函数及字段 ...

  8. Java中的IO

    引言:     对程序语言的设计者来说,创建一个好的输入/输出(I/O)系统是一项艰难的任务 < Thinking in Java >   本文的目录视图如下:   Java IO概要 a ...

  9. java中的IO操作总结

    一.InputStream重用技巧(利用ByteArrayOutputStream) 对同一个InputStream对象进行使用多次. 比如,客户端从服务器获取数据 ,利用HttpURLConnect ...

  10. java中的IO流

    Java中的IO流 在之前的时候我已经接触过C#中的IO流,也就是说集中数据固化的方式之一,那么我们今天来说一下java中的IO流. 首先,我们学习IO流就是要对文件或目录进行一系列的操作,那么怎样操 ...

随机推荐

  1. [c#基础]堆和栈

    前言 堆与栈对于理解.NET中的内存管理.垃圾回收.错误和异常.调试与日志有很大的帮助.垃圾回收的机制使程序员从复杂的内存管理中解脱出来,虽然绝大多数的C#程序并不需要程序员手动管理内存,但这并不代表 ...

  2. groovyConsole — the Groovy Swing console

    1. Groovy : Groovy Console The Groovy Swing Console allows a user to enter and run Groovy scripts. T ...

  3. iOS运行时 -- Runtime(摘抄自网络)

    运行时(iOS) 一.什么是运行时(Runtime)? 运行时是苹果提供的纯C语言的开发库(运行时是一种非常牛逼.开发中经常用到的底层技术) 二.运行时的作用? 能获得某个类的所有成员变量 能获得某个 ...

  4. if...else语句的应用题

    应用题 namespace ConsoleApplication1 { /* 题目要求:提示用户输入年龄,如果大于等于18,那么用户可以查看.如果小于10岁,则告知用户”少儿不宜“. 如果大于等于10 ...

  5. Sping MVC-创建HelloWeb项目(一)

    下面的例子显示怎样使用Spring MVC框架写一个简单的基于Web的应用程序,使用Eclipse IDE作为开发工具,按照下面的步骤使用Spring Web框架来开发一个动态的Web应用 步骤 描述 ...

  6. 让js的forin循环禁止forin到某个属性的话要怎么做

    //知识点1:for In循环是可以枚举到继承的属性的://知识点2:使用defineProperty让属性无法通过forIn枚举到://知识点3:用definedProperty重新定义一个属性药把 ...

  7. 【转】Eclipse下导入外部jar包的3种方式

    我们在用Eclipse开发程序的时候,经常要用到第三方jar包.引入jar包不是一个小问题,由于jar包位置不清楚,而浪费时间.下面配图说明3种Eclipse引入jar包的方式.   1.最常用的普通 ...

  8. RHCS配置web高可用集群

    基本条件三台主机 10.37.129.5 web1.xzdz.hk web1 10.37.129.6 web2.xzdz.hk web2 10.37.129.4 luci.xzdz.hk luci 其 ...

  9. Java输入、输入、IO流 类层次关系梳理

    本文主要关注在Java编程中涉及到的IO相关的类库.方法.以及对各个层次(抽线.接口继承)的流之间的关系进行梳理 相关学习资料 http://baike.baidu.com/view/1007958. ...

  10. HDU #3333

    Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Problem Descript ...