当前位置: 首页 > news >正文

Linux C++ 多线程高并发服务器实战项目一

文章目录

  • 1、项目介绍
  • 2、项目流程
    • 2.1、环境变量搬家
    • 2.2、设置进程title
    • 2.3、信号初始化
    • 2.4、开始监听端口
    • 2.5、创建守护进程
    • 2.6、创建子进程

1、项目介绍

1、按照包头+包体的格式收发数据包,解决粘包的问题
2、非常完整的多线程高并发服务器
3、根据收到数据包执行,不同的业务逻辑函数
用到的技术:
epoll高并发通讯技术,用的是水平触发【LT】水平触发模式
通过线程池技术处理业务逻辑
多线程、之间同步技术使用,互斥量、和条件变量
一个master管理进程,多个worker工作进程
信号、守护进程

2、项目流程

2.1、环境变量搬家

1、统计argv总大小,以及环境变量总共占用多少内存,然后创建出来一块内存,将原先的环境变量拷贝到我们创建的内存中,为后续设置进程名做铺垫。
2、代码如下,和我们描述过程一直,没有多余操作

 //统计argv所占的内存g_argvneedmem = 0;for(int i = 0; i < argc; i++)  //argv =  ./nginx -a -b -c asdfas{g_argvneedmem += strlen(argv[i]) + 1; //+1是给\0留空间。} //统计环境变量所占的内存。注意判断方法是environ[i]是否为空作为环境变量结束标记for(int i = 0; environ[i]; i++) {g_envneedmem += strlen(environ[i]) + 1; //+1是因为末尾有\0,是占实际内存位置的,要算进来} //end forg_os_argc = argc;           //保存参数个数g_os_argv = (char **) argv; //保存参数指针//设置可执行程序标题相关函数:分配内存,并且把环境变量拷贝到新内存中来//这里无需判断penvmen == NULL,有些编译器new会返回NULL,有些会报异常,但不管怎样,如果在重要的地方new失败了,你无法收场,让程序失控崩溃,助你发现问题为好; gp_envmem = new char[g_envneedmem]; memset(gp_envmem,0,g_envneedmem);  //内存要清空防止出现问题char *ptmp = gp_envmem;//把原来的内存内容搬到新地方来for (int i = 0; environ[i]; i++) {size_t size = strlen(environ[i])+1 ; //不要拉下+1,否则内存全乱套了,因为strlen是不包括字符串末尾的\0的strcpy(ptmp,environ[i]);      //把原环境变量内容拷贝到新地方【新内存】environ[i] = ptmp;            //然后还要让新环境变量指向这段新内存ptmp += size;}

2.2、设置进程title

将进程名字传进来,计算名字长度要是大于(argv参数总长度和环境变量总长度之和)就不然设置,反之可以,然后将argv[1]设置为NULL以免影响后续,然后将title拷贝进去,将名字之后的内存空间都置为空

void ngx_setproctitle(const char *title)
{//我们假设,所有的命令 行参数我们都不需要用到了,可以被随意覆盖了;//注意:我们的标题长度,不会长到原始标题和原始环境变量都装不下,否则怕出问题,不处理//(1)计算新标题长度size_t ititlelen = strlen(title); //(2)计算总的原始的argv那块内存的总长度【包括各种参数】    size_t esy = g_argvneedmem + g_envneedmem; //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);
}

2.3、信号初始化

需要设置信号处理函数的信号数组定义

typedef struct 
{int           signo;       //信号对应的数字编号 ,每个信号都有对应的#defineconst  char   *signame;    //信号对应的中文名字 ,比如SIGHUP //信号处理函数,这个函数由我们自己来提供,但是它的参数和返回值是固定的void  (*handler)(int signo, siginfo_t *siginfo, void *ucontext); //函数指针,   siginfo_t:系统定义的结构
} ngx_signal_t;static 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作为一个特殊标记
};

为以下的信号,通过sigaction函数设置信号处理函数,

