UNP I/O复用模型-EPOLL函数

I/O复用

在UNP第五章中介绍了回射服务器(多进程服务器版)的一种特殊情况:在TCP客户同时处理两个输入文件,标准输入stdin和TCP套接字TCP时,可能会由于阻塞发生错误。例如,当客户阻塞于标准输入的fgets调用期间,服务器子进程可能会被杀死(通过lsof -i:端口号查看服务器子进程pid,然后kill pid杀死进程)。在这种情况下,服务器子进程被kill之后虽然可以正确地给客户TCP发送一个FIN,但是由于客户端正在同时处理两个输入,且阻塞于其中的stdin上,它将无法读到这个EOF标志(通过FIN传送过来的),直到客户输入完成并开始读取套接字为止。因此这样的进程需要一种能力,即是能够正确处理多个套接字的能力:既能接收到服务器TCP发送来的FIN,又能及时处理客户端的输入。这个能力称为I/O复用。

select和poll的不足

有了I/O复用的概念,我们可以调用select和poll来实现I/O复用模型。调用select或epoll函数,可以使得进程阻塞与这两个系统调用之上,而这两个系统调用通过对多个套接字的监听实现同时等待多个套接字。select的套接字等待集合为fd_set,poll的套接字等待集合为pollfd结构体。但是由于select和poll的套接字等待集合的容量太小,一般是1024,使得进程不能同时处理大规模I/O请求,因此引入epoll。

epoll

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是unix下I/O复用select/poll的增强版本。select和poll都只提供了一个函数——select或者poll函数。而epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait。epoll_create是创建一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生。

创建epoll句柄

#include <sys/epoll.h>
int epoll_create(int size);

参数size用来设置epoll将要监听的描述符的个数。注意,这个size参数和select函数的第一个参数maxfdp1有所不同:maxfdp1定义待测试的最大描述符+1,比如select已监听描述1、4、5,此时maxfdp1应该为6,然而select函数同时能够监听的描述符个数为3(1、4、5);但是size是硬性的数量,若size=6,则意味epoll通知了内核有6个描述符正要被监听。
需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

epoll事件注册

int epoll_ctl(int epfd, int op, int fd, struct poll_event *event);

epfd为epoll_create创建的句柄,op为动作,fd为需要监听的fd,
event高速内核需要监听什么事件;而select是在监听事件时告诉内核要监听什么类型的事件,而是先注册要监听事件的类型。
op动作用三个宏来表示:

  1. EPOLL_CTL_ADD:注册新的fd到epfd中
  2. EPOLL_CTL_MOD:修改已经注册的fd监听事件(通过event)
  3. EPOLL_CTL_DEL:从epfd中删除一个fd;
typedef union epoll_data{
    void *ptr;
    int fd;
    _uint32_t u32;
    _uint64_t u64;
}epoll_data_t;

struct epoll_event {
    __uint32_t events; // Epoll事件
    epoll_data_t data; // 用户数据
};

第四个参数events可以是以下几个宏的集合:

  • EPOLLIN:表示对应文件描述符可读(包括对端socket正常关闭)
  • EPOLLOUT:表示对应文件描述符可写
  • EPOLLPRI:表示对应文件描述符有紧急数据可读
  • EPOLLERR:表示对应文件描述符发生错误
  • EPOLLHUP:表示对应文件描述符被挂断
  • EPOLLET:将EPOLL设为边缘触发(Edege Triggered)模式
  • EPOLLONESHOT:只监听一次事件:如果还想监听需要重新加入EPOLL队列

等待事件的产生

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

等待事件的产生,类似于调用select函数。参数events用来从内核得到事件的集合,maxevents告知内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间。一般通过给定timeout值,来限制监听事件的最长等待事件。若timeout=0,则事件发生便立即返回。

Epoll事件有两种模型:

  • Edge Triggered(ET)
  • Level Triggered(LT)

epoll服务器端示例

#include "my_unp.h"
#include "unp_base.c"
int main(void)
{
    int listenfd, connfd, sockfd, epfd;
    int i, maxi, nfds;
    ssize_t n;
    char buf[MAXLINE];
    socklen_t clilen;
    struct sockaddr_in cliaddr;
    struct sockaddr_in servaddr;

    //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
    struct epoll_event ev, events[256];

    //创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大
    epfd = Epoll_create(256);

    //创建用于TCP协议的套接字
    listenfd = Socket(AF_INET, SOCK_STREAM, 0);
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    //把socket和socket地址结构联系起来
    Bind(listenfd, (SA*)&servaddr, sizeof(servaddr));
    //开始监听LISTENQ端口
    Listen(listenfd, LISTENQ);

    //设置与要处理的事件相关的文件描述符和事件
    ev.data.fd = listenfd;
    ev.events = EPOLLIN|EPOLLET;

    //注册epoll事件
    Epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd,&ev);
    maxi = 0;

    while(1)
    {
        //等待epoll事件的发生
        //返回需要处理的事件数目nfds,如返回0表示已超时。
        nfds = Epoll_wait(epfd, events, 20, 500);

        //处理所发生的所有事件
        for(i=0; i < nfds; ++i)
        {
            //如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。
            if(events[i].data.fd == listenfd)
            {
                connfd = Accept(listenfd,(SA*)&cliaddr, &clilen);
                printf("connection from %s, port %d.\n",
                       Inet_ntop(AF_INET, (void*)&cliaddr.sin_addr, buf, sizeof(buf)),
                       ntohs(cliaddr.sin_port));

                //设置用于读操作的文件描述符和事件
                ev.data.fd = connfd;
                ev.events = EPOLLIN|EPOLLET;

                //注册事件
                Epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
            }

            //如果是已经连接的用户,并且收到数据,那么进行读入。
            else if(events[i].events & EPOLLIN)
            {
                sockfd = events[i].data.fd;
                if ( sockfd < 0 )
                    continue;
                n = read(sockfd, buf, MAXLINE);
                if ( n < 0)
                {
                    // Connection Reset:你连接的那一端已经断开了,
                    //而你却还试着在对方已断开的socketfd上读写数据!
                    if (errno == ECONNRESET)
                    {
                        Close(sockfd);
                        events[i].data.fd = -1;
                    }
                    else
                        error_quit("read error");
                }
                //如果读入的数据为空
                else if ( n == 0 )
                {
                    Close(sockfd);
                    events[i].data.fd = -1;
                }
                else
                {
                    //设置用于写操作的文件描述符和事件
                    ev.data.fd = sockfd;
                    ev.events = EPOLLOUT|EPOLLET;
                    //注册事件
                    Epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
                }
            }

            //如果有数据发送
            else if(events[i].events & EPOLLOUT)
            {
                sockfd = events[i].data.fd;
                Writen(sockfd, buf, n);

                //设置用于读操作的文件描述符和事件
                ev.data.fd = sockfd;
                ev.events = EPOLLIN|EPOLLET;

                //注册事件
                Epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
            }    
        }    
    }    
    return 0;      
}

头文件my_unp.h

#ifndef MY_UNP_H_
#define MY_UNP_H_

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/epoll.h>
#include <sys/poll.h>
#include <sys/file.h>
#include <sys/mman.h>

#define MAXLINE 1024
#define LISTENQ 1024

#define MAXNITEMS 1000000
#define MAXNTHREADS 100

#define SERV_PORT 9877
#define SERV_PORT_STR "9877"

#define SA struct sockaddr
typedef void Sigfunc(int);

#define min(a,b)    ((a) < (b) ? (a) : (b))
#define max(a,b)    ((a) > (b) ? (a) : (b))


//错误处理函数,输出错误信息后退出程序
void error_quit(char *fmt, ...);

//为了适应网络的慢速IO而编写的读写函数
ssize_t readn(int fd, void *vptr, size_t n);
ssize_t writen(int fd, const void *vptr, size_t n);
ssize_t readline(int fd, void *vptr, size_t maxlen);

//各类读写包裹函数
void Write(int fd, void *ptr, size_t nbytes);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Readn(int fd, void *ptr, size_t nbytes);
void Writen(int fd, void *ptr, size_t nbytes);
ssize_t Readline(int fd, void *ptr, size_t maxlen);
void Fputs(const char *ptr, FILE *stream);
char *Fgets(char *ptr, int n, FILE *stream);

