C++基于多设计模式下的同步异步日志系统day5

news2024/9/21 14:45:09

C++基于多设计模式下的同步&异步日志系统day5

📟作者主页:慢热的陕西人

🌴专栏链接:C++基于多设计模式下的同步&异步日志系统

📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言

主要内容完成双缓冲区异步任务处理器,日志器模块异步工作器设计与实现。

在这里插入图片描述

文章目录

  • C++基于多设计模式下的同步&异步日志系统day5
    • 1.双缓冲区异步任务处理器(AsyncLooper)设计
    • 2.日志器模块异步工作器设计与实现

1.双缓冲区异步任务处理器(AsyncLooper)设计

设计思想:异步处理线程+数据池

使⽤者将需要完成的任务添加到任务池中,由异步线程来完成任务的实际执⾏操作

任务池的设计思想:双缓冲区阻塞数据池

优势:避免了空间的频繁申请释放,且尽可能的减少了⽣产者与消费者之间锁冲突的概率,提⾼了任务处理效率

在任务池的设计中,有很多备选⽅案,⽐如循环队列等等,但是不管是哪⼀种都会涉及到锁冲突的情况,因为在⽣产者与消费者模型中,任何两个⻆⾊之间都具有互斥关系,因此每⼀次的任务添加与取出都有可能涉及锁的冲突,⽽双缓冲区不同,双缓冲区是处理器将⼀个缓冲区中的任务全部处理完毕后,然后交换两个缓冲区,重新对新的缓冲区中的任务进⾏处理,虽然同时多线程写⼊也会冲突,但是冲突并不会像每次只处理⼀条的时候频繁(减少了⽣产者与消费者之间的锁冲突),且不涉及到空间的频繁申请释放所带来的消耗。

image-20240304093702506

  • 单个缓冲区的进一步设计

设计一个缓冲区:直接存放格式化后的日志消息字符串

​ 好处:

​ 1.减少了LogMsg对象频繁的构造的消耗

​ 2.可以针对缓冲区中的日志消息,一次性进行IO,减少IO次数,提高效率

缓冲区类的设计

​ 1.管理一个存放字符串数据的缓冲区(使用vector进行空间管理)

​ 2.当前的写入数据位置的指针(指向可写区域的起始位置,避免数据的写入覆盖)

​ 3.当前的读取数据位置的指针(指向可读数据区域的起始位置,当读取指针与写入指针指向相同位置的时候,代表没有数据了)

提供的操作

​ 1.向缓冲区中写入数据

​ 2.获取可读数据起始地址的接口

​ 3.获取可读数据长度的接口

​ 4.移动读写位置的接口

​ 5.初始化缓冲区(将读写缓冲区的所有数据处理完毕之后)

​ 6.提供交换缓冲区的操作(交换空间地址,并不交换空间数据)

/*实现异步日志器的缓冲区*/

#ifndef __M_BUF_H__
#define __M_BUF_H__


#include<iostream>
#include<vector>
#include<cassert>

namespace xupt
{

    class Buffer
    {
        #define DEFAULT_BUFFER_SZIE (1 * 1024 * 1024) //100M
        #define THREASHOLD_BUFFER_SIZE (8 * 1024 * 1024)
        #define INCREAMENT_BUFFER_SIZE (1 * 1024 * 1024)
        public:
            Buffer() : _buffer(DEFAULT_BUFFER_SZIE), _reader_idx(0), _writer_idx(0) {}
 
