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

news2024/11/19 3:20:42

文章目录:

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

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

	//创建一个系统的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;  

            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.coloradmin.cn/o/919817.html

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

相关文章

光谱成像系统视觉均匀校准积分球光源

数字相机的光谱灵敏度是成像传感器、光学透镜、滤光片以及相机内部图像处理过程等诸多因素的综合结果。即使是同一台相机&#xff0c;采用不同的光学镜头和不同的滤光片&#xff0c;由于光学系统的结构和光学材料的透过率不同&#xff0c;导致整个成像系统的光谱灵敏度也有所差…

Linux系统安全:NAT(SNAT、DNAT)

目录 一.NAT 二.SNAT 三.DNAT 一.NAT NAT: network address translation&#xff0c;支持PREROUTING&#xff0c;INPUT&#xff0c;OUTPUT&#xff0c;POSTROUTING四个链 请求报文&#xff1a;修改源/目标IP&#xff0c; 响应报文&#xff1a;修改源/目标IP&#xff0c;根据…

Maven 一键部署到 SSH 服务器

简介 利用 Maven Mojo 功能一键部署 jar 包或 war 包到远程服务器上。 配置 在 maven 的setting.xml 配置服务器 SSH 账号密码。虽然可以在工程的 pom.xml 直接配置&#xff0c;但那样不太安全。 <servers><server><id>iq</id><configuration&…

科技资讯|荷兰电动自行车丢失将被拒保,苹果Find My可以减少丢失

荷兰最大的自行车协会荷兰皇家旅游俱乐部宣布&#xff0c;将不再为胖胎电动自行车提供保险&#xff0c;因为这种自行车的被盗风险极高。 随着电动自行车的销量飙升&#xff0c;胖胎也变得更受欢迎。但问题是&#xff0c;胖胎电动自行车也成为了自行车盗窃者的首选目标。ANWB …

优化时间流:区间调度问题的探索与解决

在浩如烟海的信息时代&#xff0c;时间的有效管理成为了一门不可或缺的艺术。无论是生活中的琐事&#xff0c;还是工作中的任务&#xff0c;时间都在无声地流逝&#xff0c;挑战着我们的智慧。正如时间在日常生活中具有的宝贵价值一样&#xff0c;在计算机科学领域&#xff0c;…

Java IO流(五)Netty实战[TCP|Http|心跳检测|Websocket]

Netty入门代码示例(基于TCP服务) Server端 package com.bierce.io.netty.simple; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGro…

星际争霸之小霸王之小蜜蜂(五)--为小蜜蜂降速

目录 前言 一、思路 二、调整小蜜蜂的移速 三、限制活动范围 四、继续重构 总结 前言 前面我们已经让小蜜蜂左右移动起来了&#xff0c;而且是连续的左右移动&#xff0c;但是在使用的过程中&#xff0c;因为我使用的是笔记本电脑&#xff0c;所以屏幕比较小&#xff0c;设…

Diffusion Models for Time Series Applications: A Survey

Diffusion Models for Time Series Applications: A Survey (Paper reading) Lequan Lin, The University of Sydney Business School, arXiv2023,Cited:5, Code, Paper 1. 引言 扩散模型是一类基于深度学习的生成模型&#xff0c;在前沿的机器学习研究中变得越来越突出。在…

Linux线程 --- 生产者消费者模型(C语言)

在学习完线程相关的概念之后&#xff0c;本节来认识一下Linux多线程相关的一个重要模型----“ 生产者消费者模型” 本文参考&#xff1a; Linux多线程生产者与消费者_红娃子的博客-CSDN博客 Linux多线程——生产者消费者模型_linux多线程生产者与消费者_两片空白的博客-CSDN博客…

测试平台metersphere

metersphere可以做接口测试、UI测试、性能测试。 metersphere接口测试底层是jmeter&#xff0c;可以做API管理&#xff0c;快捷调试&#xff0c;接口用例管理&#xff0c;接口自动化场景执行一键选取用例范围&#xff0c;生成测试报告。 会用jmeter&#xff0c;metersphere会…

软年架构复用-架构师之路(十一)

