大文件上传

上图就是大致的流程


一、标题图片

  1. 上传课程的标题图片

  1. Ajax发送请求到后端

  1. 后端接收到图片使用IO流去保存图片,返回图片的信息对象

  1. JS回调函数接收对象通过$("元素id").val(值),方式给页面form表达img标签src属性值,达到上传图片并回显

二、大文件上传(流媒体、音频、.zip文件等)

  1. 页面div点击事件,选中一个视频文件

  1. JS判断文件大小,判断文件类型是否合法

3.JS循环切片,计算总片数,计算每一片的起始位置,循环上传

4.发送Ajax转给后端切片

5.后端接收并创建临时目录存放

    /**
     * 分片上传,前端调用此方法
     * @param request
     * @param guid
     * @param chunk
     * @param file
     * @return
     */
    @PostMapping("/uploadSlice")
    @ResponseBody
    public ResponseResult<?> uploadSlice2(HttpServletRequest request, @RequestParam("guid") String guid,
                                          @RequestParam("chunk") Integer chunk,
                                          @RequestParam("file") MultipartFile file) {
        if (this.uploadSlice(request, guid, chunk, file)){
            return ResponseResultUtils.genResult("上传成功","");
        }else{
            return ResponseResultUtils.genErrorResult("上传失败");
        }
    }

    /**
     * 分片上传的具体方法
     * @param request
     * @param guid
     * @param chunk
     * @param file
     * @return
     */
    private boolean uploadSlice(HttpServletRequest request, String guid, Integer chunk, MultipartFile file) {
        try {
            boolean isMultipart = ServletFileUpload.isMultipartContent(request);
            logger.info("isMultipart = {}",isMultipart);
            if (isMultipart) {
                if (chunk == null){
                    chunk = 0;
                }
                // 临时目录用来存放所有分片文件
                String tempFileDir =  rootFilePath + bigPath + guid;
                File parentFileDir = new File(tempFileDir);
                if (!parentFileDir.exists()) {
                    parentFileDir.mkdirs();
                }
                logger.info("接到上传的分片文件,{},{},{}",guid,chunk,tempFileDir);
                // 分片处理时,前台会多次调用上传接口,每次都会上传文件的一部分到后台
                File tempPartFile = new File(parentFileDir, guid + "_" + chunk + ".part");
                FileUtils.copyInputStreamToFile(file.getInputStream(), tempPartFile);
            }
        } catch (Exception e) {
            return false;
        }
        return true;
    }

6.计数器变量值=总片数时,发送合并请求

7.后端合并

/**
     * 分片文件合并,前端调用此方法
     * @param guid
     * @param fileName
     * @return
     */
    @RequestMapping("/uploadMerge")
    @ResponseBody
    public ResponseResult<?> uploadMerge2(@RequestParam("guid") String guid, @RequestParam("fileName") String fileName) {
        // 得到 destTempFile 就是最终的文件
        SpaceImage image = this.uploadMerge(guid, fileName);
        if(null != imageSpaceImage){
            return ResponseResultUtils.genResult(imageSpaceImage,"合并成功");
        }else{
            return ResponseResultUtils.genErrorResult("合并文件失败");
        }
    }

    private ImageSpaceImage uploadMerge(String guid, String fileName){
        SpaceImage image = mergeFile(guid, fileName);
        //此处需要注意,OSS需要再次切片上传,但minIO是不用得,它默认5M超过就会自动切片
        String path = "";
        //移除文件
        poolTaskExecutor.execute(() -> {
            com.eyang.ecpp.utils.FileUtils.deleteFile(rootFilePath+bigPath);
        });
        return imageSpaceImage;
    }

    private ImageSpaceImage mergeFile(String guid, String fileName) {
        logger.info("接到上传的分片文件合并请求,{},{}",guid,fileName);
        try {
            String sName = fileName.substring(fileName.lastIndexOf("."));
            //时间格式化格式
            Date currentTime = new Date();
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
            //获取当前时间并作为时间戳
            String timeStamp = simpleDateFormat.format(currentTime);
            //拼接新的文件名
            String newName = timeStamp + sName;
            simpleDateFormat = new SimpleDateFormat("yyyyMM");
            String tempPath = rootFilePath + bigPath+guid;
            String margePath = rootFilePath + bigPath+simpleDateFormat.format(currentTime);
            File parentFileDir = new File(tempPath);
            if (parentFileDir.isDirectory()) {
                File destTempFile = new File(margePath, newName);
                if (!destTempFile.exists()) {
                    //先得到文件的上级目录,并创建上级目录,在创建文件
                    destTempFile.getParentFile().mkdir();
                    destTempFile.createNewFile();
                }
                for (int i = 0; i < Objects.requireNonNull(parentFileDir.listFiles()).length; i++) {
                    File partFile = new File(parentFileDir, guid + "_" + i + ".part");
                    FileOutputStream destTempfos = new FileOutputStream(destTempFile, true);
                    //遍历"所有分片文件"到"最终文件"中
                    FileUtils.copyFile(partFile, destTempfos);
                    destTempfos.close();
                }
                // 删除临时目录中的分片文件
                FileUtils.deleteDirectory(parentFileDir);

                String[] resultArr = FileStorageUtils.saveBigFile(Files.readAllBytes(Paths.get(destTempFile.getPath())), com.eyang.ecpp.utils.FileUtils.getExtension(destTempFile.getName()));
                ImageSpaceImage imageSpaceImage = new ImageSpaceImage();

                //第一个是组名 第二个是改后的文件名
                imageSpaceImage.setImgUrl(margePath+"/"+newName);
                //imageSpaceImage.setName(destTempFile.getName());
                imageSpaceImage.setName(fileName);
                return imageSpaceImage;//destTempFile.getAbsolutePath();
            }
        } catch (Exception e) {
            logger.error("切片文件合并,失败原因e:{}", e.getMessage());
        }
        return null;

    }