//初始化信号的函数,用于注册信号处理程序
//返回值: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 ifsigemptyset(&sa.sa_mask);   //比如咱们处理某个信号比如SIGUSR1信号时不希望收到SIGUSR2信号,那咱们就可以用诸如sigaddset(&sa.sa_mask,SIGUSR2);这样的语句针对信号为SIGUSR1时做处理,这个sigaddset//这里.sa_mask是个信号集(描述信号的集合),用于表示要阻塞的信号,sigemptyset():把信号集中的所有信号清0,本意就是不准备阻塞任何信号;//设置信号处理动作(信号处理函数),说白了这里就是让这个信号来了后调用我的处理程序,有个老的同类函数叫signal,不过signal这个函数被认为是不可靠信号语义,不建议使用,大家统一用sigactionif (sigaction(sig->signo, &sa, NULL) == -1) //参数1:要操作的信号//参数2:主要就是那个信号处理函数以及执行信号处理函数时候要屏蔽的信号等等内容//参数3:返回以往的对信号的处理方式【跟sigprocmask()函数边的第三个参数是的】,跟参数2同一个类型,我们这里不需要这个东西,所以直接设置为NULL;{   return -1; //有失败就直接返回}	else{            }} //end forreturn 0; //成功    
}

信号处理函数功能:目前只要只是做了master进程和worker进程信号分类处理(因为我们是进程程序),当woker进程死亡时,使用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 foraction = (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】{printf("signal %d (%s) received from %P%s", signo, sig->signame, siginfo->si_pid, action); }else{printf("signal %d (%s) received %s",signo, sig->signame, action);//没有发送该信号的进程id,所以不显示发送该信号的进程id}//子进程状态有变化,通常是意外退出if (signo == SIGCHLD) {ngx_process_get_status(); //获取子进程的结束状态} //end ifreturn;
}

通过waitpid函数,回收woker进程回收,设置WNOHANG为非阻塞调用。
返回值:
进程id:正常返回
0 表示子进程还没有结束
-1:
errno = EINTR 信号中断
errno = ECHILD 没有子进程

//获取子进程的结束状态,防止单独kill子进程时子进程变成僵尸进程
static void ngx_process_get_status(void)
{pid_t            pid;int              status;int              err;int              one=0; //抄自官方nginx,应该是标记信号正常处理过一次//当你杀死一个子进程时,父进程会收到这个SIGCHLD信号。for ( ;; ) {// 这个waitpid说白了获取子进程的终止状态,这样,子进程就不会成为僵尸进程了;//第一次waitpid返回一个> 0值,表示成功,//第二次再循环回来,再次调用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)         //没有子进程{return;}return;}  //end if(pid == -1)//-------------------------------//走到这里,表示  成功【返回进程id】 ,这里根据官方写法,打印一些日志来记录子进程的退出one = 1;  //标记waitpid()返回了正常的返回值if(WTERMSIG(status))  //获取使子进程终止的信号编号{printf("pid = %P exited on signal %d!",pid,WTERMSIG(status)); //获取使子进程终止的信号编号}else{printf("pid = %P exited with code %d!",pid,WEXITSTATUS(status)); //WEXITSTATUS()获取子进程传递给exit或者_exit参数的低八位}} //end forreturn;
}

2.4、开始监听端口

监听这个端口是否有连接到来,有连接上来并完成三次握手,就会将这个连接放入以完成队列中,等待accpet去连接这个用户,并且将监听套接字设置为非阻塞。

// 监听套接字
void listen_socket()
{connfd = socket(AF_INET,SOCK_STREAM,0);if(connfd<0){cout<<"socket failed:"<<connfd<<endl;return;}// 设置端口服用解决TIME_WAIT状态int op = 1;if(setsockopt(connfd,SOL_SOCKET,SO_REUSEADDR,(const void*)&op,sizeof(op))==-1){cout<<"setsockopt failed"<<endl;close(connfd);return;}// 设置非阻塞int nb = 1;if(ioctl(connfd,FIONBIO,&nb) == -1){cout<<"ioctl failed"<<endl;close(connfd);return;}// bind端口和ip地址struct sockaddr_in ser_addr;ser_addr.sin_family = AF_INET;ser_addr.sin_port = (in_port_t)(htons(PORT));ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);if(bind(connfd,(struct sockaddr*)&ser_addr,sizeof(ser_addr)) == -1){cout<<"bind failed"<<endl;close(connfd);return;}// listen 监听套接字if(listen(connfd,511)==-1)   {cout<<"listen failed"<<endl;close(connfd);return;}
}

设置套接字非阻塞方法

bool setnonblocking(int sockfd) 
{    int nb=1; //0:清除,1:设置  if(ioctl(sockfd, FIONBIO, &nb) == -1) //FIONBIO:设置/清除非阻塞I/O标记:0:清除,1:设置{return false;}return true;//如下也是一种写法,跟上边这种写法其实是一样的,但上边的写法更简单/* //fcntl:file control【文件控制】相关函数,执行各种描述符控制操作//参数1:所要设置的描述符,这里是套接字【也是描述符的一种】int opts = fcntl(sockfd, F_GETFL);  //用F_GETFL先获取描述符的一些标志信息if(opts < 0) {ngx_log_stderr(errno,"CSocekt::setnonblocking()中fcntl(F_GETFL)失败.");return false;}opts |= O_NONBLOCK; //把非阻塞标记加到原来的标记上,标记这是个非阻塞套接字【如何关闭非阻塞呢?opts &= ~O_NONBLOCK,然后再F_SETFL一下即可】if(fcntl(sockfd, F_SETFL, opts) < 0) {ngx_log_stderr(errno,"CSocekt::setnonblocking()中fcntl(F_SETFL)失败.");return false;}return true;*/
}

2.5、创建守护进程

使程序脱离终端,在后台运行
1、fork出一个子进程,主进程退出所有流程,子进程继续向下走
2、设置setsid,脱离终端,与父进程脱关系
3、设置umask文件权限
4、以读写方式打开/dev/null
5、将输入输出指向黑洞
6、关闭刚才打开的文件描述符

// 子进程返回0,父进程返回1,执行失败返回-1
static int ngx_daemon()
{switch (fork()){case -1:cout<<"ngx_daemon failed"<<endl;return -1;       case 0:break;default:return 1;}// 只有fork出来的子进程才能走到这个流程if(setsid() == -1){cout<<"ngx_daemon setsid failed"<<endl;return -1;}umask(0);// 打开黑洞设备,以读写方式打开int fd = open("/dev/null",O_RDWR);if(fd == -1)   {cout<<"ngx_daemon open failed"<<endl;return -1;}if(dup2(fd,STDIN_FILENO) == -1){cout<<"ngx_daemon dup2 failed"<<endl;return -1;}if(dup2(fd,STDOUT_FILENO)==-1){cout<<"ngx_daemon dup2 failed"<<endl;return -1;}if(fd>STDERR_FILENO){if(close(fd)==-1){cout<<"ngx_daemon close failed"<<endl;return -1;}}return 0;
}

2.6、创建子进程

master进程流程,为了不让创建woker进程过程中被信号给打断,为master进程设置信号集,创建完子进程,将信号集设置为空,此时master进程进入死循环,通过sigsuspend函数处理信号,它会讲我们刚才恢复为空的信号集设置进去,然后等待信号来临,一旦收到信号将恢复之前我们设置的信号屏蔽字,调用信号处理函数,等待信号处理函数返回,它才返回。

static u_char  master_process[] = "master process";
//描述:创建worker子进程
void ngx_master_process_cycle()
{    sigset_t set;        //信号集sigemptyset(&set);   //清空信号集//建议fork()子进程时学习这种写法,防止信号的干扰;sigaddset(&set, SIGCHLD);     //子进程状态改变sigaddset(&set, SIGALRM);     //定时器超时sigaddset(&set, SIGIO);       //异步I/Osigaddset(&set, SIGINT);      //终端中断符sigaddset(&set, SIGHUP);      //连接断开sigaddset(&set, SIGUSR1);     //用户定义信号sigaddset(&set, SIGUSR2);     //用户定义信号sigaddset(&set, SIGWINCH);    //终端窗口大小改变sigaddset(&set, SIGTERM);     //终止sigaddset(&set, SIGQUIT);     //终端退出符//.........可以根据开发的实际需要往其中添加其他要屏蔽的信号......//设置,此时无法接受的信号;阻塞期间,你发过来的上述信号,多个会被合并为一个,暂存着,等你放开信号屏蔽后才能收到这些信号。。。if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) //第一个参数用了SIG_BLOCK表明设置 进程 新的信号屏蔽字 为 “当前信号屏蔽字 和 第二个参数指向的信号集的并集{        printf("ngx_master_process_cycle()中sigprocmask()失败!");}//即便sigprocmask失败,程序流程 也继续往下走//首先我设置主进程标题---------beginsize_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 forngx_setproctitle(title); //设置标题printf("%s %P 【master进程】启动并开始运行......!",title,ngx_pid); //设置标题时顺便记录下来进程名,进程id等信息到日志}    //首先我设置主进程标题---------endint workprocess = 5;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/53255970sigsuspend(&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;
}

通过for循环将所有子进程创建出来,并且设置进程名字、信号集设置为空,进入死循环,后续子进程,后续业务逻辑主要在子进程中完成,master进程主要起管理作用。

//描述:根据给定的参数创建指定数量的子进程,因为以后可能要扩展功能,增加参数,所以单独写成一个函数
//threadnums:要创建的子进程数量
static void ngx_start_worker_processes(int threadnums)
{int i;for (i = 0; i < threadnums; i++)  //master进程在走这个循环,来创建若干个子进程{ngx_spawn_process(i,"worker process");} //end forreturn;
}//描述:产生一个子进程
//inum:进程编号【0开始】
//pprocname:子进程名字"worker process"
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变成了父pidngx_pid = getpid();                //重新获取pid,即本子进程的pidngx_worker_process_cycle(inum,pprocname);    //我希望所有worker子进程,在这个函数里不断循环着不出来,也就是说,子进程流程不往下边走;break;default: //这个应该是父进程分支,直接break;,流程往switch之后走            break;}//end switch//父进程分支会走到这里,子进程流程不往下边走-------------------------//若有需要,以后再扩展增加其他代码......return pid;
}//描述:worker子进程的功能函数,每个woker子进程,就在这里循环着了(无限循环【处理网络事件和定时器事件以对外提供web服务】)
//     子进程分叉才会走到这里
//inum:进程编号【0开始】
static void ngx_worker_process_cycle(int inum,const char *pprocname) 
{//设置一下变量ngx_process = NGX_PROCESS_WORKER;  //设置进程的类型,是worker进程//重新为子进程设置进程名,不要与父进程重复------sigset_t  set;      //信号集sigemptyset(&set);  //清空信号集if (sigprocmask(SIG_SETMASK, &set, NULL) == -1)  //原来是屏蔽那10个信号【防止fork()期间收到信号导致混乱】,现在不再屏蔽任何信号【接收任何信号】{cout<<"ngx_worker_process_cycle failed"<<endl;}ngx_setproctitle(pprocname); //设置标题   printf("%s %P 【worker进程】启动并开始运行......!",pprocname,ngx_pid); //设置标题时顺便记录下来进程名,进程id等信息到日志for(;;){} //end for(;;)return;
}

