Linux下的I/O多路复用select,poll,epoll浅析

一,什么是I/O多路复用
所谓的I/O多路复用在英文中其实叫 I/O multiplexing. 就是单个线程,通过记录跟踪每个I/O流(sock)的状态,来同时管理多个I/O流 。)
I/O multiplexing 这里面的 multiplexing 指的其实是在单个线程通过记录跟踪每一个Sock(I/O流)的状态(对应空管塔里面的Fight progress strip槽)来同时管理多个I/O流. 发明它的原因,是尽量多的提高服务器的吞吐量
如图
这里写图片描述
二,多路复用的方式很多今天说一下select原理:
select函数会等待,直到描述符句柄中有可用资源(可读、可写、异常)时返回,返回值是可用资源(可读/可写/异常等)描述符的个数(>0),0代表超时,-1代表错误。具体到内核大致是:当应用程序调用select() 函数, 内核就会相应调用 poll_wait(), 把当前进程添加到相应设备的等待队列上,然后将该应用程序进程设置为睡眠状态。直到该设备上的数据可以获取,然后调用wake_up()唤醒该应用程序进程。select每次轮训都会遍历所有描述符句柄。
使用select模型的路径图如下
这里写图片描述
三,函数
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
nfds: 监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态
readfds:监控有读数据到达文件描述符集合,传入传出参数
writefds:监控写数据到达文件描述符集合,传入传出参数
exceptfds:监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
timeout:定时阻塞监控时间,3种情况
1.NULL,永远等下去
2.设置timeval,等待固定时间
3.设置timeval里时间均为0,检查描述字后立即返回,轮询
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
void FD_CLR(int fd, fd_set *set); 把文件描述符集合里fd清0
int FD_ISSET(int fd, fd_set *set); 测试文件描述符集合里fd是否置1
void FD_SET(int fd, fd_set *set); 把文件描述符集合里fd位置1
void FD_ZERO(fd_set *set); 把文件描述符集合里所有位清0

select函数执行结果:执行成功则返回文件描述词状态已改变的个数,如果返回0代表在描述词状态改变前已超过timeout时间,没有返回;当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。错误
值可能为:
EBADF 文件描述词为无效的或该文件已关闭
EINTR 此调用被信号所中断
EINVAL 参数n 为负值。
ENOMEM 核心内存不足
要想理解好select模型就要理解好fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。
(1)执行fd_set set;FD_ZERO(&set);则set用位表示是0000,0000。
(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)
(3)若再加入fd=2,fd=1,则set变为0001,0011
(4)执行select(6,&set,0,0,0)阻塞等待
(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。
基于上面的讨论,可以轻松得出select模型的特点:
(1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务器上sizeof(fd_set)=128,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是128*8=1024。
(2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始 select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。
(3)可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。

四,优缺点
1,简单,可以在多种系统上使用
2,select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开
的文件描述符个数并不能改变select监听文件个数
3,解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用的是轮询模型,会大大降低服务器响应效率,不应在select上投入更多精力
五 案例
server

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

#define MAXLINE 80
#define SERV_PORT 8000

int main(int atgc,char*argv[])
{
    int i,maxi,maxfd,listenfd,connfd,sockfd;
    int nready,client[FD_SETSIZE];
    ssize_t n;
    fd_set rset,allset;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];
    socklen_t cliaddr_len;
    struct sockaddr_in  cliaddr,servaddr;
    listenfd  = socket(AF_INET,SOCK_STREAM,0);
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);
    bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
    listen(listenfd,20);
    maxfd = listenfd;
    maxi = -1;
    for(i =0;i<FD_SETSIZE;i++){
        client[i] = -1;
    }
    FD_ZERO(&allset);
    FD_SET(listenfd,&allset);
    for(;;){
        rset = allset;
        nready = select(maxfd+1,&rset,NULL,NULL,NULL);
        if(nready < 0){
            printf("select error \n");
            exit(1);
        }

        if(FD_ISSET(listenfd,&rset)){
            cliaddr_len = sizeof(cliaddr);
            connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddr_len);
            printf("received from %s at PORT %d \n",inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),ntohs(cliaddr.sin_port));
            for(i=0;i<FD_SETSIZE;i++)
                if(client[i]<0){
                    client[i]=connfd;
                    break;
                }
            if(i==FD_SETSIZE){
                printf("too many clients\n");
                exit(1);
            }
            FD_SET(connfd,&allset);
            if(connfd>maxfd)
                maxfd = connfd;
            if(i>maxi)
                maxi = i;
            if(--nready==0)
                continue;


        }
        for(i=0;i<=maxi;i++){
            if((sockfd = client[i])<0)
                continue;
            if(FD_ISSET(sockfd,&rset)){
                if((n=read(sockfd,buf,MAXLINE))==0){
                    close(sockfd);
                    FD_CLR(sockfd,&allset);
                    client[i]=-1;
                }else{

                    int j;
                    for(j=0;j<n;j++)
                        buf[j]=toupper(buf[j]);
                    write(sockfd,buf,n);
                }
                if(--nready ==0)
                    break;
            }
        }

    }
    close(listenfd);
    return 0;
}

client

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#define MAXLINE 80
#define SERV_PORT 8000

int main(int argc,char *argv[])
{
    struct sockaddr_in servaddr;
    char buf[MAXLINE];
    int sockfd,n;
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET,"192.168.1.103",&servaddr.sin_addr);
    servaddr.sin_port = htons(SERV_PORT);
    connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
    while(fgets(buf,MAXLINE,stdin)!=NULL){
        write(sockfd,buf,strlen(buf));
        n = read(sockfd,buf,MAXLINE);
        if (n==0){
            printf("the other ha closed\n");
            break;
            }
        else
            write(STDOUT_FILENO,buf,n);

    }
    close(sockfd);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值