Unix/Linux Alternative I/O Models

常见的模型

1.select() 跟 poll() 是系统API,伴随unix系统多年了,所以他的特点就是移植性相比其他model更好,但最大的问题在于无法很好的伸缩去监听大量的文件句柄

2.epoll() 主要的优势就是可以监听大量的文件句柄,但是是个linux特有的api(其他类unix系统也有类似的api 像BSD的kqueue, Solaris 提供的特殊文件/dev/poll)

3.信号驱动IO 像epoll()一样也可以监听大量的文件句柄。但是epoll 还提供的些别的特性

  • 我们可以避免跟错中复杂的信号打交道
  • 我们只要关注我们想知道的信号通知(准备读或者准备写的信号通知)
  • 我们可以选择基于条件触发还是边缘触发的通知

两种触发类型的通知

level-triggered notification:(水平触发/条件触发) 一个文件句柄只有是处于准备好可以进行非阻塞IO系统调用就可以得到这个通知,换句话说一旦关注到可写的状态就再停不下来了 除非取消关注 edge-triggered notification:(边缘触发) 自上一次处理完触发后如果再这样的行为就会发一个通知 所以每次得到通知后得及时处理完成 不然这个文件句柄就算废了 因为如果处理完就再也得不到通知了

select() 与 poll()

select()

select()会产生阻塞 直到有一个或者多个监听的文件句柄准备好了 就返回

函数原型

#include <sys/time.h> /* For portability */ 
#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds: 监听文件句柄的范围 比最大文件句柄的索引大一即可 
readfds:尝试去查看这一系列的句柄表是否有可读的
writefds:尝试去查看这一系列的句柄表是否有可写的
exceptfds:尝试去查看这一系列的句柄表是否有异常状态变化的发生
timeout:定义超时

返回值:返回准备好的文件句柄数 0则是超时 -1 是异常发生

辅助的API

#include <sys/select.h>
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在 fdset中的话 返回1 否则返回0

example

//open_listen.h
#ifndef OPEN_LISTEN
#define OPEN_LISTEN
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>

typedef struct sockaddr SA;
#define LISTENQ  1024



int open_listen(int port);

#endif


//open_listen.c
#include "open_listen.h"
int open_listen(int port)
{
    int listenfd, optval=1;
    struct sockaddr_in serveraddr;

    /*  Create a socket descriptor */
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        return -1;

    /*  Eliminates "Address already in use" error from bind. */
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, 
    (const void *)&optval , sizeof(int)) < 0)
    return -1;

    
    /*  Listenfd will be an endpoint for all requests to port
    *     on any IP address for this host 
    */
    bzero((char *) &serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET; 
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); 
    serveraddr.sin_port = htons((unsigned short)port); 
    if (bind(listenfd, (SA *)&serveraddr, sizeof(serveraddr)) < 0)
    return -1;

    /*  Make it a listening socket ready to accept connection requests */
    if (listen(listenfd, LISTENQ) < 0)
    return -1;
    return listenfd;     
}


// accept tcp connection
#include <sys/select.h>
#include <stdio.h>
#include <stdlib.h>
#include "open_listen.h"

int main(int argc, char* argv[])
{
    int listenfd, port, connfd;
    socklen_t clientlen = sizeof(struct sockaddr_in);
    struct sockaddr_in clientaddr;
    fd_set read_set, ready_set;
    port = atoi(argv[1]);

    listenfd = open_listen(port);

    FD_ZERO(&read_set);
    FD_SET(listenfd, &read_set);

    while(1) {
        ready_set = read_set;
        int count = select(listenfd + 1, &ready_set, NULL, NULL, NULL);
        if(count > 0) {
            printf("have %d ready\n", count);
        } 
        if(FD_ISSET(listenfd, &ready_set)) {
            connfd = accept(listenfd, (SA*)&clientaddr, &clientlen);
            printf("accept new connection:%d\n", connfd);
            //close(connfd);
        }
    }
    return 1;
}

poll()

函数原型

#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
// pollfd
struct pollfd {
    int   fd;
    short events;
    short revents;
};

fds:pollfd的数组 声明要监视的文件句柄以及感兴趣的事件
nfds_t: fds的长度
timeout: 超时

返回值:返回准备好的文件句柄数

example


// accept tcp connection
#include <sys/poll.h>
#include <stdio.h>
#include <stdlib.h>
#include "open_listen.h"


int main(int argc, char* argv[])
{
    int listenfd, port, connfd;
    socklen_t clientlen = sizeof(struct sockaddr_in);
    struct pollfd *pollFd;
    int pollFd_len = 1;
    struct sockaddr_in clientaddr;
    port = atoi(argv[1]);
    listenfd = open_listen(port);
    pollFd = malloc(pollFd_len * sizeof(struct pollfd));

    pollFd[0].fd = listenfd;
    pollFd[0].events = POLLIN;
    while(1) {
        int count = poll(pollFd, pollFd_len, -1);
        if(count > 0) {
             printf("have %d ready\n", count);
        }
        if (pollFd[0].revents & POLLIN) {
             connfd = accept(listenfd, (SA*)&clientaddr, &clientlen);
             printf("accept new connection:%d\n", connfd);
             //close(connfd);
        }
    }

    return 1;
}

!未完待续


阅读量: