Netty
Netty是什么
Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
也就是说,Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。
“快速”和“简单”并不用产生维护性或性能上的问题。Netty 是一个吸收了多种协议(包括FTP、SMTP、HTTP等各种二进制文本协议)的实现经验,并经过相当精心设计的项目。最终,Netty 成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性。 [1]
面向高并发,网络IO程序
事件驱动
用户一个行为触发一个事件,驱动函数或方法进行处理
应用场景
- 分布式系统各个组件需要远程服务调用,netty作为基础通信组件被RPC框架使用(比如Dubbo,实现各进程节点之间的内部通信)
- 游戏行业,高性能定制网络通信程序
- 大数据 avro实现数据文件共享
IO模型
BIO
同步并阻塞型,客户端有链接请求时客户端需要启动一个线程进行处理。不处理完请求不会继续往下执行。BIO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。
![](https://img-blog.csdnimg.cn/img_convert/16912148a0f7aef2d99bcdeff1340037.png#align=left&display=inline&height=531&margin=[object Object]&originHeight=531&originWidth=500&size=0&status=done&style=none&width=500)
NIO
NIO(JDK1.4)模型是一种同步非阻塞IO,主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(多路复用器)。传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(多路复用器)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。
NIO和传统IO(一下简称IO)之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
![](https://img-blog.csdnimg.cn/img_convert/56ea21aab4c8c8ac71a14b9957b8cca5.png#align=left&display=inline&height=254&margin=[object Object]&originHeight=254&originWidth=450&size=0&status=done&style=none&width=450)
适用于连接数目多和连接比较短的架构,聊天服务器,弹幕系统,服务期间通讯
AIO模型
异步非阻塞模型,引入一个异步通道,有效的请求才启动线程,先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用,比如相册服务器
同步和异步
同步和异步IO的概念:同步是用户线程发起I/O请求后需要等待或者轮询内核I/O操作完成后才能继续执行 异步是用户线程发起I/O请求后仍需要继续执行,当内核I/O操作完成后会通知用户线程,或者调用用户线程注册的回调函数。
阻塞和非阻塞
阻塞是指I/O操作需要彻底完成后才能返回用户空间 非阻塞是指I/O操作被调用后立即返回一个状态值,无需等I/O操作彻底完成。
BIO
高并发的状态下,BIO对服务端的压力非常大
适用于连接数目比较小的项目
可以通过线程池机制改善,实现多个客户连接服务器
编程的简单流程
- 服务器端启动一个ServerSocket
- 客户端启动Socket对服务器进行通信,服务端需要对每一个客户建立一个线程与之通信。
- 客户端发出请求后,先咨询服务器是否有线程响应,如果没有则会等待或遭服务器拒绝连接
- 如果有相应,客户端线程会等待请求结束后再继续执行(阻塞)
实例
1.手动创建线程池
new ThreadPoolExecutor(2, 5, 1L,
TimeUnit.HOURS, new LinkedBlockingDeque<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
/* 七个参数
1、corePoolSize线程池的核心线程数
2、maximumPoolSize能容纳的最大线程数
3、keepAliveTime空闲线程存活时间
4、unit 存活的时间单位
5、workQueue 存放提交但未执行任务的队列
6、threadFactory 创建线程的工厂类
7、handler 等待队列满后的拒绝策略
*/
//1.创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 5, 1L,
TimeUnit.HOURS, new LinkedBlockingDeque<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
//2.监听客户端请求
//创建服务端ServerSocket
ServerSocket serverSocket = new ServerSocket(6666);
System.out.println("服务器启动了");
while (true) {
//监听
Socket socket = serverSocket.accept();
System.out.println("连接到一个客户端,他的IP是:"+socket.getInetAddress()+" 端口号为:"+socket.getPort());
//启动一个线程去处理
threadPoolExecutor.execute(() -> {
handler(socket);
});
}
}
static void handler(Socket socket) {
byte[] bytes = new byte[1024];
//获取输入流
try {
InputStream inputStream = socket.getInputStream();
while (true) {
int read = inputStream.read(bytes);
if (read != -1) {
System.out.println(new String(bytes, 0, read));
} else {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("关闭和客户端的连接");
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
NIO
模式
三大核心部分:Channel(通道)类似BIO的socket,Buffer(缓冲区),Selector(选择器)
![](https://img-blog.csdnimg.cn/img_convert/dafbeefff5aafdb9c35cd08957122679.png#align=left&display=inline&height=1080&margin=[object Object]&originHeight=1080&originWidth=1593&size=0&status=done&style=none&width=1593)
用buffer作为缓冲,可以降低socket阻塞度,NIO是面向缓冲区或面向块编程,数据读取到一个它稍后处理的缓冲区
如果buffer区没有数据可用,就什么都不会获取,不会保持线程阻塞,这个线程(选择器)可以同时去做不同的东西
案例
//buffer的使用
//创建一个buffer
//大小为5,可以存放5个int
IntBuffer allocate = IntBuffer.allocate(5);
//向buffer存放数据
//allocate.capacity() buffer的容量
for (int i = 0; i < allocate.capacity(); i++) {
allocate.put(i * 2+2);
}
//从buffer中读取数据
//将缓冲区反转,读写切换,将position切成0
allocate.flip();
//如果后面还有元素的话
//调用get 会将索引向后移
while (allocate.hasRemaining()) {
System.out.println(allocate.get());
}
NIO以块的方式处理数据,效率比bio以流的方式效率高很多
单个线程可以监听多个客户端通道
三大核心组件
Selector,channel,buffer
三者关系
- 每个channel对应一个buffer
- 每个selector对应一个线程,一个线程对应多个channel(连接)
- 程序切换到哪个channel,是由事件决定的
- selector会根据不同的事件,在各个通道上切换
- buffer本质上是一个内存块,底层一个数组
- 采用发布订阅模式设计
- BIO是单向流处理,NIO是双向操作,但需要filp方法切换
- channel也是双向的,可以返回底层操作系统的情况
Buffer
本质上是一个可以读写数据的内存块,可以理解为容器对象,缓冲区内置对象可以跟踪和记录缓冲区的状态变化情况
private int mark = -1; //标记
private int position = 0; //当前位置索引
private int limit; //限制读入\写入多少个
private int capacity; //容量,缓冲区创建后不能改变
final int[] hb; //真正存放数据的数组
buffer是一个顶层的父类,它是一个抽象类,拥有很多子类
![](https://img-blog.csdnimg.cn/img_convert/2933ca98c580b931832d16d2bd3818fc.png#align=left&display=inline&height=341&margin=[object Object]&originHeight=341&originWidth=569&size=0&status=done&style=none&width=569)
可以设置position控制自己想读的位置
类型化
-
put放入什么数据类型,get就应该使用相应的数据类型来取出,putint,getint
-
可以将一个普通buffer转换成只读buffer
转为 class java.nio.HeapByteBufferR 对象
allocate.asReadOnlyBuffer();
-
mappedByteBuffer,可以让文件直接在内存(堆外的内存)进行修改
-
NIO可同时操作多个buffer
只读buffer
public static void main(String[] args) {
//1.创建一个buffer
ByteBuffer allocate = ByteBuffer.allocate(1024);
//2.循环读入数据
for (int i = 0; i < 64; i++) {
allocate.put((byte)i);
}
//3.转换为只读
ByteBuffer byteBuffer = allocate.asReadOnlyBuffer();
byteBuffer.flip();
//4.读出数据
for (int i = 0; i < 64; i++) {
System.out.println(byteBuffer.get((byte)i));
}
MappedByteBuffer
public abstract class MappedByteBuffer
extends ByteBuffer
//继承了ByteBuffer 类
/**
* 说明
* MappedByteBuffer 可让文件直接在内存(堆外)修改,操作系统不需要拷贝一次
*/
public static void main(String[] args) throws IOException {
RandomAccessFile rw = new RandomAccessFile("D://1.txt", "rw");
//获取对应的通道
FileChannel channel = rw.getChannel();
//调用map方法 读写模式,读写开始位置,读写位置的大小
//可以修改的范围为0到5;
MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
map.put(0,(byte)'H');
map.put(3,(byte)'9');
rw.close();
}
Scattering 和 Gathering
/**
* 写入数据可以采用buffer数组依次写入
* gathering: 将数据读出到buffer时: 可以采用buffer数组 依次读入
*/
public class Scattering {
public static void main(String[] args) throws IOException {
//使用SeverSocketChannel 和SocketChannel;
//1.创建
ServerSocketChannel open = ServerSocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
//2.绑定端口到socket 并启动
open.socket().bind(inetSocketAddress);
//3.创建buffer数组
ByteBuffer[] byteBuffers = new ByteBuffer[2];
byteBuffers[0] = ByteBuffer.allocate(5);
byteBuffers[1] = ByteBuffer.allocate(3);
//4.监听客户端连接
SocketChannel accept = open.accept();
int messageLength = 8;
//5.循环读取
while (true) {
int byteRead = 0;
while (byteRead < messageLength) {
long read = accept.read(byteBuffers);
byteRead += read;
System.out.println("读取到" + byteRead);
Arrays.asList(byteBuffers).stream().map(buffer -> "position=" + buffer.position() + ",limit=" + buffer.limit()).forEach(System.out::println);
}
Arrays.asList(byteBuffers).forEach(buffer -> buffer.flip());
long byteWrite = 0;
while (byteWrite < messageLength) {
long write = accept.write(byteBuffers);
byteWrite += write;
}
//复位
Arrays.asList(byteBuffers).forEach(buffer->{
buffer.clear();
});
System.out.println("byteRead:="+byteRead+"byteWrite"+byteWrite);
}
}
}
channel
Nio的通道,类似于流,但双向作用于buffer
![](https://img-blog.csdnimg.cn/img_convert/7fa7be1b556f66a72b78fbe261ba3325.png#align=left&display=inline&height=277&margin=[object Object]&originHeight=277&originWidth=543&size=0&status=done&style=none&width=543)
DatagramChannel用于udp的数据读写
FileChannel用于文件的数据读写
SocketChannel,ServerSocketChannel用于TCP的数据读写
FileChannel
public abstract int read(ByteBuffer dst) throws IOException; //从通道读取数据并放到缓冲区中
public abstract int write(ByteBuffer src) throws IOException; //把缓冲区的数据写到通道中
public abstract long transferFrom(ReadableByteChannel src,
long position, long count)
throws IOException; //从目标通道中复制数据到当前通道
public abstract long transferTo(long position, long count,
WritableByteChannel target)
throws IOException; //把数据从当前通道复制到目标通道
读入读出按内存来理解
案例
/**
* 将缓存的字节存入文件中
* 文件不存在就创建
*/
public static void main(String[] args) throws IOException {
String hello = "hello world";
//创建一个输出流,并包装到channel
FileOutputStream fileOutputStream = new FileOutputStream("d:\\1.txt");
//通过输出流获取文件channel
FileChannel f1=fileOutputStream.getChannel();
//创建缓冲区 并write进channel
f1.write(ByteBuffer.wrap(hello.getBytes()));
FileInputStream fileInputStream = new FileInputStream("d:\\1.txt");
FileChannel f2=fileInputStream.getChannel();
f2.read(allocate);
System.out.println(new String(allocate.array()));
//读写出来
}