相关文章:

Linux C++ 多线程高并发服务器实战项目一

文章目录1、项目介绍2、项目流程2.1、环境变量搬家2.2、设置进程title2.3、信号初始化2.4、开始监听端口2.5、创建守护进程2.6、创建子进程1、项目介绍 1、按照包头包体的格式收发数据包&#xff0c;解决粘包的问题 2、非常完整的多线程高并发服务器 3、根据收到数据包执行&…...

QML ComboBox简介

1.简介 ComboBox是一个组合按钮和弹出列表。它提供了一种以占用最小屏幕空间的方式向用户显示选项列表的方法。 ComboBox用数据模型填充。数据模型通常是JavaScript数组、ListModel或整数&#xff0c;但也支持其他类型的数据模型。 常用属性&#xff1a; count : int&#x…...

uniapp使用webview嵌入vue页面及通信

最近刚做的一个需求&#xff0c;web端&#xff08;Vue&#xff09;使用了FormMaking库&#xff0c;FormMaking是一个拖拉拽的动态设计表单、快速开发的一个东西&#xff0c;拖拽完之后最终可以导出一个很长的json&#xff0c;然后通过json再进行回显&#xff0c;快速开发&#…...

深度学习部署笔记(九): CUDA RunTime API-2.1内存管理

1. 前言 主要理解pinned memory、global memory、shared memory即可 2. 主机内存 主机内存很多名字: CPU内存&#xff0c;pinned内存&#xff0c;host memory&#xff0c;这些都是储存在内存条上的Pageable Memory(可分页内存) Page lock Memory(页锁定内存) 共同组成内存你…...

