Linux select I/O 复用
用途
在处理多个socket套接字的时候,会很自然的遇到一个问题:某个套接字什么时候可读?什么时候可写?哪些套接字是需要关闭的?我们可以回忆一下,一般我们在最开始编写socket程序的时候,send,recv都是同步的,send完后就傻等着recv。这种模式的一个很大的问题是,recv会占用一整个线程,单个线程里没法处理第二个socket。怎么办呢?加线程,每个socket分配一个线程?显然不合适,1000个客户端难道要1000个线程么。select提供了一种方式同时监控多个套接字,执行过程大致为:首先将感兴趣的套接字加入到select的集合中,select函数执行,监控这些个套接字,当其中有套接字产生了事件(可读,可写,异常)或者select超时后,select返回告知调用者,哪些个套接字发送了事件,调用者就对发生了事件的套接字挨个处理,然后继续执行select函数,下个循环开始。
通过这样的一种方式,在同一个线程里面就实现了对多个套接字的读写操作。当然需要注意的是,select调用针对的是文件描述符,不管是socket,pipe,file都是可以被select监控的。
函数说明
#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);
void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
当前linux下selet的每个fd_set最多可以监控1024个文件描述符
参数
- nfds 要监听的所有的文件描述符中最大值 + 1
 - readfds 要监听其读事件的文件描述符集合
 - writefds 要监听其写事件的文件描述符集合
 - exceptfds 要监听其异常事件的文件描述符集合(比如socket的带外数据就会引起exceptfds事件)
 - timeout 超时时间结构,指定为NULL表示一直阻塞,否则就阻塞指定的时间。
 
返回值
- -1 出错
 - = 超时
 - >0 某些套接字产生事件
 
使用
假设对于一个文件描述符fd,我们想监控其读事件,就将加入到读监控集合中。
fd_set read_set;
FD_ZERO(&read_set);
FD_SET(fd, &read_set);
select(maxFd +1, &read_set, NULL, NULL, NULL)
同理,对于写事件,异常事件也是一样的处理方式。
当select调用返回后,如果select返回>0 则可以对指定的文件描述符进行操作。
if (FD_ISSET(fd, &read_set)) {
    recv(fd)
}
在程序设计中,一般将select结合while使用。
参考
- Linux Programmer's mannual
 - 《Linux 高性能服务器编程》
 - http://www.cnblogs.com/c-slmax/p/5553857.html
 
实例
使用select构建的一个tcp echo程序,程序可以接受多个客户端的链接,并将客户端发送过来的字符串发送回去。
测试
测试环境:CentOS7.1
server端截图:

cleint1截图:

cleint2截图:

代码
/* author: bymzy
 * 2017/2/12
 * */
