1.3 信号处理函数,创建worker进程

news2024/11/13 15:11:38

文章目录

  • 1、信号处理函数
  • 2、创建worker线程
  • 3、sigsuspend函数说明
  • 4、write函数思考

1、信号处理函数

1、初始化信号的函数,用于注册信号处理程序
2、信号处理函数

初始化信号函数,遍历结构体数组,然后给结构体数组中的每个成员注册信号处理函数,并将信号集全部设置为空,表示可以接受所有信号

//一个信号有关的结构 ngx_signal_t
typedef struct 
{
    int           signo;       //信号对应的数字编号 ,每个信号都有对应的#define ,大家已经学过了 
    const  char   *signame;    //信号对应的中文名字 ,比如SIGHUP 

    //信号处理函数,这个函数由我们自己来提供,但是它的参数和返回值是固定的【操作系统就这样要求】,大家写的时候就先这么写,也不用思考这么多;
    void  (*handler)(int signo, siginfo_t *siginfo, void *ucontext); //函数指针,   siginfo_t:系统定义的结构
} ngx_signal_t;

//声明一个信号处理函数
static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext); //static表示该函数只在当前文件内可见
static void ngx_process_get_status(void);                                      //获取子进程的结束状态,防止单独kill子进程时子进程变成僵尸进程

//数组 ,定义本系统处理的各种信号,我们取一小部分nginx中的信号,并没有全部搬移到这里,日后若有需要根据具体情况再增加
//在实际商业代码中,你能想到的要处理的信号,都弄进来
ngx_signal_t  signals[] = {
    // signo      signame             handler
    { SIGHUP,    "SIGHUP",           ngx_signal_handler },        //终端断开信号,对于守护进程常用于reload重载配置文件通知--标识1
    { SIGINT,    "SIGINT",           ngx_signal_handler },        //标识2   
	{ SIGTERM,   "SIGTERM",          ngx_signal_handler },        //标识15
    { SIGCHLD,   "SIGCHLD",          ngx_signal_handler },        //子进程退出时,父进程会收到这个信号--标识17
    { SIGQUIT,   "SIGQUIT",          ngx_signal_handler },        //标识3
    { SIGIO,     "SIGIO",            ngx_signal_handler },        //指示一个异步I/O事件【通用异步I/O信号】
    { SIGSYS,    "SIGSYS, SIG_IGN",  NULL               },        //我们想忽略这个信号,SIGSYS表示收到了一个无效系统调用,如果我们不忽略,进程会被操作系统杀死,--标识31
                                                                  //所以我们把handler设置为NULL,代表 我要求忽略这个信号,请求操作系统不要执行缺省的该信号处理动作(杀掉我)
    //...日后根据需要再继续增加
    { 0,         NULL,               NULL               }         //信号对应的数字至少是1,所以可以用0作为一个特殊标记
};

