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.mzph.cn/news/55351.shtml

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

相关文章

Elasticsearch(十三)搜索---搜索匹配功能④--Constant Score查询、Function Score查询

一、前言 之前我们学习了布尔查询&#xff0c;知道了filter查询只在乎查询条件和文档的匹配程度&#xff0c;但不会根据匹配程度对文档进行打分&#xff0c;而对于must、should这两个布尔查询会对文档进行打分&#xff0c;那如果我想在查询的时候同时不去在乎文档的打分&#…

Redis(缓存预热,缓存雪崩,缓存击穿,缓存穿透)

目录 一、缓存预热 二、缓存雪崩 三、缓存击穿 四、缓存穿透 一、缓存预热 开过车的都知道&#xff0c;冬天的时候启动我们的小汽车之后不要直接驾驶&#xff0c;先让车子发动机预热一段时间再启动。缓存预热是一样的道理。 缓存预热就是系统启动前&#xff0c;提前将相关的…

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

前言&#xff1a;本篇文章将继续讲解有关指针的剩余基础知识。 学无止境&#xff0c;一起加油叭&#xff01;&#xff01; 目录 一.指针运算 1.指针 - 整数 2.指针的关系运算 3.指针 - 指针 二.指针与数组 三.二级指针 四.指针数组 总结 一.指针运算 指针运算包括以下三…

【TI毫米波雷达笔记】UART串口外设配置及驱动(以IWR6843AOP为例)

【TI毫米波雷达笔记】UART串口外设初始化配置及驱动&#xff08;以IWR6843AOP为例&#xff09; 最基本的工程建立好以后 需要给SOC进行初始化配置 int main (void) {//刷一下内存memset ((void *)L3_RAM_Buf, 0, sizeof(L3_RAM_Buf));int32_t errCode; //存放SOC初…

c#设计模式-创建型模式 之 原型模式

概述 原型模式是一种创建型设计模式&#xff0c;它允许你复制已有对象&#xff0c;而无需使代码依赖它们所属的类。新的对象可以通过原型模式对已有对象进行复制来获得&#xff0c;而不是每次都重新创建。 原型模式包含如下角色&#xff1a; 抽象原型类&#xff1a;规定了具…

AliOS-Things引入

目录 一、简介 1.1 硬件抽象层 1.2 AliOS-Things内核 rhino ​编辑 1.3 AliOS-Things组件 二、如何进行AliOS-Things开发 三、安装环境 安装python pip git 修改pip镜像源 安装aos-cube 一、简介 AliOS-Things是阿里巴巴公司推出的致力于搭建云端一体化LoT软件。AliOS-…

【python】python智能停车场数据分析(代码+数据集)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

azure data studio SQL扩展插件开发笔记

node.js环境下拉取脚手架 npm install -g yo generator-azuredatastudio yo azuredatastudio 改代码 运行 调试扩展&#xff0c;在visual studio code中安装插件即可 然后visual studio code打开进行修改运行即可 image.png 运行后自动打开auzre data studio了&#xff0c; 下面…

spring整合mybatis教程(详细易懂)

一、引言 1、Spring整合MyBatis的目的是&#xff1f; 将两个框架结合起来&#xff0c;以实现更好的开发体验和效果。Spring提供了一种轻量级的容器和依赖注入的机制&#xff0c;可以简化应用程序的配置和管理。而MyBatis是一个优秀的持久层框架&#xff0c;可以方便地进行数据…

C# .aspx网页获取RFID读卡器HTTP协议提交的访问文件Request获得卡号、机号,Response回应驱动读卡器显示响声

本示例使用的设备&#xff1a;RFID网络WIFI无线TCP/UDP/HTTP可编程二次开发读卡器POE供电语音-淘宝网 (taobao.com) 服务端代码&#xff1a; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.…

快速理解 X server, DISPLAY 与 X11 Forwarding

​ X server X server是X Window System &#xff08;简称X11或者X&#xff09;系统中的显示服务器&#xff08;display server&#xff09;&#xff0c;用于监听X client发送来的图形界面显示请求&#xff0c;并且将图形界面绘制并显示在屏幕&#xff08;screen&#xff09;…

Mybatis查询数据

上一篇我们介绍了在pom文件中引入mybatis依赖&#xff0c;配置了mybatis配置文件&#xff0c;通过读取配置文件创建了会话工厂&#xff0c;使用会话工厂创建会话获取连接对象读取到了数据库的基本信息。 如果您需要对上面的内容进行了解&#xff0c;可以参考Mybatis引入与使用…

再见 Xshell替代工具Tabby

替代Xshell 之前经常使用Xshell来操作Linux虚拟机&#xff0c;基本上是够用了。但是Xshell免费使用只供非商业用途&#xff0c;而且如果你想用FTP来进行文件传输的话&#xff0c;还需单独下载Xftp。 无意中发现了另一款开源的终端工具Tabby&#xff0c;它直接集成了SFTP功能&…

十几款拿来就能用的炫酷表白代码

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;小白零基础《Python入门到精通》 表白代码 1、坐我女朋友好吗&#xff0c;不同意就关机.vbs2、坐我女朋友好吗&…

基于静电放电算法优化的BP神经网络(预测应用) - 附代码

基于静电放电算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于静电放电算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.静电放电优化BP神经网络2.1 BP神经网络参数设置2.2 静电放电算法应用 4.测试结果&#xff1a;5…

【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…

信息化发展2

信息系统生命周期 1 、软件的生命周期通常包括&#xff1a;可行性分析与项目开发计划、需求分析、概要设计、详细设计、编码、测试、维护等阶段。 2 、信息系统的生命周期可以简化为&#xff1a;系统规划&#xff08;可行性分析与项目开发计划&#xff09;&#xff0c;系统分析…

Unity 之 transform.rotate() 实现旋转

文章目录 详细介绍默认情况下&#xff0c;以局部坐标 详细介绍 在Unity中&#xff0c;Transform.Rotate() 是一个用于在物体上进行旋转的函数。它可以用来在局部坐标系下对物体进行旋转&#xff0c;也可以在世界坐标系下进行旋转。下面是关于 Transform.Rotate() 的详细介绍&a…

2. 使用IDEA创建Spring Boot Hello项目并管理依赖——Maven入门指南

前言&#xff1a;本文将介绍如何使用IDEA创建一个Spring Boot Hello项目&#xff0c;并通过Maven来管理项目的依赖。我们从项目的创建到代码的编写&#xff0c;再到项目的构建和运行&#xff0c;一步步演示了整个过程。 &#x1f680; 作者简介&#xff1a;作为某云服务提供商的…

DataFun:推荐系统峰会

工程架构与训练推理 TFDE 多场景多目标融合 企业知识推荐系统 ATA内部社区 房产推荐场景的算法实践