简述 BIO 、NIO 模型

news2024/12/23 13:26:23
  • BIO : 同步阻塞I/O(Block IO)

         服务器实现模式为每一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,此处可以通过线程池机制进行优化。

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

/**
 * 多人聊天室 - 服务端
 */
public class BioServer {
    static List<Socket> clientList = new ArrayList<>();

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

        int port = 8080;

        ServerSocket serverSocket = new ServerSocket(port);

        while (true) {
            Socket client = serverSocket.accept();

            System.out.println("客户端: " + client.getPort() + " 连接成功!");

            clientList.add(client);

            forwardProcess(client);
        }
    }

    /**
     * 转发处理
     */
    public static void forwardProcess(Socket socket) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    forwardMsg(socket);
                }
            }
        }).start();
    }

    /**
     * 换发消息
     */
    public static void forwardMsg(Socket socket) {

        try {
            String msg = readMsg(socket);

            System.out.println(msg);

            for (Socket client : clientList) {
                if (client != socket) {
                    writeMsg(client, msg);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 写入消息
     */
    public static void writeMsg(Socket socket, String msg) throws IOException {
        PrintStream ps = new PrintStream(socket.getOutputStream());
        ps.println(msg);
        ps.flush();
    }

    /**
     * 读取消息
     */
    public static String readMsg(Socket socket) throws IOException {
        InputStream inputStream = socket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
        String msg;
        if ((msg = br.readLine()) != null) {
            msg = socket.getPort() + " 说: " + msg;
        }
        return msg;
    }

}

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class BilClient {

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

        String ip = "127.0.0.1";
        int port = 8080;

        Socket client = new Socket(ip, port);

        readProcess(client);

        OutputStream os = client.getOutputStream();
        PrintStream ps = new PrintStream(os);
        Scanner scanner = new Scanner(System.in);
        while (true) {
            String input = scanner.nextLine();
            ps.println(input);
            ps.flush();
        }
    }

    /**
     * 读取处理
     */
    public static void readProcess(Socket socket) {
        new Thread(() -> {
            while (true) {
                try {
                    System.out.println(readMsg(socket));
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
    }

    /**
     * 读取消息
     */
    public static String readMsg(Socket socket) throws IOException {
        InputStream inputStream = socket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
        String msg;
        if ((msg = br.readLine()) != null) {
        }
        return msg;
    }
}

  • ​NIO: 同步非阻塞式IO,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求会被注册到多路复用器上,多路复用器轮询到有 I/O 请求就会进行处理。
  • Channel,翻译过来就是“通道”,就是数据传输的管道,类似于“流”,但是与“流”又有着区别。
    • 既可以从Channel中读取数据,又可以写数据到Channel,但流的读写通常是单向的——输入输出流
    • 通道可以异步读写
    • 通道中的数据总是先读取到buffer(缓冲区),或者总是需要从一个buffer写入,不能直接访问数据
    • 非阻塞特性:Channel在设计上采用了非阻塞的特性,它不会像传统的流一样在读写操作上阻塞线程,而是立即返回结果,告诉调用者当前的状态。这使得程序可以在等待数据准备的过程中同时进行其他操作,实现了非阻塞IO。
    • 事件通知机制:Channel通常搭配选择器(Selector)来使用,选择器能够检测多个Channel的就绪状态,如是否可读、可写等,并通过事件通知(例如轮询或回调)及时地通知程序哪些Channel处于就绪状态,从而可以进行相应的读写操作。这种机制支持程序实现异步IO模型。
    • 操作系统底层支持:Channel的异步读写也依赖于操作系统底层的异步IO支持。Java NIO中的Channel实际上是对操作系统底层异步IO的封装和抽象,利用了操作系统提供的异步IO机制来实现其自身的异步读写功能。
  • Buffer是一个对象,里面是要写入或者读出的数据,在java.nio库中,所有的数据都是用缓冲区处理的。
    • 在读取数据时,它是直接读到缓冲区中的;在写入数据时,也是直接写到缓冲区中,任何时候访问Channel中的数据,都是通过缓冲区进行操作的。
    • 缓冲区实质上是一个数组,通常是一个字节数组ByteBuffer,当然也有其他类型的:
  • Selector被称为选择器,Selector会不断地轮询注册在其上的Channel,如果某个Channel上发生读或写事件,这个Channel就被判定处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取到就绪Channel的集合,进行后续的I/O操作。
    • 一个多路复用器Selector可以同时轮询多个Channel,JDK使用了epoll()代替了传统的select实现,所以并没有最大连接句柄的限制,这意味着只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端。


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

/**
 * NIO 聊天室 服务端
 */
public class NioServer {

    private Integer port;

    ByteBuffer readBuffer = ByteBuffer.allocate(1024);

    ByteBuffer writerBuffer = ByteBuffer.allocate(1024);

    private Charset charset = Charset.forName("UTF-8");

    public NioServer(Integer port) {
        this.port = port;
    }

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

    private void start() {

        try {

            // 开启socket
            ServerSocketChannel server = ServerSocketChannel.open();

            // 设置非阻塞
            server.configureBlocking(false);

            // 绑定端口
            server.socket().bind(new InetSocketAddress(port));

            // 开启通道, 得到 Selector (选择器)
            Selector selector = Selector.open();

            //  注册 selector 监听事件
            server.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("启动服务器, 监听端口:" + port + "...");

            while (true) {

                // 阻塞监控所有注册的通道,当有对应的事件操作时, 会将SelectionKey放入 集合内部并返回事件数量
                selector.select();

                // 返回存有SelectionKey的集合
                Set<SelectionKey> selectionKeys = selector.selectedKeys();

                for (SelectionKey selectionKey : selectionKeys) {
                    handle(selectionKey, selector);
                }

                //  处理后清理 selectionKeys
                selectionKeys.clear();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    /**
     * 事件处理
     */
    private void handle(SelectionKey key, Selector selector) throws IOException {

        //  SelectionKey 常用方法
        //isAcceptable()	是否是连接继续事件
        //isConnectable()	是否是连接就绪事件
        //isReadable()	是否是读就绪事件
        //isWritable()	是否是写就绪事件

        // SelectionKey 常用事件
        //SelectionKey.OP_ACCEPT	接收连接继续事件,表示服务器监听到了客户连接,服务器可以接收这个连接了
        //SelectionKey.OP_CONNECT	连接就绪事件,表示客户端与服务器的连接已经建立成功
        //SelectionKey.OP_READ	读就绪事件,表示通道中已经有了可读的数据,可以执行读操作了(通道目前有数据,可以进行读操作了)
        //SelectionKey.OP_WRITE	写就绪事件,表示已经可以向通道写数据了(通道目前可以用于写操作)

        //处理连接
        if (key.isAcceptable()) {
            ServerSocketChannel server = (ServerSocketChannel) key.channel();

            SocketChannel client = server.accept();
            client.configureBlocking(false);
            client.register(selector, SelectionKey.OP_READ);
            System.out.println(client.socket().getPort() + " 已建立连接 ...");
        }
        //  读取消息
        else if (key.isReadable()) {

            SocketChannel client = (SocketChannel) key.channel();

            String msg = client.socket().getPort() + " 说: " + readMsg(client);

            System.out.println(msg);

            forwardMsg(msg, client, selector);

        }
    }

    /**
     * 读取通道消息
     */
    private String readMsg(SocketChannel client) throws IOException {
        readBuffer.clear();
        while (client.read(readBuffer) > 0) ;
        readBuffer.flip();
        return String.valueOf(charset.decode(readBuffer));
    }

    /**
     * 转发
     */
    private void forwardMsg(String msg, SocketChannel client, Selector selector) throws IOException {
        for (SelectionKey key : selector.keys()) {
            Channel connectedClient = key.channel();
            if (connectedClient instanceof ServerSocketChannel) {
                continue;
            }

            if (key.isValid() && !client.equals(connectedClient)) {
                writerBuffer.clear();
                writerBuffer.put(charset.encode(msg));
                writerBuffer.flip();
                while (writerBuffer.hasRemaining())
                    ((SocketChannel) connectedClient).write(writerBuffer);
            }
        }
    }
}


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.nio.charset.Charset;
import java.util.Scanner;
import java.util.Set;

/**
 * NIO 聊天室 客户端
 */
public class NioClient {

    private String ip;

    private Integer port;

    private ByteBuffer writerBuffer = ByteBuffer.allocate(1024);

    private ByteBuffer readBuffer = ByteBuffer.allocate(1024);

    private Charset charset = Charset.forName("UTF-8");

    public NioClient(String ip, Integer port) {
        this.ip = ip;
        this.port = port;
    }

    public static void main(String[] args) {
        NioClient nioClient = new NioClient("127.0.0.1", 8080);
        nioClient.start();
    }


    public void start() {

        try {

            // 开启通道
            SocketChannel client = SocketChannel.open();

            // 设置非阻塞
            client.configureBlocking(false);

            Selector selector = Selector.open();
            client.register(selector, SelectionKey.OP_CONNECT);
            client.connect(new InetSocketAddress(ip, port));

            while (true) {
                selector.select();

                Set<SelectionKey> selectionKeys = selector.selectedKeys();

                for (SelectionKey selectionKey : selectionKeys) {
                    handle(selectionKey, selector);
                }

                selectionKeys.clear();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private void handle(SelectionKey key, Selector selector) throws IOException {
        // 处理连接事件
        if (key.isConnectable()) {

            SocketChannel client = (SocketChannel) key.channel();

            if (client.isConnectionPending()) {
                client.finishConnect();

                // 处理用户输入
                new Thread(() -> {
                    Scanner scanner = new Scanner(System.in);
                    while (true) {
                        String msg = scanner.nextLine();

                        writerBuffer.clear();
                        writerBuffer.put(charset.encode(msg));
                        writerBuffer.flip();

                        while (writerBuffer.hasRemaining()) {
                            try {
                                client.write(writerBuffer);
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                        }

                    }
                }).start();

            }
            client.register(selector, SelectionKey.OP_READ);
        }
        // 读取消息信息
        else if (key.isReadable()) {

            SocketChannel client = (SocketChannel) key.channel();

            String s = readMsg(client);
            System.out.println(s);

        }
    }

    private String readMsg(SocketChannel client) throws IOException {
        readBuffer.clear();
        while (client.read(readBuffer) > 0) ;
        readBuffer.flip();
        return String.valueOf(charset.decode(readBuffer));
    }
}

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

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

相关文章

新的项目springboot

buybuyshenglombok <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency> 添加依赖 lombok package com.example.demo.pojo;import lombok.AllArgsConstructor; import lombok.Data; import …

【机器学习与实现】线性回归分析

目录 一、相关和回归的概念&#xff08;一&#xff09;变量间的关系&#xff08;二&#xff09;Pearson&#xff08;皮尔逊&#xff09;相关系数 二、线性回归的概念和方程&#xff08;一&#xff09;回归分析概述&#xff08;二&#xff09;线性回归方程 三、线性回归模型的损…

BigDecimal:踩坑

问题描述 两个BigDecimal相除, 抛了异常 原因分析&#xff1a; Java 中使用 BigDecimal 做除法运算的时候&#xff0c;值有可能是无限循环的小数&#xff0c;结果是无限循环的小数&#xff0c;就会抛出上面这个异常。 来看看源码&#xff1a; public BigDecimal divide(BigD…

马常旭新歌《如愿》:音乐界的“旭日”再现

在这个春暖花开的季节&#xff0c;音乐界又迎来了一股清新的“旭日”气息。是的&#xff0c;就在2024年4月17日&#xff0c;马常旭的新歌《如愿》&#xff08;旭日版&#xff09;在网易云音乐上线了&#xff01;一年的等待&#xff0c;终于迎来了他的音乐回归&#xff0c;给我们…

【3D基础】坐标转换——地理坐标投影到平面

汤国安GIS原理第二章重点 1.常见投影方式 https://download.csdn.net/blog/column/9283203/83387473 Web Mercator投影&#xff08;Web Mercator Projection&#xff09;&#xff1a; 优点&#xff1a; 在 Web 地图中广泛使用&#xff0c;易于显示并与在线地图服务集成。在较…

在echarts中使用geojson地图

以中国地图为例 先看效果 代码实现&#xff1a; <div id"refChinaMap" :style"{ width: 75%, height: 100% }"></div>import * as echarts from "echarts"; import ChinaJSON from "./chinaMap.json";const initChinaMa…

场景文本检测识别学习 day09(Swin Transformer论文精读)

Patch & Window 在Swin Transformer中&#xff0c;不同层级的窗口内部的补丁数量是固定的&#xff0c;补丁内部的像素数量也是固定的&#xff0c;如上图的红色框就是不同的窗口&#xff08;Window&#xff09;&#xff0c;窗口内部的灰色框就是补丁&#xff08;Patch&#…

【Linux网络编程】自定义协议+HTTP协议

【Linux网络编程】自定义协议HTTP协议 目录 【Linux网络编程】自定义协议HTTP协议协议定制&#xff0c;序列化和反序列化应用层中的HTTP认识URL&#xff08;网址&#xff09;urlencode和urldecodeHTTP协议格式使用telnet获取百度的根目录资源HTTP的方法表单 HTTP的状态码HTTP常…

一般实现分布式锁都有哪些方式?使用 Redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高?

目录 1.Redis 分布式锁 &#xff08;1&#xff09;Redis 最普通的分布式锁 &#xff08;2&#xff09;RedLock 算法 2.zk 分布式锁 3.redis 分布式锁和zk分布式锁的对比 1.Redis 分布式锁 官方叫做 RedLock 算法&#xff0c;是 Redis 官方支持的分布式锁算法。 这个分布式…

[qnx] 通过zcu104 SD卡更新qnx镜像的步骤

0. 概述 本文演示如果给Xlinx zcu104开发板刷入自定义的qnx镜像 1.将拨码开关设置为SD卡启动 如下图所示&#xff0c;将1拨到On,2,3,4拨到Off&#xff0c;即为通过SD启动。 2.准备SD卡中的内容 首先需要将SD格式化为FAT32的&#xff08;如果已经是FAT32格式&#xff0c;则…

力扣每日一题114:二叉树展开为链表

题目 中等 提示 给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同…

sass-loader和node-sass与node版本的依赖问题

sass-loader和node-sass与node版本的依赖问题 没有人会陪你走到最后&#xff0c;碰到了便是有缘&#xff0c;即使到了要下车的时候&#xff0c;也要心存感激地告别&#xff0c;在心里留下空白的一隅之地&#xff0c;多年后想起时依然心存甘味。——林清玄 报错截图 报错信息 np…

linux/windows安装Tomcat

安装tomcat 注意版本一致问题 Tomcat 10.1.0-M15(alpha)Tomcat 10.0.x-10.0.21Tomcat 9.0.x-9.0.63Tomcat 8.5.x-8.0.53规范版本 Servlet 6.0,JSP 3.1, EL 5.0 WebSocket 2.1&#xff0c;JASPIC 3.0 Servlet 5.0,JSP 3.0, EL 4.0 WebSocket 2.0&#xff0c;JASPIC 2.0 Serv…

20240507最新 ubuntu20.04安装ros noetic

ubuntu20.04安装ros 主要参考博客 【ROS】在 Ubuntu 20.04 安装 ROS 的详细教程_ubuntu20.04安装ros-CSDN博客 出现问题 1.ubuntu20.04 更换清华源报错 ubuntu20.04 更换清华源报错_gvfs metadata is not supported. fallback to teplme-CSDN博客 &#xff1f;&#xff1f…

CNN-BiLSTM-Attention(12种算法优化CNN-BiLSTM-Attention多输入单输出)

12种算法优化CNN-BiLSTM-Attention模型预测的代码。其中Attention模型可以改为单头或者多头&#xff0c;在代码中就是改个数字而已。代码注释已写好如何更改。 12种算法优化CNN-BiLSTM-Attention多特征输入单步预测代码获取戳此处代码获取戳此处代码获取戳此处 主要功能为:采用…

数据库SQL语言实战(七)

前言 这次的有一点点难~~~~~我也写了好久 练习题 题目一 在学生表pub.student中统计名字&#xff08;姓名的第一位是姓氏&#xff0c;其余为名字&#xff0c;不考虑复姓&#xff09;的使用的频率&#xff0c;将统计结果放入表test5_01中 create table test5_01(First_name…

【一步一步了解Java系列】:探索Java基本类型转换的秘密

看到这句话的时候证明&#xff1a;此刻你我都在努力~ 加油陌生人~ 个人主页&#xff1a; Gu Gu Study ​​ 专栏&#xff1a;一步一步了解Java 喜欢的一句话&#xff1a; 常常会回顾努力的自己&#xff0c;所以要为自己的努力留下足迹。 如果喜欢能否点个赞支持一下&#…

ComfyUI 基础教程(十四):ComfyUI中4种实现局部重绘方法

在ComfyUI中有多种方式可以实现局部重绘,简单的方式是使用VAE内补编码器进行局部重绘,也可以用Fooocus inpaint进行局部重绘,还可以用controlNet的inpaint模型进行局部重绘,以及使用Clip seg蒙版插件! 本篇介绍使用VAE內补编码器进行局部重绘的方法。 1、VAE内补编码器 局…

【docker】docker compose 搭建私服

安装 Docker Registry 创建目录 mkdir -pv /usr/local/docker/registrymkdir -pv /usr/local/docker/data 创建 docker-compose.yml文件 进入目录创建docker-compose.yml cd /usr/local/docker/registrytouch docker-compose.yml 编辑docker-compose.yml vim docker-compo…

ASRPRO

https://gitee.com/gitee-128/ASR-PRO-U8G2/tree/main 不下载模型 语音就是天问51唤醒我 u8g2的移植过程 第一步 下载u8g2的源代码 第二步 修改 delay and 函数 第三步 添加头文件 显示 显示 动画 SPI I2C(SOFT SPI ;SOFT I2C U8G2 移植过程&#xff08;移植过程参考…