高级IO----select

一,阻塞式IO

当需要执行一项任务,而且去执行这项任务必须要满足一个条件,如果在条件满足前一直等待,直到满足条件再去完成任务。

这种方式就是阻塞式 。

比如:一个人去钓鱼,没有鱼上钩就一直等待,直到有鱼上钩,下去拉鱼竿。

二,非阻塞式IO

当需要执行一项任务,而且去执行这项任务必须要满足一个条件,如果在条件满足前不等待 ,而是去干其他的事情,但要按时查看条件是否满足,直到满足条件再去完成任务。

这种方式就是非阻塞式 。

比如:一个人去钓鱼,没有鱼上钩就去睡觉,每5分钟查看 一次,直到有鱼上钩,下去拉鱼竿。

三,信号驱动IO(缺点太多)

当需要执行一项任务,而且去执行这项任务必须要满足一个条件,如果在条件满足前不等待 ,而是设置一个报告,如果有报告发生,必须马上去完成任务。

这种方式就是信号驱动式 。

比如:一个人去钓鱼,没有鱼上钩就去睡觉,在鱼竿上安装一个门铃,直到有鱼上钩触发闹铃,下去拉鱼竿。

四,IO多路转换

比如:一个人去钓鱼,但是他拿了5个或者更多的鱼竿,在没有鱼上钩的时候,就等着,有鱼上钩就拉杆。

其实就是多个阻塞式任务一起执行。

五,异步IO

比如:一个人去钓鱼,没有鱼上钩就去做其他的事,雇佣一个人去给我看着鱼竿,有鱼上竿了,并且给我拉上来了,给我发送一个消息,我不一定马上去收鱼 ,可以等我有时间在去。

总结:

任何IO过程中 , 都包含两个步骤. 第⼀是等待, 第⼆是拷⻉. ⽽且在实际的应⽤场景中, 等待消耗的时 间往往都远远⾼于拷⻉的时间. 让IO更⾼效, 最核⼼的办法就是让等待的时间尽量少.。

 

六,阻塞 VS 非阻塞

阻塞与非阻塞关注的是程序在等待调用结果返回前的状态。

阻塞调用是指在等待调用结果是时,当前线程会被挂起,直到调用 结果返回。

⾮阻塞调⽤指在不能⽴刻得到结果之前,该调⽤不会阻塞当前线程.

 

七,同步通信 VS 异步通信

同步:调用者发出一个调用,在没有得到结果之前,该调⽤就不返回. 但是⼀旦调⽤返回, 就得到返回值了; 换句话说,就是由调⽤者主动等待这个调⽤的结果。

异步:异步调用则相反,调用者发出调用后,直接返回了,不主动的等待调用的结果,而之后调用会在产生结果后通知调用者。

同步与互斥:这里的同步指的为了完成一个任务,多个进程和多个线程按照一定的顺序执行。互斥指的是多个线程访问临界资源时相互排斥,比如,一个进程向文件里读,另一个进程不能系统向文件里写

总结:

举例:给小孩换尿布

同步阻塞:主动等待返回结果,而且线程挂起,比如:守在小孩身边,不做其他的事,一直等着小孩哭,小孩哭了就去换尿布。

异步阻塞:被动等待结恶,而且线程挂起,比如:守在小孩身边,不做其他的事,但是不等待小孩哭,小孩哭了就去换尿布。

同步非阻塞:

主动等待返回结果,但是线程不挂起,比如:不守在小孩身边,做其他的事,但一直等着小孩哭,小孩哭了就去换尿布。

异步非阻塞:被动等待结恶,而且线程不挂起,比如:不守在小孩身边,做其他的事,但是不等待小孩哭,小孩哭了就去换尿布。

 

八,io多路转接之select

 1,什么是select?

系统提供select函数来实现多路复用输出/输入模型

    1,select 系统调用是用来让我们的程序监视多个文件描述符的状态变化的:

    2,程序会停在select这里等待,直到被监视的文件描述符右一个或多个发生了变化;