            //向缓冲区写入数据
            void push(const char *data, size_t len)
            {   
                //缓冲区空间不:1.扩容; 2.阻塞;
                //if(len > writeAbleSize()) return;
                //2.动态空间,测试性能极限
                ensureEnough(len);
                //1.将数据拷贝到缓冲区
                std::copy(data, data + len, &_buffer[_writer_idx]);
                //2.移动写指针
                moveWriter(len);
            }
            //返回当前可写入的大小
            size_t writeAbleSize()
            {
                //对于扩容的思路来说,我们不需要这个接口,因为总是可写的
                //因此这个接口,仅仅针对固定大小的缓冲区大小所提供的
                return(_buffer.size() - _writer_idx);
            }
            //返回可读数据的起始地址
            const char* begin()
            {
                return &_buffer[_reader_idx];
            }
            //返回可读数据的长度
            size_t readAbleSize()
            {
                //当前的缓冲区并不环形的
                //当前实现的缓冲区,是双缓冲区的思路,处理完就交换,所以不存在空间循环使用的情况
                return (_writer_idx - _reader_idx);
            }
            //移动读位置的接口
            void moveReader(size_t len)
            {
                assert(len <= readAbleSize());
                _reader_idx += len;
            }
            //重置读写位置,初始化缓冲区
            void reset()
            {
                _writer_idx = 0; //缓冲区所有的空间都是空闲的
                _reader_idx = 0; //与_writer_idx相等代表没有可读的数据
            }
            //对Buffer实现交换操作
            void swap(Buffer &buffer)
            {
                _buffer.swap(buffer._buffer);
                std::swap(_reader_idx, buffer._reader_idx);
                std::swap(_writer_idx, buffer._writer_idx);
            }
            //判断缓冲区是否为空,当任务写入缓冲区不为零的时候,我们才有交换的意义。
            bool empty()
            {
                return (_reader_idx == _writer_idx);
            }
        private:
            //对空间进行扩容
            void ensureEnough(size_t len)
            {
                if(len <= writeAbleSize()) return ;  //如果空间足够,那么直接返回不扩容
                size_t newsize = 0;
                if(_buffer.size() < THREASHOLD_BUFFER_SIZE) 
                {
                    //当buffer的空间大小小于阈值的时候,二倍增长
                    newsize = _buffer.size() * 2 + len;   //+len,防止翻倍之后依旧不满足len的情况
                }
                else
                {
                    //当buffer的空间大小大于阈值的时候,线性增长
                    newsize = _buffer.size() + INCREAMENT_BUFFER_SIZE + len;
                }
                _buffer.resize(newsize);
            }
            //移动写位置的接口
            void moveWriter(size_t len)
            {
                assert((len + _writer_idx) <= _buffer.size());
                _writer_idx += len;
            }
        private:
            std::vector<char> _buffer;  //存储缓冲区数据的vector
            size_t _reader_idx;  //读取数据的下标
            size_t _writer_idx;  //写入数据的下标
    };



}

#endif

2.日志器模块异步工作器设计与实现

①异步使用双缓冲区的思想

外界将任务数据,添加到输出缓冲器中

异步线程对处理缓冲区中的数据进行处理,若处理缓冲区中没有数据了,则交换缓冲区

②实现

  • 管理的成员:

    a.双缓冲区(生产,消费)

    b.互斥锁 ,保证线程安全

    c.条件变量-生产&消费(生产缓冲区没有数据,处理完处理完消费缓冲区后就休眠)

    d.回调函数(针对缓冲区中数据的处理接口,外界传入一个函数,告诉工作器如何处理数据)

  • 提供的操作:

    a.停止异步工作器

    b.添加数据到缓冲区

  • 私有操作:

    创建线程,线程入口函数中 交换缓冲区,对消费缓区,使用回调函数进行处理

③异步日志器的设计

  • 继承于Logger日志器类

    对于写日志操作进行函数重写(不再将数据写入文件,而是通过异步消息处理器,放到缓冲区中)

  • 通过异步消息处理器,进行日志消息的实际落地

  • 管理的成员

    a.异步工作器(异步消息处理器)

//logger.hpp
/*
    完成日志器模块:
        1.抽象日志器基类
        2.派生出不同的子类(同步日志器类 & 异步日志器类)

*/
#ifndef __M_LOGER_H__
#define __M_LOGER_H__

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include"format.hpp"
#include"level.hpp"
#include"message.hpp"
#include"sink.hpp"
#include"util.hpp"
#include"looper.hpp"


