zoukankan      html  css  js  c++  java
  • (转)linux select poll

    以前写服务器程序直接就都写成多线程的了,没考虑过其他方式,也没考虑到底哪种方式好;

    前些日子看些人说windows下面用完成端口、Linux下面用epoll,这些效率高。

    其它环境一说就是select;似乎很多人不愿意提多线程方式,也许被传说中的线程同步吓得吧;

    我个人还是偏向多线程方式,这样不但可以监视多端口,还可以分离业务逻辑,便于调试维护。

    ----------------------------------------------------------------------------------------------------

    看明白这几个东西,需要一个环境,描述问题;

    就是我的程序需要同时处理两个或两个以上的文件描述符;

    这几种方法都能解决,除了多线程方式外,都要依靠非阻塞I/O;

    fd = open(filename , O_RDONLY|O_NONBLOCK);

    轮询的方法就可以当个传说中的笑话来听,没有人会这么用。

    #define BLKSIZE 1024

    int nbytes;

    char buf[BLKSIZE];

    int sign_done = 0;

    while(!sign_done){

         nbytes = read(fd,buf,BLKSIZE);

         if( ( ( nbytes<0 ) && (errno != EAGAIN ) && (errno != EINTR) ) || !nbytes)

                   do_something_process_error();

         else

                   do_something_process_data();

         //if should end loop .set sign_done to 1;

    }

    fd是非阻塞的,读不到东西的话就一直读,一直占用CPU资源。除了浪费,没有价值了吧;

    第二种方法是使用SIGPOLL信号的异步I/O;

    SIGPOLL是SISTEM V的信号,BSD系统用SIGIO;

    当系统知道有东西需要你读的时候,就发个SIGPOLL信号来通知;

    int sigpoll_received = 0;

    static void poll_handler(int signo){   //这个是SIGPOLL的信号处理函数

        sigpoll_received = 1;

    }

    int fd1, fd2 ;

    int fd1_done=0 , fd2_done = 0;

    sigset_t oldmask , newmask , zeromask;

    struct sigaction newact;

    fd1 = open (filename1 , O_RDONLY | O_NONBLOCK);

    fd2 = open (filename2 , O_RDONLY | O_NONBLOCK);

    sigemptyset ( &newmask );

    sigaddset ( &newmask , SIGPOLL);

    sigprocmask (SIG_BLOCK , &newmask , &oldmask); //要在实际读取之前阻塞SIGPOLL信号

    newact.sa_handler = poll_handler;

    sigemptyset(&newact.sa_mask);

    newact.sa_flags = 0;

    sigaction ( SIGPOLL , &newact , NULL ); //给SIGPOLL信号安装处理器

    ioctl(fd1 , I_SETSIG , S_INPUT | S_HANGUP );   //设置当有东西读时产生 SIGPOLL信号

    ioctl(fd2 , I_SETSIG , S_INPUT | S_HANGUP );

    sigemptyset ( &zeromask ) ;

    while ( !fd1_done || !fd2_done ){

        if ( !fd1_done)

              deal_with_fd1_and_set_fd1done_sign;

        if ( !fd12done)

              deal_with_fd2_and_set_fd1done_sign;

        while ( !sigpoll_received && ( !fd1_done || !fd2_done ) )

           sigsuspend ( &zeromask ) ; //没东西读的时候,阻塞在这里一直等到SIGPOLL信号来到

        sigpoll_received = 0;

    }

    第三种方法是使用select系统调用;

    select是BSD系统的,但大多数系统都支持,可能是因为spec1170的原因吧;

    #include

    #include

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

    第一个参数nfds是文件描述符集中要检测的掩码位数,这是因为以前描述符集是作为一个整数位屏蔽码实现的;

    也就是每一位表示一个描述符,但那种方法没办法处理多于32个描述符(原来洋人也有不用大脑思考问题的时候^_^);

    现在描述符一般用整数数组的位域表示;这个nfds的数值要比实际要检测的描述符数多一,具体为啥要去看系统调用的源码了(今天俺不看了);

    后面三个(fd_set*)类型的就是实际的描述符集了,读监控、写监控、异常监控三个单独的集;

    最后是超时时间,到时间函数就返回了,很多人说这个可以当成定时器用,比alarm好用;

    select被信号中断时返回-1,并设errno 为 EINTR ;

    fd_set readset;

    int maxfd, fd1 , fd2;

    maxfd = fd1;

    if (fd2 > maxfd ) maxfd = fd2;   //找出最大的描述符

    while (1) {

        FD_ZERO(&readset); /*由于select返回时会清除描述符集中无数据的描述符,所以每次select之前都要重设描述符集 */

        FD_SET(fd1 , &readset );

        FD_SET(fd2 , &readset );

        if ( (select (maxfd+1, &readset , NULL, NULL, NULL) == -1 ) && (errno != EINTR) )

            /* deal with error */

        else{

            if( FD_ISSET(fd1 , &readset ) )    /* FD_ISSET(2) 检查指定的描述符是否被设置了 */

                /* get and process data *//*这里的问题是处理这些数据的时候,进程会阻塞在这里,其他端口的数据的处理没有办法重叠操作*/

            if( FD_ISSET(fd2 , &readset ) )

                /* get and process */

        }

    }

    Poll是SVR4的东西,是和select几乎一样的东西,仅仅是对描述符使用的方式不一样;

    #include

    #include

    int poll (struct pollfd *fds, size_t nfds , int timeout);

    struct pollfd {
             int fd;        /* 文件描述符 */
             short events; /* 等待的事件 */
             short revents; /* 实际发生了的事件 */
    };

    poll用pollfd结构数组提供描述符,并且分别使用输入和输出的消息掩码,这样不用像select那样每次调用之前都设置一次。

    struct pollfd * fds;

    short errmsk;

    int idx;

    errmsk = POLLERR|POLLHUP;

    fds = (void *)calloc(num_fds , sizeof(struct pollfd));

    for(idx = 0 ; idx

        (fds + idx)->fd = *****; /*设置描述符*/

        (fds + idx)->events = POLLRDNORM; /* 设置要监听的事件 */

        (fds + idx)->revents = 0;

    }

    while (situation ){

        if( ( num_ret = poll ( fds , num_fds , INFTIM ) ) == -1 ) && (errno != EINTR ) )

            break;

        for ( idx = 0 ; idx < num_fds && num_ret >0 ; idx++){

            if( (fds + idx )->events && (fds + idx )->revents ) { /* 确实有消息来,有事件发生,而不是错误 */

                if( (fds + idx)->revents & errmsk ) {

                     /* 错误 */

                     (fds + idx)->revents = 0; /*清理掉 */

                }

                 else if( (fds +idx )->revents & POLLRDNORM ){

                      /* 正常处理数据 */

                 }

            }

        }

    }

  • 相关阅读:
    linux 清空文件内容命令
    优秀的java 社区
    vue强制刷新组件 ----组件重置到初始状态
    function的json对象转换字符串与字符串转换为对象的方法
    js实现深度优先遍历和广度优先遍历
    Egg.js中使用sequelize事务
    JavaScript ES6 数组新方法 学习随笔
    eggjs的参数校验模块egg-validate的使用和进一步定制化升级
    Node.js 服务端图片处理利器
    webp图片实践之路
  • 原文地址:https://www.cnblogs.com/qq78292959/p/2425507.html
Copyright © 2011-2022 走看看