Idea+maven+spring-cloud项目搭建系列--11-2 dubbo鉴权日志记录数据统一封装

前言&#xff1a;使用dubbo做为通信组件&#xff0c;如果接口需要鉴权&#xff0c;和日志记录需要怎样处理&#xff1b; 1 鉴权&#xff1a; 1.1 在bootstrap.yml 中定义过滤器&#xff1a; dubbo.provider.filter: 过滤器的名字&#xff1a; 1.2 resources 目录下创建配置文…...

SOLIDWORKS免费培训 SW大型装配体模式课程

在SOLIDWORKS的使用过程中&#xff0c;大家经常会遇到大型装配体的处理问题&#xff0c;微辰三维的培训课程中也包含了一些大型装配体的技术培训&#xff0c;下面整理一些常见问题&#xff0c;供参考&#xff1a;大型装配体模式1.当我们打开一个大的装配体时&#xff0c;可能会…...

xxl-job registry fail

解决方法&#xff1a; 1、检查nacos是否正确&#xff0c;一定要注意格式&#xff0c;一般都是addersses的地址问题&#xff0c;一定的要加/不然找不到&#xff0c;本机就不要使用ip了&#xff0c;用localhost。 xxl: job: admin: addresses: http://localhost:8080/xxl-job-ad…...

