JAVA的BIO、NIO、AIO模式精解(一)

news2024/12/23 13:41:46

1. BIO、NIO、AIO介绍

在不同系统或进程间数据交互,或高并发场景下都选哟网络通信。早期是基于性能低下的同步阻塞IO(BIO)实现。后支持非阻塞IO(NIO)。
前置须知:javsse,java多线程,javaIO,java网络模型
目的:局域网内通信,多系统间底层消息传递机制,高并发下大数据通信,游戏应用。

2 .java的io演进

2.1 IO模型基本说明

IO模型:性能取决于用什么通信模式或架构进行数据传输和接收。java共支持3种网络编程的IO,见标题

2.2 IO模型

javaBIO

同步并阻塞。服务器一个链接一个线程,即客户端请求服务器只启动一个线程处理,如果链接空闲则浪费线程开销。

javaNIO

同步非阻塞。服务器一个线程处理多个链接(请求)。即客户端请求都会注册到多路复用上。轮询到有IO请求就进行处理。
java NIO

javaAIO(NIO2.0版本)

异步非阻塞。服务器一个有效请求一个线程,客户端IO请求都由OS先完成了在通知服务器创建线程处理,一般用于连接数较多且链接时间较长应用。

2.3 BIO、NIO、AIO使用场景

  1. BIO:连接小且固定架构。JDK1.4前唯一选择,简单。
  2. NIO:连接多且较短架构。比如聊天,弹幕,服务间通讯等,JDK1.4后支持,复杂。
  3. AIO:连接多且较长家都。比如相册服务,充分调用OS参与并发,JDK1.7后支持,复杂。

3. Java BIO

3.1 BIO介绍

相关类接口间java.io。一个链接创建一个线程。可通过线程池优化成多客户端链接。

3.2 BIO机制

BIO

3.3 传统的BIO编程实例

网络编程CS架构实现两个进程间通信,服务端提供IP+PORT,客户端通过链接操作向服务端监听的端口地址发起请求。基于TCP三次握手建立链接,通过套接字Socket进行通信。
同步阻塞种服务端serverSocket负责绑定IP地址,启动监听端口。客户端Socket负责发起请求。通过输入输出流进行同步阻塞通信。
特点:C/S完全同步,耦合。

public class Client {
    public static void main(String[] args) throws IOException {
        //1.创建socket对象请求服务端的链接
        Socket socket = new Socket("127.0.0.1", 9999);
        //2.从socket对象中获取一个字节输出流
        OutputStream os = socket.getOutputStream();
        //3.把字节输出流包装成一个打印流
        PrintStream ps = new PrintStream(os);
        ps.println("hello world!服务端");
        ps.flush();
    }
}
/**
 * 目标:客户端发送消息,服务端接受消息
 */