2,select函数原型

#include<sys/select.h>

int select (int nfds , fd_set * readfds ,fd_set* writefds ,fd_set * exceptfds ,struct timeval * timeout);

   1,int  nfds :这个参数是需要监视的最大的文件描述符+1;

   2,rdset ,wrset , exset分别是需要检测的可读⽂件描述符的集合,可写⽂件描述符的集合及异常⽂件描述符的集合;

          这三个都是fd_set结构体指针:这个结构体内部就是一个整数(long)的数组,将他用作位图使用,也就是这个数组中的每一          个比特位表示一个文件描述符的状态,第10个比特位就表示了10号文件描述符的状态。

         下面提供一组函数控制位图

  

void FD_SET(int fd, fd_set *fdset);       //将fd加入set集合
void FD_CLR(int fd, fd_set *fdset);       //将fd从set集合中清除
int  FD_ISSET(int fd, fd_set *fdset);     //检测fd是否在set集合中,不在则返回0
void FD_ZERO(fd_set *fdset);              //将set清零使集合中不含任何fd

 

   3,struct  timeval * timeout  :这个结构体的结构如下,用来设置select()的等待时间

         ttimeout 的取值选择

             1, NULL:则表⽰select()没有timeout,select将⼀直被阻塞,直到某个⽂件描述符上发⽣了事件;(无限等待)

            ,2, 0:仅检测描述符集合的状态,然后⽴即返回,并不等待外部事件的发⽣。 (等待时间就是0)

            ,3,特定的时间值:如果在指定的时间段⾥没有事件发⽣,select将超时返回。(设定等待时间)

//头文件 
<sys/time.h>
 
//结构
timeval
{
    time_t tv_sec;  //秒 [long int]
    suseconds_t tv_usec;  //微秒 [long int]
};

    4:函数的返回值

                1,成功返回文件描述符改变的个数,

               ,2,返回0表示在文件描述符状态改变前已经超出等待时间,没有返回

               ,3,当有错误发⽣时则返回-1,错误原因存于errno,此时参数readfds,writefds, exceptfds和timeout 的值变成不可预测

 

3,理解select执行过程

                为了方便说明,取fd_set这个位图的大小时一个字节,共用8个 比特位,可以监控的文件描述符是0~7。

                1,fd_set  set;int  fd = 5,执行 FD_ZERO( &set ),set位图是 0000 0000。

                2,执行 FD_SET ( fd,&set ),set位图是 0010 0000,再加入int  fd=1,int fd=2;set位图的结果是 0010 0110

                3,执⾏select(6,&set,0,0,0)阻塞等待 *(5)若fd=1,fd=2上都发⽣可读事件,则select返回,此时set 变为0000,0110。

               ,4,注意:没有事件发⽣的fd=5被清空

4,select的特点

                1,可监控的文件描述符的个数取决于sizeof( fd_set )的值,sizeof(fd_set)的值是字节数的大小,

                     所以总共可监控的    描述符的值时sizeof(fd_set)*8个 ,当文件描述符太多时,select就搞不定了。  

                2,将fd 加入select返回后,需要用将被监控的 fd 集合保存一份,保存到tmp中

                                1,因为select返回后,没有状态就绪的文件描述符会被清空,导致下次监控不到,如listen_socket,s

                                      所以保存下来,在进入下次select之前,重新设置。

                               2,用经过select处理的位图,去判断是加入新的文件描述符还是进行读写操作。


5,select的缺点

                  1, 重复的使用文件描述符集合使用户每次调用select时都要重新设置文件描述符集合 ,

                        需要用户自己重新定义输入的文件描述符表,和输出的文件描述表,从接口使用角度来说也不方便。

                  2,每次调用select,都需要把fd集合从用户 态拷贝到内核态,这个开销在fd很多时会很大

                  3,同时每次调⽤select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很⼤

                  4,select⽀持的⽂件描述符数量太⼩