//各类标准包裹函数
int Open(const char *pathname, int flags, mode_t mode);
void Close(int fd);
Sigfunc *Signal(int signo, Sigfunc *func);
void *Malloc(size_t size);
void *Calloc(size_t n, size_t size);
void Pipe(int *fds);
pid_t Fork(void);
pid_t Waitpid(pid_t pid, int *iptr, int options);
void Dup2(int fd1, int fd2);

//各类网络包裹函数
int Socket(int family, int type, int protocol);
void Inet_pton(int family, const char *strptr, void *addrptr);
void Connect(int fd, const struct sockaddr *sa, socklen_t salen);
void Listen(int fd, int backlog);
void Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
const char *Inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
int Select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);
int Poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
void Shutdown(int fd, int how);
int Epoll_create(int size);
void Epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int Epoll_wait(int epfd, struct epoll_event *events,
               int maxevents, int timeout);
void Sendto(int fd, const void *ptr, size_t nbytes, int flags,
            const struct sockaddr *sa, socklen_t salen);
ssize_t Recvfrom(int fd, void *ptr, size_t nbytes, int flags,
                 struct sockaddr *sa, socklen_t *salenptr);
void Setsockopt(int fd, int level, int optname,
                const void *optval, socklen_t optlen);
void *Mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
void Munmap(void *addr, size_t len);
void Ftruncate(int fd, off_t length);

//各类和线程操作相关的包裹函数
void Pthread_create(pthread_t *tid, const pthread_attr_t *attr,
                    void * (*func)(void *), void *arg);
void Pthread_detach(pthread_t tid);
void Pthread_join(pthread_t tid, void **status);
void Pthread_kill(pthread_t tid, int signo);
void Pthread_mutex_lock(pthread_mutex_t *mptr);
void Pthread_mutex_unlock(pthread_mutex_t *mptr);
//此函数相当于UNP书上的set_concurrency函数
void Pthread_setconcurrency(int level);
void Pthread_cond_signal(pthread_cond_t *cptr);
void Pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr);

//各类和信号量相关的包裹函数
sem_t *Sem_open(const char *name, int oflag,
                mode_t mode, unsigned int value);
void Sem_close(sem_t *sem);
void Sem_unlink(const char *pathname);
void Sem_init(sem_t *sem, int pshared, unsigned int value);
void Sem_destroy(sem_t *sem);
void Sem_wait(sem_t *sem);
void Sem_post(sem_t *sem);
void Sem_getvalue(sem_t *sem, int *valp);

#endif

包裹函数unp_base.c

#include "my_unp.h"

//此函数是在程序发生错误时被调用
//先输出字符串fmt,再根据errno输出错误原因(如果有的话),最后退出程序
//注:在多线程程序中,错误原因可能不正确
void error_quit(char *fmt, ...)
{
    int res;
    va_list list;
    va_start(list, fmt);
    res = vfprintf(stderr, fmt, list);
    if( errno != 0 )
        fprintf(stderr, " : %s", strerror(errno));
    fprintf(stderr, "\n", list);
    va_end(list);
    exit(1);
}

//字节流套接字上调用read时,输入的字节数可能比请求的数量少,
//但这不是出错的状态,原因是内核中用于套接字的缓冲区可能已经达到了极限,
//此时需要调用者再次调用read函数
ssize_t readn(int fd, void *vptr, size_t n)
{
    size_t  nleft;
    ssize_t nread;
    char    *ptr;

    ptr = vptr;
    nleft = n;
    while (nleft > 0)
    {
        if ( (nread = read(fd, ptr, nleft)) < 0)
        {
            if (errno == EINTR)
                nread = 0;      /* and call read() again */
            else
                return(-1);
        }
        else if (nread == 0)
            break;              /* EOF */
        nleft -= nread;
        ptr   += nread;
    }
    return (n - nleft);     /* return >= 0 */
}

