beanstalkd源码分析--事件处理机制

概述

本文接着讲述beanstalkd的事件处理机制,这其实也是大多数服务器要实现的步骤。我们且看beanstalkd是如何实现的。

源码分析

接受客户端连接

在主服务函数:void srvserve(Server *s)中初始化时,会把sock.f设置成srvaccept。
该函数主要是用来接受来自客户端的连接请求。

void
srvserve(Server *s)
{
    ... 
    s->sock.f = (Handle)srvaccept;
    ...
}

接受客户端连接请求的服务函数定义如下:

void
srvaccept(Server *s, int ev)
{
    h_accept(s->sock.fd, ev, s);
}

h_accept函数的实现

通过对该函数的实现分析,我们可以看出一些可以优化的点。

void
h_accept(const int fd, const short which, Server *s)
{
    Conn *c;
    int cfd, flags, r;
    socklen_t addrlen;
    struct sockaddr_in6 addr;

    addrlen = sizeof addr;
    //接受客户端的连接请求
    cfd = accept(fd, (struct sockaddr *)&addr, &addrlen); 
    if (cfd == -1) {
        if (errno != EAGAIN && errno != EWOULDBLOCK) twarn("accept()");
        update_conns();
        return;
    }
    if (verbose) {
        printf("accept %d\n", cfd);
    }

    // 完成三次握手,建立了一个新的连接,并得到一个新的fd:cfd
    // 需要把该cfd设置成非阻塞
    flags = fcntl(cfd, F_GETFL, 0); 
    if (flags < 0) {
        twarn("getting flags");
        close(cfd);
        if (verbose) {
            printf("close %d\n", cfd);
        }
        update_conns();
        return;
    }

    r = fcntl(cfd, F_SETFL, flags | O_NONBLOCK); // 设置非阻塞
    if (r < 0) {
        twarn("setting O_NONBLOCK");
        close(cfd);
        if (verbose) {
            printf("close %d\n", cfd);
        }
        update_conns();
        return;
    }

    // 为已经连接成功的客户端,创建一个新的连接Conn结构实体,并进行初始化
    // 注:这里可以优化,做缓存,把没有使用的Conn实体缓存下来,下一次进行复用,但可能浪费一些内存。
    c = make_conn(cfd, STATE_WANTCOMMAND, default_tube, default_tube);
    if (!c) {    // 若创建Conn实体失败,关闭该连接
        twarnx("make_conn() failed");
        close(cfd);
        if (verbose) {
            printf("close %d\n", cfd);
        }
        update_conns();
        return;
    }
    // 为新创建的Conn实体,填写参数
    c->srv = s;    // 反向指针,指向该连接所在的Server结构实体。
    c->sock.x = c;    // sock.x指向本Conn实体的指针
    c->sock.f = (Handle)prothandle;   // 设置回调函数,在该函数中处理客户端的请求
    c->sock.fd = cfd;    // 设置sock.fd描述符的值为cfd

    r = sockwant(&c->sock, 'r');  // 把sock添加到读监控队列,接受客户端发送消息。
    if (r == -1) {  // 若失败,关闭和客户端的连接
        twarn("sockwant");
        close(cfd);
        if (verbose) {
            printf("close %d\n", cfd);
        }
        update_conns();
        return;
    }
    update_conns();
} 

事件驱动的实现

beanstalkd和redis一样使用epoll来做事件驱动框架,其实现如下:

int
sockinit(void)
{
    epfd = epoll_create(1);    //创建epoll文件系统句柄
    if (epfd == -1) {
        twarn("epoll_create");
        return -1;
    }
    return 0;
}


int
sockwant(Socket *s, int rw)
{
    int op;
    struct epoll_event ev = {};

    if (!s->added && !rw) {
        return 0;
    } else if (!s->added && rw) {   // 若s->added字段为0,但rw变量为1,则需要添加
        s->added = 1;
        op = EPOLL_CTL_ADD;
    } else if (!rw) {
        op = EPOLL_CTL_DEL;
    } else {
        op = EPOLL_CTL_MOD;
    }

    switch (rw) {
    case 'r':    //读事件
        ev.events = EPOLLIN;
        break;
    case 'w':  // 写事件
        ev.events = EPOLLOUT;
        break;
    }
    ev.events |= EPOLLRDHUP | EPOLLPRI;
    ev.data.ptr = s;

    return epoll_ctl(epfd, op, s->fd, &ev);
}


int
socknext(Socket **s, int64 timeout)
{
    int r;
    struct epoll_event ev;

    r = epoll_wait(epfd, &ev, 1, (int)(timeout/1000000));
    if (r == -1 && errno != EINTR) {
        twarn("epoll_wait");
        exit(1);
    }

    if (r > 0) {
        *s = ev.data.ptr;
        if (ev.events & (EPOLLHUP|EPOLLRDHUP)) {  // 发生错误
            return 'h';
        } else if (ev.events & EPOLLIN) {  // 读事件
            return 'r';
        } else if (ev.events & EPOLLOUT) { // 写事件
            return 'w';
        }
    }
    return 0;
}

总结

通过本文的分析,应该对beanstalkd对客户端的请求处理过程有一个比较清楚的认识。基本框架有了,接下来就是对其客户端和服务器端的协议进行实现。

猜你喜欢

转载自blog.csdn.net/zg_hover/article/details/81295112