IO多路复用之select全面总结(必看篇)
1、基本概念
io多路复用是指内核一旦发现进程指定的一个或者多个io条件准备读取,它就通知该进程。io多路复用适用如下场合:
(1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用i/o复用。
(2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
(3)如果一个tcp服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到i/o复用。
(4)如果一个服务器即要处理tcp,又要处理udp,一般要使用i/o复用。
(5)如果一个服务器要处理多个服务或多个协议,一般要使用i/o复用。
与多进程和多线程技术相比,i/o多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
2、select函数
该函数准许进程指示内核等待多个事件中的任何一个发送,并只在有一个或多个事件发生或经历一段指定的时间后才唤醒。函数原型如下:
	#include <sys/select.h>
	#include <sys/time.h>
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
返回值:就绪描述符的数目,超时返回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); // 检查集合中指定的文件描述符是否可以读写
(3)timeout告知内核等待所指定描述字中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和微秒数。
| 
							 
								1 
							
								2 
							
								3 
							
								4 
							
								5 
							
								6 
							
								7 
						 | 
						
							struct timeval{
      long tv_sec;  //seconds
      long tv_usec; //microseconds
};
 | 
					
这个参数有三种可能:
(1)永远等待下去:仅在有一个描述字准备好i/o时才返回。为此,把该参数设置为空指针null。
(2)等待一段固定时间:在有一个描述字准备好i/o时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。
(3)根本不等待:检查描述字后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。
原理图:
	
3、测试程序
写一个tcp回射程序,程序的功能是:客户端向服务器发送信息,服务器接收并原样发送给客户端,客户端显示出接收到的信息。
服务端程序如下:
| 
							 
								1 
							
								2 
							
								3 
							
								4 
							
								5 
							
								6 
							
								7 
							
								8 
							
								9 
							
								10 
							
								11 
							
								12 
							
								13 
							
								14 
							
								15 
							
								16 
							
								17 
							
								18 
							
								19 
							
								20 
							
								21 
							
								22 
							
								23 
							
								24 
							
								25 
							
								26 
							
								27 
							
								28 
							
								29 
							
								30 
							
								31 
							
								32 
							
								33 
							
								34 
							
								35 
							
								36 
							
								37 
							
								38 
							
								39 
							
								40 
							
								41 
							
								42 
							
								43 
							
								44 
							
								45 
							
								46 
							
								47 
							
								48 
							
								49 
							
								50 
							
								51 
							
								52 
							
								53 
							
								54 
							
								55 
							
								56 
							
								57 
							
								58 
							
								59 
							
								60 
							
								61 
							
								62 
							
								63 
							
								64 
							
								65 
							
								66 
							
								67 
							
								68 
							
								69 
							
								70 
							
								71 
							
								72 
							
								73 
							
								74 
							
								75 
							
								76 
							
								77 
							
								78 
							
								79 
							
								80 
							
								81 
							
								82 
							
								83 
							
								84 
							
								85 
							
								86 
							
								87 
							
								88 
							
								89 
							
								90 
							
								91 
							
								92 
							
								93 
							
								94 
							
								95 
							
								96 
							
								97 
							
								98 
							
								99 
							
								100 
							
								101 
							
								102 
							
								103 
							
								104 
							
								105 
							
								106 
							
								107 
							
								108 
							
								109 
							
								110 
							
								111 
							
								112 
							
								113 
							
								114 
							
								115 
							
								116 
							
								117 
							
								118 
							
								119 
							
								120 
							
								121 
							
								122 
							
								123 
							
								124 
							
								125 
							
								126 
							
								127 
							
								128 
							
								129 
							
								130 
							
								131 
							
								132 
							
								133 
							
								134 
							
								135 
							
								136 
							
								137 
							
								138 
							
								139 
							
								140 
							
								141 
							
								142 
							
								143 
							
								144 
							
								145 
							
								146 
							
								147 
							
								148 
							
								149 
							
								150 
							
								151 
							
								152 
							
								153 
							
								154 
							
								155 
							
								156 
							
								157 
							
								158 
							
								159 
							
								160 
							
								161 
							
								162 
							
								163 
							
								164 
							
								165 
							
								166 
							
								167 
							
								168 
							
								169 
							
								170 
							
								171 
							
								172 
							
								173 
							
								174 
							
								175 
							
								176 
							
								177 
							
								178 
							
								179 
							
								180 
							
								181 
							
								182 
							
								183 
							
								184 
							
								185 
							
								186 
							
								187 
							
								188 
							
								189 
							
								190 
							
								191 
							
								192 
							
								193 
							
								194 
							
								195 
							
								196 
							
								197 
							
								198 
							
								199 
							
								200 
							
								201 
							
								202 
							
								203 
							
								204 
							
								205 
							
								206 
							
								207 
							
								208 
							
								209 
							
								210 
							
								211 
							
								212 
							
								213 
							
								214 
							
								215 
							
								216 
							
								217 
							
								218 
							
								219 
							
								220 
							
								221 
							
								222 
							
								223 
							
								224 
							
								225 
							
								226 
							
								227 
						 | 
						
							#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <assert.h>
#define ipaddr   "127.0.0.1"
#define port    8787
#define maxline   1024
#define listenq   5
#define size    10
typedef struct server_context_st
{
  int cli_cnt;    /*客户端个数*/
  int clifds[size];  /*客户端的个数*/
  fd_set allfds;   /*句柄集合*/
  int maxfd;     /*句柄最大值*/
} server_context_st;
static server_context_st *s_srv_ctx = null;
/*===========================================================================
 * ==========================================================================*/
static int create_server_proc(const char* ip,int port)
{
  int fd;
  struct sockaddr_in servaddr;
  fd = socket(af_inet, sock_stream,0);
  if (fd == -1) {
    fprintf(stderr, "create socket fail,erron:%d,reason:%s\n",
        errno, strerror(errno));
    return -1;
  }
  /*一个端口释放后会等待两分钟之后才能再被使用,so_reuseaddr是让端口释放后立即就可以被再次使用。*/
  int reuse = 1;
  if (setsockopt(fd, sol_socket, so_reuseaddr, &reuse, sizeof(reuse)) == -1) {
    return -1;
  }
  bzero(&servaddr,sizeof(servaddr));
  servaddr.sin_family = af_inet;
  inet_pton(af_inet,ip,&servaddr.sin_addr);
  servaddr.sin_port = htons(port);
  if (bind(fd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1) {
    perror("bind error: ");
    return -1;
  }
  listen(fd,listenq);
  return fd;
}
static int accept_client_proc(int srvfd)
{
  struct sockaddr_in cliaddr;
  socklen_t cliaddrlen;
  cliaddrlen = sizeof(cliaddr);
  int clifd = -1;
  printf("accpet clint proc is called.\n");
accept:
  clifd = accept(srvfd,(struct sockaddr*)&cliaddr,&cliaddrlen);
  if (clifd == -1) {
    if (errno == eintr) {
      goto accept;
    } else {
      fprintf(stderr, "accept fail,error:%s\n", strerror(errno));
      return -1;
    }
  }
  fprintf(stdout, "accept a new client: %s:%d\n",
      inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
  //将新的连接描述符添加到数组中
  int i = 0;
  for (i = 0; i < size; i++) {
    if (s_srv_ctx->clifds[i] < 0) {
      s_srv_ctx->clifds[i] = clifd;
      s_srv_ctx->cli_cnt++;
      break;
    }
  }
  if (i == size) {
    fprintf(stderr,"too many clients.\n");
    return -1;
  }
101 }
static int handle_client_msg(int fd, char *buf) 
{
  assert(buf);
  printf("recv buf is :%s\n", buf);
  write(fd, buf, strlen(buf) +1);
  return 0;
}
static void recv_client_msg(fd_set *readfds)
{
  int i = 0, n = 0;
  int clifd;
  char buf[maxline] = {0};
  for (i = 0;i <= s_srv_ctx->cli_cnt;i++) {
    clifd = s_srv_ctx->clifds[i];
    if (clifd < 0) {
      continue;
    }
    /*判断客户端套接字是否有数据*/
    if (fd_isset(clifd, readfds)) {
      //接收客户端发送的信息
      n = read(clifd, buf, maxline);
      if (n <= 0) {
        /*n==0表示读取完成,客户都关闭套接字*/
        fd_clr(clifd, &s_srv_ctx->allfds);
        close(clifd);
        s_srv_ctx->clifds[i] = -1;
        continue;
      }
      handle_client_msg(clifd, buf);
    }
  }
}
static void handle_client_proc(int srvfd)
{
  int clifd = -1;
  int retval = 0;
  fd_set *readfds = &s_srv_ctx->allfds;
  struct timeval tv;
  int i = 0;
  while (1) {
    /*每次调用select前都要重新设置文件描述符和时间,因为事件发生后,文件描述符和时间都被内核修改啦*/
    fd_zero(readfds);
    /*添加监听套接字*/
    fd_set(srvfd, readfds);
    s_srv_ctx->maxfd = srvfd;
    tv.tv_sec = 30;
    tv.tv_usec = 0;
    /*添加客户端套接字*/
    for (i = 0; i < s_srv_ctx->cli_cnt; i++) {
      clifd = s_srv_ctx->clifds[i];
      /*去除无效的客户端句柄*/
      if (clifd != -1) {
        fd_set(clifd, readfds);
      }
      s_srv_ctx->maxfd = (clifd > s_srv_ctx->maxfd ? clifd : s_srv_ctx->maxfd);
    }
    /*开始轮询接收处理服务端和客户端套接字*/
    retval = select(s_srv_ctx->maxfd + 1, readfds, null, null, &tv);
    if (retval == -1) {
      fprintf(stderr, "select error:%s.\n", strerror(errno));
      return;
    }
    if (retval == 0) {
      fprintf(stdout, "select is timeout.\n");
      continue;
    }
    if (fd_isset(srvfd, readfds)) {
      /*监听客户端请求*/
      accept_client_proc(srvfd);
    } else {
      /*接受处理客户端消息*/
      recv_client_msg(readfds);
    }
  }
}
static void server_uninit()
{
  if (s_srv_ctx) {
    free(s_srv_ctx);
    s_srv_ctx = null;
  }
}
static int server_init()
{
  s_srv_ctx = (server_context_st *)malloc(sizeof(server_context_st));
  if (s_srv_ctx == null) {
    return -1;
  }
  memset(s_srv_ctx, 0, sizeof(server_context_st));
  int i = 0;
  for (;i < size; i++) {
    s_srv_ctx->clifds[i] = -1;
  }
  return 0;
}
int main(int argc,char *argv[])
{
  int srvfd;
  /*初始化服务端context*/
  if (server_init() < 0) {
    return -1;
  }
  /*创建服务,开始监听客户端请求*/
  srvfd = create_server_proc(ipaddr, port);
  if (srvfd < 0) {
    fprintf(stderr, "socket create or bind fail.\n");
    goto err;
  }
  /*开始接收并处理客户端请求*/
  handle_client_proc(srvfd);
  server_uninit();
  return 0;
err:
  server_uninit();
  return -1;
}
 | 
					
客户端程序如下:
| 
							 
								1 
							
								2 
							
								3 
							
								4 
							
								5 
							
								6 
							
								7 
							
								8 
							
								9 
							
								10 
							
								11 
							
								12 
							
								13 
							
								14 
							
								15 
							
								16 
							
								17 
							
								18 
							
								19 
							
								20 
							
								21 
							
								22 
							
								23 
							
								24 
							
								25 
							
								26 
							
								27 
							
								28 
							
								29 
							
								30 
							
								31 
							
								32 
							
								33 
							
								34 
							
								35 
							
								36 
							
								37 
							
								38 
							
								39 
							
								40 
							
								41 
							
								42 
							
								43 
							
								44 
							
								45 
							
								46 
							
								47 
							
								48 
							
								49 
							
								50 
							
								51 
							
								52 
							
								53 
							
								54 
							
								55 
							
								56 
							
								57 
							
								58 
							
								59 
							
								60 
							
								61 
							
								62 
							
								63 
							
								64 
							
								65 
							
								66 
							
								67 
							
								68 
							
								69 
							
								70 
							
								71 
							
								72 
							
								73 
							
								74 
							
								75 
							
								76 
							
								77 
							
								78 
							
								79 
							
								80 
							
								81 
							
								82 
							
								83 
							
								84 
							
								85 
							
								86 
							
								87 
							
								88 
							
								89 
							
								90 
							
								91 
							
								92 
							
								93 
						 | 
						
							#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/select.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#define maxline 1024
#define ipaddress "127.0.0.1"
#define serv_port 8787
#define max(a,b) (a > b) ? a : b
static void handle_recv_msg(int sockfd, char *buf) 
{
printf("client recv msg is:%s\n", buf);
sleep(5);
write(sockfd, buf, strlen(buf) +1);
}
static void handle_connection(int sockfd)
{
char sendline[maxline],recvline[maxline];
int maxfdp,stdineof;
fd_set readfds;
int n;
struct timeval tv;
int retval = 0;
while (1) {
fd_zero(&readfds);
fd_set(sockfd,&readfds);
maxfdp = sockfd;
tv.tv_sec = 5;
tv.tv_usec = 0;
retval = select(maxfdp+1,&readfds,null,null,&tv);
if (retval == -1) {
return ;
}
if (retval == 0) {
printf("client timeout.\n");
continue;
}
if (fd_isset(sockfd, &readfds)) {
n = read(sockfd,recvline,maxline);
if (n <= 0) {
fprintf(stderr,"client: server is closed.\n");
close(sockfd);
fd_clr(sockfd,&readfds);
return;
}
handle_recv_msg(sockfd, recvline);
}
}
}
int main(int argc,char *argv[])
{
int sockfd;
struct sockaddr_in servaddr;
sockfd = socket(af_inet,sock_stream,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = af_inet;
servaddr.sin_port = htons(serv_port);
inet_pton(af_inet,ipaddress,&servaddr.sin_addr);
int retval = 0;
retval = connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
if (retval < 0) {
fprintf(stderr, "connect fail,error:%s\n", strerror(errno));
return -1;
}
printf("client send to server .\n");
write(sockfd, "hello server", 32);
handle_connection(sockfd);
return 0;
}
 | 
					
4、程序结果
启动服务程序,执行三个个客户程序进行测试,结果如下图所示:
	
以上就是小编为大家带来的io多路复用之select全面总结(必看篇)全部内容了,希望大家多多支持~
本文由主机测评网发布,不代表主机测评网立场,转载联系作者并注明出处:https://zhujiwo.jb51.net/linux/5866.html
