I/O多路复用之select

基本概念:

IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合:

(1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。

(2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。

(3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。

(4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。

(5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。

与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。


select:

系统提供select函数用来实现I/O多路复用输入/输出模型。select系统调用是用来让我们的程序监视多个文件描述状态变化的。程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生状态变化。通常I/O操作有两个步骤,一个是等,另一个是数据搬迁。select主要是在等的这个状态阻塞着直到事件发生。

函数原型:

int select(int nfds,fd_set *reads,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);

参数:

nfds:是需要监视的最大的文件描述符的值+1。 
fd_set: 
fd_set底层是用位图实现的,每一个位都代表一个文件描述符。readfds,writefds,exceptfds分别对应于需要检测的可读文件描述符结合,可写描述符集合,异常文件描述符集合,他们都是输入输出型参数。 
当作为输入参数时:只要文件描述符集合中对应的位上为1,就表示select需要监视这个描述符的状态。比如readfds里面的文件描述符就代表他们需要等待读事件,writefds里面的文件描述符就代表他们需要等待写事件。 
当作为输出参数时,只要文件描述符集合中对应的位上为1,就代表他们等待的事件已经就绪,这是由内核设定的。

timeout:设置超时时间。 
timeout里面的成员设定为特定的时间值: 
如果在这段时间里面没有事件发生,select将超时返回。struct timeval结构用于描述一段时间长度,如果在这个时间内,需要监听的描述符没有事件发生则函数返回,返回值为0。 
struct timeval 

long tv_sec; //秒 
long tv_usec; //微秒 

timeout里面的成员等于0:表示非阻塞轮询方式,不断的去检测描述符集合的状态,然后立即返回。 
timeout为NULL:表示以阻塞的方式等待事件发生。

返回值: 
成功的话,返回文件描述符状态已改变的个数。如果返回0代表在描述符状态改变之前已经超过timeout时间。如果有错误发生的话,则返回-1。

另外,还有一组与fd_set 有关的操作

  • FD_SET(fd, _fdset),把fd加入_fdset集合中
  • FD_CLR(fd, _fdset),把fd从_fdset集合中清除
  • FD_ISSET(fd, _fdset),判定fd是否在_fdset集合中
  • FD_ZERO(_fdset),清除_fdset有描述符
select的缺点: 
1、每次调用select,都需要把fd集合从用户态拷贝到内核态。这个开销在fd很多的时候会很大。 
2、select在返回之后,需要我们遍历数组去查找事件就绪的描述符。这个过程的时间复杂度是O(N)。而epoll它查找就绪事件的时候是O(1)。 
3、select支持的文件描述符的数量太小了,默认是1024。

实现一个服务器,使用select让服务器可以同时接受多个客户的链接,并将客户发送的数据打印出来。

#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/select.h>
#include<unistd.h>
#include<sys/time.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#define SIZE 128

int startup(char *ip,int port)
{
    assert(ip);
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        perror("socket");
        exit(0);
    }
    int opt=1;
    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_port=htons(port);
    local.sin_addr.s_addr=inet_addr(ip);
    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
    {
        perror("bind");
        exit(1);
    }
    if(listen(sock,5)<0)
    {
        perror("listen");
        exit(2);
    }
    return sock;
}

int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        printf("usae: %s [IP] [PORT]\n",argv[0]);
        return 0;
    }
    int lis_sock=startup(argv[1],atoi(argv[2]));
    int gfds[SIZE];
    memset(gfds,-1,SIZE*4);
    fd_set rfds;
    FD_ZERO(&rfds);
    while(1)
    {
        struct timeval timeout={5,0};
        gfds[0]=lis_sock;
        int max_fd=-1;
        int i=0;
        for(;i<SIZE;i++)
        {
            if(max_fd<gfds[i])
            {
                max_fd=gfds[i];
            }

            if(gfds[i]>=0)
            {
                FD_SET(gfds[i],&rfds);
            }

        }
        int ret=select(max_fd+1,&rfds,NULL,NULL,NULL);

        switch(ret)
        {
        case 0:
            printf("timeout...\n");
            break;
        case -1:
            printf("error");
            break;
        default:
            if(FD_ISSET(gfds[0],&rfds))
            {
                struct sockaddr_in peer;
                socklen_t len=sizeof(peer);
                int connfd=accept(lis_sock,(struct sockaddr*)&peer,&len);
                if(connfd<0)
                {
                    perror("accept");
                }
                else
                {
                    printf("client: %s:%d fd(%d)\n",inet_ntoa(peer.sin_addr),\
                            ntohs(peer.sin_port),connfd);                    
                    int k=0;
                    for(;k<SIZE;k++)
                    {
                        if(gfds[k]==-1)
                        {
                            gfds[k]=connfd;
                             break;
                         }
                    }
                    if(k>=SIZE)
                    {
                        close(connfd);
                        gfds[k]=-1;
                     }
                 }
            }

            int j=1;
            for(;j<SIZE;j++)
            {
                if(FD_ISSET(gfds[j],&rfds))
                {
                    char buf[SIZE];
                    ssize_t s=read(gfds[j],buf,sizeof(buf));
                    if(s<0)
                    {
                        perror("read");
                         continue;
                    }
                    else if(s==0)
                    {
                         printf("client is quit!\n");
                        close(gfds[j]);
                        gfds[j]=-1;
                    }
                    else
                    {
                         buf[s]=0;
                        printf("client# %s\n",buf);
                     }
                 }
            }
            break;
        }
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值