网络编程-05IO多路复用

一、select

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
                          fd_set *exceptfds, struct timeval *timeout);
                          
返回值:成功返回检测表中的文件描述符个数,失败返回-10表示监测超时
参数:
    nfds:
        要监测的文件描述表中最大的文件描述符的值+1
    readfds:
        监测读表的首地址,如果没有监测读表,填NULL
    writefds:
        监测写表的首地址,如果没有监测写表,填NULL
    exceptfds:
        监测异常表的首地址如果没有监测异常表,填NULL
    timeout:
        超时监测,当设置时间到,没有对应的读写响应,函数select返回0
        
                                                                   
void FD_CLR(int fd,fd_set *set);    //从监测表中删除文件描述符
int FD_ISSET(int fd,fd_set *set);   //判断指定的文件描述符是否有响应,如果响应,函数返回真,否则返回假
void FD_SET(int fd,fd_set* set);    //向监测表中添加新的文件描述符
void FD_ZERO(fd_set *set);          //清0整个表
                                                     
  struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };
           
eg:
                struct timeval t;
           t.tv_sec = 5;
           t.tc_usec = 500;

监测流程:

监测流程:
    1.创建监测表(读、写、异常) ---fd_set rfds,tmp;//创建初始表和临时表
    2.清空监测表    ---FD_ZERO();
    3.将用到的文件描述符添加到监测表中去 --FD_SET();
    while(1)
    {
        tmp=rfds;
        循环监测表--select(nfds,&tmp,NULL,NULL,NULL);
        for(int i=0;i<nfds;i++)
        {
            if(i==0)    //stdin
            {
                执行对应IO操作;            
            }        
            else if(i==fd)
            {
                执行对应IO操作            
            }
        }    
    }
    练习:用IO多路复用select实现TCP服务器的并发

select机制的缺点:

1.监测文件描述符的个数有1024的限制
2.初始表和返回表是分离的,每一次成功监测,我们都需要还原初始表

eg1:实现鼠标移动打印鼠标信息和可以从键盘输出打印

int main(int argc, char *argv[])
{ 
    int fd = open("/dev/input/mice", O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        exit(-1);
    }
    fd_set rfds,tmp;
    int maxfd = fd+1;
    
    FD_ZERO(&rfds);
    FD_SET(0, &rfds);
    FD_SET(fd, &rfds);
    
    char buf[64] = {0};
    char buf1[64] = {0};
       
    while(1)
    {
        tmp = rfds;
        if(select(maxfd, &tmp, NULL, NULL, NULL) < 0)
        {
            perror("select");
            exit(-1);
        }
        for(int i = 0; i < maxfd; i++)
        {
            if(FD_ISSET(i, &tmp))
            {
           if(i == 0)
           {
                 fgets(buf, 64, stdin);
                printf("%s", buf);                      
           }
           else if(i == fd)
           {
                 read(i, buf1, 64);
                printf("%d -- %d -- %d\n", buf1[0], buf1[1], buf1[2]);                      
           }
            }
           }           
       } 
    return 0;
} 

eg2:select实现服务器并发

