网络编程基础-IO模型深入理解

news2024/11/29 20:49:48

一、IO的基本概念

什么是IO?

I/O就是计算机内存与外部设备之间拷贝数据的过程

什么是网络IO?

网络IO是指在计算机网络环境中进行的输入和输出操作,涉及数据在网络设备之间的传输。

网络IO操作可以是发送请求、接收响应、下载文件、传输数据等。在Java中,网络IO通过java.net包提供的类(如SocketServerSocket等)来实现网络通信。

二、Java的IO模型

在I/O操作中有这么两组概念,同步/异步、阻塞/非阻塞

同步/异步:

        什么意思?指的是数据就绪后,我收不到通知,需要自己每隔一段时间就询问尝试读一下,靠自己专门去读取数据,叫做同步;而数据就绪准备可读了之后,由某些程序回调(理解为通知)给我们的接收程序,这称为异步。

阻塞/非阻塞:

        阻塞:现在有一个读取数据的操作,在没有数据传过来时,读操作会一直阻塞等待,直到有数据过来;其次,缓冲区满时,对于写操作也会一直阻塞等待直到缓冲区有空间可以写入为止。

        非阻塞:非阻塞可以理解为无需等待,都是直接返回。比如读操作中,没有数据可读时,那我就不读了,直接返回,程序结束,不会阻塞等待。写操作在缓冲区已满时,我就不写了,直接程序结束。

常见的IO模型

一种很重要的IO模型叫做IO多路复用,在多路复用IO模型中,操作系统提供了一种机制(如select、poll或epoll)来监控多个IO流的状态,只有当其中某个IO操作可以执行时,程序才会进行实际的读写操作。在网络IO中呢,就是监控多个socket连接嘛,同时去定时检查多个socket,哪个要读要写了,就通知哪个线程进行IO操作

三、Java的网络IO模型

BIO

BIO是blocking I/O的简称,它是同步阻塞型IO,其相关的类和接口在java.io下

BIO模型简单来讲,就是服务端为每一个请求都分配一个线程进行处理,I/O操作都是基于流Stream的操作

 示例代码,一定要仔细阅读!

import java.io.*;
import java.net.*;

