Linux网络编程:多路I/O转接服务器(select poll epoll)

文章目录:

一:select

1.基础API 

select函数

思路分析

select优缺点

2.server.c

3.client.c

二:poll

1.基础API 

poll函数 

poll优缺点

read函数返回值

突破1024 文件描述符限制

2.server.c

3.client.c

三:epoll

1.基础API

epoll_create创建   epoll_ctl操作  epoll_wait阻塞

epoll实现多路IO转接思路

epoll优缺点 

ctags使用

2.server.c

3.client.c

4.事件模型(epoll 事件触发模型ET和LT)

4.1 server.c

4.2 client.c

5.epoll 反应堆模型


select、poll以及epoll都是系统内核来对网络通信中的通信套接字(文件描述符)来进行监视
能够在与服务器连接的大量客户端中识别出与服务器请求了数据交换的客户端,并把它们所对应的套接字通过函数返回,交给服务器
此时服务器只需要和请求了数据交换的客户端进行通信即可,而其它的套接字则不做任何处理

因此,比起服务器自身每次去轮询查询并处理每个套接字的效率要高很多

一:select

1.基础API 

select函数

 

原理:  借助内核, select 来监听, 客户端连接、数据通信事件


//将给定的套接字fd从位图set中清除出去
    void FD_CLR(int fd,fd_set* set);			
        FD_CLR(4, &rset);                    将一个文件描述符从监听集合中 移除

//检查给定的套接字fd是否在位图里面,返回值 在1 不在0
    int FD_ISSET(int fd,fd_set* set);	
        FD_ISSET(4,&rset);                   判断一个文件描述符是否在监听集合中

//将给定的套接字fd设置到位图set中		
    void FD_SET(int fd,fd_set* set);            将待监听的文件描述符,添加到监听集合中	
        FD_SET(3, &rset);	
        FD_SET(5, &rset);	
        FD_SET(6, &rset);

//将整个位图set置零		
    void FD_ZERO(fd_set* set);					
        fd_set rset;                            清空一个文件描述符集
		FD_ZERO(&rset);


//select 是一个系统调用,用于监控多个文件描述符(sockets, files等)的 I/O 活动
//它等待某个文件描述符集变为可读、可写或出现异常,然后返回	
    int select(int nfds, 
               fd_set *readfds, 
               fd_set *writefds,
               fd_set *exceptfds, 
               struct timeval *timeout);

		nfds     :监听 所有文件描述符中,最大文件描述符+1
		readfds  :读   文件描述符监听集合。	传入、传出参数
		writefds :写   文件描述符监听集合。	传入、传出参数		NULL
		exceptfds:异常 文件描述符监听集合	传入、传出参数		NULL

		timeout: 	
                > 0 : 设置监听超时时长
				NULL:阻塞监听
				0   :非阻塞监听,轮询

		返回值:
			> 0:所有监听集合(3个)中, 满足对应事件的总数
			  0:没有满足监听条件的文件描述符
			 -1:errno

思路分析

	int maxfd = 0;
	lfd = socket() ;			    创建套接字
	maxfd = lfd;                   备份
	bind();					        绑定地址结构
	listen();				        设置监听上限

	fd_set rset, allset;			创建r读监听集合
	FD_ZERO(&allset);				将r读监听集合清空
	FD_SET(lfd, &allset);			将 lfd 添加至读集合中
        lfd文件描述符在监听期间没有满足读事件发生,select返回的时候rset不会在集合中

	while(1) {
		rset = allset;			                                保存监听集合
		ret  = select(lfd+1, &rset, NULL, NULL, NULL);		监听文件描述符集合对应事件
		if(ret > 0) {							                有监听的描述符满足对应事件
            //处理连接:一次监听		
			if (FD_ISSET(lfd, &rset)) {				            1 在集合中,0不在
				cfd = accept();				                建立连接,返回用于通信的文件描述符
				maxfd = cfd;
				FD_SET(cfd, &allset);				            添加到监听通信描述符集合中
			}
            //处理通信:剩下的
			for (i = lfd+1; i <= 最大文件描述符; i++){
                //嵌套
				    FD_ISSET(i, &rset)				            有read、write事件
				read()
				小 -- 大
				write();
			}	
		}
	}

select优缺点

 当你只需要监听几个指定的套接字时, 需要对整个1024的数组进行轮询, 效率降低

缺点:监听上限受文件描述符限制。 最大1024
      检测满足条件的fd,自己添加业务逻辑提高小,提高了编码难度

      如果监听的文件描述符比较散乱、而且数量不多,效率会变低


优点:	跨平台win、linux、macOS、Unix、类Unix、mips

2.server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>

#include "wrap.h"

#define SERV_PORT 6666


void FD_CLR(int fd,fd_set* set);			//将给定的套接字fd从位图set中清除出去
int FD_ISSET(int fd,fd_set* set);			//检查给定的套接字fd是否在位图里面,返回0或1
void FD_SET(int fd,fd_set* set);			//将给定的套接字fd设置到位图set中
void FD_ZERO(fd_set* set);					//将整个位图set置零