#include<atomic>
#include<stdio.h>
#include<mutex>
#include<stdarg.h>
#include<cassert>


namespace xupt
{
    class Logger
    {
    public:
        using ptr = std::shared_ptr<Logger>;
        Logger(const std::string& logger_name,
            LogLevel::value level,
            Formatter::ptr &formatter,
            std::vector<LogSink::ptr> sinks):
            _logger_name(logger_name),
            _limit_level(level),
            _formatter(formatter),
            _sinks(sinks.begin(), sinks.end()){}
        /*完成日志构造消息,并进行格式化,得到格式化后的日志消息字符串---然后进行落地输出*/
        void debug(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            //1.判断当前的日志是否到达了输出等级
            if(LogLevel::value::DEBUG < _limit_level) { return ; }
            va_list ap;
            va_start(ap, fmt);
            char* res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if(ret == -1)
            {
                std::cout << "vasprintf failed!\n";
                return ;
            }
            va_end(ap); //将ap置空
            serialize(LogLevel::value::DEBUG, file, line, res);
            free(res);
        }
        void info(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            //1.判断当前的日志是否到达了输出等级
            if(LogLevel::value::INFO < _limit_level) { return ; }
            va_list ap;
            va_start(ap, fmt);
            char* res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if(ret == -1)
            {
                std::cout << "vasprintf failed!\n";
                return ;
            }
            va_end(ap); //将ap置空
            serialize(LogLevel::value::INFO, file, line, res);
            free(res);
        }        
        void warn(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            //1.判断当前的日志是否到达了输出等级
            if(LogLevel::value::WARN < _limit_level) { return ; }
            va_list ap;
            va_start(ap, fmt);
            char* res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if(ret == -1)
            {
                std::cout << "vasprintf failed!\n";
                return ;
            }
            va_end(ap); //将ap置空
            serialize(LogLevel::value::WARN, file, line, res);
            free(res);
        }             
        void error(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            //1.判断当前的日志是否到达了输出等级
            if(LogLevel::value::ERROR < _limit_level) { return ; }
            va_list ap;
            va_start(ap, fmt);
            char* res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if(ret == -1)
            {
                std::cout << "vasprintf failed!\n";
                return ;
            }
            va_end(ap); //将ap置空
            serialize(LogLevel::value::ERROR, file, line, res);
            free(res);
        }             
        void fatal(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            //1.判断当前的日志是否到达了输出等级
            if(LogLevel::value::FATAL < _limit_level) { return ; }
            va_list ap;
            va_start(ap, fmt);
            char* res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if(ret == -1)
            {
                std::cout << "vasprintf failed!\n";
                return ;
            }
            va_end(ap); //将ap置空
            serialize(LogLevel::value::FATAL, file, line, res);
            free(res);
        }     

    protected:
        void serialize(LogLevel::value level, const std::string &file, size_t line, char* str)
        {
            //2.构造LogMsg对象
            LogMsg msg(level, line, file, _logger_name, str);
            //3.通过格式化工具对LogMsg进行格式化,得到格式化后的日志字符串
            std::stringstream ss;
            _formatter->format(ss, msg);
            //5.进行日志落地
            log(ss.str().c_str(), ss.str().size());
        }
        /*抽象接口完成实际的落地输出--不同的日志器会有不同的实际落地方式*/
        virtual void log(const char *data, size_t len) = 0;

    protected:
        std::mutex _mutex;                         // 保证过程的线程安全
        std::string _logger_name;                  // 日志器名称
        std::atomic<LogLevel::value> _limit_level; // 日志等级
        Formatter::ptr _formatter;                 // 格式化
        std::vector<LogSink::ptr> _sinks;          // 用一个数组来存放日志落地位置
    };



