• IO多路复用--select


    1 IO多路复用的基本概念

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

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

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

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

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

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

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

    2 select 函数

    /* According to POSIX.1-2001, POSIX.1-2008 */
    #include <sys/select.h>
    
    /* According to earlier standards */
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

    参数描述:

    nfds: 集合中所有文件描述符的范围,即所有文件描述符的最大值加1,描述字0、1、2...nfds-1均将被测试;

    readfds: 指向fd_set结构的指针,这个集合中包括需要监控读事件的文件描述符,若可读则返回;当然也可以置空(NULL),表示并不关心;

    writefds, exceptfds: 同readfds;

    timeout: select的超时时间,这个参数至关重要,它可以使select处于三种状态:

    • 若将NULL以形参传入,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止; 
    • 若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;
    • timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

    返回值:

    • >0:就绪描述字的正数目;
    • -1:出错;
    • 0 : 超时;

    fd_set

    select使用描述字集,典型地是一个整数数组,其中每个整数中的每一位对应一个描述字。假设使用32位整数,那么该数组的第一个元素对应于描述字0~31,第二个元素对应于描述字32~63,依此类推。所有的实现细节都与应用程序无关,它们隐藏在名为fd_set的数据类型和以下四个宏中:
    void FD_ZERO (fd_set *fdset); // clear all bits in fdset
    void FD_SET (int fd,fd_set *fdset); // turn on the bit for fd in fdset
    void FD_CLR (int fd,fd_set *fdset); // turn off the bit for fd in fdset
    int FD_ISSET(int fd,fd_set *fdset); // is the bit for fd on in fdset

    struct timeval

    timeval结构用于指定这段时间的秒数和微秒数,

    struct timeval {
        long tv_sec;   //seconds
        long tv_usec;  //microseconds
    };
    select() may update the timeout argument to indicate how much time was left.  pselect() does not change this argument.

    3 select函数的典型应用

    下面介绍一个给予select的echo server;client直接通过telnet连接server:telnet host port,主要包括以下几个

    基本功能

    (1) client 发送quit,则server主动断开与客服端的连接;

    (2) clinet发送其他data到server,则server会原样返回;

    注意点:

    (1) 由于通过telnet与server通信,telnet client发送过来的字符串的结束符为" " ,所以判断client的断开信号时,需判断是否为"quit "

    (2) 同样打印客服端的数据时不需要再添加换行符,因为data form telnet client自带的就有;

    (3) 由于本文中,每次读数据时都是放在一个公共的缓冲区buffer,所以每次接收数据(read)并处理完(send)后,应重置缓冲区,比如重置受影响的区域(大小size)都为'',memset(buffer, 0, size);
     
    /* Handle multiple socket connections with select and fd_set on Linux */
      
    #include <stdio.h>
    #include <string.h>   //strlen
    #include <stdlib.h>
    #include <errno.h>
    #include <unistd.h>   
    #include <arpa/inet.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <sys/time.h> //FD_SET, FD_ISSET, FD_ZERO macros
      
    #define TRUE   1
    #define FALSE  0
    #define PORT 8888
    #define CLIENT_NUM 2
    #define BLOCK_NUM 2
    #define BUFFER_SIZE 1024
     
    int main(int argc , char *argv[])
    {
        int opt = TRUE;
        int master_socket , addrlen , new_socket , client_socket[CLIENT_NUM] , activity, i , valread , sd;
        int max_sd;
        struct sockaddr_in address;
        char buffer[BUFFER_SIZE+1];  //data buffer of 1K
        struct timeval tv;
        char quit[7]="quit
    ";     //for data from telnet client, the data end with "
    "
       
        //set of socket descriptors
        fd_set readfds;
          
        //a message retuan to client when it connect to the server. 
        char *message = "ECHO Daemon v1.0 
    ";
      
        //initialise all client_socket[] to 0 so not checked
        for (i = 0; i < CLIENT_NUM; i++) 
        {
            client_socket[i] = 0;
        }
        printf("size of buffer:%lu
    ",sizeof(buffer));
        memset(buffer,0,sizeof(buffer)*sizeof(buffer[0]));
          
        //create a master socket for listening
        if( (master_socket = socket(AF_INET , SOCK_STREAM , 0)) == 0) 
        {
            perror("socket failed");
            exit(EXIT_FAILURE);
        }
      
        /*一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用。*/
        if( setsockopt(master_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0 )
        {
            perror("setsockopt");
            exit(EXIT_FAILURE);
        }
      
        //type of socket created
        address.sin_family = AF_INET;
        address.sin_addr.s_addr = INADDR_ANY;
        address.sin_port = htons( PORT );
          
        //bind the socket to localhost port 8888
        if (bind(master_socket, (struct sockaddr *)&address, sizeof(address))<0) 
        {
            perror("bind failed");
            exit(EXIT_FAILURE);
        }
        printf("Listener on port %d 
    ", PORT);
         
        //try to specify maximum of BLOCK_NUM pending connections for the master socket
        if (listen(master_socket, BLOCK_NUM) < 0)
        {
            perror("listen");
            exit(EXIT_FAILURE);
        }
          
        //accept the incoming connection
        addrlen = sizeof(address);
        puts("Waiting for connections ...");
        
        /********************************call select in a infinite loop*******************************/ 
        while(TRUE) 
        {
            /*每次调用select前都要重新设置文件描述符和时间,因为事件发生后,文件描述符和时间都被内核修改啦*/
            FD_ZERO(&readfds);
      
            /*添加监听套接字*/
            FD_SET(master_socket, &readfds);
            max_sd = master_socket;
            
            tv.tv_sec = 30;
            tv.tv_usec = 0;
             
            /*添加客户端套接字*/
            for ( i = 0 ; i < CLIENT_NUM ; i++) 
            {
                //socket descriptor
                sd = client_socket[i];
                 
                //if valid socket descriptor then add to read list
                if(sd > 0)
                    FD_SET( sd , &readfds);
                 
                //highest file descriptor number, need it for the select function
                if(sd > max_sd)
                    max_sd = sd;
            }
      
        printf("
    select begin, timeout left:%lds-%ldms.
    ",tv.tv_sec,tv.tv_usec);
            //wait for an activity on one of the sockets, timeout is tv; 
            activity = select( max_sd + 1 , &readfds , NULL , NULL , &tv);
           
            //return value < 0, error happend; 
            if ((activity < 0) && (errno!=EINTR)) 
            {
                printf("select error");
            }
        //return value = 0, timeout;
        else if(activity == 0){
            //no event happend in specific time, timeout;
            printf("select return, activity:%d,  timeout left:%lds-%ldms.
    ", activity, tv.tv_sec, tv.tv_usec);
            continue;
        }
              
            //If something happened on the master socket , then its an incoming connection
            if (FD_ISSET(master_socket, &readfds)) 
            {
            printf("select return, timeout left:%lds-%ldms.
    ",tv.tv_sec,tv.tv_usec);
                if ((new_socket = accept(master_socket, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0)
                {
                    perror("accept");
                    exit(EXIT_FAILURE);
                }
              
                //inform user of socket number - used in send and receive commands
                printf("New connection , socket fd is %d , ip is : %s , port : %d 
    " , new_socket , inet_ntoa(address.sin_addr) , ntohs(address.sin_port));
            
                //send new connection greeting message
                if( send(new_socket, message, strlen(message), 0) != strlen(message) ) 
                {
                    perror("send");
                }
                  
                puts("Welcome message sent successfully");
                  
                //add new socket to array of sockets
                for (i = 0; i < CLIENT_NUM; i++) 
                {
                    //if position is empty
                    if( client_socket[i] == 0 )
                    {
                        client_socket[i] = new_socket;
                        printf("Adding to list of sockets as %d
    " , i);
                         
                        break;
                    }
                }
            }
              
            //else there is some IO operation on some other socket :)
            for (i = 0; i < CLIENT_NUM; i++) 
            {
                sd = client_socket[i];
            printf("check client--%d:fd--%d
    ",i,sd);
                  
                if (FD_ISSET( sd , &readfds)) 
                {
            printf("select return, timeout left:%lds-%ldms.
    ",tv.tv_sec,tv.tv_usec);
                    //Check if it was for closing , and also read the incoming message
                    valread = read( sd , buffer, 1024);
            printf("read form client,size:%d
    ",valread);
                    if (valread <= 0)
                    {
                        //Somebody disconnected , get his details and print
                        getpeername(sd , (struct sockaddr*)&address , (socklen_t*)&addrlen);
                        printf("Host disconnected , ip %s , port %d 
    " , inet_ntoa(address.sin_addr) , ntohs(address.sin_port));
                          
                        //Close the socket and mark as 0 in list for reuse
                        close( sd );
                        client_socket[i] = 0;
                    }
                    //get message "quit", close the connection;
                    else if(valread == strlen(quit) && memcmp(buffer,quit,strlen(quit))==0) {
                printf("client of fd:%d will be closed.
    ", sd);
                client_socket[i] = 0;
                close(sd);
            }
                    //Echo back the message that came in
            else
                    {   
                         
                        printf("data come from client:%s",buffer);
                        send(sd , buffer , strlen(buffer) , 0 );
                        memset(buffer,0,valread);
                //printf("sleep 5s
    ");
                //sleep(5);
                    }
                }
            }
        }
          
        return 0;
    } 
    View Code

     

    运行结果:

    [root@localhost LibEvent_practice]# ./echo-server-select 
    size of buffer:1025
    Listener on port 8888 
    Waiting for connections ...
    
    select begin, timeout left:30s-0ms.
    select return, timeout left:25s-213990ms.                                           
    //clinet1 connect to server, select return, the time left to timeout is "timeout left"; New connection , socket fd
    is 4 , ip is : 10.204.214.232 , port : 45042 //detail info of client Welcome message sent successfully Adding to list of sockets as 0 check client--0:fd--4 //client fd info saved in client_socket[CLIENT_NUM] check client--1:fd--0 select begin, timeout left:30s-0ms.                                select return, timeout left:20s-751072ms. //client2 connect to server New connection , socket fd is 5 , ip is : 10.204.214.248 , port : 48117 Welcome message sent successfully Adding to list of sockets as 1 check client--0:fd--4 check client--1:fd--5 select begin, timeout left:30s-0ms. check client--0:fd--4 select return, timeout left:25s-940254ms.
    //client1 fd可读,读取数据并回传给client read form client,size:
    21 data come from client:aaaaaaaaaaaaaaaaaaa check client--1:fd--5 select begin, timeout left:30s-0ms. check client--0:fd--4 check client--1:fd--5 select return, timeout left:23s-593254ms.
    //client2 fd可读,读取数据并回传,可以看到dataSize=7,可以看到的数据为bbbbb,实际来自client的数据为"bbbbb ",打印出"bbbbb"然后换行; read form client,size:
    7 data come from client:bbbbb select begin, timeout left:30s-0ms. check client--0:fd--4 select return, timeout left:19s-478711ms. read form client,size:6 client of fd:4 will be closed.
    //读取到来自client的"quit "字符串,主动关闭client check client
    --1:fd--5 select begin, timeout left:30s-0ms. select return, activity:0, timeout left:0s-0ms. select begin, timeout left:30s-0ms. ^C
     参考: 
     
  • 相关阅读:
    华为2019软件题
    图像的存储格式转化(python实现)
    windows+两个ubuntu系统的引导启动问题
    《视觉SLAM十四讲》课后习题—ch6
    视觉SLAM十四讲课后习题—ch8
    LINQ根据时间排序问题(OrderBy、OrderByDescending)
    Element的扩展
    CSharp
    jQuery函数使用记录
    日记越累
  • 原文地址:https://www.cnblogs.com/harvyxu/p/7441151.html
Copyright © 2020-2023  润新知