1.select
思路:告诉内核监听这些fd --- 时间延迟为NULL 一直阻塞 --- 有事情发生,返回ret即表示有多少fd发生事件 --- 首先检查监听fd是否发生,若接入则然后通过遍历利用FD_ISSET检测哪些fd发生事件
实现客服端的代码
#include <sys/select.h> #include <unistd.h> #include <stdio.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> #include <stdlib.h> int main() { //创建socket int lfd=socket(AF_INET,SOCK_STREAM,0); //BIND struct sockaddr_in saddr; saddr.sin_family=AF_INET; saddr.sin_addr.s_addr=INADDR_ANY; saddr.sin_port=htons(9999); int ret=bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr)); if(ret==-1) { perror("BIND"); exit(-1); } //listen ret=listen(lfd,128); if(ret==-1) { perror("listen"); exit(-1); } //IO多路复用 fd_set rdset,tmp; FD_ZERO(&rdset); //全部设置为0 FD_SET(lfd,&rdset); //把监听到的字符设置为1 int maxfd=lfd; while(1) { tmp=rdset; //调用select让内核检测哪些fd有数据 ret=select(maxfd+1,&tmp,NULL,NULL,NULL); if(ret==-1) { perror("select"); exit(-1); } else if(ret==0) { continue; } else if(ret>0) { //说明数据发生了改变 if(FD_ISSET(lfd,&tmp)) { //表示有新的客户端链接进来 struct sockaddr_in clientaddr; int len=sizeof(clientaddr); int cfd=accept(lfd,(struct sockaddr*)&clientaddr,&len); //将新的fd加入即合理 FD_SET(cfd,&rdset); //更新最大的文件描述符 maxfd=maxfd>cfd?maxfd:cfd; } for(int i=lfd+1;i<=maxfd;i++) { if(FD_ISSET(i,&tmp)) { //说明其对应的客户端发来了数据 char buf[1024]={0}; int len=read(i,buf,sizeof(buf)); if(len==-1) { perror("read"); exit(-1); } else if(len==0) { printf("client closed...\n"); close(i); FD_CLR(i,&rdset); //清空 } else if(len>0) { printf("recv data : %s\n", buf); write(i,buf,strlen(buf)+1); } } } } } return 0; }缺点:用户区和内核区需要来回拷贝;每次调用select需要遍历所有fd;支持的数量太少,仅1024比特;每次需要重置。
2.poll——解决了select支持数量有限且需要重置的问题
思路:封装一个结构体数组,先把fd都初始化为-1表示空闲,检测事件也进行初始化 --- 调用poll让内核检查哪些fd发生事件,返回的ret值表示几个fd改变 --- 然后先检查lfd是否发生事件,发生则表示有客服端接入,遍历找到数组里空闲的文件描述符赋值监听fd --- 遍历结构体数组中的fd,若发生事件则进行通信。
实现客服端的代码
#include <poll.h> #include <unistd.h> #include <stdio.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> #include <stdlib.h> int main() { //创建socket int lfd=socket(AF_INET,SOCK_STREAM,0); //BIND struct sockaddr_in saddr; saddr.sin_family=AF_INET; saddr.sin_addr.s_addr=INADDR_ANY; saddr.sin_port=htons(9999); int ret=bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr)); //listen ret=listen(lfd,128); //初始化结构体2 struct pollfd fds[1024]; for(int i=0;i<1024;i++) { fds[i].fd=-1; fds[i].events=POLLIN; } fds[0].fd=lfd; int nfds=0; while(1) { //调用poll让内核检测哪些fd有数据 ret=poll(fds,nfds+1,-1); if(ret==-1) { perror("select"); exit(-1); } else if(ret==0) { continue; } else if(ret>0) { //说明数据发生了改变 if(fds[0].revents&POLLIN) { //表示有新的客户端链接进来 struct sockaddr_in clientaddr; int len=sizeof(clientaddr); int cfd=accept(lfd,(struct sockaddr*)&clientaddr,&len); //将新的fd加入即合理,从头遍历一遍看哪个位置的文件描述符不可用 for(int i=1;i<1024;i++) { if(fds[i].fd==-1) { fds[i].fd=cfd; fds[i].events=POLLIN; break; } } //更新最大的文件描述符索引 nfds=nfds>cfd?nfds:cfd; } for(int i= 1;i<=nfds;i++) { if(fds[i].revents&POLLIN) { //说明其对应的客户端发来了数据 char buf[1024]={0}; int len=read(fds[i].fd,buf,sizeof(buf)); if(len==-1) { perror("read"); exit(-1); } else if(len==0) { printf("client closed...\n"); close(fds[i].fd); fds[i].fd=-1; //清空 } else if(len>0) { printf("recv data : %s\n", buf); write(fds[i].fd,buf,strlen(buf)+1); } } } } } return 0; }3.epoll
思路:先利用 epoll_create()创建一个实例 --- 初始化一个结构体epoll_event,并利用 epoll_ctl 函数把监听fd加入实例中 --- 通过调用 epoll_wait 函数检测哪些fd发生事件,创建一个epoll_event结构体数组用来保存内核检测后返回的数据 --- 函数返回值表示几个fd发生事件,遍历这几个结构体数据中的fd,若监听fd改变接入客户端则epoll_ctl将其加入到最初的实例中,其他fd则进行通信
实现服务端的代码:
#include <sys/epoll.h> #include <unistd.h> #include <stdio.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> #include <stdlib.h> int main() { //创建socket int lfd=socket(AF_INET,SOCK_STREAM,0); //BIND struct sockaddr_in saddr; saddr.sin_family=AF_INET; saddr.sin_addr.s_addr=INADDR_ANY; saddr.sin_port=htons(9999); int ret=bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr)); if(ret==-1) { perror("BIND"); exit(-1); } //listen ret=listen(lfd,128); if(ret==-1) { perror("listen"); exit(-1); } //创建一个epoll实例 int epfd=epoll_create(100); //将监听的加入进去 struct epoll_event epev; epev.events=EPOLLIN; epev.data.fd=lfd; epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev); struct epoll_event epevs[1024];//保存内核检测后返回的数据 while(1) { //调用epoll_wait让内核检测哪些fd有数据 ret=epoll_wait(epfd,epevs,1024,-1); if(ret==-1) { perror("epoll_wait"); exit(-1); } printf("ret6 id: %d\n",ret); for(int i=0;i<ret;i++) { if(epevs[i].data.fd==lfd) { //表示有新的客户端链接进来 struct sockaddr_in clientaddr; int len=sizeof(clientaddr); int cfd=accept(lfd,(struct sockaddr*)&clientaddr,&len); epev.events=EPOLLIN; epev.data.fd=cfd; epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev); } else { if(epevs[i].events&EPOLLOUT) { continue; } //说明其对应的客户端发来了数据 char buf[1024]={0}; int len=read(epevs[i].data.fd,buf,sizeof(buf)); if(len==-1) { perror("read"); exit(-1); } else if(len==0) { printf("client closed...\n"); epoll_ctl(epfd,EPOLL_CTL_DEL,epevs[i].data.fd,NULL); close(epevs[i].data.fd); } else if(len>0) { printf("recv data : %s\n", buf); write(epevs[i].data.fd,buf,strlen(buf)+1); } } } } close(lfd); close(epfd); return 0; }两种工作模式:
LT模式(水平触发):支持阻塞和非阻塞,只要文件描述符准备就绪了,内核就会一直通知
ET模式(边沿触发):支持非阻塞,只会通知一次,需要单独设置。
#include <sys/epoll.h> #include <unistd.h> #include <stdio.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> int main() { //创建socket int lfd=socket(AF_INET,SOCK_STREAM,0); //BIND struct sockaddr_in saddr; saddr.sin_family=AF_INET; saddr.sin_addr.s_addr=INADDR_ANY; saddr.sin_port=htons(9999); int ret=bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr)); if(ret==-1) { perror("BIND"); exit(-1); } //listen ret=listen(lfd,128); if(ret==-1) { perror("listen"); exit(-1); } //创建一个epoll实例 int epfd=epoll_create(100); //将监听的加入进去 struct epoll_event epev; epev.events=EPOLLIN | EPOLLET; epev.data.fd=lfd; epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev); struct epoll_event epevs[1024];//保存内核检测后返回的数据 while(1) { //调用epoll_wait让内核检测哪些fd有数据 ret=epoll_wait(epfd,epevs,1024,-1); if(ret==-1) { perror("epoll_wait"); exit(-1); } printf("ret6 id: %d\n",ret); for(int i=0;i<ret;i++) { if(epevs[i].data.fd==lfd) { //表示有新的客户端链接进来 struct sockaddr_in clientaddr; int len=sizeof(clientaddr); int cfd=accept(lfd,(struct sockaddr*)&clientaddr,&len); //设置为非阻塞 int flag=fcntl(cfd,F_GETFL); flag|=O_NONBLOCK; fcntl(cfd,F_SETFL,flag); epev.events=EPOLLIN; epev.data.fd=cfd; epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev); } else { if(epevs[i].events&EPOLLOUT) { continue; } //循环读取全部数据 char buf[5]; int len=0; while((len=read(epevs[i].data.fd,buf,sizeof(buf)))>0) { printf("len=%d ,recv data : %s\n",len, buf); write(epevs[i].data.fd,buf,len); } if(len==-1) { perror("read"); exit(-1); } else if(len==0) { printf("client closed...\n"); epoll_ctl(epfd,EPOLL_CTL_DEL,epevs[i].data.fd,NULL); close(epevs[i].data.fd); } } } } close(lfd); close(epfd); return 0; }