//初始化信号的函数,用于注册信号处理程序
//返回值:0成功  ,-1失败
int ngx_init_signals()
{
    ngx_signal_t      *sig;  //指向自定义结构数组的指针 
    struct sigaction   sa;   //sigaction:系统定义的跟信号有关的一个结构,我们后续调用系统的sigaction()函数时要用到这个同名的结构

    for (sig = signals; sig->signo != 0; sig++)  //将signo ==0作为一个标记,因为信号的编号都不为0;
    {        
        //我们注意,现在要把一堆信息往 变量sa对应的结构里弄 ......
        memset(&sa,0,sizeof(struct sigaction));

        if (sig->handler)  //如果信号处理函数不为空,这当然表示我要定义自己的信号处理函数
        {
            sa.sa_sigaction = sig->handler;  //sa_sigaction:指定信号处理程序(函数),注意sa_sigaction也是函数指针,是这个系统定义的结构sigaction中的一个成员(函数指针成员);
            sa.sa_flags = SA_SIGINFO;        //sa_flags:int型,指定信号的一些选项,设置了该标记(SA_SIGINFO),就表示信号附带的参数可以被传递到信号处理函数中
                                                //说白了就是你要想让sa.sa_sigaction指定的信号处理程序(函数)生效,你就把sa_flags设定为SA_SIGINFO
        }
        else
        {
            sa.sa_handler = SIG_IGN; //sa_handler:这个标记SIG_IGN给到sa_handler成员,表示忽略信号的处理程序,否则操作系统的缺省信号处理程序很可能把这个进程杀掉;
                                      //其实sa_handler和sa_sigaction都是一个函数指针用来表示信号处理程序。只不过这两个函数指针他们参数不一样, sa_sigaction带的参数多,信息量大,
                                       //而sa_handler带的参数少,信息量少;如果你想用sa_sigaction,那么你就需要把sa_flags设置为SA_SIGINFO;                                       
        } //end if

        sigemptyset(&sa.sa_mask);   //比如咱们处理某个信号比如SIGUSR1信号时不希望收到SIGUSR2信号,那咱们就可以用诸如sigaddset(&sa.sa_mask,SIGUSR2);这样的语句针对信号为SIGUSR1时做处理,这个sigaddset三章五节讲过;
                                    //这里.sa_mask是个信号集(描述信号的集合),用于表示要阻塞的信号,sigemptyset()这个函数咱们在第三章第五节讲过:把信号集中的所有信号清0,本意就是不准备阻塞任何信号;
                                    
        
        //设置信号处理动作(信号处理函数),说白了这里就是让这个信号来了后调用我的处理程序,有个老的同类函数叫signal,不过signal这个函数被认为是不可靠信号语义,不建议使用,大家统一用sigaction
        if (sigaction(sig->signo, &sa, NULL) == -1) //参数1:要操作的信号
                                                     //参数2:主要就是那个信号处理函数以及执行信号处理函数时候要屏蔽的信号等等内容
                                                      //参数3:返回以往的对信号的处理方式【跟sigprocmask()函数边的第三个参数是的】,跟参数2同一个类型,我们这里不需要这个东西,所以直接设置为NULL;
        {   
            ngx_log_error_core(NGX_LOG_EMERG,errno,"sigaction(%s) failed",sig->signame); //显示到日志文件中去的 
            return -1; //有失败就直接返回
        }	
        else
        {            
            //ngx_log_error_core(NGX_LOG_EMERG,errno,"sigaction(%s) succed!",sig->signame);     //成功不用写日志 
            //ngx_log_stderr(0,"sigaction(%s) succed!",sig->signame); //直接往屏幕上打印看看 ,不需要时可以去掉
        }
    } //end for
    return 0; //成功    
}

2、信号处理函数

siginfo:这个系统定义的结构中包含了信号产生原因的有关信息
static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext)
1、通过ngx_process全局量判断,是父进程还是子,然后进入对应的流程中处理
2、siginfo:这个系统定义的结构中包含了信号产生原因的有关信息进行日志打印
3、防止子进程退出,变成僵尸进程,需要处理SIGCHLD信号,需要调用waitpid函数,使用无阻塞调用,通过补获函数返回值进行打印日志

//信号处理函数
//siginfo:这个系统定义的结构中包含了信号产生原因的有关信息
static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext)
{    
    //printf("来信号了\n");    
    ngx_signal_t    *sig;    //自定义结构
    char            *action; //一个字符串,用于记录一个动作字符串以往日志文件中写
    
    for (sig = signals; sig->signo != 0; sig++) //遍历信号数组    
    {         
        //找到对应信号,即可处理
        if (sig->signo == signo) 
        { 
            break;
        }
    } //end for

    action = (char *)"";  //目前还没有什么动作;

    if(ngx_process == NGX_PROCESS_MASTER)      //master进程,管理进程,处理的信号一般会比较多 
    {
        //master进程的往这里走
        switch (signo)
        {
        case SIGCHLD:  //一般子进程退出会收到该信号
            ngx_reap = 1;  //标记子进程状态变化,日后master主进程的for(;;)循环中可能会用到这个变量【比如重新产生一个子进程】
            break;

        //.....其他信号处理以后待增加

        default:
            break;
        } //end switch
    }
    else if(ngx_process == NGX_PROCESS_WORKER) //worker进程,具体干活的进程,处理的信号相对比较少
    {
        //worker进程的往这里走
        //......以后再增加
        //....
    }
    else
    {
        //非master非worker进程,先啥也不干
        //do nothing
    } //end if(ngx_process == NGX_PROCESS_MASTER)

    //这里记录一些日志信息
    //siginfo这个
    if(siginfo && siginfo->si_pid)  //si_pid = sending process ID【发送该信号的进程id】
    {
        ngx_log_error_core(NGX_LOG_NOTICE,0,"signal %d (%s) received from %P%s", signo, sig->signame, siginfo->si_pid, action); 
    }
    else
    {
        ngx_log_error_core(NGX_LOG_NOTICE,0,"signal %d (%s) received %s",signo, sig->signame, action);//没有发送该信号的进程id,所以不显示发送该信号的进程id
    }

    //.......其他需要扩展的将来再处理;

    //子进程状态有变化,通常是意外退出【既然官方是在这里处理,我们也学习官方在这里处理】
    if (signo == SIGCHLD) 
    {
        ngx_process_get_status(); //获取子进程的结束状态
    } //end if

    return;
}