public class Server {
    public static void main(String[] args) {
        try {
            System.out.println("服务端启动");
            //1.定义ServerSocket对象惊醒服务器端口注册
            ServerSocket serverSocket = new ServerSocket(9999);
            //2.监听客户端的Socket链接请求
            Socket socket = serverSocket.accept();
            //3.从socket管道中得到一个字节输入流对象
            InputStream is = socket.getInputStream();
            //4.把字节输入流包装成一个缓冲字符输入流 要以行为单位读取
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            if ((msg = br.readLine()) != null) {
                System.out.println("服务端接收到:" + msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

小结:

  • 在以上通信中,服务端会一直等待客户端的消息,如果客户端没有进行消息的发送,服务端将一直进入阻塞状态
  • 同时服务端是按照行 获取信息的,客户端也必须按照行 发送。否则服务端进入等待消息的阻塞态。

3.4 BIO实现多发和多收消息

在3中,只能客户端发送消息,服务端接收消息,并不能反复的接收和发送消息。改进:

Client:
        PrintStream ps = new PrintStream(os);
        Scanner sc = new Scanner(System.in);
        while(true) {
            System.out.println("input:");
            String msg = sc.nextLine();
            ps.println(msg);
            ps.flush();
        }
Server:
       while ((msg = br.readLine()) != null) {
           System.out.println("服务端接收到:" + msg);
       }

小结:

  • 服务端只能处理一个客户端请求,因为单线程,一次只能与一个客户端进行消息通信。

3.5 BIO下接收多个客户端

需在服务端引入多线程解决多客户端请求。这样就实现了一个客户端一个线程模型。

/**
 * 目标:实现服务端同时处理多个客户端的socket连接
 * 实现:服务端每接收一个客户端socket请求对象后都交给一个独立线程处理客户端的数据交互。
 */
public class Server {
    public static void main(String[] args) {
        try{
            //1.注册端口
            ServerSocket ss = new ServerSocket(9999);
            //2.定义死循环,不断接收客户端的Socket链接
            while(true) {
                Socket socket = ss.accept();
                //3.创建一独立的线程来处理与这个客户端的socket通信
                new ThreadServerReader(socket).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class ThreadServerReader extends Thread {
    private Socket socket;

    public ThreadServerReader(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //从socket对象中得到一个字节输入流
            InputStream is = socket.getInputStream();
            //使用缓冲字符输入流包装字节输入流
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            while ((msg = br.readLine()) != null) {
                System.out.println(msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

小结:

  1. 每个Socket接收都会创建线程,线程竞争,切换上下文会影响性能。
  2. 每个线程都会占用栈空间和CPU资源
  3. 并不是每个Socket都进行IO操作,无意义的线程处理
  4. 客户端的并发访问增加时。服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致进程宕机或僵死。

3.6 伪异步IO编程

采用线程池和任务队列实现,当客户端接入,将Socket封装成一个Task交由后端的线程池进行处理。JDK线程池维护一个消息队列和N个活跃线程。对消息队列中socket任务进行处理,由于线程池可设置消息队列的大小和最大线程数,因此,资源可控,无论多少客户端并发访问,都不会资源不够。

/**
 * 目标:伪异步通信架构
 */
public class Server {
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(9999);
            //初始化线程池对象
            HandlerSocketServerPool pool = new HandlerSocketServerPool(6, 10);
            while(true) {
                Socket socket = ss.accept();
                //把socket封装成任务对象交给一个线程池来处理
                Runnable target = new ServerRunnableTarget(socket);
                pool.execute(target);
            }
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}
//线程池
public class HandlerSocketServerPool {
    //1.创建一个线程池
    private ExecutorService excutorService;

    //2.初始化
    public HandlerSocketServerPool(int maxThreadNum, int queueSize) {
        excutorService = new ThreadPoolExecutor(3, maxThreadNum, 120, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize));
    }

    //3.提供方法提交任务给线程池的任务来暂存,等待线程池来执行
    public void execute(Runnable target){
        excutorService.execute(target);
    }
}
//功能
public class ServerRunnableTarget implements Runnable {
    private Socket socket;

    public ServerRunnableTarget(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        //处理客户端socket请求
        try {
            InputStream is = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            if ((msg = br.readLine()) != null) {
                System.out.println(msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

小结:

  • 伪异步io采用线程池实现,因此避免了为每个请求都创建独立线程造成资源耗尽的问题,由于底层还是同步阻塞,没解决根本问题。
  • 如果单个消息处理缓慢,或服务器全部阻塞。那么后面的socket的io消息都将在队列中排队,新的socket将被拒绝,客户端会大量链接超时。

3.7 基于BIO形势下的文件上传

/**
 * 实现客户端任意类型文件给服务端保存
 */
public class Client {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1", 8888);
            //把字节输出流包装成一个数据输出流
            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
            //发送文件后缀给服务端
            dos.writeUTF(".png");
            //把文件数据发送给服务端
            InputStream is = new FileInputStream("//Path");
            byte[] buffer = new byte[1024];
            int len;
            while ((len = is.read(buffer)) > 0) {
                dos.write(buffer, 0, len);
            }
            dos.flush();
            //通知服务端接收完毕
            socket.shutdownOutput();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/**
 * 接收客户端任意类型文件并保存
 */
public class Server {
    public static void main(String[] args) {
        try {
            ServerSocket socket = new ServerSocket(8888);
            while (true) {
                Socket accept = socket.accept();
                //交给独立线程来处理与这个客户端的文件通信需求
                new ServerReaderThread(accept).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class ServerReaderThread extends Thread {
    private Socket socket;

    public ServerReaderThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //得到数据输入流读取客户端发送过来的数据
            DataInputStream dis = new DataInputStream(socket.getInputStream());
            //读取客户端发送的文件类型
            String suffix = dis.readUTF();
            System.out.println("收到文件,类型:" + suffix);
            //定义字节输出管道,负责把客户端发过来的数据写出
            FileOutputStream os = new FileOutputStream("C:\\path\\" + UUID.randomUUID().toString() + suffix);
            //从数据输入流中读取文件数据,写出到字节输出流中
            byte[] buffer = new byte[1024];
            int len;
            while ((len = dis.read(buffer)) > 0) {
                os.write(buffer, 0, len);
            }
            os.close();
            System.out.println("保存文件成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.8 javaBIO下的端口转发

需求:一个客户端消息可发送所有的客户端接收(群聊)
BIO端口转发

/**
 * BIO下服务端端口转发
 * 服务端需求:
 *      1.注册端口
 *      2.收到客户端的socket链接,交给独立的线程来处理
 *      3.把当前连接的客户端socket存入到一个所谓的在线socket集合中保存
 *      4.接收客户端消息,然后推送给当前所有在线的socket接收
 */
public class Server {
    //定义静态集合
    public static List<Socket> allSocketOnLine = new ArrayList<>();

    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(9999);
            while(true){
                Socket socket = ss.accept();
                //把登陆的客户端socket存入到一个在线集合中去
                allSocketOnLine.add(socket);
                //为当前登录成功的socket分配一个独立的线程来处理与之通信
                new ServerReaderThread(socket).start();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
public class ServerReaderThread extends Thread {
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run(){
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String msg;
            while ((msg = br.readLine())!=null){
                //服务端接收到客户端的消息推送给当前所有在线socket
                sendMsgToAllClient(msg);
            }
        }catch (Exception e){
            System.out.println("当前有人下线!");
            //从在线socket中移除本socket
            Server.allSocketOnLine.remove(socket);
            e.printStackTrace();
        }
    }

    //把当前客户端发来的消息推送全部在线socket
    private void sendMsgToAllClient(String msg) throws IOException {
        for (Socket sk : Server.allSocketOnLine) {
            PrintStream ps = new PrintStream(sk.getOutputStream());
            ps.println(msg);
            ps.flush();
        }
    }
}

3.9 基于BIO下即时通信

项目功能

需要解决客户端到客户端的通信,即实现客户端间的端口消息转发。
功能说明:
1.客户端登陆:输入用户名和服务端ip
2.在线人数实时更新:用户登录同步更新客户端联系人列表
3.离线人数更新:下线同步
4.群聊:任一客户端消息转发所有客户端接收
5.私聊:选择某一对象发送消息
6.@消息:可@该用户,所有人可见
7.消息用户和时间点:服务端记录用户消息时间点,然后进行多路转发或选择。

服务端设计

服务端接收多个客户端

服务端需要接收多个客户端的接入。

  • 1.服务端需要接收多个客户端,目前我们采取的策略是一个客户端对应一个服务端线程。
  • 2.服务端除了要注册端口以外,还需要为每个客户端分配一个独立线程处理与之通信。

服务端主体代码,主要进行端口注册,和接收客户端,分配线程处理该客户端请求

服务端接收登录消息及检测离线

接收客户端的登陆消息。

实现步骤
  • 需要在服务端处理客户端的线程的登陆消息。
  • 需要注意的是,服务端需要接收客户端的消息可能有很多种。
    • 分别是登陆消息,群聊消息,私聊消息 和@消息。
    • 这里需要约定如果客户端发送消息之前需要先发送消息的类型,类型我们使用信号值标志(1,2,3)。
      • 1代表接收的是登陆消息
      • 2代表群发| @消息
      • 3代表了私聊消息
  • 服务端的线程中有异常校验机制,一旦发现客户端下线会在异常机制中处理,然后移除当前客户端用户,把最新的用户列表发回给全部客户端进行在线人数更新。

服务端接收群聊

接收客户端发来的群聊消息推送给当前在线的所有客户端

实现步骤
  • 接下来要接收客户端发来的群聊消息。
  • 需要注意的是,服务端需要接收客户端的消息可能有很多种。
    • 分别是登陆消息,群聊消息,私聊消息 和@消息。
    • 这里需要约定如果客户端发送消息之前需要先发送消息的类型,类型我们使用信号值标志(1,2,3)。
      • 1代表接收的是登陆消息
      • 2代表群发| @消息
      • 3代表了私聊消息

服务端接收私聊

私聊消息的推送逻辑.

实现步骤
  • 解决私聊消息的推送逻辑,私聊消息需要知道推送给某个具体的客户端
  • 我们可以接收到客户端发来的私聊用户名称,根据用户名称定位该用户的Socket管道,然后单独推送消息给该Socket管道。
  • 需要注意的是,服务端需要接收客户端的消息可能有很多种。
    • 分别是登陆消息,群聊消息,私聊消息 和@消息。
    • 这里需要约定如果客户端发送消息之前需要先发送消息的类型,类型我们使用信号值标志(1,2,3)。
      • 1代表接收的是登陆消息
      • 2代表群发| @消息
      • 3代表了私聊消息
    小结
demo地址:https://gitee.com/xuyu294636185/JAVA_IO_DEMO.git
  • 本节我们解决了私聊消息的推送逻辑,私聊消息需要知道推送给某个具体的客户端Socket管道
  • 我们可以接收到客户端发来的私聊用户名称,根据用户名称定位该用户的Socket管道,然后单独推送消息给该Socket管道。

客户端设计

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

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

相关文章

[江西专升本/信息技术]计算机网络基础

1、概论 目前主要从资源共享观点定义计算机网络&#xff1a; 用通信路线和通信设备将分布在不同地点的具有独立功能的多个计算机系统相互连接起来&#xff0c;在功能完善的网络软件的支持下实现彼此之间的数据通信和资源共享的系统&#xff1b; 我们可以这么说&#xff0c;“…

effective c++ item30-34

item30:理解inline 1、inline函数 用inline修饰函数可以防止multiple definition的错误 // foo.h inline int foo(int x){ // 如果不加inline&#xff0c;在编译时会有两个foo.h被包含进去&#xff0c;导致链接出错static int n 1;return x * (n ); } // bar1.cpp #includ…

【车道线算法】GANet-车道线检测环境配置一文通关

目录 GANet配置全纪录 下载代码 conda环境部署 安装torch和cudatoolkit 安装其他包 编译 总结 GANet配置全纪录 下载代码 GitHub - Wolfwjs/GANet: A Keypoint-based Global Association Network for Lane Detection. Accepted by CVPR 2022 进入代码网址的默认master…

ArcGIS Pro工程

目录 1 工程基础架构 2 工程内容 3 新建工程及工程文件 3.1 工程模板 3.2 工程名称和位置 3.2.1 基于“地图模板”创建新工程 3.2.2 基于“目录模板”创建新工程 3.2.3 基于“全局场景”创建新工程 3.2.4 基于“局部场景”创建新工程 3.3 工程文件 3.3.1 默认地理…

子元素选择器

知识点&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta http-equiv"X-UA-Compatible" content"IEedge"> <meta name"viewport" c…

【MySQL高级】——用户与权限管理

一、用户管理 <1> 登录 mysql –h hostname|hostIP –P port –u username –p DatabaseName –e "SQL语句"-h参数 后面接主机名或者主机IP&#xff0c;hostname为主机&#xff0c;hostIP为主机IP。 -P参数 后面接MySQL服务的端口&#xff0c;通过该参数连接…

序列检测和序列发生器——verilog代码实现

文章目录 前言一、序列检测器1.1 重复序列检测1.1.1 序列缓存对比/移位寄存器法1.1.2 状态机法 1.2 非重复序列检测 二、序列发生器2.1 移位寄存器法2.2 反馈法2.3 计数器法 前言 2023.4.25 2023.4.26 学习打卡&#xff0c;天气转晴 一、序列检测器 1.1 重复序列检测 1.1.1 …

HTML基础标签

目录 1.html文件结构 2.注释 3.文本标签 4.图片 5.音频与视频 6.超链接 7.表单 8.列表&#xff08;可以嵌套&#xff09; 无序列表 有序列表 定义列表 9.表格 10.语义标签 11.特殊符号 1.html文件结构 <!-- 文档类型声明 --> <!DOCTYPE html> <html>…

学系统集成项目管理工程师(中项)系列12_干系人管理

1. 要管理干系人的期望&#xff0c;更要保证他们的适度参与&#xff0c;而后者是项目成功非常关键的因素之一 1.1. 识别干系人及其信息 1.2. 评估关键干系人的诉求和影响力 1.3. 对干系人分类 1.4. 制定干系人管理计划 1.5. 【19上选56】 2. 识别干系人 2.1. 典型干系人…

如何解决宝塔面板nginx占用80端口的问题

前言: 由于使用Docker部署前端项目老是报错说80端口被占用,所以不得不去查找原因并且解决 上网查了很多资料说修改80端口这对我(有点强迫症)来说修改让我很难受 开始: 我们去找到虚拟机的这个目录 /www/server/panel/vhost/nginx 然后直接修改下面的文件 修改文件的端口 最…

windows中python安装保姆教程

猜你感兴趣 牛逼的python库-使用Pyinstaller将python打包成exe使用python实现自动点击功能python进程&#xff0c;线程&#xff0c;协程简介快速搭建私有pip镜像源 简介 Python 是一种开源的解释型脚本编程语言&#xff0c;Python简单易用&#xff0c;学习成本低&#xff0c;…

ChatGLM-6B的P-Tuning微调详细步骤及结果验证

文章目录 1. ChatGLM-6B1.1 P-Tuning v2简介 2. 运行环境2.1 项目准备 3.数据准备4.使用P-Tuning v2对ChatGLM-6B微调5. 模型评估6. 利用微调后的模型进行验证6.1 微调后的模型6.2 原始ChatGLM-6B模型6.3 结果对比 1. ChatGLM-6B ChatGLM-6B仓库地址&#xff1a;https://githu…

<<c和指针>>温故及问题研讨(第三章)

第三章-数据 1. 前言2. 基本数据类型2.1 整型家族2.2 字面值常量 3. 基本声明3.1 数组的声明以及引用3.2 指针的声明注意事项3.3 隐式声明 4. 常量5. 作用域5.1 代码块作用域5.2 文件作用域5.3 原型作用域 6. 链接属性6.1 链接属性分类以及作用范围6.2 关键字:extern和static6.…

数据库基础篇 《18.MySQL8其它新特性》

第18章_MySQL8其它新特性 1. MySQL8新特性概述 MySQL从5.7版本直接跳跃发布了8.0版本&#xff0c;可见这是一个令人兴奋的里程碑版本。MySQL 8版本在功能上做了显著的改进与增强&#xff0c;开发者对MySQL的源代码进行了重构&#xff0c;最突出的一点是多MySQL Optimizer优化…

GPT详细安装教程-GPT软件国内也能使用

GPT (Generative Pre-trained Transformer) 是一种基于 Transformer 模型的自然语言处理模型&#xff0c;由 OpenAI 提出&#xff0c;可以应用于各种任务&#xff0c;如对话系统、文本生成、机器翻译等。GPT-3 是目前最大的语言模型之一&#xff0c;其预训练参数超过了 13 亿个…

LeetCode:206. 反转链表

&#x1f34e;道阻且长&#xff0c;行则将至。&#x1f353; &#x1f33b;算法&#xff0c;不如说它是一种思考方式&#x1f340; 算法专栏&#xff1a; &#x1f449;&#x1f3fb;123 一、&#x1f331;206. 反转链表 题目描述&#xff1a;给你单链表的头节点 head &#x…

Sharding-JDBC之垂直分库水平分表

目录 一、简介二、maven依赖三、数据库3.1、创建数据库3.2、订单表3.3、用户表 四、配置&#xff08;二选一&#xff09;4.1、properties配置4.2、yml配置 五、实现5.1、实体5.2、持久层5.3、服务层5.4、测试类5.4.1、保存订单数据5.4.2、查询订单数据5.4.3、保存用户数据5.4.4…

Android SeekBar控制视频播放进度(二)——seekTo()不准确

Android SeekBar控制视频播放进度二——seekTo不准确 简介seekTo()视频帧 和 视频关键帧解决办法方法一方法二 简介 上一篇文章中&#xff0c;我们介绍了使用SeekBar控制视频播放&#xff0c;使用过程中发现&#xff0c;对于一些视频&#xff0c;我们拖动SeekBar进度条调节播放…

喜报 | ScanA内容安全云监测获评“新一代信息技术创新产品”

4月20日&#xff0c;在赛迪主办的2023 IT市场年会上&#xff0c;“年度IT市场权威榜单”正式发布。 知道创宇的ScanA内容安全云监测产品荣获“新一代信息技术创新产品”奖项。作为中国IT业界延续时间最长的年度盛会之一&#xff0c;历届IT市场年会公布的IT市场权威榜单已成为市…

备份数据看这里,免费教你苹果手机怎么备份所有数据!

案例&#xff1a;苹果手机怎么算备份成功&#xff1f; 【友友们&#xff0c;手机恢复出厂设置前&#xff0c;怎么样可以备份苹果手机里面的所有数据&#xff1f;】 苹果手机备份数据对于用户来说是非常重要的。在备份数据的同时&#xff0c;还需要学会如何恢复误删的数据。那么…