- 涉及的一些基础
涉及的设计模式:观察者模式、命令模式、责任链模式 涉及的数据结构:链表(管道的底层使用了链表)
一.netty介绍
是一个java开源项目 是一个异步的、基于事件驱动的网络应用框架,用以开发高性能、高可用的网络io程序
异步是相对于同步而言的 同步:在同步中,当浏览器向服务器发送了一个请求1,需要等待服务器向浏览器返回了响应2后,浏览器才能进行操作3。
异步:在异步中,浏览器向服务器发起一个请求,但是它并不会因为响应没有到达,而发生阻塞,而是可以进行操作3,不需要等待响应2,提供了性能
高性能、高可用其实就是对java中的io进行了重写 主要针对于在TCP协议下,面向Clients的高并发应用,或者是peer-to-peer > 场景下的大量数据持续传输的应用
Netty本质是一个NIO框架,适用于服务通讯的多种应用场景,所以学好NIO。
二.Netty应用的场景
- 作为基础的通信组件被RPC框架使用,例如dubbo
- 游戏行业
- 大数据领域:AVRO实现数据文件的共享
三.IO模型
三种IO模型:用什么样的通道进行数据的发送和接受,很大程度上决定了程序通道的性能
BIO:同步并阻塞(传统阻塞),服务器实现模式为一个链接一个线程,即客户端有链接请求时服务器端就需要启动一个线程进行处理,如果这个链接不做任何事情就会造成不必要的线程开销 当一个服务器被多个客户端请求时,就会对服务器造成压力,求每个线程都会有一定的开销。
BIO:同步非阻塞,服务器实现模式为一个线程处理多个请求(链接),即客户端发送的链接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求就进行处理。 一个线程维护一个选择器,选择器可以对客户端的通道进行轮询操作,实现了可以处理更多的并发
AIO:异步非阻塞,AIO引入异步通道的概念,采用Procactor模式,简化了程序编写,有效的请求才启动线程,它的特点时由操作系统完成后才通知服务端程序开启线程去处理,一般适用于连接数较多且连接时间较长的应用(目前还不常用)
IO适用场景:
- BIO方式适用于连接数目较小且固定的框架,但是方式对服务器资源要求比较高,并发局限于应用中,jdk1.4以前的唯一选择,但是程序简单
- NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕系统,服务器间通讯、
- AIO方式适用于连接数目比较多且连接比较长的架构,比如相册服务器,充分调用OS参与并发操作,编程较为复杂,jdk7后开始支持
四.BIO讲解
同步阻塞,线程的开销大,可以通过线程池改善开销 编程流程:
- 服务器端创建一个serverSocket
- 客户端启动Socket对服务器进行通信,默认情况下服务器端需要对每个客户建立一个线程与通信
- 客户端发送请求后,先咨询服务器是否由线程响应,如果1没有则会等待,或者被拒绝
- 如果有响应,客户端线程会等待请求结束后,在继续执行
BIO相关案例:使用BIO模拟客户端向服务端发送数据
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class BIOServer {
public static void main(String[] args) throws IOException {
ExecutorService newCachedThreadPool= Executors.newCachedThreadPool();
ServerSocket serverSocket=new ServerSocket(6666);
System.out.println("服务启动了");
while(true){
System.out.println("线程信息 id:"+Thread.currentThread().getId()+" 名字为:"+Thread.currentThread().getName());
final Socket socket=serverSocket.accept();
System.out.println("有客户端进行连接了");
newCachedThreadPool.execute(new Runnable() {
@Override
public void run() {
handler(socket);
}
});
}
}
public static void handler(Socket socket){
try{
byte[] bytes=new byte[1024];
InputStream inputStream = socket.getInputStream();
while(true){
System.out.println("等待连接");
System.out.println("线程信息 id:"+Thread.currentThread().getId()+" 名字为:"+Thread.currentThread().getName());
System.out.println("read-------");
int read = inputStream.read(bytes);
if(read!=-1){
System.out.println(new String(bytes,0,read));
}else {
break;
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println("关闭连接");
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
在cmd中使用
telnet 127.0.0.1 6666
ctrl+] 进入命令行界面 后使用send命令进行发送
send helloWorld
五.Java NIO基本介绍
-
全程java non-blocking IO ,是一系列改进的输入/输出的新特性,被统称为NIO,是同步非阻塞的 -
NIO相关类都被放在java.nio 包和其子包下,并且对原java.io包中很多类进行了改写 -
三大核心部分:Channel(管道) ,Buffer(缓冲区),Selector(选择器) -
NIO是面向缓冲区,或者面向块编程的,数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中进行前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络。 -
java NIO 的非阻塞模式,是一个线程从某通道发送的请求或者读取数据,但是它仅能得到且目前可用的数据,如果目前没有数据可用时,就什么都不会获得,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情,而非阻塞也是如此,一个线程请求写入一些数据到某通道,但是不需要等待它完全写入,这个线程同时可以去做别的事情。 -
通俗理解:NIO是可以做到用一个线程来处理多个操作的,假设有10000个请求过来,根据实际情况,可以分配50个或100个,不会像阻塞IO那样,非得分配10000个 -
HTTP 2.0 使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP 1.1大了好几个数量级
NIO的Buffer小案例:
文件1
import java.nio.IntBuffer;
public class BasicBuffer {
public static void main(String[] args) {
IntBuffer intBuffer=IntBuffer.allocate(5);
intBuffer.put(10);
intBuffer.put(11);
intBuffer.put(12);
intBuffer.put(13);
intBuffer.put(14);
intBuffer.flip();
while(intBuffer.hasRemaining()){
System.out.println(intBuffer.get());
}
}
}
NIO与BIO的比较
- BIO以流的方式处数据,NIO以块的方式处理数据,块I/O的效率比流I/O高很多
- BIO是阻塞的,NIO则是非阻塞的
- BIO基于字节流和字符流进行操作,而NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取数据到缓冲区,或者从缓冲区写入到通道中,Selector(选择器)用于监听多个通道的事件(比如:连接请求、数据到达等),因此使用单个线程就可以监听多个客户端通道。
NIO三大核心
- 每个channel都对应一个Buffer
- Selector对应一个线程
- 一个线程可以对应多个channel,一个channel相当于一个连接
- 该图反应了有3个channel注册到了Selector
- 程序切换到哪个channel是由事件决定的,Event是一个很重要的事件
- Seletor会根据不同的事件在各个通道进行切换
- Buffer相当于就是一个内存块,底层是一个数组
- 数据的读取是通过Buffer,这个和BIO是完全不同的,BIO要么是输入流或者是输出流,不是双向的;但是NIO的Buffer是可以读也可以写,但是需要有flip进行切换
- channel是双向的,可以返回底层操作系统的情况,如linux底层的操作系统通道就是双向的
缓冲区Buffer
缓冲区本质上是一个可以读写数据的内存块,可以理解是一个容器对象(含数组),该对象提供了一组方法,可以更加轻松地使用内存块,缓冲区对像内置了一些机制,能够跟踪和记录缓冲区地状态变化情况,channel提供文件、网络读取数据地渠道,但是读取或写入地数据必须是经过Buffer,如图: Buffer即其子类: 先看看Buffer源码地几个重要地成员属性:
属性 | 概述 |
---|
Capacity | 容量,即可以容纳地最大数据量;在缓冲区创建时被设定并且不能改变 | Limit | 表示缓冲区地当前终点,不能对缓冲区超过极限地位置进行读写操作。且极限是可以修改的 | Position | 位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变值,为下一次读写操作准备 | Mark | 标记 |
可以对 文件1 进行代码debug调式: 当position到达5时,就无法进行写的操作了 接下来我们使用filp进行反转,我们先看看这个方法中的源代码:
将限制limit定位到当前position,表示读操作时必须在limit范围内,接着将position置为0,进行新的读操作。 Buffer中常用的方法:
ByteBuffer是Buffer的子类,也是我们进行网络传输最常用的一个类
通道Channel
基本介绍:
1.BIO中的Stream是单向的,例如FileInputStream对象只能进行读取文件数据的操作,而NIO中的通道channel是双向的,可以读操作,也可以写操作。 2.Channel在NIO中是一个接口,常用的Channel类有:FileChannel、DatagramChannel、ServerSocketChannel、SocketChannel 3.FileChannel用于文件的数据读写,DatagramChannel用于UDP的数据的读写,ServerSocketChannel和SocketChannel用于TCP的数据读写
FileChannel是其中一个重要的类,常见的方法有: int read(ByteBuffer dst) :从通道读取数据并放到缓冲区中 int write(ByteBuffer src) :把缓冲区的数据写到通道中 long transferFrom(ReadableByteChannel src,long position,long count): 从目标通道中复制数据到当前通道 long transferTo(long position,long count,WritiableByteChannel target) : 把数据从当前通道复制给目标通道
channel、buffer小案例
建议以下案例自行进行debug,观察buffer中那4个关键字段的变化
1.使用前面学习的ByteBuffer和FileChannel,将一段文字写入到file01.txt中
public class ChannelTest1 {
public static void main(String[] args) throws Exception{
String s="hello world";
FileOutputStream fileOutputStream=new FileOutputStream("d://myNIO//file01.txt");
FileChannel channel = fileOutputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(s.getBytes());
buffer.flip();
channel.write(buffer);
}
}
案例2:
使用channel和buffer读取文件file01.txt
public class ChannelTest2 {
public static void main(String[] args) throws Exception {
File file = new File("d://myNIO//file01.txt");
FileInputStream fileInputStream = new FileInputStream("d://myNIO//file01.txt");
FileChannel channel = fileInputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate((int)file.length());
int read = channel.read(buffer);
System.out.println(new String(buffer.array()));
fileInputStream.close();
}
}
|