文章首发:https://www.cmsblogs.com/article/1435620402348036096


Netty 是基于Java NIO 封装的网络通讯框架,只有充分理解了 Java NIO 才能理解好Netty的底层设计。Java NIO 由三个核心组件组件:

  • Buffer
  • Channel
  • Selector

缓冲区 Buffer

Buffer 是一个数据对象,我们可以把它理解为固定数量的数据的容器,它包含一些要写入或者读出的数据。

在 Java NIO 中,任何时候访问 NIO 中的数据,都需要通过缓冲区(Buffer)进行操作。读取数据时,直接从缓冲区中读取,写入数据时,写入至缓冲区。NIO 最常用的缓冲区则是 ByteBuffer。下图是 Buffer 继承关系图:

每一个 Java 基本类型都对应着一种 Buffer,他们都包含这相同的操作,只不过是所处理的数据类型不同而已。

通道 Channel

Channel 是一个通道,它就像自来水管一样,网络数据通过 Channel 这根水管读取和写入。传统的 IO 是基于流进行操作的,Channle 和类似,但又有些不同:

区别 通过Channel
支持异步 不支持 支持
是否可双向传输数据 不能,只能单向 可以,既可以从通道读取数据,也可以向通道写入数据
是否结合 Buffer 使用 必须结合 Buffer 使用
性能 较低 较高

正如上面说到的,Channel 必须要配合 Buffer 一起使用,我们永远不可能将数据直接写入到 Channel 中,同样也不可能直接从 Channel 中读取数据。都是通过从 Channel 读取数据到 Buffer 中或者从 Buffer 写入数据到 Channel 中,如下:

简单点说,Channel 是数据的源头或者数据的目的地,用于向 buffer 提供数据或者读取 buffer 数据,并且对 I/O 提供异步支持。

下图是 Channel 的类图

Channel 为最顶层接口,所有子 Channel 都实现了该接口,它主要用于 I/O 操作的连接。定义如下:

public interface Channel extends Closeable {/*** 判断此通道是否处于打开状态。 */public boolean isOpen();/***关闭此通道。*/public void close() throws IOException;
}

最为重要的Channel实现类为:

  • FileChannel:一个用来写、读、映射和操作文件的通道

  • DatagramChannel:能通过 UDP 读写网络中的数据

  • SocketChannel: 能通过 TCP 读写网络中的数据

  • ServerSocketChannel:可以监听新进来的 TCP 连接,像 Web 服务器那样。对每一个新进来的连接都会创建一个 SocketChannel

多路复用器 Selector

多路复用器 Selector,它是 Java NIO 编程的基础,它提供了选择已经就绪的任务的能力。从底层来看,Selector 提供了询问通道是否已经准备好执行每个 I/O 操作的能力。简单来讲,Selector 会不断地轮询注册在其上的 Channel,如果某个 Channel 上面发生了读或者写事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以获取就绪 Channel 的集合,进行后续的 I/O 操作。

Selector 允许一个线程处理多个 Channel ,也就是说只要一个线程复杂 Selector 的轮询,就可以处理成千上万个 Channel ,相比于多线程来处理势必会减少线程的上下文切换问题。下图是一个 Selector 连接三个 Channel :

实例

服务端

