Java NIO模型(提供代码示例)

news2025/1/31 8:26:31

目录

    • 一、NIO特点介绍
    • 二、NIO代码实现
      • 2.1、客户端代码
      • 2.2、服务端代码

一、NIO特点介绍

  • NIO全称 java non-blocking IO。从JDK 1.4开始,java提供了一些列改进的输入/输出(I/O)的新特性,被称为NIO,是同步非阻塞的,NIO相关类都被放在java.nio包及其子包下。
  • NIO是面向缓冲区的,或者面向块编程的,数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区内前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞的高伸缩性网络
  • Java NIO的非阻塞模式,使一个线程从某通道发送或者读取数据,但是它仅能得到目前可用的数据,如果目前没有可用的数据时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可读取之前,该线程可以继续做其他事情。非阻塞就是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
  • NIO三大核心部分:Channel(通道),Buffer(缓冲区),Selector(选择器)
    • 在线程处理过程中,如果涉及到IO操作,那么当前的线程不会被阻塞,而是会去处理其他业务代码,然后等过段时间再来查询 IO 交互是否完成。如下图:Buffer 是一个缓冲区,用来缓存读取和写入的数据;Channel 是一个通道,负责后台对接 IO 数据;而 Selector 实现的主要功能,是主动查询哪些通道是处于就绪状态。Selector复用一个线程,来查询已就绪的通道,这样大大减少 IO 交互引起的频繁切换线程的开销.

在这里插入图片描述

二、NIO代码实现

