为了更好的理解Netty异步事件驱动网络通信框架,有必要先了解一点Java NIO原生的通信理论,下面将结合基于TCP的例子程序,含客户端和服务端的源码,实现了Echo流程。

Java NIO的核心概念有三个:ChannelSelectorByteBuffer

而这当中,Channel的比重最大,NIO的功能主要基于Channel来实现,进行业务逻辑操作。Selector主要是IO事件选择器,当一个Channel创建并配置好后,注册到Selector上,与Selector相关的重要概念是SelectionKey,这个上面绑定了IO事件相关的Channel。在获取到Channel后,进行数据的读写操作,Channel的数据读写是不能直接操作数据的,必须基于ByteBuffer进行,然而,Java NIO原生的ByteBuffer操作比较繁琐,要flip和clear操作。

1. 而我们在业务逻辑操作中,用到的channel,主要有ServerSocketChannel,SocketChannel,DataGramChannel。下面,用一个图,来简要的描述下Channel到这三个具体之类之间的继承/实现关系(该图来自网络,若有不妥,请告知,谢谢)。

2. Selector,是事件选择器,创建Selector后,在调用select之前,在注册Channel到这个Selector上时,必须指定关注的事件类型(interestOps)。通过这个类的select函数,可以获取选择上监听到的IO事件。一旦select函数检测到事件,就可以从Selector上获取到具体有哪些IO事件,这些事件通过SelectionKey承载,SelectionKey上标记出该事件的类型,比如是OP_CONNECT,OP_ACCEPT还是OP_READ等。另外,SelectionKey还记录了对应该IO事件发生的Channel,可以通过SelectionKey得到该Channel。

3. ByteBuffer。 因为字节操作,是操作系统与IO设备之间进行通信的基本数据单元,在Java NIO中,各通道Channel之间进行数据通信时,指定必须是基于ByteBuffer的。 ByteBuffer有两个重要的函数,flip和clear。当Channel调用read函数,将数据读到ByteBuffer中后,ByteBuffer的数据长度指针将会移动到数据长度所在的位置,这个位置是小于等于ByteBuffer容量capacity值的。当业务逻辑操作读取到的数据前,需要对ByteBuffer做一下flip操作,就是将limit指针指向当前数据指针position的位置,然后,将position指针指向0的位置。数据逻辑结束后,一般要恢复ByteBuffer,即调用clear函数。

这三个重要的概念,做了一番解释和描述后,就以一个demo程序,基于Java NIO的TCP C/S源码,代码中带有了重要逻辑的注释,后续不再单独解释。

