IO多路复用三种实现--select、poll、epoll

目录

1、用select写一个回射服务器。

1-1、select参数

1-2、select()返回值

1-3、回射服务器代码实现

2、用poll写一个回射服务器。

2.1、poll参数

2.2、poll()的返回值

2.3、回射服务器代码实现

3、用epoll写一个回射服务器

3.1、epoll API

3.2、epoll实现 回射服务器代码:


1、用select写一个回射服务器。

1-1、select参数

          参数readfds、writefds 以及exceptfds都 是指向文件描述符集合的指针,所指向的数据类型是fd_ set。 这些参数按照如下方式使用。

          readfds是用来检测输入是否就绪的文件描述符集合。

          writefds是用来检测输出是否就绪的文件描述符集合。

          exceptfds是用来检测异常情况是否发生的文件描述符集合。

          所有关于文件描述符集合的操作都是通过四个宏来完成的:FD__ZERO(),FD_ SET(), FD_ CLR()以及FD_ ISSET()。

           FD_ ZERO()将fdset所指向的集合初始化为空。

           FD_ SET()将文件描述符fd添加到由fdset所指向的集合中。

           FD_CLR()将文件描述符fd从fdset所指向的集合中移除。

           如果文件描述符fd是fdset所指向的集合中的成员,FD_ ISSET() 返回true。

timeout参数:

        如果结构体timeval的两个域都为0的话,此时select()不会阻塞, 它只是简单地轮询指定的文件描述符集合,看看其中是否有就绪的文件描述符并立刻返回。否则,timeout将为select()指定一个等待时间的上限值。

struct timeval {
   time t  tv_sec;  /*Seconds*/
   suseconds_t  tv_usec; / *Microseconds (long int) */
};

1-2、select()返回值

        返回-1表示有错误发生。可能的错误码包括EBADF 和EINTR。EBADF 表示readfds、writefds 或者exceptfds 中有一个文件描述符是非法的(例如当前并没有打开)。EINTR 表示该调用被信号处理例程中断了。
       返回0表示在任何文件描述符成为就绪态之前select() 调用已经超时。在这种情况下,每个返回的文件描述符集合将被清空。
       返回一个正整数表示有1个或多个文件描述符已达到就绪态。返回值表示处于就绪态的文件描述符个数。在这种情况下,每个返回的文件描述符集合都需要检查(通过FD _ISSET()),以此找出发生的I/O 事件是什么。如果同一个文件描述符在readfds、 writefds 和exceptfds 中同时被指定,且它对于多个I/O事件都处于就绪态的话,那么就会被统计多次。换句话说,select() 返回所有在3个集合中被标记为就绪态的文件描述符总数。

1-3、回射服务器代码实现

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/select.h>
#define SERPORT 8000
void myerr(char* str)
{
    perror(str);
    exit(1);
}
int main(int argc, char* argv[])
{
    int lfd =socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in seraddr,cliaddr;
    seraddr.sin_family=AF_INET;
    seraddr.sin_port=htons(SERPORT);
    seraddr.sin_addr.s_addr=INADDR_ANY;

    int ret=bind(lfd,(struct sockaddr*)&seraddr,sizeof(seraddr));
    if(ret<0)
    {
        myerr("bind error");
    }
    listen(lfd,64);
    char buf[1024];
    socklen_t addrlen=sizeof(cliaddr);
    fd_set aset,rset;
    FD_ZERO(&aset);
    FD_SET(lfd,&aset);
    int maxfd=lfd;
    int i=0;
    while(1)
    {
        rset=aset;
        int selectret=select(maxfd+1,&rset,NULL,NULL,NULL);
        if(selectret<0)
        {
            myerr("select error");
        }
        else
        {
            if(FD_ISSET(lfd,&rset))
            {
                int cfd=accept(lfd,(struct sockaddr*)&cliaddr,&addrlen);
                if(cfd<0)
                {
                    myerr("accept error");
                }
                char dst[64];
                inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,dst,sizeof(dst));
                int port=ntohs(cliaddr.sin_port);
                printf("客户端建立连接成功:clientIP=%s,clientPORT=%d,clientCFD=%d\n",dst,port,cfd);
                FD_SET(cfd,&aset);
                if(maxfd<cfd)
                {
                    maxfd=cfd;
                } 
            }
            for(i=lfd;i<maxfd+1;i++)
            {
                if(FD_ISSET(i,&rset))
                {
                    int rr=read(i,buf,sizeof(buf));
                    write(1,buf,rr);
                    write(i,buf,rr);
                }
            }
        }
    }
    return 0;
}

