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位清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

返回值:所有监听的集合中,满足事件发生的总数

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()调用出错则返回-1

	listenfd = Socket(AF_INET, SOCK_STREAM, 0);

	//第二步 build IP+PORT到网络地址 到socket 创建出来的listenfd
	struct 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));
	
	//第三步,设置可以同时监听的最大的数量为1024
	Listen(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 更新maxfd
			if (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) {
						//说明是被信号打断的,一般要重新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);
				}
				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;
	//额外添加代码 start
	int 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[] */

	//额外添加代码 end

	int ret = 0;
	//第一步:创建socket。 打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符,应用程序可以像读写文件一样用read/write在网络上收发数据,如果socket()调用出错则返回-1

	listenfd = 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 创建出来的listenfd
	struct 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));

	//第三步,设置可以同时监听的最大的数量为1024
	Listen(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));

			//额外添加代码 start
			int 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 更新maxfd
			if (connectfd > maxfd) {
				maxfd = connectfd;    /* 有可能最大的connectfd 需要变化,这是因为当有新的connectfd之前,如果没有任何的connectfd没有断开,则会变成最大,但是如果中间有connect断开,则这个新的connectfd的值,有可能不是最大。select第一个参数需要 */
									//假设之前来了,4,5,6,这时候客户端4已经断开了,这时候 connectfd就等于4,如果4,5,6都没有断开过,则这时候新来的会是7
			}
			//额外添加代码 start
			if (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);
		//		}
		//	}
		//}

		//额外添加代码 start

		int 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");
					//额外添加的代码 start
					client[i] = -1;
					//额外添加的代码 end
				}
				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(sockfd);
						FD_CLR(sockfd, &allfds);
						//额外添加的代码 start
						client[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.mfbz.cn/a/481224.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Leetcode739. 每日温度

Every day a Leetcode 题目来源&#xff1a;739. 每日温度 解法1&#xff1a;单调栈-从左到右 单调栈中记录还没算出「下一个更大元素」的那些数&#xff08;的下标&#xff09;。 代码&#xff1a; /** lc appleetcode.cn id739 langcpp** [739] 每日温度*/// lc codesta…

深入理解指针03

1. 字符指针变量 在指针的类型中我们知道有⼀种指针类型为字符指针char*; ⼀般使⽤: int main(){char ch w;char *pc &ch;*pc w;return 0;} 还有⼀种使⽤⽅式如下: int main() {const char* pstr "hello world";//这⾥是把⼀个字符串放到pstr指针变量⾥了…

【Linux实践室】Linux用户管理实战指南:新建与删除用户操作详解

&#x1f308;个人主页&#xff1a;聆风吟_ &#x1f525;系列专栏&#xff1a;Linux实践室、网络奇遇记 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 一. ⛳️任务描述二. ⛳️相关知识2.1 &#x1f514;Linux创建用户命令2.1.1 知识点讲解2.1.2 案…

OceanMind海睿思入选中国信通院《2023高质量数字化转型技术解决方案集》

近日&#xff0c;由中国信息通信研究院“铸基计划”编制的《2023高质量数字化转型技术解决方案集&#xff08;第一版&#xff09;》正式发布。 中新赛克海睿思 凭借卓越的产品力以及广泛的行业实践&#xff0c;成功入选该方案集的数据分析行业技术解决方案。 为促进数字化转型…

探索uni-app项目的架构与开发实践:快速开发的项目模板参考

摘要&#xff1a;本文将深入探讨uni-app项目架构的模板设计&#xff0c;以及如何通过使用该模板实现快速开发。我们将重点介绍模板中的组件示例、SDK示例和模板页面&#xff0c;并阐述它们在提高开发效率和优化用户体验方面的作用。 一、引言 随着移动互联网的迅猛发展&#…

每日一题 --- 209. 长度最小的子数组[力扣][Go]

长度最小子数组 题目&#xff1a; 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续 子数组 [numsl, numsl1, ..., numsr-1, numsr] &#xff0c;并返回其长度**。**如果不存在符合条件的子数组&#xff0c…

Learn OpenGL 19 几何着色器

几何着色器 在顶点和片段着色器之间有一个可选的几何着色器(Geometry Shader)&#xff0c;几何着色器的输入是一个图元&#xff08;如点或三角形&#xff09;的一组顶点。几何着色器可以在顶点发送到下一着色器阶段之前对它们随意变换。然而&#xff0c;几何着色器最有趣的地方…

4.KubeSphereV3.4-DevOps配置maven私服

1.修改方法 maven容器模板中用的是中央仓库打包&#xff0c;但是我们打包需要用到私服。那么我们需要将私服配置到容器settings.xml中。 以 admin账号登录 在配置字典里找到 ks-devops-agent 将MavenSetting中mirror标签里配上私服地址&#xff1a; <?xml version"…

印刷企业实施MES管理系统如何做好需求分析

在数字化、信息化的大潮中&#xff0c;印刷企业面临着转型升级的迫切需求。MES管理系统作为连接企业资源计划ERP和现场自动化系统的桥梁&#xff0c;对于提升印刷企业的生产效率、优化资源配置、提高产品质量具有重要意义。因此&#xff0c;做好MES管理系统的需求分析&#xff…

大模型 - 相关工程总结(待更新

文章目录 下图转自 公众号 AI工程化 推理执行引擎 server vLLM vLLM 部署 Qwen https://ezcode.blog.csdn.net/article/details/135947607 HF PipelineTGIDeepSpeed-MIITensorRT-LLM pc/edge ggmlollama https://ezcode.blog.csdn.net/article/details/136482825mlc-llm Web …

不启动BMIDE,Teamcenter如何查看property的real property name

问题描述&#xff1a; Teamcenter客户端&#xff0c;查看Item 属性&#xff0c;属性名称默认显示的是Display Name。 在各类开发过程中&#xff0c;对属性的操作&#xff0c;需要使用real property name才能进行。开发可能不在server端&#xff0c;没有安装BMIDE&#xff0c;如…

新手小白学剪辑视频的知识点,什么是视频分辨率和位深度?

新手小白需要了解的视频剪辑知识点&#xff0c;什么是视频分辨率尺寸(文件大小)和位深度&#xff1f; 分辨率尺寸/文件大小 常见的视频分辨率是高清和 4K。高清素材的屏幕像素&#xff08;宽度 x 高度&#xff09;测量值通常为 1920 x 1080&#xff0c;而 4K 素材是其四倍&am…

二叉树的层次遍历经典问题-算法通关村

二叉树的层次遍历经典问题-算法通关村 1 层次遍历简介 广度优先在面试里出现的频率非常高&#xff0c;整体属于简单题。广度优先又叫层次遍历&#xff0c;基本过程如下&#xff1a; 层次遍历就是从根节点开始&#xff0c;先访问根节点下面一层全部元素&#xff0c;再访问之后…

51单片机入门:定时器

定时器的介绍 定时器&#xff1a;51单片机的定时器属于单片机的内部资源&#xff0c;其电路的设计连接和运转均在单片机内部完成。根据单片机内部的时钟或者外部的脉冲信号对寄存器中的数据加1&#xff0c;定时器实质就是加1计数器。因为又可以定时又可以计数&#xff0c;又称…

禁欲28天!一宅男居然肝出如此详细Web安全学习笔记,学妹看完直接抽搐了!

1.1. Web技术演化 1.1.1. 简单网站 1.1.1.1. 静态页面 Web技术在最初阶段&#xff0c;网站的主要内容是静态的&#xff0c;大多站点托管在ISP上&#xff0c;由文字和图片组成&#xff0c;制作和表现形式也是以表格为主。当时的用户行为也非常简单&#xff0c;基本只是浏览网…

51单片机—直流电机

1.元件介绍 2.驱动电路 3.电机调速 一般会保证一个周期的时间是一样的 应用&#xff1a; 1.LED呼吸灯 #include <REGX52.H>sbit LEDP2^0;void Delay(unsigned int t) {while(t--); } void main() {unsigned char Time,i;while(1){for(Time0;Time<100;Time){for(i0;…

Linux相关命令(1)

1、找出文件夹下包含 “aaa” 同时不包含 “bbb”的文件&#xff0c;然后把他们重新生成一下。要求只能用一行命令。 find ./ -type f -name "*aaa*" ! -name "*bbb*" -exec touch {} \;文件系统操作命令 df&#xff1a;列出文件系统的整体磁盘使用情况 …

RocketMQ基础知识和常见问题

RocketMQ 是一个 队列模型 的消息中间件&#xff0c;具有高性能、高可靠、高实时、分布式 的特点。它是一个采用 Java 语言开发的分布式的消息系统&#xff0c;由阿里巴巴团队开发&#xff0c;在 2016 年底贡献给 Apache&#xff0c;成为了 Apache 的一个顶级项目。 在阿里内…

Linux 创建交换空间

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

明牌空投:Cosmos生态项目Joltify零撸教程

简介&#xff1a;Joltify Finance 是基于Cosmos SDK的Layer1公链&#xff0c;做RWA赛道的&#xff0c;它可以将加密世界中的大量流动性与现实世界的金融资产合并&#xff0c;将有形资产转换为代币或NFT的过程&#xff0c;使它们能够在链上进行交易&#xff0c;从而在DeFi和传统…