int main(int argc, char *argv[])
{ 
    //1、创建套接字 -- socket()
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
     perror("socket");
     exit(-1);
    }
    printf("socket successful!\n");
    
    //2、绑定本机IP地址和端口号
    struct sockaddr_in saddr;
 saddr.sin_family = AF_INET;  //选择ipv4协议
 saddr.sin_port = htons(6666);
    //saddr.sin_addr.s_addr = inet_addr("192.168.16.163");  //将点分十进制转换为整形之后再赋值
 saddr.sin_addr.s_addr = INADDR_ANY;
    
    int s_len = sizeof(saddr);  //计算服务器地址结构的大小
    
    int ret = bind(sockfd, (struct sockaddr *)&saddr, s_len);
    if(ret < 0)
    {
     perror("bind");
     exit(-1);
    }
    printf("bind successful!\n");
    
    //3、设置监听套接字 -- listen()
 ret = listen(sockfd, 15);
    if(ret < 0)
    {
     perror("listen");
     exit(-1);
    }
    printf("listen successful!\n");
    
    
    
    //4、等待客户端连接

    struct sockaddr_in caddr;
    memset(&caddr, 0, sizeof(caddr));
        
    int c_len = sizeof(caddr);
    
 fd_set rfds,tmp;
 FD_ZERO(&rfds);
 FD_SET(sockfd, &rfds);
    
 int maxfd = sockfd+1;
 int connfd = -1;
    
 struct timeval t;

 while(1)
 {
    t.tv_sec = 5;
    t.tv_usec = 0;
    tmp = rfds;
    ret = select(maxfd, &tmp, NULL, NULL, &t);
    if(ret < 0)
    {
        perror("select");
        exit(-1);
     }
     if(ret == 0)
     {
         printf("time-out!\n");
         continue;
      }

      for(int i = 0; i < maxfd; i++)
      {
          if(FD_ISSET(i, &tmp))
          {
              if(sockfd == i)
              {
                    connfd = accept(i, (struct sockaddr *)&caddr, &c_len);
                    if(connfd < 0)
                    {
                        perror("accept");
                        exit(-1);
                    }
            printf("link successful!  ip -- %s  port -- %d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
                    
                    FD_SET(connfd, &rfds);
                    maxfd = (connfd >= maxfd)?(maxfd+1):(maxfd);
    
               }
               if(i > sockfd)
               {
                  char buf[64] = {0};
                  //memset(buf, 0, 64);
                  ret = read(i, buf, 64);
                  if(ret < 0)
                  {
                        perror("read");
                        break;
                    }
                    if(ret == 0)
                    {
                        //printf("a client leave!\n");
                        printf("%s leave!\n", inet_ntoa(caddr.sin_addr));
                        close(i);   
                        FD_CLR(i, &rfds);
                        maxfd = (i == maxfd-1)?(maxfd-1):(maxfd);
                        break;
                    }
                    printf("recv %dbytes:%s\n", ret, buf);
                }
            }
        }


    }

     close(sockfd);
    
    
    return 0;
} 

二、poll

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
返回值:
    错误返回-1,超时返回0;
参数:
    fds:监测表的首地址,监测表本质是一个结构体数组,每一个监测元素都是一个结构体
    nfds:监测表中监测事件的个数
    timeout:设置的超时时间,单位为ms,不设置时间写-1
    
        struct pollfd {
           int   fd;         /* file descriptor */
           short events;     /* requested events */   //选择监测方式
           short revents;    /* returned events */
           };
           
    fd:文件描述符
    events: 检测方式
        POLLIN: 读检测
        POLLOUT:写检测
        POLLERR:异常监测
    revents:监测成功的返回事件
        如果revents&POLLIN结果为真,表示该文件描述符有读响应
步骤:
    struct pollfd buf[100];
    for(int i=0;i<100;i++)    //初始化监测表
    {
        buf[i].fd=-1;    
    }
    int nfds=-1;
    struct pollfd[++nfds].fd=0;//buf[++nrfds].fd=0一样  //向表中插入文件描述符0
    struct pollfd[nfds].event=POLLIN;        //选择检测方式为读监测
    struct pollfd[++nfds].fd=3;            //插入文件描述符3
    struct pollfd[nfds].event=POLLIN;      //读监测  
    while(1)
    {
        poll(buf,nfds+1,-1);
        for(int i=0;i<=nfds;i++)
        {
            if(buf[i].revents & POLLIN)
            {
                if(buf[i].fd==0)    //stdin
                {
                    //实现对应的IO操作                
                }
                if(buf[i].fd==3)
                {
                    //执行相应操作
                    //操作完后
                    close(3);
                    buf[i].fd=-1;    //将文件描述符从表中删除                
                }            
            }        
        }    
    }

eg1:鼠标移动打印信息,键盘输入字符

