3.JAVA BIO深入剖析

news2024/11/28 2:40:11

highlight: arduino-light

1.JAVA BIO深入剖析

1.1 Java BIO 基本介绍

  • Java BIO 就是传统的 java io 编程,其相关的类和接口在 java.io
  • BIO(blocking I/O) : 同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善(实现多个客户连接服务器).
  • 每个请求都需要创建独立的线程,与对应的客户端进行数据 Read,业务处理,数据 Write 。
  • 当并发数较大时,需要创建大量线程来处理连接,系统资源占用较大。
  • 连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞在 Read 操作上,造成线程资源浪费

1.2 Java BIO 工作机制

BIO编程流程

image.png

0.服务器端启动一个 ServerSocket,注册端口,调用accpet方法监听客户端的Socket连接。

1.客户端启动Socket和服务器进行通信,默认情况下服务端需要跟每个客户建立一个线程与之通讯。

1.3 传统的BIO编程实例回顾

网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定IP地址和端口),客户端通过连接操作向服务端监听的端口地址发起连接请求,基于TCP协议下进行三次握手连接,连接成功后,双方通过网络套接字(Socket)进行通信。

传统的同步阻塞模型开发中,服务端ServerSocket负责绑定IP地址,启动监听端口。

客户端Socket负责发起连接操作。

连接成功后,双方通过输入和输出流进行同步阻塞式通信。

基于BIO模式下的通信,客户端 - 服务端是完全同步,完全耦合的。  

服务端案例如下

```java

import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; ​ /** * 服务端 */ public class ServerDemo {    public static void main(String[] args) throws Exception {        System.out.println("==服务器的启动==");        // (1)注册端口        ServerSocket serverSocket = new ServerSocket(8888);        //(2)开始在这里暂停等待接收客户端的连接,得到一个客户端到服务端的Socket管道        Socket socket = serverSocket.accept();        //(3)从Socket管道中得到一个字节输入流。        InputStream is = socket.getInputStream();        //(4)把字节输入流包装成自己需要的流进行数据的读取。        BufferedReader br = new BufferedReader(new InputStreamReader(is));        //(5)读取数据        String line ;        while((line = br.readLine())!=null){            System.out.println("服务端收到:"+line);       }   } } ```

客户端案例如下

```java

import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; /**    目标: Socket网络编程。 ​    Java提供了一个包:java.net下的类都是用于网络通信。    Java提供了基于套接字(端口)Socket的网络通信模式,我们基于这种模式就可以直接实现TCP通信。    只要用Socket通信,那么就是基于TCP可靠传输通信。 ​    功能1:客户端发送一个消息,服务端接口一个消息,通信结束!! ​    创建客户端对象:        (1)创建一个Socket的通信管道,请求与服务端的端口连接。        (2)从Socket管道中得到一个字节输出流。        (3)把字节流改装成自己需要的流进行数据的发送    创建服务端对象:        (1)注册端口        (2)开始等待接收客户端的连接,得到一个端到端的Socket管道        (3)从Socket管道中得到一个字节输入流。        (4)把字节输入流包装成自己需要的流进行数据的读取。 ​    Socket的使用:        构造器:public Socket(String host, int port)        方法: public OutputStream getOutputStream():获取字节输出流               public InputStream getInputStream() :获取字节输入流 ​    ServerSocket的使用:        构造器:public ServerSocket(int port) ​    小结:        通信是很严格的,对方怎么发你就怎么收,对方发多少你就只能收多少!! ​ */ public class ClientDemo {    public static void main(String[] args) throws Exception {        System.out.println("==客户端的启动==");        // (1)创建一个Socket的通信管道,请求与服务端的端口连接。        Socket socket = new Socket("127.0.0.1",8888);        // (2)从Socket通信管道中得到一个字节输出流。        OutputStream os = socket.getOutputStream();        // (3)把字节流改装成自己需要的流进行数据的发送        PrintStream ps = new PrintStream(os);        // (4)开始发送消息        ps.println("我是客户端,我想约你吃小龙虾!!!");        ps.flush();   } } ```

小结

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

1.4 BIO模式下多发和多收消息

