日志 - 客户端及服务端写法

2023-11-13

一、客户端

先来看一个日志类的实现方法,这个日志类也是代表着大多数客户端日志的主流写法:

log.h:

  1 #ifndef __LOG_H__
  2 #define __LOG_H__
  3 
  4 #include <stdio.h>
  5 
  6 //#ifdef _ZYL_LOG_
  7 #define LogInfo(...)     Log::GetInstance().AddLog("INFO", __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__)
  8 #define LogWarning(...)  Log::GetInstance().AddLog("WARNING", __FILE__, __LINE__, __FUNCTION__,  __VA_ARGS__)
  9 #define LogError(...)    Log::GetInstance().AddLog("ERROR", __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__)
//其中,...表示可变参数列表,__VA_ARGS__在预处理中,会被实际的参数集(实参列表)所替换。
 10 //#else 
 11 //#define LogInfo(...) (void(0))
 12 //#define LogError(...) (void(0))
 13 //#endif
 14 
 15 class Log
 16 {
 17 public:
 18         static Log& GetInstance();
 19 
 20         bool AddLog(const char* pszLevel, const char* pszFile, int lineNo, const char* pszFuncSig, char* pszFmt, ...);
 21 
 22 private:
 23         Log();
 24         ~Log();
 25         Log(const Log&);
 26         Log& operator=(const Log&);
 27 
 28 private:
 29         FILE* m_file;
 30 
 31 };
 32 
 33 #endif //!__LOG_H__

log.cpp : 

  1 #include <time.h>
  2 #include <stdio.h>
  3 #include <stdarg.h>
  4 #include <cstring>
  5 #include "log.h" 
  6       
  7 Log& Log::GetInstance()
  8 {     
  9         static Log log; 
 10         return log;
 11 } 
 12          
 13 bool Log::AddLog(const char* pszLevel, const char* pszFile, int lineNo, const char* pszFuncSig, char* pszFmt, ...)
//最后的pszFmt是一个固定的参数,用于表示格式化输出
 14 {
 15         if (m_file == NULL)
 16                 return false;
 17 
 18     char tmp[8192*10] = { 0 };
 19     va_list va;                                 //定义一个va_list型的变量,这个变量是指向参数的指针.
 20     va_start(va, pszFmt);                       //用va_start宏初始化变量,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数
 21     vsnprintf(tmp, 8192*10, pszFmt, va);        //linux环境下为vsnprintf(),vc6环境下为_vsnprintf()
 22     va_end(va);

//参数说明: int vsnprintf( char* buffer, std::size_t buf_size, const char* format, va_list vlist ); (C++11 起)
//char *str [out],把生成的格式化的字符串存放在这里.
//size_t size [in], str可接受的最大字符数[1]  (非字节数,UNICODE一个字符两个字节),防止产生数组越界.
//const char *format [in], 指定输出格式的字符串,它决定了你需要提供的可变参数的类型、个数和顺序。
//va_list ap [in], va_list变量. va:variable-argument:可变参数
//函数功能:将可变参数格式化输出到一个字符数组。

 23 
 24         time_t now = time(NULL);
 25         struct tm* tmstr = localtime(&now);
 26         char content[8192 * 10 + 256] = {0};
 27         snprintf(content, 8192*10+256, "[%04d-%02d-%02d %02d:%02d:%02d][%s][0x%04x][%s:%d %s]%s\    r\n",
 28                                 tmstr->tm_year + 1900,
 29                                 tmstr->tm_mon + 1,
 30                                 tmstr->tm_mday,
 31                                 tmstr->tm_hour,
 32                                 tmstr->tm_min,
 33                                 tmstr->tm_sec,
 34                 pszLevel,
 35                                 0,//GetCurrentThreadId(),
 36                 pszFile,
 37                 lineNo,
 38                 pszFuncSig,
 39                 tmp);
 40 
 41         if (fwrite(content, strlen(content), 1, m_file) != 1)
 42                 return false;
 43 
 44         fflush(m_file);
 45 
 46         return true;
 47 }
 48 
 49 Log::Log()
 50 {
 51         time_t now = time(NULL);
 52         struct tm* tmstr = localtime(&now);
 53         char filename[256];
 54         snprintf(filename, 256, "%04d%02d%02d%02d%02d%02d.runlog",
 55                                 tmstr->tm_year + 1900,
 56                                 tmstr->tm_mon + 1,
 57                                 tmstr->tm_mday,
 58                                 tmstr->tm_hour,
 59                                 tmstr->tm_min,
 60                                 tmstr->tm_sec);
 61 
 62         m_file = fopen(filename, "at+"); //"a+" = "at+" :打开或新建一个文本文件,可以读,但只允许在文件末尾追写
 63 }
 64 
 65 Log::~Log()
 66 {
 67         if (m_file != NULL)
 68                 fclose(m_file);
 69 }
 70 

