java BIO

目录

Java BIO基本介绍

Java BIO工作机制

传统的BIO编程实例回顾

1、BIO模式下发送和接收消息

2、BIO模式下多发和多收消息

3、BIO模式下接收多个客户端

伪异步I/O编程

基于BIO形式下的文件上传

Java BIO模式下的端口转发思想


Java BIO基本介绍

  • Java BIO就是传统的java io 编程,其相关的类和接口在java.io
  • BlO(blocking I/O):同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善(实现多个客户连接服务器).

Java BIO工作机制

传统的BIO编程实例回顾

        网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信(绑定IP地址和端口),客户端通过连接操作向服务端监听的端口地址发起连接请求,基于TCP协议下进行三次握手连接,连接成功后,双方通过网络套接字(Socket)进行通信。
        传统的同步阻塞模型开发中,服务端ServerSocket负责绑定IP地址,启动监听端口;客户端Socket负责发起连接操作。连接成功后,双方通过输入和输出流进行同步阻塞式通信。
        基于BIO模式下的通信,客户端-服务端是完全同步,完全耦合的。

1、BIO模式下发送和接收消息

服务端

/**
 * 服务端接受消息
 */
public class Server {
    public static void main(String[] args) {
        try {
            System.out.println("——服务端启动——");
            //定义一个ServerSocket对象进行服务端的端口注册
            ServerSocket ss = new ServerSocket(9999);
            //监听客户端的socket链接请求
            Socket socket = ss.accept();
            //从socket管道中得到一个字节输入流对象
            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();
        }
    }
}

客户端

/**
 * 客户端发送消息
 */
