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 不在0int 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     :监听 所有文件描述符中,最大文件描述符+1readfds  :读   文件描述符监听集合。	传入、传出参数writefds :写   文件描述符监听集合。	传入、传出参数		NULLexceptfds:异常 文件描述符监听集合	传入、传出参数		NULLtimeout: 	> 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 6666void 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 16struct 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或1clientAddrLen = 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 6666int 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;}elseWrite(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、POLLERRshort 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 1024int 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;}elseperr_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 6666int 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");elseWrite(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 errnoint epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);	                 操作控制监听红黑树epfd:epoll_create 函数的返回值 epfdop  :对该监听红黑数所做的操作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;	      对应监听事件的 fdvoid *ptr; uint32_t u32;uint64_t u64;		返回值:成功 0; 失败: -1 errnoint epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 	   阻塞监听epfd:epoll_create 函数的返回值 epfdevents:传出参数,【数组】, 满足监听条件的 哪些 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    1024int 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 6666int 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");elseWrite(STDOUT_FILENO, buf, n);}Close(sockfd);return 0;
}

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

ET工作模式:边沿触发————只有数据到来才触发,不管缓存区中是否还有数据,缓冲区剩余未读尽的数据不会导致作用:当文件描述符从未就绪变为就绪时,内核会通过epoll告诉你一次喊你就绪,直到你做操作导致那个文件描述符不再为就绪状态缓冲区未读尽的数据不会导致epoll_wait返回, 新的数据写入才会触发(等文件描述符不再为就绪状态)		struct epoll_event eventevent.events = EPOLLIN | EPOLLETLT工作模式:水平触发————只要有数据都会触发(默认采用模式)作用:内核告诉你一个文件描述符是否就绪,然后可以对这个就绪的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 10int 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\nfor (i = 0; i < MAXLINE/2; i++)buf[i] = ch;buf[i-1] = '\n';ch++;//bbbb\nfor (; i < MAXLINE; i++)buf[i] = ch;buf[i-1] = '\n';ch++;//aaaa\nbbbb\nwrite(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就绪返回eventint 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 9000int 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 9000int 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\nfor (i = 0; i < MAXLINE/2; i++)buf[i] = ch;buf[i-1] = '\n';ch++;//bbbb\nfor (; i < MAXLINE; i++)buf[i] = ch;buf[i-1] = '\n';ch++;//aaaa\nbbbb\nwrite(sockfd, buf, sizeof(buf));sleep(5);}close(sockfd);return 0;
}

5.epoll 反应堆模型

