IO多路复用之select和epoll(详细注释)

    xiaoxiao2024-12-21  4

    select: #include<stdio.h> #include<sys/socket.h> #include<arpa/inet.h> #include<netinet/in.h> #include<assert.h> #include<unistd.h> #include<stdlib.h> #include<sys/types.h> void usage(char * argv) { printf( "%s\n",argv); } int startup(char * ip,int port) { int listen_socket=socket(AF_INET,SOCK_STREAM,0); if(listen_socket<0) { perror( "socket"); exit(1); } struct sockaddr_in local; local.sin_family=AF_INET; local.sin_port=htons(port); local.sin_addr.s_addr=inet_addr(ip); //绑定 if(bind(listen_socket,(struct sockaddr*)&local,sizeof(local))<0) { perror( "bind"); exit(2); } //监听 if(listen(listen_socket,5)) { perror( "listen"); exit(3); } return listen_socket; } int fds[64]; int main(int argc,char* argv[]) { if(argc!=3) { usage(argv[0]); exit(1); } char*ip= argv[1]; int port=atoi(argv[2]); int listen_socket=startup(ip,port);//创建一个描述本地IP和端口号的套接字 struct sockaddr_in peer;//保存远端的IP和端口信息 socklen_t len= sizeof(peer); int max_fd=-1;//文件描述符最大值 fd_set writes; //写文件描述符集 fd_set reads; //读文件描述符集 int new_socket=-1; int fds_num=sizeof (fds)/sizeof(fds[0]); int i=0; for(;i<fds_num;i++) { fds[i]=-1; } fds[0]=listen_socket; //将listen_socket写入到文件描述符数组中 int done=0; while(!done) { //每次循环将读文件和写文件描述集进行初始化 FD_ZERO(&writes); FD_ZERO(&reads); struct timeval _timeout={5,0}; for(i=0;i<fds_num;i++) { if(fds[i]>0) { FD_SET(fds[i],&reads);//将需要测试的fd添加到fd_set中 if(fds[i]>max_fd) { max_fd=fds[i];//获取最大文件描述符 } } } switch(select(max_fd+1,&reads,&writes,NULL,&_timeout)) { case 0: printf( "timeout\n"); break; case -1: perror( "select"); break; default: { for(i=0;i<fds_num;i++)//轮询查看所有的fd { if(fds[i]=listen_socket&&FD_ISSET(fds[i],&reads))//用于检查某个fd在select后相应的位是否还为1(这里接收的是本地监听套接字) { new_socket=accept(listen_socket,( struct sockaddr*)&peer,&len); if(new_socket<0) { perror( "accept"); continue; } printf( "conect succeed:%d\n" ,new_socket);//等待接收远端的套接字成功 for(i=0;i<fds_num;i++) { if(fds[i]==-1) { fds[i]=new_socket;//将创建好的远端套接字加入到fd数组中 break; } } } else if (fds[i]>0&&FD_ISSET(fds[i],&reads))//监听的是远端套接字,可以进行数据发送和读写了 { char buf[1024]; ssize_t s=read(fds[i],buf,sizeof (buf)-1); if(s>0) { buf[s]= '\0'; printf( "%s\n",buf); } else if (s==0) { fds[i]=-1; printf( "client is closed\n"); } else perror( "read"); } else continue; } } } } return 0; }

    epoll:

    #include<stdio.h> #include<stdlib.h> #include<errno.h> #include<sys/epoll.h> #include<sys/socket.h> #include<unistd.h> #include<string.h> #include<netinet/in.h> #include<fcntl.h> #include<arpa/inet.h> void usage(char* argv) { printf("usage:[ip] [port]\n",argv); } int startup(const char*_ip,int _port)//创建本地套接字 { int listen_socket=socket(AF_INET,SOCK_STREAM,0); if(listen_socket<0) { perror("socket"); exit(2); } int opt=1; setsockopt(listen_socket,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//防止client断开后还继续占用 server一段时间 struct sockaddr_in local; local.sin_family=AF_INET; local.sin_port=htons(_port); local.sin_addr.s_addr=inet_addr(_ip); if(bind(listen_socket,(struct sockaddr*)&local,sizeof(local))<0) { perror("bind"); exit(3); } if(listen(listen_socket,5)<0) { perror("listen"); exit(4); } return listen_socket; } int main(int argc,char* argv[]) { if(argc!=3) { usage(argv[1]); exit(1); } int listen_sock=startup(argv[1],atoi(argv[2])); int epfd=epoll_create(256);//创建一个epoll句柄 if(epfd<0) { perror("epoll_create"); exit(2); } struct epoll_event _ev;//需要监听的事件 _ev.events=EPOLLIN; _ev.data.fd=listen_sock; epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&_ev);//先注册要监听事件的类型(第二参数表示对fd的动作),_ev即是所要监听的的事件 struct epoll_event revs[64]; int timeout=-1; int num=0; int done=0; while(!done) { switch((num=epoll_wait(epfd,revs,64,timeout)))//将监听发生过的事件赋值到revs中 { case 0: printf("timeout\n"); break; case -1: printf("epoll_wait\n"); break; default://就绪文件描述符的值,通过映射找到相关的文件描述符 { struct sockaddr_in peer; socklen_t len=sizeof(peer); int i=0; for(;i<num;i++)//遍历的是已经等待成功的事件的数目(和select的区别,select是遍历自己定义的整个的文件描述符的数组) { int rsock=revs[i].data.fd; if(rsock==listen_sock&&\ (revs[i].events&EPOLLIN)) { int new_fd=accept(listen_sock,(struct sockaddr*)&peer,&len);//接收远端的套接字 if(new_fd>0) { printf("get a new client:%s:%s\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port)); // set_nonblock(new_fd); _ev.events=EPOLLIN|EPOLLET; _ev.data.fd=new_fd; epoll_ctl(epfd,EPOLL_CTL_ADD,new_fd,&_ev); } } else//远端的套接字已经获取成功,可以进行数据的发送和读写了 { if(revs[i].events&EPOLLIN) { char buf[1024]; ssize_t s=read(rsock,buf,sizeof(buf)); if(s>0) { buf[s]='\0'; printf("client:%s\n",buf); _ev.events=EPOLLOUT|EPOLLET; _ev.data.fd=rsock; epoll_ctl(epfd,EPOLL_CTL_MOD,rsock,&_ev); } else if(s==0) { printf("client is closed...:%d\n",rsock); epoll_ctl(epfd,EPOLL_CTL_DEL,rsock,NULL); close(rsock); } else { perror("read"); } } else if(revs[i].events&EPOLLOUT) { const char* msg="HTTP/1.0 200 OK\r\n\r\n<html><h1>hello bvit!</h1></html>\r\n"; write(rsock,msg,strlen(msg)); epoll_ctl(epfd,EPOLL_CTL_DEL,rsock,NULL); close(rsock); } } } } } } }

    总结:select是先将相应的套接字添加到文件描述符中,进过select返回,然后经过将所有遍历整个文件描述符数组,并且看对应的fd是否在对应的文件描述符集中也为1,如果不是1则跳过这一个fd,对下一个fd进行判断;epoll是先创建一个epoll句柄,然后再注册一个自己想要的事件,然后通过epoll_ctl函数将本地的fd添加到epoll模型中,同时对这个fd要监听的事件为上一步注册的事件。让后让epoll模型循环等待,等待成功则将监听发生过的事件放在一个事件数组里面,下来就可以对这个已近发生过的事件的数组进行遍历和相应判断,这样这个遍历的成本较select有很大的减小,所以它高效。
    转载请注明原文地址: https://ju.6miu.com/read-1294822.html
    最新回复(0)