BIO、NIO、AIO、Netty从简单理解到使用

Java编程中BIO、NIO、AIO是三种不同的I/O(输入/输出)模型,它们代表了不同的I/O处理方式。
Netty就是基于Java的NIO(New Input/Output)类库编写的一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。

先来了解一下基本的三种基本的io模型:

BIO(Blocking I/O,阻塞I/O)

定义:BIO是Java最传统的I/O模型,基于流的同步阻塞I/O操作。每个连接都会占用一个线程,当进行I/O操作时,线程会被阻塞,直到操作完成。
特点
同步阻塞:每个I/O操作都会阻塞当前线程,直到操作完成。
线程占用:每个连接需要一个独立的线程,线程资源消耗较大。
实现简单:代码实现相对简单,易于理解和编写。
作用:适用于连接数较少且固定的架构,如传统的C/S架构。由于其实现简单、编程直观,因此在一些简单的网络编程场景中仍然被使用。

创建客户端和服务端演示数据接收与传输。

//服务端
public static void main(String[] args) {try (ServerSocket serverSocket = new ServerSocket(9999)) {while (true) {Socket socket = serverSocket.accept(); // 持续监听新连接new Thread(() -> { // 为每个客户端创建独立线程try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {String line;while ((line = reader.readLine()) != null) {System.out.println("客户端[" + socket.getPort() + "] 消息: " + line);}} catch (IOException e) {e.printStackTrace();}}).start();}} catch (IOException e) {e.printStackTrace();}}//客户端
public static void main(String[] args) {System.out.println("客户端启动...");try {//创建套接字Socket socket = new Socket("127.0.0.1", 9999);//获取输出流发送消息PrintStream ps = new PrintStream(socket.getOutputStream());Scanner sc = new Scanner(System.in);while (true){System.out.println("请输入信息:");ps.println(sc.nextLine());ps.flush();}} catch (IOException e) {throw new RuntimeException(e);}}

NIO(Non-blocking I/O,非阻塞I/O)

定义:NIO是Java 1.4引入的新I/O API,基于通道(Channel)和缓冲区(Buffer)的非阻塞I/O操作。
特点
非阻塞:I/O操作不会阻塞线程,线程可以在等待数据时执行其他任务。
多路复用:通过Selector可以管理多个Channel,提高了资源利用率。
高性能:适用于高并发场景,减少线程开销和上下文切换。
核心组件
缓冲区(Buffer):用于存储数据的固定大小的内存区域,提供了多种类型的缓冲区,如ByteBuffer、CharBuffer等。
通道(Channel):用于数据读写的通道,支持非阻塞模式,与缓冲区配合使用。
选择器(Selector):允许单个线程同时处理多个通道的I/O事件。
作用:适用于连接数较多且连接较短的架构,如高并发的服务器端应用。NIO提供了更高效、更灵活的I/O操作方式,显著提高了系统的并发性能和吞吐量。

//服务端public static void main(String[] args) throws Exception {//创建连接通道,ServerSocketChannel 绑定端口,监听新的客户端连接请求。它本身不处理任何数据传输。ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();// 设置通道为非阻塞模式serverSocketChannel.configureBlocking(false);//绑定端口serverSocketChannel.bind(new InetSocketAddress(9999));//创建 Selector 并注册 ACCEPT 事件Selector selector = Selector.open();serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("NIO 服务端启动...");//阻塞等待事件发生while (selector.select()>0){//获取到所有事件Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();//遍历所有事件while (iterator.hasNext()){SelectionKey key = iterator.next();//检查是否有新的连接请求if(key.isAcceptable()){//为每一个创建新的通道SocketChannel clientChannel = serverSocketChannel.accept();clientChannel.configureBlocking(false);clientChannel.register(selector,SelectionKey.OP_READ);}else if(key.isReadable()){//处理读数据SocketChannel channel = (SocketChannel)key.channel();ByteBuffer byteBuffer = ByteBuffer.allocate(1024);int bytesRead = 0;while ((bytesRead = channel.read(byteBuffer)) > 0){byteBuffer.flip();String message = new String(byteBuffer.array(),0,bytesRead);System.out.println("接收客户端信息:"+message);byteBuffer.clear();//回写数据给客户端ByteBuffer response = ByteBuffer.wrap(("ECHO: " + message).getBytes());channel.write(response);}}iterator.remove();}}}//客户端
public static void main(String[] args) throws Exception {// 1. 创建 SocketChannel 并连接服务器SocketChannel socketChannel = SocketChannel.open();socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999));socketChannel.configureBlocking(false); // 非阻塞模式System.out.println("NIO 客户端已连接");//接收线程new Thread(() -> {ByteBuffer buffer = ByteBuffer.allocate(1024);while (true) {try {int bytesRead = socketChannel.read(buffer);if (bytesRead > 0) {buffer.flip();byte[] data = new byte[buffer.remaining()];buffer.get(data);System.out.println("收到服务端响应: " + new String(data));buffer.clear();}} catch (IOException e) {System.out.println("连接已断开");break;}}}).start();//发送消息ByteBuffer byteBuffer = ByteBuffer.allocate(1024);Scanner sc = new Scanner(System.in);while (true){System.out.println("请输入信息:");String msg = sc.nextLine();byteBuffer.put(("AA:"+msg).getBytes(StandardCharsets.UTF_8));byteBuffer.flip();socketChannel.write(byteBuffer);byteBuffer.clear();}}

AIO(Asynchronous I/O,异步I/O)

定义:AIO是Java 7引入的异步I/O API,基于异步通道(AsynchronousChannel)和异步回调机制。
特点
异步非阻塞:I/O操作是异步的,通过回调处理结果,不阻塞线程。
回调机制:通过回调函数处理I/O操作结果,提高了程序的灵活性。
高性能:适用于高并发、高吞吐量的场景。
工作方式
程序发起一个异步I/O请求,并提供一个回调函数。
程序无需等待I/O操作完成,而是立即返回,继续执行其他任务。
当I/O操作完成后,系统会调用之前提供的回调函数,传递结果或状态信息。
作用:适用于连接数较多且连接时间较长的架构,如高并发的服务器端应用。AIO提供了真正的异步I/O操作方式,进一步提高了系统的并发性能和吞吐量。

Netty

先了解一下netty出现的原因,是干嘛用的?

  • Netty出现的原因:

Java NIO的复杂性:
Java NIO虽然提供了非阻塞IO的能力,但其API设计较为底层,使用起来比较复杂。开发者需要处理大量的细节,如选择器的管理、缓冲区的操作等,这增加了开发的难度和出错的风险。
性能瓶颈:
在高并发场景下,直接使用Java NIO进行网络编程可能会遇到性能瓶颈。例如,选择器的实现和多线程管理的复杂性可能导致性能下降。
缺乏高级功能:
Java NIO仅提供了基础的非阻塞IO机制,缺乏一些高级功能,如协议编解码、连接管理等。这些功能在开发网络应用时非常重要,但实现起来却相对复杂。

  • Netty的具体用途是什么?

简化网络编程:
Netty封装了Java NIO的复杂性,提供了一套简洁易用的API。开发者可以使用这些API快速构建网络应用,而无需关注底层的细节。
提高性能:
Netty采用了异步非阻塞IO模型,并支持零拷贝等技术,可以在保证高性能的同时,减少CPU和内存资源的消耗。这使得Netty在高并发场景下表现尤为出色。
支持多种协议:
Netty内置了对多种协议的支持,如TCP、UDP、HTTP、WebSocket等。开发者可以轻松地使用这些协议构建网络应用,而无需自己实现协议编解码等复杂功能。
灵活的扩展性:
Netty提供了丰富的扩展点,如ChannelHandler、Codec等。开发者可以通过实现这些接口来扩展Netty的功能,以满足特定的业务需求。
广泛的应用场景:
Netty经过广泛的使用和验证,具有高稳定性和可靠性,适用于各种网络应用场景,如分布式系统、微服务架构中的通信组件、实时通讯系统、游戏服务器等。
它提供了丰富的错误处理和恢复机制,能够有效地处理网络通信中的各种异常情况。
netty核心优势:
异步事件驱动、零拷贝、内存池、高度可定制。

  • 核心组件

先大概了解一下 Reactor 模型(单线程、多线程、主从多线程)。
因为Reactor 模式是 Netty 高性能和高并发能力的核心设计基础。Netty 的线程模型、事件驱动机制和异步非阻塞 I/O 都深度依赖 Reactor 模式。

Reactor 模式是一种 事件驱动的设计模式,用于处理高并发的 I/O 请求。其核心思想是 用一个或多个线程监听事件(如连接、读写请求),并将事件分发给对应的处理器异步处理,避免线程阻塞和资源浪费。

Reactor模型的核心组件
事件源(Event Source):指任何可以产生I/O事件的对象,例如网络连接、文件、设备等。
事件循环(Event Loop):Reactor模型的核心,负责监控所有事件源的状态,并在有事件发生时将其分发给相应的处理程序(即回调函数)。
事件处理器(Event Handler):处理特定事件的代码模块,通常实现为回调函数或方法。针对特定的事件源,每一个事件源通常都有一个相应的处理器,用于处理该事件源的I/O事件。
多路复用器(Demultiplexer):用于监视多个事件源并将发生的事件通知给Reactor。常见的实现包括Java的Selector、Linux的epoll等。

Reactor模型的实现方式
单Reactor单线程模型:所有操作(连接、读写)由一个线程完成。设计简单,但在高并发场景下容易成为性能瓶颈。
单Reactor多线程模型:Reactor线程负责监听和分发事件,而事件的处理则交给线程池中的工作线程完成。这种方式能够充分利用多核CPU的处理能力,但在高并发场景下,Reactor线程可能成为性能瓶颈。
主从Reactor多线程模型(Netty 默认模型-高并发):也称为多Reactor多线程模型。主Reactor线程负责监听和分发连接事件,当有新的连接到来时,将其分发给从Reactor线程处理。从Reactor线程负责监听和分发读写事件,并交给线程池中的工作线程处理。这种方式能够进一步提高系统的并发处理能力和可扩展性。

写个netty服务端示例看一下:

// 1. 创建主 Reactor(处理连接)管理一组 EventLoop,每个线程绑定一个 EventLoop(事件循环线程)
EventLoopGroup bossGroup = new NioEventLoopGroup(1);  // 主线程组(通常 1 个线程)
// 2. 创建子 Reactor(处理 I/O)
EventLoopGroup workerGroup = new NioEventLoopGroup();  // 子线程组(默认 CPU 核数 × 2)//Reactor 模式配置入口	绑定主/子线程组、设置 Channel 类型
ServerBootstrap server = new ServerBootstrap();
server.group(bossGroup, workerGroup)  // 绑定主从线程组.channel(NioServerSocketChannel.class)  // 设置 Channel 类型(NIO)代表一个 Socket 连接或监听端口.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new ServerHandler());  // 添加业务处理器,这里需要自己实现}});// 3. 绑定端口并启动服务
ChannelFuture future = server.bind(8080).sync();
//等待服务器通道关闭
future.channel().closeFuture().sync();
//关闭通道
finally {workerGroup.shutdownGracefully();bossGroup.shutdownGracefully();}//业务处理器
public class ServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {System.out.println("客户端连接成功...");}}
  • 组件说明:

Channel:
在Netty中,Channel是一个核心概念,Channel提供了数据的读取和写入操作,以及与远程端点进行通信的能力。
通过Channel可以获取到很多信息,比如:本地地址、远程地址、Channel的EventLoop、Channel的Pipeline等。
Channel 生命周期:
channelRegistered (初始化操作)→ channelActive (连接)→ channelRead(数据接收) → channelInactive(断开)→ channelUnregistered(注销)
Selector:
Selector是Java NIO(New Input/Output)中的一个核心组件,用于监控多个Channel(通道)的状态,例如连接、读、写等事件。在netty中Selector被封装在NioEventLoop中,当Channel注册到Selector并指定感兴趣的事件类型(如连接、读、写等)后,会返回一个SelectionKey,用于表示Selector与Channel之间的关联关系。Selector会不断地轮询其注册的Channel,如果有事件发生,Selector会将相应的SelectionKey放入就绪事件集合中。事件循环线程会从就绪事件集合中取出SelectionKey,并根据事件类型调用相应的ChannelHandler进行处理。处理完成后,事件循环线程会将Channel放回到Selector中,继续等待下一次事件的发生。

EventLoopGroup:
管理一组 EventLoop,通常分为 bossGroup(处理连接)和 workerGroup(处理 I/O)。
配置线程数:new NioEventLoopGroup(4) 表示 4 个线程。
ServerBootstrap:
用于启动服务器端的引导类,配置服务器参数和处理器链配置、启动服务器、关闭服务器。

  1. 配置服务器参数:

线程模型:ServerBootstrap允许您配置两个主要的线程组:bossGroup和workerGroup。
bossGroup用于处理客户端的连接请求,而workerGroup用于处理已连接的客户端的I/O操作。
通道类型:通过channel方法,您可以指定服务器使用的通道类型。对于NIO传输,通常使用NioServerSocketChannel。
通道选项:使用option方法可以设置服务器通道的选项,如SO_BACKLOG(TCP连接请求的最大队列长度)和SO_REUSEADDR(允许地址重用)。
子通道选项:childOption方法用于设置已连接客户端的通道选项,如SO_KEEPALIVE(保持连接活动状态)。
Option:可以应用于Channel或Bootstrap上。
ChannelOption:专门用于配置Channel的参数。

  1. 处理器链配置:

父处理器:通过handler方法,您可以设置处理服务器通道I/O事件的处理器。这通常用于处理连接请求。
子处理器:childHandler方法用于设置处理已连接客户端I/O事件的处理器。这是开发者编写业务逻辑处理代码的地方,通常通过添加一系列的ChannelHandler来实现。

ChannelHandler
ChannelHandler是ChannelPipeline中的基本处理单元,负责处理或拦截入站和出站事件。每个ChannelHandler都有一个关联的ChannelHandlerContext,通过它可以方便地与其他组件进行交互。ChannelPipeline则是ChannelHandler的容器,负责ChannelHandler的管理和事件拦截。

pipeline、ChannelPipeline
Pipeline是ChannelPipeline的简称。ChannelPipeline用于处理网络事件和数据的流动。负责将一系列的处理逻辑(称为ChannelHandler)串联起来,形成一个处理链,对网络事件进行拦截和处理。
事件处理:ChannelPipeline负责处理入站(Inbound)和出站(Outbound)事件,如数据读取、数据写入、连接建立、连接断开等。
拦截器链:ChannelPipeline内部维护了一个拦截器链(或称为处理器链),每个拦截器(即ChannelHandler)都可以对事件进行处理或拦截。
动态性:ChannelPipeline允许在运行时动态地添加、删除或替换拦截器,从而灵活地扩展和定制网络处理逻辑。

  1. 启动服务器:
    配置完成后,通过调用bind方法并传入服务器的端口号,ServerBootstrap将启动服务器并绑定到指定的端口。
    bind方法返回一个ChannelFuture对象,您可以使用sync方法等待服务器启动完成。
    ChannelFuture是Netty框架中特有的接口,继承自Java的Future接口。在Netty中,所有的I/O操作都是异步的,ChannelFuture用于在操作完成时通知应用程序,以便应用程序可以执行某些操作或检索操作的结果。

  2. 关闭:

当服务器需要关闭时,可以调用ChannelFuture对象的channel().closeFuture().sync()方法,等待服务器通道关闭。
最后,调用bossGroup和workerGroup的shutdownGracefully方法,以优雅地断开连接并关闭线程组。

ChannelHandlerContext:
ChannelHandlerContext封装了Channel和ChannelPipeline,使得ChannelHandler可以方便地访问和操作ChannelPipeline和Channel。
ChannelHandlerContext提供了许多方法,用于操作和传播事件,如写入数据、刷新数据、触发读事件等。
ChannelHandlerContext允许ChannelHandler将事件传递给ChannelPipeline中的下一个处理器。例如,当一个入站事件(如数据读取)发生时,ChannelHandlerContext可以调用fireChannelRead方法,将事件传递给下一个入站处理器。

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

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

相关文章

import模块到另一个文件夹报错:ModuleNotFoundError: No module named xxx

1. 问题 打开项目文件夹my_code&#xff0c;将bb.py的函数或者类import到aa.py中&#xff0c;然后运行aa.py文件&#xff0c;可能会报错ModuleNotFoundError: No module named xxx。 ‪E:\Desktop\my_code ├── a │ ├── train.sh │ └── aa.py └── b└── b…

怎么写C#命令行参数程序,及控制台带参数案例(程序完整源码)下载

C#命令行参数解析控制台带参数编写案例&#xff08;程序完整源码&#xff09;下载链接 https://download.csdn.net/download/luckyext/90434790 在CMD命令窗口&#xff0c;输入ping 、ipconfig等这样的命令&#xff0c;大家应该都知道&#xff0c;但很多同学可能不知道怎么写…

JAVA安全—手搓内存马

前言 最近在学这个内存马&#xff0c;就做一个记录&#xff0c;说实话这个内存马还是有点难度的。 什么是内存马 首先什么是内存马呢&#xff0c;顾名思义就是把木马打进内存中。传统的webshell一旦把文件删除就断开连接了&#xff0c;而Java内存马则不同&#xff0c;它将恶…

算法 并查集

目录 前言 一 并查集的思路 二 并查集的代码分析 三 实操我们的代码 四 并查集的代码优化 总结 前言 并查集主要是用来求解集合问题的&#xff0c;用来查找集合还有就是合并集合&#xff0c;可以把这个运用到最小生成树里面 一 并查集的思路 1 并查集的相关的操作…

vulnhub靶场之【digitalworld.local系列】的development靶机

前言 靶机&#xff1a;digitalworld.local-devt-improved&#xff0c;IP地址为192.168.10.10 攻击&#xff1a;kali&#xff0c;IP地址为192.168.10.6 kali采用VMware虚拟机&#xff0c;靶机选择使用VMware打开文件&#xff0c;都选择桥接网络 这里官方给的有两种方式&…

Stiring-PDF:开源免费的PDF文件处理软件

Stiring-PDF是一款开源免费且比较好用的PDF文件处理工具。 Stiring-PDF官网网址为&#xff1a;https://www.stiringpdf.com/。Stiring-PDF是一款专业的PDF文件处理工具&#xff0c;支持Windows和macOS操作系统&#xff1b;提供丰富的PDF编辑和转换功能&#xff0c;适用于日常工…

SpringCloud系列教程(十二):网关配置动态路由

除了token以外&#xff0c;还有一个很实用的功能就是把网关的路由配置放到nacos上&#xff0c;并且修改路由配置的时候&#xff0c;网关服务可以动态的更新&#xff0c;这样我们在调整网络配置的时候&#xff0c;就不用重启服务了。所以我们需要用到两个重要的类&#xff1a;Na…

R JSON 文件

R JSON 文件 引言 在当今的数据分析和处理领域&#xff0c;R语言作为一种功能强大的统计计算和图形展示工具&#xff0c;被广泛应用于各种数据分析任务中。随着大数据时代的到来&#xff0c;数据的格式和结构变得越来越多样化。JSON&#xff08;JavaScript Object Notation&a…

ES6 特性全面解析与应用实践

1、let let 关键字用来声明变量&#xff0c;使用let 声明的变量有几个特点&#xff1a; 1) 不允许重复声明 2) 块儿级作用域 3) 不存在变量提升 4) 不影响作用域链 5) 暂时性死区 6&#xff09;不与顶级对象挂钩 在代码块内&#xff0c;使用let命令声明变量之前&#x…

如何使用 Ollama 的 API 来生成聊天

如何使用 Ollama 的 API 来生成聊天 简介 生成聊天 生成聊天的示例 加载模型 卸载模型 简介 Ollama 提供了一个 RESTful API&#xff0c;允许开发者通过 HTTP 请求与 Ollama 服务进行交互。这个 API 覆盖了所有 Ollama 的核心功能&#xff0c;包括模型管理、运行和监控。本…

【学Rust写CAD】10 加法器

源码 // src/matrix/adder.rs/** 说明&#xff1a;连加计算中&#xff0c;为提高运行期效率&#xff0c;用该结构增加一个Const变量&#xff0c;方便单独合并所有Const类型&#xff0c;最后一步才有可能出现Const与Val的加法计算*/use std::ops::Add;use super::constant::{Co…

学到什么记什么(25.3.3)

Upload-labs 今日重新做了一下文件上传漏洞&#xff0c;这里第一题之前采用直接抓包改后缀名.jpg为.php&#xff0c;再写入一句话<?php phpinfo();?>然后放行&#xff0c;得到图片地址&#xff08;可复制&#xff09;&#xff0c;本来直接访问图片地址即可得到敏感信息…

el-table input textarea 文本域 自适应高度,切换分页滚动失效处理办法

场景&#xff1a; el-table 表格 需要 input类型是 textarea 高度是自适应&#xff0c;第一页数据都是单行数据 不会产生滚动条&#xff0c;但是第二页数据是多行数据 会产生滚动条&#xff0c; bug: 第一页切换到第二页 第二页滚动条无法展示 解决办法&#xff1a;直接修改样…

[杂学笔记] 封装、继承、多态,堆和栈的区别,堆和栈的区别 ,托管与非托管 ,c++的垃圾回收机制 , 实现一个单例模式 注意事项

文章目录 1.封装、继承、多态2. 堆和栈的区别3.指针和引用的区别4. 托管与非托管5. c的垃圾回收机制6. 实现一个单例模式 注意事项 1.封装、继承、多态 封装就是将数据和内部的方法封装到一个类中&#xff0c;对外隐藏内部实现细节&#xff0c;但是留下了公共接口提供给外部使…

【三维生成】StarGen:基于视频扩散模型的可扩展的时空自回归场景生成

标题&#xff1a;《StarGen: A Spatiotemporal Autoregression Framework with Video Diffusion Model for Scalable and Controllable Scene Generation》 项目&#xff1a;https://zju3dv.github.io/StarGen 来源&#xff1a;商汤科技、浙大CAD、Tetras.AI 文章目录 摘要一、…

【一个月备战蓝桥算法】递归与递推

字典序 在刷题和计算机科学领域&#xff0c;字典序&#xff08;Lexicographical order&#xff09;也称为词典序、字典顺序、字母序&#xff0c;是一种对序列元素进行排序的方式&#xff0c;它模仿了字典中单词的排序规则。下面从不同的数据类型来详细解释字典序&#xff1a; …

【Linux】【网络】UDP打洞-->不同子网下的客户端和服务器通信(成功版)

【Linux】【网络】UDP打洞–&#xff1e;不同子网下的客户端和服务器通信&#xff08;成功版&#xff09; 根据上个文章的分析 问题可能出现在代码逻辑上面 我这里重新查找资料怀疑&#xff1a; 1 NAT映射可能需要多次数据包的发送才能建立。 2 NAT映射保存时间太短&#xff…

SpaCy处理NLP的详细工作原理及工作原理框图

spaCy处理NLP的详细工作原理及工作原理框图 spaCy处理NLP的详细工作原理 spaCy是一个基于Python的开源自然语言处理&#xff08;NLP&#xff09;库&#xff0c;它提供了一系列高效且易用的工具&#xff0c;用于执行各种NLP任务&#xff0c;如文本预处理、文本解析、命名实体识…

C++ Primer 动态数组

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

【Qt】ffmpeg照片提取、视频播放▲

目录 一、图像的成像原理&#xff1a; RGB成像原理&#xff1a; YUV成像原理&#xff1a; 二、多线程 三、ffmpeg解码&#xff08;照片提取&#xff09; 1.准备工作 &#xff08;1&#xff09;在工程文件夹里面新建三个文件夹 &#xff08;2&#xff09;在main函数中加…