int main(int argc, char *argv[]){
	int i, j, n, maxi;
	
		/*数组:将需要轮询的客户端套接字放入数组client[FD_SETSIZE],防止遍历1024个文件描述符  FD_SETSIZE默认为1024*/
		int nready, client[FD_SETSIZE];		
		
		int listenFd, connectFd, maxFd, socketFd;
		
		char buf[BUFSIZ], str[INET_ADDRSTRLEN];					//#define INET_ADDRSTRLEN 16

		struct sockaddr_in serverAddr, clientAddr;
		
		socklen_t clientAddrLen;
		
		fd_set rset, allset;                            		//rset读事件文件描述符集合,allset用来暂存
	
	
	/*得到监听套接字*/
		listenFd = Socket(AF_INET, SOCK_STREAM, 0);
			/*定义两个集合,将listenFd放入allset集合当中*/
		fd_set rset, allset;
		FD_ZERO(&allset);										//将整个位图set置零
		//将给定的套接字fd设置到位图set中
		FD_SET(listenFd, &allset);								//将connectFd加入集合:构造select监控文件描述符集
	
	
	/*设置地址端口复用*/
		int opt = 1;
		setsockopt(listenFd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt));
	
	
	/*填写服务器地址结构*/
		bzero(&serverAddr, sizeof(serverAddr));
		
		serverAddr.sin_family = AF_INET;
		serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
		serverAddr.sin_port = htons(SERVER_PORT);
	
	
	/*绑定服务器地址结构*/
		Bind(listenFd, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
		Listen(listenFd, 128);
	
	
	/*将listenFd设置为数组中最大的Fd*/
		maxFd = listenFd;										//起初 listenfd 即为最大文件描述符
		maxi = -1;												//将来用作client[]的下标, 初始值指向0个元素之前下标位置
		
	
	/*数组:初始化自己的数组为-1*/
		for (i = 0; i < FD_SETSIZE; ++i)
			client[i] = -1;

	while (1){
		/*把allset给rest,让他去用*/
		rset = allset;											//备份:每次循环时都从新设置select监控信号集
		nready = select(maxFd + 1, &rset, NULL, NULL, NULL);	//使用select监听文件描述符集合对应事件

		if (nready == -1)										//出错返回
			perr_exit("select error");

		/*listen满足监听的事件:如果有了新的连接请求,得到connectFd,并将其放入自定义数组中*/
			if (FD_ISSET(listenFd, &rset)){						//检查给定的套接字fd是否在位图里面,返回0或1
				clientAddrLen = sizeof(clientAddr);
				
				//建立链接,不会阻塞
				connectFd = Accept(listenFd, (struct sockaddr *)&clientAddr, &clientAddrLen);
				
				printf(
					"Recived from %s at PORT %d\n", 
					inet_ntop(AF_INET, 
					&(clientAddr.sin_addr.s_addr), 
					str, 
					sizeof(str)), 
					ntohs(clientAddr.sin_port));

				for (i = 0; i < FD_SETSIZE; ++i)
					if (client[i] < 0){							//找client[]中没有使用的位置
						client[i] = connectFd;					//保存accept返回的文件描述符到client[]里	
						break;
					}
					
				/*自定义数组满了:达到select能监控的文件个数上限 1024 */
					if(i==FD_SETSIZE){
						fputs("Too many clients\n",stderr);
						exit(1);
					}
				
				/*connectFd加入监听集合:向监控文件描述符集合allset添加新的文件描述符connectFd*/
					FD_SET(connectFd, &allset);					//将给定的套接字fd设置到位图set中

				/*更新最大的Fd*/
					if (maxFd < connectFd)
						maxFd = connectFd;
					
				/*更新循环上限*/
					if(i>maxi)
						maxi=i;									//保证maxi存的总是client[]最后一个元素下标
					
				/*select返回1,说明只有建立连接请求,没有数据传送请求,跳出while循环剩余部分(下面的for循环轮询过程)*/
				//如果只有listen事件,只需建立连接即可,无需数据传输,跳出循环剩余部分
					if (--nready == 0)
						continue;
			}
		/*检测哪个clients 有数据就绪:select返回不是1,说明有connectFd有数据传输请求,遍历自定义数组*/
		//否则,说明有数据传输需求
			for (i = 0; i <= maxi; ++i){
				if((socketFd=client[i])<0)
					continue;
					
				/*遍历检查*/
				if (FD_ISSET(socketFd, &rset)){					//检查给定的套接字fd是否在位图里面,返回0或1
					/*read返回0说明传输结束,关闭连接:当client关闭链接时,服务器端也关闭对应链接*/
					if ((n=read(socketFd,buf,sizeof(buf)))==0){
						close(socketFd);
						//将给定的套接字fd从位图set中清除出去
						FD_CLR(socketFd, &allset);				//解除select对此文件描述符的监控
						client[i]=-1;
					}else if(n>0){
						for (j = 0; j < n; ++j)
							buf[j] = toupper(buf[j]);
						write(socketFd, buf, n);
						write(STDOUT_FILENO, buf, n);
					}
					
					/*不懂:需要处理的个数减1?*/
					if(--nready==0)
						break;									//跳出for, 但还在while中
				}
			}
	}
	close(listenFd);
	return 0;
}

3.client.c