【C#进阶】C# 反射

序号系列文章11【C#基础】C# 预处理器指令12【C#基础】C# 文件与IO13【C#进阶】C# 特性文章目录前言1&#xff0c;反射的概念2&#xff0c;使用反射访问特性3&#xff0c;反射的用途4&#xff0c;反射的优缺点比较4.1 优点&#xff1a;4.2 缺点&#xff1a;5&#xff0c;System…...

公网NAT网关与VPC NAT网关介绍与实践

NAT网关介绍 NAT网关是一种网络地址转换服务&#xff0c;提供NAT代理&#xff08;SNAT和DNAT&#xff09;能力。 公有云NAT分为公网NAT网关和VPC NAT网关。 1&#xff09;公网NAT网关&#xff1a;提供公网地址转换服务。 2&#xff09;VPC NAT网关&#xff1a;提供私网地址转换…...

Windows中UWP、WPF和Windows窗体的区别

Windows 中开发应用&#xff08;或者可以说客户端&#xff09;有三种方法&#xff1a; UWP&#xff08;Universal Windows Platform&#xff09;、WPF&#xff08;Windows Presentation Foundation&#xff09;和 Windows 窗体&#xff08;Win Forms&#xff09;。这三种方法在…...

Flink从入门到精通系列(一)

1、Flink概述 Apache Flink 是一个框架和分布式处理引擎&#xff0c;用于在&#xff0c; 无边界和有边界数据流上进行有状态的计算 &#xff0c;Flink 能在所有常见集群环境中运行&#xff0c;并能以内存速度和任意规模进行计算。 Apache Flink 功能强大&#xff0c;支持开发…...

云原生应用风险介绍

本博客地址&#xff1a;https://security.blog.csdn.net/article/details/129303616 一、传统风险 传统风险主要是注入、敏感数据泄露、跨站脚本、配置错误等等&#xff0c;这些传统的安全风险在云原生应用中也是存在的&#xff0c;这里就不具体展开说了。 二、云原生应用架…...

什么是测试用例设计?

前言 想要进行测试自动化的团队都会遇到这个问题&#xff1a;自动化的成功和编码能力有多大的关联&#xff1f;现在更多的招聘信息越来越偏重于对测试人员的编程能力的要求&#xff0c;似乎这个问题的答案是极大的正关联性。 测试人员可以将编码能力用于与测试相关的各种目的…...

数据分析:基于K-近邻(KNN)对Pima人糖尿病预测分析

数据分析&#xff1a;基于K-近邻(KNN)对Pima人糖尿病预测分析 作者&#xff1a;AOAIYI 作者简介&#xff1a;Python领域新星作者、多项比赛获奖者&#xff1a;AOAIYI首页 &#x1f60a;&#x1f60a;&#x1f60a;如果觉得文章不错或能帮助到你学习&#xff0c;可以点赞&#x…...

Kettle体系结构及源码解析

介绍 ETL是数据抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;、装载&#xff08;Load&#xff09;的过程。Kettle是一款国外开源的ETL工具&#xff0c;有两种脚本文件transformation和job&#xff0c;transformation完成针对数据的基础转换&…...

