高性能服务器————高性能服务器IO复用之select

本文主要探讨了IO复用中的select方法,详细介绍了select的工作原理、规则及系统调用参数。通过示例展示了如何使用select监控标准输入以及实现TCP服务器,并解释了在每次使用select前重置相关位图的重要性。
摘要由CSDN通过智能技术生成

谈到IO复用就不得不了解一下,都有哪些关于IO复用的方法
IO复用的方法分为3种,这篇重点介绍select,至于其余的两种方法将会在下一篇介绍,考虑到篇幅和问题研究的专一性,这篇的重点就是分析select接口和原理,以及select使用的实例,如果对于为什么要使用IO复用以及IO复用的好处,可以翻看上一篇博客。
建议在了解这篇博客之前,先了解这些概念:阻塞、非阻塞、以及文件描述符的就绪状态、IO复用(以上这些可以在上一篇IO中已有一些介绍)。
IO复用的三种方法:select、poll、epll,这几种方式均为(同步IO处理)
select:
IO复用在设计上遵守一下规则:
1.I/O多路复用:当一个文件描述符I/O就绪时进行通知。
2.都不可用?在有用的文件描述符到来之前处于睡眠状态。
3.唤醒:哪个文件描述符可用了?
4.处理所有I/O就绪的文件描述符,没有阻塞。
5.返回第1步,重新开始。
select()系统调用:

 #include <sys/select.h>
/* According to earlier standards */
#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);

参数说明:
nfds:这个值对应于所要监控的所有描述符中描述符值最大的加1
readfds:是一个fd_set类型,fd_set是一个结构体数组仅包含一个整型数组,该数组的每个元素的每一位(bit)标记一个文件描述符。fd_set能容纳的文件描述符数量有FD_SETSIZE决定,这个限制了select同时能处理的文件描述的总量。Linux下默认是1024。readfds可以看作一个位图,其中加入的文件描述符,我们关心的是它们的可读事件。
writefds:可以对照readfds理解。
exceptfds:在这个位图中加入的文件描述符,我们只关心他们的可写事件。
timeout:前面我们说过,当没有就绪的文件描述符到来时select一直处于阻塞状态,而有了这个timeout之后,程序会在调用处select阻塞timeout时间段,直到有就绪的文件描述符时才返回,或者超出这段时间时返回。
注意: select调用之后,readfds、writefds、exceptfds均会被改变,因此我们在每次使用之前需要将它们重置。
协助select工作的宏函数:

		void FD_CLR(int fd, fd_set *set);//将一个文件描述符从set中清除 
       int  FD_ISSET(int fd, fd_set *set);//判断某一个文件描述是否在set中
       void FD_SET(int fd, fd_set *set);//将某一个文件描述加入到set
       void FD_ZERO(fd_set *set);//将set全置为0

下面是一段是关于select使用的详细实例:
实例一:监视标准输入

#include<unistd.h>
#include<stdio.h>
#include<fcntl.h>
#include<sys/select.h>

int main()
{
  //要监控的描述符
  int fd = STDIN_FILENO;
  struct timeval s;
  s.tv_sec = 2;
  //select使用之前的fd_set
  fd_set read_set;
  fd_set temp_set;
  FD_ZERO(&read_set);
  int nfds = fd;
  //将标准输入加入到read_set中
  FD_SET(fd,&read_set);
  temp_set = read_set;
  //持续监控stdin状态是否发生变化
  while(1)
  {
    //因为select每次都会对read_set进行修改,所以每次调用select时,进行重置
    read_set = temp_set;
    s.tv_sec = 2;
    int ret = select(nfds+1,&read_set,NULL,NULL,&s);
    if(ret<0)
    {
      printf("select!\n");
      return 1;
    }
    else if(ret == 0)
    {
      printf("time out!\n");
      //continue;
    }
    char buf[1024] = {0};
    if(FD_ISSET(fd,&read_set))
    {
      int rz = read(fd,buf,1023);
      if(rz<0)
      {
        printf("read!\n");
        return 3;
      }
      if(rz == 0)
      {
        printf("断开该文件描述符!\n");
        close(fd);
      }
      printf("read:%s",buf);
    }
  }
  return 0;
}