testlog.cpp: 

  1 #include "log.h"
  2 
  3 int main(){
  4     LogInfo("test log info: %d, %c, %s", 1,'2',"3");
  5     LogWarning("test log warning");
  6     LogError("test log error");
  7 
  8     return 0;
  9 }

输出:生成文件 20181213025414.runlog

[2018-12-13 02:54:14][INFO][0x0000][testlog.cpp:4 main]test log info: 1, 2, 3
[2018-12-13 02:54:14][WARNING][0x0000][testlog.cpp:5 main]test log warning
[2018-12-13 02:54:14][ERROR][0x0000][testlog.cpp:6 main]test log error

这个日志类,每次输出一行,一行中输出时间、日志级别、线程id、文件名、行号、函数签名和自定义的错误信息。至此,宏  LogInfo、LogWarning 和 LogError 可以类似prinf使用格式化输出形式将内容输出到日志文件中去。

上述日志的实现虽然通用,但其局限性也只能用于客户端这样对性能和效率要求不高的程序(这里的性能和效率是相对于高并发高性能的服务器程序来说的,也就是说上述日志实现可用于大多数客户端程序,但不能用于高性能高并发的服务器程序)。那么上述程序存在什么问题?问题是效率低!

上述日志类实现,是在调用者线程中直接进行IO操作,相比较于高速的CPU,IO磁盘操作是很慢的,直接在某些工作线程(包括UI线程)写文件,程序执行速度太慢,尤其是当日志数据比较多的时候。这也就是服务器端日志和客户端日志的区别之一,客户端程序日志一般可以在直接在所在的工作线程写日志,因为这点性能和时间损失对大多数客户端程序来说,是可以忽略的,但对于要求高并发(例如并发量达百万级乃至千万级的系统)的服务器程序来说,单位时间内耗在磁盘写操作上的时间就相当可观了。

参考资料:

服务器编程心得(五)—— 如何编写高性能日志

std::vsnprintf

std::snprintf

va_list 、va_start、 va_arg、 va_end 使用说明


二、服务端

作者的做法是参考陈硕的muduo库的做法,使用一个队列,需要写日志时,将日志加入队列中,单独用另外一个专门的日志线程来写日志。

logger.h:

  1 #ifndef __LOGGER_H__
  2 #define __LOGGER_H__
  3 
  4 #include <string>
  5 #include <memory>
  6 #include <thread>
  7 #include <mutex>
  8 #include <condition_variable>
  9 #include <list>
 10 
 11 //struct FILE;
 12 
 13 #define LogInfo(...)        Logger::GetInstance().AddToQueue("INFO", __FILE__, __LINE__, __PRETT    Y_FUNCTION__, __VA_ARGS__)
 14 #define LogWarning(...)     Logger::GetInstance().AddToQueue("WARNING", __FILE__, __LINE__, __PR    ETTY_FUNCTION__, __VA_ARGS__)
 15 #define LogError(...)       Logger::GetInstance().AddToQueue("ERROR", __FILE__, __LINE__, __PRET    TY_FUNCTION__, __VA_ARGS__)
 16 
 17 class Logger
 18 {
 19 public:
 20     static Logger& GetInstance();
 21 
 22     void SetFileName(const char* filename);
 23     bool Start();
 24     void Stop();
 25 
 26     void AddToQueue(const char* pszLevel, const char* pszFile, int lineNo, const char* pszFuncSi    g, char* pszFmt, ...);
 27 
 28 private:
 29     Logger() = default;
 30     Logger(const Logger& rhs) = delete;
 31     Logger& operator =(Logger& rhs) = delete;
 32 
 33     void threadfunc();
 34 
 35 
 36 private:
 37     std::string                     filename_;
 38     FILE*                           fp_{};
 39     std::shared_ptr<std::thread>    spthread_;
 40     std::mutex                      mutex_;
 41     std::condition_variable         cv_;            //有新的日志到来的标识
 42     bool                            exit_{false};
 43     std::list<std::string>          queue_;
 44 };
 45 
 46 #endif //!__LOGGER_H__