    //同步日志器
    class SyncLogger : public Logger
    {
    public:
        SyncLogger(const std::string& logger_name,
            LogLevel::value level,
            Formatter::ptr &formatter,
            std::vector<LogSink::ptr> sinks):
            Logger(logger_name, level, formatter, sinks){}
    protected:
        virtual void log(const char *data, size_t len)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            if(_sinks.empty()) return;
            for(auto &sink : _sinks)
            {
                sink->log(data,len);
            }
        }
    };


    class AsyncLogger : public Logger
    {
    public:
        AsyncLogger(const std::string& logger_name,
            LogLevel::value level,
            Formatter::ptr &formatter,
            std::vector<LogSink::ptr> sinks,
            AsyncType looper_type = AsyncType::ASYNC_SAFE
            ):
            Logger(logger_name, level, formatter, sinks),
            _looper(std::make_shared<AsyncLooper>(std::bind(&AsyncLogger::realLog, this, std::placeholders::_1), looper_type))
            {}

        //将数据写入缓冲区
        virtual void log(const char* data, size_t len)
        {
            _looper->push(data, len);
        }

        //设计一个实际落地的函数(将缓冲区中的数据落地)
        void realLog(Buffer& buf)
        {
            //这里不需要加锁,因为这里不是多线程,是串行进行的
            if(_sinks.empty()) return;
            for(auto &sink : _sinks)
            {
                sink->log(buf.begin(), buf.readAbleSize());
            }
        }
    private:
        AsyncLooper::ptr _looper; //
    };



    enum class LoggerType
    {
        LOGGER_SYNC,
        LOGGER_ASYNC
    };
    /*使用建造者模式来建造日志器,而不用用户直接去构造日志器,减少用户的使用复杂度*/
    //1.抽象一个日志器建造者类(完成日志器对象所需零部件的构造 & 日志器的构建)
    //  1.设置日志器类型
    //  2.将不同类型日志器的创建放到同一个日志器建造类中完成
    class LoggerBuilder
    {
        public:
            LoggerBuilder():
                _logger_type(LoggerType::LOGGER_SYNC),
                _limit_level(LogLevel::value::DEBUG),
                _looper_type(AsyncType::ASYNC_SAFE)
            {}
            void buildLoggerType(LoggerType type) { _logger_type = type; }
            void buildEnableUnsafeAsync() { _looper_type = AsyncType::ASYNC_UNSAFE; }
            void buildLoggerName(const std::string & name) { _logger_name = name; }
            void buildLoggerLevel(LogLevel::value level) { _limit_level = level; }
            void buildFomatter(const std::string &pattern) 
            { 
                _formatter = std::make_shared<Formatter>(pattern); 
            }
            template<typename SinkType, typename ...Args>
            void buildSink(Args &&... args)
            {
                LogSink::ptr psink = SinkFactory::create<SinkType>(std::forward<Args>(args)...);
                _sinks.push_back(psink);
            }
            virtual Logger::ptr build() = 0;
        protected:
            AsyncType _looper_type;
            LoggerType _logger_type;
            std::string _logger_name;                  // 日志器名称
            std::atomic<LogLevel::value> _limit_level; // 日志等级
            Formatter::ptr _formatter;                 // 格式化
            std::vector<LogSink::ptr> _sinks;          // 用一个数组来存放日志落地位置
    };
    //2.派生出具体的建造者类---局部的日志器建造者&全局的日志器建造者(后边添加了全局单例管理器之后,将日志器添加全局管理)

    class LocalLoggerBuilder : public LoggerBuilder
    {
        public:
            Logger::ptr build() override
            {
                assert(_logger_name.empty() == false); //必须有日志器名称
                if(_formatter.get() == nullptr)
                {
                    _formatter = std::make_shared<Formatter>();
                }
                if(_sinks.empty())
                {
                    buildSink<StdoutSink>();
                }
                if(_logger_type == LoggerType::LOGGER_ASYNC)
                {
                    return std::make_shared<AsyncLogger>(_logger_name, _limit_level, _formatter, _sinks);
                }

                //返回同步日志器
                return std::make_shared<SyncLogger>(_logger_name, _limit_level, _formatter, _sinks);
            }
    };  

}