8.合并成功后进行转码

转码需要下载一个ffmpeg,下载完解压打开bin目录,打开电脑的环境变量往path中添加上bin的目录即可。

还需要引入依赖

 <!--  视频编码 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>

9.转码发送请求

10.后端转码

/**
     * 视频编码
     *
     * @param absolutePath 绝对路径
     * @return {@link ResponseResult}<{@link ?}>
     */
    @RequiresPermissions("cms:article:edit")
    @RequestMapping(value = "video/coding")
    @ResponseBody
    public ResponseResult<?> videoCoding(@RequestParam String absolutePath) {
        
        TranscodeConfig transcodeConfig = new TranscodeConfig();
        //设置视频封面
        transcodeConfig.setPoster("1");
        transcodeConfig.setTsSeconds("60");
        ResponseResult<?> transResult = TranscodeFileUtils.transCodeFile(absolutePath, transcodeConfig);
        String retUrl = "";
        if (null != transResult) {
            Map<String, Object> data = (Map<String, Object>) transResult.getData();
            if (null != data) {
                Map<String, Object> videoInfo = (Map<String, Object>) data.get("data");
                if (null != videoInfo) {
                    retUrl = (String) videoInfo.get("m3u8");
                }
            }
        }
        return ResponseResultUtils.genResult(retUrl,"转码成功");
    }

视频转码配置实体类

package com.utils;

public class TranscodeConfig {

    private String poster; // 截取封面的时间

    private String tsSeconds; // ts分片大小,单位是秒

    private String cutStart; // 视频裁剪,开始时间

    private String cutEnd; // 视频裁剪,结束时间


    public String getPoster() {
        return poster;
    }

    public void setPoster(String poster) {
        this.poster = poster;
    }

    public String getTsSeconds() {
        return tsSeconds;
    }

    public void setTsSeconds(String tsSeconds) {
        this.tsSeconds = tsSeconds;
    }

    public String getCutStart() {
        return cutStart;
    }

    public void setCutStart(String cutStart) {
        this.cutStart = cutStart;
    }

    public String getCutEnd() {
        return cutEnd;
    }

    public void setCutEnd(String cutEnd) {
        this.cutEnd = cutEnd;
    }

    public TranscodeConfig() {
    }

    public TranscodeConfig(String poster, String tsSeconds, String cutStart, String cutEnd) {
        this.poster = poster;
        this.tsSeconds = tsSeconds;
        this.cutStart = cutStart;
        this.cutEnd = cutEnd;
    }
}

转码工具类

public class TranscodeFileUtils {


