Linux I/O复用——select()

原创: 编程TWO 编程小兔崽 今天

今天跟大家介绍一个函数,这个函数在Linux编程里边特别重要,很多地方都用到。

select()函数

函数原型

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

参数分析:nfds+1,在其后的读、写、异常、超时找模式运行

函数说明

select()用来等待文件描述词状态的改变。参数n代表最大的文件描述词加1,参数readfds、writefds 和exceptfds 称为描述词组,是用来回传该描述词的读,写或例外的状况。底下的宏提供了处理这三种描述词组的方式:

 

FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位

FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真

FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位

FD_ZERO(fd_set *set); 用来清除描述词组set的全部位

 

结构体说明

先说明两个结构体: 

 struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。 

 

fd_set集合可以通过一些宏由人为来操作,比如 

清空集合FD_ZERO(fd_set *); 

 

将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set*); 

 

将一个给定的文件描述符从集合中删除FD_CLR(int,fd_set*); 

检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )。

 

struct timeval是一个常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。如下所示:

 

struct timeval

{

    time_t tv_sec;

    time_t tv_usec;

};

错误代码

执行成功则返回文件描述词状态已改变的个数,如果返回0代表在描述词状态改变前已超过timeout时间,当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。

 

EBADF 文件描述词为无效的或该文件已关闭

EINTR 此调用被信号所中断

EINVAL 参数n 为负值。

ENOMEM 核心内存不足

 

具体参数说明: 

  ( 1) int n:是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。 

  

 ( 2) fd_set*readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。 

 

(3) fd_set*writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。 

 

( 4) fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。 

   

( 5)struct timeval *timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

 

  (1)、recv、send、select......都是阻塞函数

  但是在这里用阻塞函数-------->解决非阻塞问题;

  (2)、当可读事件发生时,区别两种情况:

  a、请求与服务器的连接;   b、已经连接好了,直接进行通信;

  (3)、每次都要重置,只留下一个客户端即可。

  (4)、select()是轮询模式,走访所有的套接字;时间设置为0,不阻塞,直接返回。

4、I/O复用的本质

  对其返回值(select().....)需要特别注意,< == >按不同的情况进行处理;

  I/O复用只关心:服务器在跟哪个客户打交道;


 

1、Linux I/O多路复用

  之前:我们的处理是,每到来一个客户端,都为其开辟一个新的进/线程,对其进行一对一的服务,这是VIP的模式;在高并发情况下,将造成资源消耗过大。

  现在,对应高并发:一个线程为多个客户服务;

  同一个时刻,只能为一个客户服务(作用排队);

模型分析

Linux I/O复用——select()_第1张图片

此时就会产生select()、poll()、epoll()模式

5、代码实现

(1)、utili.h

 

#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT  8787
#define LISTEN_QUEUE 5
#define SIZE 10
#define BUFFER_SIZE 256

  (2)、ser.c

 

