【死磕NIO】— NIO基础详解
文章首发: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基础详解相关推荐
- BIOS设置基础详解
BIOS设置基础详解AMI BIOS设置 开机显卡自检测完成后,点击<DEL键>即可进入AMI BIOS SETUP设置界面主菜单. 进入了AMI BIOS NEW SETUP UTILI ...
- c 语言中 %是什么运算符,C 语言基础----详解C中的运算符
C语言中又有哪些运算符呢? 如下所示: ※ 算术运算符 ※ 赋值运算符 ※ 关系运算符 ※ 逻辑运算符 ※ 三目运算符 C语言基本算术运算符如下表: 除法运算中注意: 如果相除的两个数都是整数的话,则 ...
- Python学习二:词典基础详解
作者:NiceCui 本文谢绝转载,如需转载需征得作者本人同意,谢谢. 本文链接:http://www.cnblogs.com/NiceCui/p/7862377.html 邮箱:moyi@moyib ...
- 【云原生之k8s】k8s基础详解
[云原生之k8s]k8s基础详解 前言 一.kubernetes介绍 (1)kubernetes简介 (2)应用部署方式的演变 二.kubernetes组件 (1)kubernetes架构 (2)ma ...
- 微信小程序详解 php,微信小程序canvas基础详解
canvas 元素用于在网页上绘制图形.HTML5 的 canvas 元素使用 JavaScript 在网页上绘制2D图像.本文主要和大家分享微信小程序canvas基础详解,希望能帮助到大家. 一.了 ...
- 线程状态,优先级,守护线程基础详解
线程状态,优先级,守护线程基础详解 线程状态 停止线程 线程休眠 线程礼让 线程强制执行 线程状态检测 线程的优先级 守护线程 线程同步 线程状态 创建状态(new 之后就是创建状态 就绪状态(调用s ...
- 03 html基础详解
02html基础详解 文章目录 02html基础详解 1.HTML编辑器 2.标签 html常用标签 3.元素 4.属性 常用属性 5.标题 水平线 注释 6.段落 折行 7.格式化标签 属性dir ...
- 视频教程-FPS游戏逆向与安全+UE4引擎基础详解-其他
FPS游戏逆向与安全+UE4引擎基础详解 想把自己的知识传播出去,让更多人学习到 苏瑞兵 ¥188.00 立即订阅 扫码下载「CSDN程序员学院APP」,1000+技术好课免费看 APP订阅课程,领取 ...
- linux网络服务详解,Linux网络服务器配置基础详解 (3)
Linux网络服务器配置基础详解 (3) Linux网络服务器配置基础详解 (3) 第三步:编辑"inetd.conf"文件(vi /etc/inetd.conf),禁止所有不需要 ...
- 主线剧情03-NXP-i.MX系列的u-boot移植基础详解
u-boot 移植基础详解 本文系广泛撷取.借鉴和整理(相关的内容在网络上有很多,但很多相互抄,或者是版本太老,或者就是不通用的非常有平台针对性的步骤,碎片化泛滥,甚至就是有待分拣的垃圾厂,当然也有一 ...
最新文章
- ArrayList源码学习
- c和python哪个好学-C/C++和Python哪个更有前景?
- 用DELPHI的RTTI实现数据集的简单对象化
- 技嘉主板GA-B85M-D3V PLUS 1150组装问题汇总
- PDFlib免费下载地址及详细介绍手册
- 通过Python实现马尔科夫链蒙特卡罗方法的入门级应用
- 计算机视觉中的多视图几何_基于深度学习的视觉三维重建研究总结
- 轮播图高度自适应_干货!弘成教你写轮播图全自动适应封装代码
- excel 自动生成目录
- 逆火软件测试工资,世界级人体工学设计:HyperX Pulsefire FPS逆火鼠标评测
- 小红书心灵捕手招募令,百亿流量扶持优质情感主播!
- 苹果11蓝牙配对不成功怎么办_iphone11蓝牙搜不到设备怎么办
- [Windows10]Win10如何获取最高管理员权限
- Android开发——如何设计开发一款Android App
- 学习笔记(1):深蓝解读区块链技术-五大要素
- 【演示文稿制作软件】Focusky教程 | 设置动画效果
- 如何在ppt中生成柱状图_在ppt中做柱状图的方法图解步骤
- python/folium绘制中国人口数量热力图(HeatMap)
- 大学生学完Python靠几个接单网站兼职,实现经济独立,这不香吗?
- CSS line-height 和 vertical-align 精解