IO多路复用之select

1、背景知识
我们首先来看看服务器编程的模型,客户端发来的请求服务端会产生一个进程来对其进行服务,每当来一个客户请求就产生一个进程来服务,然而进程不可能无限制的产生,因此为了解决大量客户端访问的问题,引入了IO复用技术。
  即:一个进程可以同时对多个客户请求进行服务。
  也就是说IO复用的“介质”是进程(准确的说复用的是select和poll,因为进程也是靠调用select和poll来实现的),复用一个进程(select和poll)来对多个IO进行服务,虽然客户端发来的IO是并发的但是IO所需的读写数据多数情况下是没有准备好的,因此就可以利用一个函数(select和poll)来监听IO所需的这些数据的状态,一旦IO有数据可以进行读写了,进程就来对这样的IO进行服务。
  IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合:
  (1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。
  (2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
  (3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
(4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
  (5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。

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

2、select函数
  该函数准许进程指示内核等待多个事件中的任何一个发送,并只在有一个或多个事件发生或经历一段指定的时间后才唤醒。函数原型如下:

#include <sys/select.h>
#include <sys/time.h>

int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
返回值:就绪描述符的数目,超时返回0,出错返回-1

函数参数介绍如下:

(1)第一个参数maxfdp1指定待测试的描述字个数,它的值是待测试的最大描述字加1(因此把该参数命名为maxfdp1),描述字0、1、2…maxfdp1-1均将被测试。

因为文件描述符是从0开始的。

(2)中间的三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述字。如果对某一个的条件不感兴趣,就可以把它设为空指针。struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符,可通过以下四个宏进行设置:

 void FD_ZERO(fd_set *fdset);           //清空集合

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

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

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

(3)timeout告知内核等待所指定描述字中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和微秒数。

 struct timeval{
                   long tv_sec;   //seconds
                   long tv_usec;  //microseconds
       };

这个参数有三种可能:
(1)永远等待下去:仅在有一个描述字准备好I/O时才返回。为此,把该参数设置为空指针NULL。
(2)等待一段固定时间:在有一个描述字准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。
(3)根本不等待:检查描述字后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。

3.代码演示(只对客户端消息进行读功能):
select服务器端:

#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>

int startup(char* ip, int port)
{
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock<0)
    {
        perror("socket");
        return 2;
    }

    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");
        return 3;
    }

    if(listen(sock, 10) < 0)
    {
        perror("listen");
        return 4;
    }

    return sock;
}

void usage(const char* proc)
{
    printf("usage:%s [local_ip] [local_port]\n", proc);
}

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        usage(argv[0]);
        return 1;
    }

    int listen_sock = startup(argv[1],atoi(argv[2]));

    int nums = sizeof(fd_set)*8;
    int fds[nums];
    int i = 1;

    fds[0] = listen_sock;
    for( ; i<nums; i++)
    {
        fds[i] = -1;
    }

    int max = listen_sock;
    fd_set rfds;
    while(1)
     {
        char buf[1024];
        FD_ZERO(&rfds);
        struct timeval timeout ={5,0};

        for( i=0; i<nums; i++)
        {
            if(fds[i] > -1)
            {
                FD_SET(fds[i], &rfds);
                if(max< fds[i])
                {
                     max = fds[i];
                }
            }
        }

        switch(select(max+1, &rfds, NULL, NULL, &timeout))
        {
        case 0:
            printf("wait timeout!\n");
            break;

        case -1:
            perror("select");
            break;

        default:
            {
                for(i=0; i<nums; i++)
                {
                    if(fds[i] == -1)
                    {
                        continue;
                    }
                    if((i==0)&&(FD_ISSET(fds[i], &rfds)))
                    //listen_sock
                    {
                        struct sockaddr_in client;
                        socklen_t len = sizeof(client);
                        int new_sock = accept(listen_sock, (struct sockaddr*)&client, &len);
                        if(new_sock < 0)
                        {
                            perror("accept");
                            continue;
                        }
                        printf("get a client:[%s, %d]\n",inet_ntoa(client.sin_addr), ntohs(client.sin_port));

                        int j = 0;
                        for(;j<nums; j++)
                        {
                            if(fds[j] == -1)
                            {
                                fds[j] = new_sock;
                                FD_SET(fds[j], &rfds);
                                break;
                            }
                        }

                    }
                    else if((i != 0)&&(FD_ISSET(fds[i], &rfds)))
                    {
                        char buf[1024];
                        ssize_t s = read(fds[i], buf, sizeof(buf)-1);
                        if(s>0)
                        {
                            buf[s] = 0;
                            printf("client:%s\n", buf);
                        }
                        else if(s == 0)
                        {
                            printf("client is quit!\n");
                            close(fds[i]);
                            fds[i] = -1;
                            break;
                        }
                        else
                        {
                            perror("read");
                            close(fds[i]);
                            fds[i] = -1;
                            break;
                        }

                    }

                }//for
            }
        }
     }
    return 0;
}

select服务器:
这里写图片描述

用telnet工具测试:
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fly_bit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值