Linux网络基础-高级IO之select

5种IO模型:

1.阻塞IO:
在内核将数据准备好之前,系统调用会一直等待,所有的套接字默认都是阻塞方式。
这里写图片描述
2.非阻塞IO:
如果内核还未将数据准备好,系统调用仍然会直接返回,并返回EWOULDBLOCK错误码。
这里写图片描述
3.信号驱动IO:
内核将数据准备好的时候,使用SIGIO信号通知应用程序进行IO操作。
这里写图片描述
4.IO多路转接:
与阻塞IO类似,不同之处在于IO多路转接能同时等待多个文件描述符的就绪状态。
这里写图片描述
5.异步IO:
由内核在数据拷贝完成时,通知应用程序,与信号驱动IO不同之处在于,信号驱动IO是告诉应用程序何时可以开始拷贝数据。
这里写图片描述

同步与异步VS同步与互斥:
同步与异步:

同步:
发出调用时,在没有得到结果前,该调用不会返回,一旦调用返回,便得到了结果。即由调用者主动等待这个调用的结果。
异步:
发出调用后,调用者不会立即得到结果,而是被调用者在结果准备好的时候,通过状态、通知等通知调用者或通过回调函数处理这个调用。

同步与互斥:

为了完成某个任务,创建的多个进程或线程,需要协调它们的工作次序而等待、传输信息等所产生的制约关系。

select服务器:
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);

说明:系统提供了select()函数实现IO多路转接模型,select()函数可以同时监视多个文件描述符的状态变化,直到被监视的文件描述符中由一个或多个发生了状态改变。
参数:

  • nfds:需要监视的最大文件描述符值+1。
  • readfds:输入输出型参数,输入时,表示要监视的可读文件描述符集合,输出时,表示监视的可读文件描述符集合中读就绪的文件描述符集合,不关心读事件可置为NULL。
  • writefds:输入输出型参数,输入时,表示要监视的可写文件描述符集合,输出时,表示监视的可写文件描述符集合中写就绪的文件描述符集合,不关心写事件可置为NULL。
  • exceptfds:输入输出型参数,输入时,表示要监视的异常文件描述符集合,输出时,表示监视的异常文件描述符集合中异常发生的文件描述符集合,不关心异常事件可置为NULL。
  • timeout:用于设置select()等待时间。NULL,表示select将一直阻塞等待,直到监听的某个文件符状态改变;0,表示立即返回;特定时间,表示在特定的时间内等待监听文件描述符的状态改变,若特定时间内没有文件符状态改变,select()超时,返回0,若特定时间内有文件描述符状态改变,则select()立即返回改变文件描述符个数,timeout为剩余时间。

返回值:

  • 大于0:文件描述符状态改变的个数。
  • 等于0:在timeout时间内文件描述符没有状态改变的。
  • 等于-1:调用发生错误,错误原因存于errno,此时readfds、writefds、exceptfds、timeout值变为不可预测。

备注:
fd_set:fd_set类型是一个整型数组,类似一个位图,通过其中的对应的位来表示要监视的文件描述符。系统为我们提供了一组操作fd_set的接口,如下所示:

//清除文件描述符集set中相应fd的位
void FD_CLR(int fd, fd_set *set);
//判断文件描述符集set中相应fd的位是否为真
int  FD_ISSET(int fd, fd_set *set);
//设置文件描述符集set中相应fd的位
void FD_SET(int fd, fd_set *set);
//清除文件描述符集set的全部位
void FD_ZERO(fd_set *set);

timeval:

struct timeval  
{  
__time_t tv_sec;        /* Seconds. */  
__suseconds_t tv_usec;  /* Microseconds. */  
}; 

timeval结构体表示一段时间,tv_sec表示多少秒,tv_usec表示一秒内的多少微秒。

socket就绪条件:

读就绪:

  • 内核中接收缓冲区的字节数,大于等于低水位标记SO_RCVLOWAT。此时可以非阻塞读该文件描述符,
  • socket TCP通信中对端关闭连接。此时对该socket对,则返回0.
  • 监听socket上有新的连接请求。
  • socket上有未处理的错误。

写就绪:

  • 内核中发送缓冲区的空余字节数,大于等于低水位标记SO_SNDLOWAT。此时可以无阻塞的写,并且返回值大于0。
  • socket的写操作被关闭。此时对该socket进行写,会触发SIGPIPE信号。
  • socket使用非阻塞connect连接成功或失败之后。
  • socket上有未读取的错误。

异常就绪:

  • select上收到带外数据。
特点:
  • 可监视的文件描述符个数取决于sizeof(fd_set)*8的大小,此参数可调整,但涉及编译内核。
  • 将要监视的文件描述符加入监视文件描述符集的同时,需要一个额外的数组来保存加入监视文件描述符集的文件描述符。原因有两个:一个是因为,select返回后,会将监视文件描述符中未发生事件的文件描述符清除,再次使用select时,必须重新利用数组重建监视文件描述符集,在重建时获得最大文件描述符,用于select第一个参数。另外一个原因是,在select返回后,需要利用数组和返回的发生事件文件描述符集进行FD_ISSET判断文件描述符的事件是否发生。
