IO多路复用-select

       select系统调用的目的是:在一段指定时间内,监听用户感兴趣的文件描述符上的可读、可写和异常事件。poll和select应该被归类为这样的系统 调用,它们可以阻塞地同时探测一组支持非阻塞的IO设备,直至某一个设备触发了事件或者超过了指定的等待时间——也就是说它们的职责不是做IO,而是帮助 调用者寻找当前就绪的设备。

 

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

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

     参数nfds是需要监视的文件描述符数,要监视的文件描述符值为0 ~ nfds-1。

    参数readfds指定需要监视的可读文件描述符集合,当这个集合中的一个描述符上有数据到达时,系统将通知调用select函数的程序。

    参数writefds指定需要监视的可写文件描述符集合,当这个集合中某个描述符可以发送数据时,程序将收到通知。

    参数exceptfds指定需要监视的异常文件描述符集合,当这个集合中的一个描述符发生异常时,程序将受到通知。

    参数timeout指定了阻塞的时间,如果在这段时间内监视的文件描述符上没有事件发生,则函数select()将返回0。

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);   // 检查集合中指定的文件描述符是否可以读写 

    fd_set结构体是文件描述符集,该结构体实际上是一个整型数组,数组中的每个元素的每一位标记一个文件描述符。fd_set能容纳的文件描述符 数量由FD_SETSIZE指定,一般情况下,FD_SETSIZE等  于1024,这就限制了select能同时处理的文件描述符的总量。


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

 

 struct timeval{

                   long tv_sec;   /* seconds */

                   long tv_usec;  /* microseconds */

       };

这个参数有三种可能:

    1)永远等待下去:仅在有一个描述字准备好I/O时才返回。为此,把该参数设置为空指针NULL。

    2)等待一段固定时间:在有一个描述字准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。

    3)根本不等待:检查描述字后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。

 

 

 

select()返回值情况: 

超时时间内,如果文件描述符就绪,select返回就绪的文件描述符总数(包括可读、可写和异常),如果没有文件描述符就绪,select返回0; 

select调用失败时,返回 -1并设置errno,如果收到信号,select返回 -1并设置errno为EINTR。

文件描述符的就绪条件: 
在网络编程中, 

1)下列情况下socket可读: 

a) socket内核接收缓冲区的字节数大于或等于其低水位标记SO_RCVLOWAT; 

b) socket通信的对方关闭连接,此时该socket可读,但是一旦读该socket,会立即返回0(可以用这个方法判断client端是否断开连接); 

c) 监听socket上有新的连接请求; 

 

 

d) socket上有未处理的错误。 

 

 

2)下列情况下socket可写: 

a) socket内核发送缓冲区的可用字节数大于或等于其低水位标记SO_SNDLOWAT; 

b) socket的读端关闭,此时该socket可写,一旦对该socket进行操作,该进程会收到SIGPIPE信号; 

c) socket使用connect连接成功之后; 

d) socket上有未处理的错误。


select优点

  解决了读写阻塞的问题,一进程一线程就可完成数据间的传输,避免了多进程多线程的消耗,提高了资源利用率。

 

select缺点

    select支持的文件描述符数量最大1024

    每次调用select都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

    每次调用select都需要在内核遍历传递进来的所有fd,查看有没有就绪的fd,这个开销在fd很多时也很大,效率随FD数目增加而线性下降

 

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

const int backlog = 8;  //最大监听数
int fds[32];
int fds_nums = sizeof(fds) / sizeof(*fds);
#define BuffSize 1024   

int m_listen(char *ip, int port)
{
    //套接字结构体
    struct sockaddr_in ser;
    (struct sockaddr_in*)memset(&ser, 0x00, sizeof(struct sockaddr_in));
    ser.sin_family = AF_INET;
    ser.sin_port = htons(port);
    ser.sin_addr.s_addr = inet_addr(ip);

    //套接字
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(sock_fd == -1)
    {
        perror("socket error:");
        exit(1);
    }

    //设置套接字属性
    int optval = 1;
    if(setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&optval, sizeof(int)) == -1)
    {
        perror("setsockopt error:");
        exit(1);
    }

    //绑定端口
    if(bind(sock_fd, (struct sockaddr*)&ser, sizeof(struct sockaddr)) == -1)
    {
        perror("bind error:");
        exit(1);
    }

    //监听
    if(listen(sock_fd, backlog) == -1)
    {
        perror("listen error:");
        exit(1);
    }


    return sock_fd;
}

int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        fprintf(stderr, "Usage [%s] [ip] [port]\n", argv[0]);
        exit(0);
    }

    int sock_fd = m_listen(argv[1], atoi(argv[2]));

    fd_set readfds;

    //清空读取文件描述符集
    FD_ZERO(&readfds);
    
    int i;
    for(i = 0; i < fds_nums; ++i)
        fds[i] = -1;

    //将监听套接字加入0号位
    fds[0] = sock_fd;

    while(1)
    {
        int max_fd = -1;
        for(i = 0; i < fds_nums; ++i)
            if(fds[i] > 0)
            {
                FD_SET(fds[i], &readfds);
                max_fd = max_fd > fds[i] ? max_fd : fds[i];
            }
        switch(select(max_fd + 1, &readfds, NULL, NULL, NULL))
        {
            case 0:
                fprintf(stderr, "time out ...\n");
                break;
            case -1:
                perror("select error:");
                break;
            default:
                for(i = 0; i < fds_nums; ++i)//遍历所有接受管理的描述符
                {
                    //如果是监听套接字
                    if(i == 0 && FD_ISSET(sock_fd, &readfds))
                    {
                        struct sockaddr_in cli;
                        socklen_t len = sizeof(struct sockaddr_in);
                        int conn_fd;
                        if((conn_fd = accept(sock_fd, (struct sockaddr*)&cli, &len)) == -1)
                        {
                            perror("connect error:");
                            break;
                        }
                        
                        fprintf(stderr, "accept a new client, ip:%s port:%d\n", inet_ntoa(cli.sin_addr),\
                                ntohs(cli.sin_port));

                        int j;
                        for(j = 0; j < fds_nums; ++j)
                            if(fds[j] == -1)
                            {
                                fds[j] = conn_fd;
                                break;
                            }
                        if(j == fds_nums)
                            close(conn_fd);

                    }

                    else 
                    {
                        if(FD_ISSET(fds[i], &readfds))
                        {
                            int ret;
                            int buff[BuffSize] = {0};
                            if((ret = recv(fds[i], buff, BuffSize - 1, 0)) == -1)
                            {
                                perror("recv error:");
                                close(fds[i]);
                                fds[i] = -1;
                            }

                            else if(ret)
                            {
                                buff[ret] = '\0';
                                fprintf(stderr, "recv:%s\n", buff);
                            }
                            else
                            {
                                close(fds[i]);
                                fds[i] = -1;
                            }

                        }

                    }
                }

                break;
        }

    }

    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值