smart Java——BIO、NIO、AIO的工作流程和代码实现

news2024/11/28 10:49:04

文章目录

  • 〇、前置知识
    • 1.套接字
    • 2.线程池
  • 一、BIO
    • 1.工作流程
    • 2.代码实现
    • 3.缺点
  • 二、NIO(基于轮训)
    • 1.相比于BIO的优化
    • 2.工作流程
    • 3.代码实现
  • 三、AIO(基于订阅-通知)
    • 1.工作流程
    • 2.代码实现
  • 参考

〇、前置知识

1.套接字

在计算机网络编程技术中,两个进程或者说两台计算机可以通过一个网络通信连接实现数据的交换,这种通信链路的端点就被称为“套接字”(英文名称也就是Socket)。在Java语言中,服务端使用ServerSocket套接字,客户端使用Socket套接字。

  • ServerSocket:
// 创建ServerSocket对象
ServerSocket(int port):这个使用指定的端口去创建ServerSocket,IP地址使用默认的本地IP地址
ServetSocket(int port,int backlog):除了端口外,还有一个用来改变队列长度参数的backlog,指定当服务器繁忙时,可以与之保持连接请求的客户端数量,默认为50
ServetSocket(int port,int backlog,InetAddress ip):这个使用指定的端口、backlog、地址去创建ServetSocket

// 两个静态方法获取InetAddress对象:
getByName(String hostName)
getByAddress(byte[] address)

// accept()
serverSocket.accept():没有参数,返回一个Socket,如果接收到客户端的一个Socket,则返回,否则一直处于等待状态,线程也被阻塞。
  • Socket:
// 创建Socket对象
Socket(InetAddress address,int port):使用指定IP与指定端口构造Socket,默认使用本地ip,端口则动态分配
Socket(String address,int port):使用String表示IP
Socket(InetAddress address,int port,InetAddress localAddr,int localPort):创建指定了远程IP、远程端口、本地IP、本地端口的Socket
Socket(String address,int port,InetAddress localAddr,int localPort):使用String表示远程IP,用InetAddress表示本地IP
  • 简单代码示例:
package socket;

import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException{
        ServerSocket serverSocket = new ServerSocket(8888);
        while(true){
            Socket socket=serverSocket.accept();//阻塞等待客户端连接
            PrintStream printStream = new PrintStream(socket.getOutputStream());//创建输出流
            printStream.println("message from server 8888");
            printStream.close();
            socket.close();
        }
    }
}
package socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

public class Client {
    public static void main(String[] args) throws IOException{
        Socket socket = new Socket("127.0.0.1", 8888);
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        System.out.println("This message comes from server:"+bufferedReader.readLine());
        bufferedReader.close();
        socket.close();
    }
}

跑一下试试,成功!

在这里插入图片描述

2.线程池

请看我写的另一篇博客:跳转

一、BIO

BIO,即Blocking IO,阻塞型I/O。阻塞体现在两个地方,连接线程的阻塞和读写的阻塞。

1.工作流程

服务端启动ServerSocket;
客户端启动 Socket 对服务器进行通信,服务端对每个客户端建立一个线程与之通讯(可以使用线程池进行优化);
客户端发出请求后,先咨询服务器是否有线程响应,如果没有则会等待(即阻塞);
如果有响应,客户端线程会等待请求结束后,再继续执行。

在这里插入图片描述

2.代码实现

  • 服务端:
package bio;

import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class BIOServer {
    //创建一个线程池,用于处理客户端连接后的工作
    public static ThreadPoolExecutor pool=new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
    public static void main(String[] args) throws IOException{
        ServerSocket serverSocket=new ServerSocket(8888);
        while(true){
            //1 等待客户端连接是阻塞的
            Socket socket=serverSocket.accept();
            System.out.println("客户端连接上了");
            //2 连接上以后向线程池提交一个任务用于处理连接
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        try{
                            //读写也是阻塞的
                            //创建输出流,server向client输出
                            PrintStream printStream = new PrintStream(socket.getOutputStream());
                            printStream.println("message from server 8888");
                            printStream.close();
                            socket.close();
                        }catch(IOException e){
                            e.printStackTrace();
                        }
                        
                    }
                }
            });
        }
    }
}

  • 客户端:
package bio;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

public class BIOClient {
    public static void main(String[] args) throws IOException{
        Socket socket = new Socket("127.0.0.1", 8888);
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        System.out.println("This message comes from server:"+bufferedReader.readLine());
        bufferedReader.close();
        socket.close();
    }
}