优点:
  • select可以同时等待多个文件描述符,将多个文件描述符的等待数据的时间复用,节省了程序总体等待数据的时间。
缺点:
  • select可监视的文件描述符个数太少。
  • 每次调用select前都要手动重新设置监视文件描述符集。
  • 每次调用select时都要将监视文件描述符集从用户态拷贝到内核态,在监视文件描述符集很大的时候开销会很大。
  • 每次调用select时内核都要遍历监视文件描述符集,在监视文件描述符集很大的时候开销会很大。
select服务器:

利用select编写多连接ECHO服务器。
server.c:

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


void Init(int *fd_list,int list_size){
  int i = 0;
  for(;i < list_size;i++){
    fd_list[i] = -1; 
  }
}

void Reload(int listen_fd,int *connect_fds,int fds_size,fd_set *read_fds,int *maxfd){
  FD_ZERO(read_fds);
  FD_SET(listen_fd,read_fds);
  int max = listen_fd;
  int i = 0;
  for(;i < fds_size;i++){
    if(connect_fds[i] != -1){
      FD_SET(connect_fds[i],read_fds);
      if(connect_fds[i] > max){
        max = connect_fds[i];
      }
    }
  }
  *maxfd = max;
}

void Add(int fd,int *connect_fds,int fds_size){
  int i = 0;
  for(;i < fds_size;i++){
    if(connect_fds[i] == -1){
      connect_fds[i] = fd;
      break;
    }
  }
}


int main(int argc,char *argv[]){
  if(argc != 3){
    printf("Usage: ./server ip port\n");
    return -1;
  }
  int sock = socket(AF_INET,SOCK_STREAM,0);
  if(sock < 0){
    perror("socket");
    return -2;
  }
  int opt = 1;
  setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
  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]));
  int ret = bind(sock,(struct sockaddr*)&addr,sizeof(struct sockaddr_in));
  if(ret < 0){
    perror("bind");
    return -3;
  }
  ret = listen(sock,5);
  if(ret < 0){
    perror("listen");
    return -4;
  }
  fd_set read_fds;
  int connect_fds[1024];
  Init(connect_fds,sizeof(connect_fds)/sizeof(int));
  while(1){
    int maxfd;
    Reload(sock,connect_fds,sizeof(connect_fds)/sizeof(int),&read_fds,&maxfd);
    printf("before select:%d\n",FD_ISSET(sock,&read_fds));
    int ret = select(maxfd+1,&read_fds,NULL,NULL,NULL);
    printf("after select:%d\n",FD_ISSET(sock,&read_fds));
    if(ret < 0){
      perror("select");
      continue;
    }else if(ret == 0){
      printf("error! select wait fail\n");
      continue;
    }else{
      if(FD_ISSET(sock,&read_fds)){
        struct sockaddr_in connect_addr;
        socklen_t len = sizeof(connect_addr);
        int connect_sock = accept(sock,(struct sockaddr*)&connect_addr,&len);
        if(connect_sock < 0){
          perror("accept");
          continue;
        }
        printf("client %s:%d\n",inet_ntoa(connect_addr.sin_addr),ntohs(connect_addr.sin_port));
        Add(connect_sock,connect_fds,sizeof(connect_fds)/sizeof(int));
      }
      int i = 0;
      for(;i < sizeof(connect_fds)/sizeof(int);i++){
        if(connect_fds[i] == -1){
          continue;
        }else if(!FD_ISSET(connect_fds[i],&read_fds)){
          continue;
        }else{
          char buf[1024] = {0};
          ssize_t readsize = read(connect_fds[i],buf,sizeof(buf)-1);
          if(readsize < 0){
            perror("read");
            continue;
          }
          if(readsize == 0){
            printf("client say goodbay\n");
            close(connect_fds[i]);
            connect_fds[i] = -1;
            continue;
          }
          buf[readsize] = 0;
          printf("client $ %s",buf);
          write(connect_fds[i],buf,strlen(buf));
        }
      }
    }
  }
  return 0;
}

client.c:

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


int main(int argc,char *argv[]){
  if(argc != 3){
    perror("Usage :./server ip port\n");
    return -1;
  }
  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]));
  int sock = socket(AF_INET,SOCK_STREAM,0);
  if(sock < 0){
    perror("socket");
    return -1;
  }
  int ret = connect(sock,(struct sockaddr*)&addr,sizeof(addr));
  if(ret < 0){
    perror("connect");
    return -2;
  }
  while(1){
    char buf[1024] = {0};
    printf("> ");
    fflush(stdout);
    read(0,buf,sizeof(buf)-1);
    ssize_t writesize = write(sock,buf,strlen(buf));
    if(writesize < 0){
      perror("write");
      continue;
    }
    ssize_t readsize = read(sock,buf,sizeof(buf)-1);
    if(readsize < 0){
      perror("read");
      continue;
    }
    if(readsize == 0){
      printf("server close\n");
      break;
    }
    buf[readsize] = 0;
    printf("server say:%s",buf);
  }
  close(sock);
  return 0;
}

结果演示:

这里写图片描述

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值