#endif


//looper.hpp
/*实现异步工作器*/


#ifndef __M_LOP_H__
#define __M_LOP_H__


#include"buffer.hpp"

#include<mutex>
#include<condition_variable>
#include<thread>
#include<functional>
#include<atomic>

namespace xupt
{
    using Functor = std::function<void(Buffer&)>;

    enum class AsyncType
    {
        ASYNC_SAFE,  // 安全模式,表示缓冲区满了则退出
        ASYNC_UNSAFE // 无限扩容
    };

    class AsyncLooper
    {
    public:
        using ptr = std::shared_ptr<AsyncLooper>;
        AsyncLooper(const Functor & cb, AsyncType looper_type = AsyncType::ASYNC_SAFE):
        _looper_type(looper_type),
        _stop(false),
        _thread(std::thread(&AsyncLooper::threadEntry, this)),
        _callback(cb){}
        ~AsyncLooper() { stop(); }
        void stop()
        {
            _stop = true;
            _cond_con.notify_all(); //唤醒所有的消费线程
            _thread.join();//等待工作线程的退出
        }
        void push(const char *data, size_t len) //
        {
            //1.无限扩容 -- 不安全  2.固定大小 -- 缓冲区满的时候阻塞
            std::unique_lock<std::mutex> lock(_mutex);

            if(_looper_type == AsyncType::ASYNC_SAFE)
            //条件变量控制,如果当前缓冲区的大小,足够放入数据,则可以添加数据
            _cond_pro.wait(lock, [&](){ return _pro_buf.writeAbleSize() >= len; });
            //将数据写入到缓冲区
            _pro_buf.push(data, len);
            //唤醒消费者对缓冲区中的数据进行处理
            _cond_con.notify_one();
        } 
    private:
        // 线程入口函数
        void threadEntry()
        {
            while(true)
            {
                // 为互斥锁设置一个声明周期,当缓冲区交换完,就解锁(并不对数据的处理过程加锁)
                {
                    //退出标志已设置,并且缓冲区中没有数据,则退出
                    if(_stop && _pro_buf.empty()) break;
                    
                    // 1.判断缓冲区是否有数据,有数据则交换,无则阻塞
                    std::unique_lock<std::mutex> lock(_mutex);
                    _cond_con.wait(lock, [&]() { return _stop || !_pro_buf.empty(); });
                    _con_buf.swap(_pro_buf);

                    if(_looper_type == AsyncType::ASYNC_SAFE)
                    // 唤醒生产者
                    _cond_pro.notify_all(); 
                }
                // 2.被唤醒后,对消费缓冲区进行处理
                _callback(_con_buf);
                // 3.初始化消费者缓冲区
                _con_buf.reset();
            }
        }
    private:
        Functor _callback; //具体对缓冲区数据处理的函数,由异步工作器的使用这传入  
    private:
        AsyncType _looper_type; 
        std::atomic<bool> _stop;                        // 是否停止
        Buffer _pro_buf;                   // 生产缓冲区
        Buffer _con_buf;                   // 消费缓冲区
        std::mutex _mutex;                 // 互斥锁
        std::condition_variable _cond_pro; // 生产条件变量
        std::condition_variable _cond_con; // 消费条件变量
        std::thread _thread;               // 异步工作器对应的工作线程
    };

}

到这本篇博客的内容就到此结束了。
如果觉得本篇博客内容对你有所帮助的话,可以点赞,收藏,顺便关注一下!
如果文章内容有错误,欢迎在评论区指正

在这里插入图片描述

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

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

相关文章

技术总结: PPT绘图