软件架构复用 软件产品线是 一组产业密集型系统&#xff0c;规定用公用的 核心资产集成 开发而来。 机会复用 和 系统复用。 机会复用&#xff1a;临时发现有可服用资产立马复用。 系统复用&#xff1a;开发之前进行规划好哪些需要复用。 复用的三个阶段&#xff1a; 获取到…

高阶数据结构并查集

目录&#xff1a; 并查集的概念代码实现 并查集的概念 将n个不同的元素划分成一些不相交的集合。开始时&#xff0c;每个元素自成一个单元元素集合&#xff0c;然后按一定的规律将归于同一组元素的集合合并。在此过程中反复遇到查询某一个元素属于那个集合的运算&#xff0c;这…

储能运行约束的Matlab建模方法

最近一段时间有很多人问我最优潮流计算中储能系统的建模方法。部分朋友的问题我回复了&#xff0c;有些没有回消息的&#xff0c;我就不再一一回复了&#xff0c;在这里我写一篇博客统一介绍一下。 1.储能系统介绍 首先&#xff0c;让【GPT】简单介绍一下储能系统&#xff1a;…

【多天线传输技术】BPSK调制信号在AWGN信道下的理论误码率与仿真误码率

%% [0、预处理] clc; clear; close all&#xff1b;%% [1、配置参数] N1000000; %数据点数&#xff08;个&#xff09; SNR_dB0:10; %信噪比&#xff08;dB形式&#xff09; SNR10.^(SNR_dB/10); %信噪比&#xff08;一般形式&#xff0c;Eb/N0&#xff09;…

【业务功能篇78】微服务-前端后端校验- 统一异常处理-JSR-303-validation注解

5. 前端校验 我们在前端提交的表单数据&#xff0c;我们也是需要对提交的数据做相关的校验的 Form 组件提供了表单验证的功能&#xff0c;只需要通过 rules 属性传入约定的验证规则&#xff0c;并将 Form-Item 的 prop 属性设置为需校验的字段名即可 校验的页面效果 前端数据…

Android相机-HAL子系统

引言 应用框架要通过拍照预览摄像获得照片或者视频,就需要向相机子系统发出请求, 一个请求对应一组结果 一次可发起多个请求&#xff0c;并且提交请求是非阻塞的&#xff0c;始终按照接收的顺序以队列的形式先进先出地进行顺序处理 一个请求包含了拍摄和拍照配置的所有信息&…

企业数字化转型中,VR数字展厅能有哪些体验?

在数字化转型的浪潮下&#xff0c;企业纷纷开始注重数字展厅的开展&#xff0c;VR虚拟展厅结合VR全景技术&#xff0c;可以创造出许多有趣的玩法和体验&#xff0c;无论是虚拟参观、互动体验还是VR云会议对接&#xff0c;都为企业客户带来了全新的感知方式。 同传统展厅相比&am…

【LeetCode-中等题】560. 和为 K 的子数组

题目 题解一&#xff1a;逆序枚举数组 //方法一:枚举数组&#xff08;顺序&#xff09;int count 0;// 记录最终符合条件的数组个数int n nums.length;for(int end 0; end<n ; end){int sum 0;//记录每一次经过的元素总和for(int start end; start>0;start--){sum n…

漏洞挖掘和漏洞利用技术:讨论漏洞发现、利用和修复,深入研究不同类型漏洞的技术细节

章节一&#xff1a;引言 在当今数字化时代&#xff0c;计算机技术的迅猛发展为我们的生活带来了无数便利&#xff0c;然而也伴随着各种安全威胁。恶意黑客利用漏洞进行攻击已成为一种常见现象。本文将深入探讨漏洞挖掘和漏洞利用技术&#xff0c;以及如何修复这些漏洞&#xf…

微信小程序路由以及跳转页面传递参数

路由 在app.json的pages里面写 "pages/页面/页面" 直接保存pages直接生成非常方便 跳转页面 wx.navigateTo() 保留当前页面&#xff0c;跳转到应用内的某个非tabBar页面。 <text bindtap"daka">点击</text> daka:function () {wx.navigateTo…