在1.3的案例中,只能实现客户端发送消息,服务端接收消息,并不能实现反复的收消息和反复的发消息,我们只需要在客户端案例中,加上反复按照行发送消息的逻辑即可!案例代码如下:

服务端代码如下

```

import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; ​ /** * 服务端 */ public class ServerDemo {    public static void main(String[] args) throws Exception {                System.out.println("==服务器的启动==");        //(1)注册端口        ServerSocket serverSocket = new ServerSocket(8888);        //(2)开始在这里暂停等待接收客户端的连接,得到一个端到端的Socket管道        Socket socket = serverSocket.accept();        //(3)从Socket管道中得到一个字节输入流。只要客户端有写入操作,is就能读取到。        //在while循环的时候会立刻读取到        InputStream is = socket.getInputStream();        //(4)把字节输入流包装成 自己需要的流进行数据的读取。        BufferedReader br = new BufferedReader(new InputStreamReader(is));        //(5)读取数据        String line ;        while((line = br.readLine())!=null){            System.out.println("服务端收到:"+line);       }   } } ```

客户端代码如下

```java

import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; ​ /**    目标: Socket网络编程。    功能1:客户端可以反复发消息,服务端可以反复收消息    小结:        通信是很严格的,对方怎么发你就怎么收,对方发多少你就只能收多少!! */ public class ClientDemo {    public static void main(String[] args) throws Exception {        System.out.println("==客户端的启动==");        // (1)创建一个Socket的通信管道,请求与服务端的端口连接。        Socket socket = new Socket("127.0.0.1",8888);        // (2)从Socket通信管道中得到一个字节输出流。        OutputStream os = socket.getOutputStream();        // (3)把字节流改装成自己需要的流进行数据的发送        PrintStream ps = new PrintStream(os);        // (4)开始发送消息        Scanner sc = new Scanner(System.in);        while(true){            System.out.print("请说:");            String msg = sc.nextLine();            ps.println(msg);            ps.flush();       }   } } ```

小结

  • 本案例中确实可以实现客户端多发多收
  • 但是服务端只能处理一个客户端的请求,因为服务端是单线程的。一次只能与一个客户端进行消息通信。

1.5 BIO模式下接收多个客户端

概述

在上述的案例中,一个服务端只能接收一个客户端的通信请求,那么如果服务端需要处理很多个客户端的消息通信请求应该如何处理呢,此时我们就需要在服务端引入线程了,也就是说客户端每发起一个请求,服务端就创建一个新的线程来处理这个客户端的请求,这样就实现了一个客户端一个线程的模型,图解模式如下:

image.png

服务端案例代码

```java /**    服务端    1.每接收到一个客户端的连接就启动一个线程    2.将客户端连接作为参数传入线程中    3.监听客户端连接的输入流    4.空转输出输入流内容 */ public class ServerDemo {    public static void main(String[] args) throws Exception {        System.out.println("==服务器的启动==");        // (1)注册端口        ServerSocket serverSocket = new ServerSocket(7777);        while(true){            //(2)开始在这里暂停等待接收客户端的连接,得到一个端到端的Socket管道            Socket socket = serverSocket.accept();            new ServerReadThread(socket).start();            System.out.println(socket.getRemoteSocketAddress()+"上线了!");       }   } }

class ServerReadThread extends Thread{    private Socket socket;

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

   @Override    public void run() {        try{            //(3)从Socket管道中得到一个字节输入流。            InputStream is = socket.getInputStream();            //(4)把字节输入流包装成自己需要的流进行数据的读取。            BufferedReader br = new BufferedReader(new InputStreamReader(is));            //(5)读取数据            String line ;            while((line = br.readLine())!=null){              System.out.println("服务端收到:"+                                socket.getRemoteSocketAddress()+":"+line);           }       }catch (Exception e){            System.out.println(socket.getRemoteSocketAddress()+"下线了!");       }   } } ```

客户端案例代码

