推荐学习:《java教程》
一、java思维导图
二、i/o模型i/o模型的本质是用什么样的通道进行数据的发送和接收,很大程度上决定了程序通信的性能。
java共支持三种网络编程模型:bio、nio、aio
bio:同步并阻塞,服务实现模式为一个连接一个线程,即客户端有一个连接请求时,服务端就需要启动一个线程进行处理。
nio: 同步非阻塞,服务器实现模式为一个线程处理多个请求连接,即客户端发送的请求都会注册到多路复用器上,多路复用器轮询到连接有i/o请求就进行处理。
aio:异步非阻塞,aio引入异步通道的概念,采用了proactor模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端。
三、bio、nio、aio应用场景bio方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高, 并发局限于应用中,jdk1.4以前的唯一选择,但程序简单易理解。
nio方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕 系统,服务器间通讯等。编程比较复杂,jdk1.4开始支持。
aio方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分 调用os参与并发操作,编程比较复杂,jdk7开始支持
四、bio编程简单流程服务器端启动一个serversocket;
客户端启动socket对服务器进行通 信,默认情况下服务器端需要对每 个客户 建立一个线程与之通讯;
客户端发出请求后, 先咨询服务器 是否有线程响应,如果没有则会等 待,或者被拒绝;
如果有响应,客户端线程会等待请 求结束后,在继续执行;
五、nio核心nio 有三大核心部分:selector(选择器)、channel(通道)、buffer(缓冲区)。
nio是面向缓冲区,或者说面向块编程,数据读取到一个 它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就 增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络。
http2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求 的数量比http1.1大了好几个数量级。
简而言之,nio可以一个线程处理多个请求。
六、bio与nio比较bio 以流的方式处理数据,而 nio 以块的方式处理数据,块 i/o 的效率比流 i/o 高很多;
bio 是阻塞的,nio 则是非阻塞的;
bio基于字节流和字符流进行操作,而 nio 基于 channel(通道)和 buffer(缓冲区)进 行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因 此使用单个线程就可以监听多个客户端通道。
七、nio 三大核心原理示意图
流程图说明:
selector 对应一个线程, 一个线程对应多个channel(连接);
该图反应了有三个channel 注册到 该selector //程序;
每个channel 都会对应一个buffer;
程序切换到哪个channel 是有事件决定的, event 就是一个重要的概念;
selector 会根据不同的事件,在各个通道上切换;
buffer 就是一个内存块 , 底层是有一个数组;
数据的读取写入是通过buffer, 这个和bio , bio 中要么是输入流,或者是 输出流, 不能双向,但是nio的buffer 是可以读也可以写, 需要 flip 方法切换;
channel 是双向的, 可以返回底层操作系统的情况, 比如linux , 底层的操作系统 通道就是双向的;
八、缓冲区(buffer)缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个 容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,,缓冲区对 象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。channel 提供从文件、 网络读取数据的渠道,但是读取或写入的数据都必须经由 buffer。
在 nio 中,buffer 是一个顶层父类,它是一个抽象类。
1、常用buffer子类一览bytebuffer,存储字节数据到缓冲区;
shortbuffer,存储字符串数据到缓冲区;
charbuffer,存储字符数据到缓冲区;
intbuffer,存储整数数据到缓冲区;
longbuffer,存储长整型数据到缓冲区;
doublebuffer,存储小数到缓冲区;
floatbuffer,存储小数到缓冲区;
2、buffer四大属性mark:标记
position:位置,下一个要被读或写的元素的索引, 每次读写缓冲区数据时都会改变改值, 为下次读写作准备。
limit:表示缓冲区的当前终点,不能对缓冲区 超过极限的位置进行读写操作。且极限 是可以修改的
capacity:容量,即可以容纳的最大数据量;在缓 冲区创建时被设定并且不能改变。
3、buffer常用apijdk1.4时,引入的api
public final int capacity( )//返回此缓冲区的容量public final int position( )//返回此缓冲区的位置public final buffer position (int newpositio)//设置此缓冲区的位置public final int limit( )//返回此缓冲区的限制public final buffer limit (int newlimit)//设置此缓冲区的限制public final buffer mark( )//在此缓冲区的位置设置标记public final buffer reset( )//将此缓冲区的位置重置为以前标记的位置public final buffer clear( )//清除此缓冲区, 即将各个标记恢复到初始状态,但是数据并没有真正擦除, 后面操作会覆盖public final buffer flip( )//反转此缓冲区public final buffer rewind( )//重绕此缓冲区public final int remaining( )//返回当前位置与限制之间的元素数public final boolean hasremaining( )//告知在当前位置和限制之间是否有元素public abstract boolean isreadonly( );//告知此缓冲区是否为只读缓冲区jdk1.6时引入的api
public abstract boolean hasarray();//告知此缓冲区是否具有可访问的底层实现数组public abstract object array();//返回此缓冲区的底层实现数组public abstract int arrayoffset();//返回此缓冲区的底层实现数组中第一个缓冲区元素的偏移量public abstract boolean isdirect();//告知此缓冲区是否为直接缓冲区
九、通道(channel)1、基本介绍(1)nio的通道类似于流
通道可以同时进行读写,而流只能读或者只能写;通道可以实现异步读写数据通道可以从缓冲读数据,也可以写数据到缓冲(2)bio 中的 stream 是单向的,例如 fileinputstream 对 象只能进行读取数据的操作,而 nio 中的通道 (channel)是双向的,可以读操作,也可以写操作。
(3)channel在nio中是一个接口
(4)常用的 channel 类有:filechannel、 datagramchannel、serversocketchannel 和 socketchannel。serversocketchanne 类似 serversocket , socketchannel 类似 socket。
(5)filechannel 用于文件的数据读写, datagramchannel 用于 udp 的数据读写, serversocketchannel 和 socketchannel 用于 tcp 的数据读写。
2、filechannelfilechannel主要用来对本地文件进行 io 操作,常见的方法有:
read,从通道读取数据并放到缓冲区中
write,把缓冲区的数据写到通道中
transferfrom,从目标通道 中复制数据到当前通道
transferto,把数据从当 前通道复制给目标通道
3、关于buffer 和 channel的注意事项和细节bytebuffer 支持类型化的put 和 get, put 放入的是什么数据类型,get就应该使用 相应的数据类型来取出,否则可能有 bufferunderflowexception 异常。
可以将一个普通buffer 转成只读buffer。
nio 还提供了 mappedbytebuffer, 可以让文件直接在内存(堆外的内存)中进 行修改, 而如何同步到文件由nio 来完成。
nio 还支持 通过多个 buffer (即 buffer 数组) 完成读写操作,即 scattering 和 gathering。
十、selector(选择器)1、基本介绍java 的 nio,用非阻塞的 io 方式。可以用一个线程,处理多个的客户端连 接,就会使用到selector(选择器)。
selector 能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然 后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个 通道,也就是管理多个连接和请求。
只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大地减少 了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程。
避免了多线程之间的上下文切换导致的开销。
2、selector的相关方法open();//得到一个选择器对象
select(long timeout);//监控所有注册的通道,当其 中有 io 操作可以进行时,将 对应的 selectionkey 加入到内部集合中并返回,参数用来 设置超时时间
selectedkeys();//从内部集合中得 到所有的 selectionkey。
3、注意事项nio中的 serversocketchannel功能类似serversocket,socketchannel功能类 似socket。
十一、通过nio实现简单的服务端客户端通信1、服务端package com.nezha.guor.nio;import java.io.ioexception;import java.net.inetsocketaddress;import java.nio.bytebuffer;import java.nio.channels.*;import java.util.iterator;public class nioserver { private selector selector; private serversocketchannel serversocketchannel; private static final int port = 8080; public nioserver() { try { //获得选择器 selector = selector.open(); serversocketchannel = serversocketchannel.open(); //绑定端口 serversocketchannel.socket().bind(new inetsocketaddress(port)); //设置非阻塞模式 serversocketchannel.configureblocking(false); //将该serversocketchannel 注册到selector serversocketchannel.register(selector, selectionkey.op_accept); }catch (ioexception e) { system.out.println(nioserver error:+e.getmessage()); } } public void listen() { system.out.println(监听线程启动: + thread.currentthread().getname()); try { while (true) { int count = selector.select(); if(count > 0) { //遍历得到selectionkey集合 iterator<selectionkey> iterator = selector.selectedkeys().iterator(); while (iterator.hasnext()) { selectionkey key = iterator.next(); if(key.isacceptable()) { socketchannel sc = serversocketchannel.accept(); sc.configureblocking(false); sc.register(selector, selectionkey.op_read); system.out.println(sc.getremoteaddress() + 上线 ); } //通道发送read事件,即通道是可读的状态 if(key.isreadable()) { getdatafromchannel(key); } //当前的key 删除,防止重复处理 iterator.remove(); } } else { system.out.println(等待中); } } }catch (exception e) { system.out.println(listen error:+e.getmessage()); } } private void getdatafromchannel(selectionkey key) { socketchannel channel = null; try { channel = (socketchannel) key.channel(); bytebuffer buffer = bytebuffer.allocate(1024); int count = channel.read(buffer); //根据count的值做处理 if(count > 0) { string msg = new string(buffer.array()); system.out.println(来自客户端: + msg); //向其它的客户端转发消息(排除自己) sendinfotootherclients(msg, channel); } }catch (ioexception e) { try { system.out.println(channel.getremoteaddress() + 离线了); //取消注册 key.cancel(); }catch (ioexception ex) { system.out.println(getdatafromchannel error:+ex.getmessage()); } }finally { try { channel.close(); }catch (ioexception ex) { system.out.println(channel.close() error:+ex.getmessage()); } } } //转发消息给其它客户(通道) private void sendinfotootherclients(string msg, socketchannel self ) throws ioexception{ system.out.println(服务器转发消息中...); system.out.println(服务器转发数据给客户端线程: + thread.currentthread().getname()); //遍历 所有注册到selector 上的 socketchannel,并排除 self for(selectionkey key: selector.keys()) { channel targetchannel = key.channel(); //排除自己 if(targetchannel instanceof socketchannel && targetchannel != self) { socketchannel dest = (socketchannel)targetchannel; //将信息存储到buffer bytebuffer buffer = bytebuffer.wrap(msg.getbytes()); //将buffer数据写入通道 dest.write(buffer); } } } public static void main(string[] args) { //创建服务器对象 nioserver nioserver = new nioserver(); nioserver.listen(); }}
2、客户端package com.nezha.guor.nio;import java.io.ioexception;import java.net.inetsocketaddress;import java.nio.bytebuffer;import java.nio.channels.selectionkey;import java.nio.channels.selector;import java.nio.channels.socketchannel;import java.util.iterator;import java.util.scanner;public class nioclient { private final int port = 8080; //服务器端口 private selector selector; private socketchannel socketchannel; private string username; public nioclient() throws ioexception { selector = selector.open(); socketchannel = socketchannel.open(new inetsocketaddress(127.0.0.1, port)); //设置非阻塞 socketchannel.configureblocking(false); //将channel注册到selector socketchannel.register(selector, selectionkey.op_read); username = socketchannel.getlocaladdress().tostring().substring(1); system.out.println(username + is ok...); } //向服务器发送消息 public void sendinfo(string info) { info = username + 说: + info; try { socketchannel.write(bytebuffer.wrap(info.getbytes())); }catch (ioexception e) { system.out.println(sendinfo error:+e.getmessage()); } } //读取从服务器端回复的消息 public void readinfo() { try { int readchannels = selector.select(); if(readchannels > 0) { iterator<selectionkey> iterator = selector.selectedkeys().iterator(); while (iterator.hasnext()) { selectionkey key = iterator.next(); if(key.isreadable()) { //得到相关的通道 socketchannel sc = (socketchannel) key.channel(); //得到一个buffer bytebuffer buffer = bytebuffer.allocate(1024); //读取 sc.read(buffer); //把读到的缓冲区的数据转成字符串 string msg = new string(buffer.array()); system.out.println(msg.trim()); } } iterator.remove(); //删除当前的selectionkey, 防止重复操作 } else { system.out.println(没有可以用的通道...); } }catch (exception e) { system.out.println(readinfo error:+e.getmessage()); } } public static void main(string[] args) throws exception { nioclient nioclient = new nioclient(); new thread() { public void run() { while (true) { nioclient.readinfo(); try { thread.currentthread().sleep(2000); }catch (interruptedexception e) { system.out.println(sleep error:+e.getmessage()); } } } }.start(); //发送数据给服务器端 scanner scanner = new scanner(system.in); while (scanner.hasnextline()) { nioclient.sendinfo(scanner.nextline()); } }}
3、控制台输出
推荐学习:《java教程》
以上就是带你完全掌握java nio(总结分享)的详细内容。