关闭

memched1.0源码阅读(4)——事件的处理流程

标签: memched分布式内存存储
247人阅读 评论(0) 收藏 举报
分类:
在讲解事件处理之前,先讲解套接字会话的创建:
我们可以看到在套接字会话创建的过程中,调用了一个叫event_set/event_add的函数,这是libevent的接口,它就把套接字感兴趣的事件(例如读写之类的事件)以及相应的事件回调函数(event_handler)添加到libevent中,这样每当这些事件发生的时候libevent就会调用这个事件回调函数。memched做的所有工作几乎都在event_handler这个函数中。
// 新建一个套接字会话
conn *conn_new(int sfd, int init_state, int event_flags) {
    conn *c;

    /* do we have a free conn structure from a previous close? */
    // 从空闲的connect数组中查找一个conn
    if (freecurr > 0) {
        c = freeconns[--freecurr];
    }
    // 如果在空闲数组中没有找到
    else { /* allocate a new one */
        // 申请内存
        if (!(c = (conn *)malloc(sizeof(conn)))) {
            perror("malloc()");
            return 0;
        }
        // 读写缓存的初始化
        c->rbuf = c->wbuf = 0;
        c->ilist = 0;

        // 创建读写缓存
        c->rbuf = (char *) malloc(DATA_BUFFER_SIZE);
        c->wbuf = (char *) malloc(DATA_BUFFER_SIZE);

        // 创建该连接对应的item数组,该数组的每一个元素都存放了一个item的指针
        c->ilist = (item **) malloc(sizeof(item *)*200);

        if (c->rbuf == 0 || c->wbuf == 0 || c->ilist == 0) {
            if (c->rbuf != 0) free(c->rbuf);
            if (c->wbuf != 0) free(c->wbuf);
            if (c->ilist !=0) free(c->ilist);
            free(c);
            perror("malloc()");
            return 0;
        }
        c->rsize = c->wsize = DATA_BUFFER_SIZE;
        c->isize = 200;
        // 增加系统中连接的计数
        stats.conn_structs++;
    }

    c->sfd = sfd;
    c->state = init_state;
    c->rlbytes = 0;
    c->rbytes = c->wbytes = 0;
    c->wcurr = c->wbuf;
    c->rcurr = c->rbuf;
    c->icurr = c->ilist; 
    c->ileft = 0;
    c->iptr = c->ibuf;
    c->ibytes = 0;

    // 下一个准备进行的操作?
    c->write_and_go = conn_read;
    c->write_and_free = 0;
    c->item = 0;

    // 设置对应的事件处理器,事件处理的回调函数是event_handler,这个函数会调用核心函数drive_machine
    // drive_machine中处理各种事件(accpet,read、write)
    event_set(&c->event, sfd, event_flags, event_handler, (void *)c);
    c->ev_flags = event_flags;

    // 把它的事件处理器添加到libevent中
    if (event_add(&c->event, 0) == -1) {
        free(c);
        return 0;
    }

    stats.curr_conns++;
    stats.total_conns++;

    return c;
}
事件的处理流程——核心函数drive_machine(在event_handler 中被调用) :进入一个大循环中,根据套接字会话的状态进行不同的处理:
1、如果是conn_listening状态。表示套接字会话要处理客户的连接,那么调用accpet函数,接收一个套接字,然后把套接字设置为非阻塞,然后根据这个套接字创建套接字会话(conn_new函数),并同时把该套接字感兴趣事件以及相应的事件回调函数添加到libevent中。
2、如果是conn_read状态。表示套接字准备处理读事件,那么调用try_read_command读取客户发送过来的命令,然后调用try_read_network读取客户发送过来数据块,接着调用update_event更新套接字感兴趣的事件。
3、如果是conn_nread状态。表示套接字准备接收指定长度的数据,如果还没有接收完成,那么就继续接收。如果接收完成,那么调用complete_nread函数,这个函数主要的作用就是往memched添加或者更新一个对象。
4、如果是conn_swallow状态。在某些情况下,memched已经满了(内存不足),那么对于客户端发送过来的数据进行读取然后忽略掉。
5、如果是conn_write状态。表示套接字准备向客户发送数据。
6、如果是conn_mwrite状态。同上,不过情况稍微复杂点。
7、如果是conn_closing状态。表示客户端关闭了。
// 核心函数
void drive_machine(conn *c) {

    int exit = 0;
    int sfd, flags = 1;
    socklen_t addrlen;
    struct sockaddr addr;
    conn *newc;
    int res;

    while (!exit) {
      /*printf("state %d\n", c->state); */
        switch(c->state) {
        // 如果是监听事件
        case conn_listening:
            addrlen = sizeof(addr);
            // accpet一个套接字
            if ((sfd = accept(c->sfd, &addr, &addrlen)) == -1) {
                if (errno == EAGAIN || errno == EWOULDBLOCK) {
                    perror("accept() shouldn't block");
                } else {
                    perror("accept()");
                }
                return;
            }
            // 设置套接字为非阻塞
            if ((flags = fcntl(sfd, F_GETFL, 0)) < 0 ||
                fcntl(sfd, F_SETFL, flags | O_NONBLOCK) < 0) {
                perror("setting O_NONBLOCK");
                close(sfd);
                return;
            }
            // 根据套接字以及相关信息创建会话对象
            newc = conn_new(sfd, conn_read, EV_READ | EV_PERSIST);
            if (!newc) {
                if(settings.verbose)
                    fprintf(stderr, "couldn't create new connection\n");
                close(sfd);
                return;
            }
            exit = 1;
            break;

        // 如果是读事件
        case conn_read:
            // 读取命令
            if (try_read_command(c)) {
                continue;
            }
            // 读取数据
            if (try_read_network(c)) {
                continue;
            }
            /* we have no command line and no data to read from network */
            // 更新事件
            if (!update_event(c, EV_READ | EV_PERSIST)) {
                if(settings.verbose)
                    fprintf(stderr, "Couldn't update event\n");
                c->state = conn_closing;
                break;
            }
            exit = 1;
            break;

        case conn_nread:
            /* we are reading rlbytes into rcurr; */
            // 读取指定数量的字节,rlbytes存放着还要读取的字节的数量
            // 如果rlbytes等于0,那么表示已经读取完毕
            if (c->rlbytes == 0) {
                // 进行处理
                complete_nread(c);
                break;
            }
            /* first check if we have leftovers in the conn_read buffer */
            // 如果读取的字节数量大于0
            // 这里没看懂,为什么要把rbuf指向的数据复制复制到rcurr所指的位置(关键是它们指向同一个缓冲区)?
            if (c->rbytes > 0) {
                int tocopy = c->rbytes > c->rlbytes ? c->rlbytes : c->rbytes;
                // 那么把读取到的数据复制到读缓存中
                memcpy(c->rcurr, c->rbuf, tocopy);
                c->rcurr += tocopy;
                c->rlbytes -= tocopy;
                if (c->rbytes > tocopy) {
                    memmove(c->rbuf, c->rbuf+tocopy, c->rbytes - tocopy);
                }
                c->rbytes -= tocopy;
                break;
            }

            /*  now try reading from the socket */
            // 尝试读取数据(非阻塞的读)
            res = read(c->sfd, c->rcurr, c->rlbytes);
            // 成功读取到数据
            if (res > 0) {
                stats.bytes_read += res;
                c->rcurr += res;
                c->rlbytes -= res;
                break;
            }
            // 返回0,表示对方关闭了连接
            if (res == 0) { /* end of stream */
                c->state = conn_closing;
                break;
            }
            // 出错,但是出错码是EAGAIN或者EWOULDBLOCK,表示系统正在等待数据的到来
            if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
                if (!update_event(c, EV_READ | EV_PERSIST)) {
                    if(settings.verbose) 
                        fprintf(stderr, "Couldn't update event\n");
                    c->state = conn_closing;
                    break;
                }
                exit = 1;
                break;
            }
            /* otherwise we have a real error, on which we close the connection */
            if(settings.verbose)
                fprintf(stderr, "Failed to read, and not due to blocking\n");
            c->state = conn_closing;
            break;

        // conn_swallow状态是当内存不足并且调用add/set/replace命令的时候才会出现, 此操作主要是读取客户端发送过来的数据, 并且忽略掉
        case conn_swallow:
            /* we are reading sbytes and throwing them away */
            if (c->sbytes == 0) {
                c->state = conn_read;
                break;
            }

            /* first check if we have leftovers in the conn_read buffer */
            if (c->rbytes > 0) {
                int tocopy = c->rbytes > c->sbytes ? c->sbytes : c->rbytes;
                c->sbytes -= tocopy;
                if (c->rbytes > tocopy) {
                    memmove(c->rbuf, c->rbuf+tocopy, c->rbytes - tocopy);
                }
                c->rbytes -= tocopy;
                break;
            }

            /*  now try reading from the socket */
            res = read(c->sfd, c->rbuf, c->rsize > c->sbytes ? c->sbytes : c->rsize);
            if (res > 0) {
                stats.bytes_read += res;
                c->sbytes -= res;
                break;
            }
            if (res == 0) { /* end of stream */
                c->state = conn_closing;
                break;
            }
            if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
                if (!update_event(c, EV_READ | EV_PERSIST)) {
                    if(settings.verbose)
                        fprintf(stderr, "Couldn't update event\n");
                    c->state = conn_closing;
                    break;
                }
                exit = 1;
                break;
            }
            /* otherwise we have a real error, on which we close the connection */
            if(settings.verbose)
                fprintf(stderr, "Failed to read, and not due to blocking\n");
            c->state = conn_closing;
            break;

        // 写
        case conn_write:
            /* we are writing wbytes bytes starting from wcurr */
            if (c->wbytes == 0) {
                if (c->write_and_free) {
                    free(c->write_and_free);
                    c->write_and_free = 0;
                }
                c->state = c->write_and_go;
                break;
            }
            // 非阻塞写
            res = write(c->sfd, c->wcurr, c->wbytes);
            if (res > 0) {
                stats.bytes_written += res;
                c->wcurr  += res;
                c->wbytes -= res;
                break;
            }
            if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
                if (!update_event(c, EV_WRITE | EV_PERSIST)) {
                    if(settings.verbose)
                        fprintf(stderr, "Couldn't update event\n");
                    c->state = conn_closing;
                    break;
                }                
                exit = 1;
                break;
            }
            /* if res==0 or res==-1 and error is not EAGAIN or EWOULDBLOCK,
               we have a real error, on which we close the connection */
            if(settings.verbose)
                fprintf(stderr, "Failed to write, and not due to blocking\n");
            c->state = conn_closing;
            break;
        // 发送(写)指定长度的字节
        case conn_mwrite:
            /* 
             * we're writing ibytes bytes from iptr. iptr alternates between
             * ibuf, where we build a string "VALUE...", and it->data for the 
             * current item. When we finish a chunk, we choose the next one using 
             * ipart, which has the following semantics: 0 - start the loop, 1 - 
             * we finished ibuf, go to current it->data; 2 - we finished it->data,
             * move to the next item and build its ibuf; 3 - we finished all items, 
             * write "END".
             */
            if (c->ibytes > 0) {
                res = write(c->sfd, c->iptr, c->ibytes);
                if (res > 0) {
                    stats.bytes_written += res;
                    c->iptr += res;
                    c->ibytes -= res;
                    break;
                }
                if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
                    if (!update_event(c, EV_WRITE | EV_PERSIST)) {
                        if(settings.verbose)
                            fprintf(stderr, "Couldn't update event\n");
                        c->state = conn_closing;
                        break;
                    }
                    exit = 1;
                    break;
                }
                /* if res==0 or res==-1 and error is not EAGAIN or EWOULDBLOCK,
                   we have a real error, on which we close the connection */
                if(settings.verbose)
                    fprintf(stderr, "Failed to write, and not due to blocking\n");
                c->state = conn_closing;
                break;
            } else {
                item *it;
                /* we finished a chunk, decide what to do next */
                switch (c->ipart) {
                case 1:
                    it = *(c->icurr);
                    c->iptr = it->data;
                    c->ibytes = it->nbytes;
                    c->ipart = 2;
                    break;
                case 2:
                    it = *(c->icurr);
                    item_remove(it);
                    if (c->ileft <= 1) {
                        c->ipart = 3;
                        break;
                    } else {
                        c->ileft--;
                        c->icurr++;
                    }
                    /* FALL THROUGH */
                case 0:
                    it = *(c->icurr);
                    sprintf(c->ibuf, "VALUE %s %u %u\r\n", it->key, it->flags, it->nbytes - 2);
                    c->iptr = c->ibuf;
                    c->ibytes = strlen(c->iptr);
                    c->ipart = 1;
                    break;
                case 3:
                    out_string(c, "END");
                    break;
                }
            }
            break;

        case conn_closing: // 关闭套接字
            conn_close(c);
            exit = 1;
            break;
        }

    }

    return;
}


0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:219197次
    • 积分:5468
    • 等级:
    • 排名:第5167名
    • 原创:320篇
    • 转载:30篇
    • 译文:0篇
    • 评论:50条