A. TCP Server:

  1. /**
  2. * @author "shihuc"
  3. * @date 2017年3月16日
  4. */
  5. package javaSocket.tcp.server;
  6.  
  7. import java.io.IOException;
  8. import java.net.InetSocketAddress;
  9. import java.nio.ByteBuffer;
  10. import java.nio.channels.SelectionKey;
  11. import java.nio.channels.Selector;
  12. import java.nio.channels.ServerSocketChannel;
  13. import java.nio.channels.SocketChannel;
  14. import java.nio.charset.Charset;
  15. import java.util.Iterator;
  16. import java.util.Set;
  17.  
  18. import javaSocket.tcp.Constants;
  19.  
  20. /**
  21. * @author chengsh05
  22. *
  23. */
  24. public class TcpServer {
  25.  
  26. /**
  27. * @param args
  28. */
  29. public static void main(String[] args) {
  30. try {
  31. startServer(Constants.SERVER_PORT);
  32. } catch (IOException e) {
  33. e.printStackTrace();
  34. }
  35. }
  36.  
  37. public static void startServer(int port) throws IOException{
  38. /*
  39. *开启一个服务channel,
  40. *A selectable channel for stream-oriented listening sockets.
  41. */
  42. ServerSocketChannel serverChannel = ServerSocketChannel.open();
  43. serverChannel.configureBlocking(false);
  44. serverChannel.bind(new InetSocketAddress(port));
  45.  
  46. /*
  47. * 创建一个selector
  48. */
  49. Selector selector = Selector.open();
  50. /*
  51. * 将创建的serverChannel注册到selector选择器上,指定这个channel只关心OP_ACCEPT事件
  52. */
  53. serverChannel.register(selector, SelectionKey.OP_ACCEPT);
  54.  
  55. while (true) {
  56. /*
  57. * select()操作,默认是阻塞模式的,即,当没有accept或者read时间到来时,将一直阻塞不往下面继续执行。
  58. */
  59. int readyChannels = selector.select();
  60. if (readyChannels <= ) {
  61. continue;
  62. }
  63.  
  64. /*
  65. * 从selector上获取到了IO事件,可能是accept,也有可能是read
  66. */
  67. Set<SelectionKey> SelectonKeySet = selector.selectedKeys();
  68. Iterator<SelectionKey> iterator = SelectonKeySet.iterator();
  69.  
  70. /*
  71. * 循环遍历SelectionKeySet中的所有的SelectionKey
  72. */
  73. while (iterator.hasNext()) {
  74. SelectionKey key = iterator.next();
  75. if (key.isAcceptable()) { //处理OP_ACCEPT事件
  76. SocketChannel socketChannel = serverChannel.accept();
  77. socketChannel.configureBlocking(false);
  78. socketChannel.register(selector, SelectionKey.OP_READ);
  79. } else if (key.isReadable()) { //处理OP_READ事件
  80. SocketChannel socketChannel = (SocketChannel) key.channel();
  81. StringBuilder sb = new StringBuilder();
  82. ByteBuffer byteBuffer = ByteBuffer.allocate();
  83.  
  84. int readBytes = ;
  85. int ret = ;
  86. /*
  87. * 注意读数据的时候,ByteBuffer的操作,需要flip,clear进行指针位置的调整
  88. */
  89. while ((ret = socketChannel.read(byteBuffer)) > ) {
  90. readBytes += ret;
  91. byteBuffer.flip();
  92. sb.append(Charset.forName("UTF-8").decode(byteBuffer).toString());
  93. byteBuffer.clear();
  94. }
  95.  
  96. if (readBytes == ) {
  97. System.err.println("handle opposite close Exception");
  98. socketChannel.close();
  99. }
  100.  
  101. String message = sb.toString();
  102. System.out.println("Message from client: " + message);
  103. if (Constants.CLIENT_CLOSE.equalsIgnoreCase(message.toString().trim())) {
  104. System.out.println("Client is going to shutdown!");
  105. socketChannel.close();
  106. } else if (Constants.SERVER_CLOSE.equalsIgnoreCase(message.trim())) {
  107. System.out.println("Server is going to shutdown!");
  108. socketChannel.close();
  109. serverChannel.close();
  110. selector.close();
  111. System.exit();
  112. } else {
  113. String outMessage = "Server response:" + message;
  114. socketChannel.write(Charset.forName("UTF-8").encode(outMessage));
  115. }
  116. }
  117. /*
  118. * 将selector上当前已经监听到的且已经处理了的事件标记清除掉。
  119. */
  120. iterator.remove();
  121. }
  122. }
  123. }
  124. }