/* client.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 6666

int main(int argc, char *argv[])
{
    struct sockaddr_in servaddr;
    char buf[MAXLINE];
    int sockfd, n;

    if (argc != 2) {
        printf("Enter: ./client server_IP\n");
        exit(1);
    }

    sockfd = Socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
    servaddr.sin_port = htons(SERV_PORT);

    Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    printf("------------connect ok----------------\n");

    while (fgets(buf, MAXLINE, stdin) != NULL) {
        Write(sockfd, buf, strlen(buf));
        n = Read(sockfd, buf, MAXLINE);
        if (n == 0) {
            printf("the other side has been closed.\n");
            break;
        }
        else
            Write(STDOUT_FILENO, buf, n);
    }
    Close(sockfd);

    return 0;
}

二:poll

这个函数是一个半成品,用的很少 

1.基础API 

poll函数 

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
		fds:监听的文件描述符,传入传出【数组】
			struct pollfd {			
				  int fd      :待监听的文件描述符				
				  short events:待监听的文件描述符对应的监听事件
						  取值:POLLIN、POLLOUT、POLLERR
				 short revnets:
                            传入时,给0
                            如果满足对应事件的话, 返回 非0 --> POLLIN、POLLOUT、POLLERR
			}

		nfds: 监听数组的,实际有效监听个数

		timeout:  
             > 0:超时时长。单位:毫秒
			  -1:阻塞等待
			   0:不阻塞

		返回值:返回满足对应监听事件的文件描述符 总个数

poll优缺点

优点:
		自带数组结构。 可以将 监听事件集合 和 返回事件集合 分离
		拓展 监听上限。 超出 1024限制


缺点:
		不能跨平台。 Linux
		无法直接定位满足监听事件的文件描述符, 编码难度较大

read函数返回值

	> 0: 实际读到的字节数

	=0: socket中,表示对端关闭。close()

	-1:	
        如果 errno == EINTR                    被异常终端                         需要重启
		如果 errno == EAGIN 或 EWOULDBLOCK     以非阻塞方式读数据,但是没有数据    需要,再次读
		如果 errno == ECONNRESET               说明连接被 重置                    需要 close(),移除监听队列
		错误

突破1024 文件描述符限制

cat /proc/sys/fs/file-max     ——> 当前计算机所能打开的最大文件个数。 受硬件影响

ulimit -a 	                  ——> 当前用户下的进程,默认打开文件描述符个数。  缺省为 1024

修改:
    打开 sudo vi /etc/security/limits.conf, 写入:
        * soft nofile 65536			    --> 设置默认值, 可以直接借助命令修改。 【注销用户,使其生效】
        * hard nofile 100000			--> 命令修改上限

2.server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <errno.h>
#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 6666
#define OPEN_MAX 1024



int main(int argc,char* argv[]){
	int ret=0;
	/*poll函数返回值*/
	int nready=0;
	int i,j,maxi;
	int connectFd,listenFd,socketFd;
	ssize_t n;
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	socklen_t clientLen;
	
	
	
	/*创建结构体数组*/	
		struct pollfd client[OPEN_MAX];
	
	/*创建客户端地址结构和服务器地址结构*/
		struct sockaddr_in clientAddr,serverAddr;
	
	/*得到监听套接字listenFd*/
		listenFd=Socket(AF_INET,SOCK_STREAM,0);
	
	
	
	/*设置地址可复用*/
		int opt=0;
		ret=setsockopt(listenFd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(opt));
		if(ret==-1)
			perr_exit("setsockopt error");
		
	/*向服务器地址结构填入内容*/
		bzero(&serverAddr,sizeof(serverAddr));
		serverAddr.sin_family=AF_INET;
		serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
		serverAddr.sin_port=htons(SERVER_PORT);
	
	/*绑定服务器地址结构到监听套接字,并设置监听上限*/
		Bind(listenFd,(const struct sockaddr*)&serverAddr,sizeof(serverAddr));
		Listen(listenFd,128);
	
	/*初始化第一个pollfd为监听套接字*/
		client[0].fd=listenFd;					//listenfd监听普通读事件 
		client[0].events=POLLIN;				//事件已经准备好被读取或处理
	
	/*将pollfd数组的余下内容的fd文件描述符属性置为-1*/
		for(i=1;i<OPEN_MAX;++i)
			client[i].fd=-1;					//用-1初始化client[]里剩下元素
			
		maxi=0;									//client[]数组有效元素中最大元素下标
	
		while(1){
		/*nready是有多少套接字有POLLIN请求*/
			nready=poll(client,maxi+1,-1);		//阻塞
			if(nready==-1)
				perr_exit("poll error");
				
			
		/*如果listenFd的revents有POLLIN请求,则调用Accept函数得到connectFd*/
			if(client[0].revents&POLLIN){		//有客户端链接请求
				clientLen=sizeof(clientAddr);
				connectFd=Accept(listenFd,(struct sockaddr*)&clientAddr,&clientLen);
				
				/*打印客户端地址结构信息*/
					printf("Received from %s at PORT %d\n",
							inet_ntop(AF_INET,&(clientAddr.sin_addr.s_addr),str,sizeof(str)),
							ntohs(clientAddr.sin_port));
					
				/*将创建出来的connectFd加入到pollfd数组中*/
					for(i=1;i<OPEN_MAX;++i)
						if(client[i].fd<0){
							//找到client[]中空闲的位置,存放accept返回的connfd 
							client[i].fd=connectFd;			
							break;
						}

					if(i==OPEN_MAX)
						perr_exit("Too many clients,I'm going to die...");
						
				/*当没有错误时,将对应的events设置为POLLIN*/
					client[i].events=POLLIN;	//设置刚刚返回的connfd,监控读事件

					if(i>maxi)						
						maxi=i;					//更新client[]中最大元素下标
					if(--nready<=0)
						continue;				//没有更多就绪事件时,继续回到poll阻塞
			}
		
		
		/*开始从1遍历pollfd数组*/
			for(i=1;i<=maxi;++i){				//检测client[] 
				/*到结尾了或者有异常*/
					if((socketFd=client[i].fd)<0)
						continue;
						
				/*第i个客户端有连接请求,进行处理	read*/
				if(client[i].revents&POLLIN){
					if((n=read(socketFd,buf,sizeof(buf)))<0){
						/*出错时进一步判断errno*/
							if(errno=ECONNRESET){
								printf("client[%d] aborted connection\n",i);
								close(socketFd);
								client[i].fd=-1;
							}else
								perr_exit("read error");
					}else if(n==0){
						/*read返回0,说明读到了结尾,关闭连接*/
							printf("client[%d] closed connection\n",i);
							close(socketFd);
							client[i].fd=-1;
					}else{
						/*数据处理*/
							for(j=0;j<n;++j)
								buf[j]=toupper(buf[j]);
							Writen(STDOUT_FILENO,buf,n);
							Writen(socketFd,buf,n);
					}
					if(--nready==0)
						break;
				}
			}
	}
	return 0;
}

3.client.c

/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 6666

int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr;
	char buf[MAXLINE];
	int sockfd, n;

	sockfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	servaddr.sin_port = htons(SERV_PORT);

	Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	while (fgets(buf, MAXLINE, stdin) != NULL) {
		Write(sockfd, buf, strlen(buf));
		n = Read(sockfd, buf, MAXLINE);
		if (n == 0)
			printf("the other side has been closed.\n");
		else
			Write(STDOUT_FILENO, buf, n);
	}
	Close(sockfd);
	return 0;
}

三:epoll

epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率:都连接但不发送数据 

1.基础API

红黑树

lfd数据连接


cfd数据通信

epoll_create创建   epoll_ctl操作  epoll_wait阻塞

int epoll_create(int size);						                                    创建一棵监听红黑树
		size:创建的红黑树的监听节点数量(仅供内核参考)
		返回值:
            成功:指向新创建的红黑树的根节点的 fd
			失败: -1 errno


int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);	                 操作控制监听红黑树
		epfd:epoll_create 函数的返回值 epfd

		op  :对该监听红黑数所做的操作
			EPOLL_CTL_ADD 添加fd到 监听红黑树
			EPOLL_CTL_MOD 修改fd在 监听红黑树上的监听事件
			EPOLL_CTL_DEL 将一个fd 从监听红黑树上摘下(取消监听)

		fd:待监听的fd			

		event:本质struct epoll_event 结构体 地址
			成员 events:EPOLLIN / EPOLLOUT / EPOLLERR				
                EPOLLIN :	表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
		        EPOLLOUT:	表示对应的文件描述符可以写
		        EPOLLPRI:	表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
		        EPOLLERR:	表示对应的文件描述符发生错误
		        EPOLLHUP:	表示对应的文件描述符被挂断;
		        EPOLLET: 	将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)而言的
		        EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
                
			成员 typedef union epoll_data: 联合体(共用体)
				int fd;	      对应监听事件的 fd
				void *ptr; 
				uint32_t u32;
				uint64_t u64;		

		返回值:成功 0; 失败: -1 errno


