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

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有很大的减小,所以它高效。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值