码迷,mamicode.com
首页 > 其他好文 > 详细

IO多路复用——select

时间:2016-08-01 23:34:07      阅读:431      评论:0      收藏:0      [点我收藏+]

标签:linux   优点   select   缺点   多路复用   i/o   

IO多路复用

     是同步IO的一种,用一个进程一次等待多个IO就绪事件的发生,加大概率,尽可能高效的等。

     适用场景

  (1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。

  (2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。

  (3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。

  (4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。

  (5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。

  与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。


select函数

该函数准许进程指示内核等待多个事件中的任何一个发送,并只在有一个或多个事件发生或经历一段指定的时间后才唤醒。函数原型如下:

#include <sys/.h>
<sys/>
 ( maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,返回值:就绪描述符的数目,超时返回0,出错返回-1

函数参数介绍如下:

(1)第一个参数maxfdp1指定待测试的描述字个数,它的值是待测试的最大描述字加1(因此把该参数命名为maxfdp1),描述字0、1、2...maxfdp1-1均将被测试。因为文件描述符是从0开始的。

(2)中间的三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述字。如果对某一个的条件不感兴趣,就可以把它设为空指针。struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符,可通过以下四个宏进行设置:

          void FD_ZERO(fd_set *fdset);           //清空集合

          void FD_SET(int fd, fd_set *fdset);   //将一个给定的文件描述符加入集合之中

          void FD_CLR(int fd, fd_set *fdset);   //将一个给定的文件描述符从集合中删除

          int FD_ISSET(int fd, fd_set *fdset);   // 检查集合中指定的文件描述符是否可以读写 

fd_set结构体是文件描述符集,该结构体实际上是一个整型数组,数组中的每个元素的每一位标记一个文件描述符。fd_set能容纳的文件描述符 数量由FD_SETSIZE指定,一般情况下,FD_SETSIZE等  于1024,这就限制了select能同时处理的文件描述符的总量。

(3)timeout告知内核等待所指定描述字中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和微秒数。

         struct timeval{

                   long tv_sec;   //seconds

                   long tv_usec;  //microseconds

       };

这个参数有三种可能:

    1)永远等待下去:仅在有一个描述字准备好I/O时才返回。为此,把该参数设置为空指针NULL。

    2)等待一段固定时间:在有一个描述字准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。

    3)根本不等待:检查描述字后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。

(4)返回值情况: 
a)超时时间内,如果文件描述符就绪,select返回就绪的文件描述符总数(包括可读、可写和异常),如果没有文件描述符就绪,select返回0; 
b)select调用失败时,返回 -1并设置errno,如果收到信号,select返回 -1并设置errno为EINTR。

(5)文件描述符的就绪条件: 
在网络编程中, 
1)下列情况下socket可读: 
a) socket内核接收缓冲区的字节数大于或等于其低水位标记SO_RCVLOWAT; 
b) socket通信的对方关闭连接,此时该socket可读,但是一旦读该socket,会立即返回0(可以用这个方法判断client端是否断开连接); 
c) 监听socket上有新的连接请求; 
d) socket上有未处理的错误。 
2)下列情况下socket可写: 
a) socket内核发送缓冲区的可用字节数大于或等于其低水位标记SO_SNDLOWAT; 
b) socket的读端关闭,此时该socket可写,一旦对该socket进行操作,该进程会收到SIGPIPE信号; 
c) socket使用connect连接成功之后; 
d) socket上有未处理的错误。


selelct原理图

技术分享

说明:

   1、select只负责等待IO,不负责对IO进行操作,由recv/send等函数进行

   2、select一共有两次系统调用:1)select系统调用 2)recvfrom系统调用

select原理概述

调用select时,会发生以下事情:

  1. 从用户空间拷贝fd_set到内核空间;

  2. 注册回调函数__pollwait;

  3. 遍历所有fd,对全部指定设备做一次poll(这里的poll是一个文件操作,它有两个参数,一个是文件fd本身,一个是当设备尚未就绪时调用的回调函数__pollwait,这个函数把设备自己特有的等待队列传给内核,让内核把当前的进程挂载到其中);

  4. 当设备就绪时,设备就会唤醒在自己特有等待队列中的【所有】节点,于是当前进程就获取到了完成的信号。poll文件操作返回的是一组标准的掩码,其中的各个位指示当前的不同的就绪状态(全0为没有任何事件触发),根据mask可对fd_set赋值;

  5. 如果所有设备返回的掩码都没有显示任何的事件触发,就去掉回调函数的函数指针,进入有限时的睡眠状态,再恢复和不断做poll,再作有限时的睡眠,直到其中一个设备有事件触发为止。

  6. 只要有事件触发,系统调用返回,将fd_set从内核空间拷贝到用户空间,回到用户态,用户就可以对相关的fd作进一步的读或者写操作了。