int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 	   阻塞监听
		epfd:epoll_create 函数的返回值 epfd
		events:传出参数,【数组】, 满足监听条件的 哪些 fd 结构体
		maxevents:数组 元素的总个数 1024(不是字节数)				
			       struct epoll_event evnets[1024]
		timeout:
			-1: 阻塞————通过等待某些特定条件出现来实现的,而在等待的过程中,程序的其他部分都会被暂停执行
			 0:不阻塞
			>0: 超时时间 (毫秒)

		read返回值:
			> 0: 满足监听的 总个数,可以用作循环上限
			  0:没有fd满足监听事件
			 -1:失败,errno

epoll实现多路IO转接思路

lfd = socket();			                    监听连接事件lfd
bind();
listen();


int epfd = epoll_create(1024);				    epfd, 监听红黑树的树根

    struct epoll_event tep, ep[1024];			tep, 用来设置单个fd属性, ep是epoll_wait() 传出的满足监听事件的数组
    tep.events = EPOLLIN;					    初始化  lfd的监听属性_文件描述符可以读
    tep.data.fd = lfd
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &tep);		将 lfd 添加到监听红黑树上


while (1) {
	ret = epoll_wait(epfd, ep,1024, -1);		                           阻塞监听

	for (i = 0; i < ret; i++) {		
        //lfd数据连接
		if (ep[i].data.fd == lfd) {				                           lfd 满足读事件,有新的客户端发起连接请求
			cfd = Accept();

			tep.events = EPOLLIN;				                           初始化  cfd的监听属性_文件描述符可以读
			tep.data.fd = cfd;

			epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tep);                    将 cfd 添加到监听红黑树上
		}
         //cfd数据通信
         else {						                                       cfd 们 满足读事件, 有客户端写数据来
			n = read(ep[i].data.fd, buf, sizeof(buf));
			if ( n == 0) {
				close(ep[i].data.fd);
				epoll_ctl(epfd, EPOLL_CTL_DEL, ep[i].data.fd , NULL);	   将关闭的cfd,从监听树上摘下
			} else if (n > 0) {
				小--大
				write(ep[i].data.fd, buf, n);
			}
		}
	}
}

epoll优缺点 

优点:
		高效。突破1024文件描述符

缺点:
		不能跨平台。 Linux

ctags使用

是vim下方便代码阅读的工具1

    `ctags ./* -R`在项目目录下生成ctags文件;
    
    `Ctrl+]`跳转到函数定义的位置;

    `Ctrl+t`返回此前的跳转位置;

    `Ctrl+o`屏幕左边列出文件列表, 再按关闭;

    `F4`屏幕右边列出函数列表, 再按关闭;



(还是VSCode比较香)

2.server.c

#include "033-035_wrap.h"

#define SERVER_PORT 9527
#define MAXLINE     80
#define OPEN_MAX    1024

int main(int argc,char* argv[]){
    int i=0,n=0,num=0;
    int clientAddrLen=0;
    int listenFd=0,connectFd=0,socketFd=0;
    ssize_t nready,efd,res;
    char buf[MAXLINE],str[INET_ADDRSTRLEN];

    struct sockaddr_in serverAddr,clientAddr;
	
    /*创建一个临时节点temp和一个数组ep*/
		struct epoll_event temp;
		struct epoll_event ep[OPEN_MAX];




    /*创建监听套接字*/
		listenFd=Socket(AF_INET,SOCK_STREAM,0);
	
    /*设置地址可复用*/
		int opt=1;
		setsockopt(listenFd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(opt));

    /*初始化服务器地址结构*/
		bzero(&serverAddr,sizeof(serverAddr));
		serverAddr.sin_family=AF_INET;
		serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
		serverAddr.sin_port=htons(SERVER_PORT);

    /*绑定服务器地址结构*/
		Bind(listenFd,(const struct sockaddr*)&serverAddr,sizeof(serverAddr));
		
    /*设置监听上限*/
		Listen(listenFd,128);

    /*创建监听红黑树树根*/
		efd=epoll_create(OPEN_MAX);
		if(efd==-1)
			perr_exit("epoll_create error");

    /*将listenFd加入监听红黑树中*/
		temp.events=EPOLLIN;
		temp.data.fd=listenFd;
		res=epoll_ctl(efd,EPOLL_CTL_ADD,listenFd,&temp);
		if(res==-1)
			perr_exit("epoll_ctl error");

    while(1){
        /*阻塞监听写事件*/
			nready=epoll_wait(efd,ep,OPEN_MAX,-1);
			if(nready==-1)
				perr_exit("epoll_wait error");

        /*轮询整个数组(红黑树)*/
			for(i=0;i<nready;++i){
				if(!(ep[i].events&EPOLLIN))
					continue;

            /*如果是建立连接请求*/
				// lfd 满足读事件,有新的客户端发起连接请求
				if(ep[i].data.fd==listenFd){
					clientAddrLen=sizeof(clientAddr);
					connectFd=Accept(listenFd,(struct sockaddr*)&clientAddr,&clientAddrLen);
					
					printf("Received from %s at PORT %d\n",
							inet_ntop(AF_INET,
							&clientAddr.sin_addr.s_addr,
							str,
							sizeof(str)),
							ntohs(clientAddr.sin_port));
					printf("connectFd=%d,client[%d]\n",connectFd,++num);

					/*将新创建的连接套接字加入红黑树*/
						//初始化  cfd的监听属性_文件描述符可以读
							temp.events=EPOLLIN;
							temp.data.fd=connectFd;
							
							res=epoll_ctl(efd,EPOLL_CTL_ADD,connectFd,&temp);
						
						if(res==-1)
							perr_exit("epoll_ctl errror");
				}else{
					/*不是建立连接请求,是数据处理请求*/
						socketFd=ep[i].data.fd;
						//cfd 们 满足读事件, 有客户端写数据来
						n=read(socketFd,buf,sizeof(buf));
				
						/*读到0说明客户端关闭*/
							//已经读到结尾
							if(n==0){
								res=epoll_ctl(efd,EPOLL_CTL_DEL,socketFd,NULL);
								if(res==-1)
									perr_exit("epoll_ctl error");
								close(socketFd);
								printf("client[%d] closed connection\n",socketFd);
							//报错
							}else if(n<0){	
								/*n<0报错*/
									perr_exit("read n<0 error");
									
									// 将关闭的cfd,从监听树上摘下
										res=epoll_ctl(efd,EPOLL_CTL_DEL,socketFd,NULL);
										close(socketFd);
							//   > 0实际读到的字节数
							}else{
								/*数据处理*/
									for(i=0;i<n;++i)
										buf[i]=toupper(buf[i]);
									write(STDOUT_FILENO,buf,n);
									Writen(socketFd,buf,n);
							}
				}
        }
    }

    close(listenFd);
    close(efd);
    return 0;
}

