Netty入门--传统IO与NIO详解

news2024/12/29 10:55:31

文章目录

  • IO模型
    • 传统阻塞的IO模型--BIO
      • Client端案例
      • Server端案例
    • NIO(Java non-blocking IO)非阻塞IO
      • NIO的三大组件 Channel Selector Buffer
        • Buffer(缓冲区)
        • Channel(通道)
          • Channe的分类,与Buffer的关系
        • Selector(选择器)
          • SelectionKey(选择键)
      • 案例NIOClient
      • 案例NIOServer
    • NIO、BIO、AIO比较

IO模型

就是用什么样的通道或者通信模式和架构进行数据的传输和接收,很大程度上决定了程序通信的性能,包括BIO(阻塞IO)、NIO(同步非阻塞IO)、AIO(异步非阻塞IO)

传统阻塞的IO模型–BIO

同步并阻塞,服务器实现模式为一个客户端创建一个线程,即用户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情就会造成不必要的线程开销(如图所示)

img.png

Client端案例

package com.yly.netty.bio.tcp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;

/**
 * @author :yangliyuan
 * @date :9:52 2022/10/14
 */
public class TcpClient {
    /**
     * 结束连接标识
     */
    public static final String FINISH_MESSAGE = "bye";

    public static void main(String[] args) throws IOException {
        Socket socket = new Socket();
        try {
            System.out.println("启动客户端。。。");

            // 设置获取流超时时间
            socket.setSoTimeout(3000);

            //连接目标服务器
            socket.connect(new InetSocketAddress(InetAddress.getLocalHost(), 10001), 3000);
            System.out.println("连接服务器成功!请输入要发送的消息,按ENTER发送!");
            // 发送消息
            sendMessage(socket);
        } catch (SocketException | UnknownHostException e) {
            e.printStackTrace();
        } finally {
            socket.close();
        }
    }

    /**
     * 发送消息
     *
     * @param client socket客户端
     * @throws IOException io异常
     */
    private static void sendMessage(Socket client) throws IOException {
        // 构建键盘输入流
        InputStream in = System.in;
        BufferedReader input = new BufferedReader(new InputStreamReader(in));

        // 得到socket输入流,并转换成打印流
        OutputStream outputStream = client.getOutputStream();
        PrintStream socketPrintStream = new PrintStream(outputStream);

        // 获取服务器socket输入流
        InputStream socketIn = client.getInputStream();
        BufferedReader socketBufferedReader = new BufferedReader(new InputStreamReader(socketIn));

        boolean flag = true;
        do {
            // 键盘读取一行
            String str = input.readLine();
            // 发送数据到服务器
            socketPrintStream.println(str);

            // 读取服务器数据并打印
            String echo = socketBufferedReader.readLine();
            if (FINISH_MESSAGE.equalsIgnoreCase(echo)) {
                client.close();
                flag = false;
                System.out.println("服务器要求断开连接,bye! bye! ");
            } else {
                System.out.println("回传:" + echo);
            }
        } while (flag);

        // 资源释放
        socketPrintStream.close();
        socketBufferedReader.close();
    }
}

Server端案例

package com.yly.netty.bio.tcp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

import lombok.extern.slf4j.Slf4j;

/**
 * @author :yangliyuan
 * @date :9:51 2022/10/14
 */
@Slf4j
public class TcpService {
    private static boolean flag = true;

    public static void main(String[] args) {
        startSocketServer();
    }