public class NIOServer {/*接受数据缓冲区*/private ByteBuffer sendbuffer = ByteBuffer.allocate(1024);/*发送数据缓冲区*/private  ByteBuffer receivebuffer = ByteBuffer.allocate(1024);private Selector selector;public NIOServer(int port) throws IOException {// 打开服务器套接字通道ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();// 服务器配置为非阻塞serverSocketChannel.configureBlocking(false);// 检索与此通道关联的服务器套接字ServerSocket serverSocket = serverSocketChannel.socket();// 进行服务的绑定serverSocket.bind(new InetSocketAddress(port));// 通过open()方法找到Selectorselector = Selector.open();// 注册到selector,等待连接serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("Server Start----:");}private void listen() throws IOException {while (true) {selector.select();Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();while (iterator.hasNext()) {SelectionKey selectionKey = iterator.next();iterator.remove();handleKey(selectionKey);}}}private void handleKey(SelectionKey selectionKey) throws IOException {// 接受请求ServerSocketChannel server = null;SocketChannel client = null;String receiveText;String sendText;int count=0;// 测试此键的通道是否已准备好接受新的套接字连接。if (selectionKey.isAcceptable()) {// 返回为之创建此键的通道。server = (ServerSocketChannel) selectionKey.channel();// 接受到此通道套接字的连接。// 此方法返回的套接字通道(如果有)将处于阻塞模式。client = server.accept();// 配置为非阻塞client.configureBlocking(false);// 注册到selector,等待连接client.register(selector, SelectionKey.OP_READ);} else if (selectionKey.isReadable()) {// 返回为之创建此键的通道。client = (SocketChannel) selectionKey.channel();//将缓冲区清空以备下次读取receivebuffer.clear();//读取服务器发送来的数据到缓冲区中count = client.read(receivebuffer);if (count > 0) {receiveText = new String( receivebuffer.array(),0,count);System.out.println("服务器端接受客户端数据--:"+receiveText);client.register(selector, SelectionKey.OP_WRITE);}} else if (selectionKey.isWritable()) {//将缓冲区清空以备下次写入sendbuffer.clear();// 返回为之创建此键的通道。client = (SocketChannel) selectionKey.channel();sendText="message from server--";//向缓冲区中输入数据sendbuffer.put(sendText.getBytes());//将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位sendbuffer.flip();//输出到通道client.write(sendbuffer);System.out.println("服务器端向客户端发送数据--:"+sendText);client.register(selector, SelectionKey.OP_READ);}}public static void main(String[] args) throws IOException {int port = 8080;NIOServer server = new NIOServer(port);server.listen();}}

客户端

public class NIOClient {/*接受数据缓冲区*/private static ByteBuffer sendbuffer = ByteBuffer.allocate(1024);/*发送数据缓冲区*/private static ByteBuffer receivebuffer = ByteBuffer.allocate(1024);public static void main(String[] args) throws IOException {// 打开socket通道SocketChannel socketChannel = SocketChannel.open();// 设置为非阻塞方式socketChannel.configureBlocking(false);// 打开选择器Selector selector = Selector.open();// 注册连接服务端socket动作socketChannel.register(selector, SelectionKey.OP_CONNECT);// 连接socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));Set<SelectionKey> selectionKeys;Iterator<SelectionKey> iterator;SelectionKey selectionKey;SocketChannel client;String receiveText;String sendText;int count=0;while (true) {//选择一组键,其相应的通道已为 I/O 操作准备就绪。//此方法执行处于阻塞模式的选择操作。selector.select();//返回此选择器的已选择键集。selectionKeys = selector.selectedKeys();//System.out.println(selectionKeys.size());iterator = selectionKeys.iterator();while (iterator.hasNext()) {selectionKey = iterator.next();if (selectionKey.isConnectable()) {System.out.println("client connect");client = (SocketChannel) selectionKey.channel();// 判断此通道上是否正在进行连接操作。// 完成套接字通道的连接过程。if (client.isConnectionPending()) {client.finishConnect();System.out.println("完成连接!");sendbuffer.clear();sendbuffer.put("Hello,Server".getBytes());sendbuffer.flip();client.write(sendbuffer);}client.register(selector, SelectionKey.OP_READ);} else if (selectionKey.isReadable()) {client = (SocketChannel) selectionKey.channel();//将缓冲区清空以备下次读取receivebuffer.clear();//读取服务器发送来的数据到缓冲区中count=client.read(receivebuffer);if(count>0){receiveText = new String( receivebuffer.array(),0,count);System.out.println("客户端接受服务器端数据--:"+receiveText);client.register(selector, SelectionKey.OP_WRITE);}} else if (selectionKey.isWritable()) {sendbuffer.clear();client = (SocketChannel) selectionKey.channel();sendText = "message from client--";sendbuffer.put(sendText.getBytes());//将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位sendbuffer.flip();client.write(sendbuffer);System.out.println("客户端向服务器端发送数据--:"+sendText);client.register(selector, SelectionKey.OP_READ);}}selectionKeys.clear();}}
}

运行结果

『chenssy』,江湖人称大明哥,专注于【死磕 Java】系列文章创作。
【死磕 Java】系列是大明哥精心打造Java 进阶类系列文章,深入分析 Java 技术核心原理,从源码层次阐述 Java 相关技术。目前系列包括:
1.【死磕 Java 并发】:https://www.cmsblogs.com/category/1391296887813967872:(已完成)
2.【死磕 Spring 之 IOC】:https://www.cmsblogs.com/category/1391374860344758272:(已完成)
3.【死磕 Redis】:https://www.cmsblogs.com/category/1391389927996002304:(已完成)
4.【死磕 Java 基础】:https://www.cmsblogs.com/category/1411518540095295488
5.【死磕 NIO】:https://www.cmsblogs.com/article/1435620402348036096

【死磕NIO】— NIO基础详解相关推荐

  1. BIOS设置基础详解

    BIOS设置基础详解AMI BIOS设置 开机显卡自检测完成后,点击<DEL键>即可进入AMI BIOS SETUP设置界面主菜单. 进入了AMI BIOS NEW SETUP UTILI ...

  2. c 语言中 %是什么运算符,C 语言基础----详解C中的运算符

