Java BIO (同步阻塞型IO) 内容上集

IO简介

一、前言

在java软件设计开发中,通信框架是不可避免的,我们在不同的系统或者这不同的进程之间进行数据交互,或者在高并发的场景下需要用到网络通信相关的技术,从上节课的例子当中我们看出同步阻塞式的IO通信(BIO)效率过于低下。于是Java在2002年开始支持同步非阻塞式的IO通信技术(NIO),那就让我们系统的学习一下java的IO吧。

二、javaIO发展之路

1.IO基本模型

IO模型:就是用什么样的通道或者说是通讯模式和架构进行数据的传输和接收,很大程度上决定了程序通讯的性能,java共支持3种网络的IO模型:BIO NIO AIO
实际通讯需求下,根据不同的业务场景和性能决定选择不同的I/O模型

2.I/O模型

Java BIO (同步阻塞型IO)

BIO属于同步阻塞型IO,在服务器端的实现模式为,一个连接对应一个线程。当客户端有连接请求的时候服务端需要启动一个新的线程与之进行对应处理。
这个模式的缺点很明显,当我们的连接请求发送到服务器端的时候服务器就会新启动一个线程来进行处理,当新建的线程不做任何处理只是挂着(阻塞)的时候,就会给服务器造成不必要的线程开销。

举个栗子:我们用BIO开发了一个即时聊天系统,每一个客户端给我们的服务器端发送消息之前都要和我们的服务器端进行连接。但是发送完消息之后我们的客户端却不下线(不主动关闭),服务器端的线程就要一直阻塞的等待客户端给他发消息,(将线程挂起来)。这就会给服务器造成不必要的线程开销。这还不是最可怕的,当我们的客户端越来越多的情况下,每一个客户端的接入服务器都会建立一个新的线程与之对应。当客户端的连接数增多,产生高并发的时候,整个BIO网络就会占用大量的jvm线程,造成服务器性能降低,最后可能导致服务器宕机。

在这里插入图片描述

Java NIO (同步非阻塞型IO)

服务器的实现模式为一个线程,处理多个请求(连接),既客户端发送的请求连接,客户端发送的请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理。
在这里插入图片描述

这样做显然有一个缺点,就比如有一个通道上有大量的数据,那么这个唯一的线程就需要用大量的时间去处理这个通道上的数据,从而降低通道通道的利用率,造成系统资源的浪费

BIO、NIO适用场景分析

1.BIO 方式适用于连接数目较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,程序简单易理解。
2.NIO 方式适用于链接数目多且连接比较短(轻操作)的框架,比如聊天系统,单面系统,服务器间通讯等。编程比较复杂,jdk1.4开始支持。

什么是socket

一、计算机网络七层协议,以及每层的作用

在这里插入图片描述

二、什么是Socket

从上图当中我们可以看出应用层、表示层、会话层都属于我们的应程序,而后边四层都属于操作系统。
那么由我们程序员开发的的应用程序应该如何调用操作系统当中的后四层协议呢?
socket 其实就是操作系统提供给程序员操作「网络协议栈」的接口,说人话就是,你能通过socket 的接口,来控制协议找工作,从而实现网络通信,达到跨主机通信。
在这里插入图片描述
从上图可以看出Socket是网络上运行的程序之间双向通信链路的终结点,是TCP和UDP的基础,。使用java的net包当中的Socket API可以实现基于TCP或UDP的socket服务器端和客户端。

三、Socket TCP实现服务端和客户端的通信

服务器端代码

