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优缺点
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; }
相关文章:
![](https://img-blog.csdnimg.cn/c181b55fdbe845839283f1cdf2b49ecc.png)
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 …...
![](https://www.ngui.cc/images/no-images.jpg)
Mybatis系列原理剖析之项目实战:自定义持久层框架
Mybatis系列原理剖析之:项目实战:自定义持久层框架 持久层是JAVA EE三层体系架构中,与数据库进行交互的一层,持久层往往被称为dao层。需要说明的是,持久层的技术选型有很多,绝不仅仅只有mybatis一种。像早…...
![](https://img-blog.csdnimg.cn/img_convert/fd603936c4abc21dc01cffad20291590.jpeg#?w=1080&h=376&e=jpg&b=040404)
阿里云 Serverless 应用引擎 2.0,正式公测!
阿里云 Serverless 应用引擎 SAE2.0 正式公测上线!全面升级后的 SAE2.0 具备极简体验、标准开放、极致弹性三大优势,应用冷启动全面提效,秒级完成创建发布应用,应用成本下降 40% 以上。 此外,阿里云还带来容器服务 Se…...
![](https://img-blog.csdnimg.cn/bc05b83f6f7a4d0ea5f71b67f2d6d76e.png#pic_center)
西北大学计算机考研844高分经验分享
西北大学计算机考研844经验分享 个人介绍 本人是西北大学22级软件工程研究生,考研专业课129分,过去一年里在各大辅导机构任职,辅导考研学生专业课844,辅导总时长达288小时,帮助多名学生专业课高分上岸。 前情回顾…...
![](https://www.ngui.cc/images/no-images.jpg)
【java并发编程的艺术读书笔记】volatile关键字介绍、与synchronized的区别
volatile的简介 volatile是轻量级锁,只用来修饰变量,保证这个变量在多线程下的可见性以及一致性(一个volatile变量被线程修改时会立刻通知其他所有线程),防止指令重排序,但是并不能保证绝对的线程安全 vol…...
![](https://img-blog.csdnimg.cn/7144eeb984054b0a8da98d96d5ec04a0.jpeg)
LinkedList的顶级理解
目录 1.LinkedList的介绍 LinkedList的结构 2.LinkedList的模拟实现 2.1创建双链表 2.2头插法 2.3尾插法 2.4任意位置插入 2.5查找关键字 2.6链表长度 2.7遍历链表 2.8删除第一次出现关键字为key的节点 2.9删除所有值为key的节点 2.10清空链表 2.11完整代码 3.…...
![](https://img-blog.csdnimg.cn/img_convert/2b1cc6b401b1881928851bc8fff59001.webp?x-oss-process=image/format,png)
再学http-为什么文件上传要转成Base64?
1 前言 最近在开发中遇到文件上传采用Base64的方式上传,记得以前刚开始学http上传文件的时候,都是通过content-type为multipart/form-data方式直接上传二进制文件,我们知道都通过网络传输最终只能传输二进制流,所以毫无疑问他们本…...
![](https://img-blog.csdnimg.cn/021b6ee17c2540698abe4cd29b498744.png)
使用oracleVM搭建虚拟机
选择新建,点击 取名字,选择你的安装路径,选择你爹镜像光盘,再勾选下面的,表示跳过一些步骤 其他的都可以默认,下一步即可 创建好了,点击设置,改变光驱,硬盘的顺序 等待它…...
![](https://img-blog.csdnimg.cn/7a80245f0b5f4021a033b3789a9efdeb.png)
深入探讨C存储类和存储期——Storage Duration
🔗 《C语言趣味教程》👈 猛戳订阅!!! —— 热门专栏《维生素C语言》的重制版 —— 💭 写在前面:这是一套 C 语言趣味教学专栏,目前正在火热连载中,欢迎猛戳订阅&#…...
![](https://img-blog.csdnimg.cn/4bb3b37787844711924af0a54ef01ab5.png)
医学图像融合的深度学习方法综述
文章目录 Deep learning methods for medical image fusion: A review摘要引言非端到端的融合方法基于深度学习的决策映射基于深度学习的特征提取 端到端图像融合方法基于卷积神经网络(CNN)的图像融合方法单级特征融合方法多级特征融合基于残差神经网络的图像融合方法基于密集神…...
![](https://www.ngui.cc/images/no-images.jpg)
【Qt学习】04:QDialog
QDialog OVERVIEW QDialog一、自定义对话框1.模态对话框2.非模态对话框3.练习代码 二、标准对话框1.消息对话框2.文件对话框3.颜色对话框4.字体对话框 对话框是 GUI 程序中不可或缺的组成部分,对话框通常会是一个顶层窗口出现在程序最上层,用于实现短期任…...
![](https://www.ngui.cc/images/no-images.jpg)
如何更好的进行异常处理
背景 在实际开发中,我们都希望程序可以一直按照期望的流程,无误的走下去。但是由于不可避免的内外部因素,可能导致出现异常的情况,轻则导致报错,重则数据错乱、服务不可用等情况。严重影响系统的稳定性,甚至…...
![](https://img-blog.csdnimg.cn/20210927203620256.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5Yaw5Yaw57OWeXl5,size_20,color_FFFFFF,t_70,g_se,x_16)
若依微服务版部署到IDEA
1.进入若依官网,找到我们要下的微服务版框架 2.点击进入gitee,获取源码,下载到本地 3.下载到本地后,用Idea打开,点击若依官网,找到在线文档,找到微服务版本的,当然你不看文档,直接按…...
![](https://img-blog.csdnimg.cn/304848c456fe4868b83496294412ff74.png)
Elasticsearch 入门安装
1.Elasticsearch 是什么 The Elastic Stack, 包括 Elasticsearch、 Kibana、 Beats 和 Logstash(也称为 ELK Stack)。能够安全可靠地获取任何来源、任何格式的数据,然后实时地对数据进行搜索、分析和可视化。 Elaticsearch,简称为…...
![](https://img-blog.csdnimg.cn/c26b8af2cc464e30b3bd10d579b2cd55.png)
【80天学习完《深入理解计算机系统》】第十一天 3.5 过程(函数调用)
专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客,如有问题交流,欢迎评论区留言,一定尽快回复!(大家可以去看我的专栏,是所有文章的目录) 文章字体风格: 红色文字表示&#…...
![](https://www.ngui.cc/images/no-images.jpg)
LinuxUbuntu安装VMware tools Segmentation fault (core dumped)怎么解决
LinuxUbuntu安装VMware tools Segmentation fault (core dumped)怎么解决 在安装VMware Tools时遇到"Segmentation fault (core dumped)"错误,通常是由于兼容性问题或系统配置不正确导致的。以下是一些可能的解决方法: 检查VMware Tools兼容性…...
![](https://img-blog.csdnimg.cn/b191c8641748431098d7360bea1af2e9.png#pic_center)
002微信小程序云开发API数据库-迁移状态查询/更新索引
文章目录 微信小程序云开发API数据库-迁移状态查询案例代码微信小程序云开发API数据库-更新索引案例代码 微信小程序云开发API数据库-迁移状态查询 在微信小程序中,云开发API数据库是一种方便快捷的数据库解决方案。但是,有时候我们可能需要将云开发数据…...
![](https://img-blog.csdnimg.cn/f3abf823de6d4432b61a8c35aeb0e875.png)
十几款拿来就能用的炫酷表白代码
「作者主页」:士别三日wyx 「作者简介」:CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」:小白零基础《Python入门到精通》 表白代码 1、坐我女朋友好吗,不同意就关机.vbs2、坐我女朋友好吗&…...
![](https://img-blog.csdnimg.cn/9f2821b2632746be810dd1248c9fae41.png)
证券低延时环境设置并进行性能测试
BIOS设置BIOS参考信息 关闭 logical Process Virtualization Technology 在System Profiles Settings 中System Profile 选择Performance Workload Profile 选择HPC Profile OS中信息参考在/etc/default/grub文件中添加 intel_idle.max_cstate=0 processor.max_cstate=0 idle=p…...
![](https://img-blog.csdnimg.cn/img_convert/3664c074b194a1b051698229354d461d.png)
百度工程师浅析解码策略
作者 | Jane 导读 生成式模型的解码方法主要有2类:确定性方法(如贪心搜索和波束搜索)和随机方法。确定性方法生成的文本通常会不够自然,可能存在重复或过于简单的表达。而随机方法在解码过程中引入了随机性,以便生成更…...
![](https://www.ngui.cc/images/no-images.jpg)
windows下实现查看软件请求ip地址的方法
一、关于wmic和nestat wmic是Windows Management Instrumentation的缩写,是一款非常常用的用于Windows系统管理的命令行实用程序。wmic可以通过命令行操作,获取系统信息、安装软件、启动服务、管理进程等操作。 netstat命令是一个监控TCP/IP网络的非常有…...
![](https://img-blog.csdnimg.cn/c05fc0ad5b0e45adaaa8e478890d1ab7.png)
【JAVA】String 类
⭐ 作者:小胡_不糊涂 🌱 作者主页:小胡_不糊涂的个人主页 📀 收录专栏:浅谈Java 💖 持续更文,关注博主少走弯路,谢谢大家支持 💖 String 1. 字符串构造2. String对象的比…...
![](https://img-blog.csdnimg.cn/img_convert/082457d1093d52c67ce0a6bac024b9a1.png)
LoRA继任者ReLoRA登场,通过叠加多个低秩更新矩阵实现更高效大模型训练效果
论文链接: https://arxiv.org/abs/2307.05695 代码仓库: https://github.com/guitaricet/peft_pretraining 一段时间以来,大模型(LLMs)社区的研究人员开始关注于如何降低训练、微调和推理LLMs所需要的庞大算力…...
![](https://img-blog.csdnimg.cn/img_convert/66e844f9e4befc913c1b758b7e89710a.gif)
Elasticsearch 8.X reindex 源码剖析及提速指南
1、reindex 源码在线地址 为方便大家验证,这里给出 reindex github 源码地址。 https://github.com/elastic/elasticsearch/blob/001fcfb931454d760dbccff9f4d1b8d113f8708c/server/src/main/java/org/elasticsearch/index/reindex/ReindexRequest.java reindex 常见…...
![](https://img-blog.csdnimg.cn/img_convert/7249b94c2dc5dda1ee1ca18dde29a90f.png#?w=926&h=334&e=png&b=ffffff)
前端组件库造轮子——Input组件开发教程
前端组件库造轮子——Input组件开发教程 前言 本系列旨在记录前端组件库开发经验,我们的组件库项目目前已在Github开源,下面是项目的部分组件。文章会详细介绍一些造组件库轮子的技巧并且最后会给出完整的演示demo。 文章旨在总结经验,开源…...
![](https://www.ngui.cc/images/no-images.jpg)
Day04-Vue基础-监听器-双向绑定-组件通信
Day04-Vue基础-监听器-双向绑定-组件通信 一 侦听器 语法一 <template><div>{{name}}<br><button @click="update1">修改1</button><...
![](https://img-blog.csdnimg.cn/a5d6006c20724f6eaaf57f148e724c05.png)
Java小白基础自学阶段(持续更新...)
引言 Java作为一门广泛应用于企业级开发的编程语言,对初学者来说可能会感到有些复杂。然而,通过适当的学习方法和资源,即使是小白也可以轻松掌握Java的基础知识。本文将提供一些有用的建议和资源,帮助小白自学Java基础。 学习步骤…...
![](https://www.ngui.cc/images/no-images.jpg)
Vue自定义指令- v-loading封装
Vue自定义指令- v-loading封装 文章目录 Vue自定义指令- v-loading封装01-自定义指令自定义指令的两种注册语法: 02自定义指令的值03-自定义指令- v-loading指令封装 01-自定义指令 什么是自定义指令? 自定义指令:自己定义的指令,…...
![](https://www.ngui.cc/images/no-images.jpg)
C++中提供的一些关于查找元素的函数
C中提供的所有关于查找的函数 std::find(begin(), end(), key) std::find(begin(), end(), key):这个函数用于在一个范围内查找一个等于给定值的元素,返回一个指向该元素的迭代器,如果没有找到则返回范围的结束迭代器。 1.1 例如ÿ…...
![](https://img-blog.csdnimg.cn/42f55eab8f1846d2ada1692ac50478f9.png)
Wlan——STA上线流程与802.11MAC帧讲解以及报文转发路径
目录 802.11MAC帧基本概念 802.11帧结构 802.11MAC帧的分类 管理帧 控制帧 数据帧 STA接入无线网络流程 信号扫描—管理帧 链路认证—管理帧 用户关联—管理帧 用户上线 不同802.11帧的转发路径 802.11MAC帧基本概念 802.11协议在802家族中的角色位置 其中802.3标…...
![](https://img-blog.csdnimg.cn/20200719143913116.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dqaW5qaWU=,size_16,color_FFFFFF,t_70#pic_center)
wordpress 删除分类目录/深圳搜索引擎优化收费
大家好!我是【AI 菌】,一枚爱弹吉他的程序员。我热爱AI、热爱分享、热爱开源! 这博客是我对学习的一点总结与思考。如果您也对 深度学习、机器视觉、数据结构与算法、编程 等感兴趣,可以关注我的动态,我们一起学习&…...
![](https://oscimg.oschina.net/oscnet/e3e4e197cad6d52836d81d6b60b8193c301.jpg)
政府网站什么时候建设的/现在做网络推广都有什么方式
2019独角兽企业重金招聘Python工程师标准>>> 虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流(即字节码)”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称…...
![](/images/no-images.jpg)
做网站的ebay网/网站专业术语中seo意思是
Description 给定一张 \(n\) 个点 \(m\) 条边的无向图,一开始你在点 \(1\),且价值为 \(0\) 每次你可以选择一个相邻的点,然后走过去,并将价值异或上该边权 如果在点 \(n\),你可以选择结束游戏 求一种方案,使…...
![](/images/no-images.jpg)
建设电子商务网站的试卷/黑帽seo培训多少钱
该标签不是HTML3.2的一部分,并且只支持MSIE3以后内核,所以如果你使用非IE内核浏览器(如:Netscape)可能无法看到下面一些很有意思的效果 该标签是个容器标签 语法: <marquee></marquee>以下是一个最简单的例子&…...
![](/images/no-images.jpg)
wordpress媒体库空白/网络推广大概需要多少钱
Linuxqq安装及其所引发的问题{权限位是 777 (必须 >0755 且 <0755)},linuxqq777oghostloghost-virtual-machine:~$ ~/home/loghost/qq$ sudo dpkg -i linuxqq_v1.0.2_beta1_i386.debbash: /home/loghost/home/loghost/qq$:没有那个文件或目录//问题1出现rootl…...
![](/images/no-images.jpg)
佛山外贸网站设计/大数据培训包就业靠谱吗
简介 在这篇文章中,我将向大家演示怎样向一个通用计算器一样解析并计算一个四则运算表达式。当我们结束的时候,我们将得到一个可以处理诸如 12*-(-32)/5.63样式的表达式的计算器了。当然,你也可以将它拓展的更为强大。 我本意是想提供一个简单…...