3.client.c

/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 6666

int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr;
	char buf[MAXLINE];
	int sockfd, n;

	sockfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	servaddr.sin_port = htons(SERV_PORT);

	Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	while (fgets(buf, MAXLINE, stdin) != NULL) {
		Write(sockfd, buf, strlen(buf));
		n = Read(sockfd, buf, MAXLINE);
		if (n == 0)
			printf("the other side has been closed.\n");
		else
			Write(STDOUT_FILENO, buf, n);
	}

	Close(sockfd);
	return 0;
}

4.事件模型(epoll 事件触发模型ET和LT

ET工作模式:边沿触发————只有数据到来才触发,不管缓存区中是否还有数据,缓冲区剩余未读尽的数据不会导致
    作用:当文件描述符从未就绪变为就绪时,内核会通过epoll告诉你一次喊你就绪,直到你做操作导致那个文件描述符不再为就绪状态
		     缓冲区未读尽的数据不会导致epoll_wait返回, 新的数据写入才会触发(等文件描述符不再为就绪状态)		
			    struct epoll_event event
			    event.events = EPOLLIN | EPOLLET

LT工作模式:水平触发————只要有数据都会触发(默认采用模式)
    作用:内核告诉你一个文件描述符是否就绪,然后可以对这个就绪的fd进行io操作,如果你不做任何操作,内核还会继续通知你
	        缓冲区未读尽的数据会导致epoll_wait返回(继续通知你)
	
结论:epoll 的 ET模式, 高效模式,但是只支持 非阻塞模式
             --- 忙轮询:用于在计算机系统中处理硬件中断
                     忙轮询是一种不进入内核的方式,它在用户空间中轮询检测硬件状态
                     及时响应硬件的中断请求,避免CPU在中断服务程序中处理完所有的中断请求后,又再次触发中断

		struct epoll_event event;
		event.events = EPOLLIN | EPOLLET;

		epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event);	

		int flg = fcntl(cfd, F_GETFL);	 非阻塞
		flg |= O_NONBLOCK;
		fcntl(cfd, F_SETFL, flg);

代码实现 

#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <errno.h>
#include <unistd.h>

#define MAXLINE 10

int main(int argc, char *argv[])
{
    int efd, i;
    int pfd[2];
    pid_t pid;
    char buf[MAXLINE], ch = 'a';

    pipe(pfd);
    pid = fork();

    if (pid == 0) {             //子 写
        close(pfd[0]);
        while (1) {
            //aaaa\n
            for (i = 0; i < MAXLINE/2; i++)
                buf[i] = ch;
            buf[i-1] = '\n';
            ch++;
            //bbbb\n
            for (; i < MAXLINE; i++)
                buf[i] = ch;
            buf[i-1] = '\n';
            ch++;
            //aaaa\nbbbb\n
            write(pfd[1], buf, sizeof(buf));
            sleep(5);
        }
        close(pfd[1]);

    } else if (pid > 0) {       //父 读
        struct epoll_event event;
        struct epoll_event resevent[10];          //epoll_wait就绪返回event
        int res, len;

        close(pfd[1]);
        efd = epoll_create(10);

        event.events = EPOLLIN | EPOLLET;         // ET 边沿触发
       // event.events = EPOLLIN;                 // LT 水平触发 (默认)
        event.data.fd = pfd[0];
        epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);

        while (1) {
            res = epoll_wait(efd, resevent, 10, -1);
            printf("res %d\n", res);
            if (resevent[0].data.fd == pfd[0]) {
                len = read(pfd[0], buf, MAXLINE/2);
                write(STDOUT_FILENO, buf, len);
            }
        }

        close(pfd[0]);
        close(efd);

    } else {
        perror("fork");
        exit(-1);
    }

    return 0;
}

4.1 server.c

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>

#define MAXLINE 10
#define SERV_PORT 9000

int main(void)
{
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];
    int efd;

    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    listen(listenfd, 20);

    struct epoll_event event;
    struct epoll_event resevent[10];
    int res, len;

    efd = epoll_create(10);
    event.events = EPOLLIN | EPOLLET;     /* ET 边沿触发 */
    //event.events = EPOLLIN;                 /* 默认 LT 水平触发 */

    printf("Accepting connections ...\n");

    cliaddr_len = sizeof(cliaddr);
    connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
    printf("received from %s at PORT %d\n",
            inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
            ntohs(cliaddr.sin_port));

    event.data.fd = connfd;
    epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);

    while (1) {
        res = epoll_wait(efd, resevent, 10, -1);

        printf("res %d\n", res);
        if (resevent[0].data.fd == connfd) {
            len = read(connfd, buf, MAXLINE/2);         //readn(500)   
            write(STDOUT_FILENO, buf, len);
        }
    }

    return 0;
}

4.2 client.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define MAXLINE 10
#define SERV_PORT 9000

int main(int argc, char *argv[])
{
    struct sockaddr_in servaddr;
    char buf[MAXLINE];
    int sockfd, i;
    char ch = 'a';

    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    servaddr.sin_port = htons(SERV_PORT);

    connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    while (1) {
        //aaaa\n
        for (i = 0; i < MAXLINE/2; i++)
            buf[i] = ch;
        buf[i-1] = '\n';
        ch++;
        //bbbb\n
        for (; i < MAXLINE; i++)
            buf[i] = ch;
        buf[i-1] = '\n';
        ch++;
        //aaaa\nbbbb\n
        write(sockfd, buf, sizeof(buf));
        sleep(5);
    }
    close(sockfd);

    return 0;
}

5.epoll 反应堆模型

作用:提高网络IO处理的效率



epoll ET模式 + 非阻塞、轮询 + void *ptr
    void *ptr:指向结构体,该结构体包含socket、地址、端口等信息


原来:epoll实现多路IO转接思路
		socket、bind、listen -- epoll_create 创建监听 红黑树 --  返回 epfd -- epoll_ctl() 向树上添加一个监听fd -- while(1)--

		-- epoll_wait 监听 -- 对应监听fd有事件产生 -- 返回 监听满足数组。 -- 判断返回数组元素 -- lfd满足 -- Accept -- cfd 满足 

		-- read() --- 小->大 -- write回去


