152 Linux C++ 通讯架构实战7 ,makefile编写改成for cpp,读配置文件,内存泄漏查找,设置标题实战

news2024/11/28 4:26:57

读写配置文件代码实战。nginx.conf

一个项目要启动,需要配置很多信息,第一项就是学习如何配置一个项目

nginx.conf的内容

#是注释行,
#每个有效配置项用 等号 处理,等号前不超过40个字符,等号后不超过400个字符;

 
#[开头的表示组信息,也等价于注释行
[Socket]
ListenPort = 5678    

DBInfo = 127.0.0.1;1234;myr;123456;mxdb_g

我们的目的是:

1.将 ListenPort的值取出来

2.将DBInfo的值取出来

ngx_c_conf.h


#ifndef __NGX_CONF_H__
#define __NGX_CONF_H__

#include <vector>

#include "ngx_global.h"  //一些全局/通用定义

//类名可以遵照一定的命名规则规范,比如老师这里,第一个字母是C,后续的单词首字母大写
class CConfig
{
//---------------------------------------------------
//这段代码老师在《c++从入门到精通》 多线程这章老师提过 单例设计模式,就是如下这些代码,大家即便没学过,也可以现在学
private:
	CConfig();
public:
	~CConfig();
private:
	static CConfig *m_instance;

public:	
	static CConfig* GetInstance() 
	{	
		if(m_instance == NULL)
		{
			//锁
			if(m_instance == NULL)
			{					
				m_instance = new CConfig();
				static CGarhuishou cl; 
			}
			//放锁		
		}
		return m_instance;
	}	
	class CGarhuishou  //类中套类,用于释放对象
	{
	public:				
		~CGarhuishou()
		{
			if (CConfig::m_instance)
			{						
				delete CConfig::m_instance;
				CConfig::m_instance = NULL;				
			}
		}
	};
//---------------------------------------------------
public:
    bool Load(const char *pconfName); //装载配置文件
	const char *GetString(const char *p_itemname);
	int  GetIntDefault(const char *p_itemname,const int def);

public:
	std::vector<LPCConfItem> m_ConfigItemList; //存储配置信息的列表

};

#endif

ngx_c_conf.cxx


//系统头文件放上边
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>

//自定义头文件放下边,因为g++中用了-I参数,所以这里用<>也可以
#include "ngx_func.h"     //函数声明
#include "ngx_c_conf.h"   //和配置文件处理相关的类,名字带c_表示和类有关

//静态成员赋值
CConfig *CConfig::m_instance = NULL;

//构造函数
CConfig::CConfig()
{		
}

//析构函数
CConfig::~CConfig()
{    
	std::vector<LPCConfItem>::iterator pos;	
	for(pos = m_ConfigItemList.begin(); pos != m_ConfigItemList.end(); ++pos)
	{		
		delete (*pos);
	}//end for
	m_ConfigItemList.clear(); 
}

//装载配置文件
bool CConfig::Load(const char *pconfName) 
{   
    FILE *fp;
    fp = fopen(pconfName,"r");
    if(fp == NULL)
        return false;

    //每一行配置文件读出来都放这里
    char  linebuf[501];   //每行配置都不要太长,保持<500字符内,防止出现问题
    
    //走到这里,文件打开成功 
    while(!feof(fp))  //检查文件是否结束 ,没有结束则条件成立
    {    
        //大家要注意老师的写法,注意写法的严密性,商业代码,就是要首先确保代码的严密性
        if(fgets(linebuf,500,fp) == NULL) //从文件中读数据,每次读一行,一行最多不要超过500个字符 
            continue;

        if(linebuf[0] == 0)
            continue;

        //处理注释行 
        if(*linebuf==';' || *linebuf==' ' || *linebuf=='#' || *linebuf=='\t'|| *linebuf=='\n')
			continue;
        
    lblprocstring:
        //屁股后边若有换行,回车,空格等都截取掉
		if(strlen(linebuf) > 0)
		{
            //10 换行,13回车,32 是空格
			if(linebuf[strlen(linebuf)-1] == 10 || linebuf[strlen(linebuf)-1] == 13 || linebuf[strlen(linebuf)-1] == 32) 
			{
				linebuf[strlen(linebuf)-1] = 0;
				goto lblprocstring;
			}		
		}
        if(linebuf[0] == 0)
            continue;
        if(*linebuf=='[') //[开头的也不处理
			continue;

        //这种 “ListenPort = 5678”走下来;
        char *ptmp = strchr(linebuf,'=');
        if(ptmp != NULL)
        {
            LPCConfItem p_confitem = new CConfItem;                    //注意前边类型带LP,后边new这里的类型不带
            memset(p_confitem,0,sizeof(CConfItem));
            strncpy(p_confitem->ItemName,linebuf,(int)(ptmp-linebuf)); //等号左侧的拷贝到p_confitem->ItemName
            strcpy(p_confitem->ItemContent,ptmp+1);                    //等号右侧的拷贝到p_confitem->ItemContent

            Rtrim(p_confitem->ItemName);
			Ltrim(p_confitem->ItemName);
			Rtrim(p_confitem->ItemContent);
			Ltrim(p_confitem->ItemContent);

            //printf("itemname=%s | itemcontent=%s\n",p_confitem->ItemName,p_confitem->ItemContent);            
            m_ConfigItemList.push_back(p_confitem);  //内存要释放,因为这里是new出来的 
        } //end if
    } //end while(!feof(fp)) 

    fclose(fp); //这步不可忘记
    return true;
}

