您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

带你完全掌握Java NIO(总结分享)

2024/3/16 21:45:45发布18次查看
本篇文章给大家带来了关于java的相关知识,其中主要介绍了nio的相关问题,包括了nio核心、bio与nio比较、通过nio实现简单的服务端客户端通信,希望对大家有帮助。
推荐学习:《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(总结分享)的详细内容。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product