//获取子进程的结束状态,防止单独kill子进程时子进程变成僵尸进程
static void ngx_process_get_status(void)
{
    pid_t            pid;
    int              status;
    int              err;
    int              one=0; //抄自官方nginx,应该是标记信号正常处理过一次

    //当你杀死一个子进程时,父进程会收到这个SIGCHLD信号。
    for ( ;; ) 
    {
        //waitpid,有人也用wait,但老师要求大家掌握和使用waitpid即可;这个waitpid说白了获取子进程的终止状态,这样,子进程就不会成为僵尸进程了;
        //第一次waitpid返回一个> 0值,表示成功,后边显示 2019/01/14 21:43:38 [alert] 3375: pid = 3377 exited on signal 9【SIGKILL】
        //第二次再循环回来,再次调用waitpid会返回一个0,表示子进程还没结束,然后这里有return来退出;
        pid = waitpid(-1, &status, WNOHANG); //第一个参数为-1,表示等待任何子进程,
                                              //第二个参数:保存子进程的状态信息(大家如果想详细了解,可以百度一下)。
                                               //第三个参数:提供额外选项,WNOHANG表示不要阻塞,让这个waitpid()立即返回        

        if(pid == 0) //子进程没结束,会立即返回这个数字,但这里应该不是这个数字【因为一般是子进程退出时会执行到这个函数】
        {
            return;
        } //end if(pid == 0)
        //-------------------------------
        if(pid == -1)//这表示这个waitpid调用有错误,有错误也理解返回出去,我们管不了这么多
        {
            //这里处理代码抄自官方nginx,主要目的是打印一些日志。考虑到这些代码也许比较成熟,所以,就基本保持原样照抄吧;
            err = errno;
            if(err == EINTR)           //调用被某个信号中断
            {
                continue;
            }

            if(err == ECHILD  && one)  //没有子进程
            {
                return;
            }

            if (err == ECHILD)         //没有子进程
            {
                ngx_log_error_core(NGX_LOG_INFO,err,"waitpid() failed!");
                return;
            }
            ngx_log_error_core(NGX_LOG_ALERT,err,"waitpid() failed!");
            return;
        }  //end if(pid == -1)
        //-------------------------------
        //走到这里,表示  成功【返回进程id】 ,这里根据官方写法,打印一些日志来记录子进程的退出
        one = 1;  //标记waitpid()返回了正常的返回值
        if(WTERMSIG(status))  //获取使子进程终止的信号编号
        {
            ngx_log_error_core(NGX_LOG_ALERT,0,"pid = %P exited on signal %d!",pid,WTERMSIG(status)); //获取使子进程终止的信号编号
        }
        else
        {
            ngx_log_error_core(NGX_LOG_NOTICE,0,"pid = %P exited with code %d!",pid,WEXITSTATUS(status)); //WEXITSTATUS()获取子进程传递给exit或者_exit参数的低八位
        }
    } //end for
    return;
}

2、创建worker线程

主进程流程函数:现将下列信号添加到信号集中屏蔽这些信号,获取主进程名字长度+argv参数长度加进来,然后设置进去,写一个日志,然后通过读取配置文件获取需要创建的子进程数量,然后调用创建子进程函数,创建完之后,回到主进程,将信号集设置为空,然后进入主进程循环

//变量声明
static u_char  master_process[] = "master process";