目录 写在前面参考文档技巧总结PPT中元素的连接立方体调整厚度调整图形中的文本3D 图片调整渐变中的颜色 写在前面 能绘制好一个好看的示意图非常重要, 在科研和工作中好的示意图能精准表达出自己的想法, 减少沟通的成本, 可视化的呈现也可以加强自身对系统的理解, 时间很久后…

Unity 协程(Coroutine)到底是什么?

参考链接&#xff1a;Unity 协程(Coroutine)原理与用法详解_unity coroutine-CSDN博客 为啥在Unity中一般不考虑多线程 因为在Unity中&#xff0c;只能在主线程中获取物体的组件、方法、对象&#xff0c;如果脱离这些&#xff0c;Unity的很多功能无法实现&#xff0c;那么多线程…

python lambda表达式(匿名函数)

lambda 表达式 在Python中&#xff0c;匿名函数&#xff08;也称为lambda函数&#xff09;是一种简洁的方式来定义小函数&#xff0c;这些函数可以在需要的地方直接定义和使用&#xff0c;而不需要使用def关键字来定义一个具有名称的函数。 lambda 函数是一种小型、匿名的、内…

vue+element ui上传图片到七牛云服务器

本来打算做一个全部都是前端完成的资源上传到七牛云的demo&#xff0c;但是需要获取token&#xff0c;经历了九九八十一难&#xff0c;最终还是选择放弃&#xff0c;token从后端获取&#xff08;springboot&#xff09;。如果你们有前端直接能解决的麻烦记得私我哦&#xff01;…

学习网络编程No.12【传输层协议之TCP】

引言&#xff1a; 北京时间&#xff1a;2024/2/27/14:12&#xff0c;不知过了多久终于在今天上午更新了新的文章。促使好久没有登录CSDN的我回关了几个近期关注我的人&#xff0c;然后过了没多久有人就通过二维码加了我的微信&#xff0c;他问了我一个问题&#xff0c;如何学好…

【S32DS报错】-7-程序进入HardFault_Handler,无法正常运行

【S32K3_MCAL从入门到精通】合集&#xff1a; S32K3_MCAL从入门到精通https://blog.csdn.net/qfmzhu/category_12519033.html 问题背景&#xff1a; 在S32DS IDE中使用PEmicro&#xff08;Multilink ACP&#xff0c;Multilink Universal&#xff0c;Multilink FX&#xff09…

3分钟,学会一个测试员必懂 Lambda 小知识!

今天再来给大家介绍下函数式接口和方法引用。 函数式接口 问&#xff1a;Lambda 表达式的类型是什么&#xff1f; 答&#xff1a;函数式接口 问&#xff1a;函数式接口是什么&#xff1f; 答&#xff1a;只包含一个抽象方法的接口&#xff0c;称为函数式接口 &#xff08;…

【图像版权】论文阅读:CRMW 图像隐写术+压缩算法

不可见水印 前言背景介绍ai大模型水印生成产物不可见水印CRMW 在保护深度神经网络模型知识产权方面与现有防御机制有何不同&#xff1f;使用图像隐写术和压缩算法为神经网络模型生成水印数据集有哪些优势&#xff1f;特征一致性训练如何发挥作用&#xff0c;将水印数据集嵌入到…

MSCKF5讲:后端代码分析

MSCKF5讲&#xff1a;后端代码分析 文章目录 MSCKF5讲&#xff1a;后端代码分析1 初始化initialize()1.1 加载参数1.2 初始化IMU连续噪声协方差矩阵1.3 卡方检验1.4 接收与订阅话题createRosIO() 2 IMU静止初始化3 重置resetCallback()4 featureCallback4.1 IMU初始化判断4.2 I…

YOLOv9独家改进|动态蛇形卷积Dynamic Snake Convolution与RepNCSPELAN4融合

专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;主力高效涨点&#xff01;&#xff01;&#xff01; 一、改进点介绍 Dynamic Snake Convolution是一种针对细长微弱的局部结构特征与复杂多变的全局形态特征设计的卷积模块。 RepNCSPELAN4是YOLOv9中的特…