在这里插入图片描述

3.缺点

  • accept()等待客户端连接是阻塞的,有时候需要进行无谓的等待,效率低下,浪费资源。
  • 引入线程池进行优化提升了高并发能力,即能够同时处理多个客户端请求了,但是却带来了一个问题,随着开启的线程数目增多,将会消耗过多的内存资源,导致服务器变慢甚至崩溃。
  • 读写操作仍然是阻塞的,如果客户端半天没有操作,也会浪费资源,因此效率不高。

二、NIO(基于轮训)

1.相比于BIO的优化

NIO,即non-blocking lO,非阻塞型IO。

  • 非阻塞——减少线程资源的浪费:
    BIO提供非阻塞读写模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。可以做到 用一个线程来处理多个操作,体现了一种多路复用的思想。 而不是像BIO那样,一个连接过来就得分配一个线程,造成资源的浪费。

  • 三大核心组件——提升效率:
    NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道。

2.工作流程

Channel(通道),Buffer(缓冲区), Selector(选择器)为NIO的三大核心组件。

  • Channel(通道):
    相比于BIO流的读写,Channel的读写是双向的,既可以从通道中读取数据,又可以写数据到通道。通道可以非阻塞读取和写入通道/缓冲区,也支持异步地读写。

  • Buffer(缓冲区):
    在客户端和Channel之间,增加Buffer缓冲区的支持,更加容易操作和管理。

  • Selector(选择器):
    用来检查一个或多个NIO通道,并确定哪些通道已经准备好进行读取或写入。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接,提高效率。

在这里插入图片描述

3.代码实现

代码来自:here

  • 服务端:
package 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;

public class NIOServer {
    public static void main(String[] args) throws IOException {
        // 创建ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        // 创建一个Selector对象,
        Selector selector = Selector.open();

        // 绑定端口6666, 在服务器端监听
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        // 设置为非阻塞
        serverSocketChannel.configureBlocking(false);

        // 把serverSocketChannel注册到selector
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 循环等待用户连接
        while (true){
            if (selector.select(1000) == 0){ //等待(阻塞)一秒, 没有事件发生
//            if (selector.selectNow() == 0){ // 也可以设置成非阻塞的
                System.out.println("服务器等待了一秒,无连接");
                continue;
            }

            // 如果返回的>0 , 说明客户端有了动作,就获取相关的selectionKey集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys(); // 返回关注事件的集合

            // 遍历selectionKeys
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();

            while (keyIterator.hasNext()){
                // 获取到selectionKey
                SelectionKey key = keyIterator.next();
                //根据key对应的通道获取事件并做相应处理
                if (key.isAcceptable()){
                    //如果是OP_ACCEPT, 表示有新的客户端产生
                    //给该客户端生成SocketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    //将socketChannnel设置为非阻塞
                    socketChannel.configureBlocking(false);
                    //将socketChannel注册到selector上, 设置事件为OP_READ,同时给socketChannel关联一个buffer
                    socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }

                if (key.isReadable()){
                    // 发生了OP_READ
                    SocketChannel channel=(SocketChannel)key.channel();
                    ByteBuffer buffer = (ByteBuffer)key.attachment();
                    channel.read(buffer);
                    System.out.println("from 客户端"+new String(buffer.array()));
                }

                // 手动从集合中移除当前的selectionKey, 防止多线程情况下的重复操作
                keyIterator.remove();

            }


        }

    }
}

  • 客户端:
package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class NIOClient {

    public static void main(String[] args) throws IOException {
        // 获取一个网络通道
        SocketChannel socketChannel = SocketChannel.open();
        // 设置为非阻塞
        socketChannel.configureBlocking(false);
        //设置服务器端ip和端口
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
        if (!socketChannel.connect(inetSocketAddress)){

            while (!socketChannel.finishConnect()){
                //如果没有连接成功,客户端是非阻塞的,可以做其它工作
                System.out.println("等待连接...");
            }
        }

        // 如果连接成功,就发送数据
        String str = "hello world";
        ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
        // 发送数据 , 将buffer中的数据写入到channel中
        socketChannel.write(buffer);
        System.in.read();

    }

}

在这里插入图片描述

三、AIO(基于订阅-通知)

AIO,即Asynchronous I/O,异步非阻塞IO。AIO提供的最大的特点是具备异步功能,采用“订阅-通知”模式,即应用程序向操作系统注册IO监听,然后继续做自己的事情。当操作系统发生IO事件,并且准备好数据后,在主动通知应用程序,触发相应的函数。