//描述:创建worker子进程
void ngx_master_process_cycle()
{    
    sigset_t set;        //信号集

    sigemptyset(&set);   //清空信号集
    sigaddset(&set, SIGCHLD);     //子进程状态改变
    sigaddset(&set, SIGALRM);     //定时器超时
    sigaddset(&set, SIGIO);       //异步I/O
    sigaddset(&set, SIGINT);      //终端中断符
    sigaddset(&set, SIGHUP);      //连接断开
    sigaddset(&set, SIGUSR1);     //用户定义信号
    sigaddset(&set, SIGUSR2);     //用户定义信号
    sigaddset(&set, SIGWINCH);    //终端窗口大小改变
    sigaddset(&set, SIGTERM);     //终止
    sigaddset(&set, SIGQUIT);     //终端退出符
    //.........可以根据开发的实际需要往其中添加其他要屏蔽的信号......
    
    //设置,此时无法接受的信号;阻塞期间,你发过来的上述信号,多个会被合并为一个,暂存着,等你放开信号屏蔽后才能收到这些信号。。。
    //sigprocmask()在第三章第五节详细讲解过
    if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) //第一个参数用了SIG_BLOCK表明设置 进程 新的信号屏蔽字 为 “当前信号屏蔽字 和 第二个参数指向的信号集的并集
    {        
        ngx_log_error_core(NGX_LOG_ALERT,errno,"ngx_master_process_cycle()中sigprocmask()失败!");
    }
    //即便sigprocmask失败,程序流程 也继续往下走

    //首先我设置主进程标题---------begin
    size_t size;
    int    i;
    size = sizeof(master_process);  //注意我这里用的是sizeof,所以字符串末尾的\0是被计算进来了的
    size += g_argvneedmem;          //argv参数长度加进来    
    if(size < 1000) //长度小于这个,我才设置标题
    {
        char title[1000] = {0};
        strcpy(title,(const char *)master_process); //"master process"
        strcat(title," ");  //跟一个空格分开一些,清晰    //"master process "
        for (i = 0; i < g_os_argc; i++)         //"master process ./nginx"
        {
            strcat(title,g_os_argv[i]);
        }//end for
        ngx_setproctitle(title); //设置标题
        ngx_log_error_core(NGX_LOG_NOTICE,0,"%s %P 【master进程】启动并开始运行......!",title,ngx_pid); //设置标题时顺便记录下来进程名,进程id等信息到日志
    }    
    //首先我设置主进程标题---------end
        
    //从配置文件中读取要创建的worker进程数量
    CConfig *p_config = CConfig::GetInstance(); //单例类
    int workprocess = p_config->GetIntDefault("WorkerProcesses",1); //从配置文件中得到要创建的worker进程数量
    ngx_start_worker_processes(workprocess);  //这里要创建worker子进程

    //创建子进程后,父进程的执行流程会返回到这里,子进程不会走进来    
    sigemptyset(&set); //信号屏蔽字为空,表示不屏蔽任何信号
    
    for ( ;; ) 
    {

    //    usleep(100000);
        //ngx_log_error_core(0,0,"haha--这是父进程,pid为%P",ngx_pid);

        // sigsuspend(const sigset_t *mask))用于在接收到某个信号之前, 临时用mask替换进程的信号掩码, 并暂停进程执行,直到收到信号为止。
        // sigsuspend 返回后将恢复调用之前的信号掩码。信号处理函数完成后,进程将继续执行。该系统调用始终返回-1,并将errno设置为EINTR。

        //sigsuspend是一个原子操作,包含4个步骤:
        //a)根据给定的参数设置新的mask 并 阻塞当前进程【因为是个空集,所以不阻塞任何信号】
        //b)此时,一旦收到信号,便恢复原先的信号屏蔽【我们原来调用sigprocmask()的mask在上边设置的,阻塞了多达10个信号,从而保证我下边的执行流程不会再次被其他信号截断】
        //c)调用该信号对应的信号处理函数
        //d)信号处理函数返回后,sigsuspend返回,使程序流程继续往下走
        //printf("for进来了!\n"); //发现,如果print不加\n,无法及时显示到屏幕上,是行缓存问题,以往没注意;可参考https://blog.csdn.net/qq_26093511/article/details/53255970

        sigsuspend(&set); //阻塞在这里,等待一个信号,此时进程是挂起的,不占用cpu时间,只有收到信号才会被唤醒(返回);
                         //此时master进程完全靠信号驱动干活    

//        printf("执行到sigsuspend()下边来了\n");
        
        //printf("master进程休息1秒\n");      
        //ngx_log_stderr(0,"haha--这是父进程,pid为%P",ngx_pid); 
        sleep(1); //休息1秒        
        //以后扩充.......

    }// end for(;;)
    return;
}