int main(int argc, char *argv[])
{ 
    int fd = open("/dev/input/mice", O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        exit(-1);
    }

    struct pollfd buf[1000];
    for(int i = 0; i < 1000; i++)
    {
        buf[i].fd = -1;
    }

    int nfds = -1;
    buf[++nfds].fd = 0;
    buf[nfds].events = POLLIN;
    buf[++nfds].fd = 3;
    buf[nfds].events = POLLIN;

    int ret;
    char buf2[64] = {0};
    char buf1[64] = {0};
    while(1)
    {
        ret = poll(buf, nfds+1, 5000);
        if(ret < 0)
        {
            perror("poll");
            exit(-1);
        }
        if(ret == 0)
        {
            printf("time-out!\n");
            exit(-1);
        }

        for(int i = 0; i <= nfds; i++)
        {
            if(buf[i].revents & POLLIN)
            {
                if(buf[i].fd == 0)
                {
                    fgets(buf2, 64, stdin);
                    printf("%s", buf2);
                }
                else if(buf[i].fd == fd)
                {
                    read(fd, buf1, 64);
                    printf("%d -- %d -- %d\n", buf1[0], buf1[1], buf1[2]);
                }
            }
        }
    }

    return 0;
} 

eg2:poll实现服务器并发

int main(int argc, char *argv[])
{ 
    //1、创建套接字 -- socket()
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("socket");
        exit(-1);
     }
     printf("socket successful!\n");
    