反应堆:不但要监听 cfd 的读事件、还要监听cfd的写事件

		socket、bind、listen -- epoll_create 创建监听 红黑树 --  返回 epfd -- epoll_ctl() 向树上添加一个监听fd -- while(1)--

		-- epoll_wait 监听 -- 对应监听fd有事件产生 -- 返回 监听满足数组。 -- 判断返回数组元素 -- lfd满足 -- Accept -- cfd 满足 

		-- read() --- 小->大 
		
		-- cfd从监听红黑树上摘下 -- EPOLLOUT -- 回调函数 -- epoll_ctl() -- EPOLL_CTL_ADD 重新放到红黑上监听“写”事件-- 等待 epoll_wait 返回 -- 说明 cfd 可写 -- write回去 
		
		-- cfd从监听红黑树上摘下 -- EPOLLIN -- epoll_ctl() -- EPOLL_CTL_ADD 重新放到红黑上监听“读”事件 -- epoll_wait 监听

		


eventset函数:设置回调函数
				lfd --> acceptconn()
				cfd --> recvdata();
				cfd --> senddata();
				
eventadd函数:将一个fd, 添加到 监听红黑树
              设置监听读事件,还是监听写事件


网络编程中: read --- recv()            write --- send();

epoll基于非阻塞I/O事件驱动

/*
 *epoll基于非阻塞I/O事件驱动
 */
#include <stdio.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#define MAX_EVENTS  1024                                    						//监听上限数
#define BUFLEN 4096
#define SERV_PORT   8080															//默认端口号

void recvdata(int fd, int events, void *arg);
void senddata(int fd, int events, void *arg);

/* 描述就绪文件描述符相关信息 */
struct myevent_s {
    int fd;                                                 						//要监听的文件描述符
    int events;                                             						//对应的监听事件
    void *arg;                                              						//泛型参数
    void (*call_back)(int fd, int events, void *arg);       						//回调函数
    int status;                                             						//是否在监听:1->在红黑树上(监听), 0->不在(不监听)
    char buf[BUFLEN];
    int len;
    long last_active;                                       						//记录每次加入红黑树 g_efd 的时间值
};

int g_efd;                                                  						//全局变量, 保存epoll_create返回的文件描述符
struct myevent_s g_events[MAX_EVENTS+1];                    						//自定义结构体类型数组. +1-->listen fd


	/*将结构体 myevent_s 成员变量 初始化赋值*/
	void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg)
	{
		ev->fd = fd;
		ev->call_back = call_back;													//设置回调函数
		ev->events = 0;
		ev->arg = arg;
		ev->status = 0;
		memset(ev->buf, 0, sizeof(ev->buf));
		ev->len = 0;
		ev->last_active = time(NULL);                       						//调用eventset函数的时间

		return;
	}

	/* 向 epoll监听的红黑树 添加一个 文件描述符 */
	//eventadd函数: 将一个fd添加到监听红黑树, 设置监听读事件还是写事件
	//eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]);
	void eventadd(int efd, int events, struct myevent_s *ev)
	{
		struct epoll_event epv = {0, {0}};
		int op;
		epv.data.ptr = ev;
		epv.events = ev->events = events;      									    //EPOLLIN 或 EPOLLOUT

		if (ev->status == 0) {                                          			//已经在红黑树 g_efd 里
			op = EPOLL_CTL_ADD;                 									//将其加入红黑树 g_efd, 并将status置1
			ev->status = 1;
		}

		if (epoll_ctl(efd, op, ev->fd, &epv) < 0)                       			//实际添加/修改
			printf("event add failed [fd=%d], events[%d]\n", ev->fd, events);
		else
			printf("event add OK [fd=%d], op=%d, events[%0X]\n", ev->fd, op, events);

		return ;
	}

	/* 从epoll 监听的 红黑树中删除一个 文件描述符*/
	void eventdel(int efd, struct myevent_s *ev)
	{
		struct epoll_event epv = {0, {0}};

		if (ev->status != 1)                                        				//不在红黑树上
			return ;

		//epv.data.ptr = ev;
		epv.data.ptr = NULL;
		ev->status = 0;                                             				//修改状态
		epoll_ctl(efd, EPOLL_CTL_DEL, ev->fd, &epv);                				//从红黑树 efd 上将 ev->fd 摘除

		return ;
	}

			/*  当有文件描述符就绪, epoll返回, 调用该函数 与客户端建立链接 */
			void acceptconn(int lfd, int events, void *arg)
			{
				struct sockaddr_in cin;
				socklen_t len = sizeof(cin);
				int cfd, i;

				if ((cfd = accept(lfd, (struct sockaddr *)&cin, &len)) == -1) {
					if (errno != EAGAIN && errno != EINTR) {
						/* 暂时不做出错处理 */
					}
					printf("%s: accept, %s\n", __func__, strerror(errno));
					return ;
				}

				do {
					for (i = 0; i < MAX_EVENTS; i++)                               			//从全局数组g_events中找一个空闲元素
						if (g_events[i].status == 0)                                		//类似于select中找值为-1的元素
							break;                                                  		//跳出 for

					if (i == MAX_EVENTS) {
						printf("%s: max connect limit[%d]\n", __func__, MAX_EVENTS);
						break;                                                      		//跳出do while(0) 不执行后续代码
					}

					int flag = 0;
					if ((flag = fcntl(cfd, F_SETFL, O_NONBLOCK)) < 0) {             		//将cfd也设置为非阻塞
						printf("%s: fcntl nonblocking failed, %s\n", __func__, strerror(errno));
						break;
					}

					/* 给cfd设置一个 myevent_s 结构体, 回调函数 设置为 recvdata */
					eventset(&g_events[i], cfd, recvdata, &g_events[i]);   
					eventadd(g_efd, EPOLLIN, &g_events[i]);                         		//将cfd添加到红黑树g_efd中,监听读事件

				} while(0);

				printf("new connect [%s:%d][time:%ld], pos[%d]\n", 
						inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), g_events[i].last_active, i);
				return ;
			}

			//epoll反应堆-wait被触发后read和write回调及监听	
				void recvdata(int fd, int events, void *arg)
				{
					struct myevent_s *ev = (struct myevent_s *)arg;
					int len;

					len = recv(fd, ev->buf, sizeof(ev->buf), 0);            				//读文件描述符, 数据存入myevent_s成员buf中

					eventdel(g_efd, ev);        //将该节点从红黑树上摘除

					if (len > 0) {

						ev->len = len;
						ev->buf[len] = '\0';                                				//手动添加字符串结束标记
						printf("C[%d]:%s\n", fd, ev->buf);

						eventset(ev, fd, senddata, ev);                     				//设置该 fd 对应的回调函数为 senddata
						eventadd(g_efd, EPOLLOUT, ev);                      				//将fd加入红黑树g_efd中,监听其写事件

					} else if (len == 0) {
						close(ev->fd);
						/* ev-g_events 地址相减得到偏移元素位置 */
						printf("[fd=%d] pos[%ld], closed\n", fd, ev-g_events);
					} else {
						close(ev->fd);
						printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
					}

					return;
				}

				void senddata(int fd, int events, void *arg)
				{
					struct myevent_s *ev = (struct myevent_s *)arg;
					int len;

					len = send(fd, ev->buf, ev->len, 0);                    				//直接将数据 回写给客户端。未作处理

					eventdel(g_efd, ev);                                					//从红黑树g_efd中移除

					if (len > 0) {

						printf("send[fd=%d], [%d]%s\n", fd, len, ev->buf);
						eventset(ev, fd, recvdata, ev);                     				//将该fd的 回调函数改为 recvdata
						eventadd(g_efd, EPOLLIN, ev);                       				//从新添加到红黑树上, 设为监听读事件

					} else {
						close(ev->fd);                                      				//关闭链接
						printf("send[fd=%d] error %s\n", fd, strerror(errno));
					}

					return ;
				}


	/*创建 socket, 初始化lfd */
	void initlistensocket(int efd, short port)
	{
		struct sockaddr_in sin;

		//将socket设为lfd非阻塞
		int lfd = socket(AF_INET, SOCK_STREAM, 0);
		fcntl(lfd, F_SETFL, O_NONBLOCK);                                            

		//设置地址结构
		memset(&sin, 0, sizeof(sin));                                               //bzero(&sin, sizeof(sin))
		sin.sin_family = AF_INET;
		sin.sin_addr.s_addr = INADDR_ANY;
		sin.sin_port = htons(port);

		bind(lfd, (struct sockaddr *)&sin, sizeof(sin));

		listen(lfd, 20);

		/* void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg);  */
		 /*把g_events数组的最后一个元素设置为lfd,回调函数设置为acceptconn*/
			eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]);

		/* void eventadd(int efd, int events, struct myevent_s *ev) */
		/*挂上树*/
			eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]);

		return ;
	}