    /**
     * 视频根路径
     */
    private static String videoFolder= Global.getConfig("video.folder");

    private static final Logger LOGGER = LoggerFactory.getLogger(TranscodeFileUtils.class);

    public static ResponseResult<Map<String, Object>> transCodeFile(String filePath, TranscodeConfig transcodeConfig){
        try {

            // 按照日期生成子目录
            String today = DateTimeFormatter.ofPattern("yyyyMMdd").format(LocalDate.now());
            String s = Identities.uuid2();
            Path targetFolder = Paths.get(videoFolder, today, s);

            // 执行转码操作
            LOGGER.info("开始转码");

            FFmpegUtils.transcodeToM3u8(filePath, targetFolder.toString(), transcodeConfig);

            // 封装结果
            Map<String, Object> videoInfo = new HashMap<>();
            videoInfo.put("m3u8", String.join("\\", targetFolder.toString(), "index.m3u8"));
            videoInfo.put("poster", String.join("\\", targetFolder.toString(), "poster.jpg"));

            Map<String, Object> result = new HashMap<>();
            result.put("success", true);
            result.put("data", videoInfo);
            return ResponseResultUtils.genResult(result,"转码成功!");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    public static void main(String[] args){

        System.out.println(TranscodeFileUtils.transCodeFile("E:\\录屏\\shipin\\test.mp4", new TranscodeConfig("00:00:00.001","15","","")));

    }

}

转码工具类

public class FFmpegUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(FFmpegUtils.class);

    // 跨平台换行符
    private static final String LINE_SEPARATOR = System.getProperty("line.separator");

    /**
     * 生成随机16个字节的AESKEY
     * @return
     */
    private static byte[] genAesKey ()  {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
            keyGenerator.init(128);
            return keyGenerator.generateKey().getEncoded();
        } catch (NoSuchAlgorithmException e) {
            return null;
        }
    }

    /**
     * 在指定的目录下生成key_info, key文件,返回key_info文件
     * @param folder
     * @throws IOException
     */
    private static Path genKeyInfo(String folder) throws IOException {
        // AES 密钥
        byte[] aesKey = genAesKey();
        // AES 向量
        String iv = Hex.encodeHexString(genAesKey());

        // key 文件写入
        Path keyFile = Paths.get(folder, "key");
        Files.write(keyFile, aesKey, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);

        // key_info 文件写入
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("key").append(LINE_SEPARATOR);                    // m3u8加载key文件网络路径
        stringBuilder.append(keyFile.toString()).append(LINE_SEPARATOR);    // FFmeg加载key_info文件路径
        stringBuilder.append(iv);                                            // ASE 向量

        Path keyInfo = Paths.get(folder, "key_info");

        Files.write(keyInfo, stringBuilder.toString().getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);

        return keyInfo;
    }

    /**
     * 指定的目录下生成 master index.m3u8 文件
     * @param file            master m3u8文件地址
     * @param indexPath            访问子index.m3u8的路径
     * @param bandWidth            流码率
     * @throws IOException
     */
    private static void genIndex(String file, String indexPath, String bandWidth) throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("#EXTM3U").append(LINE_SEPARATOR);
        stringBuilder.append("#EXT-X-STREAM-INF:BANDWIDTH=" + bandWidth).append(LINE_SEPARATOR);  // 码率
        stringBuilder.append(indexPath);
        Files.write(Paths.get(file), stringBuilder.toString().getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
    }