//根据ItemName获取配置信息字符串,不修改不用互斥
const char *CConfig::GetString(const char *p_itemname)
{
	std::vector<LPCConfItem>::iterator pos;	
	for(pos = m_ConfigItemList.begin(); pos != m_ConfigItemList.end(); ++pos)
	{	
		if(strcasecmp( (*pos)->ItemName,p_itemname) == 0)
			return (*pos)->ItemContent;
	}//end for
	return NULL;
}
//根据ItemName获取数字类型配置信息,不修改不用互斥
int CConfig::GetIntDefault(const char *p_itemname,const int def)
{
	std::vector<LPCConfItem>::iterator pos;	
	for(pos = m_ConfigItemList.begin(); pos !=m_ConfigItemList.end(); ++pos)
	{	
		if(strcasecmp( (*pos)->ItemName,p_itemname) == 0)
			return atoi((*pos)->ItemContent);
	}//end for
	return def;
}



nginx.cxx



#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include "ngx_c_conf.h"  //和配置文件处理相关的类,名字带c_表示和类有关
#include "ngx_signal.h"
#include "ngx_func.h"    //各种函数声明

//和设置标题有关的全局量
char **g_os_argv;            //原始命令行参数数组,在main中会被赋值
char *gp_envmem = NULL;      //指向自己分配的env环境变量的内存
int  g_environlen = 0;       //环境变量所占内存大小

int main(int argc, char *const *argv)
{             
    g_os_argv = (char **) argv;
    ngx_init_setproctitle();    //把环境变量搬家
    
    //我们在main中,先把配置读出来,供后续使用 
    CConfig *p_config = CConfig::GetInstance(); //单例类
    if(p_config->Load("nginx.conf") == false) //把配置文件内容载入到内存
    {
        printf("配置文件载入失败,退出!\n");
        exit(1);
    }

    
    //获取配置文件信息的用法    
    int port = p_config->GetIntDefault("ListenPort",0); //0是缺省值
    printf("port=%d\n",port);
    const char *pDBInfo = p_config->GetString("DBInfo");
    if(pDBInfo != NULL)
    {
       printf("DBInfo=%s\n",pDBInfo);
    }
    
    for(;;)
    {
        sleep(1); //休息1秒
        printf("休息1秒\n");
    }


    printf("程序退出,再见!\n");
    return 0;
}


运行结果

hunandede@hunandede-virtual-machine:/mnt/hgfs/linux/4-2/nginx$ ./nginx
port=5678
DBInfo=127.0.0.1;1234;myr;123456;mxdb_g
休息1秒
休息1秒

内存泄漏查找工具valgrind 以及使用

使用环境: linux 下的工具,

安装:

sudo apt-get install valgrind

使用:

valgrind --tool=memcheck --leak-check=full --show-reachable=yes ./nginx