6,socket就绪的条件

             只要文件描述符的是就绪状态,那么这个文件描述符在被select函数监控后,就不会被清空

                读就绪

                     1, socket内核中, 接收缓冲区中的字节数, ⼤于等于低⽔位标记SO_RCVLOWAT.

                            此时可以⽆阻塞的读 该⽂件描述符, 并且返回值⼤于0;

                     2,socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;

                     3,监听的socket上有新的连接请求; socket上有未处理的错   

               写就绪

                          1,socket内核中, 发送缓冲区中的可⽤字节数(发送缓冲区的空闲位置⼤⼩),

                               ⼤于等于低⽔位标记 SO_SNDLOWAT, 此时可以⽆阻塞的写, 并且返回值⼤于0;

                          2,socket的写操作被关闭(close或者shutdown). 对⼀个写操作被关闭的socket进⾏写操作, 会触发 SIGPIPE信号;

                          3,socket使⽤⾮阻塞connect连接成功或失败之后;

九,编写基于 TCP的select 多路复用网络服务器

#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/select.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>

typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;

typedef struct Fdset
{
  fd_set set;
  int max_fd; //set 之中的最大文件描述符是哪个 
}FdSet;



void FdSetInit(FdSet* fdset)
{
    FD_ZERO(&fdset->set);//初始化set
    fdset->max_fd=0;
}
//此处的插入过程需要 维护max_fd
void FdSetAdd(FdSet* fdset,int fd)
{
    FD_SET(fd,&fdset->set);
    if(fd > fdset->max_fd)//将最大的文件描述符保存起来
    {
      fdset->max_fd=fd;
    }
}


void FDSetDel(FdSet* fdset , int fd)
{
  FD_CLR(fd,&fdset->set);
  //如果删最大的文件描述符,就要更新最大文件描述符,那怎么去找第二大文件描述符
  int max_fd=-1;
  int i=0;
  for( ;i<=fdset->max_fd;i++)
  {
    if(!__FD_ISSET(i,&fdset->set))
    {
      continue;
    }
    if(i>max_fd)
    {
      max_fd=i;
    }
  }
  fdset->max_fd=max_fd;
}

int ProcessRequest(int new_sock)
{
  //循环从 socket中读取数据
  char buf[1024]={0};
  ssize_t read_size = read(new_sock,buf,sizeof(buf)-1);
  if(read_size <0 )
  {
    perror("read");
    return -1;
  }
  if(read_size==0)
  {
    printf("read done");
    return  0;
  }
  buf[read_size]='\0';
  printf("[client %d]say :%s\n",new_sock,buf);;
  write(new_sock,buf,sizeof(buf));
  return 1;
}



int ServerInit(char* ip,short port)
{
  int fd = socket(AF_INET,SOCK_STREAM,0);
  if(fd<0)
  {
    perror("socket");
    return -1;
  }
  sockaddr_in addr;
  addr.sin_family=AF_INET;
  addr.sin_addr.s_addr=inet_addr(ip);
  addr.sin_port=htons(port);
  int ret=bind(fd,(sockaddr*)&addr,sizeof(addr));
  if(ret<0)
  {
    perror("bind");
    return -1;
  }
  ret=listen(fd,5);
  if(ret<0)
  {
    perror("listen");
    return -1;
  }
  return fd;
}