int main(int argc, char *argv[])
{
	/*选择默认端口号或指定端口号*/
    unsigned short port = SERV_PORT;

    if (argc == 2)
		//使用用户指定端口.如未指定,用默认端口
        port = atoi(argv[1]);                           								

	//创建红黑树,返回给全局 g_efd
    g_efd = epoll_create(MAX_EVENTS+1);                 								 
    if (g_efd <= 0)
        printf("create efd in %s err %s\n", __func__, strerror(errno));

	//初始化监听socket
    initlistensocket(g_efd, port);                      								

	//创建一个系统的epoll_event的数组,与my_events的规模相同
		struct epoll_event events[MAX_EVENTS+1];           								//保存已经满足就绪事件的文件描述符数组 
		printf("server running:port[%d]\n", port);

    int checkpos = 0, i;
    while (1) {
        /* 超时验证,每次测试100个链接,不测试listenfd 当客户端60秒内没有和服务器通信,则关闭此客户端链接 */	
			long now = time(NULL);                          							//当前时间
			for (i = 0; i < 100; i++, checkpos++) {         							//一次循环检测100个。 使用checkpos控制检测对象
				if (checkpos == MAX_EVENTS)
					checkpos = 0;
				if (g_events[checkpos].status != 1)        								//不在红黑树 g_efd 上
					continue;

				long duration = now - g_events[checkpos].last_active;       			//时间间隔,客户端不活跃的世间

				if (duration >= 60) {
					close(g_events[checkpos].fd);                           			//关闭与该客户端链接
					printf("[fd=%d] timeout\n", g_events[checkpos].fd);
					eventdel(g_efd, &g_events[checkpos]);                   			//将该客户端 从红黑树 g_efd移除
				}
			}

        /*监听红黑树g_efd, 将满足的事件的文件描述符加至events数组中, 1秒没有事件满足, 返回 0*/
			int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000);
			if (nfd < 0) {
				printf("epoll_wait error, exit\n");
				break;
			}

			for (i = 0; i < nfd; i++) {
				/*使用自定义结构体myevent_s类型指针, 接收 联合体data的void *ptr成员*/
				struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;  

				//cfd从监听红黑树上摘下  
				if ((events[i].events & EPOLLIN) && (ev->events & EPOLLIN)) {          	//读就绪事件
					ev->call_back(ev->fd, events[i].events, ev->arg);
					//lfd  EPOLLIN  
				}
				if ((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT)) {         //写就绪事件
					ev->call_back(ev->fd, events[i].events, ev->arg);
				}
			}
    }

    /* 退出前释放所有资源 */
    return 0;
}

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

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

相关文章

OpenCV + CLion在windows环境下使用CMake编译, 出现Mutex相关的错误的解决办法

最近在windows下面用cmake编译OpenCV的项目代码,但是一直碰到找不到mutex的问题&#xff0c;百思不得其解, Executing task: g -g -o bin/debug.exe src/main.cppC:\MinGW\lib\opencv\build\include/opencv2/core/utility.hpp:697:14: error: recursive_mutex in namespace st…

智慧能源管理系统助力某制造企业提高能源利用效率

随着全球能源需求不断增加和能源价格的上涨&#xff0c;企业和机构日益意识到能源管理的重要性。传统的能源管理方式不仅效率低下&#xff0c;还容易造成资源浪费和环境污染。因此&#xff0c;许多企业开始探索采用智慧能源管理系统来提高能源利用效率&#xff0c;降低能源成本…

9. 解谜游戏

目录 题目 Description Input Notes 思路 暴力方法 递归法 注意事项 C代码&#xff08;递归法&#xff09; 关于DFS 题目 Description 小张是一个密室逃脱爱好者&#xff0c;在密室逃脱的游戏中&#xff0c;你需要解开一系列谜题最终拿到出门的密码。现在小张需要打…

Linux:Nginx服务与搭建