1.工作流程

在这里插入图片描述

2.代码实现

请看他的blog:here

参考

套接字:https://blog.csdn.net/qq_27525611/article/details/102633014
BIO:https://blog.csdn.net/java_lg/article/details/126274158
NIO:https://blog.csdn.net/K_520_W/article/details/123454627
NIO:https://www.cnblogs.com/jobbible/p/16913990.html
AIO:https://zhuanlan.zhihu.com/p/504968873

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

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

相关文章

回溯算法之广度优先遍历

目录 迷宫问题 N叉树的层序遍历 腐烂的橘子 单词接龙 最小基因变化 打开转盘锁 迷宫问题 假设有一个迷宫&#xff0c;里面有障碍物&#xff0c;迷宫用二维矩阵表示&#xff0c;标记为0的地方表示可以通过&#xff0c;标记为1的地方表示障碍物&#xff0c;不能通过。现在给一…

【机器人3】图像雅可比矩阵原理与推导

图像雅可比矩阵原理与推导 理想情况下&#xff0c;图像像素坐标系和图像物理坐标系无倾斜&#xff0c;则二者坐标转换关系如下&#xff0c;且两边求导&#xff1a; [ u v 1 ] [ 1 d x 0 u 0 0 1 d y v 0 0 0 1 ] [ x y 1 ] (1) \begin{bmatrix}u\\v\\1\end{bmatrix}\begin{b…

C语言-变量

1 内存的分区 1、内存&#xff1a;物理内存、虚拟内存 物理内存&#xff1a;实实在在存在的存储设备 虚拟内存&#xff1a;操作系统虚拟出来的内存。 操作系统会在物理内存和虚拟内存之间做映射。 在32位系统下&#xff0c;每个进程的寻址范围是4G,0x00 00 00 00 ~0xff ff …

和想要通过学习 Python 转行的同学聊一聊

在开始之前我想说&#xff0c;关于这类话题&#xff0c;永远会存在分歧和争论。比如有人看好互联网发展&#xff0c;有人说泡沫太大&#xff1b;有人说要做项目&#xff0c;有人说得多刷题&#xff1b;有人说要去培训班&#xff0c;有人说不如自学&#xff1b;有人说你学 Pytho…

【MySql】基本查询

文章目录 插入操作insert查询操作selectselect查询where条件判断order by排序limit筛选分页结果 更新操作update删除操作delete插入查询结果 CRUD : Create(创建), Retrieve(读取)&#xff0c;Update(更新)&#xff0c;Delete&#xff08;删除&#xff09; 先创建提供一张表&am…

给大家分享一份适合练手的软件测试实战项目

我们都知道软件测试是一个理论性强的分支。 正是这种特点&#xff0c;决定了在学习的过程中不单单是看或者去背相应的知识点&#xff0c;而是真真切切的基于这些理论基础知识&#xff0c;结合实战项目进行演练。这也就是所谓的眼过千遍不如手过一遍。而且大家也都能看到&#…

13. 100ASK-V853-PRO开发板 摄像头测试指南

100ASK-V853-PRO开发板 摄像头测试指南 硬件要求&#xff1a; 100ASK-V853-PRO开发板GC2053摄像头 软件要求&#xff1a; 固件下载地址&#xff1a;链接&#xff1a;百度网盘 提取码&#xff1a;sp6a 固件位于资料光盘中的10_测试镜像/3.测试摄像头/v853_linux_100ask_uart0…

119.【Uniapp】

uni-app (一)、uni-app 起步1.Uniapp简介2.Uniapp开发工具(1).下载HbuilderX(2).安装scss/sass编译(3).快捷键方案切换(4).修改编辑器的基本设置 3.新建 uni-app项目4.uniapp 的目录结构5.把项目运行到微信开发者工具中(1).填写自己的微信小程序的AppID:(2).在HBuilderX中&…

java循环语句

文章目录 for循环while循环do-while循环嵌套循环关键字break和continue的使用break带标签的使用 for循环 语法格式&#xff1a; for (①初始化部分; ②循环条件部分; ④迭代部分)&#xff5b;③循环体部分; &#xff5d;说明&#xff1a; for(;;)中的两个&#xff1b;不能多…

视觉SLAM十四讲——ch7实践(视觉里程计1)

视觉SLAM十四讲----ch7的实践操作及避坑 1. 实践操作前的准备工作2. 实践过程2.1 特征提取与匹配2.2 对极几何2.3 三角测量2.4 求解PnP2.5 求解ICP 3. 遇到的问题3.1 准备工作遇到的问题 1. 实践操作前的准备工作 在终端中进入ch7文件夹下&#xff0c;顺序执行以下命令进行编译…