B. TCP Client

  1. /**
  2. * @author "shihuc"
  3. * @date 2017年3月16日
  4. */
  5. package javaSocket.tcp.client;
  6.  
  7. import java.io.IOException;
  8. import java.net.InetSocketAddress;
  9. import java.nio.ByteBuffer;
  10. import java.nio.channels.SelectionKey;
  11. import java.nio.channels.Selector;
  12. import java.nio.channels.SocketChannel;
  13. import java.nio.charset.Charset;
  14. import java.util.Scanner;
  15.  
  16. import javaSocket.tcp.Constants;
  17.  
  18. /**
  19. * @author chengsh05
  20. *
  21. */
  22. public class TcpClient {
  23.  
  24. /**
  25. * @param args
  26. */
  27. public static void main(String[] args) {
  28. try {
  29. startClient(Constants.SERVER_IP, Constants.SERVER_PORT);
  30. } catch (IOException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34.  
  35. public static void startClient(String serverIp, int serverPort) throws IOException{
  36. /*
  37. * 创建一个SocketChannel,指定为非阻塞模式
  38. * A selectable channel for stream-oriented connecting sockets.
  39. */
  40. SocketChannel socketChannel = SocketChannel.open();
  41. socketChannel.configureBlocking(false);
  42.  
  43. /*
  44. * 连接到指定的服务地址
  45. */
  46. socketChannel.connect(new InetSocketAddress(serverIp, serverPort));
  47.  
  48. /*
  49. * 创建一个事件选择器Selector
  50. */
  51. Selector selector = Selector.open();
  52.  
  53. /*
  54. * 将创建的SocketChannel注册到指定的Selector上,并指定关注的事件类型为OP_CONNECT
  55. */
  56. socketChannel.register(selector, SelectionKey.OP_CONNECT);
  57.  
  58. /*
  59. * 从系统输入终端读取数据,作为客户端信息输入源
  60. */
  61. Scanner sc = new Scanner(System.in);
  62. String cont = null;
  63. while(true){
  64. if(socketChannel.isConnected()){
  65. cont = sc.nextLine();
  66. socketChannel.write(Charset.forName("UTF-8").encode(cont));
  67. if(cont == null || cont.equalsIgnoreCase(Constants.CLIENT_CLOSE)){
  68. socketChannel.close();
  69. selector.close();
  70. sc.close();
  71. System.out.println("See you, 客户端退出系统了");
  72. System.exit();
  73. }
  74. }
  75. /*
  76. * 设置1sec的超时时间,进行IO事件选择操作
  77. */
  78. int nSelectedKeys = selector.select();
  79. if(nSelectedKeys > ){
  80. for(SelectionKey skey: selector.selectedKeys()){
  81. /*
  82. * 判断检测到的channel是不是可连接的,将对应的channel注册到选择器上,指定关心的事件类型为OP_READ
  83. */
  84. if(skey.isConnectable()){
  85. SocketChannel connChannel = (SocketChannel) skey.channel();
  86. connChannel.configureBlocking(false);
  87. connChannel.register(selector, SelectionKey.OP_READ);
  88. connChannel.finishConnect();
  89. }
  90. /*
  91. * 若检测到的IO事件是读事件,则处理相关数据的读相关的业务逻辑
  92. */
  93. else if(skey.isReadable()){
  94. SocketChannel readChannel = (SocketChannel) skey.channel();
  95. StringBuilder sb = new StringBuilder();
  96. /*
  97. * 定义一个ByteBuffer的容器,容量为1k
  98. */
  99. ByteBuffer byteBuffer = ByteBuffer.allocate();
  100.  
  101. int readBytes = ;
  102. int ret = ;
  103. /*
  104. * 注意,对ByteBuffer的操作,需要关心的是flip,clear等。
  105. */
  106. while ((ret = readChannel.read(byteBuffer)) > ) {
  107. readBytes += ret;
  108. byteBuffer.flip();
  109. sb.append(Charset.forName("UTF-8").decode(byteBuffer).toString());
  110. byteBuffer.clear();
  111. }
  112.  
  113. if (readBytes == ) {
  114. System.err.println("handle opposite close Exception");
  115. readChannel.close();
  116. }
  117. }
  118. }
  119. /*
  120. * 一次监听的事件处理完毕后,需要将已经记录的事件清除掉,准备下一轮的事件标记
  121. */
  122. selector.selectedKeys().clear();
  123. }else{
  124. System.err.println("handle select timeout Exception");
  125. socketChannel.close();
  126. }
  127. }
  128. }
  129. }

阅读上述代码时,请注意,server和client的实现风格不太一样,主要是针对SelectionKeySet的遍历,一次select操作获取到的所有的SelectionKey处理完后的扫尾工作,体现出Selector的工作逻辑,若写过C程序实现过TCP server/client程序,对事件选择的过程应该就更清楚了。

最后,总结一下Java NIO TCP协议下的C/S结构程序流程图,为彻底理解Java NIO服务。

基于这个例子引出的Java NIO的逻辑过程和思想,再去研读Netty的代码,相信会容易理解Netty的核心reactor模型工作原理。

Java NIO通信的基础,基于TCP C/S例子介绍的更多相关文章

  1. Java NIO通信框架在电信领域的实践

    [http://www.codeceo.com/article/java-nio-communication.html]   华为电信软件技术架构演进 Java NIO框架在技术变迁中起到的关键作用 ...

  2. JAVA Socket 底层是怎样基于TCP/IP 实现的???

    首先必须明确:TCP/IP模型中有四层结构:       应用层(Application Layer).传输层(Transport  Layer).网络层(Internet Layer  ).链路层( ...

  3. Java NIO 网络编程基础

    Java NIO提供了一套网络api,可以用来处理连接数很多的情况.他的基本思想就是用一个线程来处理多个channel. 123456789101112131415161718192021222324 ...

  4. java NIO socket 通信实例

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/zhuyijian135757/article/details/37672151 java Nio 通 ...

  5. python中基于tcp协议的通信(数据传输)

    tcp协议:流式协议(以数据流的形式通信传输).安全协议(收发信息都需收到确认信息才能完成收发,是一种双向通道的通信) tcp协议在OSI七层协议中属于传输层,它上承用户层的数据收发,下启网络层.数据 ...

  6. 海纳百川而来的一篇相当全面的Java NIO教程

    目录 零.NIO包 一.Java NIO Channel通道 Channel的实现(Channel Implementations) Channel的基础示例(Basic Channel Exampl ...

  7. Java NIO的理解和应用

    Java NIO是一种基于通道和缓冲区的I/O方式,已经被广泛的应用,成为解决高并发与大量连接和I/O处理问题的有效方式. Java NIO相关组件 Java NIO主要有三个核心部分组成,分别是:C ...

  8. Java NIO (转)

    Java NIO提供了与标准IO不同的IO工作方式: Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(B ...

  9. 【转】java NIO 相关知识

    原文地址:http://www.iteye.com/magazines/132-Java-NIO Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的 ...

随机推荐

  1. binarysearchtree

    public class binarytree<Value> { private Node root = null; private class Node{ private Value v ...

  2. HDU2717-Catch That Cow (BFS入门)

    题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=2717 Catch That Cow Time Limit: 5000/2000 MS (Java/O ...

  3. 2019 flag

    学习 1.学会一种新的编程语言或脚本语言,并编写不少于十个应用 2.读5-8本其他学科书籍,(经济,心里学等) 3.坚持每个月最少更新8-10篇博客(技术,学习) 4.阅读并理解和应用两个开源lib ...

  4. 2.21 JS处理滚动条

    2.21 JS处理滚动条 前言    selenium并不是万能的,有时候页面上操作无法实现的,这时候就需要借助JS来完成了.常见场景:当页面上的元素超过一屏后,想操作屏幕下方的元素,是不能直接定位到 ...

  5. 又一个opengl教程,多多益善

    http://ogldev.atspace.co.uk/index.html http://wiki.jikexueyuan.com/project/modern-opengl-tutorial/tu ...

  6. VMware网络连接IP设置

    网络配置(仅主机模式) 一.改变虚拟机IP地址达到联网目的 仅主机模式,第一步,打开我的电脑属性,查看VMt1网卡IP设置,设置一个区段:192.168.xx.aa       xx.aa自由设置,简 ...

  7. 举例说明MySQL中的事务

    一.场景导入 现在有一张仓库表,仓库表中记录了每一个物品的数量,还有一张用户表,用户购买产品,仓库表的产品数量减少,而用户拥有产品的数量增加. 但是如果仓库中的产品数量不足时怎么处理? 例子: #仓库 ...

  8. MySQL最基本的DML语句

    一.什么叫DML? DML(Data Manipulation Language):数据操作语言.主要操作数据表中的数据,使用DML可以完成以后三件事: 插入数据 修改数据 查询数据 二.具体的语句操 ...

  9. js知识点: 数组

    1.行内元素  margin  padding 左右值都有效,上下值都无效 2.var ev = ev || window.event document.documentElement.clientW ...

  10. 20155219 2016-2017-2 《Java程序设计》第7周学习总结

    20155219 2016-2017-2 <Java程序设计>第7周学习总结 教材学习内容总结 认识时间与日期 时间的度量 1.格林威治时间(GMT):通过观察太阳而得,因为地球公转轨道为 ...