2.1、客户端代码

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 NioClient implements Runnable {
    private String host;
    private int port;
    private volatile boolean started;
    private Selector selector;
    private SocketChannel socketChannel;

    public NioClient(String ip, int port) {
        this.host = ip;
        this.port = port;

        try {
            /*创建选择器的实例*/
            selector = Selector.open();
            /*创建ServerSocketChannel的实例*/
            socketChannel = SocketChannel.open();
            /*设置通道为非阻塞模式*/
            socketChannel.configureBlocking(false);

            started = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        try {
            doConnect();
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }

        //循环遍历selector
        while (started) {
            try {
                //无论是否有读写事件发生,selector每隔1s被唤醒一次
                selector.select(1000);
                //获取当前有哪些事件可以使用
                Set<SelectionKey> keys = selector.selectedKeys();
                //转换为迭代器
                Iterator<SelectionKey> it = keys.iterator();
                SelectionKey key = null;
                while (it.hasNext()) {
                    key = it.next();
                    /*我们必须首先将处理过的 SelectionKey 从选定的键集合中删除。
                    如果我们没有删除处理过的键,那么它仍然会在主集合中以一个激活
                    的键出现,这会导致我们尝试再次处理它。*/
                    it.remove();
                    try {
                        if (key.isValid()) {
                            //连接事件
                            if (key.isConnectable()) {
                                connectHandler(key);
                            }
                            //读事件
                            if (key.isReadable()) {
                                readHandler(key);
                            }
                        }
                    } catch (Exception e) {
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null) {
                                key.channel().close();
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                System.exit(1);
            }
        }
        //selector关闭后会自动释放里面管理的资源
        if (selector != null)
            try {
                selector.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
    }

    private void connectHandler(SelectionKey key) throws IOException {
        //获得关心当前事件的channel
        SocketChannel sc = (SocketChannel) key.channel();
        if (sc.finishConnect()) {
            socketChannel.register(selector,
                    SelectionKey.OP_READ);
        } else System.exit(1);
    }

    private void readHandler(SelectionKey key) throws IOException {
        //获得关心当前事件的channel
        SocketChannel sc = (SocketChannel) key.channel();
        //创建ByteBuffer,并开辟一个1M的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //读取请求码流,返回读取到的字节数
        int readBytes = sc.read(buffer);
        //读取到字节,对字节进行编解码
        if (readBytes > 0) {
            //将缓冲区当前的limit设置为position,position=0,
            // 用于后续对缓冲区的读取操作
            buffer.flip();
            //根据缓冲区可读字节数创建字节数组
            byte[] bytes = new byte[buffer.remaining()];
            //将缓冲区可读字节数组复制到新建的数组中
            buffer.get(bytes);
            String result = new String(bytes, "UTF-8");
            System.out.println("客户端收到消息:" + result);
        }
        //链路已经关闭,释放资源
        else if (readBytes < 0) {
            key.cancel();
            sc.close();
        }
    }

    private void doWrite(SocketChannel channel, String request)
            throws IOException {
        //将消息编码为字节数组
        byte[] bytes = request.getBytes();
        //根据数组容量创建ByteBuffer
        ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
        //将字节数组复制到缓冲区
        writeBuffer.put(bytes);
        //flip操作
        writeBuffer.flip();
        //发送缓冲区的字节数组
        /*关心事件和读写网络并不冲突*/
        channel.write(writeBuffer);
    }

    private void doConnect() throws IOException {
        /*非阻塞的连接*/
        if (socketChannel.connect(new InetSocketAddress(host, port))) {
            socketChannel.register(selector, SelectionKey.OP_READ);
        } else {
            socketChannel.register(selector, SelectionKey.OP_CONNECT);
        }
    }

    //写数据对外暴露的API
    public void sendMsg(String msg) throws Exception {
        doWrite(socketChannel, msg);
    }

    public void stop() {
        started = false;
    }


    public static void main(String[] args) throws Exception {

        NioClient nioClient = new NioClient("127.0.0.1", 9998);
        new Thread(nioClient).start();
        System.out.println("请输入消息:");
        Scanner scanner = new Scanner(System.in);
        while (true) {
            nioClient.sendMsg(scanner.next());
        }
    }
}

2.2、服务端代码

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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NioServer {
    private Selector selector;
    private ServerSocketChannel serverChannel;
    private volatile boolean started;

    /**
     * 构造方法
     *
     * @param port 指定要监听的端口号
     */
    public NioServer(int port) {
        try {
            //创建选择器
            selector = Selector.open();
            //打开监听通道
            serverChannel = ServerSocketChannel.open();
            //如果为 true,则此通道将被置于阻塞模式;
            // 如果为 false,则此通道将被置于非阻塞模式
            serverChannel.configureBlocking(false);//开启非阻塞模式
            //绑定端口 backlog设为1024
            serverChannel.socket()
                    .bind(new InetSocketAddress(port), 1024);
            //监听客户端连接请求
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            //标记服务器已开启
            started = true;
            System.out.println("服务器已启动,端口号:" + port);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    public void run() {
        //循环遍历selector
        while (started) {
            try {
                //阻塞,只有当至少一个注册的事件发生的时候才会继续.
                selector.select();
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> it = keys.iterator();
                SelectionKey key = null;
                while (it.hasNext()) {
                    key = it.next();
                    System.out.println("当前通道的事件:" + key.interestOps());
                    it.remove();
                    try {
                        if (key.isValid()) {
                            //处理新接入的请求消息
                            if (key.isAcceptable()) {
                                acceptHandle(key);
                            }
                            //读消息
                            if (key.isReadable()) {
                                readHandler(key);
                            }
                            //写消息
                            if (key.isWritable()) {
                                writHandler(key);
                            }
                        }

                    } catch (Exception e) {
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null) {
                                key.channel().close();
                            }
                        }
                    }
                }
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }
        //selector关闭后会自动释放里面管理的资源
        if (selector != null)
            try {
                selector.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
    }

    private void acceptHandle(SelectionKey key) throws IOException {
        //获得关心当前事件的channel
        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
        //通过ServerSocketChannel的accept创建SocketChannel实例
        //完成该操作意味着完成TCP三次握手,TCP物理链路正式建立
        SocketChannel sc = ssc.accept();
        System.out.println("======socket channel 建立连接=======");
        //设置为非阻塞的
        sc.configureBlocking(false);
        //连接已经完成了,可以开始关心读事件了
        sc.register(selector, SelectionKey.OP_READ);
    }

    private void readHandler(SelectionKey key) throws IOException {
        System.out.println("======socket channel 数据准备完成," +
                "可以去读==读取=======");
        SocketChannel sc = (SocketChannel) key.channel();
        //创建ByteBuffer,并开辟一个1M的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(2);
        //读取请求码流,返回读取到的字节数
        int readBytes = sc.read(buffer);
        //读取到字节,对字节进行编解码
        if (readBytes > 0) {
            //将缓冲区当前的limit设置为position,position=0,
            // 用于后续对缓冲区的读取操作
            buffer.flip();
            //根据缓冲区可读字节数创建字节数组
            byte[] bytes = new byte[buffer.remaining()];
            //将缓冲区可读字节数组复制到新建的数组中
            buffer.get(bytes);
            String message = new String(bytes, "UTF-8");
            System.out.println("服务器收到消息:" + message);
            //处理数据 构建应答消息(这里只简单响应,有需要自行额外处理)
            String result = "服务器已经收到 message = " + message;
            //发送应答消息
            doWrite(sc, result);
        }
        //链路已经关闭,释放资源
        else if (readBytes < 0) {
            key.cancel();
            sc.close();
        }
    }

    private void writHandler(SelectionKey key) throws IOException {
        SocketChannel sc = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        if (buffer.hasRemaining()) {
            int count = sc.write(buffer);
            System.out.println("write :" + count
                    + "byte, remaining:" + buffer.hasRemaining());
        } else {
            /*取消对写的注册*/
            key.interestOps(SelectionKey.OP_READ);
        }
    }


    //发送应答消息
    private void doWrite(SocketChannel channel, String response)
            throws IOException {
        //将消息编码为字节数组
        byte[] bytes = response.getBytes();
        //根据数组容量创建ByteBuffer
        ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
        //将字节数组复制到缓冲区
        writeBuffer.put(bytes);
        //flip操作
        writeBuffer.flip();
        channel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ,
                writeBuffer);
    }

    public void stop() {
        started = false;
    }


    public static void main(String[] args) {
        NioServer nioServer = new NioServer(9998);
        nioServer.run();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1094186.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝桥杯 常用STL (C++) 未完待续

动态数组 有些时候想开一个数组&#xff0c;但是却不知道应该开多大长度的数组合适&#xff0c;因为我们需要用到的数组可能会根据情况变动。 这时候我们就需要用到动态数组。所谓动态数组&#xff0c;也就是不定长数组&#xff0c;数组的长度是可以根据我们的需要动态改变的。…

Python数据类型转换

举例&#xff1a;使用Python实现超市的收银系统 name input(‘请输入您要购买商品名称&#xff1a;’) id input(‘请输入您要购买商品编号&#xff1a;’) price input(‘请输入您要购买的商品价格&#xff1a;’) print(f’您购买了{name}&#xff0c;商品编号为{id}&am…

《Deep Residual Learning for Image Recognition》阅读笔记

论文标题 《Deep Residual Learning for Image Recognition》 撑起CV界半边天的论文Residual &#xff1a;主要思想&#xff0c;残差。 作者 何恺明&#xff0c;超级大佬。微软亚研院属实是人才辈出的地方。 初读 摘要 提问题&#xff1a; 更深层次的神经网络更难训练。 …

关于unaipp生成的vue3项目开启微信云函数所遇到的问题

使用uniapp创建的vue3项目&#xff0c;需要用到H5静态页面跳转小程序的时候&#xff08;具体操作看微信开发文档&#xff1a;https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/staticstorage/jump-miniprogram.html&#xff09;&#xff0c;会用到微信开发者工…

Linux基本配置与用户创建

文章目录 一.Linux的配置二.Linux用户的创建 一.Linux的配置 Linux是一款类UNIX的操作系统&#xff0c;一般指的是Linux内核加GUN套件。Linux系统稳定&#xff0c;而且是开源软件&#xff0c;应用范围很广&#xff0c;很受欢迎。为了能学习Linux我们需要配置一个Linux系统&…

LeetCode讲解篇之198. 打家劫舍

LeetCode讲解篇之198. 打家劫舍 文章目录 LeetCode讲解篇之198. 打家劫舍题目描述题解思路题解代码 题目描述 题解思路 该问题可以通过递推来完成 递推公式&#xff1a; 前n间房的最大金额 max&#xff08;前n-1间房的最大金额&#xff0c; 前n-2间房的最大金额第n-1间房的最…

蓝桥杯双周赛算法心得——三带一(暴力枚举)

大家好&#xff0c;我是晴天学长&#xff0c;枚举思想&#xff0c;需要的小伙伴可以关注支持一下哦&#xff01;后续会继续更新的。 1) .三带一 2) .算法思路 1.通过Scanner读取输入的整数n&#xff0c;表示接下来有n个字符串需要处理。 2.使用循环遍历每个字符串&#xff1a;…

2023版 STM32实战10 内部Flash读写

简介和注意事项 -1-STM32内部提供了闪存区域&#xff0c;用来存储用户代码和自定义数据 -2-F1系列的写入最少两字节&#xff08;半字&#xff09; -3-每个地址存8位数据 -4-写入之前需要解锁&#xff0c;写完需要上锁 -5-读取不需要解锁 存储器分布 以下三张图可清楚的…

SLAM从入门到精通(bresenham绘制算法)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们说过&#xff0c;学术界和工业界对于slam的要求是不一样的。前者要求robot在运动的过程中&#xff0c;同步实现定位和制图的操作。但是工业…

罗德施瓦兹频谱仪使用笔记

本文主要介绍罗德频谱仪的两个重要参数RBW和VBW&#xff0c;以及测量设置&#xff0c;以帮助初学者理解频谱解析以及频谱仪的具体使用。 一、什么是RBW和VBW&#xff0c;它们对测量得到结果的影响。 在罗德施瓦兹频谱仪的频谱测量中&#xff0c;有两个参数设置用得比较多&…

机器视觉工程师越学习越谦虚-庆幸自己始终保持清醒

目录 ​这么多年的职场生涯&#xff0c;不断的学习&#xff0c;不断的进取&#xff0c;不断追求新的梦想。一直学习&#xff0c;越学习&#xff0c;越感觉自己的不足&#xff0c;自我感觉越无知&#xff0c;越谦虚。所以孔子讲“不耻下问”、“三人行必有我师”&#xff0c;像释…

芯片学习记录MP2144

MP2144 芯片介绍 MP2144是一款具有内部功率MOSFET的单片降压开关模式转换器。它可以从2.5V到-5.5V的输入电压实现高达2A的连续输出电流&#xff0c;具有出色的负载和线路调节。输出电压可以调节到低至0.6V。 恒定导通时间控制方案提供快速瞬态响应并简化环路稳定。故障状态保…

__main__文件学习测试如下

参考博客 https://www.jb51.net/article/251567.htm test01.py # https://www.jb51.net/article/251567.htmdef step():print(__name__)print("step1 买菜""step2 洗菜""step3 切菜""step4 炒菜")if __name__ __main__:print(准备制…

【统计学概念】初学者指南:了解置信区间

一、说明 什么是置信区间&#xff1f;如何将概率转化成信心度&#xff1f;信心度如何去工作&#xff1f;这些初步的统计概念需要明晰&#xff0c;然后才能应用统计模型&#xff0c;然后是贝叶斯推理&#xff0c;我们将逐步深入这些概念。 二、总体与样本个体统计 总体是研究人…

自定义Docker镜像--Jupyterlab

概述 自定义Jupyterlab镜像&#xff0c;为deployment做准备 步骤 下载基础镜像&#xff1a;centos:7.9.2009 docker search centos:7.9.2009 docker pull centos:7.9.2009 启动容器 部署应用 # 启动容器 docker run -it --name test centos:7.9.2009 bash# 在容器内部署…

windows 10LTSC安装微软应用商店(理论上适用于所有windows10)

LTSC-Add-MicrosoftStore-2019.zip官方版下载丨最新版下载丨绿色版下载丨APP下载-123云盘123云盘为您提供LTSC-Add-MicrosoftStore-2019.zip最新版正式版官方版绿色版下载,LTSC-Add-MicrosoftStore-2019.zip安卓版手机版apk免费下载安装到手机,支持电脑端一键快捷安装https://w…

习题2.17

很简单的一个提 代码如下 (defn last-pair[a](if (empty? (rest a))(first a)(last-pair (rest a) ))) 获取列表最后一个元素&#xff0c;递归的办法如上。

WLAN 无线案例(AC配置模板)

实验说明&#xff1a; 无线用户VLAN 30 192.168.30.0/24 AP和AC用VLAN 20 192.168.20.0/24 有线网段 VLAN 10 192.168.10.0/24 步骤一&#xff1a;全网互通 sw1&#xff1a; sysname sw1 # vlan batch 10 20 30 # dhcp enable # ip pool 20 gateway-list 192.168.20.1…

PHP基础语法(上)

目录 前言 一、基础语法 1.1 标记 1.2 输出语句 1.2.1 echo 1.2.2 print 1.3 注释 1.3.1 单行注释 1.3.2 多行注释 1.4 标识符 1.5 关键字 二、数据与运算 2.1 常量 2.1.1 常量的定义和使用 2.1.2 预定义常量 2.2 变量 2.2.1 变量的赋值 2.2.2 超全局变量 2.3 数据类型 2.3.1 …

Python 中的 DNS 查找

本文将讨论 DNS 和 DNS 查找的概念。 之后&#xff0c;我们讨论使用 Python 进行 DNS 查找的方法。 什么是 DNS DNS&#xff08;域名系统&#xff09;将域名转换为公共IP地址。 DNS 服务器将任何网站的域名转换为该网站主机服务器的公共 IP。 DNS查询 DNS 查找是一个过程&…