NIO

慕课网课程链接:https://www.imooc.com/video/19331
Java 知识目录:https://blog.csdn.net/qq_38009970/article/details/103266870

NIO

  • 用于应对高性能高并发的场景

BIO 的网络模型缺点

  • 阻塞式 IO 模型
  • 弹性伸缩能力差
  • 多线程耗资源

NIO网络模型

  • 主要分为两步:
    • 建立连接
    • 发送请求
      在这里插入图片描述

NIO 核心

Channel
  • 双向性
  • 非阻塞性
  • 操作唯一性
实现类
  • 文件类: FileChannel
  • UDP类:DatagramChannel
  • TCP类:ServerSocketChannel/SocketChannel
Channel 使用:ServerSocketChannel && SocketChannel
//服务端通过服务器socket创建channel
ServerSocketChannel serverSocketChannel = new ServerSocketChannel.open();
//服务器端绑定端口
serverSocketChannel.bind(new InetSocketAddress(8000));
//服务器监听客户端连接,建立 socketChannel 连接
SocketChannel socketChannel = serverSocketChanel.accept();
//客户端连接远程主机及端口
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8000));

Buffer
  • 读写 Channel 中的数据
  • 本质是一块内存区域
  • 所有基本类型都有其对应的 Buffer
属性
  • Capacity:容量
    • 表明 byte 数组可以容纳的字节数
  • Position:位置
    • 写数据时,表示当前位置,初始位置为 0。当一个数据写入到buffer后,position会向后移动到可写的位置。最大值为容量 - 1
    • 读数据时,position会被重置为0。当从中读取数据时,positon会向后移动到可读的位置
  • Limit:上限
    • 写模式下,表示最多能向buffer 中写入多少数据。此时Limit = Capacity
    • 读模式下,表示最多能从buffer中读取多少数据。此时Limit = 写模式下的position值
  • Mark:标记
    • 存储一个特定的 position 位置。可以调用 Buffered.reset 方法获取这个位置
API使用 :以 ByteBuffer 为例
Buffer API
Buffer flip() 	翻转这个缓冲区。  
Buffer mark() 	将此缓冲区的标记设置在其位置。  
Buffer clear() 	清除此缓冲区。  

ByteBuffer  API:
static ByteBuffer allocate(int capacity)  			分配一个新的字节缓冲区。  
ByteBuffer put(byte[] src, int offset, int length) 	相对大容量 put方法 (可选操作) 。  

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Selector
  • 作用:I/O 的就绪与选择
  • 是 NIO 网络编程的基础
SelectonKey 状态
  • OP_ACCEPT 操作集位用于插座接受操作。
  • OP_CONNECT 用于套接字连接操作的操作集位。
  • OP_READ 读操作的操作位。
  • OP_WRITE 写操作的操作位。
使用
//创建Selector
Selector selector = new Selector.open();
//将 channel 注册到 selector 上,监听读就绪事件
SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_READ);
//阻塞等待 channel 有就绪事件发生
int selectNum = selector.select();
//获取就绪事件的 channel 集合
Set<SelectionKey> selectionkeys = selector.selectedKeys();

NIO编程实现

实现步骤
服务器端
  1. 创建 Selector
  2. 创建 ServerSocketChannel,并绑定监听端口
  3. Channel设置为非阻塞模式
  4. Channel 注册到 Selector 上,监听链接事件
  5. 循环调用Selectorselect方法,检测就绪情况
  6. 调用 selectedKey 方法获取就绪 channel 集合
  7. 判断就绪事件总类,调用业务处理方法
  8. 根据业务需要决定是否再次注册监听事件,重复执行第 3 步操作

代码示例:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;

public class NioServer {