logger.cpp: 

  1 #include "logger.h"
  2 #include <time.h>
  3 #include <stdio.h>
  4 #include <string>
  5 #include <memory>
  6 #include <functional>
  7 #include <stdarg.h>
  8 
  9 Logger& Logger::GetInstance()
 10 {
 11     static Logger logger;
 12     return logger;
 13 }
 14 
 15 void Logger::SetFileName(const char* filename)
 16 {
 17     filename_ = filename;
 18 }
 19 
 20 bool Logger::Start()
 21 {
 22     if (filename_.empty())
 23     {
 24         time_t now = time(NULL);
 25         struct tm* t = localtime(&now);
 26         char timestr[64] = { 0 };
 27         sprintf(timestr, "%04d%02d%02d%02d%02d%02d.imserver.log", t->tm_year + 1900, t->tm_mon +     1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
 28         filename_ = timestr;
 29     }
 30 
 31     fp_ = fopen(filename_.c_str(), "wt+");
 32     if (fp_ == NULL)
 33         return false;
 34 
 35     spthread_.reset(new std::thread(std::bind(&Logger::threadfunc, this)));
 36 
 37     return true;
 38 }
 39 
 40 void Logger::Stop()
 41 {
 42     exit_ = true;
 43     cv_.notify_one();
 44 
 45     //阻塞等待线程this结束
 46     spthread_->join();
 47 }
 48 
 49 void Logger::AddToQueue(const char* pszLevel, const char* pszFile, int lineNo, const char* pszFu    ncSig, char* pszFmt, ...)
 50 {
 51     char msg[256] = { 0 };
 52 
 53     va_list vArgList;
 54     va_start(vArgList, pszFmt);
 55     vsnprintf(msg, 256, pszFmt, vArgList);
 56     va_end(vArgList);
 57 
 58     time_t now = time(NULL);
 59     struct tm* tmstr = localtime(&now);
 60     char content[512] = { 0 };
 61     sprintf(content, "[%04d-%02d-%02d %02d:%02d:%02d][%s][0x%04x][%s:%d %s]%s\n",
 62                 tmstr->tm_year + 1900,
 63                 tmstr->tm_mon + 1,
 64                 tmstr->tm_mday,
 65                 tmstr->tm_hour,
 66                 tmstr->tm_min,
 67                 tmstr->tm_sec,
 68                 pszLevel,
 69                 std::this_thread::get_id(),
 70                 pszFile,
 71                 lineNo,
 72                 pszFuncSig,
 73                 msg);
 74     {
 75         std::lock_guard<std::mutex> guard(mutex_);
 76         queue_.emplace_back(content);
 77     }
 78 
 79     cv_.notify_one();
 80 }
 81 
 82 void Logger::threadfunc()
 83 {
 84     if (fp_ == NULL)
 85         return;
 86 
 87     while (!exit_)
 88     {
 89         //写日志
 90         std::unique_lock<std::mutex> guard(mutex_);
 91         while (queue_.empty())
 92         {
 93             if (exit_)
 94                 return;
 95                 
 96             cv_.wait(guard);
 97         }   
 98         
 99         //写日志
100         const std::string& str = queue_.front();
101         
102         fwrite((void*)str.c_str(), str.length(), 1, fp_);
103         fflush(fp_);
104         queue_.pop_front();
105     }   
106 }   

testlogger.cpp: 

调用 Logger::GetInstance().Start() 启动日志线程:

  1 #include "logger.h"
  2 #include "unistd.h"
  3 
  4 int main(){
  5     Logger::GetInstance().Start();  //启动日志线程
  6 
  7     LogInfo(" Test LogInfo : %d, %c, %s" , 1, '2', "3");
  8 
  9     sleep(3);
 10     Logger::GetInstance().Stop();
 11 
 12     return 0;
 13 }

生成日志文件 20181213042114.imserver.log ,内容为:

[2018-12-13 04:21:14][INFO][0x7a85f740][testlogger.cpp:7 int main()] Test LogInfo : 1, 2, 3


三、总结

1、对c++程序而已,最好整个程序(包括主程序和程序库)都使用相同的日志库,程序有一个整体的日志输出,而不要各个组件有各自的日志输出。从这个意义上讲,日志库是一个singleton
2、c++日志库的前端大体有两种风格:

  • C / JAVA 的 printf ( fmt , ... ) 风格,如  log_info("Rechieved %d bytes from %s" , len, getClientName.c_str());
  • C++ 的 stream << 风格, 如  LOG_INFO << "Rechieved " << len << " bytes from " << getClientName();  

3、对于分布式系统中的服务进程而言,日志的目的地只有一个:本地文件。往网络写日志消息是不靠谱的。以本地文件为日志的目的地,那么日志文件的滚动(rolling)是必需的,这样可以简化日志归档(archive)的实现。

4、日志库不能每条消息都flush硬盘,更不能每条日志都open\close文件,这样性能开销太大。改进方法:定期(默认3秒)将缓冲区内的日志消息 flush 硬盘。但程序奔溃时,后边的日志会丢失。

5、往往写日志的一个常见问题是,万一程序奔溃,那么最后若干条日志往往就丢失了(因为考虑到性能开销问题,日志库不能每条消息都flush硬盘,更不能每条日志都open\close文件)。改进方法:

  • 定期将缓冲区内的日志消息 flush 硬盘。这也就需要利用 buffer 对日志消息进行缓存
  • 每条内存中的日志消息都带有cookie(或者叫哨兵值/sentry),其值为某个函数的地址,这样通过core dump 文件中查找cookie就能找到尚未来得及写入磁盘的消息。

6、多线程异步日志(叫“非阻塞日志”似乎更准确)

保证线程安全(即多个线程可以并发写日志)的基础上,考虑性能。简单的做法有二:用一个全局mutex保护IO,或者每个线程单独写一个日志文件。但这两种方法效率堪忧,前者会造成全部线程枪一个锁,后者可能让业务线程阻塞在写磁盘操作上。因此,应该用一个背景线程负责收集日志消息,并写入日志文件,其他业务线程只管往这个“日志线程”发送日志消息,这称为“异步日志”。

(原因:在正常的实时业务处理流程中应该彻底避免磁盘IO,因为线程是复用的,阻塞意味着影响多个客户连接。) 

但“异步日志”可能出现日志消息堆积问题(典型的生产速度高于消费速度问题)。而同步日志则无此问题(阻塞IO自然就限制了前端的写入速度)。解决:直接丢掉多余的日志buffer,以腾出内存。

 

参考资料:

Linux多线程服务端编程:使用moduo C++网络库

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

日志 - 客户端及服务端写法 的相关文章

  • 使用Loki采集Kubernetes应用日志

    本文章旨在指导如何使用轻量级日志引擎Loki来采集Kubernetes的应用日志 并展示在grafana中 背景 最近我们公司的项目上了Kubernetes集群 产生的大量应用的日志需要采集起来 便于溯源问题 跟踪问题和及时报警 考虑到EL
  • epoll实现原理

    epoll的使用 epoll只有以下的三个系统函数调用 epoll create epoll ctl和epoll wait int epoll create int size 其中参数 1 size指明了生成描述符的最大范围 该函数返回一个
  • linux网络编程(一)

    1 linux的网络模型 linux使用的网络模型是TCP UP四层网络模型 主要由应用程序 传输层 网络层 网络接口层组成 与OSI七层模型不同 但是又相互对应 它们之间关系如下图 OSI模型的应用层 表示层 会话层对应着TCP IP模型
  • Linux网络编程socket错误分析

    转自 http aigo iteye com blog 1911134 socket错误码 EINTR 4 阻塞的操作被取消阻塞的调用打断 如设置了发送接收超时 就会遇到这种错误 只能针对阻塞模式的socket 读 写阻塞的socket时
  • [C++]-日志记录库SPDLog简介

    文章目录 spdlog库 日志记录槽sink 日志记录器logger 输出格式pattern 对齐方式 截断 字符串格式化fmt Format Specification spdlog使用 异常处理 logger 基础用法 stdout日志
  • tcp/udp建立连接并通信的过程

    基于TCP的socket编程的服务器程序流程如下 1 创建套接字 SOCKETsockSrv socket AF INET SOCK STREAM 0 2 将套接字绑定到本地地址和端口上 SOCKADDR INaddrSrv addrSrv
  • 基于升序链表的定时器及其简单应用

    Linux网络编程笔记 定时器 基于升序链表的定时器 这其实就是一个结点为 class util timer public util timer prev NULL next NULL 构造函数 public time t expire 任
  • 三八节致敬女程序员

    在历史中 日志既可以作为文献资料 又可以作为查案辅证 既可以作为航海记录 又可以作为海难追源 而随着时间的推移 科技的发展 时代逐渐赋予了日志更为广泛的内涵 当人们还在谈论历史日志 航海日志时 计算机时代便已悄然而至 开启了 日志数据 的大
  • 抓包工具Wireshark使用体会

    这两天在工作上遇到了一些问题 必须要用抓包工具来捕获手机端发送过来的数据包 分析其帧结构 以前虽然学习过网络知识 但是也从未接触过抓包工具Wireshark 迫于工作的压力 自己在摸索中学到了一些基本的使用方法 文件格式 pcap 帧排序
  • 三、Linux网络编程:Socket编程-网络模型

    3 Socket编程 网络模型 3 1 OSI七层模型 图解 每层的功能 模型 功能 物理层 比特流传输 数据链路层 网络控制 链路纠错 网络层 寻址 路由 传输层 建立主机端到端的连接 会话层 建立 维护和管理会话 表示层 格式转化 加密
  • printf() 函数不加 \n 无法及时输出

    for printf worker进程休息1秒 sleep 1 printf 函数末尾不加 n 就无法及时地将信息显示到屏幕上 这是因为行缓存 Windows上一般没有 类Unix上才有 行缓存 需要输出的数据不直接显示到终端 而是首先缓存
  • OSI七层模型与TCP/IP五层模型

    1 OSI open system interconnection 七层模型 OSI模型为开放式系统互联参考模型 是一个逻辑上的定义和规范 把网络从逻辑上划分为了7层 每一层都有相应的物理设备 OSI模型是一种框架性的设计方法 其主要功能是
  • 分配给套接字的IP地址与端口号

    文章目录 1 网络地址 Internet Address 2 网络地址分类与主机地址边界 3 用于区分套接字的端口号 IP 是 Internet Protocol 网络协议 的简写 是为收发网络数据而分配给计算机的值 端口号并非赋予计算机的
  • 【框架篇】Spring Boot 日志

    Spring Boot 日志 一 日志用途 尽管一个项目在没有日志记录的情况下可能能够正常运行 但是日志记录对于我们来说却是至关重要的 它存在以下功能 1 故障排查和调试 当项目出现异常或者故障时 日志记录可以快速帮助我们定位到异常的部分以
  • spring boot jar部署 控制台 日志 乱码

    spring boot jar部署 控制台 日志 乱码 问题描述 spring boot jar包部署 通过java jar 命令运行 jar文件 代码中通过变量log输出到控制台的中文 乱码 但是仅仅是在运行jar时才乱码 而在用ecli
  • 日志 - 客户端及服务端写法

    一 客户端 先来看一个日志类的实现方法 这个日志类也是代表着大多数客户端日志的主流写法 log h 1 ifndef LOG H 2 define LOG H 3 4 include
  • 字节序转换函数

    由于主机的千差万别 主机的字节序不能做到统一 但是对于网络上传输的变量 它们的值必须有一个统一的表示方法 网络字节序是指多字节变量在网络传输时的表示方法 网络字节序采用大端字节序的表示方法 所以小端字节序的系统通过网络传输变量的时候需要进行
  • TCP的keep-alive机制分析

    TCP中的keep alive机制 问题和解决思路 详细内容 缺陷分析 问题和解决思路 建立tcp连接后 双方互相发送信息 但是可能存在的情况是双方在处理数据 暂时并不会互相发送数据 那么这个时候如何判断双方连接是否依然正常 而没有意外断开
  • 530 Please login with USER and PASS.

    安装 npm install save hexo deployer ftpsync 配置 deploy type ftpsync host xx xx xx xx user bxu123123 pass xx11123 remote htd
  • 程序员怎样为自己工作?每天做的事终身受益。避免产品做完只拿工资走人,绑定客户绑定粉丝。

    工作即是创业 是所有人为你打工 打工什么是自己的 首先是跟随你的人才 你的小兄弟们 带着他们一起发财 把你身边的所有人所有的资源调动起来 都为你项目服务 不管是你的老板 你的客户 还是你竞争对手客户 大家都是同一个目标 赚钱 人设 跟着我有

随机推荐

  • zookeeper、dubbo、kafka简单了解

    1 zookeeper如何实现高可用 1 zookeeper 多台构成集群实现高可用 有三种角色群首 leader 追随者 follower 观察者 observer Leader作为整个ZooKeeper集群的主节点 负责响应所有对Zoo
  • 自动化设计-框架介绍

    3 框架介绍 由于软件测试的工作量很大 40 到60 的总开发时间 而又有很大部分适于自动化 因此 测试的改进会对整个开发工作的质量 成本和周期带来非常显著的效果 通过第二部分对Ruby Watir框架的介绍 下面我们正式进入自动化测试框架
  • XSS跨站脚本攻击

    以下是自己的一些见解与总结 若有不足或者错误的地方请各位指出 目录 1 简介 2 XSS攻击的危害包括 3 XSS攻击分类 4 XSS攻击实例分析 5 XSS漏洞修复 1 简介 跨站脚本 cross site script 为了避免与样式c
  • Python~ModuleNotFoundError: No module named ‘pydotplus‘

    错误代码 import pydotplus 错误原因 没有安装pydotplus库 解决方案 安装对应的库 pip install pydotplus 重新导入模块 import pydotplus
  • 获取内网IP地址

    工作中遇到一个需求 要求根据不同场地的IP地址 显示相应的大屏页面 首先要配置一下Chrome浏览器 1 在Chrome浏览器中输入 chrome flags 2 搜索 enable webrtc hide local ips with m
  • 量化交易,关于止损止盈的一点思考

    如何设置止损止盈指标才能达到 使损失基本稳定 让盈利充分攀升 止损位设置 如果当天可以卖出 止损位就是成本 1 X 如x设为5 就是股价跌5 就止损 一般就是5 略多一点损失 略多一点是因为可能需要降价保证抛出和交易成本 如果考虑交易成本
  • webSocket详解:技术原理+前后端实现

    webSocket详解 技术原理 前后端实现 一 webSocket技术原理 1 内容简介 websocket就是通过服务器向客户端推送消息 客户端也可以主动向服务器发送消息 是真正的双向平等对话 是一种长连接 只需要通过一次请求进行初始化
  • IDEA设置

    目录 目录 双击idea时默认不打开最后一次的项目 设置黑色 白色主图 编辑 通过CTRL 鼠标滚轮设置字体大小 自动导入包 显示行号与方法分隔符 打开类多后 在多行显示 文件编码UTF 8设定 打开文件按顺序展示 推荐的插件 双击idea
  • 如何用多线程执行 unittest 测试用例实现方案

    前言 使用python做过自动化测试的小伙伴 想必都知道unittest和pytest这两个单元测试框架 其中unittest是python的官方库 功能相对于pytest来要逊色不少 但是uniitest使用上手简单 也受到的很多的小伙伴
  • 企业应该选择哪种区块链

    随着探索如何把区块链应用在各种场景 许多人就想到 也许不需要全世界的人共同参与 也不需要挖矿 我们只需要用到区块链的可信任 可追溯特性 通过较少节点达到拜占庭将军容错 于是私有链就诞生了 但私有链仍是中心化的 难以维持去中心化的优势 因此又
  • webdriver版本不匹配,重新下载webdriver后不知道应该放在哪个文件夹

    1 从官网上面按照对应的版本下载了对应浏览器的webdriver版本 我的是chrome windows 113开头的版本 2 下载并解压之后发现有很多个webdriver exe的程序 正确步骤是解压后复制exe文件放在原来的文件夹即可
  • Ubuntu根目录文件作用分析

    Ubuntu Linux的文件结构与Windows的文件结构不同 Windows将硬盘分成 等盘 也就是分成这些分区 而Linux操作系统不是把硬盘分 成这样的分区 它有一个根目录 用 表示 一个目录就相当于一个文件夹 根目录就相当于Lin
  • 360周鸿祎:互联网好产品六字法则——刚需、痛点、高频

    如何找到好的产品 它必须满足三个条件 刚需 痛点 高频 6月6号 奇虎360创始人董事长兼CEO周鸿祎走上颠覆式创新研习社的讲台 以他的产品经历 带来移动互联网产品观 干货满满 全程无尿点 课程实录分为上 下两部分 研习社根据演讲整理 未经
  • vue Antd单独隐藏Modal.confirm(this.$confirm方式)对话框的默认ok或cancel按钮

    有时候我们需要单独隐藏Modal对话框的默认确定或取消按钮 设置 footer null 会把两个按钮都隐藏 Antd有提供两个参数用于单独修改确定 取消按钮 对于确定按钮 设置 ok button props style display
  • STM32学习记录——74HC595四位数码管显示

    数模管作为STM32的一个重要外设 由于其成本低 稳定 被用于许多场景中 本篇文章来介绍下四位数码管的使用方法 数码管显示 一 数码管的分类 二 74HC595芯片 串入并出 三 原理图 四 代码主要操作 五 代码分析 1 void HC5
  • PyCharm安装

    lt 一 gt PyCharm安装 下载地址 http www jetbrains com pycharm download section windows professional 表示专业版 community 是社区版 推荐安装社区版
  • CSS3 Flexbox轻松实现元素的水平居中和垂直居中

    网上有很多关于Flex的教程 对于Flex的叫法也不一 有的叫Flexbox 有的叫Flex 其实这两种叫法都没有错 只是Flexbox旧一点 而Flex是刚出来不久的东西而已 为了方便说明 赶上新技术 下面我就把这种布局叫Flex布局 元
  • Unity --- 动画脚本

    1 什么是动画 在Unity中 首先如果游戏物体想要在场景中动起来的话 就必须使其对应组件中的属性发生合适的变化 而将属性的变化过程保存下来后形成的就是动画 以后如果想播放动画的话 只需要调用这段保存下来的属性变化就可以了 二 基础知识部分
  • 内存管理之一__align字节对齐

    转 http www cnblogs com ye moooooo p 4601189 html 一 什么是字节对齐 为什么要对齐 现代计算机中内存空间都是按照byte划分的 从理论上讲似乎对任何类型的变量的访问可以从任何地址开始 但实际情
  • 日志 - 客户端及服务端写法

    一 客户端 先来看一个日志类的实现方法 这个日志类也是代表着大多数客户端日志的主流写法 log h 1 ifndef LOG H 2 define LOG H 3 4 include