    /**
     * 开启SocketServer
     */
    private static void startSocketServer() {
        try (ServerSocket serverSocket = new ServerSocket(10001)) {
            // 等待客户端连接
            while (flag) {
                Socket accept = serverSocket.accept();
                // 每一个客户端连接就会新创建线程处理
                ClientHandler clientHandler = new ClientHandler(accept);
                clientHandler.start();
            }
            log.info("服务端已准备就绪!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 关闭SocketServer
     */
    public static void closeServer() {
        flag = false;
    }

    /**
     * 客户端消息处理器
     */
    private static class ClientHandler extends Thread {
        /**
         * 结束标识字符串
         */
        private static final String FINISH_MESSAGE = "bye";
        private final Socket socket;
        private boolean flag;

        public ClientHandler(Socket socket) {
            this.socket = socket;
            this.flag = true;
        }

        @Override
        public void run() {
            super.run();
            log.info("客户端已连接:{}:{}", socket.getInetAddress(), socket.getPort());
            try {
                // 获取socket输入流
                InputStream socketInputStream = socket.getInputStream();
                BufferedReader clientBufferReader = new BufferedReader(new InputStreamReader(socketInputStream));

                // 获取socket输出流
                OutputStream outputStream = socket.getOutputStream();
                PrintStream socketPrintStream = new PrintStream(outputStream);

                do {
                	// 阻塞读取一行
                    final String echo = clientBufferReader.readLine();

                    // 客户端要求断开连接
                    if (FINISH_MESSAGE.equalsIgnoreCase(echo)) {
                        log.info("客户端请求断开连接,{}", socket.getInetAddress());
                        this.flag = false;
                        socket.close();
                        socketInputStream.close();
                        outputStream.close();
                        continue;
                    }

                    log.info("客户端消息:{}", echo);
                    // 服务端回传消息给客户端
                    socketPrintStream.println(echo.length());

                } while (flag);

            } catch (IOException e) {
                log.error(e.getMessage());
            }
        }
    }
}

NIO(Java non-blocking IO)非阻塞IO

  • NIO是面向缓冲区的,基于通道的IO操作。数据总是从通道写到缓冲区或者从缓冲区写入通道
  • Java NIO可以让你异步的使用IO
  • 同步非阻塞的

NIO的三大组件 Channel Selector Buffer

img_1.png

Buffer(缓冲区)

一个用于特定基本数据类型的容器。由 java.nio 包定义的,所有缓冲区都是 Buffer 抽象类的子类。

  1. 缓冲区的基本类型

    • ByteBuffer(字节缓冲区–常用缓冲区)
      • MappedByteBuffer
      • DirectByteBuffer
      • HeapByteBuffer
    • CharBuffer
    • ShortBuffer
    • IntBuffer
    • LongBuffer
    • FloatBuffer
    • DoubleBuffer
  • 获取缓冲区常用方法allocate(),例如 ByteBuffer.allocate() 获取缓冲区
  1. 缓冲区常用方法
    • put() 存
    • get() 取
    • clear() 清空缓冲区并返回对缓冲区的引用
    • flip() 为将缓冲区的界限设置为当前位置, 并将当前位置重置为 0
    • hasRemaining() 判断缓冲区中是否还有元素
  2. 缓冲区四个核心属性
    • capacity: 容量,表示缓冲区中最大存储数据的容量。一旦声明不能更改。
    • limit: 界限,表示缓冲区中可以操作数据的大小。(limit 后的数据不能进行读写)
    • position: 位置,表示缓冲区中正在操作数据的位置。
    • mark: 标记,表示记录当前 position 的位置。可以通过 reset() 恢复到 mark 的位置
      img.png

Channel(通道)

由 java.nio.channels 包定义的。Channel 表示 IO 源与目标打开的连接。Channel 类似于传统的流,只不过 Channel 本身不能存储数据,数据由Buffer存储,而Channel 只能与 Buffer 进行交互。

Channe的分类,与Buffer的关系

在这里插入图片描述

Selector(选择器)

为了避免传统阻塞IO线程一个客户端连接救护开启一个线程而可能导致线程浪费的情况,nio从创造了Selector(选择器),Selector 能够检测多个注册的服务端通道上是否有事件发生,当监测到有时间发生时,便可以获取事件集且针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。(当然也可以使用线程池处理事件,减少线程创建和销毁的开销)

SelectionKey(选择键)

SelectionKey是java.nio.channels包下的一个类,主要用于绑定selector和channel之间的关联(注册关系)

  • 四个常量代表不同的事件

SelectionKey.OP_ACCEPT=1<<4 新网络请求,值为16

SelectionKey.OP_CONNECT=1<<3 连接已建立,值为8

SelectionKey.OP_WRITE=1<<2 写操作,值为4

SelectionKey.OP_READ= 1<<0 读操作,值为1

  • 对应的常用的判断方法

SelectionKey.isAcceptable(): 是否是连接继续事件

SelectionKey.isConnectable(): 是否是连接就绪事件

SelectionKey.isReadable(): 是否是读就绪事件

SelectionKey.isWritable(): 是否是写就绪事件

案例NIOClient

package com.yly.netty.nio;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;

import lombok.extern.slf4j.Slf4j;

/**
 * @author :yangliyuan
 * @date :10:54 2022/10/20
 */
@Slf4j
public class NioClient {
    /**
     * NIO服务器端口
     */
    private static final Integer NIO_PORT = 10014;

    public static void main(String[] args) throws IOException {
        // 获取SocketChannel网络通道
        SocketChannel client = SocketChannel.open();
        // 设置客户端为非阻塞
        client.configureBlocking(false);

        // 连接到服务端
        if (!client.connect(new InetSocketAddress(InetAddress.getLocalHost(), NIO_PORT))) {
            while (!client.finishConnect()) {
                // 非阻塞连接,可以做其他事情
                log.info("客户端正在请求连接中...");
            }
        }

        // 构建键盘输入流
        InputStream inputStream = System.in;
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

        while (true) {
            // 获取键盘输入的一行
            String str = bufferedReader.readLine();
            log.info(str);
            // 按需指定大小发送数据
            client.write(ByteBuffer.wrap(str.getBytes(StandardCharsets.UTF_8)));
        }

    }
}

案例NIOServer

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

import lombok.extern.slf4j.Slf4j;

/**
 * @author :yangliyuan
 * @date :10:08 2022/10/20
 */
@Slf4j
public class NioServer {
    private static boolean flag = true;

    public static void main(String[] args) {
        try (ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
            // 绑定端口号为10013
            serverSocketChannel.socket().bind(new InetSocketAddress(10014));
            // 设置为非阻塞
            serverSocketChannel.configureBlocking(false);
            // 开启Selector
            Selector selector = Selector.open();
            // serverSocketChannel注册到selector 关心事件为OP_ACCEPT
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            // selector 循环监听事件
            while (flag) {
                if (selector.select(1000) <= 0) {
                    continue;
                }

                // 有客户端连接 获取关注事件的集合
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    // 判断是哪种事件
                    // 如果是连接请求
                    if (selectionKey.isAcceptable()) {
                        acceptEvent(serverSocketChannel, selector);
                    }

                    // 如果是读就绪事件
                    if (selectionKey.isReadable()) {
                        readEvent(selectionKey);
                    }

                    // 删除当前selectionKey,不删除迭代器会会保留当前selectionKey
                    iterator.remove();
                }

            }

        } catch (IOException exception) {
            exception.printStackTrace();
        }
    }

    /**
     * 连接请求事件
     *
     * @param serverSocketChannel 服务套接字通道
     * @param selector 选择去
     */
    private static void acceptEvent(ServerSocketChannel serverSocketChannel, Selector selector) throws IOException {
        // 生成socketChannel
        SocketChannel client = serverSocketChannel.accept();
        //设置为非阻塞客户端
        client.configureBlocking(false);

        // 注册并绑定buffer
        client.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
        log.info("接收到客户端连接:{}", client.getRemoteAddress());

    }

    /**
     * 读取数据事件
     * @param selectionKey 选择健
     * @throws IOException io异常
     */
    private static void readEvent(SelectionKey selectionKey) throws IOException {
            // 通过selectionKey获取socketChannel
            SocketChannel channel = (SocketChannel) selectionKey.channel();
            // 通过selectionKey获取Buffer
            ByteBuffer buffer = (ByteBuffer) selectionKey.attachment();
            buffer.clear();
            channel.read(buffer);
            String str = new String(buffer.array());
            log.info("{}:{}", channel.getRemoteAddress(), str.trim());
    }
}

NIO、BIO、AIO比较

在这里插入图片描述

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

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

相关文章

【Spring(六)】使用篇:AOP在开发中的使用

有关Spring的所有文章都收录于我的专栏&#xff1a;&#x1f449;Spring&#x1f448; 目录 一、前言 二、演示 三、切面类中声明通知方法 四、使用 相关文章 【Spring&#xff08;一&#xff09;】如何获取对象&#xff08;Bean&#xff09;【Spring&#xff08;一&#xff09…

刷爆力扣之数组形式的整数加法

刷爆力扣之数组形式的整数加法 HELLO&#xff0c;各位看官大大好&#xff0c;我是阿呆 &#x1f648;&#x1f648;&#x1f648; 今天阿呆继续记录下力扣刷题过程&#xff0c;收录在专栏算法中 &#x1f61c;&#x1f61c;&#x1f61c; 该专栏按照不同类别标签进行刷题&…

Nodejs -- 一文了解Express模块

文章目录1. 初识Express1.1 Express简介1.1.1 什么是Express1.1.2 进一步理解Express1.1.3 Express能做什么1.2 Express的基本使用1.2.1 安装1.2.2 基本使用1.2.3 监听GET请求1.2.4 监听POST请求1.2.5 把内容响应给客户端1.2.6 获取URL中携带的查询参数1.2.7 获取URL中的动态参…

一文读懂TCP的三次握手(详细图解)

在学习TCP三次握手的过程前&#xff0c;首先熟悉几个缩写简称&#xff1a; TCB 传输控制块&#xff0c;打开后服务器/客户端进入监听&#xff08;LISTEN&#xff09;状态 SYNTCP报文标志位&#xff0c;该位为1时表示发起一个新连接ACKTCP报文标志位&#xff0c;该位为1时&…

傻白入门芯片设计,如何降低CPU功耗?(八)

低功耗芯片设计是本世纪以来最重要的新兴设计方法。可以说没有低功耗设计&#xff0c;就没有今天的智能手机&#xff0c;移动设备&#xff0c;物联网&#xff0c;及高性能计算等产业。随着芯片图形尺寸越来越小&#xff0c;低功耗设计在现在及未来的芯片中会起到越来越重要的作…

使用 Learner Lab - 使用 Lambda 转换图片为 base64 格式

使用 Learner Lab - 使用 Lambda 转换图片为 base64 格式 AWS Academy Learner Lab 是提供一个帐号让学生可以自行使用 AWS 的服务&#xff0c;让学生可以在 100 USD的金额下&#xff0c;自行练习所要使用的 AWS 服务&#xff0c;以下使用 使用 Lambda 转换图片为 base64 格式…

UNIAPP实战项目笔记46 订单确认页面的布局

UNIAPP实战项目笔记46 订单确认页面的布局 实际案例图片 订单页面 具体内容图片自己替换哈&#xff0c;随便找了个图片的做示例 具体位置见目录结构 完善布局页面和样式 代码 confirm-order.vue部分 confirm-order.vue 确认订单页面布局和渲染 flex 样式布局 <template>…

Uncaught TypeError: i.createPopper is not a function

“createPopper”不是我们使用引导程序时发生的函数错误 需要popper.js脚本但不在页面上加载它的组件或 在引导脚本之后加载它。要解决此错误&#xff0c;请包括引导程序 在运行 JavaScript 代码之前捆绑脚本。 这是一个工作示例&#xff0c;它加载引导捆绑包脚本来解决 错误。…

黑盒测试用例设计 - 边界值分析法

边界值的选择原则 如果输入条件规定了值的范围&#xff0c;则应取刚达到这个范围边界的值&#xff0c;以刚刚超越这个范围边界的值作为测试输入数据如果输入条件规定了值的个数&#xff0c;则用最大个数、最小个数、比最小个数少1、比最大个数多1的数作为测试数据如果程序的规…

[附源码]计算机毕业设计springboot电商小程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

[附源码]计算机毕业设计Springboot大学生志愿者服务管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

《web课程设计》期末网页制作 基于HTML+CSS+JavaScript制作公司官网页面精美

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

数据被删除怎么办?4个硬盘数据恢复工具分享

日常电脑工作中&#xff0c;都会用到硬盘。但是也很容易出现各种问题&#xff0c;比如数据误删&#xff0c;或者格式化等问题。我们怎么应对这种情况&#xff1f;有没有什么硬盘数据恢复工具&#xff1f;下面给大家分享一下关于硬盘数据恢复的工具&#xff01; ​ 工具一&#…

基于PHP+MySQL图书管理系统的设计与实现

开发本图书管理系统目的是为了实现对图书馆的图书,借阅等进行科学化的管理,便于图书信息以及借阅信息的查询和安全控制,提高设备使用效率,减少维护成本。 图书管理系统实现对图书的管理和借阅管理,利用PHP及技术来实现对图书信息的控制和管理。 图书管理系统功能结构图 通过对各…

Java并发-多线程售票案例

1. 前言 本节内容主要是使用 Java 的使用 Condition 和 Lock 机制对多线程售票案例进行实现。售票案例多数情况下主要关注多线程如何安全的减少库存&#xff0c;也就是剩余的票数&#xff0c;当票数为 0 时&#xff0c;停止减少库存。 2. 售票机制模型 如下图所示&#xff0…

简单聊聊什么是react-redux,它能解决哪些问题

或许 在大多数人眼中 redux是一个相对复查很多的知识点 但确实如果你熟悉了流程 其实也比较简单的 redux是一个数据管理方案 我们先来举个例子 目前我们知道 react中有两种组件数据通信的方式 分别是 props 父传子 定义事件 子传父 通过事件将自己的数据传给父级 那如果是兄弟…

论硬件开发过程中开发文档规范化的重要性

硬件开发的标准化是公司管理过程中的重要组成部分&#xff0c;它离不开硬件开发文档的规范化&#xff0c;很多公司并不了解开发文档的重要性&#xff0c;容易将其忽视。一个项目开发完成后&#xff0c;还有着漫长的生命周期、售后维护和更新迭代&#xff0c;总结出开发文档&…

APS排程软件与ERP、MES的集成方式

ERP通常是企业第一个引入的信息系统&#xff0c;主要处理财务、订单、物料、人力资源等企业运营的基本数据&#xff0c;但ERP不能解决生产现场的问题。而要实现制造过程的精益化&#xff0c;对生产中的每个环节全面优化和监管&#xff0c;还需要其它的信息系统帮助。 ERP&#…

内网渗透笔记

内网靶场搭建 国内的红日安全团队曾提供内网渗透实战靶场的下载&#xff08;大小共 13 G&#xff09;&#xff0c;你可以从百度网盘上下载&#xff0c;如果自己从头搭建测试环境的话&#xff0c;配置流程相当麻烦。 百度网盘&#xff1a;https://pan.baidu.com/s/1nC6V8e_EuK…

Win10 桌面图标出现空文件夹的删除及桌面图标排列问题

今天电脑开机后&#xff0c;桌面平白无故出现了两个空白的文件夹&#xff0c;也没有名字&#xff0c;如下图所示。 右键该文件夹后有以下下拉选项。 点击删除后&#xff0c;在回收站里面也没有这两个文件夹&#xff0c;在桌面鼠标右键&#xff0c;然后点击刷新后&#xff0c;…