【C语言】动态内存管理------常见错误,以及经典笔试题分析,柔性数组【图文详解】

欢迎来CILMY23的博客喔&#xff0c;本篇为【C语言】动态内存管理------常见错误&#xff0c;以及经典笔试题分析&#xff0c;柔性数组【图文详解】&#xff0c;感谢观看&#xff0c;支持的可以给个一键三连&#xff0c;点赞关注收藏。 前言 在了解完内存操作中最关键的一节---动…

怎样裁剪视频上下多余的部分?分享3个裁剪的工具!

在数字时代&#xff0c;视频已成为我们生活中不可或缺的一部分。无论是观看电影、制作个人vlog&#xff0c;还是进行专业的视频编辑&#xff0c;我们时常会遇到需要裁剪视频上下多余部分的情况。那么&#xff0c;如何进行视频裁剪呢&#xff1f;本文将为您详细介绍几种常用的视…

day46_Servlet

今日内容 0 复习昨日 1 Servlet基础 1.1 Servlet介绍 1.2 第一个Servlet 1.3 流程分析 1.4 使用细节 1.5 映射细节 1.6 生命周期 2 HttpServlet 2.1 HTTP请求、响应、状态码 2.2 GET和POST的区别 2.3 HttpServlet 0 复习昨日 1 maven创建-java项目结构 2 maven创建-javaweb项目…

16.网络游戏逆向分析与漏洞攻防-网络通信数据包分析工具-设计数据发送结构实现更复杂的数据发送

上一个内容&#xff1a;15.发送通信数据包至分析工具 码云地址&#xff08;master 分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/titan 码云版本号&#xff1a;f691a6a12ab49a711713f8ccdc8dd712c05826e9 代码下载地址&#xff0c;在 titan 目录下&…

京东商品优惠券API获取商品到手价

item_get_app-获得JD商品详情原数据 公共参数 请求地址: jd/item_get_app 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff09;[item_search,i…

Git 指令深入浅出【2】—— 分支管理

Git 指令深入浅出【2】—— 分支管理 分支管理1. 常用分支管理指令2. 合并分支合并冲突合并模式 3. 实战演习 分支管理 1. 常用分支管理指令 # 查看本地分支 git branch# 查看远程分支 git branch -r# 查看全部分支 git branch -aHEAD 指向的才是当前的工作分支 # 查看当前分…

源码的角度分析Vue2数据双向绑定原理

什么是双向绑定 我们先从单向绑定切入&#xff0c;其实单向绑定非常简单&#xff0c;就是把Model绑定到View&#xff0c;当我们用JavaScript代码更新Model时&#xff0c;View就会自动更新。那么双向绑定就可以从此联想到&#xff0c;即在单向绑定的基础上&#xff0c;用户更新…

win中删除不掉的文件,火绒粉碎删除亲测有效

看网上的 win R 然后终端输入什么删除的&#xff0c;照做了都没有删掉 有火绒的可以试试&#xff1a; 拖进去就删掉了 很好使

开源项目_代码生成项目介绍

1 CodeGeeX 系列 1.1 CodeGeeX 项目地址&#xff1a;https://github.com/THUDM/CodeGeeX 7.6k Star主要由 Python 编写深度学习框架是 Mindspore代码约 2.5W 行有 Dockerfile&#xff0c;可在本地搭建环境模型大小为 150 亿参数相对早期的代码生成模型&#xff0c;开放全部代…

【PCL】 (十六)点云距离图可视化

&#xff08;十六&#xff09;点云距离图可视化 以下代码实现点云及其对应距离图的可视化。 数据样例&#xff1a;sphere100.pcd range_image_visualization.cpp #include <iostream>#include <pcl/range_image/range_image.h> #include <pcl/io/pcd_io.h&g…