Warning : Trying to access array offset on value of type bool in
/www/wwwroot/wql_luoqin_ltd/wp-content/themes/Sakura/inc/theme_plus.php on line
286
一,I/O模型的基本概述
在java中IO其实分两大类:
这里我们讲的是网络IO,而非磁盘IO
I/O模型:通俗的说就是什么样的通道进行数据的发送和接收,很大程度上决定了程序通讯的性能
java共支持三种网络编程IO模型:BIO,NIO,AIO
1,java BIO:同步堵塞(传统堵塞型IO),服务器的实现模式为一个连接一个线程,即客户端有请求连接服务端时,服务器就会启动一个线程处理该客户端的请求,如果这个连接不做任何事情就会造成不必要的线程开销
2,Java NIO:同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送连接请求都会注册到一个多路复用器上,多路复用器轮询到连接有I/O请求进行处理(事件驱动多路复用)
3,Java AIO(NIO.2):异步非阻塞,AIO引进了异步通道的概念,采用了Proactor模式,简化了程序的编写,有效的请求才启动线程,它的特定是先由操作系统完成后才通知和服务端程序去处理,一般适用于连接数较多且连接时间较长的应用
注:AIO(NIO.2)是JDK1.7之后产生的,并没有得到一个广泛的应用,netty是基于NIO并不是基于AIO
二,三大IO模型的对比
异步,同步,堵塞,非堵塞举例说明:以WQL剪头发为例
同步堵塞:WQL进入店里,发现有很多人,它就在店里等待,直到理完头发
同步非堵塞:WQL进入店里,发现要排队剪头发,他就去旁边的网吧打LOL,每打一下就去店里看有没有轮到自己
异步非堵塞:WQL打电话叫理发师上门访问,它一边打游戏,等待理发师过来(AIO底层有OS已经处理)
三,3大I/O模型的应用场景
1,BIO:适用于连接数目比较小且固定的架构,这种方式对服务器的性能资源要求比较高,并发的访问,局限性大,但它JDK1.4以前的唯一选择,但程序简单易理解
2,NIO:适用于连接数目多且连接时间短(轻操作)的架构,比如:聊天服务器,弹幕系统,服务器间的通讯,编程较复杂,JDK1.4开始支持
3,AIO:适用于连接数目多且连接时间长(重操作)的架构,比如:相册服务器,充分调用OS参与并发操作,对操作系统依赖大,编程较复杂,JDK1.7开始支持
二,BIO模型
java BIO就是传统的java IO编程,其相关的类和接口在java.io中
BIO(block I/O):同步堵塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务端就启动一个线程进行处理,如果这个连接不做任何事情会造成资源浪费
BIO工作原理
BIO编程普通的流程:
服务端启动一个ServerSocket
客户端启动Socket对服务端进行通信,默认情况下服务端需要对每一个客户建立一个线程与之通信
客户端发出请求后,会先咨询服务器是否有线程可以响应,如果没有则会等待或者被拒绝
如果有响应,客户端会等待请求结束后,才继续执行下一个请求
BIO编程优化的流程(加线程池):
服务端启动一个ServerSocket
建立一个Executor线程池
客户端请求连接服务端,服务器从线程池里取线程处理客户端请
如果有响应,客户端会等待请求结束后,才继续执行下一个请求
BIO案例:
1,使用BIO编写一个服务器,监听9090端口,当客户端连接时,启动一个线程与之通信
2,使用线程池
3,客户端使用windows的telnet
telnet的使用:telnet是windows中自带的基于TCP/IP协议的客户端
telnet默认是关闭的:在启用和关闭Windows功能中开启
在黑窗口中执行:telnet IP地址 端口号 连接服务器
通过send命令发送信息
服务端代码:
//新建一个连接池
ExecutorService executorService = Executors.newCachedThreadPool();
//创建一个ServerSocket服务端
ServerSocket serverSocket =new ServerSocket(9090);
//while轮询获取连接
while(true){
System.out.println("等待连接!!");
final Socket socket = serverSocket.accept();
System.out.println("连接一个客户端!!");
executorService.execute(new Runnable() {
public void run() {
try {
//线程调用handle方法
handle(socket);
} catch (IOException e) {
e.printStackTrace();
}
}
});
}}
public static void handle(Socket socket) throws IOException {
//获取流
InputStream inputStream =socket.getInputStream();
//定义一个缓冲数组
byte[] a =new byte[1024];
//偏移量
int len;
//循环读取
while(true){
int b = inputStream.read(a);
if(b!=-1){
System.out.println(new String(a,0,b));
}else{
break;
}
}
结果:
三,NIO模型
一,NIO的基本概念
1,java NIO(java non-blocking IO)指JDK提供的新API,从JDK1.4开始,java提供了一系列改进的输入/输出的新特性,被统称为NIO(即new IO) 同步非阻塞
2,NIO相关类都存放在java.nio包及子包下,并且对原java.io包中的很多类进行了改写
3,NIO有三大核心部分:Channel(通道),Buffer(缓冲区),Selector(选择器)
4,NIO是面向缓冲区,或者面向块编程的,数据读取到一个它稍后处理的缓存区中,需要时可在缓冲区中前后移动,这就增加了处理过程的灵活性,使用它可以提供使用它可以提供非阻塞式的高伸缩性网络
NIO各组件的活动状态:
一个Thread维护一个selector,一个selector维护多个客户端的连接,每一个客户端都有单独的Channel和Buffer做在中间层
Selector是一个轮询选择器(多路复用),当client有读写请求过来,selector可以轮询到进行处理,但没有任何读写请求,维护selector的Thread也并不是堵塞的,Thread可以做其他事情
NIO中Channel是双向的可读可写(底层封装并改造了传统IO),传统的IO流的单向的要么输入要么输出
Channel和Buffer之间的也是双向的,Channel可以写数据到Buffer,Buffer也可以写数据到Channel,当中间有一个切换的过程
NIO的优势: NIO可以使用一个线程做多个操作,假设有10000个请求过来,根据实际情况,服务器可以分配50或者100(这要看具体的分配策略)个线程进行处理,不像BIO那样一连接请求一个线程,节省了服务器的资源
Selector和Channel,Buffer三者的关系:
每一个Channel都会对应一个Buffer
Selector都会对应一个Thread,一个Thread对应多个Channel(通道)
每一个Channel都需要注册到selector中
程序切换到那个channel是由事件决定的,Event就是一个重要的概念
Selector会根据不同的事件在各个通道上切换
Buffer就是一个内存块,底层是有一个数组
数据的写入是通过Buffer的,这个和BIO,BIO中要么是输入流/输出流,不能双向,但是NIO的Buffer是可以读也可以写的,需要flip方法切换
Channel是双向的,可以返回底层操作系统的情况,底层操作系统通道就是双向的
BIO和NIO的比较:
BIO以流的方式处理数据,而NIO以块(缓冲区)的方式处理数据块,块I/O效率比流I/O高很多
BIO堵塞,NIO非堵塞
BIO基于字节流和字符流进行操作,而NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据从通道读到缓冲区或是从缓冲区写入通道中,Select(选择器)用于监听多个通道(比如:连接请求,数据到达等),因此使用单个线程可以监听多个客户端通道
二,NIO的Buffer缓冲区
一,缓冲区的基本概述
缓冲区(Buffer):缓冲区本质上是一个可以读写的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机制,能跟踪和记录缓冲区的状态情况,Channel提供从文件,网络读取数据的渠道,但是读取或者写入的数据都必须由Buffer进行
java NIO的缓冲区在java.nio包下:
本质上NIO的Buffer底层就是一个数组
Buffer类定义了所有的缓冲区都具有的四大属性来提供关于其所包含的数据元素信息:
Capacity:容量,即可以容纳的最大数据,在缓冲区创建时被设定并且不能改变
Limit:表示缓冲区的当前终点,不能超过限制的位置进行读写操作,且极限可以修改
Position:位置,下一个要被读写或写的元素的索引,每一次读写缓冲区数据时都会改变值
Mark:标记,调用mark()来设置mark=position,再调用reset()可以让position恢复坐标位置
源码:
二,Buffer缓冲区的操作
缓冲区的根类API:ByteBuffer,DoubleBuffer……都继承了Buffer类
Buffer类的API:
缓冲区的操作API:以IntBuffer为例,其他的类型的Buffer大同小异
例:Buffer添加和获取
//定义一个Intbuffer,容量为5
IntBuffer intBuffer = IntBuffer.allocate(5);
//循环添加数据
for (int i=0;i<intBuffer.limit();i++){
intBuffer.put(i*2);//postition每一次向后移一位+1
}
//读写切换,把缓冲区进行翻转,把position设置为0,limit=当前的position,进行读取
intBuffer.flip();
//读取
while (intBuffer.hasRemaining()){
System.out.println(intBuffer.get());
}
}
三,NIO的Channel通道
NIO的通道类似于流但与传统流又有区别:
通道可以同时进行读写,而流流只能读或者只能写
通道可以实现异步读写数据
通道可以从缓冲区读数据,也可以写数据到缓冲区
Channel在java NIO中是一个接口,在Channel接口下面有很多实现类,分别对应不同场景的数据传输
常见有:
channel常用的方法:
关于Buffer和Channel的注意细节:
Buffer支持类型化的put和get,put放入什么类型的数据,get就为什么类型的数据,否则可以抛BufferUnderflowException异常
可以讲一个普通的Buffer转化成一个只读的Buffer
NIO还提供了MappedByteBuffer,可以让文件直接在内存中(堆外内存)进行修改,而任何同步到文件由NIO完成
前面我们讲的读写操作,都是通过一个Buffer完成的,NIO还支持通过多个Buffer完成读写操作即Scattering和Getering
案例一(本地文件写):使用ByteBuffer(字节缓冲区)和FileChannel(通道),将自定义字符写入到wql.txt文件中
String content = "WQL 6666";
//获取一个文件输出流
FileOutputStream Stream =new FileOutputStream("wql.txt");
//通过流获取FileChannel通道
FileChannel fileChannel = Stream.getChannel();
//创建一个缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//往Buffer中存字符
byteBuffer.put(content.getBytes());
//反转缓冲区
byteBuffer.flip();
//将缓冲区的数据写入通道
fileChannel.write(byteBuffer);
案例二(本地文件读):使用ByteBuffer(字节缓冲区)和FileChannel(通道),将file01.txt中的数据读入程序
//读取文件wql.txt
FileInputStream fileInputStream = new FileInputStream(new File("wql.txt"));
//获取Channel通道(数据也通过inputStream进入通道)
FileChannel fileChannel = fileInputStream.getChannel();
//创建Buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//将通道的数据read到Buffer中
fileChannel.read(byteBuffer);
//反转Buffer
byteBuffer.flip();
//打印数据
System.out.println(new String(byteBuffer.array()));
fileChannel.close();
}
案例三(文件的拷贝):使用ByteBuffer(字节缓冲区)和FileChannel(通道),将wql.txt文件拷贝到fq.txt中
//1,将要拷贝文件的读入
FileInputStream fileInputStream = new FileInputStream("wql.txt");
//通过FileInputStream获取Channel
FileChannel fileChannel = fileInputStream.getChannel();
//创建Buffer缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//将Channel的数据read到Buffer中
fileChannel.read(byteBuffer);
//2,将要拷贝文件写出,创建FileoutputStream
FileOutputStream fileOutputStream = new FileOutputStream("fq.txt");
//缓冲区反转
byteBuffer.flip();
//通过fileOutputStream创建Channel
FileChannel fileChannel1=fileOutputStream.getChannel();
//将缓冲区的数据writer写入Channel
fileChannel1.write(byteBuffer);
案例四:使用transferFrom和transferTo进行图片拷贝,之前是通过channel的Read和Writer加Buffer进行文件拷贝,现在换一种方式
//创建输入流和输出流
FileInputStream fileInputStream = new FileInputStream("FQ.jpg");
FileOutputStream fileOutputStream = new FileOutputStream("love.jpg");
//通过输入流和输出流创建Channel
FileChannel fileChannel = fileInputStream.getChannel();
FileChannel fileChannel1 = fileOutputStream.getChannel();
//将目标通道的数据复制到当前通道
//必须设置position位置和长度
fileChannel1.transferFrom(fileChannel,0,fileChannel.size());
//关闭通道
fileChannel.close();
fileChannel1.close();
fileInputStream.close();
fileOutputStream.close();
四,NIO的Selector选择器
一,Selector的概述
Selector的基本概念:
1,NIO是非堵塞的IO模型,一个线程可以处理多个客户端,而多处理的根本就是Selector(选择器)
2,Selector能够检测出多个注册的通道上是否有事件发生(注:多个Channel以事件的方式可以注册到同一个Selector),如果事件发生,便获取事件然后针对每一个事件进行处理,
3,只有在连接真正有读写事件发生时,才会进行读写,就大大的减少了系统的开销,并且不必为每一个连接都创建一个线程,不用去维护多个连接
4,避免了多个线程之间的上下文切换的系统消耗
Selector的特定和说明:
Netty的IO线程NioEventLoop聚合了Selector(选择器也叫多路复用器),可以同时并发处理成白上千的客户端连接
当线程从某客户端Socket通道进行读写数据时,若没有数据可以使用时,该线程进行其他任务
线程通常将非堵塞IO的空闲时间用于在其他通道上执行IO操作,使用单独的线程可以管理多个输入和输出通道
由于读写操作是非堵塞的,这就可以充分的提高IO线程的运行效率,避免由于频繁IO导致线程挂起
一个I/O线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统同步堵塞I/O一连接一线程模型。使架构性能,弹性伸缩能力和可靠性得到提升
二,Selector的API
常用方法:
open():得到一个选择器对象
select():监控所有注册的通道,当其中有IO操作可以进行时,将对应的SelectionKey加入到内部集合中返回,参数设置超时时间
Keys():得到所有SelectionKey的集合(所有)
selectedKeys():从内部集合得到所有发生事件的SelectionKey集合(有事件发生)
功能方法:
三, SelectionKey概述
SelectionKey是Channel通道在Selector上的一个注册标记集合,底层是一个List集合,每一个注册再
SelectionKey的静态常量 :
SelectionKey的方法:
常用的方法:
isAcceptable:判断是否为连接请求
isReadable:判断是否为读请求
isWritable:判断是否为读请求
channle:通过集合得到对应的通道
attachment:得到Buffer
流程的说明:
当客户端连接时,会通过ServerSocketChannel的监听得到SocketChannel
将监听到的SocketChannel注册到Selector,通过register()方法一个selector可以注册多个SocketChannel
注册后Selector会生成一个SelectionKey的注册表,这个表和Channel通道关联,里面有通道信息
Selector通过select方法进行监听,返回为false说明没有事件发生
通过Selector.selectedKeys()获取有事件发生的selectionKey的集合
selectionKey通过channel()方法反向获取SockedChannel通道
案例:通过NIO进行客户端和服务端的通信
ServerSockerChannel服务端:
//创建ServerSocketChannel -> ServerSocket
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//得到一个Selector对象
Selector selector = Selector.open();
//绑定端口6666,在服务器端监听
serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1",6666));
//设置为非阻塞
serverSocketChannel.configureBlocking(false);
//把serverSocketChannel注册到selector关心事件为连接事件OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//循环等待客户端连接
while(true){
//等待一秒,如果没有事件发生,就continue(轮询的监听)
if(selector.select(1000)==0){
System.out.println("服务器等待1秒,无连接!");
//当selector没有事件产生跳出本次循环
continue;
}
//如果selector.select大于零,获取相关的selectionkey集合
//1,返回大于零表示已经获取到产生事件的集合
//2,selector.selectedKeys获取发生事件的集合,注:selector.Keys是注册到Selector的所有集合,我们之需要发生事件的集合
//3,通过SelectionKey反向获取通道
Set<SelectionKey> selectionKeys =selector.selectedKeys();
//set<SelectionKey>使用迭代器
Iterator<SelectionKey> iterator = selectionKeys.iterator();
//遍历迭代器
while(iterator.hasNext()){
//得到SelectionKey集合元素
SelectionKey selectionKey = iterator.next();
//对事件分别处理,因为事件有连接事件,读事件,写事件通过if进行分别处理
//如果发生的是连接事件OP_ACCEPT,有新的客户端连接
if(selectionKey.isAcceptable()){
//新的客户端连接,就产生一个新的SocketChannel分配给客户端
SocketChannel socketChannel= serverSocketChannel.accept();
//设置channel为非阻塞
socketChannel.configureBlocking(false);
//将新产生的socketChannel注册到Selector上,同时为channel关联一个Buffer
socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
System.out.println("客户端连接成功:"+socketChannel.hashCode());
}
//如果发生的是读取事件,就读取数据
if(selectionKey.isReadable()){
//反向获取channel
SocketChannel channel = (SocketChannel)selectionKey.channel();
//获取到channel关联的buffer
ByteBuffer attachment = (ByteBuffer)selectionKey.attachment();
channel.read(attachment);
System.out.println("客户端发送数据:"+ new String(attachment.array()));
}
//结束后从集合中移动selectionKey,防止重复操作
iterator.remove();
}
}
SockerChannel客户端:
//得到一个网络通道
SocketChannel socketChannel = SocketChannel.open();
//设置非阻塞
socketChannel.configureBlocking(false);
//提高服务器端的IP和端口
InetSocketAddress inetSocketAddress =new InetSocketAddress("127.0.0.1",6666);
//连接服务器
if(!socketChannel.connect(inetSocketAddress)){
while(!socketChannel.finishConnect()){
System.out.println("连接等待,客户端不会堵塞!");
}
}
String str = "袁爷爷一路走后!!";
ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
//发送数据,将ByteBuffer的数据写入channel
socketChannel.write(byteBuffer);
}
Comments | NOTHING
Warning: Undefined variable $return_smiles in /www/wwwroot/wql_luoqin_ltd/wp-content/themes/Sakura/functions.php on line 1109