//Valgrind:帮助程序员寻找程序里的bug和改进程序性能的工具集。擅长是发现内存的管理问题;
     //里边有若干工具,其中最重要的是Memcheck(内存检查)工具,用于检查内存的泄漏;
    //(2.1)memcheck的基本功能,能发现如下的问题;
    //a)使用未初始化的内存
    //b)使用已经释放了的内存
    //c)使用超过malloc()分配的内存
    //d)对堆栈的非法访问
    //e)申请的内存是否有释放*****
    //f)malloc/free,new/delete申请和释放内存的匹配
    //g)memcpy()内存拷贝函数中源指针和目标指针重叠;

    //(2.2)内存泄漏检查示范
    //所有应该释放的内存,都要释放掉,作为服务器程序开发者,要绝对的严谨和认真
    //格式:
    //valgrind --tool=memcheck  一些开关      可执行文件名
    //--tool=memcheck :使用valgrind工具集中的memcheck工具
    //--leak-check=full : 指的是完全full检查内存泄漏
    //--show-reachable=yes :是显示内存泄漏的地点
    //--trace-children = yes :是否跟入子进程
    //--log-file=log.txt:讲调试信息输出到log.txt,不输出到屏幕
    //最终用的命令:
    //valgrind --tool=memcheck --leak-check=full --show-reachable=yes ./nginx
    //查看内存泄漏的三个地方:
    //(1) 9 allocs, 8 frees  差值是1,就没泄漏,超过1就有泄漏
    //(2)中间诸如: by 0x401363: CConfig::Load(char const*) (ngx_c_conf.cxx:77)和我们自己的源代码有关的提示,就要注意;
    //(3)LEAK SUMMARY:definitely lost: 1,100 bytes in 2 blocks

测试结果分析

注意这里:==3236==   total heap usage: 10 allocs, 9 frees, 79,021 bytes allocated

hunandede@hunandede-virtual-machine:/mnt/hgfs/linux$ cd 4-2/nginx/
hunandede@hunandede-virtual-machine:/mnt/hgfs/linux/4-2/nginx$ valgrind --tool=memcheck --leak-check=full --show-reachable=yes ./nginx
==3236== Memcheck, a memory error detector
==3236== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==3236== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==3236== Command: ./nginx
==3236== 
port=5678
DBInfo=127.0.0.1;1234;myr;123456;mxdb_g
程序退出,再见!
==3236== 
==3236== HEAP SUMMARY:
==3236==     in use at exit: 72,704 bytes in 1 blocks
==3236==   total heap usage: 10 allocs, 9 frees, 79,021 bytes allocated
==3236== 
==3236== 72,704 bytes in 1 blocks are still reachable in loss record 1 of 1
==3236==    at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==3236==    by 0x4EC3EFF: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==3236==    by 0x40106F9: call_init.part.0 (dl-init.c:72)
==3236==    by 0x401080A: call_init (dl-init.c:30)
==3236==    by 0x401080A: _dl_init (dl-init.c:120)
==3236==    by 0x4000C69: ??? (in /lib/x86_64-linux-gnu/ld-2.23.so)
==3236== 
==3236== LEAK SUMMARY:
==3236==    definitely lost: 0 bytes in 0 blocks
==3236==    indirectly lost: 0 bytes in 0 blocks
==3236==      possibly lost: 0 bytes in 0 blocks
==3236==    still reachable: 72,704 bytes in 1 blocks
==3236==         suppressed: 0 bytes in 0 blocks
==3236== 
==3236== For counts of detected and suppressed errors, rerun with: -v
==3236== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
hunandede@hunandede-virtual-machine:/mnt/hgfs/linux/4-2/nginx$ 

设置标题代码实战

我们这样执行写好的代码,就是说带了参数 aa bb cc

./nginx aa bb cc

hunandede@hunandede-virtual-machine:/mnt/hgfs/linux/4-2/nginx$ ./nginx aa bb cc
port=5678
DBInfo=127.0.0.1;1234;myr;123456;mxdb_g
休息1秒
休息1秒
休息1秒
休息1秒

然后使用 如下命令查看

ps -eo pid,ppid,sid,tty,pgrp,comm,stat,cmd | grep -E 'bash|PID|nginx'


 

hunandede@hunandede-virtual-machine:~$ ps -eo pid,ppid,sid,tty,pgrp,comm,stat,cmd | grep -E 'bash|PID|nginx'
   PID   PPID    SID TT         PGRP COMMAND         STAT CMD
  2652   2651   2652 pts/9      2652 bash            Ss   -bash
  3326   3325   3326 pts/10     3326 bash            Ss   -bash
  3348   2652   2652 pts/9      3348 nginx           S+   ./nginx aa bb cc
  3371   3326   3326 pts/10     3370 grep            R+   grep --color=auto -E bash|PID|nginx
