• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

第二十二篇Java NIO重点

武飞扬头像
sunnyday0426
帮助1

1.1 JAVA NIO简介

由于之前老的I/O类库是阻塞I/O,New I/O类库的目标就是要让Java支持非阻塞I/O,所以,更多的人喜欢称之为非阻塞I/O(Non-block I/O)

java 1.4版本推出了一种新型的IO API,与原来的IO具有相同的作用和目的;可代替标准java IO,只是实现的方式不一样,NIO是面向缓冲区、基于通道的IO操作;通过NIO可以提高对文件的读写操作。基于这种优势,现在使用NIO的场景越来愈多,很多主流行的框架都使用到了NIO技术,如Tomcat、Netty、Jetty等。

1.2 NIO与传统IO的对比

NIO IO
面向缓冲区Buffer 面向流Stream
双向(基于通道Channel) 单向(分别建立输入流、输出流)
同步非阻塞(non-blocking) 同步阻塞
选择器(Selector,多路复用)
支持字符集编码解码解决方案,支持锁,支持内存映射文件的文件访问接口

1.3 NIO原理详解

NIO是当前Java中最流行的IO方式,大名鼎鼎的网络框架Netty就是基于NIO

NIO中有三个核心:

1.3.1 Buffer简介

Buffer就是缓冲池。Buffer和Channel配合使用。

1、将Channel中的数据读取到Buffer中。
2、将Buffer中的数据写入到Channel中。

Java NIO 有以下Buffer类型

ByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
MappedByteBuffer

Buffer详解

Buffer就是一个数组缓冲池。
Buffer有两种模式,往Buffer中写数据,
或者从Buffer中读取数据。
读写模式通过flip()函数切换。
Buffer默认为写模式。

Buffer主要通过三个变量和一个切换函数flip()来维护。

1、position
当前第一个可以读或者写的下标
2、limit
当前可读或者可写的最后一位下标 1
3、capacity
Buffer的容量,也就是数组长度。
4、flip()
flip()用来切换读写模式。
当从写切换读之后,position和limit之间的数据是之前写的数据,可以进行读取。
当从读切换写之后,postion和limit之间才可以被写数据。

public final Buffer flip() {
    limit = position;
    position = 0;
    return this;
}

主要方法

get(): 读取一个byte,将position
get(byte[] bytes,int offeset,int length): 将buffer中的[offset,offset length)复制到bytes中,position =length
put(byte[] bytes): 往buffer中写入bytes,position = bytes.length
clear(): 将buffer清除,position=0,limit=capacity
compact(): 假设还有n个字节未被读取,就将这n个字节搬运到数组头部,从数组的第n位开始写。
remaining(): 返回limit - position,也就是当前还是多少字节未读取或者未写

1.3.2 Channel

Channel是基于流的改进,Channel是面向缓冲区的,并且需要与另一个Buffer配和使用,我的猜测是将数据弄了一个缓冲区,然后通过Buffer将数据拷贝到缓冲区或者从缓冲区里将数据拷贝到Buffer中。

与流有以下区别

  Channel
是否支持异步 支持异步 不支持异步
是否支持双向储传输数据 双向 单向
原理 缓冲区
性能
是否集合Buffer使用 结合Buffer使用 不用

网络IO中Channel主要有以下两大类:

1.3.2.1 ServerSocketChannel

用于服务器的Channel,负责接收客户端SocketChannel的连接

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//绑定端口
serverSocketChannel.socket().bind(new InetSocketAddress(9999));

while(true){
    //聆听新连接,如果调用configureBlocking(false)的话,accept不阻塞,如果没有相关数据就返回null
    //默认为阻塞
    SocketChannel socketChannel = serverSocketChannel.accept();

    //非阻塞模式下
	if(socketChannel != null){
        //do something with socketChannel...
    }

}

1.3.2.2 SocketChannel

用于接收客户端SocketChannel的数据传输

SocketChannel socketChannel = SocketChannel.open();
//连接
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

//读取数据
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);


//写入数据
String newData = "New String to write to file..."   
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();

while(buf.hasRemaining()) {
    channel.write(buf);
}
学新通

1.3.3 Selector

NIO的优势就是利用单线程来处理多个Socket。其原理就是依靠Selector

Selector就是IO复用中的监视器,一个Selector可以监视多个Socket,Socket需要将其感兴趣的事件注册给Selector。当对应的Socket事件发生后,会将其加入到Selector对应的队列中,然后会将Selector唤醒,执行其业务代码。

Socket共有四个事件:

1、CONNECT
2、ACCEPT
3、READ
4、WRITE

通道触发了一个事件意思是该事件已经就绪。所以,某个channel成功连接到另一个服务器称为“CONNECT”。一个server socket channel准备好接收新进入的连接称为“ACCEPT”。一个有数据可读的通道可以说是“READ”。等待写数据的通道可以说是“WRITE”

具体的步骤就看下面的代码吧。

1.3 代码