//2、绑定本机IP地址和端口号
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;  //选择ipv4协议
    saddr.sin_port = htons(6666);
    //saddr.sin_addr.s_addr = inet_addr("192.168.16.163");  //将点分十进制转换为整形之后再赋值
    saddr.sin_addr.s_addr = INADDR_ANY;
    
     int s_len = sizeof(saddr);  //计算服务器地址结构的大小
    
     int ret = bind(sockfd, (struct sockaddr *)&saddr, s_len);
     if(ret < 0)
     {
         perror("bind");
         exit(-1);
     }
     printf("bind successful!\n");
    
    //3、设置监听套接字 -- listen()
     ret = listen(sockfd, 15);
     if(ret < 0)
     {
         perror("listen");
         exit(-1);
     }
     printf("listen successful!\n");
    
    
    //4、等待客户端连接

    struct sockaddr_in caddr;
    memset(&caddr, 0, sizeof(caddr));
        
    int c_len = sizeof(caddr);
    
    struct pollfd buf[100];
    for(int i = 0; i < 100; i++)
    {
     buf[i].fd = -1;
    }
    
    int nfds = -1;
 buf[++nfds].fd = sockfd;
 buf[nfds].events = POLLIN;    
    
 int connfd = -1;
 int j;

 while(1)
{
    ret = poll(buf, nfds+1, 10000);
    if(ret < 0)
    {
        perror("poll");
        exit(-1);
    }
    if(ret == 0)
    {
        printf("time-out!\n");
        continue;
     }

    for(int i = 0; i <= nfds; i++)
    {
         if(buf[i].revents & POLLIN)
         {
              if(sockfd == buf[i].fd)
              {
                  connfd = accept(buf[i].fd, (struct sockaddr *)&caddr, &c_len);
                  if(connfd < 0)
                  {
                        perror("accept");
                        exit(-1);
                  }
                printf("link successful!  ip -- %s  port -- %d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
                    
                  for(j = 0; j < nfds+1; j++)
                  {
                       if(buf[j].fd == -1)
                       {
                           buf[j].fd = connfd;
                           buf[nfds].events = POLLIN;
                           break;        
                       }
                  }
                  if(j == nfds+1)
                  {
                      buf[++nfds].fd = connfd;
                        buf[nfds].events = POLLIN;
                   }
                   
                     if(buf[i].fd > sockfd)
                     {
                        char buf1[64] = {0};
                        //memset(buf, 0, 64);
                        ret = read(buf[i].fd, buf1, 64);
                        if(ret < 0)
                        {
                            perror("read");
                            break;
                        }
                        if(ret == 0)
                        {
                            //printf("a client leave!\n");
                            printf("%s leave!\n", inet_ntoa(caddr.sin_addr));
                            close(buf[i].fd);   
                            buf[i].fd = -1;                       
                            break;
                        }
                        printf("recv %dbytes:%s\n", ret, buf1);
                    }
                }
            }
        }

    close(sockfd);
    
    
    return 0;
} 

三、epoll

epoll监测表的实质是一个结构体数组存放每一个需要监测的信息,
和poll不同的是epoll的描述符会依次填充

0.epoll执行步骤

int epfd=epoll_create();
struct epoll_event buf[N];    //创建表
epoll_ctl();                  //向表中添加元素
while(1)
{
    ret=epoll_wait();
    for(int i=0;i<ret;i++)
    {
        if(buf[i].events & EPOLLIN)
        {
            if(buf[i].data.fd==sockfd)
            {
                accept();            
            }        
        }    
    }
}

1.epoll_create()

#include <sys/epoll.h>

int epoll_create(int size);
返回值:
   成功:返回控制监测表的句柄,本质是一个文件描述符
           该句柄的作用:用来操作整个表
   失败:返回-1
参数:
    size:大于0的整数,可以是任意值    

2.epoll_ctl

#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:向表中添加元素

返回值:失败返回-1,成功返回0
参数:
    epfd:控制监测表的句柄
    op:
        EPOLL_CTL_ADD:添加文描述符
        EPOLL_CTL_DEL:删除文件描述符
        EPOLL_CTL_MOD:修改文件描述符
    fd:
        想要操作的文件描述符
    
           struct epoll_event {
               uint32_t     events;      /* Epoll events */
               epoll_data_t data;        /* User data variable */
           };                 
          typedef union epoll_data {
               void        *ptr;
               int          fd;
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;
           
     例:向监测表添加文件描述符3
     struct epoll_event buf[100];
     for(int i=0;i<100;i++)
     {
         buf[i].data.fd=-1;     
     }                     
     int nfds=-1;
     buf[++nfds].data.fd=3;
     buf[nfds].events=EPOLLIN;
     epoll_ctl(epfd,EPOLL_CTL_ADD,3,&buf[nfds]);                                    

3.epoll_wait()

#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event *events,
                                  int maxevents, int timeout);
返回值:
    成功返回响应事件的个数,
    失败返回-10表示超时
参数:
    epfd:句柄
    events:监测表的首地址   (监测表的实质是一个结构体数组)
    maxevents:监测事件的个数 nfds+1  (nfds为下标)
    timeout:设置的超时时间,单位为ms,如果不想设置超时时间,填-1
    
判断事件是否有响应:
    buf[i].events&EPOLLIN----结果为真表示有读响应

eg.用epoll来实现 鼠标移动打印信息,键盘输入字符

int main(int argc, char *argv[])
{ 
    int fd = open("/dev/input/mice", O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        exit(-1);
    }

    struct epoll_event buf[100];
    for(int i = 0 ; i < 100; i++)
    {
    buf[i].data.fd = -1;
    }
    
    int epfd = epoll_create(1);        //
    
    int nfds = -1;
 buf[++nfds].data.fd = 0;        //将0放进结构体数组中
 buf[nfds].events = EPOLLIN;
    epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &buf[nfds]);
    
 buf[++nfds].data.fd = 3;        //将描述符3放进监测表
 buf[nfds].events = EPOLLIN;
    epoll_ctl(epfd, EPOLL_CTL_ADD, 3, &buf[nfds]);    

char buf1[64] = {0};
char buf2[64] = {0};
while(1)
{
    int ret = epoll_wait(epfd, buf, 100, 10000);
    for(int i = 0; i < ret; i++)
    {
        if(buf[i].events & EPOLLIN)
        {
            if(buf[i].data.fd == 0)
            {
                 fgets(buf1, 64, stdin);
                printf("%s", buf1);                   
            }
            else if(buf[i].data.fd == fd)
            {
                 read(fd, buf2, 64);
                printf("%d -- %d -- %d\n", buf2[0], buf2[1], buf2[2]);                   
            }
        }
    }
   }

    return 0;
} 

4. 三种机制优缺点

1.select机制:
    缺点:
        检测文件描述符最大只能管理1024个
        初始表和返回表是分离的,每一次在检测之间应该还原初始表

2.poll机制
    特点:
        没有检测文件描述符1024的上限
        初始化表和返回表是结合的
        
3.epoll机制:
    只能在linux下使用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值