简述 BIO 、NIO 模型

  • 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.mfbz.cn/a/597712.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

分享5款PDF编辑软件

PDF编辑不易&#xff0c;有需要的朋友可以试试这5款专业软件&#xff0c;每一个都能直接在PDF文件上编辑&#xff0c;不同的软件对PDF可编辑的范围不同&#xff0c;大家可以按需求选用。 1.edge浏览器 Edge浏览器不仅是浏览网页的得力助手&#xff0c;还悄然成为了轻量级PDF管…

【Stream 流】通过一个例子看遍所有Stream API使用场景

前言 上篇文章记录了方法引用&#xff0c;Lambda表达式等基础的知识点&#xff0c;这篇文章主要结合课设项目详细介绍Stream 流的API以及它的主要场景。 Stream API作用 在Java 8及其以后的版本中&#xff0c;Stream API为处理集合数据提供了强大而灵活的功能。有了Stream AP…

Petalinux的使用——定制Linux系统

文章目录 配置petalinux运行环境petalinux设计流程 配置petalinux运行环境 Petalinux的安装在文章Ubuntu镜像源的更改及其Petalinux的安装中已经介绍&#xff0c;下面介绍petalinux运行环境的配置过程。 进入到petalinux的安装路径下&#xff0c;使用下面的命令对petalinux的运…

类和对象-Python-第一部分

