IO多路复用——select

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;
}

结果:

时间: 2024-12-24 08:13:56

IO多路复用——select的相关文章

非阻塞io模型和io多路复用----select

一.四种io阻塞1.io阻塞:(1 等待数据处于阻塞状态(2从内核copy到用户态处于阻塞状态2.非io阻塞只有从内核copy到用户态处于阻塞状态3.io多路复用----->优势:可以同时监听多个对象(1从check----->ready 通过selec函数来做,处于阻塞状态(2从内核copy到用户态处于阻塞状态3.异步io不用阻塞二.io多路复用select  poll epoll 都属于io同步里面的io多路复用select:轮询问题,监听数量有限poll:提高了监听数量epoll:解决了

IO多路复用-select

首先列一下,sellect.poll.epoll三者的区别 select select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作. select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一. select的一个缺点在于单个进程能够监视的文件描述符的数量存在

转一贴,今天实在写累了,也看累了--【Python异步非阻塞IO多路复用Select/Poll/Epoll使用】

下面这篇,原理理解了, 再结合 这一周来的心得体会,整个框架就差不多了... http://www.haiyun.me/archives/1056.html 有许多封装好的异步非阻塞IO多路复用框架,底层在linux基于最新的epoll实现,为了更好的使用,了解其底层原理还是有必要的.下面记录下分别基于Select/Poll/Epoll的echo server实现.Python Select Server,可监控事件数量有限制: 1 2 3 4 5 6 7 8 9 10 11 12 13 14

IO多路复用select、poll、epoll的区别

(1)select==>时间复杂度O(n) 它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作.所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长. (2)poll==>时间复杂度O(n) poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的

Python IO多路复用select模块

多路复用的分析实例:服务端.客户端 #服务端配置 from socket import * import time import select server = socket(AF_INET, SOCK_STREAM) server.bind(('127.0.0.1',8080)) server.listen(5) server.setblocking(False) ''' select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接. 当任何一个socket中的数据准

python IO 多路复用 select poll epoll

三个多路复用模型的概念 select select 原理 select 是通过系统调用来监视着一个由多个文件描述符(file descriptor)组成的数组,当select()返回后,数组中就绪的文件描述符会被内核修改标记位(其实就是一个整数),使得进程可以获得这些文件描述符从而进行后续的读写操作.select饰通过遍历来监视整个数组的,而且每次遍历都是线性的. select 优点 select目前几乎在所有的平台上支持,良好跨平台性. select 缺点 每次调用select,都需要把fd集

IO多路复用深入浅出

前言 从零单排高性能问题,这次轮到异步通信了.这个领域入门有点难,需要了解UNIX五种IO模型和 TCP协议,熟练使用三大异步通信框架:Netty.NodeJS.Tornado.目前所有标榜异步的通信框架用的都不是异步IO模型,而是IO多路复 用中的epoll.因为Python提供了对Linux内核API的友好封装,所以我选择Python来学习IO多路复用. IO多路复用 select 举一个EchoServer的例子,客户端发送任何内容,服务端会原模原样返回. #!/usr/bin/env p

IO模型--阻塞IO,非阻塞IO,IO多路复用,异步IO

IO模型介绍: * blocking IO 阻塞IO * nonblocking IO 非阻塞IO * IO multiplexing IO多路复用 * signal driven IO 信号驱动IO () * asynchronous IO 异步IO IO模型介绍: 为了更好地了解IO模型,我们需要事先回顾下:同步.异步.阻塞.非阻塞 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,

IO多路复用之select

基本概念 IO多路复用是指内核(线性扫描)一旦发现进程指定的一个或者多个IO条件准备就绪,它就通知该进程,执行定义的操作. 适用场景 1.当客户处理多个描述符时(一般是交互式输入和网络套接字),必须使用I/O复用. 2.当一个客户同时处理多个套接字时,而这种情况是可能的,但很少出现. 3.如果一个TCP服务器既要处理监听套接字,又要处理已连接套接字,一般也要用到I/O复用. 4.如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用. 5.如果一个服务器要处理多个服务或多个协议,一般