hunandede@hunandede-virtual-machine:~$ 

注意下图红色的部分,实际上我们执行的该可执行程序的 加上参数

在ps中最终会显示在这里。

改动目标:将启动的可执行程序和其参数,都替换成我们想要的字符串

我们这里的想法就是改动这块。

原理和实现思路分析


    //argc:命令行参数的个数
    //argv:是个数组,每个数组元素都是指向一个字符串的char *,里边存储的内容是所有命令行参数;
    例如我们在执行nginx的时候,加上了3个参数

./nginx -v -s 5

那么对应的argc 和 argv数组的每个值如下:
       //argc = 4
       //argv[0] = ./nginx    ----指向的就是可执行程序名: ./nginx
       //argv[1] = -v
       //argv[2] = -s
       //argv[3] = 5

    //argv内存之后,接着连续的就是环境变量参数信息内存【是咱们这个可执行程序执行时有关的所有环境变量参数信息】
      //可以通过一个全局的environ[char **]就可以访问
    //environ内存和argv内存紧紧的挨着


   

我们先来验证自己的猜测是否正确 。

int main(int argc, char *const *argv)
{
    cout << "验证argc 和 argv 以及 环境变量的存储是否是我们理解的布局 start" << endl;

    cout << "参数个数argc的值为:" << argc << endl;
    for (int i = 0; i < argc; ++i)
    {
        //这里注意老师的写法,在c中打印argv ,也就是 char *argv[]中的地址的写法,argv是一个指针数组,本质上是一个数组,这个数组的每一项都是一个指针
        // cout << "myargv[" << i << "] 的地址为:" << *(&argv[i]) << endl;//myargv[0] 的地址为:./nginx
        // cout << "myargv[" << i << "] 的1地址为:" << argv[i] << endl;//myargv[0] 的1地址为:./nginx
        // cout << "myargv[" << i << "] 的2地址为:" << *argv[i] << endl;myargv[0] 的2地址为:.
        printf("argv[%d]地址=%x    ", i, (unsigned int)((unsigned long)argv[i]));//由于数组中的每一项都是指针,我们要先拿到数组里面的 指针,由于指针和long 在32位上占用 4个字节,在64位上占用8个字节,因此可以先将 指针转成 unsigned long,由于打印的时候需要的%x,因此在转成(unsigned int)
        //这里可以思考一个额外的问题,这里将指针 从 unsigned long 强行转成 unsigned int 会不会有丢失呢?

        printf("argv[%d]内容=%s\n", i, argv[i]);
    }
    // 下边环境变量随便打两个,只要打印的地址和上面的地址能够连贯,就说明我们关于 参数的地址和环境变量的地址挨着的判断是正确的
    for (int i = 0; i < 2; ++i)
    {
        printf("evriron[%d]地址=%x    ", i, (unsigned int)((unsigned long)environ[i]));
        printf("evriron[%d]内容=%s\n", i, environ[i]);
    }
    cout << "验证argc 和 argv 以及 环境变量的存储是否是我们理解的布局 end" << endl;
    //这里注意的是,这段代码在vs2017上和 在linux上执行的结果不同,在linux的内存图确实如我们所想,但是在vs2017上,是environ的内存在前面,argv的内存在后边
}

//这里注意的是,这段代码在vs2017上和 在linux上执行的结果不同,在linux的内存图确实如我们所想,但是在vs2017上,是environ的内存在前面,argv的内存在后边

注意这两行

argv[3]地址=93a03693    argv[3]内容=cc
evriron[0]地址=93a03696    evriron[0]内容=LC_PAPER=zh_CN.UTF-8
 

evrion[0]的地址刚好紧挨着 argv[3],这说明我们的猜想是对的

hunandede@hunandede-virtual-machine:/mnt/hgfs/linux/4-2/nginx$ ./nginx aa bb cc
验证argc 和 argv 以及 环境变量的存储是否是我们理解的布局 start
参数个数argc的值为:4
argv[0]地址=93a03685    argv[0]内容=./nginx
argv[1]地址=93a0368d    argv[1]内容=aa
argv[2]地址=93a03690    argv[2]内容=bb
argv[3]地址=93a03693    argv[3]内容=cc
evriron[0]地址=93a03696    evriron[0]内容=LC_PAPER=zh_CN.UTF-8
evriron[1]地址=93a036ab    evriron[1]内容=LC_ADDRESS=zh_CN.UTF-8
验证argc 和 argv 以及 环境变量的存储是否是我们理解的布局 end

