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的模式;在高并发情况下,将造成资源消耗过大。

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

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

模型分析

此时就会产生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

客户端2

推荐阅读:

线程池网络服务

多线程网络服务

Socket网络编程

线程高级操作

Linux多线程编程

线程

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

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

推荐阅读更多精彩内容

  • 1、基本概念 IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用...
    Daniel521阅读 193评论 0 0
  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom阅读 869评论 0 1
  • 大纲 一.Socket简介 二.BSD Socket编程准备 1.地址 2.端口 3.网络字节序 4.半相关与全相...
    VD2012阅读 488评论 0 5
  • 串口操作 串口操作需要的头文件 #include /*标准输入输出定义*/ #include /*标准函数库定...
    旅行家John阅读 588评论 0 3
  • RxJava是android中比较流行的框架,在刚开始使用这个框架时发现一个奇怪的问题,现在解决了,写下来; 我们...
    heybik阅读 2,073评论 10 4