循环创建子进程:fork函数如果创建失败就写日志,进入子进程流程,第一步先将所有信号集初始化为0,然后设置进去,然后设置进程名字,写一条日志表示创建成功

static void ngx_start_worker_processes(int threadnums)
{
    int i;
    for (i = 0; i < threadnums; i++)  //master进程在走这个循环,来创建若干个子进程
    {
        ngx_spawn_process(i,"worker process");
    } //end for
    return;
}

static int ngx_spawn_process(int inum,const char *pprocname)
{
    pid_t  pid;

    pid = fork(); //fork()系统调用产生子进程
    switch (pid)  //pid判断父子进程,分支处理
    {  
    case -1: //产生子进程失败
        ngx_log_error_core(NGX_LOG_ALERT,errno,"ngx_spawn_process()fork()产生子进程num=%d,procname=\"%s\"失败!",inum,pprocname);
        return -1;

    case 0:  //子进程分支
        ngx_parent = ngx_pid;              //因为是子进程了,所有原来的pid变成了父pid
        ngx_pid = getpid();                //重新获取pid,即本子进程的pid
        ngx_worker_process_cycle(inum,pprocname);    //我希望所有worker子进程,在这个函数里不断循环着不出来,也就是说,子进程流程不往下边走;
        break;

    default: //这个应该是父进程分支,直接break;,流程往switch之后走            
        break;
    }//end switch

    //父进程分支会走到这里,子进程流程不往下边走-------------------------
    //若有需要,以后再扩展增加其他代码......
    return pid;
}

static void ngx_worker_process_cycle(int inum,const char *pprocname)
{
    sigset_t  set;      //信号集

    sigemptyset(&set);  //清空信号集
    if (sigprocmask(SIG_SETMASK, &set, NULL) == -1)  //原来是屏蔽那10个信号【防止fork()期间收到信号导致混乱】,现在不再屏蔽任何信号【接收任何信号】
    {
        ngx_log_error_core(NGX_LOG_ALERT,errno,"ngx_worker_process_init()中sigprocmask()失败!");
    }
    
    ngx_setproctitle(pprocname); //设置标题   
    ngx_log_error_core(NGX_LOG_NOTICE,0,"%s %P 【worker进程】启动并开始运行......!",pprocname,ngx_pid); //设置标题时顺便记录下来进程名,进程id等信息到日志
	for(;;)
	{

	}
}

3、sigsuspend函数说明

sigsuspend是一个原子操作,包含4个步骤:
a)根据给定的参数设置新的mask 并 阻塞当前进程【因为是个空集,所以不阻塞任何信号】
b)此时,一旦收到信号,便恢复原先的信号屏蔽【我们原来调用sigprocmask()的mask在上边设置的,阻塞了多达10个信号,从而保证我下边的执行流程不会再次被其他信号截断】
c)调用该信号对应的信号处理函数
d)信号处理函数返回后,sigsuspend返回,使程序流程继续往下走
阻塞在这里等待一个信号,此时进程是挂起的,不占用CPU时间,只有收到信号才会被唤醒,就会返回,他是把多个操作组合在一起,是一个原子操作,不可分割的,是同一时刻执行的,

4、write函数思考

读个进程同时写一个文件,比如5个进程同时往日志文件中写,会不会造成混乱?
通过查看,确定是有序的,因为父子进程是亲缘关系,是会共享表项,write是原子操作,不会被打断,

如果不是父子进程关系进程,可能出现数据混乱

在这里插入图片描述

write函数返回时,用户缓存区已经将数据放到内核缓存区了,但是无法确定写入磁盘,因为write调用速度极快,可能没有时间完成该项工作,实际写磁盘。

断电导致write数据丢失解决:
在这里插入图片描述
在这里插入图片描述