Sentinel的限流和Gateway的限流差别?

Sentinel的限流与Gateway的限流有什么差别&#xff1f; 问题说明&#xff1a;考察对限流算法的掌握情况 限流算法常见的有三种实现&#xff1a;滑动时间窗口&#xff0c;令牌桶算法&#xff0c;漏桶算法。gateway则采用基于Redis实现的令牌桶算法。但是我们不会去用&#xff…

PCB做了盲埋孔,还有必要再做盘中孔工艺吗

一博高速先生成员--王辉东 初夏的西湖美艳无边&#xff0c;若不去看看人生总觉遗憾。 杭州两大美女明明和琪琪约好这个星期天&#xff0c;一起去西湖转转&#xff0c;到灵隐寺许个愿&#xff0c;再到北高峰爬个山。 话说两人正行之间&#xff0c;看到正对面也有两个美女结伴同…

Spring Security Oauth2.1 最新版 1.1.0 整合 gateway 完成授权认证(基于 springboot 3.1)

目录 背景 版本 Spring Boot 3.1 Spring Authorization Server 1.1.0官方文档 基础 spring security OAuth2.0 模块构成 授权方式 集成过程 官方demo 代码集成 依赖 授权服务AuthorizationServerConfig配置 重要组件 测试 查看授权服务配置 访问授权服务 授…

渗透测试与自动化安全测试工具比较

应用程序安全性并不新鲜&#xff0c;但它在需求、复杂性和深度方面正迅速增长。随着网络犯罪自疫情爆发以来增长了近600%&#xff0c;越来越多的SaaS企业开始争相保护他们的应用程序。即使那些运行最新端点保护的系统也面临重大漏洞。 然而随之而来的一个问题是&#xff1a;即…

【javaweb+springboot】旅游网页面设计(主购物车功能)——前后端分离+服务端客户端增删改查(完整代码+文档)

一、项目背景 由于疫情原因&#xff0c;张家界旅游业受到很大的影响&#xff0c;为了促进旅游业的发展&#xff0c;吸引更多游客来到张家界旅游&#xff0c;帮助游客更好地了解张家界&#xff0c;创建张家界旅游网&#xff0c;推进旅游发展大会的开展&#xff0c;展示当地风土人…

商城系统功能有哪些?

商城系统是一种以电子商务为基础的技术工具&#xff0c;为企业涉足电子商务提供了完整的解决方案。商城系统不仅可以帮助企业降低成本&#xff0c;提高效率&#xff0c;还可以实现全方位的在线营销&#xff0c;为企业争取更多的竞争优势&#xff0c;如SHOP、Magento等一系列成熟…

EBU5476 Microprocessor System Design 知识点总结_3 Assembly

Assembly 汇编语法。 顺序结构 label ; 可省略&#xff0c;用于跳转到此位置助记符 operand1, operand2, … ; CommentsMOV r1, #0x01 ; 数据0x01放入r1 MOV r1, #A ; 数据A的ascii码放入r1 MOV R0, R1 ; move R1 into R0 MOVS R0, R1 ; move R1 i…

当 GraphQL 遇上图数据库,便有了更方便查询数据的方式

人之初&#xff0c;性本鸽。 大家好&#xff0c;我叫储惠龙&#xff08;实名上网&#xff09;&#xff0c;你可以叫我小龙人&#xff0c;00 后一枚。目前从事后端开发工作。 今天给大家带来一个简单的为 NebulaGraph 提供 GraphQL 查询支持的 DEMO&#xff0c;为什么是简单的…

职业教育机构转线上时,选择平台要注意哪些方面?

职业教育是提升技能和知识的重要途径&#xff0c;有效的职业教育能够帮助培养和发展人才&#xff0c;相比较线下面授课程相比&#xff0c;在线直播的教学&#xff0c;可以节省较大成本&#xff0c;那么在选型直播平台时&#xff0c;要注意哪些方面呢&#xff1f; 1.需要实现高清…

记录一次使用__dirname和./引出的bug

JS项目中 保存本地生成的图片时使用的路径:__dirname“/waitToFinishTask.png"。 但是在获取这张图片的时候我使用的是“./waitToFinishTask.png”。 从而抛出异常&#xff1a;Error: ENOENT, No such file or directory ./waitToFinishTask.png 找了好久都不知道为什么会…