Netty
I/O模型
java支持3种模型:BIO NIO AIO
BIO: 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求服务端就要启动一个线程进行处理
NIO: 同步非阻塞,服务器实现模式为一个线程处理多个连接,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到有io请求就进行处理
AIO: 异步非阻塞,引入Proactor模式,在有效的请求才启动线程,一般用于连接数较多且时间较长的应用
重点:多路复用
适用场景
1.bio适用于连接数比较小并且固定的架构,简单
2.nio适用于连接数多且连接时间比较短(轻操作)的架构,比如聊天服务
3.aio适用于连接数多且连接比较长的架构,如相册服务器,充分调用os参与并发操作
public static void main(String[] args) throws IOException {
ExecutorService executorService = Executors.newCachedThreadPool();
ServerSocket serverSocket = new ServerSocket(6666);
while (true) {
final Socket socket = serverSocket.accept();
System.out.println("一个客户端");
executorService.execute(new Runnable() {
@Override
public void run() {
handler(socket);
}
});
}
}
public static void handler(Socket socket) {
System.out.println("当前线程:"+Thread.currentThread().getName());
byte[] bytes = new byte[1024];
try {
InputStream inputStream = socket.getInputStream();
while (true) {
int len = inputStream.read(bytes);
if (len != -1) {
System.out.println(new String(bytes, 0, len));
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
bio缺点:
1.每个请求都要创建独立的线程,与对应客户端进行数据read,业务处理,数据write
2.当并发量较大时,需要创建大量线程处理连接,占用系统资源
3.连接建立后,如果当前线程暂时没有数据可读,线程阻塞在Read上,造成线程资源浪费
nio三大核心部分: channel(通道),Buffer(缓冲区),Selector(选择器)
nio buffer的使用
public static void main(String[] args) {
IntBuffer intBuffer = IntBuffer.allocate(5);
intBuffer.put(10);
intBuffer.flip();
while (intBuffer.hasRemaining()) {
System.out.println(intBuffer.get());
}
}
nio channel
通道可以同时进行读写,而流是能读或者写
通道可以实现异步读写数据
通道可以从缓冲读取数据,也可以写数据到缓冲
常用Channel: FileChannel DatagramChannel ServerSockerChannel SockerChannel
buffer写文件
public static void main(String[] args) throws IOException {
String str = "hello,aaaa";
FileOutputStream fileOutputStream = new
FileOutputStream("d:/e.txt");
FileChannel fileChannel = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put(str.getBytes());
byteBuffer.flip();
fileChannel.write(byteBuffer);
fileOutputStream.close();
}
buffer读文件
public static void main(String[] args) throws IOException {
String str = "hello,bbb";
File f = new File("d:/e.txt");
FileInputStream fileInputStream = new FileInputStream(f);
FileChannel fileChannel = fileInputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate((int) f.length());
fileChannel.read(byteBuffer);
System.out.println(new String(byteBuffer.array()));
}
channel读写文件
public static void main(String[] args) throws IOException {
String str = "hello,bbb";
File f = new File("d:/e.txt");
FileInputStream fileInputStream = new FileInputStream(f);
FileChannel inputChannel = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream("D:/f.txt");
FileChannel outputChannel = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
while (true){
byteBuffer.clear();
int read = inputChannel.read(byteBuffer);
if(read == -1){
break;
}
byteBuffer.flip();
outputChannel.write(byteBuffer);
}
}
使用transferFrom拷贝文件
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("D:\\vm\\CentOS-7-x86_64-DVD-1810.iso");
FileChannel inputChannel = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream("D:\\g.iso");
FileChannel outputChannel = fileOutputStream.getChannel();
outputChannel.transferFrom(inputChannel,0,inputChannel.size());
}
selector
selector能够检测多个注册的通道上是否有事件发生
多个channel以事件的方式可以注册到同一个selector,如果有事件发生,便获取事件然后针对每一个事件进行相应的处理,这样就可以用一个单线程去管理多个通道,也就是管理多个连接和请求
Netty简介
BossGroup和WatchGroup协同工作
Netty是JBoss提供的一个java开源框架
Netty是一个异步的,基于事件驱动的网络应用框架
BIO:每个客户端连接到服务端都会新增一个线程处理,会出现性能问题,会阻塞
NIO
同步非阻塞,服务器实现模式为一个线程处理多个请求
客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有io请求则进行处理
NIO和BIO比较
缓冲区Buffer
flip操作:切换buffer的读写状态,其实是将position重置为0
通道
通道可以同时进行读写,而流只能读或者写
通道可以实现异步读写数据
常用的有:FileChannel(文件读写),DatagramChannel(UDP读写),ServerSocketChannel和SocketChannel
Selector(选择器)
selector能够检测多个注册的通道上是否有事件发生
如果有事件发生,便获取事件然后针对每个事件进行相应的处理
这样可以用一个单线程去管理多个通道,也就是管理多个连接和请求
通过selectkey可以反向获取到channel
selectionKey
零拷贝
常用的零拷贝有mmap和sendFile
传统io的read方法:
1.先使用DMA将数据从硬盘读取到内核中,(DMA拷贝就是不通过cpu的直接内存拷贝)然后用cpu将数据从内核态缓冲区读取到用户态的缓冲区中
2.在用户态缓冲区中对数据进行修改
3.用cpu将用户态缓冲区数据拷贝到socket的缓冲区
4.将socket缓冲区的数据用DMZ拷贝到协议栈
传统io是进行4次拷贝,3次状态切换
优化方法:
mmap:少了一次拷贝
通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据,这样,在进行网络传输时,减少从内核空间到用户空间的拷贝次数
sendFile:
Linux2.1提供了sendFile函数,原理:数据根本不经过用户态,直接从内核缓冲区进入到socketbuffer,同时,由于和用户态完全无关,就减少了一次上下文切换
0拷贝不是不拷贝,而是没有cpu拷贝的意思,从硬盘出来的DMA拷贝是一定要存在的
Linux2.4做了一写修改,避免从内核缓冲区拷贝到socketbuffer操作,直接拷贝到协议栈
又少了一次数据的拷贝
mmap和sendfile的区别:
Netty架构设计
目前存在的线程模型:
1.传统阻塞I/O服务模型
采用阻塞io模式获取输入的数据
每个连接都需要独立的线程完成数据的输入,业务处理,数据返回
2.Reactor模式
Reactor一共有3中实现
Reactor模式,通过一个或多个输入同时传递给服务处理器的模式,基于事件驱动
服务端程序处理传入的多个请求,并将它们同步分派到相应的处理线程
1)单Reactor单线程
2)单Reactor多线程
3)主从Reactor多线程
Netty线程模式是基于主从Reactor多线程模型做了一定的改进,其中主从Reactor多线程模型有多个Reactor
Netty模型
简单版
复杂版
任务队列taskQueue
问题:在pineline中执行长时间操作会出现问题,应该将这些任务放到任务队列中
提交任务到队列中
这样就会把任务放到taskQueue执行,防止阻塞到handler中的某个方法中
如果在同一个方法中有两个任务执行,则会阻塞,因为是在同一方法中是同一线程执行
如果在handler中有长时间任务执行,则最好使用这种方式执行
提交任务到定时队列中
方案再说明
异步模型
Netty核心组件
Bootstrap,ServerBootStrap
Future,ChannelFuture
Channel
selector
ChannelHandler
Pipeline和ChannelPipeline
ChannelHandlerContext
Unpooled