实例二:通过select实现tcp服务端
server.cc:

#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/select.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<fcntl.h>

#include<stdlib.h>
#include<string.h>

int main(int argc,char* argv[])
{
  if(argc<3)
  {
    printf("./server [ip] [port]\n");
    return 1;
  }
  //1.创建socket
  int listen_sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
  if(listen_sock<0)
  {
    printf("socket\n");
    return 2;
  }
  printf("%d\n",listen_sock);
  //2.绑定
  struct sockaddr_in listen_addr;
  listen_addr.sin_family = AF_INET;
  listen_addr.sin_addr.s_addr = inet_addr(argv[1]);
  listen_addr.sin_port = htons(atoi(argv[2]));
  socklen_t len = sizeof(listen_addr);
  if((bind(listen_sock,(const struct sockaddr*)&listen_addr,len))<0)
  {
    perror("bind!\n");
    return 3;
  }

  //3.改套接字状态为监听状态
  if(listen(listen_sock,5)<0)
  {
    printf("listen!\n");
    return 4;
  }
  //这里才是select的重点部分
  //4.多路复用部分
  fd_set read_set;
  fd_set temp_set;//因为select每次都会修改read_set,通过temp_set备份
  int max = 0;
  int a_size = 0;
  //添加listen套接字到read_set中
  FD_ZERO(&read_set);
  FD_ZERO(&temp_set);
  FD_SET(listen_sock,&read_set);
  max = listen_sock;
  a_size++;
  temp_set = read_set;
  //文件描述符表
  int a[1024] = {-1};
  a[0] = listen_sock;
  //5.循环处理业务
  while(1)
  {
    read_set = temp_set;
    sockaddr_in peer_addr;
    FD_SET(listen_sock,&read_set);
    struct timeval t;
    t.tv_sec = 2;
    int ret = select(max+1,&read_set,NULL,NULL,&t);
    if(ret<0)
    {
      printf("select\n");
    }
    if(ret == 0)
    {
      printf("time out!\n");
    }
    if(FD_ISSET(listen_sock,&read_set))
    {
      int connect_fd = accept(listen_sock,(struct sockaddr*)&peer_addr,&len);
      if(connect_fd<0)
      {
        printf("accept!\n");
        return 5;
      }
      //判断fd是否加入到文件描述符表
      a[a_size++] = connect_fd; 
      //将connect_fd加入到temp_set中
      FD_SET(connect_fd,&temp_set);
      //判断是否需要更新max
      if(connect_fd>max)
        max = connect_fd;
    }
    //处理connect_fd
    for(int i = 0;i<a_size;i++)
    {
      if(FD_ISSET(a[i],&read_set))
      {
        char buf[1024] = {0};
        int rz = read(a[i],buf,1023);
        if(rz<0)
        {
          printf("read!\n");
          continue;
        }
        if(rz == 0)
        {
          printf("peer close!!\n");
          close(a[i]);
        }
        printf("peer say:%s\n",buf);
        write(a[i],buf,strlen(buf));
      }
    }
  }
  return 0;
}

客户端就是我们之前写过的现在拿来直接有就可以:
client.cc:

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

int main(int argc,char* argv[])
{
  if(argc<3)
  {
    printf("./client [IP] [port]\n");
    return 1;
  }
  //创建套接字
  int cli_sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
  if(cli_sock<0)
  {
    printf("socket!\n");
    return 2;
  }
  //连接服务端
  struct sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = inet_addr(argv[1]);
  addr.sin_port = htons(atoi(argv[2]));
  socklen_t len = sizeof(addr);
  int ret = connect(cli_sock,(const struct sockaddr*)&addr,len);
  if(ret<0)
  {
    printf("connect!\n");
    return 0;
  }
  while(1)
  {
    char buf[1024]={0};
    printf("client[enter]:");
    fflush(stdout);
    scanf("%s",buf);
    write(cli_sock,buf,1024);
    read(cli_sock,buf,1023);
    printf("server[say]:%s\n",buf);
  }
  close(cli_sock);
}

实例运行效果:
客户端1:
在这里插入图片描述
客户端2:
在这里插入图片描述
服务端:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值