目录 一、Nginx概述 二、Nginx三大作用&#xff1a;反向代理、负载均衡、动静分离 三、Nginx和Apache 3.1Nginx和Apache的差异 3.2Nginx和Apache的优缺点比较 四、编译安装niginx 五、创建Nginx 自启动文件 六、Nginx的信号使用 6.1信号 七、升级 nginx1.18 nginx1.2…

项目---日志系统

目录 项目系统开发环境核心技术日志系统介绍为什么需要日志系统? 日志系统框架设计日志系统模块划分代码实现通用工具实现日志等级模块实现日志消息模块实现格式化模块实现落地模块实现日志器模块同步日志器异步日志器缓冲区实现异步工作器实现 回归异步日志器模块建造者模式日…

前端需要理解的工程化知识

1 Git 1.1 Git 常见工作流程 Git 有4个区域&#xff1a;工作区&#xff08;workspace)、index&#xff08;暂存区&#xff09;、repository&#xff08;本地仓库&#xff09;和remote&#xff08;远程仓库&#xff09;&#xff0c;而工作区就是指对文件发生更改的地方&#xff…

Android View动画整理

View 动画相关内容可参考官网 动画资源 此前也有写 View 动画相关的内容&#xff0c;但都只是记录代码&#xff0c;没有特别分析。以此篇作为汇总、整理、分析。 Android View 动画有4中&#xff0c;分别是 平移动画 TranslateAnimation缩放动画 ScaleAnimation旋转动画 Rot…

大数据时代的软件开发实践:利用云计算和AI赋能创新

文章目录 云计算的赋能弹性资源管理远程协作与分布式开发持续集成和持续交付成本效益 人工智能的赋能数据驱动的决策自动化智能预测和优化自适应系统 创新的实践方法数据驱动的创新智能化产品开放式创新迭代和反馈 &#x1f388;个人主页&#xff1a;程序员 小侯 &#x1f390;…

VBJSON报错:缺少:语句结束

项目中使用JSON库VBJSON时报错&#xff1a; 编译错误&#xff1a;缺少&#xff1a;语句结束 cJSONScript和cStringBuilder报相同的错误&#xff0c;都在第一行: VERSION 1.0 CLASS 研究了半天没啥结果&#xff0c;之前使用这个库的时候没有什么问题&#xff0c;所以判定是当前…

yolov8实战之torchserve服务化:使用yolov8x来预打标

前言 最近在做一个目标检测的任务&#xff0c;部署在边缘侧&#xff0c;对于模型的速度要求比较严格&#xff08;yolov8n这种&#xff09;&#xff0c;所以模型的大小不能弄太大&#xff0c;所以原模型的性能受限&#xff0c;更多的重点放在增加数据上。实测yolov8x在数据集上…

【C/C++】父类指针指向子类对象 | 隐藏

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

(四)CUDA应用程序编程接口详解

C语言扩展 CUDA的编程接口是C语言的扩展集&#xff0c;其中主要的是Runtime库&#xff0c;该库分为三个组件&#xff1a;主机组件、设备组件以及公共组件 主机组件&#xff1a;在主机上运行并提供函数来控制和访问一个或多个计算设备 设备组件&#xff1a;设备运行并且提供特…

考研C语言进阶题库——更新41-50题

目录 41.编写程序要求输出整数a和b若a和b的平方和大于100&#xff0c;则输出a和b的平方和&#xff0c;否则输出a和b的和 42.现代数学的著名证明之一是Georg Cantor证明了有理数是可枚举的。他是用下面这一张表来证明这一命题的&#xff1a;第一项是1/1&#xff0c;第二项是是…

unity-AI自动导航

unity-AI自动导航 给人物导航 一.地形创建 1.首先我们在Hierarchy面板中创建一个地形对象terrian&#xff0c;自行设定地形外貌&#xff0c;此时我们设置一个如下的地形外观。 二.创建导航系统 1.在主人公的Inspector、面板中添加Nav Mesh Agent &#xff08;导航网格代理&…

SQLSTATE[IMSSP]: The active result for the query contains no fields.

我的是SQL server 报错场景&#xff0c;代码&#xff1a; $psendmx_sql"SET IDENTITY_INSERT PSENDMX ON;INSERT INTO psendmx (DJBH,MIBH,MXBH,SPDM,GG1DM,GG2DM,SL,SL_2,CKJ,ZK,DJ,DJ_1,JE,HH) VALUES {$mx_values};SET IDENTITY_INSERT PSENDMX OFF;"; $a$db_er…

mysql数据库root密码遗忘后,修改root密码

目录 方式一&#xff1a; 方式二&#xff1a; 2.1 也可以像我这样&#xff0c;普通用户登录进去后 2.2 执行如下命令&#xff0c;将已知的user1的加密密文更新到root中 2.3 查询数据库 2.4 用root用户登录 2.5 登录正常&#xff0c;但这会root登录进去后&#xff0c;无法…

【Terraform学习】使用 Terraform 托管 S3 静态网站(Terraform-AWS最佳实战学习)

使用 Terraform 托管 S3 静态网站 实验步骤 前提条件 安装 Terraform&#xff1a; 地址 下载仓库代码模版 本实验代码位于 task_s3 文件夹中。 变量文件 variables.tf 在上面的代码中&#xff0c;您将声明&#xff0c;aws_access_key&#xff0c;aws_secret_key和区域变量…

Web自动化测试之图文验证码的解决方案

对于web应用程序来讲&#xff0c;处于安全性考虑&#xff0c;在登录的时候&#xff0c;都会设置验证码&#xff0c; 验证码的类型种类繁多&#xff0c;有图片中辨别数字字母的&#xff0c;有点击图片中指定的文字的&#xff0c;也有算术计算结果的&#xff0c;再复杂一点就是滑…

小研究 - Java虚拟机垃圾收集器的性能分析与调节

垃圾收集器是&#xff2a;&#xff41;&#xff56;&#xff41;虚拟机&#xff08;&#xff2a;&#xff36;&#xff2d;&#xff09;的核心组成部分之一&#xff0c;对&#xff2a;&#xff41;&#xff56;&#xff41;虚拟机的性能有非常重要的影响。本文将介绍&#xff2…

C语言练习5(巩固提升)

C语言练习5 选择题 选择题 1&#xff0c;下面代码的结果是&#xff1a;( ) #include <stdio.h> #include <string.h> int main() {char arr[] { b, i, t };printf("%d\n", strlen(arr));return 0; }A.3 B.4 C.随机值 D.5 &#x1f4af;答案解析&#…
最新文章