//字节流套接字上调用write时,输出的字节数可能比请求的数量少,
//但这不是出错的状态,原因是内核中用于套接字的缓冲区可能已经达到了极限,
//此时需要调用者再次调用write函数
ssize_t writen(int fd, const void *vptr, size_t n)
{
    size_t      nleft;
    ssize_t     nwritten;
    const char  *ptr;

    ptr = vptr;
    nleft = n;
    while (nleft > 0)
    {
        if ( (nwritten = write(fd, ptr, nleft)) <= 0)
        {
            if (nwritten < 0 && errno == EINTR)
                nwritten = 0;       /* and call write() again */
            else
                return(-1);         /* error */
        }
        nleft -= nwritten;
        ptr   += nwritten;
    }
    return n;
}

static int  read_cnt;
static char *read_ptr;
static char read_buf[MAXLINE];

//内部函数my_read每次最多读MAXLINE个字节,然后每次返回一个字节
static ssize_t my_read(int fd, char *ptr)
{
    if (read_cnt <= 0)
    {
    again:
        if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0)
        {
            if (errno == EINTR)
                goto again;
            return(-1);
        }
        else if (read_cnt == 0)
            return(0);
        read_ptr = read_buf;
    }

    read_cnt--;
    *ptr = *read_ptr++;
    return 1;
}

//从描述符中读取文本行
ssize_t readline(int fd, void *vptr, size_t maxlen)
{
    ssize_t n, rc;
    char    c, *ptr;

    ptr = vptr;
    for (n = 1; n < maxlen; n++)
    {
        if ( (rc = my_read(fd, &c)) == 1)
        {
            *ptr++ = c;
            if (c == '\n')
                break;  /* newline is stored, like fgets() */
        }
        else if (rc == 0)
        {
            *ptr = 0;
            return(n - 1);  /* EOF, n - 1 bytes were read */
        }
        else
            return(-1);     /* error, errno set by read() */
    }
    *ptr = 0;   /* null terminate like fgets() */
    return n;
}

ssize_t Readn(int fd, void *ptr, size_t nbytes)
{
    ssize_t n = readn(fd, ptr, nbytes);
    if ( n < 0)
        error_quit("readn error");
    return n;
}

void Writen(int fd, void *ptr, size_t nbytes)
{
    if ( writen(fd, ptr, nbytes) != nbytes )
        error_quit("writen error");
}

ssize_t Readline(int fd, void *ptr, size_t maxlen)
{
    ssize_t n = readline(fd, ptr, maxlen);
    if ( n < 0)
        error_quit("readline error");
    return n;
}

ssize_t Read(int fd, void *ptr, size_t nbytes)
{
    ssize_t n = read(fd, ptr, nbytes);
    if ( n == -1)
        error_quit("read error");
    return n;
}

void Write(int fd, void *ptr, size_t nbytes)
{
    if (write(fd, ptr, nbytes) != nbytes)
        error_quit("write error");
}

int Open(const char *pathname, int flags, mode_t mode)
{
    int fd = open(pathname, flags, mode);
    if( -1 == fd )
        error_quit("open file %s error", pathname);
    return fd;
}

void Close(int fd)
{
    if (close(fd) == -1)
        error_quit("close error");
}

void Fputs(const char *ptr, FILE *stream)
{
    if (fputs(ptr, stream) == EOF)
        error_quit("fputs error");
}

char *Fgets(char *ptr, int n, FILE *stream)
{
    char *rptr = fgets(ptr, n, stream);
    if ( rptr == NULL && ferror(stream) )
        error_quit("fgets error");
    return rptr;
}

int Socket(int family, int type, int protocol)
{
    int n = socket(family, type, protocol);
    if( n < 0)
        error_quit("socket error");
    return n;
}

void Inet_pton(int family, const char *strptr, void *addrptr)
{
    int n = inet_pton(family, strptr, addrptr);
    if( n < 0)
        error_quit("inet_pton error for %s", strptr);
}

void Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
    if (connect(fd, sa, salen) < 0)
        error_quit("connect error");
}

void Listen(int fd, int backlog)
{
    if (listen(fd, backlog) < 0)
        error_quit("listen error");
}

void Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
    if (bind(fd, sa, salen) < 0)
        error_quit("bind error");
}

int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
    int n = accept(fd, sa, salenptr);
    if ( n < 0)
        error_quit("accept error");
    return n;
}

