【死磕NIO】— NIO基础详解
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()方法找到Selector
selector = 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();
}
}
}
运行结果


【死磕NIO】— NIO基础详解的更多相关文章
- tomcat启动nio,apr详解以及配置
tomcat启动nio,apr详解以及配置 前言 在正文开始之前,我们先在idea工具中看看启动的信息,顺便看下启动的基本信息 在这里插入图片描述可以看到信息有tomcat版本操作系统版本java版本 ...
- Dom探索之基础详解
认识DOM DOM级别 注::DOM 0级标准实际并不存在,只是历史坐标系的一个参照点而已,具体的说,它指IE4.0和Netscape Navigator4.0最初支持的DHTML. 节点类型 注:1 ...
- Android中Canvas绘图基础详解(附源码下载) (转)
Android中Canvas绘图基础详解(附源码下载) 原文链接 http://blog.csdn.net/iispring/article/details/49770651 AndroidCa ...
- javaScript基础详解(1)
javaScript基础详解 首先讲javaScript的摆放位置:<script> 与 </script> 可以放在head和body之间,也可以body中或者head中 J ...
- Python学习一:序列基础详解
作者:NiceCui 本文谢绝转载,如需转载需征得作者本人同意,谢谢. 本文链接:http://www.cnblogs.com/NiceCui/p/7858473.html 邮箱:moyi@moyib ...
- Python学习二:词典基础详解
作者:NiceCui 本文谢绝转载,如需转载需征得作者本人同意,谢谢. 本文链接:http://www.cnblogs.com/NiceCui/p/7862377.html 邮箱:moyi@moyib ...
- 深入浅出DOM基础——《DOM探索之基础详解篇》学习笔记
来源于:https://github.com/jawil/blog/issues/9 之前通过深入学习DOM的相关知识,看了慕课网DOM探索之基础详解篇这个视频(在最近看第三遍的时候,准备记录一点东西 ...
- 三剑客基础详解(grep、sed、awk)
目录 三剑客基础详解 三剑客之grep详解 1.通配符 2.基础正则 3.grep 讲解 4.拓展正则 5.POSIX字符类 三剑客之sed讲解 1.sed的执行流程 2.语法格式 三剑客之Awk 1 ...
- java继承基础详解
java继承基础详解 继承是一种由已存在的类型创建一个或多个子类的机制,即在现有类的基础上构建子类. 在java中使用关键字extends表示继承关系. 基本语法结构: 访问控制符 class 子类名 ...
- java封装基础详解
java封装基础详解 java的封装性即是信息隐藏,把对象的属性和行为结合成一个相同的独立单体,并尽可能地隐藏对象的内部细节. 封装的特性是对属性来讲的. 封装的目标就是要实现软件部件的"高 ...
随机推荐
- Mybatis学习笔记-分页
为何要分页 减少数据处理量 便于前端展示数据 使用Limit分页 语法结构 SELECT * FROM user LIMIT startIndex,pageSize; SELECT * FROM us ...
- [解决方案]docker: Error response from daemon: OCI runtime create failed
错误原因 在新服务器上安装好docker后,发现无法运行,经常一顿搜索后,发现是docker安装的版本过高,最新版本docker-18.06 的核心好像没有经过充分的测试就发布了. 导致一运行,就提示 ...
- CC攻击和C2的区别
[一]背景 今天被旁边姐姐问C2.CC是什么,虽然平时老看到这个词,身边也有自己写C2工具的大佬.但好像突然被问到有点懵,不知道怎么回答. [二]内容 CC ( Challenge Collapsar ...
- 2020年!最全Android大厂面试真题合集(附答案)
这份Android面试真题涵盖了图片,网络和安全机制,网络,数据库,插件化.模块化.组件化.热修复.增量更新.Gradle,架构设计和设计模式,Android Framework .Android优秀 ...
- 线程创建的三种方法:继承Thread类,实现Runnable接口,实现Callable接口
线程创建 三种创建方式 1. 继承Thread类 自定义线程类继承Thread类 重写run()方法,编写线程执行体 创建线程对象,调用start()方法启动线程 线程不一定执行,CPU按排调度 pa ...
- 关于Tomcat服务器的笔记
javaWEB的概念: a)什么是 JavaWeb: JavaWeb 是指,所有通过 Java 语言编写可以通过浏览器访问的程序的总称,叫 JavaWeb. JavaWeb 是 ...
- 你认为的.NET数据库连接池,真的是全部吗?
一般我们的项目中会使用1到2个数据库连接配置,同程艺龙的数据库连接配置被收拢到统一的配置中心,由DBA统一配置和维护,业务方通过某个字符串配置拿到的是Connection对象. DBA能在对业务方无侵 ...
- Linux搭建Snmp服务
1:安装snmp yum install net-snmp net-snmp-devel net-snmp-libs net-snmp-utils php-snmp 上面的程序首先会校验需要升级的文件 ...
- 使用SuperSocket实现自定义协议C/S设计
一.简介: 21世纪是出于互联网+的时代,许多传统行业和硬件挂钩的产业也逐步转向了系统集成智能化,简单来说就是需要软硬件的结合.这时,软硬件通讯便是这里面最主要的技术点,我们需要做到的是让硬件能够听懂 ...
- rocketmq知识点
消息队列mq 参考资料:https://www.jianshu.com/p/824066d70da8 一.消息中间件的主要作用和功能: 1)异步解耦和分流: 2)挡住前端的数据洪峰,保证后端系统的稳定 ...