运行效果:

2、用poll写一个回射服务器。

2.1、poll参数

       pollfd结构体数组

#include <poll.h>
//数据结构
struct pollfd {
    int fd;  //需要监视的文件描述符
    short events;  //需要内核监视的事件
    short revents;  //实际发生的事件
};

// API
int poll (struct pollfd fds[] ,nfds_t nfds,int timeout) ;
 //Returns number of ready file descriptors, 0 on timeout, or -1 on error

       timeout参数: 参数timeout决定了pol1()的阻塞行为, 具体如下。
       如果timeout 等于-1,poll()会一 直阻塞直到fds数组中列出的文件描述符有一个达到就绪态(定义在对应的events字段中)或者捕获到一个信号。
       如果timeout等于0,pol1()不会阻塞---- 只是执行- -次检查看看哪个文件描述符处于就绪态。
       如果timeout 大于0,pol1()至多阻塞timeout 毫秒,直到fds列出的文件描述符中有一个达到就绪态,或者直到捕获到一个信号为止。同select()一样, timeout 的精度受软件时钟粒度的限制。

2.2、poll()的返回值

       返回-1表示有错误发生。一种可能的错误是EINTR, 表示该调用被一个信号
处理例程中断。
       返回0表示该调用在任意一个文件描述符成为就绪态之前就超时了。
       返回正整数表示有1个或多个文件描述符处于就绪态了。返回值表示数组fds中拥有非零
revents字段的pollfd 结构体数量。
 

2.3、回射服务器代码实现