大数据 | (二)SSH连接报错Permission denied

大数据 | &#xff08;三&#xff09;centos7图形界面无法执行yum命令&#xff1a;centos7图形界面无法执行yum命令 哈喽&#xff01;各位CSDN的朋友们大家好&#xff01; 今天在执行Hadoop伪分布式安装时&#xff0c;遇到了一个问题&#xff0c;在此跟大家分享&#xff0c; …...

前端——6.文本格式化标签和<div>和<span>标签

这篇文章&#xff0c;我们来讲一下HTML中的文本格式化标签 目录 1.文本格式化标签 1.1介绍 1.2代码演示 1.3小拓展 2.div和span标签 2.1介绍 2.2代码演示 2.3解释 3.小结 1.文本格式化标签 在网页中&#xff0c;有时需要为文字设置粗体、斜体和下划线等效果&#xf…...

浅谈Xpath注入漏洞

目录 知识简介 攻击简介 基础语法 语法演示 漏洞简介 漏洞原理 漏洞复现 Xpath盲注 知识简介 攻击简介 XPath注入攻击是指利用XPath 解析器的松散输入和容错特性&#xff0c;能够在 URL、表单或其它信息上附带恶意的XPath 查询代码&#xff0c;以获得权限信息的访问权…...

Oracle LogMiner分析归档日志

目录&#xff1a;Oracle LogMiner分析归档日志一、准备测试环境1、开启数据库归档日志2、打开数据库最小附加日志3、设置当前session时间日期格式二、创建测试数据1、创建数据2、数据落盘三、日志发掘测试挖掘在上次归档的Redo Log File1.确定最近归档的Redo Log File2.指定要分…...

趣味三角——第15章——傅里叶定理

