多路IO复用技术--select

2019-12-15  本文已影响0人  精简好文

1.功能介绍

1.1 网络通信中,对于套接字(文件描述符)在任意时刻是否有数据可读,我们不知道,只会用while 10毫秒循环收发,select能够解决这个问题,时时监听套接字的读写情况,有收到数据就读取。

2.相关函数说明

2.1 int select(int n,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout);
说明:select()用来等待文件描述符状态的改变。参数n代表最大的文件描述符加1,参数readfds、writefds和exceptfds称为文件描述符集,填了readfd代表监听可读事件(收数据),不填代表不监听该事件,其他两个同理,timeout代表超时时间设置,就是个时间,可不设置,填NULL,struct timeval结构体如下:
 struct timeval{      
         long tv_sec;   /*秒 */

         long tv_usec;  /*微秒 */   

     }
2.2 字符集相关函数操作

int FD_ZERO(fd_set *fdset);  

int FD_CLR(int fd, fd_set *fdset);   

int FD_SET(int fd, fd_set *fdset);  

int FD_ISSET(int fd, fd_set *fdset);
说明:FD_ZERO是初始化字符集变量,使用FD_SET将套接字fd(文件描述符)加入字符集fdset,使用FD_CLR将套接字fd(文件描述符)从字符集fdset中删除,使用FD_ISSET来判断套接字fd(文件描述符)是否加入字符集fdset进行监听。

3.使用场景

3.1 有多个套接字(高并发)需要收发数据。

4.使用代码

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

int main()
{
    //创建socket
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd < = 0)
    {
           return -1;
    }

    //设置端口复用
    int opt = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    //绑定
    struct sockaddr_in serv;
    serv.sin_family = AF_INET;
    serv.sin_port = htons(8888);
    serv.sin_addr.s_addr = htonl(INADDR_ANY);
    bind(lfd, (struct sockaddr *)&serv, sizeof(serv));

    //监听
    listen(lfd, 128);

    //定义文件描述符集变量
    fd_set readfds, tmpfds;
    
    //初始化文件描述符集变量
    FD_ZERO(&readfds);
    FD_ZERO(&tmpfds);
    
    //将lfd加入到readfds中
    FD_SET(lfd, &readfds);
    
    int maxfd = lfd;
    int cfd;
    int i;
    int sockfd;
    int nready;
    int n;
    char buf[1024];
    
    sleep(10);
    
    while(1)
    {
        FD_ZERO(&tmpfds);
        tmpfds = readfds;
        
        //将文件描述符集委托给内核监控
        //tmpfds是传入传出参数, 
            //传入:告诉内核要监测哪些文件描述符
            //传出:内核告诉应用程序那些文件发生了变化, 但是并没有具体告诉到底是哪些.
        nready = select(maxfd+1, &tmpfds, NULL, NULL, NULL);
        printf("nready==[%d]\n", nready);
        if(nready<0)
        {
            if(errno==EINTR)//若被信号中断,则errno为EINTR
            {
                perror("select error");
                continue;
            }
            close(lfd);
            exit(-1);
        }
        
        //若有客户端连接请求, 则接受新的连接,同时将新的连接加入到文件描述符集中
        if(FD_ISSET(lfd, &tmpfds))
        {
            cfd = accept(lfd, NULL, NULL);
            if(lfd < = 0)
            {
                continue;
            }
            //将cfd加入到文件描述符集中
            FD_SET(cfd, &readfds);
            
            if(maxfd<cfd)
            {
                maxfd = cfd;
            }
            
            if(--nready==0)
            {
                continue;
            }
        }
        
        //下面是有客户端发送数据到来的情况
        for(i=lfd+1; i<=maxfd; i++)
        {
            //判断某个文件描述符是否有变化
            sockfd = i;
            if(FD_ISSET(sockfd, &tmpfds))
            {
                //读数据
                memset(buf, 0x00, sizeof(buf));
                n = Read(sockfd, buf, sizeof(buf));
                if(n<=0)
                {
                    //若读数据失败或者对方关闭连接, 则将sockfd从文件描述符集中删除
                    close(sockfd);
                    FD_CLR(sockfd, &readfds);                   
                }
                else 
                {
                    for(int k=0; k<n; k++)
                    {
                        buf[k] = toupper(buf[k]);
                    }
                    Write(sockfd, buf, n);
                }
                
                if(--nready==0)
                {
                    break;
                }
            }
        }
    }
    
    close(lfd);
    
    return 0;
}

5.缺点

5.1 当客户端多个连接, 但少数活跃的情况, select效率较低。

例如: 作为极端的一种情况, 3-1023文件描述符全部打开, 但是只有1023有发送数据, select就显得效率低下。

5.2 fd_set能容纳的文件描述符 数量由FD_SETSIZE指定,一般情况下,FD_SETSIZE等 于1024,这就限制了select能同时处理的(套接字)文件描述符的总量(FD_SETSIZE=1024 fd_set使用了该宏, 当然可以修改内核, 然后再重新编译内核, 一般不建议这么做.)。

上一篇下一篇

猜你喜欢

热点阅读