#include <stdio.h>                                                                                                                              
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <poll.h>
#define PORT 8000
#define IP 0 //IP端口,可改
int main(int argc, char* argv[])
{
    int lfd=socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in seraddr,cliaddr;
    seraddr.sin_family=AF_INET;
    seraddr.sin_port=htons(PORT);
    seraddr.sin_addr.s_addr=IP;
    int a=bind(lfd,(struct sockaddr*)&seraddr,sizeof(seraddr));
    if(a<0)
    {
         perror("bind error:");
         return 0;
    }
   listen(lfd,64);
   socklen_t len=sizeof(cliaddr);
    //定义一个结构体数组
   struct pollfd fds[1025];//定义了1025个结构体数组,他比select优势在于他的个数可以由结构体指定,而不是像select一样固定的1024个
   int i=0;
   for(i=0; i<1025;i++)
   {
       fds[i].fd=-1;//全部赋值为-1是为了后面好操作。
   }
   fds[0].fd=lfd;//监听lfd文件描述符
   fds[0].events=POLLIN;
   int maxfd=0;//在结构体数组中的目前最大下标是0.
   while(1)
   {
       int pollnum= poll(fds, maxfd+1 , -1);//-1表示一直等待,如果没有事件发生他不会返回直接执行下面的语句。fds表示结构体数组
       if(pollnum < 0)
       {
           perror("accpet error:");
           // exit(1);
       }
       else//poll没有出错,监听有没有新的客户机和我连接
       {
           if(fds[0].revents & POLLIN)//判断lfd的POLLIN事件有没有发生,用&,判断是因为如果不仅仅是POLLIN事件发生了那么用==判断就会出错,这里只>
           //没有就执行for语句的数据处理,不加该判断会导致,没有新的连接时阻塞在accept上
           {
               int  cfd=accept(lfd,(struct sockaddr*)&cliaddr,&len);
               if(cfd < 0)//表示accept出错
               {
                   perror("accept error:");
                   // exit(1);
               }
               else
               {
                   //accept没有出错,解析客户机的IP和端口号
                   int cliport=ntohs(cliaddr.sin_port);//网络端口号转本地端口号
                   char det[1025];
                   inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, det,sizeof(det));//网络IP转本地IP
                   printf("客户端已连接:IP = %s  , 端口号 = %d\n", det , cliport);
                   //我们要把accept返回的文件描述符即与客户机通信的描述符重新加回到监听行列中
                   for(i= 1;i < 1025; i++)
                   {
                       if(fds[i].fd==-1)//前面初始化时候全部赋值为-1就是为了这里好找到空闲的
                       {
                           fds[i].fd=cfd;
                           fds[i].events=POLLIN;
                           break;
                       }
                       // if(--pollnum ==0 )//表示全部加到监听结构体里面了
                       // {
                       //   break;
                       // }为什么不能把这个if语句写在for里面,假如同时间有n个客户机连接我。但是accept一次只能从全连接队列里面取一个出来,你写在for里面看似把所有的客户机的描述符都放进来结构体数组里面,其实结构体数组里面有多个重复的fds[].fd
                   }
                   //修改maxfd的值
                   if(maxfd < i)
                   {
                       maxfd = i;
                   }
                   if(--pollnum == 0)
                   {
                       continue;
                   }
               }
           }
           //lfd处理完了,现在可以对客户机发送的数据进行处理了。
           for( i=1 ; i < maxfd+1; i++)//通过循环的方式,一个一个的去判断POLLIN事件有没有发生
           {
               if(fds[i].revents & POLLIN)//判断POLLIN事件有没有发生
               {
                   char buf[1024];
                   int rr=read(fds[i].fd, buf,sizeof(buf));
                   if(rr==0)
                   {
                       printf("客户端断开连接\n");
                       close(fds[i].fd);
                       fds[i].fd=-1;
                   }
                  if(rr<0)
                  {
                      perror("read error:");
                      exit(1);
                  }
                  write(1, buf, rr);
                  write(fds[i].fd, buf, rr);
                  if(--pollnum==0)//要么不写这个,要么只能写if(fds[i].revents & POLLIN)的里面
                  {
                      break;
                  }
              }
                 //if(--pollnum==0)
                 //{
                   //  break;
                //}//为什么不能把这个if语句写外面,假如现在只有一个客户端发送了数据,但是这个客户端的描述符可能不是1。但是你每一次都执行了--pollunm就导致了都没有判断到客户端的cfd触发没有呢就break了
           }
       }
   }
   return 0;
}

运行效果:

3、用epoll写一个回射服务器

3.1、epoll API

        记录了在进程中声明过的感兴趣的文件描述符列表一interest list (兴趣列表),
        维护了处于I/0就绪态的文件描述符列表-ready list (就绪列表)。.ready list中的成员是interest list的子集。

         epoll_create()创建epoll实例:

参数size指定了我们想要epoll实例来检查的文件描述符个数。但并不是一个上限值,目的是让内核知道应该如何为内部数据结构划分初始大小。

#include <sys/epoll.h>
int epoll_create(int size);

          epoll_ctl()修改epoll的兴趣列表

#include <sys/epo1l .h>
int epoll_ctl (int epfd,int op,int fd,struct epoll_event *event) ;

      参数fd指明了要修改兴趣列表中的哪一个文件描述符的设定。该参数可以是代表管道、套接字、POSIX 消息队列等。但是,这里fd不能作为普通文件或目录的文件描述符(会出现EPERM错误)

     参数op用来指定需要执行的操作,它可以是如下几种值。

     EPOLL_CTL_ ADD   将描述符fd添加到epoll 实例epfd中的兴趣列表中去。
     EPOLL_CTL_ MOD  修改描述符fd上设定的事件,需要用到由event 所指向的结构体中的信息。
     EPOLL_CTL_DEL     将文件描述符fd从epfd 的兴趣列表中移除。
    参数event是指向结构体epoll_ event的指针,结构体如下:

struct epoll_ event {
   uint32_t events ;   /* Epoll events */
   epoll_data_t data;  /* User data variable */
};

//结构体 epoll_ event 中的data 字段的类型为:
typedef union epoll_ data 
{
   void *ptr;
   int   fd;
   uint32_t u32 ;
   uint64_t u64 ;
} epoll_ data_ t;

        epoll_wait()等待事件

#include <sys/epo1l .h>
int epoll_wait (int epfd, struct epoll_event *events, int maxevents,int timeout) ;
//Returns number of ready file descriptors,0 on timeout, or -1 on error

      参数events所指向的结构体数组中返回的是有关就绪态文件描述符的信息。

     参数timeout 用来确定epoll_ wait()的阻塞行为,有以下几种:
     如果timeout等于-1,调用将一直阻塞,直到兴趣列表中的文件描述符.上有
事件产生,或者直到捕获到一个信号为止。
     如果timeout 等于0,执行一次非阻塞式的检查,看兴趣列表中的文件描述
符上产生了哪个事件。
     如果timeout 大于0,调用将阻塞至多timeout 毫秒,直到文件描述符上有
事件发生,或者直到捕获到一个信号为止。
 

3.2、epoll实现 回射服务器代码:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <poll.h>
#include <sys/epoll.h>
#define SERPORT 8000
#define MAXEVENT 1025

void myerr(char* str)
{
    perror(str);
    exit(1);
}

int main(int argc, char* argv[])
{
    //socket的初始化
    int lfd  = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in seraddr, cliaddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(SERPORT);
    seraddr.sin_addr.s_addr = INADDR_ANY;
    int ret = bind(lfd, (struct sockaddr*)&seraddr, sizeof(seraddr));
    if(ret < 0)
    {
        myerr("bind error");
    }
    listen(lfd, 64);
    char buf[1024];
    socklen_t addrlen = sizeof(cliaddr);
    //创建一个epoll实例
    int epfd = epoll_create(64);

    struct epoll_event event;
    event.events = EPOLLIN;
    event.data.fd = lfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &event);

    struct epoll_event events[2048];
    int i = 0;
    int cfd;
    while(1)
    {
        int epollret = epoll_wait(epfd, events, 2048, -1);
        if(epollret < 0)
        {
            myerr("epoll_wait error");
        }
        else
        {
            for(i = 0; i < epollret; i++)
            {
                if(events[i].events & EPOLLIN)
                {
                    int hfd = events[i].data.fd;
                    if(hfd == lfd)
                    {
                        cfd = accept(lfd, (struct sockaddr*)&cliaddr, &addrlen);
                        if(cfd < 0)
                        {
                            myerr("accept error");
                        }
                        char dst[64];
                        inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, dst, sizeof(dst));
                        int port = ntohs(cliaddr.sin_port);
                        printf("客户端建立连接成功:clientIP = %s, clientPORT = %d, clientCFD = %d\n", dst, port, cfd);
                        
                        event.events = EPOLLIN;
                        event.data.fd = cfd;
                        epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event);
                    }
                    else
                    {
                        int rr = read(hfd, buf, sizeof(buf));
                        if(rr < 0)
                        {
                            myerr("read error");
                        }
                        else if(rr == 0)
                        {
                            printf("客户端断连接\n");
                            epoll_ctl(epfd, EPOLL_CTL_DEL, hfd, NULL);
                            close(hfd);
                        }
                        else
                        {
                            write(1, buf, rr);
                            write(hfd, buf, rr);
                        }
                    }
                }
            }
        }
    }
    return 0;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白要躺平

谢谢您的鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值