1、open是加上O_DIRECT,表示跳过内核缓存区,直接访问磁盘,用posix_memalign来分配一块特殊的内存
2、open文件时,加上O_SYNC选项,同步选项,把数据同步到磁盘,只针对write函数,使每次write操作等待物理IO操作完成
3、缓存同步:尽量保证缓存数据和写到磁盘上的数据一致,
sync(void)将所有修改过的缓冲区排入写队列,然后返回,并不等待实际写磁盘操作结束,数据是否写入磁盘并没有保证。
fsync(int fd)将文件描述符,对应的文件缓存区立即写入磁盘,并等待磁盘写入结束。
fdatasync(int fd)类似fsync,只影响fsync但是只影响文件数据内容,而非fsync不一样,他会修改文件属性
例如:
write(4k) 写了1000次直到将所有写入
fsync最后调用一次

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

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

相关文章

python实现自动检测核酸用码记录 ---- 自动化办公小技巧(摸鱼利器)

自动检测核酸用码记录&#x1f947;预备知识✈️os库os.path.exists()os.mkdir()os.remove()os.listdir()&#x1f47d;Python 3 查看字符编码方法⏰python3获取当前系统时间&#x1f424;读取图片&#xff0c;保存到指定目录&#x1f47c;将数据保存到csv文件中&#x1f948;p…

使用html+css实现一个静态页面(含源码)

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置&#xff0c;有div的样式格局&#xff0c;这个实例比较全面&#xff0c;有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 文章目录一、网页介绍一…

freeRTOS学习(三)

任务管理 任务功能&#xff1a;任务以C函数的形式实现。唯一特别的地方是它们的原型&#xff0c;它必须返回void并接受一个void指针形参。 void ATaskFunction(void *pvParameters);**每个任务本身都是一个小程序。它有一个入口点&#xff0c;通常在无限循环中永远运行&#…

回归测试选择用例,看这里就可以了。

介绍 在软件生命周期中&#xff0c;软件经常发生变化&#xff0c;软件开发人员任何代码改动都会有引入故障的风险&#xff09;。 为了消除或减小这种风险&#xff0c;在软件迭代开发模式下&#xff0c;回归测试扮演着重要的角色&#xff1a;它能够帮助测试人员验证新增的功能…

mac配置jdk环境变量

**下载完成后直接安装一路next直到完成。 查看JDK安装后的路径 在终端输入以下命令查看JDK地址** 按照以下命令的顺序来设置jdk环境变量 查看JDK地址 &#xff1a;/usr/libexec/java_home -V。 open -e .bash_profile打开文件 touch .bash_profile 创建文件 打开.bash_prof…

G2O (General Graph Optimization)入门及简单使用

g2o全称是General Graph Optimization&#xff0c;也就是图优化&#xff0c;我们在做SLAM后端或者更加常见的任何优化问题&#xff08;曲线拟合&#xff09;都可以使用G2O进行处理。 先放出本文的几个参考链接&#xff1a; 半闲居士&#xff08;高翔博士&#xff09; 非线性…

智能终端信息安全概念(一):开篇

最近屁事以及自己对于时间的把控太差了&#xff0c;还有就是一个师妹最近让辅导作业&#xff0c;很烦。 回归正轨&#xff0c;好好学习Linux驱动的方面。 在实际的Linux驱动中&#xff0c;Linux内核尽量做得更多&#xff0c;以便于底层的驱动可以做得更少。 而且&#xff0c…

【C++笔试强训】第十七天

&#x1f387;C笔试强训 博客主页&#xff1a;一起去看日落吗分享博主的C刷题日常&#xff0c;大家一起学习博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a;夜色难免微凉&#xff0c;前方必有曙光 &#x1f31e;。 &#x1f4a6;&a…

【微服务容器化】第四章-Docker应用部署

&#x1f334;第四章 Docker应用部署&#x1f343;4.1 Mysql部署&#x1f343;4.2 Tomcat部署&#x1f343;4.3 Nginx 部署&#x1f343;4.4 redis 部署&#x1f343;4.1 Mysql部署 分析: 容器内的网络服务和外部机器不能直接通信 外部机器和宿主机可以直接通信 宿主机和容器…

因为有了它,我用舍友玩王者的时间拿到了华为offer

