147 Linux 网络编程3 ,高并发服务器 --多路I/O转接服务器 - select

从前面的知识学习了如何通过socket ,多进程,多线程创建一个高并发服务器,但是在实际工作中,我们并不会用到前面的方法 去弄一个高并发服务器,有更加好用的方法,就是多路I/O转接器

零 多路I/O转接服务器

多路IO转接服务器也叫做多任务IO服务器。该类服务器实现的主旨思想是,不再由应用程序自己监视客户端连接,取而代之由内核替应用程序监视文件。

主要使用的方法有三种 select , poll , epoll

其中select 可以跨平台

poll 和 epoll不能跨平台,只能在linux上使用。

重点是epoll

次重点是 select

poll知道就行了,再学习后我们会知道原因。

本章主要学习 select。

一 select 

  1. select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数
  2. 解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用的是轮询模型,会大大降低服务器响应效率,不应在select上投入更多精力。

select 函数借助内核监听两件事,:客户端连接,数据通信事件。

核心函数: select函数 ,FD_CLR,FD_ISSET,FD_SET,FD_ZERO

int select(int nfds, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, struct timeval *timeout);

void FD_CLR(int fd, fd_set *set); //把文件描述符集合里fd位清0

int FD_ISSET(int fd, fd_set *set); //测试文件描述符集合里fd是否置1

void FD_SET(int fd, fd_set *set); //把文件描述符集合里fd位置1

void FD_ZERO(fd_set *set); //把文件描述符集合里所有位清0

#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);nfds: 		监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态readfds:	监控有读数据到达文件描述符集合,传入传出参数writefds:	监控写数据到达文件描述符集合,传入传出参数exceptfds:	监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数timeout:	定时阻塞监控时间,3种情况1.NULL,永远等下去2.设置timeval,等待固定时间3.设置timeval里时间均为0,检查描述字后立即返回,轮询struct timeval {long tv_sec; /* seconds */long tv_usec; /* microseconds */};void FD_CLR(int fd, fd_set *set); 	//把文件描述符集合里fd位清0int FD_ISSET(int fd, fd_set *set); 	//测试文件描述符集合里fd是否置1void FD_SET(int fd, fd_set *set); 	//把文件描述符集合里fd位置1void FD_ZERO(fd_set *set); 			//把文件描述符集合里所有位清0返回值:所有监听的集合中,满足事件发生的总数