初识对象 使用对象组织数据 class Student:nameNonegenderNonenationalityNonenative_placeNoneageNonestu_1Student()stu_1.name"林军杰" stu_1.gender"男" stu_1.nationality"中国" stu_1.native_place"山东" stu_1.age31print(stu…

《视觉十四讲》例程运行记录(2)——运行ch4的例程评估轨迹误差

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、运行ch4的example1. 编译例程2. 运行报错(1) 报错一(2) 报错二 一、运行ch4的example 1. 编译例程 (1) 在slambook2/ch4/example目录下&#xff0c;创建build…

五月节放假作业讲解

目录 作业1&#xff1a; 问题&#xff1a; 结果如下 作业2&#xff1a; 结果: 作业1&#xff1a; 初始化数组 问题&#xff1a; 如果让数组初始化非0数会有问题 有同学就问了&#xff0c;我明明已经初始化定义过了&#xff0c;为啥还有0呀 其实这种初始化只会改变第一个…

【嵌入式笔试题】网络编程笔试题

非常经典的笔试题。 2.网络编程(29道) 2.1列举一下OSI协议的各种分层。说说你最熟悉的一层协议的功能。 ( 1 )七层划分为:应用层、表示层、会话层、传输层、网络层、数据链路层、物理 层。 ( 2 )五层划分为:应用层、传输层、网络层、数据链路层、物理层。 ( 3 )…

视频改字祝福/豪车装X系统源码/小程序uniapp前端源码

uniapp视频改字祝福小程序源码&#xff0c;全开源。创意无限&#xff01;AI视频改字祝福&#xff0c;豪车装X系统源码开源&#xff0c;打造个性化祝福视频不再难&#xff01; 想要为你的朋友或家人送上一份特别的祝福&#xff0c;让他们感受到你的真诚与关怀吗&#xff1f;现在…

VALSE 2024年度进展评述内容分享-视觉基础大模型的进展

2024年视觉与学习青年学者研讨会&#xff08;VALSE 2024&#xff09;于5月5日到7日在重庆悦来国际会议中心举行。本公众号将全方位地对会议的热点进行报道&#xff0c;方便广大读者跟踪和了解人工智能的前沿理论和技术。欢迎广大读者对文章进行关注、阅读和转发。文章是对报告人…

探无止境,云游未来 | “游戏出海云”发布

4月28日下午&#xff0c;2024中国移动算力网络大会之“游戏出海”分论坛在江苏省苏州金鸡湖国际会议中心圆满落幕。 此次论坛由中国移动海南公司主办&#xff0c;中国移动通信集团政企事业部、中国移动云能力中心、中国移动国际公司共同协办。海南省工业与信息化厅副厅长黄业晶…

【栈】Leetcode 1047. 删除字符串中的所有相邻重复项

题目讲解 1047. 删除字符串中的所有相邻重复项 算法讲解 使用栈这个数据结构&#xff0c;每一次入栈的时候观察此时的字符和当前栈顶字符是否相等&#xff0c;如相等&#xff1a;栈顶出栈&#xff1b;不相等&#xff1a;入栈 class Solution { public:string removeDuplica…

Linux 基础IO篇

1. C语言中的文件操作 1 #include<stdio.h>2 3 int main()4 {5 FILE* pf fopen("log.txt", "w");6 if(NULL pf)7 {8 perror("fopen");9 return 1; 10 }11 fprintf(pf, &quo…

Spring框架学习笔记(一):Spring基本介绍(包含容器底层结构)

1 官方资料 1.1 官网 https://spring.io/ 1.2 进入 Spring5 下拉 projects, 进入 Spring Framework 进入 Spring5 的 github 1.3 在maven项目中导入依赖 <dependencies><!--加入spring开发的基本包--><dependency><groupId>org.springframework<…

ICode国际青少年编程竞赛- Python-1级训练场-变量的计算

ICode国际青少年编程竞赛- Python-1级训练场-变量的计算 1、 a 2 for i in range(4):Spaceship.step(a-1)Dev.step(a)Dev.step(-a)a a 12、 a 2 for i in range(4):Dev.step(2 a)Dev.step(-a)Dev.turnRight()a a 13、 y 4 for i in range(3):Dev.step(y)Dev.turnRigh…

数据同步新突破!一招解决文化公司系统对接难题!

一、客户介绍 某文化传播有限公司&#xff0c;是一家专注于文化艺术领域&#xff0c;集创作、制作、交流等多功能于一体的公司。公司始终秉承创意和质量的双重标准&#xff0c;为观众带来一系列高质量的文化艺术作品。该公司的经营范围广泛&#xff0c;涵盖了组织文化艺术交流…

cmake进阶:变量的作用域说明三(从函数作用域方面)

一. 简介 前一篇文章从函数作用域方面学习了 变量的作用域。文章如下&#xff1a; cmake进阶&#xff1a;变量的作用域说明一&#xff08;从函数作用域方面&#xff09;-CSDN博客cmake进阶&#xff1a;变量的作用域说明二&#xff08;从函数作用域方面&#xff09;-CSDN博客…

VALSE 2024年度进展评述内容分享-视觉通用人工智能

2024年视觉与学习青年学者研讨会&#xff08;VALSE 2024&#xff09;于5月5日到7日在重庆悦来国际会议中心举行。本公众号将全方位地对会议的热点进行报道&#xff0c;方便广大读者跟踪和了解人工智能的前沿理论和技术。欢迎广大读者对文章进行关注、阅读和转发。文章是对报告人…

优雅处理返回信息状态码:Result对象在Spring Boot中的应用

前言 在开发过程中&#xff0c;处理返回的信息状态码是一个重要的问题&#xff0c;尤其是在大型项目中。为了统一处理这些状态码&#xff0c;我在Spring Boot中创建了一个名为Result的Java对象&#xff0c;用于封装返回的信息和状态码。在本文中&#xff0c;我将分享如何实现这…

【C++题解】1435. 数池塘(八方向)

问题&#xff1a;1435. 数池塘&#xff08;八方向&#xff09; 类型&#xff1a;深搜 题目描述&#xff1a; 农夫约翰的农场可以表示成 NM&#xff08;1≤N,M≤100&#xff09;个方格组成的矩形。由于近日的降雨&#xff0c;在约翰农场上的不同地方形成了池塘。 每一个方格或…

重写muduo之获取线程tid代码

目录 1、概述 2、CurrentThread.h 3、 CurrentThread.cc 1、概述 我们的服务器程序不一定就只有1个Eventloop&#xff0c;我们可能有很多的Eventloop&#xff0c;每个Eventloop都有很多channel&#xff0c;自己channel上的事件要在自己的Eventloop线程上去处理&#xff0c;E…
最新文章