JAVA/NIO

IO流

流的概念和作用

流的本质:数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作

作用:为数据源和目的地建立一个输送通道

Java IO所采用的模型

Java的IO模型设计非常优秀,它使用Decorator(装饰者)模式,按功能划分Stream,您可以动态装配这些Stream,以便获得您需要的功能。

IO流的分类

按数据流的方向分为 输入流、输出流

按处理数据单位不同分为 字节流、字符流  

按功能不同分为 节点流、处理流

Java 的 I/O 操作类在包 java.io 下,大概有将近 80 个类,但是这些类大概可以分成四组

 

基本的抽象流类型,所有的流都继承自如下四个接口

     输入流      输出流

字节流  InputStream  outputStream

字符流  Reader      Writer

 

NIO

一种叫非阻塞IO(Non-blocking I/O),另一种也叫新的IO(New I/O),其实是同一个概念。

它是一种同步非阻塞的I/O模型,也是I/O多路复用的基础

 

IO分阻塞型IO和非阻塞型IO(NIO)

阻塞型IO在读取数据时,如果数据未到达,会一直阻塞到读取到数据为止,所以称为阻塞型IO,在高并发的环境下性能不佳。

NIO不是使用 “流” 来作为数据的传输方式,而是使用通道,通道的数据传输是双向的,且NIO的数据写入和读取都是异步的,不会阻塞线程,所以称为非阻塞型IO,在高并发的环境下性能很好。

 

NIO主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector。

Channel
Channel(通道)表示到实体,如硬件设备、文件、网络套接字或可以执行一个或多个不同 I/O 操作(如读取或写入)的程序组件的开放的连接。Channel接口的常用实现类有FileChannel(对应文件IO)、DatagramChannel(对应UDP)、SocketChannel和ServerSocketChannel(对应TCP的客户端和服务器端)。Channel和IO中的Stream(流)是差不多一个等级的。只不过Stream是单向的,譬如:InputStream, OutputStream.而Channel是双向的,既可以用来进行读操作,又可以用来进行写操作。

这些是Java NIO中最重要的通道的实现:

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

FileChannel 从文件中读写数据。

DatagramChannel 能通过UDP读写网络中的数据。

SocketChannel 能通过TCP读写网络中的数据。

ServerSocketChannel可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。

Selector(选择器)介绍

Selector 一般称 为选择器 ,当然你也可以翻译为 多路复用器 。它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。

使用Selector的好处在于: 使用更少的线程来就可以来处理通道了, 相比使用多个线程,避免了线程上下文切换带来的开销。

Buffer

使用Buffer读写数据一般遵循以下四个步骤:

  1. 写入数据到Buffer
  2. 调用flip()方法
  3. 从Buffer中读取数据
  4. 调用clear()方法或者compact()方法

当向buffer写入数据时,buffer会记录下写了多少数据。一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式。在读模式下,可以读取之前写入到buffer的所有数据。

一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用clear()或compact()方法。clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。

 

服务端代码

package nio;


import java.io.IOException; 
import java.net.InetSocketAddress; 
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey; 
import java.nio.channels.Selector; 
import java.nio.channels.ServerSocketChannel; 
import java.nio.channels.SocketChannel; 
import java.util.*; 
import java.util.concurrent.ConcurrentHashMap; 
 
public class Server { 
    private Selector selector; 
    private ByteBuffer readBuffer = ByteBuffer.allocate(1024);//调整缓存的大小可以看到打印输出的变化 
    private ByteBuffer sendBuffer = ByteBuffer.allocate(1024);//调整缓存的大小可以看到打印输出的变化 
 
    String str;
    public void start() throws IOException {
        // 打开服务器套接字通道 
        ServerSocketChannel ssc = ServerSocketChannel.open(); 
        // 服务器配置为非阻塞 
        ssc.configureBlocking(false); 
        // 进行服务的绑定 
        ssc.bind(new InetSocketAddress("localhost", 8001)); 
        
        // 通过open()方法找到Selector
        selector = Selector.open(); 
        // 注册到selector,等待连接
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        
        while (!Thread.currentThread().isInterrupted()) { 
            selector.select(); 
            Set<SelectionKey> keys = selector.selectedKeys(); 
            Iterator<SelectionKey> keyIterator = keys.iterator(); 
            while (keyIterator.hasNext()) { 
                SelectionKey key = keyIterator.next(); 
                if (!key.isValid()) { 
                    continue; 
                } 
                if (key.isAcceptable()) { 
                    accept(key); 
                } else if (key.isReadable()) { 
                    read(key); 
                } else if (key.isWritable()) {
                    write(key);
                }
                keyIterator.remove(); //该事件已经处理,可以丢弃
            } 
        } 
    }

    private void write(SelectionKey key) throws IOException, ClosedChannelException {
        SocketChannel channel = (SocketChannel) key.channel();
        System.out.println("write:"+str);
        
        sendBuffer.clear();
        sendBuffer.put(str.getBytes());
        sendBuffer.flip();
        channel.write(sendBuffer);
        channel.register(selector, SelectionKey.OP_READ);
    } 
 