    C语言中又有哪些运算符呢? 如下所示: ※ 算术运算符 ※ 赋值运算符 ※ 关系运算符 ※ 逻辑运算符 ※ 三目运算符 C语言基本算术运算符如下表: 除法运算中注意: 如果相除的两个数都是整数的话,则 ...

  3. Python学习二:词典基础详解

    作者:NiceCui 本文谢绝转载,如需转载需征得作者本人同意,谢谢. 本文链接:http://www.cnblogs.com/NiceCui/p/7862377.html 邮箱:moyi@moyib ...

  4. 【云原生之k8s】k8s基础详解

    [云原生之k8s]k8s基础详解 前言 一.kubernetes介绍 (1)kubernetes简介 (2)应用部署方式的演变 二.kubernetes组件 (1)kubernetes架构 (2)ma ...

  5. 微信小程序详解 php,微信小程序canvas基础详解

    canvas 元素用于在网页上绘制图形.HTML5 的 canvas 元素使用 JavaScript 在网页上绘制2D图像.本文主要和大家分享微信小程序canvas基础详解,希望能帮助到大家. 一.了 ...

  6. 线程状态,优先级,守护线程基础详解

    线程状态,优先级,守护线程基础详解 线程状态 停止线程 线程休眠 线程礼让 线程强制执行 线程状态检测 线程的优先级 守护线程 线程同步 线程状态 创建状态(new 之后就是创建状态 就绪状态(调用s ...

  7. 03 html基础详解

    02html基础详解 文章目录 02html基础详解 1.HTML编辑器 2.标签 html常用标签 3.元素 4.属性 常用属性 5.标题 水平线 注释 6.段落 折行 7.格式化标签 属性dir ...

  8. 视频教程-FPS游戏逆向与安全+UE4引擎基础详解-其他

    FPS游戏逆向与安全+UE4引擎基础详解 想把自己的知识传播出去,让更多人学习到 苏瑞兵 ¥188.00 立即订阅 扫码下载「CSDN程序员学院APP」,1000+技术好课免费看 APP订阅课程,领取 ...

  9. linux网络服务详解,Linux网络服务器配置基础详解 (3)

    Linux网络服务器配置基础详解 (3) Linux网络服务器配置基础详解 (3) 第三步:编辑"inetd.conf"文件(vi /etc/inetd.conf),禁止所有不需要 ...

  10. 主线剧情03-NXP-i.MX系列的u-boot移植基础详解

    u-boot 移植基础详解 本文系广泛撷取.借鉴和整理(相关的内容在网络上有很多,但很多相互抄,或者是版本太老,或者就是不通用的非常有平台针对性的步骤,碎片化泛滥,甚至就是有待分拣的垃圾厂,当然也有一 ...

最新文章

  1. ArrayList源码学习
  2. c和python哪个好学-C/C++和Python哪个更有前景?
  3. 用DELPHI的RTTI实现数据集的简单对象化
  4. 技嘉主板GA-B85M-D3V PLUS 1150组装问题汇总
  5. PDFlib免费下载地址及详细介绍手册
  6. 通过Python实现马尔科夫链蒙特卡罗方法的入门级应用
  7. 计算机视觉中的多视图几何_基于深度学习的视觉三维重建研究总结
  8. 轮播图高度自适应_干货!弘成教你写轮播图全自动适应封装代码
  9. excel 自动生成目录
  10. 逆火软件测试工资,世界级人体工学设计:HyperX Pulsefire FPS逆火鼠标评测
  11. 小红书心灵捕手招募令,百亿流量扶持优质情感主播!
  12. 苹果11蓝牙配对不成功怎么办_iphone11蓝牙搜不到设备怎么办
  13. [Windows10]Win10如何获取最高管理员权限
  14. Android开发——如何设计开发一款Android App
  15. 学习笔记(1):深蓝解读区块链技术-五大要素
  16. 【演示文稿制作软件】Focusky教程 | 设置动画效果
  17. 如何在ppt中生成柱状图_在ppt中做柱状图的方法图解步骤
  18. python/folium绘制中国人口数量热力图(HeatMap)
  19. 大学生学完Python靠几个接单网站兼职,实现经济独立,这不香吗?
  20. CSS line-height 和 vertical-align 精解

热门文章

  1. OpenSSL - 学习/实践
  2. 条码打印软件批量制作的条码如何固定尺寸不变
  3. 零基础Matlab Note11--三维图像
  4. Android快速实现扫描二维码功能
  5. 【python数据分析实战】国产烂片深度揭秘(5)—— 不同导演每年的电影产量如何?
  6. JavaScript基础(3)函数
  7. Cadence Allegro 17.4学习记录开始19-PCB Editor 17.4软件测量距离和查询操作
  8. 1. 获取两个字符串中最大相同子串
  9. 23种设计模式之外观模式
  10. 软件设计模式及体系结构之外观模式