1.3.1 服务器端

public class ServerConnect
{
    private static final int BUF_SIZE=1024;
    private static final int PORT = 8080;
    private static final int TIMEOUT = 3000;
    public static void main(String[] args)
    {
        selector();
    }
	//如果是新连接
    public static void handleAccept(SelectionKey key) throws IOException{
        //获取其请求的ServerSocketChannel
        ServerSocketChannel ssChannel = (ServerSocketChannel)key.channel();
        //为客户端新建SocketChannel
        SocketChannel sc = ssChannel.accept();
        //配置非阻塞
        sc.configureBlocking(false);
        //为其注册Selector,监听事件为READ
        sc.register(key.selector(), SelectionKey.OP_READ);
    }

   //读取数据
    public static void handleRead(SelectionKey key) throws IOException{
        SocketChannel sc = (SocketChannel)key.channel();
        ByteBuffer buf = ByteBuffer.allocateDirect(BUF_SIZE);
        long bytesRead = sc.read(buf);
        while(bytesRead>0){
            buf.flip();
            while(buf.hasRemaining()){
                System.out.print((char)buf.get());
            }
            System.out.println();
            buf.clear();
            bytesRead = sc.read(buf);
        }
        if(bytesRead == -1){
            sc.close();
        }

	    //如果要是往客户端返回数据的话,可以在此处直接发送,也可以注册写事件,因为如果在此处发的话,有可能缓冲区不可用,可以绑定一个事件,让Selector监控,在缓冲区可用的时候,发送
	    key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
	    //绑定要发送的Buffer
	    key.attach(buffer);
    }
    
    //写数据
    public static void handleWrite(SelectionKey key) throws IOException{
       ByteBuffer buffer = (ByteBuffer) key.attachment();
    	SocketChannel channel = (SocketChannel) key.channel();
    	if (buffer.hasRemaining()) {
        	channel.write(buffer)
    	} else {
        //发送完了就取消写事件,否则下次还会进入该分支
        key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
    	}
    }
    
    public static void selector() {
        Selector selector = null;
        ServerSocketChannel ssc = null;
        try{
            //生成Selector监视器
            selector = Selector.open();
            //生成ServerSocketChannel
            ssc= ServerSocketChannel.open();
            //为ServerSocket绑定端口
            ssc.socket().bind(new InetSocketAddress(PORT));
            //ServerSocket与Selector搭配使用时,必须使用非阻塞模式
            ssc.configureBlocking(false);
            //将ServerSocketChannel注册到Selector上,其关心的事件时ACCEPT,也就是有新的SocketChannel连接的时候
            ssc.register(selector, SelectionKey.OP_ACCEPT);
            //死循环来监视Socket
            while(true){
                //如果超时了还没有Socket事件发生,就继续
                if(selector.select(TIMEOUT) == 0){
                    System.out.println("==");
                    continue;
                }
                //获取发生的Socket事件
                Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                //遍历Socket事件
                while(iter.hasNext()){
                    //获得对应的事件
                    SelectionKey key = iter.next();
                    //如果是客户端SocketChannel来连接
                    if(key.isAcceptable()){
                        handleAccept(key);
                    }
                    //如果是有客户端SocketChannel发来数据
                    if(key.isReadable()){
                        handleRead(key);
                    }
                    //如果是写数据
                    if(key.isWritable() && key.isValid()){
                        handleWrite(key);
                    }
                    
                    if(key.isConnectable()){
                        System.out.println("isConnectable = true");
                    }
                    iter.remove();
                }
            }
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            try{
                if(selector!=null){
                    selector.close();
                }
                if(ssc!=null){
                    ssc.close();
                }
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }
}
学新通

1.3.2 客户端代码

//打开通道
SocketChannel socketChannel = SocketChannel.open();

//连接远程主机
socketChannel.connect(new InetSocketAddress("127.0.0.1",8080));
//循环处理
ByteBuffer buffer = ByteBuffer.allocate(1024);
int r = 0;
while ( (r = channel.read(buffer)) > 0){
    buffer.flip();
    byte[] bytes1 = new byte[buffer.remaining()];
    buffer.get(bytes1);
    System.out.println(new String(bytes1));

1.4 总结

NIO的网络通信(Selector的核心应用)

1.4.1 三大核心

通道(channel): 负责管道节点的连接及数据的运输
缓冲区(buffer): 负责数据的存取
选择器(selector): 是selectableChannel的多路复用器,用于监控SelectableChannel的IO状况。

阻塞与非阻塞

  • 传统的 IO 流都是阻塞式的。也就是说,当一个线程调用 read() 或 write()时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务。因此,在完成网络通信进行 IO 操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,当服务器端需要处理大量客户端时,性能急剧下降。
  • Java NIO 是非阻塞模式的。当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。因此,NIO 可以让服务器端使用一个或有限几个线程来同时处理连接到服务器端的所有客户端。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgcbcif
系列文章
更多 icon
同类精品
更多 icon
继续加载