#include <sys/select.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <strings.h>
#include <stdlib.h>
#define PORT 3333
#define IP "127.0.0.1"
#define BACKLOG 5
#define BUFSIZE 1025
void SetFDSet(int *clientFd, int listenFd, fd_set *read, fd_set *except, int *maxFd)
{
    int i = listenFd + 1;
    FD_ZERO(read);
    FD_ZERO(except);
    for (;i <= *maxFd; ++i) {
        if (clientFd[i] != 0) {
            FD_SET(clientFd[i], read);
            FD_SET(clientFd[i], except);
        }
    }
}
void CheckFDSet(int *clientFd, int listenFd, fd_set *read, fd_set *except, int *maxFd)
{
    int i = listenFd + 1;
    int tempMaxFd = *maxFd;
    char buf[1024];
    int recved = 0;
    bool needClose = false;
    bzero(buf, 1024);
    for (;i <= tempMaxFd; ++i) {
        bzero(buf, recved);
        if (FD_ISSET(clientFd[i], read)) {
            recved = recv(clientFd[i], buf, BUFSIZE -1 , 0);
            if (recved <= 0) {
                perror("Recv error");
                close(clientFd[i]);
                if (i == tempMaxFd) {
                    *maxFd = -1;
                }
                clientFd[i] = 0;
                continue;
            }
            printf("Recv: %s \n", buf);
            send(clientFd[i], buf, recved, 0);
        }
        bzero(buf, recved);
        if (FD_ISSET(clientFd[i], except)) {
            recved = recv(clientFd[i], buf, BUFSIZE - 1, MSG_OOB);
            if (recved <= 0) {
                perror("Recv error");
                close(clientFd[i]);
                if (i == tempMaxFd) {
                    *maxFd = -1;
                }
                clientFd[i] = 0;
                continue;
            }
            printf("OOB: %s \n", buf);
            send(clientFd[i], buf, recved, MSG_OOB);
        }
    }
}
void ChooseMaxFd(int *clientFd, int listenFd, int total, int *maxFd)
{
    int i = listenFd;
    for (;i < total; ++i) {
        if(clientFd[i] != 0) {
            *maxFd = clientFd[i];
        }
    }
}
void CloseFd(int *clientFd, int listenFd, int total)
{
    int i = listenFd + 1;
    for (;i < total; ++i) {
        if(clientFd[i] != 0) {
            close(clientFd[i]);
            clientFd[i] = 0;
        }
    }
}
int main(int argc, char* argv[])
{
    int err = 0;
    int listenFd = -1;
    do {
        listenFd = socket(AF_INET, SOCK_STREAM, 0);
        if (listenFd < 0) {
            err = errno;
            perror("Create listen socket failed");
            break;
        }
        struct sockaddr_in bindaddr;
        bzero(&bindaddr, 0);
        bindaddr.sin_addr.s_addr = inet_addr(IP);
        bindaddr.sin_port = htons(PORT);
        bindaddr.sin_family = AF_INET;
        socklen_t socklen = sizeof(bindaddr);
        int reuse = 1;
        setsockopt(listenFd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
        err = bind(listenFd, (sockaddr*)&bindaddr, socklen);
        if (err != 0) {
            err = errno;
            perror("Listen socket bind failed!");
            break;
        }
        err = listen(listenFd, BACKLOG);
        if (err != 0) {
            err = errno;
            perror("Listen socket listen failed!");
            break;
        }
        /* use select read data */
        fd_set read_set;
        fd_set exception_set;
        struct timeval tv;
        tv.tv_sec = 5;
        tv.tv_usec = 0;
        int *clientFd = (int*)malloc(sizeof(int) * (FD_SETSIZE + listenFd + 1));
        clientFd[listenFd] = listenFd;
        int maxFd = -1;
        while (1) {
            if (maxFd == -1) {
                ChooseMaxFd(clientFd, listenFd, FD_SETSIZE + listenFd + 1, &maxFd);
            }
            SetFDSet(clientFd, listenFd, &read_set, &exception_set, &maxFd);
            if (maxFd != (FD_SETSIZE + listenFd)) {
                FD_SET(listenFd, &read_set);
            }
            tv.tv_sec = 5;
            tv.tv_usec = 0;
            err = select(maxFd + 1, &read_set, NULL, &exception_set, &tv);
            if (err < 0) {
                perror("Select failed");
                err = errno;
                break;
            } else if (err == 0) {
                printf("Select timeout!\n");
            } else {
                if (FD_ISSET(listenFd, &read_set)) {
                    struct sockaddr_in clientaddr;
                    socklen_t clientlen;
                    int tempFd= -1;
                    tempFd = accept(listenFd, (sockaddr*)&clientaddr, &clientlen);
                    if (tempFd < 0) {
                        err = errno;
                        perror("accept failed!");
                        break;
                    }
                    clientFd[tempFd] = tempFd;
                    if (tempFd > maxFd) {
                        maxFd = tempFd;
                    }
                    printf("Accept client fd: %d\n", tempFd);
                }
                CheckFDSet(clientFd, listenFd, &read_set, &exception_set, &maxFd);
            }
        }
        CloseFd(clientFd, listenFd, FD_SETSIZE + listenFd + 1);
        free(clientFd);
    } while(0);
    if (listenFd != -1) {
        close(listenFd);
        listenFd = -1;
    }
    return err;
}
												
											Linux select I/O 复用的更多相关文章
- Linux网络编程-IO复用技术
		
IO复用是Linux中的IO模型之一,IO复用就是进程预先告诉内核需要监视的IO条件,使得内核一旦发现进程指定的一个或多个IO条件就绪,就通过进程进程处理,从而不会在单个IO上阻塞了.Linux中,提 ...
 - linux select 与 阻塞( blocking ) 及非阻塞 (non blocking)实现io多路复用的示例
		
除了自己实现之外,还有个c语言写的基于事件的开源网络库:libevent http://www.cnblogs.com/Anker/p/3265058.html 最简单的select示例: #incl ...
 - Linux中的IO复用接口简介(文件监视?)
		
I/O复用是Linux中的I/O模型之一.所谓I/O复用,指的是进程预先告诉内核,使得内核一旦发现进程指定的一个或多个I/O条件就绪,就通知进程进行处理,从而不会在单个I/O上导致阻塞. 在Linux ...
 - linux select 与 阻塞( blocking ) 及非阻塞 (non blocking)实现io多路复用的示例【转】
		
转自:https://www.cnblogs.com/welhzh/p/4950341.html 除了自己实现之外,还有个c语言写的基于事件的开源网络库:libevent http://www.cnb ...
 - linux select函数详解
		
linux select函数详解 在Linux中,我们可以使用select函数实现I/O端口的复用,传递给 select函数的参数会告诉内核: •我们所关心的文件描述符 •对每个描述符,我们所关心的状 ...
 - Linux select 机制深入分析
		
Linux select 机制深入分析 作为IO复用的实现方式.select是提高了抽象和batch处理的级别,不是传统方式那样堵塞在真正IO读写的系统调用上.而是堵塞在sele ...
 - linux—select具体解释
		
linux—select具体解释 select系统调用时用来让我们的程序监视多个文件句柄的状态变化的.程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变. 关于文件句柄,事 ...
 - Linux Select之坑
		
最近在写一个demo程序,调用select()来监听socket状态,流程如下: r_set 初始化 timeout 初始化3秒超时 loop{ select(ntfs, &r_set, nu ...
 - 实现Linux select IO复用C/S服务器代码
		
已在ubuntu 下验证可用 服务器端 #include<stdio.h>#include<unistd.h>#include<stdlib.h>#include& ...
 
随机推荐
- DIV 和 SPAN  区别
			
DIV 和 SPAN 元素最大的特点是默认都没有对元素内的对象进行任何格式化渲染.主要用于应用样式表(共同点). 两者最明显的区别在于DIV是块元素,而SPAN是行内元素(也译作内嵌元素). 详解:1 ...
 - Delphi中unicode转汉字函数(转)
			
源:Delphi中unicode转汉字函数 近期用到这个函数,无奈没有找到 delphi 自带的,网上找了下 有类似的,没有现成的,我需要的是 支持 “\u4f00 ” 这种格式的,即前面带标准的 “ ...
 - new sun.misc.BASE64Encoder()报错找不到jar包
			
解决方案1(推荐): 只需要在project build path中先移除JRE System Library,再添加库JRE System Library,重新编译后就一切正常了. 解决方案2: W ...
 - coreGraphs和动画
			
http://www.jianshu.com/p/b71c3d450e8e http://blog.csdn.net/volcan1987/article/details/9969455 http:/ ...
 - FZU 1064 教授的测试
			
递归构造答案. 根据当前整颗树的编号,可以计算左右子树有几个节点以及编号.因此,不断dfs下去就可以了. #include<cstdio> #include<cstring> ...
 - c#中怎么求百分比
			
string Scorepercent = (lowScoreNum*1.0/ ScoreNum).ToString("P");//百分比 ToString("P&quo ...
 - HDU 3264 Open-air shopping malls  ——(二分+圆交)
			
纯粹是为了改进牛吃草里的两圆交模板= =. 代码如下: #include <stdio.h> #include <algorithm> #include <string. ...
 - PHP上传文件大小的修改
			
采用了plupload来上传文件,但是一直失败. 设置了插件的参数和接受的参数,仍旧失败. 此时想到php.ini中需要修改 post_max_sizeupload_file_size 然后重启服务器
 - JDBC executeBatch 抛出异常停止
			
进行批量更新的时候发现: addBatch(sql); executeBatch 抛出异常后,剩余的sql没有更新,即出现异常之前的都入库了,异常之后即使有可执行sql都不会执行. 百度资料后了解:这 ...
 - UVa 10034 - Freckles
			
题目大意:给出n个点的坐标(x,y),要求用线段将n个点连接起来,求最小的线段和. 最小生成树问题,用Kruskal算法进行求解,其中用到了并查集.将所有的点连接,构成一张图,对每一条边进行编号,两点 ...