随时随地刷Leetcode题的方法&#xff0c;大学因为这个&#xff0c;我入职大厂! 下面有免费试用网址 目录我如何拿到大厂offer秘密武器使用教程VSCODE云IDE介绍完整功能&#xff0c;兼容VS Code安装的插件没有效果&#xff1f;通过终端启动项目后无法预览&#xff1f;CIDE收费…

sklearn笔记:调参

1 介绍 超参数是不直接在估计器中学习的参数。 在 scikit-learn 中&#xff0c;它们作为参数传递给估计器类的构造函数。 需要搜索超参数空间以获得最佳交叉验证分数。scikit-learn 中提供了两种通用的参数搜索方法&#xff1a; 对于给定的值&#xff0c;GridSearchCV 会详尽地…

客快物流大数据项目(八十四):Impala优化

文章目录 Impala优化 一、​​​​​​​Impala关键配置 二、Impala查询分析

C++ Reference: Standard C++ Library reference: C Library: cstring: strspn

C官网参考链接&#xff1a;strspnhttps://cplusplus.com/reference/cstring/strspn/strspn 函数 <cstring> strspn size_t strspn ( const char * str1, const char * str2 ); 获取字符串中字符集的跨度 返回str1的初始部分的长度&#xff0c;它只包含str2的一部分字符…

FPGA双线性插值图像缩放详细讲解,上板验证稳定通过,提供两套工程源码

开局直接放大招&#xff1a;提供源码及工程&#xff1b; 重点讲解双线性插值图像缩放&#xff1b; 此功能模块使用HLS实现&#xff0c;并已封装导出IP&#xff0c;可在工程中添加并使用&#xff0c;可提供HLS工程源码&#xff1b; 若是用verilog实现双线性插值图像缩放&#xf…

【Java中23种面试常考的设计模式之组合模式(Composite)---结构型模式】

【Java中23种面试常考的设计模式之组合模式(Composite)—结构型模式】 知识回顾: 之前我们讲过的设计模式在这里呦: 【面试最常见的设计模式之单例模式】 【面试最常见的设计模式之工厂模式】 【Java中23种面试常考的设计模式之备忘录模式(Memento)—行为型模式】 【Java中23种…

【每日渗透笔记】后台弱口令+未授权尝试

目录 一、特点&#xff1a; 1.1、特征&#xff1a; 1.2、分析&#xff1a; 1.3、所处情景&#xff1a; 目前&#xff1a; 问题&#xff1a; 二、渗透 一、特点&#xff1a; 1.1、特征&#xff1a; 用户登陆页面 1.2、分析&#xff1a; 毋庸置疑&#xff0c;既然有用户登…

SpringBoot系列之动态生成cron表达式执行定时程序

业务场景 最近需要实现一个功能&#xff0c;根据页面选择的星期&#xff0c;默认是凌晨执行&#xff0c;生成cron表达式&#xff0c;然后定时执行定时程序 环境准备 开发环境 JDK 1.8SpringBoot2.2.1Maven 3.2 开发工具 IntelliJ IDEAsmartGitNavicat15 在IDEA里集成阿里的…

Pycharm 如何自动调整 Python 代码符合 pep8 编码规范

前言 学生时代&#xff0c;写的一手漂亮的好字&#xff0c;能给人留下好的印象。作为 IT 人&#xff0c;写的一手漂亮的代码也会给人留下美好的印象。 代码就是自己的脸面&#xff0c;不管写质量怎样&#xff0c;首先要写的漂亮。Python 有一套 pep8 编码规范标准。 什么是 p…

电感重要参数的理解

电感作为一种储能元件&#xff0c;广泛运用在硬件电路的各个模块。较为常见的有DCDC电路&#xff0c;滤波电路以及振荡电路等。对于电感的选取&#xff0c;大多数人往往只关心感值&#xff0c;感值越大&#xff0c;储能越强&#xff0c;纹波也就越小。然而除了感值以外&#xf…

用Python剪辑视频?太简单了

人生苦短&#xff0c;快学Python&#xff01; 最近我在网上下载一个视频&#xff0c;结果下载到本地是近百个视频片段&#xff0c;为了方便观看只能将这些片段合并为一个视频整体。 不过我并没有搜到能够处理类似情况的小工具&#xff0c;只是发现剪映等软件可以实现视频合并功…