select优点

  select模型是Windows sockets中最常见的IO模型。它利用select函数实现IO 管理。通过对select函数的调用,应用程序可以判断套接字是否存在数据、能否向该套接字写入据。

    如:在调用recv函数之前,先调用select函数,如果系统没有可读数据那么select函数就会阻塞在这里。当系统存在可读或可写数据时,select函数返回,就可以调用recv函数接   收数据了。

   可以看出使用select模型,需要两次调用函数。第一次调用select函数第二次socket API。使用该模式的好处是:可以等待多个套接字。

select缺点

  1. 最大并发数限制:使用32个整数的32位,即32*32=1024来标识fd;

  2. 效率低:每次都会线性扫描整个fd_set,集合越大速度越慢;

  3. 内核/用户空间内存拷贝问题。



select实现TCP服务器技术分享

代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int fds[64];
const fds_nums=sizeof(fds)/sizeof(fds[0]);
static int startup(const char *ip,int port)
{
  int sock=socket(AF_INET,SOCK_STREAM,0);
  if(sock<0)
  {
    perror("socket");
    exit(2);
  }
  int opt = 1;
  setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
  struct sockaddr_in local;
  local.sin_family=AF_INET;
  local.sin_port=htons(port);
  local.sin_addr.s_addr=inet_addr(ip);
  if(bind(sock,(struct sockaddr *)&local,sizeof(local))<0)
  {
    perror("bind");
    exit(3);
  }
  if(listen(sock,5)<0)
  {
    perror("listen");
    exit(4);
  }
  return sock;
}
static void usage(const char *proc)
{
  printf("%s [ip] [port]\n",proc);
}
int main(int argc,char *argv[])
{
  if(argc!=3)
  {
    usage(argv[0]);
    exit(1);
  }
  int listen_sock=startup(argv[1],atoi(argv[2]));
  fd_set rset;
  int i=0;
  FD_ZERO(&rset);
  FD_SET(listen_sock,&rset);
  //initial fds
  for(;i<fds_nums;i++)
  {
      fds[i]= -1;
  }
  fds[0]=listen_sock;
  int done=0;
  while(!done)
  {
    //reset current rset
    int max_fd= -1;
    for(i=0;i<fds_nums;i++)
    {
      if(fds[i]>0)
      {
        FD_SET(fds[i],&rset);
        max_fd=max_fd<fds[i]?fds[i]:max_fd;
      }
    }
    //struct timeval _ti={5,0};
    switch(select(max_fd+1,&rset,NULL,NULL,NULL))
    {
      case 0:
        printf("time out...\n");
        break;
      case -1:
        perror("select");
        break;
      default:
          for(i=0;i<fds_nums;i++)
          {
            //listen_fd
            if(i==0&&FD_ISSET(listen_sock,&rset))
            {
                //printf("there\n");
                struct sockaddr_in peer;
                socklen_t len=sizeof(peer);
                int newfd=accept(listen_sock,(struct sockaddr *)&peer,&len);
                if(newfd>0)
                {
                  printf("get a new client$ socket->%s:%d\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port));
                }
                int j=0;
                for(j;j<fds_nums;j++)
                {
                  if(fds[j]== -1)
                  {
                    fds[j]=newfd;
                    break;
                  }
                }
                //mfull of queue
                if(j==fds_nums)
                {
                  close(newfd);
                }
            }  
            else//normal accept_fd
            {
             // printf("there\n");
              if(FD_ISSET(fds[i],&rset))
              {
               
                char buf[1024];
                memset(buf,‘\0‘,sizeof(buf));
                ssize_t _s=read(fds[i],buf,sizeof(buf)-1);
                if(_s>0)
                {
                  buf[_s-1]=‘\0‘;
                  printf("client$ %s\n",buf);
                }
                else if(_s==0)
                {
                  printf("%d is read done..\n",fds[i]);
                  close(fds[i]);
                  fds[i]= -1;
                }
                else{
                  perror("read");
                }
                
              }
            }
          }
        break;
    }
  }
  return 0;
}

结果:技术分享

本文出自 “momo就是辣么萌” 博客,请务必保留此出处http://momo462.blog.51cto.com/10138434/1832969

IO多路复用——select

标签:linux   优点   select   缺点   多路复用   i/o   

原文地址:http://momo462.blog.51cto.com/10138434/1832969

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!