#include"../utili.h"
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 void server_uninit(){
   if(s_srv_ctx)
   {
       free(s_srv_ctx);
       s_srv_ctx = NULL;
   }  
   }
   
   static void server_init()
   {
       int i;
       s_srv_ctx = (server_context_st*)malloc(sizeof(server_context_st));
       assert(s_srv_ctx != NULL);
       memset(s_srv_ctx, 0, sizeof(server_context_st));
       for(i=0; i        {
           s_srv_ctx->clifds[i] = -1;
   }  
   }s
   tatic int create_server_proc(const char *ip, int port)
   {
       printf("ip>%s\n",ip);
       printf("port:>%d\n",port);
       int fd = socket(AF_INET, SOCK_STREAM, 0);
       struct sockaddr_in addrSer;
       addrSer.sin_family = AF_INET;
       addrSer.sin_port = htons(port);
       addrSer.sin_addr.s_addr = inet_addr(ip);
       socklen_t len = sizeof(struct sockaddr);

       int yes = 1;
       setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));

       bind(fd, (struct sockaddr*)&addrSer, len);
       listen(fd, LISTEN_QUEUE);
       return fd;}static int accept_client_proc(int srvfd){
       struct sockaddr_in cliaddr;
       socklen_t len = sizeof(struct sockaddr);
       int clifd = accept(srvfd, (struct sockaddr*)&cliaddr, &len);

       printf("Server Accept Client Connect OK.\n");
       int i;
       for(i=0; i        {
           if(s_srv_ctx->clifds[i] == -1)
           {
               s_srv_ctx->clifds[i] = clifd;
               s_srv_ctx->cli_cnt++;
               break;
       }
   }

   if(i == SIZE)
   {
       printf("too many client.\n");
   }
   }
   static void handle_client_msg(int fd, char *buf)
   {
       printf("recv buffer :>%s\n",buf);
       send(fd, buf, strlen(buf)+1, 0);}static void recv_client_msg(fd_set *readfds){
       int clifd;
       char buffer[BUFFER_SIZE];
       int i;

       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))
       {
           recv(clifd, buffer, BUFFER_SIZE, 0);
           handle_client_msg(clifd, buffer);
       }
   }
   }
       static void handle_client_proc(int srvfd)
       {
       int clifd = -1;
       fd_set *readfds = &s_srv_ctx->allfds;

       int retval;

       int i;
       struct timeval tv;
       while(1)
       {
           FD_ZERO(readfds);
           FD_SET(srvfd, readfds);
           s_srv_ctx->maxfd = srvfd;
           for(i=0; icli_cnt; ++i)
           {
               clifd = s_srv_ctx->clifds[i];
           FD_SET(clifd, readfds);
           s_srv_ctx->maxfd = (clifd > s_srv_ctx->maxfd ? clifd : s_srv_ctx->maxfd);
       }    
       //retval  =select(maxfd+1, NULL, NULL, readfds)

       tv.tv_sec = 0;
       tv.tv_usec = 0;
       retval = select(s_srv_ctx->maxfd+1, readfds, NULL, NULL, &tv);
       if(retval == -1){   //错误返回
           perror("select");
           return ;
       }
       if(retval == 0){   //处理超时
           printf("Server Wait Time Out.\n");
           continue;
       }

       if(FD_ISSET(srvfd, readfds)){
           accept_client_proc(srvfd); //处理客户端的连接
       }else{
           recv_client_msg(readfds); //服务器接收客户端的消息
       }

   }
   }
   
   int main(int argc, char *argv[])
   {
       server_init();
       int srvfd = create_server_proc(SERVER_IP, SERVER_PORT);
       handle_client_proc(srvfd);
       return 0;
       }

(3)、cli.c

 

#include"../utili.h"
static void handle_connection(int sockfd)
{
   fd_set readfds;
   int retval = 0;
   char buffer[BUFFER_SIZE];
   int maxfd;
   while(1)
   {
       FD_ZERO(&readfds);
       FD_SET(sockfd, &readfds);
       maxfd = sockfd;

       retval = select(maxfd+1, &readfds, NULL, NULL, NULL);
       if(retval == -1)
       {
           perror("select");
           return;
       }  

       if(FD_ISSET(sockfd, &readfds))
       {
           recv(sockfd, buffer, BUFFER_SIZE, 0);
           printf("client recv self msg:> %s\n",buffer);
           //sleep(1);
           printf("Msg:>");
           scanf("%s",buffer);
           send(sockfd, buffer, strlen(buffer)+1, 0);
       }  
   }  
   }
   int main(int argc, char *argv[])
   {
       int sockfd = socket(AF_INET, SOCK_STREAM, 0);
       struct sockaddr_in addrSer;
       addrSer.sin_family = AF_INET;
       addrSer.sin_port = htons(SERVER_PORT);
       addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);
       int retval = connect(sockfd, (struct sockaddr*)&addrSer, sizeof(struct sockaddr));
       if(retval == -1)
       {
           perror("connect");
           return -1;
   }
   else
   {
       printf("Client Connect Server OK.\n");
   }

       send(sockfd, "hello server.", strlen("hello server")+1, 0);
       handle_connection(sockfd);
       return 0;
   }

运行结果

服务器端:一直在等待客户端的连接,比较快,图不好截取;

客户端1

Linux I/O复用——select()_第2张图片

客户端2

Linux I/O复用——select()_第3张图片

推荐阅读:

线程池网络服务

多线程网络服务

Socket网络编程

线程高级操作

Linux多线程编程

线程

欢本文的朋友们,欢迎长按下图关注订阅号编程小兔崽,收看更多精彩内容

Linux I/O复用——select()_第4张图片

每天进步一点点,如果有用给小编点个赞

你可能感兴趣的:(网络编程)