public class Client {
    public static void main(String[] args) {
        try {
            //创建socket对象请求服务端的链接
            Socket socket = new Socket("127.0.0.1",9999);
            //从socket对象中获取一个字节输出流
            OutputStream os = socket.getOutputStream();
            //把字节输出流包装成一个打印流
            PrintStream ps = new PrintStream(os);
            ps.println("hello world!");
            ps.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
2、BIO模式下多发和多收消息

服务端

/**
 * 服务端可以反复的接收消息,
 */
public class Server {
    public static void main(String[] args) {
        try {
            System.out.println("——服务端启动——");
            //定义一个ServerSocket对象进行服务端的端口注册
            ServerSocket ss = new ServerSocket(9999);
            //监听客户端的socket链接请求
            Socket socket = ss.accept();
            //从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();
        }
    }
}

客户端

/**
 * 客户端可以反复的发送消息。
 */
public class Client {
    public static void main(String[] args) {
        try {
            //创建socket对象请求服务端的链接
            Socket socket = new Socket("127.0.0.1",9999);
            //从socket对象中获取一个字节输出流
            OutputStream os = socket.getOutputStream();
            //把字节输出流包装成一个打印流
            PrintStream ps = new PrintStream(os);
            //键盘输入
            Scanner sc = new Scanner(System.in);
            while (true){
                System.out.print("请输入:");
                String msg = sc.nextLine();
                ps.println(msg);
                ps.flush();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
3、BIO模式下接收多个客户端

打开idea点击运行

点击拷贝,然后点击apply和ok,然后点击运行

客户端分别输入信息发现只能接受第一个客户端的信息,因为服务端只有一个线程只能处理一个客户端的消息

服务端

/**
 * 目标:实现服务端可以同时接收多个客户端的Socket通信需求。
 * 思路:是服务端每接收到一个客户端socket请求对象之后都交给一个独立的线程
 */
public class Server {
    public static void main(String[] args) {

        ServerSocket ss = null;
        try {
            System.out.println("——服务端启动——");
            //定义一个ServerSocket对象进行服务端的端口注册
            ss = new ServerSocket(9999);
            //监听客户端的socket链接请求
            while (true){
                Socket socket = ss.accept();
                //创建一个独立的线程来处理与这个客户端的socket
                new ServerThreadReader(socket).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

 服务端使用多线程处理多个客户端

public class ServerThreadReader extends Thread{
    private Socket socket;

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

    @Override
    public void run() {
        InputStream is = null;
        try {
            //从socket管道中得到一个字节输入流对象
            is = socket.getInputStream();
            //把字节输入流包装成一个缓冲字符输入流
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            if ((msg = br.readLine()) != null){
                System.out.println("服务端接收到:"+msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

客户端

/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) {
        try {
            //创建socket对象请求服务端的链接
            Socket socket = new Socket("127.0.0.1",9999);
            //从socket对象中获取一个字节输出流
            OutputStream os = socket.getOutputStream();
            //把字节输出流包装成一个打印流
            PrintStream ps = new PrintStream(os);
            //键盘输入
            Scanner sc = new Scanner(System.in);
            while (true){
                System.out.print("请输入:");
                String msg = sc.nextLine();
                ps.println(msg);
                ps.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

结果显示服务端可以接受多个客户端发出的消息 

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

伪异步I/O编程

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

服务端

/**
 * 目标:开发实现伪异步通信架构
 */
public class Server {
    public static void main(String[] args) {
        try {
            //定义一个ServerSocket对象进行服务端的端口注册
            ServerSocket ss = new ServerSocket(9999);
            //初始化一个线程池
            HandlerSocketServerPool pool = new HandlerSocketServerPool(6,10);
            //监听客户端的socket链接请求
            while (true){
                //2、定义一个循环接收客户端的Socket链接请求
                Socket socket = ss.accept();
                //3、把socket对象交给一个线程池进行处理
                //把socket封装成一个任务对象交给线程池处理
                Runnable target = new ServerRunnableTarget(socket);
                pool.execute(target);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

创建一个线程池

/**
 * 创建一个线程池来处理消息
 */
public class HandlerSocketServerPool {
    //1、创建一个线程池的成员变量用于存储一个线程池对象
    private ExecutorService executorService;
    //2、创建这个类的对象的时候就需要初始化线程池对象
    public  HandlerSocketServerPool(int maxThread , int queueSize){
        executorService = new ThreadPoolExecutor(3,maxThread,120,
                TimeUnit.SECONDS ,new ArrayBlockingQueue<Runnable>(queueSize));
    }
    //3、提供一个方法来提交任务给线程池的任务队列来暂存,等着线程池来处理
    public void execute(Runnable target){
        executorService.execute(target);
    }
}
/**
 * 创建多线程处理消息
 */
public class ServerRunnableTarget implements Runnable{

    private Socket socket;
    public ServerRunnableTarget(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();
        }
    }
}

客户端

/**
 * 客户端可以反复的发送消息。
 */
public class Client {
    public static void main(String[] args) {
        try {
            //创建socket对象请求服务端的链接
            Socket socket = new Socket("127.0.0.1",9999);
            //从socket对象中获取一个字节输出流
            OutputStream os = socket.getOutputStream();
            //把字节输出流包装成一个打印流
            PrintStream ps = new PrintStream(os);
            //键盘输入
            Scanner sc = new Scanner(System.in);
            while (true){
                System.out.print("请输入:");
                String msg = sc.nextLine();
                ps.println(msg);
                ps.flush();
            }

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

小结
伪异步io采用了线程池实现,因此避免了为每个请求创建一个独立线程造成线程资源耗尽的问题,但由于底层依然是采用的同步阻塞模型,因此无法从根本上解决问题。
如果单个消息处理的缓慢,或者服务器线程池中的全部线程都被阻塞,那么后续socket的i/o消息都将在队列中排队。新的Socket请求将被拒绝,客户端会发生大量连接超时。

基于BIO形式下的文件上传

服务端

/**
 * 目标:服务端开发,可以实现接收客户端的任意类型文件,并保存到服务端磁盘
 */
public class Server {
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(8888);
            while (true){
                Socket socket = ss.accept();
                //交给一个独立的线程来处理与这个客户端的文件通信需求。
                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() {
        OutputStream os = null;
        try {
            //1、得到一个数据输入流读取客户端发送过来的数据
            DataInputStream dis = new DataInputStream(socket.getInputStream());
            //2、读取客户端发送过来的文件类型
            String suffix = dis.readUTF();
            System.out.println("文件类型位:"+suffix);
            //3、定义一个字节输出管道负责把客户端发来的文件数据写出去
            os = new FileOutputStream("E:\\photo\\server\\"+ UUID.randomUUID().toString()+suffix);
            //4、从数据输入流中读取文件数据,写出到字节输出流中去
            byte[] bytes = new byte[1024];
            int len;
            while ((len = dis.read(bytes)) != -1){
                os.write(bytes,0,len);
            }
            System.out.println("服务器端接受文件成功!");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

 客户端

/**
 * 目标:实现客户端上传任意类型的文件数据给服务端保存起来。
 */
public class Client {
    public static void main(String[] args) {
        try {
            //1、请求与服务端的Socket链接
            Socket socket = new Socket("127.0.0.1",8888);
            //2、把字节输出流包装成一个数据输出流
            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
            //3、法发送上传文件的后缀给服务端
            dos.writeUTF(".png");
            //4、把文f件数据发送给服务嵩进行接收
            InputStream is = new FileInputStream("E:\\photo\\figure\\1.png");
            byte [] bytes = new byte[1024];
            int len;
            while ((len = is.read(bytes)) != -1){
                dos.write(bytes,0,len);
            }
            dos.flush();
            //通知服务端这边的数据发送完毕
            socket.shutdownOutput();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

Java 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 {
            //1、从socket中去获取当前客户端的输入流
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String msg;
            while ((msg = br.readLine()) != null){
                //2、服务端接收到了客户端的消息之后,是需要推送给当前所有的在线socket
                sendMsgToAllClient(msg);
            }
        }catch (Exception e){
            System.out.println("当前有人下线了");
            //从在线socket集合中移除本socket
            Server.allSocketOnLine.remove(socket);
        }
    }

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

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

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

相关文章

Pytorch学习 day03(Tensorboard)

Tensorboard Tensorboard能够可视化loss的变化过程&#xff0c;便于我们查看模型的训练状态&#xff0c;也能查看模型当前的输入和输出结果 在Pycharm中&#xff0c;可以通过按住ctrl&#xff0c;并左键点击某个库来进入源文件查看该库的使用方法 SummaryWriter是用来向log_di…

C语言:指针(二)

目录 1.数组名的理解2.使用指针访问数组3.一维数组传参的本质4.二级指针5.指针数组6.字符指针变量7.数组指针变量8.二维数组传参的本质9.函数指针变量10.函数指针数组11.回调函数12.qsort函数13.使用回调函数模拟实现qsort函数 1.数组名的理解 int main() {int arr[] { 1,2,3…

上帝视角看GPU(5):图形流水线里的不可编程单元

【GPU】图形流水线基础【GPU】逻辑上的模块划分【GPU】部署到硬件【GPU】完整的软件栈 前几期我们过了一遍GPU的软硬栈。这次我们将深入GPU图形流水线的一些细节&#xff0c;看看那些不可编程的模块是怎么工作的。 对于GPU的图形流水线来说&#xff0c;最核心最重要的一个组件就…

通过人工智能增强的对话建立有意义的联系

人工智能如何重塑我们的交流&#xff1f;2024年最新对话AI趋势 在技术和人类互动比以往任何时候都更加复杂地交织在一起的时代&#xff0c;人工智能增强的对话已成为建立有意义的联系的关键要素。 这种转变不仅关乎效率&#xff0c;还关乎效率。 这是为了丰富沟通的结构。 在这…

MATLAB--pie函数绘制复杂分类饼图(2)--附案例代码

MATLAB–pie函数绘制复杂分类数据的饼状图 目录 MATLAB--pie函数绘制复杂分类数据的饼状图摘要1. 问题描述2. 具体步骤&#xff1a;3. 绘制结果4. 小结 摘要 在数据可视化中&#xff0c;饼状图是一种常用的展示分类数据的方式。之前&#xff0c;文章介绍了使用MATLAB绘制饼状图…

Vue中的计算属性和方法有什么区别?

Vue.js是一款流行的JavaScript前端框架&#xff0c;提供了丰富的功能和便捷的开发方式。在Vue中&#xff0c;计算属性和方法是常用的两种方式来处理数据和逻辑。但它们之间存在一些区别&#xff0c;本文将详细介绍Vue中计算属性和方法的区别&#xff0c;并通过示例代码加深理解…

654.最大二叉树

这段Java代码实现了一个名为Solution的类&#xff0c;其中包含两个方法&#xff1a;constructMaximumBinaryTree()和constructMaximumBinaryTree1()&#xff0c;目的是从给定的整数数组nums中构建出一个最大二叉树。以下是详细的注释说明&#xff1a; class Solution {// 主方…

GitHub Copilot extension activation error: ‘No access to GitHub Copilot found‘

好不容易学生认证通过了&#xff0c;打开vscode用copilot结果一直报这个错误。我的原因是&#xff1a;还未给copilot授权&#xff0c; 通过了学生认证后要进入这里进行授权&#xff1a;

MCU设计--M3内核整体功能说明

整体架构 内核特性 CM3内核支持3级流水哈佛结构 :数据和指令隔离Blanked SP :两个堆栈,一个堆栈只允许系统操作,另一个堆栈开放给用户。Handler and Thread modes低延迟中断进入和退出支持非对齐操作嵌套中断向量 最大支持1-240个外部中断可设置3-8的优先级可动态配置优先级…

一文了解什么是园区网以及如何部署园区网

目录 一、局域网分类 二、园区网的业务部署内容 1、构建高可靠可冗余网络 2、组播业务的快速开展 3、语音业务的部署 4、网络安全的部署 5、网络管理和维护的应用 一、局域网分类 &#xff08;1&#xff09;园区网&#xff1a; 目的&#xff1a;让各种服务器提供服务 …

mysql学习笔记3——授权操作

利用select查询数据库数据时&#xff0c;可以在后面加上参数 意为限制显示条数 对数据库进行远程操作时&#xff0c;为了避免不同用户操作混乱&#xff0c;要设置不同用户的权限&#xff0c;可以使用 具体格式为 其中*代表任意均可 &#xff0c;这里用户创建采用与授权同时进…

【OJ】求和与计算日期

文章目录 1. 前言2. JZ64 求123...n2.1 题目分析2.2 代码 3. HJ73 计算日期到天数转换3.1 题目分析3.2 代码 4. KY222 打印日期4.1 题目分析4.2 代码 1. 前言 下面两个题目均来自牛客&#xff0c;使用的编程语言是c&#xff0c;分享个人的一些思路和代码。 2. JZ64 求123…n …

北京大学发布,将试错引入大模型代理学习!

引言&#xff1a;探索语言智能的新边界 在人工智能的发展历程中&#xff0c;语言智能始终是一个核心的研究领域。随着大语言模型&#xff08;LLM&#xff09;的兴起&#xff0c;我们对语言智能的理解和应用已经迈入了一个新的阶段。这些模型不仅能够理解和生成自然语言&#x…

20240304-1-操作系统

操作系统 知识体系 Questions 1.进程和线程的区别 进程是系统进行资源分配和调度的基本单位&#xff1b;线程是CPU调度和分派的基本单位。 每个进程都有独立的代码和数据空间&#xff08;程序上下文&#xff09;&#xff0c;程序之间的切换会有较大的开销&#xff1b;线程可…

Java基础 - 7 - 常用API(三)

API&#xff08;全称 Application Programming Interface&#xff1a;应用程序编程接口&#xff09; API就是Java帮我们已经写好的一些程序&#xff0c;如类、方法等&#xff0c;可以直接拿过来用 JDK8 API文档&#xff1a;Java Platform SE 8 一. JDK8之前传统的日期、时间 …

基于springboot+vue的流浪宠物管理系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

js面试 forEach ,map,for ,for in , for of

forEach ,map&#xff0c;for ,for in , for of 1 forEach 回调3个参数value&#xff0c;index&#xff0c;arr&#xff08;原数组&#xff09; 2 map 1&#xff1a;map() 不会改变原始数组 2&#xff1a;函数的作用是对数组中的每一个元素进行处理&#xff0c;返回新的元素…

如何使用生成式人工智能探索视频博客的魅力?

视频博客&#xff0c;尤其是关于旅游的视频博客&#xff0c;为观众提供了一种全新的探索世界的方式。通过图像和声音的结合&#xff0c;观众可以身临其境地体验到旅行的乐趣和发现的喜悦。而对于内容创作者来说&#xff0c;旅游视频博客不仅能分享他们的旅行故事&#xff0c;还…

YOLOv8姿态估计实战:训练自己的数据集

课程链接&#xff1a;https://edu.csdn.net/course/detail/39355 YOLOv8 基于先前 YOLO 版本的成功&#xff0c;引入了新功能和改进&#xff0c;进一步提升性能和灵活性。YOLOv8 同时支持目标检测和姿态估计任务。 本课程以熊猫姿态估计为例&#xff0c;将手把手地教大家使用C…

大模型推荐落地啦!融合知识图谱,蚂蚁集团发布!

引言&#xff1a;电商推荐系统的新突破 随着电子商务平台的蓬勃发展&#xff0c;推荐系统已成为帮助用户在信息过载时代中筛选和发现产品的关键工具。然而&#xff0c;传统的推荐系统主要依赖历史数据和用户反馈&#xff0c;这限制了它们在新商品推出和用户意图转变时的有效性…
最新文章