    /**
     * 转码视频为m3u8
     * @param source                源视频
     * @param destFolder            目标文件夹
     * @param config                配置信息
     * @throws IOException
     * @throws InterruptedException
     */
    public static void transcodeToM3u8(String source, String destFolder, TranscodeConfig config) throws IOException, InterruptedException {

        // 判断源视频是否存在
        if (!Files.exists(Paths.get(source))) {
            throw new IllegalArgumentException("文件不存在:" + source);
        }

        // 创建工作目录
        Path workDir = Paths.get(destFolder, "ts");
        Files.createDirectories(workDir);

        // 在工作目录生成KeyInfo文件
        Path keyInfo = genKeyInfo(workDir.toString());

        // 构建命令
        List<String> commands = new ArrayList<>();
        commands.add("ffmpeg");
        commands.add("-i");
        commands.add(source);                    // 源文件
        commands.add("-c:v");
        commands.add("libx264");                // 视频编码为H264
        commands.add("-c:a");
        commands.add("copy");                    // 音频直接copy
        commands.add("-hls_key_info_file");
        commands.add(keyInfo.toString());        // 指定密钥文件路径
        commands.add("-hls_time");
        commands.add(config.getTsSeconds());    // ts切片大小
        commands.add("-hls_playlist_type");
        commands.add("vod");                    // 点播模式
        commands.add("-hls_segment_filename");
        commands.add("%06d.ts");                // ts切片文件名称

        if (StringUtils.hasText(config.getCutStart())) {
            commands.add("-ss");
            commands.add(config.getCutStart());    // 开始时间
        }
        if (StringUtils.hasText(config.getCutEnd())) {
            commands.add("-to");
            commands.add(config.getCutEnd());        // 结束时间
        }
        commands.add("index.m3u8");                                                        // 生成m3u8文件

        // 构建进程
        Process process = new ProcessBuilder()
                .command(commands)
                .directory(workDir.toFile())
                .start();

        // 读取进程标准输出
        new Thread(() -> {
            try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line = null;
                while ((line = bufferedReader.readLine()) != null) {
                    LOGGER.info(line);
                }
            } catch (IOException e) {
            }
        }).start();

        // 读取进程异常输出
        new Thread(() -> {
            try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
                String line = null;
                while ((line = bufferedReader.readLine()) != null) {
                    LOGGER.info(line);
                }
            } catch (IOException e) {
            }
        }).start();


        // 阻塞直到任务结束
        if (process.waitFor() != 0) {
            throw new RuntimeException("视频切片异常");
        }

        // 切出封面
        if (!screenShots(source, String.join(File.separator, destFolder, "poster.jpg"), config.getPoster())) {
            throw new RuntimeException("封面截取异常");
        }

        // 获取视频信息
        MediaInfo mediaInfo = getMediaInfo(source);
        if (mediaInfo == null) {
            throw new RuntimeException("获取媒体信息异常");
        }

        // 生成index.m3u8文件
        genIndex(String.join(File.separator, destFolder, "index.m3u8"), "ts/index.m3u8", mediaInfo.getFormat().getBitRate());

        // 删除keyInfo文件
        Files.delete(keyInfo);
    }

    /**
     * 获取视频文件的媒体信息
     * @param source
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    public static MediaInfo getMediaInfo(String source) throws IOException, InterruptedException {
        List<String> commands = new ArrayList<>();
        commands.add("ffprobe");
        commands.add("-i");
        commands.add(source);
        commands.add("-show_format");
        commands.add("-show_streams");
        commands.add("-print_format");
        commands.add("json");

        Process process = new ProcessBuilder(commands)
                .start();

        MediaInfo mediaInfo = null;

        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            mediaInfo = new Gson().fromJson(bufferedReader, MediaInfo.class);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (process.waitFor() != 0) {
            return null;
        }

        return mediaInfo;
    }

    /**
     * 截取视频的指定时间帧,生成图片文件
     * @param source        源文件
     * @param file            图片文件
     * @param time            截图时间 HH:mm:ss.[SSS]
     * @throws IOException
     * @throws InterruptedException
     */
    public static boolean screenShots(String source, String file, String time) throws IOException, InterruptedException {

        List<String> commands = new ArrayList<>();
        commands.add("ffmpeg");
        commands.add("-i");
        commands.add(source);
        commands.add("-ss");
        commands.add(time);
        commands.add("-y");
        commands.add("-q:v");
        commands.add("1");
        commands.add("-frames:v");
        commands.add("1");
        commands.add("-f");
        commands.add("image2");
        commands.add(file);

        Process process = new ProcessBuilder(commands)
                .start();

        // 读取进程标准输出
        new Thread(() -> {
            try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line = null;
                while ((line = bufferedReader.readLine()) != null) {
                    LOGGER.info(line);
                }
            } catch (IOException e) {
            }
        }).start();

        // 读取进程异常输出
        new Thread(() -> {
            try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
                String line = null;
                while ((line = bufferedReader.readLine()) != null) {
                    LOGGER.error(line);
                }
            } catch (IOException e) {
            }
        }).start();

        return process.waitFor() == 0;
    }

}