const char *Inet_ntop(int family, const void *addrptr, char *strptr, size_t len)
{
    const char  *ptr = inet_ntop(family, addrptr, strptr, len);
    if ( ptr == NULL)
        error_quit("inet_ntop error");
    return ptr;
}

pid_t Fork(void)
{
    pid_t pid = fork();
    if ( pid == -1)
        error_quit("fork error");
    return pid;
}

Sigfunc *Signal(int signo, Sigfunc *func)
{
    Sigfunc *sigfunc = signal(signo, func);
    if ( sigfunc == SIG_ERR)
        error_quit("signal error");
    return sigfunc;
}

int Select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout)
{
    int n = select(nfds, readfds, writefds, exceptfds, timeout);
    if ( n < 0 )
        error_quit("select error");
    return n;       /* can return 0 on timeout */
}

int Poll(struct pollfd *fdarray, unsigned long nfds, int timeout)
{
    int n = poll(fdarray, nfds, timeout);
    if ( n < 0 )
        error_quit("poll error");
    return n;
}

void Shutdown(int fd, int how)
{
    if (shutdown(fd, how) < 0)
        error_quit("shutdown error");
}

int Epoll_create(int size)
{
    int n = epoll_create(size);
    if( n < 0 )
        error_quit("epoll create error");
    return n;
}

void Epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
{
    if( epoll_ctl(epfd, op, fd, event) < 0 )
        error_quit("epoll ctl error");
}

int Epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
{
    int n = epoll_wait(epfd, events, maxevents, timeout);
    if( n < 0 )
        error_quit("epoll wait error");
    return n;
}

void Sendto(int fd, const void *ptr, size_t nbytes, int flags,
            const struct sockaddr *sa, socklen_t salen)
{
    if (sendto(fd, ptr, nbytes, flags, sa, salen) != (ssize_t)nbytes)
        error_quit("sendto error");
}

ssize_t Recvfrom(int fd, void *ptr, size_t nbytes, int flags,
                 struct sockaddr *sa, socklen_t *salenptr)
{
    ssize_t n = recvfrom(fd, ptr, nbytes, flags, sa, salenptr);
    if ( n < 0 )
        error_quit("recvfrom error");
    return n;
}

ssize_t Recvmsg(int fd, struct msghdr *msg, int flags)
{
    ssize_t n = recvmsg(fd, msg, flags);
    if ( n < 0 )
        error_quit("recvmsg error");
    return(n);
}

void *Malloc(size_t size)
{
    void *ptr = malloc(size);
    if ( ptr == NULL )
        error_quit("malloc error");
    return ptr;
}

void *Calloc(size_t n, size_t size)
{
    void *ptr = calloc(n, size);
    if ( ptr == NULL)
        error_quit("calloc error");
    return ptr;
}

void Pipe(int *fds)
{
    if ( pipe(fds) < 0 )
        error_quit("pipe error");
}

pid_t Waitpid(pid_t pid, int *iptr, int options)
{
    pid_t   retpid = waitpid(pid, iptr, options);
    if ( retpid == -1)
        error_quit("waitpid error");
    return retpid;
}

void Setsockopt(int fd, int level, int optname,
                const void *optval, socklen_t optlen)
{
    if (setsockopt(fd, level, optname, optval, optlen) < 0)
        error_quit("setsockopt error");
}

void Socketpair(int family, int type, int protocol, int *fd)
{
    int n = socketpair(family, type, protocol, fd);
    if ( n < 0 )
        error_quit("socketpair error");
}

void Dup2(int fd1, int fd2)
{
    if (dup2(fd1, fd2) == -1)
        error_quit("dup2 error");
}

void *Mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset)
{
    void *ptr = mmap(addr, len, prot, flags, fd, offset);
    if ( ptr == MAP_FAILED )
        error_quit("mmap error");
    return ptr;
}

void Munmap(void *addr, size_t len)
{
    if (munmap(addr, len) == -1)
        error_quit("munmap error");
}

void Ftruncate(int fd, off_t length)
{
    if (ftruncate(fd, length) == -1)
        error_quit("ftruncate error");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值