//./server [ip] [port]
int main (int argc,char* argv[])
{
  if(argc != 3)
  {
    printf("Usage ./server [ip] [port]\n");
    return 1;
  }
//服务器初始化,创建一个socket
  int listen_socket=ServerInit(argv[1],atoi(argv[2]));
  if(listen_socket<0)
  {
    printf("ServerInit Failed\n");
    return 1;
  }
  printf("ServerInit KO!\n");
  //2,把listen_socket 添加到select对应的文件描述符位图中
  FdSet input_fds;
  FdSetInit(&input_fds);
  FdSetAdd(&input_fds,listen_socket);
  while(1)
  {
        //input_fds 添加内容:有新的客户端连接进来
        //input_fds 删除内容:客户端断开连接才删除
        FdSet output_fds=input_fds;//因为一经过 select函数,位图就会发生变化,所以将位图先保存起来
        int ret=select(output_fds.max_fd+1,&(output_fds.set),NULL,NULL,NULL);
        if(ret<0)
        {
          perror("select");
          continue;
        }
        if(FD_ISSET(listen_socket,&output_fds.set))//这里的要监控的位图有可能改变,所以用output-fds
        {
          //条件成立,就是有新的客户端件,建立连接,那么liste_socket就会读就绪
          //此时就可以直接调用accept获取这个连接
          sockaddr_in peer;
          socklen_t len=sizeof(peer);
          int new_sock = accept(listen_socket,(sockaddr*)&peer,&len);
          if(new_sock<0)
          {
            perror("accept");
            continue;
          }
          FdSetAdd(&input_fds,new_sock);//有新的文件描述符加入了,就必须更新到原始位图中
          printf("[client %d]\n",new_sock);
        }

        else//不是listen_socket  那就是new_socket
        {
              int i=0;
              for(;i<=output_fds.max_fd;i++)
                
              {
                if(!FD_ISSET(i,&output_fds.set))//这里的位图肯定会变化,所以用output_fds
                {
                     continue;
                }
                //对当前就绪的文件描述符进行读写处理操作
                int ret=ProcessRequest(i);
                //在这里listen_socket就没有了,所以要解决这个bug 
                if(0 == ret)
                {
                  //说明对端关闭了socket
                  close(i);
                  FDSetDel(&input_fds,i);//有文件描述符关闭了,就必须要更新到原始的位图中
                  printf("[cilent %d] cut line!",i);
                }
              }
        }

  }
  return 0;
}

十,IO多路复用之-----poll

1,函数原型

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
// pollfd结构

struct pollfd {
 int fd; /* file descriptor */
 short events; /* requested events */
 short revents; /* returned events */
};

2,参数分析

struct polled * fds :当只关注一个文件描述符时,就填一个结构体指针,当关注多个文件描述符时,就要传一个结构体数组指针。

                             在polled结构体中,int fd:表示要关注的文件描述符

                                                           evevts:输入参数,用一组宏中的一个来替换,表示要关注什么事,如读就绪

                                                           revents:输出参数,也是宏,比如读就绪是否等待完成。

nfds_t nfds:      关注的文件描述符的个数

int timout   ;      等待的时间

3,编写例子

#include<stdio.h>
#include<unistd.h>
#include<poll.h>

int main ()
{
  //定义一个这样的结构体就只能检测一个文件描述符
  //要监控多个文件描述符就要定义一个结构体数组
  struct pollfd fds;//这里只监控一个
  fds.events=POLLIN;
  while(1)
  {
    int ret=poll(&fds,1,0);
    if(ret<0)
    {
      perror("poll");
    }
    //如果poll返回了,就说明0号文件描述符就绪了
    char buf[1024]={0};
    ssize_t read_size=read(0,buf,sizeof(buf)-1);
    if(read_size<0)
    {
      perror("read");
      return 1;
    }
    if(read_size==0)
    {
      printf("read done!");
      return 0;
    }
    buf[read_size]='\0';
    printf ("%s\n",buf);
  }
  return 0;
}

 

十一;poll和select的比较

1,在接口方面,poll和select每次都需要重新设置文件描述符。但是poll设置了输出文件描述符集合,

      相当于解决了select要用户自己定义输出描述符表的问题。不存在描述符表复用的问题。

2,select需要将fd位图集合拷贝到内核,poll也需要将结构体数组(多个文件描述符),

      拷贝到内核,而且是结构体,开销更大。拷贝的频率也是一样的。

3,select和poll也都需要遍历文件描述符集合。

4,select有上限,是4096,poll没有上限,可以有无限制个结构体。

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值