作用:提高网络IO处理的效率epoll ET模式 + 非阻塞、轮询 + void *ptrvoid *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 或 EPOLLOUTif (ev->status == 0) {                                          			//已经在红黑树 g_efd 里op = EPOLL_CTL_ADD;                 									//将其加入红黑树 g_efd, 并将status置1ev->status = 1;}if (epoll_ctl(efd, op, ev->fd, &epv) < 0)                       			//实际添加/修改printf("event add failed [fd=%d], events[%d]\n", ev->fd, events);elseprintf("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;                                                  		//跳出 forif (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 对应的回调函数为 senddataeventadd(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的 回调函数改为 recvdataeventadd(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_efdg_efd = epoll_create(MAX_EVENTS+1);                 								 if (g_efd <= 0)printf("create efd in %s err %s\n", __func__, strerror(errno));//初始化监听socketinitlistensocket(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.rhkb.cn/news/108244.html

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

相关文章

数据库事务四大特性

事务的4大特性&#xff08;ACID&#xff09;&#xff1a; 原子性(Atomicity)&#xff1a; 事务是数据库的逻辑工作单位&#xff0c;它对数据库的修改要么全部执行&#xff0c;要么全部不执行。 一致性(Consistemcy)&#xff1a; 事务前后&#xff0c;数据库的状态都满足所有的完…

LoRA继任者ReLoRA登场,通过叠加多个低秩更新矩阵实现更高效大模型训练效果

论文链接&#xff1a; https://arxiv.org/abs/2307.05695 代码仓库&#xff1a; https://github.com/guitaricet/peft_pretraining 一段时间以来&#xff0c;大模型&#xff08;LLMs&#xff09;社区的研究人员开始关注于如何降低训练、微调和推理LLMs所需要的庞大算力&#xf…

【BUG】解决安装oracle11g或12C中无法访问临时位置的问题

项目场景&#xff1a; 安装oracle时&#xff0c;到第二步出现oracle11g或12C中无法访问临时位置的问题。 解决方案&#xff1a; 针对客户端安装&#xff0c;在cmd中执行命令&#xff1a;前面加实际路径setup.exe -ignorePrereq -J"-Doracle.install.client.validate.cli…

多线程和并发(1)—等待/通知模型

一、进程通信和进程同步 1.进程通信的方法 同一台计算机的进程通信称为IPC&#xff08;Inter-process communication&#xff09;&#xff0c;不同计 算机之间的进程通信被称为 RPC(Romote process communication)&#xff0c;需要通过网络&#xff0c;并遵守共同的协议。**进…

前端基础之滚动显示

marquee滚动标签 注&#xff1a;该标签已经过时&#xff0c;被w3c弃用!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 使用样例&#xff1a; <marquee>这是个默认的marquee标签</marquee> 多条数据上下滚动&#xff1a; 代码如下&#xff1a; <body><mar…

淘宝商品详情采集接口item_get-获得淘宝商品详情(可高并发线程)

获得淘宝商品详情页面数据采集如下&#xff1a; taobao.item_get 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;注册key账号接入secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff0…

基于SSM+vue框架的个人博客网站源码和论文

基于SSMvue框架的个人博客网站源码和论文061 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm &#xff08;设计&#xff09;研究背景与意义 关于博客的未来&#xff1a;在创办了博客中国(blogchina)、被誉为“…

华为手机实用功能介绍

一、内置app介绍 分四块介绍&#xff0c;包括出门款、规划款、工作款和生活款。 出门款&#xff1a;红色框框部分&#xff0c;照镜子化妆/看天气 规划款&#xff1a;黄色框框部分&#xff0c;日程表/计划表/番茄时间/计时 工作款&#xff1a;蓝色框框部分&#xff0c;便笺/录…

最新AI系统ChatGPT程序源码/微信公众号/H5端+搭建部署教程+完整知识库

一、前言 SparkAi系统是基于国外很火的ChatGPT进行开发的Ai智能问答系统。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。 那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧&#xff01…

五、性能测试之linux分析命令

linux分析命令 一、服务器基础知识二、linux文件结构三、linux文件权限四、linux命令1、安装应用fedora家族: 如centosdebain家族&#xff1a;如ubuntu 2、获取帮助第一种&#xff1a;command --help第二种&#xff1a;man command第三种&#xff1a;info 3、服务器性能分析基础…

英国选校8.27

目录 IC帝国理工学院 UCL伦敦大学学院 爱丁堡 曼彻斯特 KCL伦敦国王学院 Bristol布里斯托 华威 南安普顿 IC帝国理工学院 UCL伦敦大学学院 爱丁堡 曼彻斯特 KCL伦敦国王学院 24qs专业位置双非con雅思气候备注40 移动&个人通信 24fall不要双非&#xff1f; 24fall新…

C语言基础之——指针(上)

前言&#xff1a;小伙伴们又见面啦&#xff01;本期内容&#xff0c;博主将展开讲解有关C语言中指针的上半部分基础知识&#xff0c;一起学习起来叭&#xff01;&#xff01;&#xff01; 目录 一.什么是指针 二.指针类型 1.指针的解引用 2.指针-整数 三.野指针 1.野指针…

12. Oracle中case when详解

格式&#xff1a; case expression when condition_01 then result_01 when condition_02 then result_02 ...... when condition_n then result_n else result_default end 表达式expression符合条件condition_01&#xff0c;则返回…

【算法专题突破】双指针 - 快乐数(3)

目录 1. 题目解析 2. 算法原理 3. 代码编写 写在最后&#xff1a; 1. 题目解析 题目链接&#xff1a;202. 快乐数 - 力扣&#xff08;Leetcode&#xff09; 这道题的题目也很容易理解&#xff0c; 看一下题目给的示例就能很容易明白&#xff0c; 但是要注意一个点&#…

pycharm 右键运行代码时总是测试模式运行(run pytest)

*# 问题 使用pycharm时&#xff0c;右键运行代码&#xff0c;结果是这样的&#xff1a; 运行_‘pytesr(xxx.py 内)’ 英语界面可能是这样&#xff1a;run_‘pytesr(xxx.py)’我并不想使用测试模式。如何改回正常模式&#xff1f; 解决办法 本着遇到什么问题就搜什么问题的态…

【mindspore学习】环境配置

本次实验搭配的环境是 CUDA 11.6 CUDNN v8.9.4 TensorRT-8.4.1.5 mindspore 2.1.0。 1、配置 Nvidia 显卡驱动 如果原来的主机已经安装了 nvidia 驱动&#xff0c;为避免版本的冲突&#xff0c;建议先清除掉旧的 nvidia驱动 sudo apt-get --purge remove nvidia* sudo apt…

苍穹外卖总结

前言 1、软件开发流程 瀑布模型需求分析//需求规格说明书、产品原型↓ 设计 //UI设计、数据库设计、接口设计↓编码 //项目代码、单元测试↓ 测试 //测试用例、测试报告↓上线运维 //软件环境安装、配置第一阶段&#xff1a;需求分析需求规格说明书、产品原型一般来说…

系统架构设计师-计算机系统基础知识(1)

目录 一、计算机系统概述 1、冯诺依曼计算结构​编辑 二、存储系统 三、操作系统概述 1、特殊的操作系统 四、进程管理 1、进程与线程的概念 2、进程的同步与互斥 3、PV操作 4、死锁与银行家算法 一、计算机系统概述 1、冯诺依曼计算结构 二、存储系统 从上到下依次&#…

记录一个问题~beego中的配置文件autorender

事情的经过是这样的: 在学习beego框架时,遇到了一个问题: tpl模板文件不显示内容; 原因所在: beego配置文件: appname hello httpport 8080 runmode dev world world dataSourceInfo root:955945tcp(localhost:3306)/gmusic?charsetutf8 #自动渲染 这里关闭后就关闭了自…

【PHP面试题81】php-fpm是什么?它和PHP有什么关系

文章目录 &#x1f680;一、前言&#xff0c;php-fpm是什么&#x1f680;二、php-fpm与PHP之间的关系&#x1f680;三、php-fpm解决的问题&#x1f50e;3.1 进程管理&#x1f50e;3.2 进程池管理&#x1f50e;3.3 性能优化&#x1f50e;3.4 并发处理 &#x1f680;四、php-fpm常…