public class BIOServer {
    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(8080)) {
            System.out.println("服务器启动,等待客户端连接...");
            while (true) {
                // 1. 接受客户端连接(阻塞式)
                Socket socket = serverSocket.accept();  // 阻塞,直到客户端连接
                System.out.println("客户端连接成功");

                // 2. 为每个连接创建一个新线程处理
                new Thread(new ClientHandler(socket)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class ClientHandler implements Runnable {
    private Socket socket;

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

    @Override
    public void run() {
        try {
            // 2. 获取输入流并读取客户端消息
            InputStream inputStream = socket.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            String clientMessage = reader.readLine();
            System.out.println("收到客户端消息: " + clientMessage);

            // 3. 获取输出流并向客户端发送响应
            OutputStream outputStream = socket.getOutputStream();
            PrintWriter writer = new PrintWriter(outputStream, true);
            writer.println("你好,客户端!服务器已收到你的消息。");

            // 4. 关闭客户端连接
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

其中主要有两个缺点:

线程开销:客户端的并发数与后端的线程数成1:1的比例,线程的创建、销毁是非常消耗系统资源的,随着并发量增大,服务端性能将显著下降,甚至会发生线程堆栈溢出等错误

线程阻塞:当连接创建后,如果该线程没有操作时,会进行阻塞操作,这样极大的浪费了服务器资源(这就是同步阻塞的缺点!

NIO

java当中的NIO就是我们的同步非阻塞模型,代码非常简单,只有一个配置项,这样就能实现非阻塞。

我们主要来讲解Java NIO当中实现的IO多路复用这种同步非阻塞IO模型

其核心就是在轮询检测中,引入Select替我们去同时检测多个连接(否则就要每一个连接上的read单独去做轮询),哪一个连接有IO事件发生,就通知哪一个进行数据读写。

接下来详细介绍NIO

NIO的三大核心组件:Buffer(缓冲区)、Channel(通道)、Selector(选择器/多路复用器)

Buffer(缓冲区):

        Buffer是一个对象,包含一些要写入或者读出的数据,体现了与原I/O的一个重要区别,在面向流的I/O中,数据读写是直接进入到Stream中,而在NIO中,所有数据都是用缓冲区处理的,读数据直接从缓冲区读,写数据直接写入到缓冲区。
        缓冲区的本质是一个数组,通常是一个字节数组(ByteBuffer),也可以使用其他类型,但缓冲区又不仅仅是一个数组,它还提供了对数据结构化访问以及维护读写位置等操作。

        所以Buffer就是NIO当中的数据容器,网络IO中读写的数据都是存放在Buffer中,写数据是写入到Buffer,读数据是从Buffer中读。

Channel(通道):

        Channel 是一个通道,管道,网络数据通过Channel读取和写入,Channel和流Stream的不同之处在于Channel是双向的,流只在一个方向上移动(InputStream/OutputStream),而Channel可以用于读写同时进行,即Channel是全双工的。

        所以有了Channel之后,我们就不从Stream中读写数据,而是通过Channel从Buffer容器里读数据或写数据。

        接下来要简单讲一个SocketChannel和ServerSocketChannel,可以把他们都理解为连接对象

Selector(选择器/多路复用器):

        Selector会不断轮询注册在其上的Channel,如果某个Channel上面发生读或者写事件,即该Channel处于就绪状态,它就会被Selector轮询出来,然后通过selectedKeys可以获取就绪Channel的集合,进行后续的I/O操作。

代码示例,一定要看!

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 NIOServerWithThread {
    public static void main(String[] args) {
        try {
            // 1. 创建 ServerSocketChannel,绑定端口
            ServerSocketChannel serverChannel = ServerSocketChannel.open();
            serverChannel.bind(new InetSocketAddress(8080));
            serverChannel.configureBlocking(false); // 设置为非阻塞模式

            // 2. 创建 Selector 并注册 ServerSocketChannel
            Selector selector = Selector.open();
            serverChannel.register(selector, SelectionKey.OP_ACCEPT); // 监听连接请求

            System.out.println("服务器启动,等待客户端连接...");

            // 3. 事件循环,监听 Selector
            while (true) {
                // 3.1 阻塞等待事件,返回事件数量
                int readyChannels = selector.select();
                if (readyChannels == 0) continue;

                // 3.2 获取可用的通道(即有事件发生的通道)
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

                // 3.3 处理每个事件
                while (keyIterator.hasNext()) {
                    SelectionKey key = keyIterator.next();

                    if (key.isAcceptable()) {
                        // 处理新的客户端连接
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
                        SocketChannel clientChannel = server.accept();
                        clientChannel.configureBlocking(false);
                        System.out.println("客户端连接成功: " + clientChannel.getRemoteAddress());

                        // 注册客户端通道,监听可读事件
                        clientChannel.register(selector, SelectionKey.OP_READ);

                    } else if (key.isReadable()) {
                        // 创建一个新的线程来处理客户端发送的数据
                        new Thread(() -> handleClient(key)).start();
                    }

                    // 3.4 处理完当前事件后,需要将它从 selectedKeys 集合中移除
                    keyIterator.remove();
                }
            }

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

    // 处理客户端的逻辑
    private static void handleClient(SelectionKey key) {
        try {
            // 处理客户端发送的数据
            SocketChannel clientChannel = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int bytesRead = clientChannel.read(buffer);

            if (bytesRead > 0) {
                buffer.flip();
                String clientMessage = new String(buffer.array(), 0, bytesRead).trim();
                System.out.println("收到客户端消息: " + clientMessage);

                // 响应客户端
                buffer.clear();
                buffer.put("你好,客户端!服务器已收到你的消息。".getBytes());
                buffer.flip();
                clientChannel.write(buffer);
            } else if (bytesRead == -1) {
                // 客户端关闭连接
                System.out.println("客户端断开连接: " + clientChannel.getRemoteAddress());
                clientChannel.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

非阻塞性的体现:

1、在设置 ServerSocketChannel 和 SocketChannel 时,我们调用了 configureBlocking(false),这意味着在进行 I/O 操作时(如连接、读取和写入),这些操作不会阻塞线程。如果没有数据可读或连接不可用,线程不会被挂起,而是继续执行后面的代码。

2、Selector 是基于事件驱动的,主线程调用 selector.select() 方法后,会阻塞等待有事件发生(例如客户端连接或可读事件)。如果没有事件发生,主线程会继续等待,这个过程是非阻塞的,因为它不会强制执行任何 I/O 操作。

3、在上面的代码演示中,主线程在 while (true) 循环中不断调用 selector.select(),在没有事件发生时不会被阻塞(即使 selector.select() 方法是阻塞的,但它并不会影响处理其他已注册的事件的能力)。

AIO

aio就是异步模型,由于在一些操作系统上的支持不同,用的也不多,技术还不够成熟,因此不做介绍。

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

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

相关文章

adb devices没找到安卓设备的解决办法

要想让设备让adb识别到&#xff0c;要开启设备的开发者模式&#xff0c;并且开启USB调试功能&#xff1a; 然后重新运行&#xff1a;就找到了

Linux零基础教程学习(黑马)

1.初识Linux 1.2远程连接Linux系统 图形化、命令行 对于操作系统的使用&#xff0c;有2种使用形式&#xff1a; 图形化页面使用操作系统 以命令的形式使用操作系统 不论是Windows还是Linux亦或是MacOS系统&#xff0c;都是支持这两种使用形式。 图形化&#xff1a;使用操作…

前端页面使用google地图api实现导航功能,开发国外网站免费简单好用

开发国外软件的时候&#xff0c;想使用goole map实现导航等功能&#xff0c;可以使用google的api来做&#xff0c;官方文档地址&#xff1a;https://developers.google.com/maps/documentation/urls/get-started?hlzh-cn &#xff0c;比如&#xff1a; 支持的请求的操作&…

基于MATLAB/octave的容积卡尔曼滤波(CKF)【带逐行注释】

介绍 CKF的三维滤波程序例程 产品概述 我们的 MATLAB 数据处理工具是专为科研人员、工程师和数据分析师设计的高效解决方案。该工具提供了一系列强大的功能&#xff0c;能够快速处理和分析大规模数据集&#xff0c;适用于各种科学和工程应用&#xff0c;包括信号处理、图像分…

Redis Search系列 - 第四讲 支持中文

目录 一、支持中文二、自定义中文词典2.1 Redis Search设置FRISOINI参数2.2 friso.ini文件相关配置1&#xff09;自定义friso UTF-8字典2&#xff09;修改friso.ini配置文件 三、实测中文分词效果 一、支持中文 Redis Stack 从版本 0.99.0 开始支持中文文档的添加和分词。中文…

什么是大数据分析:定义、优缺点、应用、机遇和风险

大数据分析的概念已经成为我们社会不可或缺的一部分。众多公司和机构已经开发了大数据应用程序&#xff0c;取得了不同程度的成功。社交媒体平台和传感器等技术正在以前所未有的速度生成数据&#xff0c;就像一条装配线。如今&#xff0c;几乎所有东西都是物联网的一部分&#…

C#教程笔记

C#开发的程序依附.NET平台 编译器->IL中间语言->CLR->机器指令 .NET CORE平台 跨平台 .cs后缀名 快捷键 CtrlKD格式化CtrlL或CtrlX删除一行CtrlY反撤销cwTab快速生成命令行输出Ctrl空格或CtrlJ获取提示///方法注释CtrlMO代码全部折叠CtrlML代码全部展开 上升沿0变1 安…

硬件产品经理的开店冒险之旅(下篇)

缘起&#xff1a;自己为何想要去寻找职业第二曲线 承接上篇的内容&#xff0c;一名工作13年的普通硬件产品经理将尝试探索第二职业曲线。根本原因不是出于什么高大上的人生追求或者什么职业理想主义&#xff0c;就是限于目前的整体就业形式到了40岁的IT从业人员基本不可能在岗…

Spring Boot技术栈在电影评论网站中的应用

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

【论文阅读】Bi-Mamba+: Bidirectional Mamba for Time Series Forecasting

文章目录 概要阅读背景知识引言创新之处 研究方法概述方法部分的核心模块多尺度打补丁&#xff08;Multi-Scale Patching&#xff09;Mamba&#xff1a;全局模式专家Local Window Transformer&#xff08;LWT&#xff09;&#xff1a;局部变化专家长短期路由器&#xff08;Long…

Aatrox-Bert-VITS2部署指南

一、模型介绍 【AI 剑魔 ①】在线语音合成&#xff08;Bert-Vits2&#xff09;&#xff0c;将输入文字转化成暗裔剑魔亚托克斯音色的音频输出。 作者&#xff1a;Xz 乔希 https://space.bilibili.com/5859321 声音归属&#xff1a;Riot Games《英雄联盟》暗裔剑魔亚托克斯 …

LLM:参数高效微调方法总结

可以分为基于适配器、基于提示的。 一、基于适配器的方法 其核心思想是在预训练模型的各层之间插入轻量级的额外模块——适配器&#xff0c;通过仅微调这些适配器模块以适应特定的下游任务&#xff0c;同时冻结原模型的大部分参数。 1.Lora系列 原始的Lora LoRA 表示通过低…

DFS算法经典题目: Leetcode 51.N皇后

DFS算法经典题目&#xff1a; Leetcode 51.N皇后 题目详情如下 这道题如果使用暴力解法的话&#xff0c;需要对N个皇后放在每个地方都进行枚举并判断是否可行&#xff0c;时间复杂度非常之高&#xff0c;肯定是过不了的&#xff0c;所以需要使用其他解法。 根据题目可以知道每…

RT-Thread之STM32使用定时器实现输入捕获

前言 基于RT-Thread的STM32开发&#xff0c;配置使用定时器实现输入捕获。 比如配置特定通道捕获上升沿&#xff0c;该通道对应的引脚有上升沿信号输入&#xff0c;则触发捕获中断。 一、新建工程 二、工程配置 1、打开CubeMX 进行工程配置 2、时钟使用外部高速晶振 3、配置…

链栈的基本算法

初始化一个链栈 &#xff0c;将元素e&#xff0c;f&#xff0c;g&#xff0c;h&#xff0c;i依次进栈&#xff0c;获取栈顶元素&#xff0c;将栈中元素依次出栈并输出 链栈的表示 既采用链式储存结构实现的栈 具体代码 #include <stdio.h> #include <stdlib.h>…

安装TDengine数据库3.3版本和TDengine数据库可视化管理工具

安装TDengine数据库3.3版本和TDengine数据库可视化管理工具 一、下载安装包二、解压安装包三、部署四、启动服务五、进入数据库六、创建数据库、表和往表中插入数据七、测试 TDengine 性能八、使用数据库九、查询数据十、TDengine数据库可视化界面 一、下载安装包 TDengine-cl…

RHCE——例行性工作

准备工作 [rootlocalhost ~]# cat /etc/yum.repos.d/aliyun.repo [ali-app] nameali-app baseurlhttps://mirrors.aliyun.com/centos-stream/9-stream/AppStream/x86_64/os/ gpgcheck0[ali-base] nameali-base baseurlhttps://mirrors.aliyun.com/centos-stream/9-stream/Base…

Qt - 地图相关 —— 1、加载百度在线地图(附源码)

效果图 开始加载地图 1、百度地图开发者网站中注册,获取密钥 2、进入开发文档中 将下图内容保存到本地文件中,文件名为"index.html"文件即可。接着将内容中的“您的密钥”改为刚刚创建应用出来的AK密钥即可。 然后双击打开若在浏览器中正常看到下图右侧地图则说明没…

现代物流管理:SpringBoot技术突破

3系统分析 3.1可行性分析 通过对本智能物流管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本智能物流管理系统采用SSM框架&#xff0c;JAVA作为开发语…

excel判断某一列(A列)中的数据是否在另一列(B列)中

如B列如果有7个元素&#xff0c;在A列右边的空白列中&#xff0c;输入如下公式&#xff1a; COUNTIF($B$1:$B$7,A1), 其中&#xff0c;$B$1:$B$7代表A列中的所有数据即绝对范围&#xff0c;A1代表B列中的一个单元格.