那么在linux上的原理图大致如下:

实现思路


(1)重新分配一块内存,用来保存environ中的内容;

    //第一步 环境变量原先的地址
    cout<<"环境变量原先的地址"<<endl;
    for (int i = 0; environ[i]; i++)
    {
        printf("evriron[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));
        printf("evriron[%d]内容=%s\n" ,i,environ[i]);
    }
    printf("--------------------------搬家中------------------------------\n");

    //让环境变量搬家
    g_os_argv = (char **)argv;
    ngx_init_setproctitle(); // 把环境变量搬家

    cout<<"环境变量搬家后的地址"<<endl;
    for (int i = 0; environ[i]; i++)
    {
        printf("evriron[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));
        printf("evriron[%d]内容=%s\n" ,i,environ[i]);
    }

其核心是这个方法

    ngx_init_setproctitle(); // 把环境变量搬家

//设置可执行程序标题相关函数:分配内存,并且把环境变量拷贝到新内存中来
void ngx_init_setproctitle()
{    
    int i;
    //统计环境变量所占的内存。注意判断方法是environ[i]是否为空作为环境变量结束标记
    for (i = 0; environ[i]; i++) 
    {
        g_environlen += strlen(environ[i]) + 1; //+1是因为末尾有\0,是占实际内存位置的,要算进来
    } //end for

    //这里无需判断penvmen == NULL,有些编译器new会返回NULL,有些会报异常,但不管怎样,如果在重要的地方new失败了,你无法收场,让程序失控崩溃,助你发现问题为好; 
    gp_envmem = new char[g_environlen]; 
    memset(gp_envmem,0,g_environlen);  //内存要清空防止出现问题

    char *ptmp = gp_envmem;

    //把原来的内存内容搬到新地方来
    for (i = 0; environ[i]; i++) 
    {
        size_t size = strlen(environ[i])+1 ; //不要拉下+1,否则内存全乱套了,因为strlen是不包括字符串末尾的\0的
        strcpy(ptmp,environ[i]);      //把原环境变量内容拷贝到新地方【新内存】
        environ[i] = ptmp;            //然后还要让新环境变量指向这段新内存
        ptmp += size;
    }
    return;
}

(2)修改argv[0]所指向的内存;

    // 第二步,设置标题代码title,我要保证所有命令行参数我都不 用了,如果还要用,那么建议存储起来,并告知其他模块的owner,如果要使用启动程序时的参数,请到存储的地方去拿。这个要其他用到的owner sync到位

    ngx_setproctitle("nginx: my master process");

//设置可执行程序标题
void ngx_setproctitle(const char *title)
{
    //我们假设,所有的命令 行参数我们都不需要用到了,可以被随意覆盖了;
    //注意:我们的标题长度,不会长到原始标题和原始环境变量都装不下,否则怕出问题,不处理
    
    //(1)计算新标题长度
    size_t ititlelen = strlen(title); 

    //(2)计算总的原始的argv那块内存的总长度【包括各种参数】
    size_t e_environlen = 0;     //e表示局部变量
    for (int i = 0; g_os_argv[i]; i++)  
    {
        e_environlen += strlen(g_os_argv[i]) + 1;
    }

    size_t esy = e_environlen + g_environlen; //argv和environ内存总和
    if( esy <= ititlelen)
    {
        //你标题多长啊,我argv和environ总和都存不下?注意字符串末尾多了个 \0,所以这块判断是 <=【也就是=都算存不下】
        return;
    }

    //空间够保存标题的,够长,存得下,继续走下来    

    //(3)设置后续的命令行参数为空,表示只有argv[]中只有一个元素了,这是好习惯;防止后续argv被滥用,因为很多判断是用argv[] == NULL来做结束标记判断的;
    g_os_argv[1] = NULL;  

    //(4)把标题弄进来,注意原来的命令行参数都会被覆盖掉,不要再使用这些命令行参数,而且g_os_argv[1]已经被设置为NULL了
    char *ptmp = g_os_argv[0]; //让ptmp指向g_os_argv所指向的内存
    strcpy(ptmp,title);
    ptmp += ititlelen; //跳过标题

    //(5)把剩余的原argv以及environ所占的内存全部清0,否则会出现在ps的cmd列可能还会残余一些没有被覆盖的内容;
    size_t cha = esy - ititlelen;  //内存总和减去标题字符串长度(不含字符串末尾的\0),剩余的大小,就是要memset的;
    memset(ptmp,0,cha);  
    return;
}

测试结果。

在一个终端让其跑起来

hunandede@hunandede-virtual-machine:/mnt/hgfs/linux/4-2/nginx$ ./nginx aa bb cc

在另一个终端测试看是否改动成功,我们在代码中是写成替换成"my master process"
 

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

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

相关文章

时序分解 | Matlab实现GSWOA-VMD改进鲸鱼优化算法优化变分模态分解时间序列信号分解

时序分解 | Matlab实现GWO-CEEMDAN基于灰狼算法优化CEEMDAN时间序列信号分解 目录 时序分解 | Matlab实现GWO-CEEMDAN基于灰狼算法优化CEEMDAN时间序列信号分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现GSWOA-VMD改进鲸鱼优化算法优化变分模态分解时间序…

《编程菜鸟学 Python 数据分析》让工作自动化起来!

随着我国企业数字化和信息化的深入&#xff0c;企业对办公自动化的效率和灵活性要求越来越高。Python作为一种开源的软件应用开发方式&#xff0c;通过提供强大丰富的库文件包&#xff0c;极大地简化了应用开发过程&#xff0c;降低了技术门槛。Python开发有哪些优势、挑战以及…

NB-IOT——浅谈NB-IOT及模块测试

浅谈NB-IOT及模块基本使用测试 介绍什么是NB-IOT&#xff1f;NB-IOT的特点 使用准备基本使用 总结 介绍 什么是NB-IOT&#xff1f; NB-IoT&#xff0c;即窄带物联网&#xff08;Narrowband Internet of Things&#xff09;&#xff0c;是一种低功耗广域物联网&#xff08;LPW…

Python学习从0到1 day20 第二阶段 面向对象 ② 封装

缘分 朝生暮死犹如露水 —— 24.4.1 学习目标&#xff1a; 1.理解封装的概念 2.掌握私有成员的使用 一、面向对象三大特性&#xff1a; 面向对象编程&#xff0c;是许多编程语言都支持的一种编程思想 简单理解是&#xff1a;基于模板&#xff08;类&#xff09;去创建实体&…

Lua 和 Love 2d 教程 二十一点朴克牌 (上篇lua源码)

GitCode - 开发者的代码家园 Lua版完整原码 规则 庄家和玩家各发两张牌。庄家的第一张牌对玩家是隐藏的。 玩家可以拿牌&#xff08;即拿另一张牌&#xff09;或 停牌&#xff08;即停止拿牌&#xff09;。 如果玩家手牌的总价值超过 21&#xff0c;那么他们就爆掉了。 面牌…

Tulsimer® CH-99硼选择吸附树脂在超纯水除硼领域的卓越应用与优势

超纯水&#xff08;UPW&#xff09;是一种高度纯净的水体形态&#xff0c;通过一系列精密的净化步骤&#xff0c;几乎去除了所有非氢氧成分&#xff0c;包括但不限于微生物、有机污染物及矿物质微量元素。其制备流程涵盖了预处理、反渗透、离子交换、蒸馏、紫外线或超滤等多种高…

成都三环旁的数字影像文创产业园,建设热度高,创新活力足

在成都市金牛区的“九里九园”簇群建设中心区域&#xff0c;一座充满活力的国际数字影像产业园正在崛起。这个成都数字产业园不仅建设热度高涨&#xff0c;更以其创新活力吸引了无数目光。作为数字产业的重要一环&#xff0c;它正在为成都乃至全球的数字文创产业描绘出一幅充满…

plasmo内容UI组件层级过高导致页面展示错乱

我使用plasmo写了一个行内样式的UI组件&#xff0c;但是放到页面上之后&#xff0c;会和下拉组件出现层级错乱&#xff0c;看了一下样式&#xff0c;吓我一跳&#xff1a;层级竟然设置的如此之高 所以就需要将层级设置低一点&#xff1a; #plasmo-shadow-container {z-index: …

vue+element ui实现表单组件的封装

效果图&#xff1a; 主要是使用vue elmentUi 的from的基础上进行封装 使用改组件&#xff0c;是需要自定义从父组件传入一下字段表单字段 export const topicTypeMainTaskEdit: any new Map([// 主任务可编辑状态[feasibleInstructions, // 督办件[{value: documentNum…

基于单片机智能数字温度采集报警器系统设计

**单片机设计介绍&#xff0c;基于单片机智能数字温度采集报警器系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机智能数字温度采集报警器系统设计的核心目标是通过单片机实现温度的实时采集、显示以及超温报警…

Nexpose v6.6.244 for Linux Windows - 漏洞扫描

Nexpose v6.6.244 for Linux & Windows - 漏洞扫描 Rapid7 Vulnerability Management, Release Mar 27, 2024 请访问原文链接&#xff1a;https://sysin.org/blog/nexpose-6/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.o…

Windows启动动画的小秘密

>网管小贾 / sysadm.cc 员工求领导办事&#xff0c;司空见惯&#xff0c;没啥稀奇。 领导求员工办事&#xff0c;想都别想&#xff0c;怎么可能。 然而这世上之事万里总有个一&#xff0c;这不&#xff0c;就在上个礼拜&#xff0c;开天辟地头一遭&#xff0c;让我给碰上…

Redis开源协议调整,我们怎么办?

2024年3月20日, Redis官方宣布&#xff0c;从 Redis 7.4版本开始&#xff0c;Redis将获得源可用许可证 ( RSALv2 ) 和服务器端公共许可证 ( SSPLv1 ) 的双重许可&#xff0c;时间点恰逢刚刚完成最新一轮融资&#xff0c;宣布的时机耐人寻味。 Redis协议调整&#xff0c;对云计算…

需要本地后端的真机调试-微信

打开和修改IP改为电脑与手机同一局域网的 不知道这个要不要

交通标志识别项目 | 基于Tensorflow+SSD实现道路交通标志识别

项目应用场景 面向智能驾驶或自动驾驶场景道路道路交通标志的识别&#xff0c;在交通标志识别的基础上为下一步的智能决策提供前提 项目效果&#xff1a; 项目细节 > 具体参见项目 README.md (1) 安装依赖 Python3.5、TensorFlow v0.12.0、Pickle、OpenCV-Python、Matplotl…

宁波ISO45001:2018职业健康与安全管理体系认证

&#x1f913;ISO45001&#xff1a;2018职业健康与安全管理体系认证 ISO45001&#xff08;OHSAS18001&#xff09;全称&#x1f349;是职业安全卫生&#x1f350;管理标准&#xff0c;是国际上&#x1f955;继ISO9000质量管理体系&#x1f334;标准和ISO14000环境管理&#x1f…

主站设备通过Modbus转Profinet网关与湿度传感器通讯配置

Modbus转Profinet网关&#xff08;XD-MDPN100&#xff09;可以实现不同协议设备通讯&#xff0c;有些现场需要实时监测环境参数&#xff0c;但大由于当时环境仪表设备不能达到直连效果&#xff0c;通过Modbus转Profinet网关&#xff0c;湿度传感器的数据可以被准确、可靠地传输…

【通信原理笔记】【三】模拟信号调制——3.3 包络调制(AM)

文章目录 前言一、AM的数学表示二、AM的相干解调三、AM的非相干解调四、AM调制的性能总结 前言 本文将介绍包络调制方法&#xff0c;该方法的思路是将 m ( t ) m(t) m(t)作为已调信号的复包络的模——即包络。 一、AM的数学表示 根据包络调制的思路&#xff0c;我们有如下数学…

注册接口和前置SQL及数据生成及封装

注册接口 演示注册接口的三步操作&#xff1a;【注册流程逻辑】 第一步&#xff1a;发送注册短信验证码接口请求 请求方法&#xff1a; put 请求地址&#xff1a;http://shop.lemonban.com:8107/user/sendRegisterSms 请求参数&#xff1a;{“mobile”:“13422337766”} 请求头…

NASA数据集——2016-2019 年北极地区天气研究和预报(WRF)随机时间倒拉格朗日传输(STILT)粒子轨迹文件

ABoVE: Level-4 WRF-STILT Particle Trajectories for Circumpolar Receptors, 2016-2019 简介 文件修订日期&#xff1a;2021-12-07 数据集版本: 1 摘要 本数据集提供了 2016-2019 年期间天气研究和预报&#xff08;WRF&#xff09;随机时间倒拉格朗日传输&#xff08;ST…