11.转码成功返回m3u8文件的路径,放到页面input隐藏标签中

编码成功后执行回调函数,m3u8文件赋给_data变量如下图

使用$(#页面元素id).val(值),下图是把m3u8赋值给页面的input

12.最后页面form提交,保存视频转码后m3u8的路径。

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

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

相关文章

CVPR 2023 | 旷视研究院入选论文亮点解读

近日&#xff0c;CVPR 2023 论文接收结果出炉。近年来&#xff0c;CVPR 的投稿数量持续增加&#xff0c;今年收到有效投稿 9155 篇&#xff0c;和 CVPR 2022 相比增加 12%&#xff0c;创历史新高。最终&#xff0c;大会收录论文 2360 篇&#xff0c;接收率为 25.78 %。本次&…

HCIP-6.2NAT协议原理与配置

HCIP-6.2NAT协议原理与配置1、NAT的工作原理1.1、静态NAT1.2、动态NAT1.3、NAPT&#xff08;Network Address Port Translation&#xff09;端口多路复用1.3.1、NAT与NAPT区别2、Easy IP3、NAT服务器随着Internet的发展和网络应用的增多&#xff0c;IPv4地址枯竭已经成为制约网…

Qt5.12实战之控件设计

QPushButton使用静态创建,通过设计窗口拖放控件:添加信号处理:为Pressed,Released,Clicked三个按钮添加信号自动添加的信号槽:动态生成的槽定义:动态创建创建QPushButtonQPushButton *btn new QPushButton(QString::fromLocal8Bit("动态添加的按钮"),this);connect(…

并查集、并查集+离线、并查集+倒叙回答

文章目录并查集[200. 岛屿数量](https://leetcode.cn/problems/number-of-islands/)[721. 账户合并](https://leetcode.cn/problems/accounts-merge/)并查集 离线计算[1697. 检查边长度限制的路径是否存在](https://leetcode.cn/problems/checking-existence-of-edge-length-l…

JVM知识整理

JVM知识整理 JVM的主要组成部分 JVM包含两个两个子系统&#xff08;类加载子系统和执行引擎&#xff09;和两个组件&#xff08;运行时数据区与和本地库接口&#xff09; 类加载子系统&#xff1a;根据给定的全限定类名来加载class文件到运行时数据区域中的方法区。执行引擎&a…

Python实现人脸识别检测, 对美女主播照片进行评分排名

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 素材、视频、代码、插件安装教程我都准备好了&#xff0c;直接在文末名片自取就可点击此处跳转 开发环境: Python 3.8 Pycharm 2021.2 模块使用&#xff1a; requests >>> pip install requests tqdm >…

串口通信(STM32演示实现)

目录 一、串行通信的概念 二、寄存器 2.1控制寄存器USART_CR1 2.2控制寄存器USART_CR2​编辑 2.3串口寄存器USART_BRR 2.4 USART_ISR 2.5USART_TDR 2.6USART_RDR​编辑 三、实现串口数据的收发 一、串行通信的概念 u通信&#xff0c;最少要有两个对象&#xff0c;一个收…

奇安信_防火墙部署_透明桥模式

奇安信_防火墙部署_透明桥模式一、预备知识二、项目场景三、拓扑图四、基本部署配置1. 登录web控制台2.连通性配置3.可信主机配置4.授权导入5.特征库升级6.安全配置文件五、透明桥配置1. 创建桥2. 端口绑定桥3. 创建桥端口六、结语一、预备知识 安全设备接入网络部署方式 二、…

​selenium+python做web端自动化测试框架与实例详解教程​

下面有详细的代码介绍&#xff0c;如果不是很明白的话&#xff0c;可以看看这套视频&#xff0c;在哔站学习人数超过数万人&#xff01; 在华为工作了10年的大佬出的Web自动化测试教程&#xff0c;华为现用技术教程&#xff01;_哔哩哔哩_bilibili在华为工作了10年的大佬出的W…

数据结构——二叉树与堆

作者&#xff1a;几冬雪来 时间&#xff1a; 内容&#xff1a;二叉树与堆内容讲解 目录 前言&#xff1a; 1.完全二叉树的存储&#xff1a; 2.堆的实现&#xff1a; 1.创建文件&#xff1a; 2.定义结构体&#xff1a; 3.初始化结构体&#xff1a; 4.扩容空间与扩容…

从 X 入门Pytorch——BN、LN、IN、GN 四种归一化层的代码使用和原理

Pytorch中四种归一化层的原理和代码使用前言1 Batch Normalization&#xff08;2015年提出&#xff09;Pytorch官网解释原理Pytorch代码示例2 Layer Normalization&#xff08;2016年提出&#xff09;Pytorch官网解释原理Pytorch代码示例3 Instance Normalization&#xff08;2…

【docker】docker安装MySQL

目录前言一、安装MySQL不指定版本&#xff0c;默认下载最新版本指定版本号二、创建MySQL实例镜像拉取完成后&#xff0c;用该镜像创建MySQL实例创建成功后使用下面命令查看创建好的MySQL实例三、检查是否启动成功查看端口号是否配置并开启用Navicat测试数据库是否启动成功前言 …

银河麒麟v10sp2安装nginx

nginx官网下载&#xff1a;http://nginx.org/download/ 银河麒麟系统请先检查yum源是否配置&#xff0c;若没有配置请参考&#xff1a;https://qdhhkj.blog.csdn.net/article/details/129680789 一、安装 1、yum安装依赖 yum install gcc gcc-c make unzip pcre pcre-devel …

[ 网络 ] 应用层协议 —— HTTP协议

目录 1.HTTP协议 1.1URL urlencode和urldecode 2. HTTP协议格式 HTTP请求 HTTP响应 3.告知服务器意图的HTTP方法 GET&#xff1a;获取资源 POST&#xff1a;传输实体主体 GET和POST的区别 使用Cookie的状态管理 4.返回结果的HTTP状态码 状态码告知从服务器端返回的…

Linux防火墙——SNAT、DNAT

目录 NAT 一、SNAT策略及作用 1、概述 SNAT应用环境 SNAT原理 SNAT转换前提条件 1、临时打开 2、永久打开 3、SNAT转换1&#xff1a;固定的公网IP地址 4、SNAT转换2&#xff1a;非固定的公网IP地址&#xff08;共享动态IP地址&#xff09; 二、SNAT实验 配置web服务…

Redis单线程还是多线程?IO多路复用原理

目录专栏导读一、Redis版本迭代二、Redis4.0之前为什么一直采用单线程&#xff1f;三、Redis6.0引入多线程四、Redis主线程和IO线程是如何完成请求的&#xff1f;1、服务端和客户端建立socket连接2、IO线程读取并解析请求3、主线程执行请求命令4、IO线程会写回socket和主线程清…

【C++】科普:C++中的浮点数怎么在计算机中表示?

这里我们以8.25这个数为例说明计算机时如何存取float类型的数据的&#xff1a; float a 8.25;引言 1. 所占位数 首先&#xff0c;明确一个概念&#xff0c;float类型的数据在常规计算机中通常占4个字节&#xff0c;也就是32位。其内存分布如图&#xff1a; 位字段说明所占位…

TCP和UDP协议的区别?

是否面向连接&#xff1a; TCP 是面向连接的传输&#xff0c;UDP 是面向无连接的传输。 是否是可靠传输&#xff1a;TCP是可靠的传输服务&#xff0c;在传递数据之前&#xff0c;会有三次握手来建立连接&#xff1b;在数据传递时&#xff0c;有确认、窗口、重传、拥塞控制机制…

【C语言蓝桥杯每日一题】——排序

【C语言蓝桥杯每日一题】—— 排序&#x1f60e;前言&#x1f64c;排序&#x1f64c;总结撒花&#x1f49e;&#x1f60e;博客昵称&#xff1a;博客小梦 &#x1f60a;最喜欢的座右铭&#xff1a;全神贯注的上吧&#xff01;&#xff01;&#xff01; &#x1f60a;作者简介&am…

【Docker】CAdvisor+InfluxDB+Granfana容器监控

文章目录原生命令 docker stats容器监控3剑客CIGCAdvisorInfluxDBGranfanacompose容器编排&#xff0c;一套带走新建目录新建3件套组合的 docker-compose.yml检查配置&#xff0c;有问题才有输出 docker-compose config -q启动docker-compose文件 docker-compose up -d测试浏览…
最新文章