    private void read(SelectionKey key) throws IOException { 
        SocketChannel socketChannel = (SocketChannel) key.channel(); 
 
        // Clear out our read buffer so it's ready for new data 
        this.readBuffer.clear(); 
//        readBuffer.flip();
        // Attempt to read off the channel 
        int numRead; 
        try { 
            numRead = socketChannel.read(this.readBuffer); 
        } catch (IOException e) { 
            // The remote forcibly closed the connection, cancel 
            // the selection key and close the channel. 
            key.cancel(); 
            socketChannel.close(); 
            
            return; 
        } 
        
        str = new String(readBuffer.array(), 0, numRead);
        System.out.println(str);
        socketChannel.register(selector, SelectionKey.OP_WRITE);
    } 
 
    private void accept(SelectionKey key) throws IOException { 
        ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); 
        SocketChannel clientChannel = ssc.accept(); 
        clientChannel.configureBlocking(false); 
        clientChannel.register(selector, SelectionKey.OP_READ); 
        System.out.println("a new client connected "+clientChannel.getRemoteAddress()); 
    } 
 
    public static void main(String[] args) throws IOException { 
        System.out.println("server started..."); 
        new Server().start(); 
    } 
} 

客户端代码
package 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; 
import java.util.Set; 
 
 
public class Client { 
 
    ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
    ByteBuffer readBuffer = ByteBuffer.allocate(1024);
    
    public void start() throws IOException { 
        // 打开socket通道  
        SocketChannel sc = SocketChannel.open(); 
        //设置为非阻塞
        sc.configureBlocking(false); 
        //连接服务器地址和端口
        sc.connect(new InetSocketAddress("localhost", 8001)); 
        //打开选择器
        Selector selector = Selector.open(); 
        //注册连接服务器socket的动作
        sc.register(selector, SelectionKey.OP_CONNECT); 
        
        Scanner scanner = new Scanner(System.in); 
        while (true) { 
            //选择一组键,其相应的通道已为 I/O 操作准备就绪。  
            //此方法执行处于阻塞模式的选择操作。
            selector.select();
            //返回此选择器的已选择键集。
            Set<SelectionKey> keys = selector.selectedKeys(); 
            System.out.println("keys=" + keys.size()); 
            Iterator<SelectionKey> keyIterator = keys.iterator(); 
            while (keyIterator.hasNext()) { 
                SelectionKey key = keyIterator.next(); 
                keyIterator.remove(); 
                // 判断此通道上是否正在进行连接操作。 
                if (key.isConnectable()) { 
                    sc.finishConnect(); 
                    sc.register(selector, SelectionKey.OP_WRITE); 
                    System.out.println("server connected..."); 
                    break; 
                } else if (key.isWritable()) { //写数据 
                    System.out.print("please input message:"); 
                    String message = scanner.nextLine(); 
                    //ByteBuffer writeBuffer = ByteBuffer.wrap(message.getBytes()); 
                    writeBuffer.clear();
                    writeBuffer.put(message.getBytes());
                    //将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
                    writeBuffer.flip();
                    sc.write(writeBuffer); 
                    
                    //注册写操作,每个chanel只能注册一个操作,最后注册的一个生效
                    //如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来
                    //int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
                    //使用interest集合
                    sc.register(selector, SelectionKey.OP_READ);
                    sc.register(selector, SelectionKey.OP_WRITE);
                    sc.register(selector, SelectionKey.OP_READ);
                    
                } else if (key.isReadable()){//读取数据
                    System.out.print("receive message:");
                    SocketChannel client = (SocketChannel) key.channel();
                    //将缓冲区清空以备下次读取 
                    readBuffer.clear();
                    int num = client.read(readBuffer);
                    System.out.println(new String(readBuffer.array(),0, num));
                    //注册读操作,下一次读取
                    sc.register(selector, SelectionKey.OP_WRITE);
                }
            } 
        } 
    } 
 
    public static void main(String[] args) throws IOException { 
        new Client().start(); 
    } 
}


https://blog.csdn.net/chengkui1990/article/details/81558522

----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- 

IO同步阻塞接受客户端请求会造成阻塞,可以使用多线程实现非阻塞

缺点:在高并发情况下多线程上下文切换资源浪费严重,非高并发环境可以使用线程池解决。

NIO同步非阻塞实现不阻塞

NIO多路复用,客户端线程将请求注册到select上面,服务端工作线程轮训处理,

客户端A发送请求给服务器,服务器的有专门一个线程处理请求。得到请求后,把这个请求分派给工作线程处理 读和写,而请求线程就直接返回了。

这里的重点是:客户端A发送请求给请求线程了,请求线程把请求交给工作线程处理了。请求线程将任务分派给别的任务之后就空闲了,此时请求线程又可以处理别的请求了(不管前一个工作线程是否处理完)。

比如客户端A提交了一次IO请求,可能此次IO请求在上述所说的工作线程没处理完,但是 客户端A还能发出IO请求,而服务器同样能接收这个请求(因为请求线程是空闲的)。此时,客户端请求服务器来说,我们就认为是非阻塞的。

学习视频:

https://www.bilibili.com/video/BV1DJ411m7NR?p=4 

 

 

NIO设计思路:(单线程解决并发)

单线程处理并发,bio并发消息会在读的时候阻塞,只能通过多线程解。

nio通过selecto实现非阻塞,客户端发送数据到selecto,服务端循环selecto判断有没有数据,有就处理,没有就不处理,实现非阻塞。

Selector(限制器,也叫多路复用器)

posted @ 2020-07-30 15:08  91程序猿  阅读(152)  评论(0)    收藏  举报