public class Server {
    public static void main(String[] args) throws Exception {
        ServerSocket server = new ServerSocket(4700);
        System.out.println("Server started...");

        // while(true) 持续接收请求
        while(true) {
            Socket socket = server.accept();
            System.out.println("有客户端连接了..." + socket.getRemoteSocketAddress());
            // 给每个连接都启动一个线程,实现并发处理
            new Thread(() -> {
                try {
                    handler(socket);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

    private static void handler(Socket socket) throws Exception {
        System.out.println("-->" + Thread.currentThread().getName());

        // 读
        InputStream inputStream = socket.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        String str = reader.readLine();
        System.out.println(socket.getRemoteSocketAddress() + ":" + str);

        TimeUnit.SECONDS.sleep(10); // 通过sleep来验证可以通过处理多个连接

        // 写
        OutputStream outputStream = socket.getOutputStream();
        PrintWriter writer = new PrintWriter(outputStream);
        writer.println("ACK");
        writer.flush();
        System.out.println("已向" + socket.getRemoteSocketAddress() + "回发确认消息ACK");
    }
}

客户端代码

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("101.43.152.120", 4700);
        System.out.println("Connect success...");


        Scanner scanner = new Scanner(System.in);
        String input="";
        input=scanner.next();

        // 写
        OutputStream outputStream = socket.getOutputStream();
        PrintWriter writer = new PrintWriter(outputStream);
        writer.println(input);
        writer.flush();
        System.out.println("向服务端发送数据结束");

        // 读
        InputStream inputStream = socket.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        String str = reader.readLine();
        System.out.println("Server:" + str);

        socket.close();
    }
}

在本地启动,首先启动服务器端代码,其次启动客户端代码
在这里插入图片描述
在这里插入图片描述
以上在服务器端中可以直接使用Socket实例化一个socket对象实现tcp协议绑定端口并启动,然后通过阻塞方法accept接收并读取客户端发来的信息,而客户端则通过带套接字信息的Socket类连接服务器端,双方建立连接以后即可通过流进行双向通信。

四、Socket UDP实现服务端和客户端的通信

服务器端代码

public class UDPSocketServer {
    public static void main(String[] args) {
        recive();
    }

    public static void recive(){
        System.out.println("服务器端启动");
        try{
            //创建接收方的套接字对象
            DatagramSocket socket = new DatagramSocket(4701);
            //接收数据的buf数组并指定大小
            byte[] buf = new byte[1024];
            //创建接收数据包,并存储在buf中
            DatagramPacket packet = new DatagramPacket(buf,buf.length);
            //接收操作,代码会停顿在这里,直到接收到数据包
            socket.receive(packet);
            //接收的数据
            byte[] data = packet.getData();
            //接收的地址
            InetAddress address = packet.getAddress();
            System.out.println("接收的文本=====》"+new String(data));
            System.out.println("接收的ip地址====》"+ address.toString());
            System.out.println("接收的端口=====》" + packet.getPort());
            //告诉发送者数据接收完毕
            String temp = "数据接收完毕";
            byte buffer[] = temp.getBytes();
            //创建数据报,指定发送者的地址socketAddress地址
            DatagramPacket packet2 = new DatagramPacket(buffer,buffer.length,packet.getSocketAddress());
            //发送
            socket.send(packet2);
            //关闭
            socket.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

}

服务器端

public class UDPSocketClient {
    public static void main(String[] args) {
        send();
    }
    public static void send(){
        System.out.println("发送端接收数据");
        //发送端
        try {
            //创建发送段的socket对象
            DatagramSocket socket = new DatagramSocket(4700);
            //发送的内容
            String text = "hello from sender";
            byte[] buf = text.getBytes(StandardCharsets.UTF_8);
            //创建数据包,将长度为length的数据包发送到指定的端口号
            DatagramPacket packet = new DatagramPacket(buf,buf.length, InetAddress.getByName("localhost"),4701);
            //发送数据
            socket.send(packet);
            //接收者返回的数据
            displayReciveInfo(socket);
            //关闭此数据报的套接字
            socket.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void displayReciveInfo(DatagramSocket socket) throws Exception {
        byte[] buffer = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
        socket.receive(packet);

        byte[] data = packet.getData(); //接收的数据
        InetAddress address = packet.getAddress();//接收的地址
        System.out.println("接收的文本=====》" + new String(data));
        System.out.println("接收的ip地址=====》" + address.toString());
        System.out.println("接收的端口====》" + packet.getPort());
    }
}

服务端使用了DatagramSocket类创建接收端套接字,客户端使用DatagramSocket创建发送端套接字。因为UDP不是面向连接的,所以发送端创建套接字后就可以发送数据包,数据包通过DatagramPacket进行封装,在调用DatagramSocket的send方法来发送数据包

五、将客户端代码配置到服务器上进行通信

1.将servlet.java文件放入到服务器当中

在这里插入图片描述

2.将服务器的4700端口打开

在这里插入图片描述

3.更换客户端访问服务器端的ip和端口

在这里插入图片描述

4.启动客户端,进行通信

在这里插入图片描述
在这里插入图片描述

六、发现的问题

当客户端连接到服务器端以后,服务器端就会创建一个新的线程来处理客户端的请求,但是客户端迟迟不发生请求,这也就导致服务器端处理请求的线程被挂起,如果存在大量的这样的客户端,那么服务器端迟早会崩溃。
在这里插入图片描述
在这里插入图片描述

java BIO

一、BIO的工作原理

传统Io(BIO)的本质就是面向字节流来进行数据传输的
在这里插入图片描述

①:当两个进程之间进行相互通信,我们需要建立一个用于传输数据的管道(输入流、输出流),原来我们传输数据面对的直接就是管道里面一个个字节数据的流动(我们弄了一个 byte 数组,来回进行数据传递),所以说原来的 IO 它面对的就是管道里面的一个数据流动,所以我们说原来的 IO 是面向流的
②:我们说传统的 IO 还有一个特点就是,它是单向的。解释一下就是:如果说我们想把目标地点的数据读取到程序中来,我们需要建立一个管道,这个管道我们称为输入流。相应的,如果如果我们程序中有数据想要写到目标地点去,我们也得再建立一个管道,这个管道我们称为输出流。所以我们说传统的 IO 流是单向的

二、传统的BIO编程实例回顾

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

服务器端代码

public class Server {
    public static void main(String[] args) {
        try {
            System.out.append("服务器端启动。。。");
            //1.定义ServerSocket对象进行服务端的端口注册
            ServerSocket serverSocket = new ServerSocket(8080);
            //2.监听客户端的Socket连接程序
            Socket socket = serverSocket.accept();
            //3.从socket对象当中获取到一个字节输入流对象
            InputStream iStream = socket.getInputStream();
            //打印输出
            int len = 0;
            int ReviceLen = 0;
            //计算机网络数据是以8bit为一个单元进行发送,我们接收到发送方发送的byte数据
            //将其转化为utf-8的格式进行输出
            byte[] recvBuf = new byte[1024];
            while ((ReviceLen = iStream.read(recvBuf)) != -1) {
                System.out.println("  客户端说:"
                        + new String(recvBuf, 0, ReviceLen, "UTF-8"));
            }
        } catch (Exception e) {

        }
    }
}

客户端代码

public class Client {
    public static void main(String[] args) throws Exception {
        //1.创建socket对象请求服务器的连接
        Socket socket = new Socket("127.0.0.1",8080);
        //2.从socket对象中获取一个字节输出流、
        OutputStream oStream = socket.getOutputStream();
        oStream.write(("你好服务器").getBytes());//以字节流的形式发送数据
        //4.关闭
        oStream.flush();
    }
}

三、BIO模式下的多发和多收消息

服务器端不变,客户端:

public class Client {
    public static void main(String[] args) throws Exception {
        //1.创建socket对象请求服务器的连接
        Socket socket = new Socket("127.0.0.1",8080);
        //2.从socket对象中获取一个字节输出流、
        OutputStream oStream = socket.getOutputStream();
        Scanner scanner = new Scanner(System.in);
        while (true){
            System.out.println("请说....");
            String message = scanner.nextLine();
            oStream.write(message.getBytes());
            //4.关闭
            oStream.flush();
        }
    }
}

在这里插入图片描述
在这里插入图片描述

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

在上述的案例当中,一个服务端只能接收一个客户端的通信请求,那么如果服务端需要处理很多个客户端的消息通讯请求应该如何处理呢?

当我们启动两个客户端,分别去访问服务器端的时候,我们发现服务器端只连接了一个客户端,并且只能和一个客户端进行通信。
在这里插入图片描述
在这里插入图片描述
什么原因导致了我们服务器只能链接一个客户端
在这里插入图片描述
那如何解决呢?
此时就需要在服务端引入线程了,也就是说客户端发起一次请求,服务端就会创建一个新的线程来处理一个新的线程来处理这个客户端的请求,这样就实现了一个客户端一个线程的模型,图解如下:
在这里插入图片描述
服务器端改进代码

public class Server {
    public static void main(String[] args) {
        try {
            System.out.append("服务器端启动。。。");
            //1.定义ServerSocket对象进行服务端的端口注册
            ServerSocket serverSocket = new ServerSocket(8080);
            while (true){
                //2.监听客户端的Socket连接程序
                Socket socket = serverSocket.accept();
                //创建一个独立的线程来处理也客户端的Socket请求
                new ServerThreadReader(socket).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class ServerThreadReader extends Thread {
    private Socket socket;

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

    @Override
    public void run() {
        //3.从socket对象当中获取到一个字节输入流对象
        try {
            InputStream iStream = socket.getInputStream();
            //打印输出
            int len = 0;
            int ReviceLen = 0;
            //计算机网络数据是以8bit为一个单元进行发送
            byte[] recvBuf = new byte[1024];
            while ((ReviceLen = iStream.read(recvBuf)) != -1) {
                System.out.println("  客户端说:"
                        + new String(recvBuf, 0, ReviceLen, "UTF-8"));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
小结

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

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

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

相关文章

【计算机网络】基本概念

基本概念 IP 地址端口号协议协议分层封装分用客户端服务器请求和响应两台主机之间的网络通信流程 IP 地址 概念:IP 地址主要是用于唯一标识网络主机、其他网络设备(如路由器)的网络地址。简单来说,IP地址用来唯一定位主机。格式&…

cartographer学习与使用

记录一下在配置和使用cartographer建图时遇到的各种问题吧。 我的数据 配置文件&#xff1a; my_rslidar.launch <launch> <param name"/use_sim_time" value"false" /> <!--启动建图节点--> <node name"cartographer_n…

【JACS】:用于稳定单原子分散的催化剂架构可对吸附到 Pt 原子、氧化 Pt 簇和 TiO2上金属 Pt 簇的 CO 进行特定位点光谱和反应性测量

摘要&#xff1a;氧化物负载的贵金属纳米粒子是广泛使用的工业催化剂。由于费用和稀有性&#xff0c;开发降低贵金属纳米颗粒尺寸并稳定分散物质的合成方案至关重要。负载型原子分散的单贵金属原子代表了最有效的金属利用几何结构&#xff0c;尽管由于合成均匀且稳定的单原子分…

关于MySQL数据库的学习3

目录 前言: 1.DQL&#xff08;数据查询语言): 1..1基本查询&#xff1a; 1.2条件查询&#xff1a; 1.3排序查询&#xff1a; 1.3.1使用ORDER BY子句对查询结果进行排序。 1.3.2可以按一个或多个列进行排序&#xff0c;并指定排序方向&#xff08;升序ASC或降序DESC&#…

(十八)【Jmeter】取样器(Sampler)之BeanShell 取样器

简述 操作路径如下: 作用:通过Beanshell脚本来编写自定义请求。配置:编写Beanshell脚本代码,实现请求逻辑。使用场景:在JMeter中利用Beanshell脚本语言的特性进行自定义请求。优点:可以利用Beanshell脚本语言的丰富功能。缺点:脚本语言的性能可能不如其他编译语言,且…

Day67:WEB攻防-Java安全JNDIRMILDAP五大不安全组件RCE执行不出网

知识点&#xff1a; 1、Java安全-RCE执行-5大类函数调用 2、Java安全-JNDI注入-RMI&LDAP&高版本 3、Java安全-不安全组件-Shiro&FastJson&JackJson&XStream&Log4j Java安全-RCE执行-5大类函数调用 Java中代码执行的类&#xff1a; GroovyRuntimeExecPr…

<el-tab>样式自定义——一个可以触类旁通的小例子

首先在网页的检查确定想要自定义的部分叫什么 例如&#xff1a; 我想修改的组件是el-tabs__header.is-top 的margin-bottom 则在相应vue文件的<style>里面增加这一属性 其中&#xff0c;::v-deep可以帮助覆盖子组件内部元素的样式。 ::v-deep .el-tabs__header.is-to…

Ubuntu18.04 中编译 TI 官方的ros驱动包中 autonomous_robotics_ros 包所存在的问题及解决方案

环境&#xff1a; 安装有 ROS 系统的 Ubuntu18.04 环境&#xff0c;并且已将 TI 官方的毫米波雷达 ROS 驱动下载到Ubuntu18.04系统中&#xff0c;如需获取此代码请点击此链接根据教程下载即可。 代码下载链接&#xff1a;TI IWR6843ISK ROS驱动程序搭建-CSDN博客 问题1&…

计算机设计大赛 题目:基于深度学习卷积神经网络的花卉识别 - 深度学习 机器视觉

文章目录 0 前言1 项目背景2 花卉识别的基本原理3 算法实现3.1 预处理3.2 特征提取和选择3.3 分类器设计和决策3.4 卷积神经网络基本原理 4 算法实现4.1 花卉图像数据4.2 模块组成 5 项目执行结果6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基…

基于Spark的气象数据处理与分析

文章目录 一、实验环境二、实验数据介绍三、数据获取1.观察数据获取方式2.数据爬取3.数据存储4.数据读取5.数据结构6.爬虫过程截图 四、数据分析1.计算各个城市过去24小时累积雨量2.计算各个城市当日平均气温3.计算各个城市当日平均湿度4.计算各个城市当日平均风速 五、数据可视…

WebRTC:真正了解 RTP 和 RTCP

介绍 近年来&#xff0c;通过互联网进行实时通信变得越来越流行&#xff0c;而 WebRTC 已成为通过网络实现实时通信的领先技术之一。WebRTC 使用多种协议&#xff0c;包括实时传输协议 (RTP) 和实时控制协议 (RTCP)。 RTP负责通过网络传输音频和视频数据&#xff0c;而RTCP负责…

Uibot (RPA设计软件)RPA基础培训-财务会计Web应用自动化(批量开票机器人)

Uibot (RPA设计软件&#xff09;Mage AI智能识别&#xff08;发票识别&#xff09;———机器人的小项目友友们可以参考小北的课前材料五博客~ (本博客中会有部分课程ppt截屏,如有侵权请及请及时与小北我取得联系~&#xff09; 紧接着小北的前两篇博客&#xff0c;友友们我们…

【全面了解自然语言处理三大特征提取器】RNN(LSTM)、transformer(注意力机制)、CNN

目录 一 、RNN1.RNN单个cell的结构2.RNN工作原理3.RNN优缺点 二、LSTM1.LSTM单个cell的结构2. LSTM工作原理 三、transformer1 Encoder&#xff08;1&#xff09;position encoding&#xff08;2&#xff09;multi-head-attention&#xff08;3&#xff09;add&norm 残差链…

PyCharm实现一个简单的注册登录Django项目

之前已经实现了一个简单的Django项目&#xff0c;今天我们j基于之前的项目来实现注册、登录以及登录成功之后跳转到StuList页面。 1、连接数据库 1.1 配置数据库信息&#xff1a; 首先在myweb的settings.py 文件中设置MySQL数据库连接信息&#xff1a; DATABASES {default…

在线疫苗预约小程序|基于微信小程序的在线疫苗预约小程序设计与实现(源码+数据库+文档)

在线疫苗预约小程序目录 目录 基于微信小程序的在线疫苗预约小程序设计与实现 一、前言 二、系统设计 三、系统功能设计 1、疫苗管理 2、疫苗订单管理 3、论坛管理 4、公告管理 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源…

html5cssjs代码 022 表单输入类型示例

html5&css&js代码 022 表单输入类型示例 一、代码二、解释 这段HTML代码定义了一个网页&#xff0c;展示了表单输入类型示例。 一、代码 <!DOCTYPE html> <html lang"zh-cn"> <head><title>编程笔记 html5&css&js 表单输入…

SpringBoot整合JPA

一 运行效果如下 二 项目结构图 三 代码 pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance&qu…

2024 年(第 12 届)“泰迪杯”数据挖掘挑战赛——A 题:生产线的故障自动识别与人员配置具体思路以及源代码分析

一、问题背景 随着新兴信息技术的大规模应用&#xff0c;工业生产线的智能化控制技术日益成熟。自动生产线 可以自动完成物品传送、物料填装、产品包装和质量检测等过程&#xff0c;极大地提高了生产效率和 产品质量&#xff0c;减少了生产成本。自动生产线融入故障智能报警…

【Spring Boot 源码学习】深入应用上下文初始化器实现

《Spring Boot 源码学习系列》 深入应用上下文初始化器实现 一、引言二、往期内容三、主要内容3.1 spring-boot 子模块中内置的实现类3.1.1 ConfigurationWarningsApplicationContextInitializer3.1.2 ContextIdApplicationContextInitializer3.1.3 DelegatingApplicationConte…

FFmpeg-aac、h264封装flv及时间转换

文章目录 时间概念流程api核心代码 时间概念 dts: 解码时间戳, 表示压缩帧的解码时间 pts: 显示时间戳, 表示将压缩帧解码后得到的原始帧的显示时间 时间基: time_base &#xff0c; 通常以ms为单位 时间戳: timestamp , 多少个时间基 真实时间&#xff1a;time_base * timest…