第15章 傅里叶定理(Fourier’s Theorem) Fourier, not being noble, could not enter the artillery, although he was a second Newton. (傅立叶出生并不高贵&#xff0c;因此按当时的惯例进不了炮兵部队&#xff0c;虽然他是第二个牛顿。) —Franois Jean Dominique Arag…...

市场营销的核心是什么?

之所以写下「市场营销的核心是什么&#xff1f;」这篇文章&#xff0c;是因为这几天刚读完了《经理人参阅&#xff1a;市场营销》这本书。作为一个有着近十年工作经验的市场营销从业人员&#xff0c;看完这本书也产生了很多新的想法&#xff0c;也想记录一下&#xff0c;遂成此…...

c/cpp - 多线程/进程 多进程

c/cpp - 多线程/进程 多进程多进程创建多进程进程等待多进程 宏观上 两个进程完全并发的 父子进程具有互相独立的进程空间 父进程结束&#xff0c;不影响子进程的执行 创建多进程 #include <sys/types.h> #include <unistd.h> #include <stdio.h>int main()…...

MySQL必知必会 | 存储过程、游标、触发器

使用存储过程 存储过程 简单来说就是为了以后的使用而保存的一条或多条MySQL语句的集合。 我觉得就是封装了一组sql语句 为什么需要存储过程&#xff08;简单来说就是&#xff0c;简单、安全、高性能 通过把处理封装在容易使用的单元中&#xff0c;简化复杂操作所有开发人员…...

优化Facebook广告ROI的数据驱动方法:从投放到运营

“投放广告并不是最终的目的&#xff0c;关键在于如何最大程度地利用数据驱动的方法来提高广告投放的回报率&#xff08;ROI&#xff09;”Facebook广告是现代数字营销中最为常见和重要的广告形式之一。但是&#xff0c;要让Facebook广告真正发挥作用&#xff0c;需要通过数据驱…...

动态规划入门经典问题讲解

最近开始接触动态规划问题&#xff0c;以下浅谈&#xff08;或回顾&#xff09;一下这些问题的求解过程。解题思路对于动态规划问题&#xff0c;由于最终问题的求解需要以同类子问题作为基础&#xff0c;故需要定义一个dp数组&#xff08;一维或二维&#xff09;来记录问题求解…...

快速入门深度学习1(用时1h)

速通《动手学深度学习》1写在最前面0.内容与结构1.深度学习简介1.1 问题引入1.2 思路&#xff1a;逆向思考1.3 跳过1.4 特点1.5 小结2.预备知识&#xff08;MXNet版本&#xff0c;学错了。。。。&#xff09;2.1 获取和运行本书的代码2.2 数据操作2.2.1 略过2.2.2 小结2.3 自动…...

PaddleOCR关键信息抽取(KIE)的训练(SER训练和RE训练)错误汇总

1.SER训练报错: SystemError: (Fatal) Blocking queue is killed because the data reader raises an exception 1.1.问题描述 在执行训练任务的时候报错 单卡训练 python3 tools/train.py -c train_data/my_data/ser_vi_layoutxlm_xfund_zh.yml错误信息如下&#xff1a; T…...

信息收集之搜索引擎

Google Hacking 也可以用百度&#xff0c;不过谷歌的搜索引擎更强大 site 功能&#xff1a;搜索指定域名的网页内容&#xff0c;可以用来搜索子域名、跟此域名相关的内容 示例&#xff1a; site:zhihu.com 搜索跟zhihu.com相关的网页“web安全” site:zhihu.com 搜索zhihu…...

Flutter(四)布局类组件

目录布局类组件简介布局原理与约束线性布局&#xff08;Row和Column&#xff09;弹性布局流式布局&#xff08;Wrap、Flow&#xff09;层叠布局&#xff08;Stack、Positioned&#xff09;对齐与相对定位&#xff08;Align&#xff09;Align和Stack对比Center组件LayoutBuilder…...

【黑马】Java基础从入门到起飞目录合集

视频链接&#xff1a; Java入门到起飞&#xff08;上部&#xff09;&#xff1a;BV17F411T7AoJava入门到起飞&#xff08;下部&#xff09;&#xff1a;BV1yW4y1Y7Ms 学习时间&#xff1a; 2023/02/01 —— 2023/03/09断断续续的学习&#xff0c;历时大概37天&#xff0c;完结撒…...

企业网站的建设要注意哪些方面/郑州做网站最好的公司

题目&#xff1a;原题链接&#xff08;中等&#xff09; 标签&#xff1a;几何、数学 解法时间复杂度空间复杂度执行用时Ans 1 (Python)O(1)O(1)O(1)O(1)O(1)O(1)44ms (37.96%)Ans 2 (Python)Ans 3 (Python) 解法一&#xff1a; class Solution:def checkOverlap(self, radi…...

做网站泊头/企业推广是什么职业

--导出问题分析 --两个时间语句分析&#xff0c;该语句只导出4,059,292 数据&#xff0c;10分钟后数据没有继续导出 Snap Id Snap Time Sessions Cursors/Session Begin Snap: 39396 13-Jul-14 18:30:32 558 1.7 End Snap: 39407 13-Jul-14 20:20:07 556 1.7 Elapsed: 109.58 (…...

建网站系统平台/如何宣传推广自己的产品

more-cdn 自建 cdn 服务器&#xff0c;包含常用的js库文件 不推荐使用第三方 cdn&#xff0c;不能保证可用性 https://www.bootcdn.cn/ https://cdnjs.com/ 使用方式 1、直接使用(不推荐) 将域名https://cdnjs.cloudflare.com 替换为 https://mouday.github.io/more-cdn…...

怎样用ps做网站首页图/希爱力的作用与功效

首先把django的组件的app复制到新的项目的根目录下&#xff0c; 删除migrations的文件&#xff0c;只剩下__init__.py,因为以前生成过新的表&#xff0c;再进来要建立新表 注册app在设置文件中&#xff0c;...

成都网站建设电话/免费模板网站

现在我们的画面制作成功了&#xff01;请再次保存它。 对于画面的保存我们还有另外一种方法&#xff0c;如果你想使用一张大图或者得到的画面质量非常高&#xff0c;请使用Ultra Fractal 自带的“Render to Disk ”&#xff08;渲染到硬盘&#xff09;功能。 现在我们来渲染这幅…...

公司介绍简短范文/杭州排名优化公司

Linux学习笔记二&#xff08;艰辛的wxWidgets环境搭建&#xff09; 十分钟前我的第一个wxWidgets程序终于在Linux上现身了&#xff0c;虽然只是一个小小的测试窗体程序&#xff0c;但是。他的出现确实让我惊喜重重&#xff0c;因为在环境搭建的过程中真的是走了很多弯路。现在把…...