    public void start() throws Exception {
        //1. 创建 `Selector`
        Selector selector = Selector.open();
        //2. 创建 `ServerSocketChannel`,并绑定监听端口
        ServerSocketChannel channel = ServerSocketChannel.open();
        channel.bind(new InetSocketAddress(8000));
        //3. 将`Channel`设置为非阻塞模式
        channel.configureBlocking(false);
        //4. 将`Channel` 注册到 `Selector` 上,监听链接事件
        channel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务器启动成功");
        //5. 循环调用`Selector`的`select`方法,检测就绪情况
        for(;;){
            //TODO 获取可用连接的数量
            int readChannel = selector.select();
            //TODO 为什么加这一句
            if(readChannel == 0) continue;
            //6. 调用 `selectedKey` 方法获取就绪 `channel` 集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                //获取 SelectionKey 实例
                SelectionKey selectionKey = iterator.next();
                //移除当前的 selectionKey
                iterator.remove();
                //7. 判断就绪事件总类,调用业务处理方法
                //如果是接入事件
                if(selectionKey.isAcceptable()){
                    acceptHandler(channel,selector);
                }
                //如果是可读事件
                if(selectionKey.isReadable()){
                    readHandler(selectionKey,selector);
                }
            }

        }


    }

    /**
     * 接入事件处理器
     */
    public void acceptHandler(ServerSocketChannel serverSocketChannel,Selector selector) throws Exception{
        //如果是接入事件,创建socketChannel
        SocketChannel socketChannel = serverSocketChannel.accept();
        //将socketChannel设置为非阻塞的工作模式
        socketChannel.configureBlocking(false);
        //将ServerSocketChannel注册到selector上,监听可读事件
        socketChannel.register(selector,SelectionKey.OP_READ);
        //回复给客户端信息
        socketChannel.write(Charset.forName("UTF-8").encode("你与聊天室里其他人都不是朋友关系,请注意隐私安全"));
    }

    /**
     * 可读事件处理器
     */
    public void readHandler(SelectionKey selectionKey,Selector selector)throws Exception{
        //从 socketChannel 中获取已就绪的channel
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        //创建buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        //循环读取客户端的请求信息
        String request = "";
        while (socketChannel.read(byteBuffer)>0){
            //切换buffer模式
            byteBuffer.flip();
            //读取buffer中的内容
            request += Charset.forName("UTF-8").decode(byteBuffer);
        }
        //将socketChannel再次注册到selector上,监听它的可读事件
        socketChannel.register(selector,SelectionKey.OP_READ);
        //将客户端发送的请求消息发送给其他客户端
        if(request.length()>0){
            broadCast(selector,socketChannel,request);
        }
    }

    public void broadCast(Selector selector,
                          SocketChannel sourceChannel,
                          String request){
        //获取已接入的客户端Channel
        Set<SelectionKey> selectionKeys = selector.keys();
        selectionKeys.forEach(selectionKey -> {
            Channel channel = selectionKey.channel();
            if(channel instanceof SocketChannel && channel != sourceChannel){
                try {
                    ((SocketChannel)channel).write(Charset.forName("UTF-8").encode(request));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

    }

    public static void main(String[] args) throws Exception {
        NioServer nioServer = new NioServer();
        nioServer.start();
    }

}

客户端
  1. 连接服务器
  2. 获取服务器响应数据
  3. 向服务器发送数据

代码实现
NioClient

public class NioClient {
    public void start(String nickname) throws Exception{
        //连接服务器
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8000));
        System.out.println("客户端启动成功!");
        //获取服务端响应数据
        Selector selector = Selector.open();
        socketChannel.configureBlocking(false);
        socketChannel.register(selector, SelectionKey.OP_READ);
        new Thread(new NioClientHandler(selector)).start();
        //向服务器端发送数据
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()){
            String request = scanner.nextLine();
            if(request != null && request.length()>0){
                socketChannel.write(Charset.forName("UTF-8").encode(nickname + " : " + request));
            }
        }

    }		
	public static void main(String[] args) throws Exception {
        NioClient nioClient = new NioClient();
        nioClient.start("A");
    }	

}

ClientHandler
作用:获取服务端响应数据。和 NioClient for循环的代码一致

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;

public class NioClientHandler implements Runnable{
    private Selector selector;

    public NioClientHandler(Selector selector) {
        this.selector = selector;
    }

    @Override
    public void run() {
        //循环调用selector接口,检测就绪状态
        try {
            for(;;){
                int select = selector.select();
                if(select == 0) continue;
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()){
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();
                    if(selectionKey.isReadable()){
                        readHandler(selectionKey,selector);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void readHandler(SelectionKey selectionKey,Selector selector) throws IOException {
        SocketChannel channel = (SocketChannel) selectionKey.channel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        StringBuffer response = new StringBuffer(new String(""));
        while (channel.read(byteBuffer) > 0){
            byteBuffer.flip();
            response.append(Charset.forName("UTF-8").decode(byteBuffer));
        }
        channel.register(selector,SelectionKey.OP_READ);
        if(response.length()>0){
            System.out.println(response);
        }
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值