java /**    目标: Socket网络编程。    功能1:客户端可以反复发,一个服务端可以接收无数个客户端的消息!!    小结:         服务器如果想要接收多个客户端,那么必须引入线程,一个客户端一个线程处理!! ​ */ public class ClientDemo {    public static void main(String[] args) throws Exception {        System.out.println("==客户端的启动==");        // (1)创建一个Socket的通信管道,请求与服务端的端口连接。        Socket socket = new Socket("127.0.0.1",7777);        // (2)从Socket通信管道中得到一个字节输出流。        OutputStream os = socket.getOutputStream();        // (3)把字节流改装成自己需要的流进行数据的发送        PrintStream ps = new PrintStream(os);        // (4)开始发送消息        Scanner sc = new Scanner(System.in);        while(true){            System.out.print("请说:");            String msg = sc.nextLine();            ps.println(msg);            ps.flush();       }   } }

小结

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

1.6 使用BIO模拟NIO

概述:BIO + 线程池

在上述案例中:客户端的并发访问增加时。服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致进程宕机或者僵死,从而不能对外提供服务。

接下来我们采用一个伪异步I/O的通信框架,采用线程池和任务队列实现,当客户端接入时,将客户端的Socket封装成一个Task(该任务实现java.lang.Runnable线程任务接口)交给后端的线程池中进行处理。JDK的线程池维护一个消息队列和N个活跃的线程,对消息队列中Socket任务进行处理,由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。

但是如果单个消息处理的缓慢,或者服务器线程池中的全部线程都被阻塞,那么后续socket的i/o消息都将在队列中排队。新的Socket请求将被拒绝,客户端会发生大量连接超时。

图示如下:

image.png

线程池处理类

```java // 线程池处理类 public class HandlerSocketThreadPool { // 线程池 private ExecutorService executor; //核心线程数 队列数量 public HandlerSocketThreadPool(int maxPoolSize, int queueSize){ //核心线程数默认是3 this.executor = new ThreadPoolExecutor( 3, maxPoolSize,
120L, TimeUnit.SECONDS, new ArrayBlockingQueue (queueSize) ); }

public void execute(Runnable task){ this.executor.execute(task); } } ```

服务端源码分析

java /***    服务端    0.创建一个线程池    1.每接收到一个客户端的连接就启动一个线程    2.将客户端连接作为参数传入线程中    3.监听客户端连接的输入流    4.空转输出输入流内容 ***/   public class Server {   public static void main(String[] args) {      try {         System.out.println("----------服务端启动成功------------");         ServerSocket ss = new ServerSocket(9999); ​         // 一个服务端只需要对应一个线程池         HandlerSocketThreadPool handlerSocketThreadPool =               new HandlerSocketThreadPool(3, 1000); ​         // 客户端可能有很多个         while(true){            Socket socket = ss.accept() ; // 阻塞式的!            System.out.println("有人上线了!!");            // 每次收到一个客户端的socket请求,都需要为这个客户端分配一个            // 独立的线程 专门负责对这个客户端的通信!!            handlerSocketThreadPool.execute(new ReaderClientRunnable(socket));         } ​     } catch (Exception e) {         e.printStackTrace();     }   } ​ } class ReaderClientRunnable implements Runnable{ ​   private Socket socket ; ​   public ReaderClientRunnable(Socket socket) {      this.socket = socket;   } ​   @Override   public void run() {      try {         // 读取一行数据         InputStream is = socket.getInputStream() ;         // 转成一个缓冲字符流         Reader fr = new InputStreamReader(is);         BufferedReader br = new BufferedReader(fr);         // 一行一行的读取数据         String line = null ;         while((line = br.readLine())!=null){ // 阻塞式的!!            System.out.println("服务端收到了数据:"+line);         }     } catch (Exception e) {         System.out.println("有人下线了");     } ​   } }

客户端源码分析

java public class Client {   public static void main(String[] args) {      try {         // 1.简历一个与服务端的Socket对象:套接字         Socket socket = new Socket("127.0.0.1", 9999);         // 2.从socket管道中获取一个输出流,写数据给服务端         OutputStream os = socket.getOutputStream() ;         // 3.把输出流包装成一个打印流         PrintWriter pw = new PrintWriter(os);         // 4.反复接收用户的输入         BufferedReader br = new BufferedReader(new InputStreamReader(System.in));         String line = null ;         while((line = br.readLine()) != null){            pw.println(line);            pw.flush();         }     } catch (Exception e) {         e.printStackTrace();     }   } }

小结

  • 伪异步io采用了线程池实现,因此避免了为每个请求创建一个独立线程造成线程资源耗尽的问题,但由于底层依然是采用的同步阻塞模型,因此无法从根本上解决问题。

  • 如果单个消息处理的缓慢,或者服务器线程池中的全部线程都被阻塞,那么后续socket的i/o消息都将在队列中排队。新的Socket请求将被拒绝,客户端会发生大量连接超时。

比如核心线程数和最大线程数都设置1,但是客户端有2个。客户端A来连接的时候,如果它一直不下线,客户端B是一直无法被处理的,这个时候使用BIO+线程池就会陷入和1.5案例一样的困境:如果其它的客户端不下线,必须1个客户端对应1个线程,其它的客户端只能在队列中等待。除非客户端A下线释放线程池中的线程,这个时候客户端B才有被处理的可能。

  • 采用 多线程+ 阻塞IO 可以达到NIO类似的效果,但是由于在多线程 + 阻塞IO 中,每个socket对应一个线程,这样会造成很大的资源占用,并且尤其是对于长连接来说,线程的资源一直不会释放,如果后面陆续有很多连接的话,就会造成性能上的瓶颈。

    而多路复用IO模式,通过一个线程就可以管理多个socket,只有当socket真正有读写事件发生才会占用资源来进行实际的读写操作。因此,多路复用IO比较适合连接数比较多的情况。

    多路复用IO为何比非阻塞IO模型的效率高是因为在非阻塞IO中,不断地询问socket状态是通过用户线程去进行的,而在多路复用IO中,轮询每个socket状态是内核在进行的,这个效率要比用户线程要高的多。

到底什么是多路IO复用,后续再讲。

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

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

相关文章

东莞-戴尔R540服务器故障告警处理方法

DELL PowerEdge R540服务器故障维修案例:(看到文章就是缘分) 客户名称:东莞市某街道管理中心 故障机型:DELL R540服务器 故障问题:DELL R540服务器无法开机,前面板亮黄灯,工程师通过…

PatchMatchNet运行dtu数据集、

文章目录 1 准备工作2 dtu数据集重建演示2.1 bash文件超参数解释2.2 lists/dtu解释1 准备工作 MVSNet、PatchMatchNet环境配置 数据集下载,准备好数据集,我的dtu数据集放在/PatchmatchNet-main/Data/testData/dtu/下,如图所示 进入mvs 环境:source activate mvs 2 dtu数据…

Appium 安卓环境的配置

目录 前言: 环境准备 写个脚本玩玩 前言: 在使用Appium进行安卓自动化测试之前,需要配置相应的安卓环境。 环境准备 为了避免走弯路,我们先要确保三点: Android SDK API > 17 (Additional features require …

14matlab数理统计 多项式的求根和根据根求多项式(matlab程序)

1.简述 分享一下通过多种不同的方法计算多项式的根。 数值根 使用代换法求根 特定区间内的根 符号根 数值根 roots 函数用于计算系数向量表示的单变量多项式的根。 例如,创建一个向量以表示多项式 x2−x−6,然后计算多项式的根。 p [1 -1 -6]; r …

利用Python和Selenium编程,实现定时自动检索特定网页,发现特定网页内容发生变化后,向管理员发送提醒邮件(一)

一、项目需求 要求爬取某单位网站,登录后台查看是否有新增“网友提问”,如果有新的提问,向特定邮箱发出提醒邮件。 二、项目分析 (一)判断是否可用爬虫爬取相关内容 首先查看该网站的robots.txt文件,发现…

SpringCloud(四)Hystrix服务降级、熔断、监控页面

一、服务熔断 官方文档:https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/1.3.5.RELEASE/single/spring-cloud-netflix.html#_circuit_breaker_hystrix_clients 我们知道,微服务之间是可以进行相互调用的,那么如果出现了…

YUM报错:Could not retrieve mirrorlist

​​​​​​背景说明 ESXI新安装CentOS7,无法执行yum命令 报错内容解决方案 1.报错说明:问题在于yum无法获取CentOS的软件仓库地址,导致无法找到合法的baseurl2.检查网络连接:确保服务器可以访问互联网,以及能够解析D…

C++ CEF库 源码编译及使用(VS2019)

源码编译 官网下载源码 CEF Automated Builds

linux下mmdetection安装

linux下mmdetection安装 使用 MIM 安装 MMEngine 和 MMCV。安装 MMDetection 前提条件是配置了pytorch18的linux环境 参考流程:配置pycharm环境 拷贝pycharm环境为mmdet conda create -n mmdet --clone torch18进入 conda activate mmdet使用 MIM 安装 MMEngine …

量化基础 PTQ QAT

简介 固定bit下的量化始终无法在Accuracy和 (FLOPs & Parameters)之间达到一个非常细粒度的trade-off,所以就需要混合精度量化(Mixed-Precision Quantization, MPQ)来对模型实现进一步的高效压缩 混合精度量化区别于混合精度训练这个概念,后者指的是…

c基本数据类型

关键字 charshort intintlong intfloatdouble 常量和变量 常量:在程序运行过程中,其值不可改变的量变量:其值可以改变的量称为变量 字符数据 字符常量 直接常量:用单引号括起来,如:‘a’,‘b’.转义字…

7.4Java EE——Bean的作用域

一、singleton作用域 Spring支持的5种作用域 作用域名城 描述 singleton 单例模式。在单例模式下,Spring 容器中只会存在一个共享的Bean实例, 所有对Bean的请求,只要请求的id(或name)与Bean的定义相匹配&#xff0…

msvc2017x64编译器编译项目报错”编译器的堆空间不足“错误 的解决方法

开发日常软件的时候,因为项目较大,模块较多,编译时,报错”编译器编译空间不足“,且常规方法无法消除的问题。有 opengl模块 占用内存大 尝试 尝试1   按照常规的,在pro里面加大资源配置: CONF…

记录一下uniapp开发中遇到的一些问题

概述 最近码代码的时候遇到一些问题,这里自己记录总结一下,供大家参考,说得不对的地方希望大家指出 大概介绍一下我用到的内容 用HbuilderX新建一个uni-app项目 ,vue版本选的2,爬坑轻松一点移动端ui框架选了uView&…

C 知识积累 回车与换行 Linux C 语法分析

目录 回车与换行一.知其然二.知其所以然 关键字,操作符和函数区别1:关键字2:操作符3:函数 命令行参数argv原码补码补码加法 Linux C 语法分析结构体指针类型函数宏定义其他 const语法整理 回车与换行 一.知其然 \n是换行&#x…

智能电表远程抄表系统原理

智能电表远程抄表系统是现代智能电网建设的重要组成部分,它利用物联网技术实现电表数据的远程采集、传输和处理,提高了电力公司的抄表效率,同时也为用户提供了更加便捷、准确的用电服务。本文将从远程智能电表抄表系统的工作原理、特点、应用…

KDE项目近日发布了KDE Frameworks 5.108

导读KDE项目近日发布了KDE Frameworks 5.108,作为这个开源软件套件的最新版本,它由80多个Qt附加库组成,为KDE Plasma桌面环境和KDE应用程序提供常用功能。 KDE Frameworks 5.108在这里修复了Plasma桌面崩溃的问题,该问题发生在用中…

Openlayers layer 基础及重点内容讲解

图层就像是含有文字或图形等元素的图片,一张张按顺序叠放在一起,组合起来形成页面的最终效果。 在 openlayers 中,图层是使用 layer 对象表示的,主要有 WebGLPoints Layer、热度图(HeatMap Layer)、图片图层(Image Layer)、切片图层(Tile Layer)和 矢量图层(Vector Layer…

ShardingSphere分库分表实战之水平分表

🚀 ShardingSphere 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜&…

科东软件入选“四化”赋能重点平台

科东软件Intewell工业嵌入式实时操作系统成功入选广州市“四化”赋能重点平台名单 2023年7月14日,广州市工业和信息化局公布广州市“四化”赋能重点平台名单(第二批),科东软件凭借国产化技术创新优势、成熟的数字化转型方案&#…