希望看完没篇文章后,会 哦~~ 原来就个这呀~~~

1. java NIO
这里强调一点,netty 仅是在NIO 的基础上进行了优化,简化了API 的使用,解决了一些bug ,并没有从0到1的开发NIO; so , 一些NIO 的核心特性,我们还是要学习原生的;
javaNIO 有几个核心的对象需要掌握: 缓冲区(Buffer)、选择器(Selector)、通道(channel)
1.1 Buffer
在NIO 库中,所有数据都是用缓冲区处理的。
在读数据时,它是直接读到缓冲区;
在写数据时,它也是写入到缓冲区中;
在面向流I/O 系统中,所有数据都是直接写入或者将数据读取到Stream 对象中。
所有的缓冲区类型都继承于抽象类Buffer, 最常用的就是ByteBuffer ; 当然 java 基本数据类型都有对应的Buffer ;

基本操作代码
public class BufferDemo {
//put/get
public static void main(String args[]) throws Exception {
//这用用的是文件IO处理
String path = BufferDemo.class.getResource("/application.properties").getPath();
FileInputStream fin = new FileInputStream(path);
//创建文件的操作管道
FileChannel fc = fin.getChannel();
//分配一个10个大小缓冲区,说白了就是分配一个10个大小的byte数组
ByteBuffer buffer = ByteBuffer.allocate(10);
output("初始化", buffer);
//先读一下
fc.read(buffer);
output("调用read()", buffer);
//准备操作之前,先锁定操作范围
buffer.flip();
output("调用flip()", buffer);
System.out.println("===============================================");
//判断有没有可读数据
while (buffer.remaining() > 0) {
byte b = buffer.get();
System.out.print(((char)b + " "));
// System.out.println(".......");
output("调用get()", buffer);
}
System.out.println("===============================================");
output("调用get()", buffer);
//可以理解为解锁
buffer.clear();
output("调用clear()", buffer);
//最后把管道关闭
fin.close();
}
//把这个缓冲里面实时状态给答应出来
public static void output(String step, ByteBuffer buffer) {
System.out.println(step + " : ");
//容量,数组大小
System.out.print("capacity: " + buffer.capacity() + ", ");
//当前操作数据所在的位置,也可以叫做游标
System.out.print("position: " + buffer.position() + ", ");
//锁定值,flip,数据操作范围索引只能在position - limit 之间
System.out.println("limit: " + buffer.limit());
System.out.println();
}
}
复制代码
运行结果:
初始化 :
capacity: 10, position: 0, limit: 10
调用read() :
capacity: 10, position: 5, limit: 10
调用flip() :
capacity: 10, position: 0, limit: 5
===============================================
h 调用get() :
capacity: 10, position: 1, limit: 5
e 调用get() :
capacity: 10, position: 2, limit: 5
l 调用get() :
capacity: 10, position: 3, limit: 5
l 调用get() :
capacity: 10, position: 4, limit: 5
o 调用get() :
capacity: 10, position: 5, limit: 5
===============================================
调用get() :
capacity: 10, position: 5, limit: 5
调用clear() :
capacity: 10, position: 0, limit: 10
复制代码
核心对象解释
- capactity: 指定缓冲区的最大数据容量,即底层数组的大小;
- position : 指下一个要被
读取或者写入的位置;
- limit : 指还有多少数据可以被
读取或者写入的位置;
位置关系: 0 <= position <= limit <= capacity

重点方法
// 反转这个缓冲区,开始读取操作;
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
复制代码
1.2 选择器Selector
NIO 中非阻塞I/O 采用基于Reactor 模式的工作方式,I/O 调用不会被阻塞,采用的是将感兴趣的特定I/O 事件注册到Selector ; Selector 为操作系统底层提供的函数; 当事件发生时,Selector 可以告诉我们所发生的事件;
如下图:

当有读或者写 等任何注册时间发生时,可以从Selector 中获得相应的SelectionKey, 同时从SelectionKey 中可以找到与该事件发生关联的SelectableChannel ,以获取客户端发送过来的数据;
使用 NIO 中非阻塞 I/O 编写服务器处理程序,大体上可以分为下面三个步骤:
- 向Selector 对象注册感兴趣的事件;
- 从Selector 中获取感兴趣的事件;
- 根据不同的事件进行相应的处理;
向Selector对象注册感兴趣的事件,示例代码如下:
/*
* 注册事件
*/
private Selector getSelector() throws IOException {
// 创建可选择通道,并配置为非阻塞模式
ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(false);
// 绑定通道到指定端口
ServerSocket socket = server.socket();
InetSocketAddress address = new InetSocketAddress(port);
socket.bind(address);
// 创建 Selector 对象
Selector sel = Selector.open();
// 向 Selector 中注册感兴趣的事件
server.register(sel, SelectionKey.OP_ACCEPT);
return sel;
}
复制代码
从Selector 中获取感兴趣的事件,即开始监听,进入内部循环:
/*
* 开始监听
*/
public void listen() {
System.out.println("listen on " + port);
try {
while(true) {
// 该调用会阻塞,直到至少有一个事件发生
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iter = keys.iterator();
while (iter.hasNext()) {
SelectionKey key = (SelectionKey) iter.next();
iter.remove();
// 处理相应的事件
process(key);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
复制代码
在非阻塞I/O中,内部循环都是遵循这个方式;
首先调用select() 方法,该方法是阻塞的,直到有事件发生;
然后再使用selectKey() 方法获取发生事件的 SelectionKey, 在使用迭代器进行循环;
最后就是根据不同的事件,编写相应的处理逻辑;
/*
* 根据不同的事件做处理
*/
private void process(SelectionKey key) throws IOException{
// 接收请求
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel channel = server.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
}
// 读信息
else if (key.isReadable()) {
// TODO
}
// 写事件
else if (key.isWritable()) {
// TODO
}
}
复制代码
此处分别判断是接收请求、读数据 还是 写事件,分别作不同的处理;java1.4 中推出 NIO , 这是一个面向 块的I/O系统,系统以块的方式处理数据;
1.3 通道Channel
通道是一个对象,通过它可以读取和写入数据,当然了所有数据都通过Buffer对象来处理。
- 我们永远不会将字节直接写入通道中,而是将数据写入包含一个或者多个字节的缓冲区;
- 也同样不会从通道中读取数据,而是将数据读入缓冲区,再从缓冲区读取这个数据;
在NIO 中提供了多种通道对象,而所有的通道对象都实现了Channel 接口,它们之间的继承关系如下:

使用NIO读取数据
- 从FileInputStream 获取Channel;
- 创建Buffer;
- 将数据从Channel 读取到Buffer 中;
下面是一个简单读取、写入示例:
读取:
public class DirectBuffer {
static public void main(String args[]) throws Exception {
//首先我们从磁盘上读取刚才我们写出的文件内容
String filePath = DirectBuffer.class.getResource("/application.properties").getFile();
FileInputStream fin = new FileInputStream(filePath);
FileChannel fcin = fin.getChannel();
//把刚刚读取的内容写入到一个新的文件中
String outfile = filePath.replace("application.properties","application-copy.properties");
FileOutputStream fout = new FileOutputStream(outfile);
FileChannel fcout = fout.getChannel();
// 使用 allocateDirect,而不是 allocate
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (true) {
buffer.clear();
int r = fcin.read(buffer);
if (r == -1) {
break;
}
buffer.flip();
fcout.write(buffer);
}
}
}
复制代码
写入:
public class FileOutputDemo {
static private final byte message[] = "hello shiqi".getBytes(StandardCharsets.UTF_8);
static public void main( String args[] ) throws Exception {
String path = FileOutputDemo.class.getResource("/application.properties").getPath();
String outfile = path.replace("application.properties","application-copy.properties");
FileOutputStream fout = new FileOutputStream( outfile );
FileChannel fc = fout.getChannel();
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
for (int i=0; i<message.length; ++i) {
buffer.put( message[i] );
}
buffer.flip();
fc.write( buffer );
fout.close();
}
}
复制代码
IO多路复用
- 由一个/一组专门的线程来处理所有的IO事件,并负责分发;
- 事件驱动机制:事件到的时候去触发,而不是同步的去监视事件;
- 线程通信:线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都有意义。减少不必要的线程切换。
下面给我最简单的单线程 Reactor 模式的NIO 处理流程图:

2. Netty 与NIO
2.1 官方介绍
Netty 是一个异步、事件驱动的用来做高性能、高可靠性的网络应用框架。主要的优点有:
- 框架设计优雅,底层模型随意切换适应不同的网络协议要求;
- 提供很多标准的协议、安全、编码解码的支持;
- 解决了很多NIO不易用的问题;
- 社区更为活跃,在很多开源框架中使用,例如:Dubbo、RocketMq、Spark 等;

由官方的架构图,我们得出以下几点:
- 核心 :零拷贝、通用API、可扩展的事件模型;
- 传输服务:管道通信(不太清楚)、Http隧道、TCP和UDP;
- 协议支持:支持市面上主流服务;
2.2 Netty 采用NIO而非AIO的理由
- Netty 不看重windows 上的使用,在linux 系统上,AIO 的底层使用EPOLL, 没有很好的实现AIO; 在性能上没有明显的优势;
- AIO 不够成熟(
? 对不起,我们分了吧,我觉得你不够成熟;???); - AIO 是 proactor 模型,与Reactor 模型匹配不优好;





















![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)