server 端代码实现1

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <errno.h>
#include <sys/select.h>#include "wrap.h"//select 实现高并发代码示例
int main() {int listenfd;int ret = 0;//第一步:创建socket。 打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符,应用程序可以像读写文件一样用read/write在网络上收发数据,如果socket()调用出错则返回-1listenfd = Socket(AF_INET, SOCK_STREAM, 0);//第二步 build IP+PORT到网络地址 到socket 创建出来的listenfdstruct sockaddr_in servaddr;bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(8000);Bind(listenfd, (struct sockaddr *) &servaddr,sizeof(servaddr));//第三步,设置可以同时监听的最大的数量为1024Listen(listenfd, 1024);//第四步,这时候 //void FD_CLR(int fd, fd_set *set);//int  FD_ISSET(int fd, fd_set *set);//void FD_SET(int fd, fd_set *set);//void FD_ZERO(fd_set *set);fd_set readfds;FD_ZERO(&readfds);FD_SET(listenfd, &readfds);fd_set allfds =  readfds;int maxfd = listenfd;//第五步,这时候就要弄一个循环去监听了。//第一次的时候,肯定是只监听了listenfd一个,都是后面,如果有cfd连接上的话,那就监听的多了//因此要使用一个maxfd 作为备份记录//select 函数的意义是:
//	int select(int nfds, fd_set *readfds, fd_set *writefds,
//		fd_set *exceptfds, struct timeval *timeout);
//
//	nfds: 		监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态
//	readfds:	监控有读数据到达文件描述符集合,传入传出参数
//	writefds:	监控写数据到达文件描述符集合,传入传出参数
//	exceptfds:	监控异常发生达文件描述符集合, 如带外数据到达异常,传入传出参数
//	timeout:	定时阻塞监控时间,3种情况
//	1.NULL,永远等下去
//	2.设置timeval,等待固定时间
//	3.设置timeval里时间均为0,检查描述字后立即返回,轮询
//	struct timeval {
//	long tv_sec; /* seconds */
//	long tv_usec; /* microseconds */
//};返回值:所有监听的集合中,满足事件发生的总数int nready = 0;struct sockaddr_in clie_addr;int clie_addrlen = sizeof(clie_addr);int connectfd = 0;char buf[BUFSIZ];char str[INET_ADDRSTRLEN];while (1) {readfds = allfds;nready = select(maxfd + 1, &readfds,NULL,NULL,NULL);//nready为所有监听的集合中,满足事件发生的总数//第三个参数timeval我们设置的是NULL,表示阻塞等待//因此如果有事件发生,那么就会走到后面的代码,且readfds集合中会改动成为真正//有监听事件发生后,第一步做错误判断处理if (nready < 0 ) {//说明有error发生perr_exit("select error");}//第六步,判断nready中是否有 新的连接事件发生,也就是说listenfd是否if (FD_ISSET(listenfd, &readfds)) {//走到这里,说明有新的链接过来了,那么我们要做如下的几件事//6.1 server赶紧连接,注意,这时候并不会阻塞,因为listenfd已经有了读取事件才会走到这里connectfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addrlen);//这里加一行log,目的是将连接的客户端的信息打印出来printf("received from %s at PORT %d\n",inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),ntohs(clie_addr.sin_port));//这里添加一行判断,如果connectedfd已经超过1024,则不支持,select对于每个进程或者线程只能支持最多1024个if (connectfd == FD_SETSIZE) { /* 达到select能监控的文件个数上限 1024 */fputs("too many clients\n", stderr);exit(1);}//6.2 将这个新的链接添加到 监听的readfds中FD_SET(connectfd,&allfds); /* 向监控文件描述符集合allset添加新的文件描述符connfd *///6.3 更新maxfdif (connectfd > maxfd) {maxfd = connectfd;    /* 有可能最大的connectfd 需要变化,这是因为当有新的connectfd之前,如果没有任何的connectfd没有断开,则会变成最大,但是如果中间有connect断开,则这个新的connectfd的值,有可能不是最大。select第一个参数需要 *///假设之前来了,4,5,6,这时候客户端4已经断开了,这时候 connectfd就等于4,如果4,5,6都没有断开过,则这时候新来的会是7}//6.4 判断select 的返回值 是否只有一个,且是listenfd事件,如果走到这里说明这一个已经被处理了,因此就没有必要往下再执行了if (--nready == 0 ) {continue;}}//第七步,走到这里说明nready中有一个或者多个 客户端发送数据的事件过来。//我们当前的写法是通过循环1024次,挨个往出拿,/* 检测哪个clients 有数据就绪 */char buf[BUFSIZ] = {0};int realreadnum = 0;for (int i = listenfd+1; i < 1024;++i) {bzero(buf, BUFSIZ);if (FD_ISSET(i, &readfds)) {REREADPOINT:realreadnum = Read(i, buf, BUFSIZ); //真正的读取到了数据if (realreadnum == 0 ) {//在网络环境下,read函数返回0,说明是对端关闭了,也就是说,客户端关闭了//那么就应该关闭当前的connect端,并将该监听从 allfds中 移除Close(i);FD_CLR(i, &allfds);}else if (realreadnum == -1) {if (errno == EINTR) {//说明是被信号打断的,一般要重新readprintf("信号打断\n");goto REREADPOINT;}else if(errno == EAGAIN || errno == EWOULDBLOCK){printf(" WOULDBLOCK \n");//说明在打开文件的时候是使用的O_NONBLOCK方式打开的,但是没有读取到数据//当前代码是不会走到这里的,因为前面代码select的最后一个参数用的NULL,是阻塞的//一般在这里 也要重新读,但是这里有个问题,如果一直都读取不到,会不会死循环?goto REREADPOINT;}else if (errno == ECONNRESET) {//ECONNRESET 说明连接被重置了,因此要将该cfd关闭,并重新移除监听队列Close(i);FD_CLR(i, &allfds);}else {//这就是真正的有问题了,注意这里不要exit程序,应该只是让打印log//不退出程序是因为,这时候还有其他的链接连上的perror("read num <0");}}else if (realreadnum > 0 ) {//真正的读取到了客户端发送过来的数据for (int j = 0; j < realreadnum;++j) {buf[j] = toupper(buf[j]);}Write(i, buf,realreadnum);Write(STDOUT_FILENO,buf,realreadnum);}if (--nready == 0) { //有可能50个已经连接上的链接中,这时候只有3个有数据发送过来了,还是要从50个中遍历,但是不同的是,假设是 5,19,30,那么写这一块代码就能节省时间了,当遍历到30后,--nready 就会等于0,直接跳出break;    /* 跳出for, 但还在while中 */}}}}return ret;}

server 端代码优化

可以看到,select函数在有客户端和服务器端沟通的过程中,需要依次遍历对比知道具体是哪一个connectfd有数据发送过来,这很影响工作效率,因此有了如下的优化。

优化的整体思路是:当有了connectfd链接后,就将这些connectfd记录到另一个数组中,并记录这个数组的最大下标。当客户端的链接断开的时候,则将当前的下标重置

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <errno.h>
#include <sys/select.h>#include "wrap.h"
//从select1.c我们看到,代码还有可以完善的地方,这里就完善一下
//1.最后每次都要循环1020次,遍历查看哪些cfd有数据发送过来,这是不合理的
//因此我们需要搞一个数组,用这个数组记录真正的有数据访问的cfd
//并且弄一个int 值,记录这个数组的最大下标 maxi//select 实现高并发代码示例
int main() {int listenfd;//额外添加代码 startint client[FD_SETSIZE];  /* 自定义数组client, 防止遍历1024个文件描述符  FD_SETSIZE默认为1024 */int maxi;maxi = -1;                                                  /* 将来用作client[]的下标, 初始值指向0个元素之前下标位置 */for (int i = 0; i < FD_SETSIZE; i++)client[i] = -1;                                         /* 用-1初始化client[] *///额外添加代码 endint ret = 0;//第一步:创建socket。 打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符,应用程序可以像读写文件一样用read/write在网络上收发数据,如果socket()调用出错则返回-1listenfd = Socket(AF_INET, SOCK_STREAM, 0);//端口复用的代码添加上,端口复用的代码需要写在bind之前,socket之后int opt = 1; //1表示可以让端口复用,0表示不让端口复用setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//第二步 build IP+PORT到网络地址 到socket 创建出来的listenfdstruct sockaddr_in servaddr;bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(8000);Bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));//第三步,设置可以同时监听的最大的数量为1024Listen(listenfd, 1024);//第四步,这时候 //void FD_CLR(int fd, fd_set *set);//int  FD_ISSET(int fd, fd_set *set);//void FD_SET(int fd, fd_set *set);//void FD_ZERO(fd_set *set);fd_set readfds;FD_ZERO(&readfds);FD_SET(listenfd, &readfds);fd_set allfds = readfds;int maxfd = listenfd;//第五步,这时候就要弄一个循环去监听了。//第一次的时候,肯定是只监听了listenfd一个,都是后面,如果有cfd连接上的话,那就监听的多了//因此要使用一个maxfd 作为备份记录//select 函数的意义是:
//	int select(int nfds, fd_set *readfds, fd_set *writefds,
//		fd_set *exceptfds, struct timeval *timeout);
//
//	nfds: 		监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态
//	readfds:	监控有读数据到达文件描述符集合,传入传出参数
//	writefds:	监控写数据到达文件描述符集合,传入传出参数
//	exceptfds:	监控异常发生达文件描述符集合, 如带外数据到达异常,传入传出参数
//	timeout:	定时阻塞监控时间,3种情况
//	1.NULL,永远等下去
//	2.设置timeval,等待固定时间
//	3.设置timeval里时间均为0,检查描述字后立即返回,轮询
//	struct timeval {
//	long tv_sec; /* seconds */
//	long tv_usec; /* microseconds */
//};返回值:所有监听的集合中,满足事件发生的总数int nready = 0;struct sockaddr_in clie_addr;int clie_addrlen = sizeof(clie_addr);int connectfd = 0;char buf[BUFSIZ];char str[INET_ADDRSTRLEN];while (1) {readfds = allfds;nready = select(maxfd + 1, &readfds, NULL, NULL, NULL);//nready为所有监听的集合中,满足事件发生的总数//第三个参数timeval我们设置的是NULL,表示阻塞等待//因此如果有事件发生,那么就会走到后面的代码,且readfds集合中会改动成为真正//有监听事件发生后,第一步做错误判断处理if (nready < 0) {//说明有error发生perr_exit("select error");}//第六步,判断nready中是否有 新的连接事件发生,也就是说listenfd是否if (FD_ISSET(listenfd, &readfds)) {//走到这里,说明有新的链接过来了,那么我们要做如下的几件事//6.1 server赶紧连接,注意,这时候并不会阻塞,因为listenfd已经有了读取事件才会走到这里connectfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addrlen);//这里加一行log,目的是将连接的客户端的信息打印出来printf("received from %s at PORT %d\n",inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),ntohs(clie_addr.sin_port));//额外添加代码 startint i = 0;for (; i < FD_SETSIZE; i++)if (client[i] < 0) {                            /* 找client[]中没有使用的位置 */client[i] = connectfd;                         /* 保存accept返回的文件描述符到client[]里 */break;}//额外添加代码 end//这里添加一行判断,如果connectedfd已经超过1024,则不支持,select对于每个进程或者线程只能支持最多1024个if (connectfd == FD_SETSIZE) { /* 达到select能监控的文件个数上限 1024 */fputs("too many clients\n", stderr);exit(1);}//6.2 将这个新的链接添加到 监听的readfds中FD_SET(connectfd, &allfds); /* 向监控文件描述符集合allset添加新的文件描述符connfd *///6.3 更新maxfdif (connectfd > maxfd) {maxfd = connectfd;    /* 有可能最大的connectfd 需要变化,这是因为当有新的connectfd之前,如果没有任何的connectfd没有断开,则会变成最大,但是如果中间有connect断开,则这个新的connectfd的值,有可能不是最大。select第一个参数需要 *///假设之前来了,4,5,6,这时候客户端4已经断开了,这时候 connectfd就等于4,如果4,5,6都没有断开过,则这时候新来的会是7}//额外添加代码 startif (i > maxi) {maxi = i;           /* 保证maxi存的总是client[]最后一个元素下标 */}//额外添加代码 end//6.4 判断select 的返回值 是否只有一个,且是listenfd事件,如果走到这里说明这一个已经被处理了,因此就没有必要往下再执行了if (--nready == 0) {continue;}}//第七步,走到这里说明nready中有一个或者多个 客户端发送数据的事件过来。//我们当前的写法是通过循环1024次,挨个往出拿,/* 检测哪个clients 有数据就绪 */char buf[BUFSIZ] = { 0 };int realreadnum = 0;//for (int i = listenfd + 1; i < 1024; ++i) {//	bzero(buf, BUFSIZ);//	if (FD_ISSET(i, &readfds)) {//	REREADPOINT://		realreadnum = Read(i, buf, BUFSIZ); //真正的读取到了数据//		if (realreadnum == 0) {//在网络环境下,read函数返回0,说明是真的读取到文件末尾了//			//既然已经读取到了文件末尾,那么就应该关闭当前的connect端,并将该监听从 allfds中 移除//			Close(i);//			FD_CLR(i, &allfds);//		}//		else if (realreadnum == -1) {//			if (errno == EINTR) {//				//说明是被信号打断的,一般要重新read//				printf("信号打断\n");//				goto REREADPOINT;//			}//			else if (errno == EAGAIN || errno == EWOULDBLOCK)//			{//				printf(" WOULDBLOCK \n");//				//说明在打开文件的时候是使用的O_NONBLOCK方式打开的,但是没有读取到数据//				//当前代码是不会走到这里的,因为前面代码select的最后一个参数用的NULL,是阻塞的//				//一般在这里 也要重新读,但是这里有个问题,如果一直都读取不到,会不会死循环?//				goto REREADPOINT;//			}//			else if (errno == ECONNRESET) {//				//ECONNRESET 说明连接被重置了,因此要将该cfd关闭,并重新移除监听队列//				Close(i);//				FD_CLR(i, &allfds);//			}//			else {//				//这就是真正的有问题了,注意这里不要exit程序,应该只是让打印log//				//不退出程序是因为,这时候还有其他的链接连上的//				perror("read num <0");//			}//		}//		else if (realreadnum > 0) {//			//真正的读取到了客户端发送过来的数据//			for (int j = 0; j < realreadnum; ++j) {//				buf[j] = toupper(buf[j]);//			}//			Write(i, buf, realreadnum);//			Write(STDOUT_FILENO, buf, realreadnum);//		}//	}//}//额外添加代码 startint sockfd = 0;for (int i = 0; i <= maxi; i++) {  /* 检测哪个clients 有数据就绪 */if ((sockfd = client[i]) < 0)continue;bzero(buf, BUFSIZ);if (FD_ISSET(sockfd, &readfds)) {REREADPOINT:realreadnum = Read(sockfd, buf, BUFSIZ); //真正的读取到了数据if (realreadnum == 0) {//在网络环境下,read函数返回0,说明是对端关闭了,也就是说,客户端关闭了//那么就应该关闭当前的connect端,并将该监听从 allfds中 移除Close(sockfd);FD_CLR(sockfd, &allfds);printf("read done\n");//额外添加的代码 startclient[i] = -1;//额外添加的代码 end}else if (realreadnum == -1) {if (errno == EINTR) {//说明是被信号打断的,一般要重新readprintf("信号打断\n");goto REREADPOINT;}else if (errno == EAGAIN || errno == EWOULDBLOCK){printf(" WOULDBLOCK \n");//说明在打开文件的时候是使用的O_NONBLOCK方式打开的,但是没有读取到数据//当前代码是不会走到这里的,因为前面代码select的最后一个参数用的NULL,是阻塞的//一般在这里 也要重新读,但是这里有个问题,如果一直都读取不到,会不会死循环?goto REREADPOINT;}else if (errno == ECONNRESET) {//ECONNRESET 说明连接被重置了,因此要将该cfd关闭,并重新移除监听队列Close(sockfd);FD_CLR(sockfd, &allfds);//额外添加的代码 startclient[i] = -1;//额外添加的代码 end}else {//这就是真正的有问题了,注意这里不要exit程序,应该只是让打印log//不退出程序是因为,这时候还有其他的链接连上的perror("read num <0");}}else if (realreadnum > 0) {//真正的读取到了客户端发送过来的数据for (int j = 0; j < realreadnum; ++j) {buf[j] = toupper(buf[j]);}Write(sockfd, buf, realreadnum);Write(STDOUT_FILENO, buf, realreadnum);}if (--nready == 0) { //有可能50个已经连接上的链接中,这时候只有3个有数据发送过来了,还是要从50个中遍历,但是不同的是,假设是 5,19,30,那么写这一块代码就能节省时间了,当遍历到30后,--nready 就会等于0,直接跳出break;    /* 跳出for, 但还在while中 */}}}//额外添加代码 end}return ret;}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据库系统概论(超详解!!!) 第四节 关系数据库标准语言SQL(Ⅰ)

1.SQL概述 SQL&#xff08;Structured Query Language&#xff09;结构化查询语言&#xff0c;是关系数据库的标准语言 SQL是一个通用的、功能极强的关系数据库语言 SQL的动词 基本概念 基本表 &#xff1a;本身独立存在的表&#xff1b; SQL中一个关系就对应一个基本表&am…

Python将字符串转换为datetime

有这样一些字符串&#xff1a; 1710903685 20240320110125 2024-03-20 11:01:25 要转换成Python的datetime 代码如下&#xff1a; import functools import re from datetime import datetime, timedelta from typing import Union# pip install python-dateutil from date…

鸿蒙Harmony应用开发—ArkTS-@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化

上文所述的装饰器仅能观察到第一层的变化&#xff0c;但是在实际应用开发中&#xff0c;应用会根据开发需要&#xff0c;封装自己的数据模型。对于多层嵌套的情况&#xff0c;比如二维数组&#xff0c;或者数组项class&#xff0c;或者class的属性是class&#xff0c;他们的第二…

康奋威科技邀您到场参观2024长三角快递物流展

参展企业介绍 杭州康奋威科技股份有限公司创立于2005年&#xff0c;由国家“万人计划”专家任天挺先生创立并担任法人&#xff0c;是一家专业从事智能装备研发与制造的国家级高新技术企业。专注于自动化控制、机械设计、信息化方面的技术研究&#xff0c;主要为太阳能光伏、智…

水果软件FL Studio 21 for mac 21.2.3.3586破解版的最新版本2024介绍安装

音乐是人类最美好的语言&#xff0c;它能够跨越国界、文化和语言&#xff0c;将人们紧密地联系在一起。在当今数字化时代&#xff0c;音乐创作已经不再是专业人士的专利&#xff0c;越来越多的音乐爱好者开始尝试自己动手制作音乐。而FL Studio21中文版编曲软件正是这样一个为你…

MySQL | 用户管理

目前为止&#xff0c;我们一直使用的是root权限写的SQL语句。但如果我们只能用root&#xff0c;这样存在安全隐患。而MySQL是给我们提供了用户管理的&#xff0c;可以创建用户&#xff0c;提供权限&#xff0c;收回权限。 1. 用户 MySQL中的用户&#xff0c;都存储在系统数据库…

【零基础C语言】联合体(共用体)和枚举

目录 自定义类型&#xff1a;联合体(共用体)和枚举 1.自定义类型&#xff1a;联合体(共用体) 1.1 联合体的声明 1.2 联合体的特点 ​编辑1.3 联合体的大小计算 1.4使⽤联合体是可以节省空间的 1.5使用联合体写一个程序判断机器是大端还是小端存储 2.自定义类型&#xff1a;…

银行数字人民币系统应用架构设计

2019年10月&#xff0c;01区块链联合数字资产研究院发布了《人民币3.0&#xff1a;中国央行数字货币运行框架与技术解析》&#xff0c;从数字货币界定和人民币发展历程出发&#xff0c;区分了央行数字货币与比特币、移动支付等的区别&#xff0c;全面介绍了央行数字货币的发展历…

【Qt】使用Qt实现Web服务器(七):动态模板引擎

1、示例 2、源码 2.1 模板配置参数 配置文件中关于模板配置参数如下 path为存放模板的目录suffix为模板文件后缀[templates] path=templates suffix=.tpl encoding=UTF-8 cacheSize=1000000

springcloud+nacos服务注册与发现

快速开始 | Spring Cloud Alibaba 参考官方快速开始教程写的&#xff0c;主要注意引用的包是否正确。 这里是用的2022.0.0.0-RC2版本的springCloud&#xff0c;所以需要安装jdk21&#xff0c;参考上一个文章自行安装。 nacos-config实现配置中心功能-CSDN博客 将nacos-conf…

MySQL 排序的那些事儿

书接上回 上次发了几张图&#xff0c;给了几个MySQL Explain的场景&#xff0c;链接在这儿&#xff1a;你是不是MySQL老司机&#xff1f;来看看这些explain结果你能解释吗&#xff1f;MySQL 夺命6连问 我们依次来分析下这6个问题。 在分析之前&#xff0c;我们先来了解一下M…

GaussDB WDR分析之集群报告篇

AWR报告目前已经成为Oracle DBA分析问题&#xff0c;定位故障最为重要的报告&#xff0c;阅读与分析AWR报告的技能也是Oracle DBA必备的技能。国产数据库为了提高运维便捷性&#xff0c;都在做类似Oracle AWR报告的模仿&#xff0c;只不过由于指标体系不够完善&#xff0c;因此…

每日一题——LeetCode2549.统计桌面上的不同数字

方法一 模拟 维护一个数组arr&#xff0c;初始值为n,每次循环将arr[i] % j(1<j<n) 如果结果为1则将j加入&#xff0c; 最后将arr转为Set集合去重&#xff0c;Set的长度就是答案 var distinctIntegers function(n) {let arr[]arr.push(n)for(let i0;i<arr.length;i…

Spring Boot1

SpringBoot概述 Spring Boot是Spring提供的一个子项目&#xff0c;用于快速构建Spring应用程序 SpringBoot特性 起步依赖 本质上就是一个Maven坐标&#xff0c;整合了完成一个功能所需要的所有坐标 自动配置 遵循约定大于配置的原则&#xff0c;再boot程序启动后&#xff0…

阿里云4核16G服务器价格26.52元1个月、149.00元半年,ECS经济型e实例

阿里云4核16G服务器优惠价格26.52元1个月、79.56元3个月、149.00元半年&#xff0c;配置为阿里云服务器ECS经济型e实例ecs.e-c1m4.xlarge&#xff0c;4核16G、按固定带宽 10Mbs、100GB ESSD Entry系统盘&#xff0c;活动链接 aliyunfuwuqi.com/go/aliyun 活动链接打开如下图&a…

zookeeper快速入门(合集)

zookeeper作为一个分布式协调框架&#xff0c;它的创建就是为了方便或者简化分布式应用的开发。除了服务注册与发现之外&#xff0c;它还能够提供更多的功能&#xff0c;但是对于入门来说&#xff0c;看这一篇就够了。后续会讲zookeeper的架构设计与原理&#xff0c;比如zookee…

Docker入门到实践之环境配置

Docker入门到实践之环境配置 docker 环境安装 Ubuntu/Debian: sudo apt update sudo apt install docker.ioCentOS/RHEL: sudo yum install dockerArch Linux: sudo pacman -S docker如果未安装成功&#xff0c;或者env的path未设置成功&#xff0c;运行时会报错 Bash: Do…

怎么拆解台式电脑风扇CPU风扇的拆卸步骤-怎么挑

今天我就跟大家分享一下如何选购电脑风扇的知识。 我也会解释一下机箱散热风扇一般用多少转。 如果它恰好解决了您现在面临的问题&#xff0c;请不要忘记关注本站并立即开始&#xff01; 文章目录列表&#xff1a;大家一般机箱散热风扇都用多少转&#xff1f; 机箱散热风扇选择…

力扣501 二叉搜索树中的众数 Java版本

文章目录 题目描述代码使用非递归的方法使用递归的方法并且遍历的同时统计众数 题目描述 给你一个含重复值的二叉搜索树&#xff08;BST&#xff09;的根节点 root &#xff0c;找出并返回 BST 中的所有 众数&#xff08;即&#xff0c;出现频率最高的元素&#xff09;。 如果…

web自动化--元素定位之xpath和css

元素定位 xpath绝对路径相对路径案例xpath策略&#xff08;路径&#xff09;案例xpath策略&#xff08;层级、扩展&#xff09;属性层级与属性层级与属性拓展层级与属性综合 csscss选择器&#xff08;id、类、标签、属性&#xff09;id选择器类选择器标签选择器属性选择器案例-…