memcached源码分析-----get命令处理流程



        转载请注明出处:http://blog.csdn.net/luotuo44/article/details/44217383



        本文以get命令为例子,探讨memcached是如何处理命令的。本文只是探讨memcached处理命令的工作流程,具体的代码细节在不影响阅读的前提下能省略的就省略、能取默认值就取默认值、内存是足够的(不需要动态申请空间就够用了)。涉及到数组、缓存区的就假设已经分配好了。


        现在假定memcached里面有了一个键值为”tk”的item,此时我们使用命令”get tk”获取对应item的内容。


       《网络模型》展示了当memcached进程accept一个新客户端连接时,会把该连接的一些信息封装成一个conn结构体,并且把新连接的初始状态设置成conn_new_cmd。此时,worker线程等待客户端命令的到来。conn结构体有很多成员变量,后文只会列出使用到的成员。



读取命令:

等待有数据可读:


        当客户端发送get命令后,memcached的event_base就会监听到客户端对应的socket fd变成可读了,接着就会调用回调函数event_handler处理这个可读事件。实际上回调函数event_handler只是一个傀儡函数,它会调用drive_machine函数进行处理。drive_machine是一个有限状态机,在真正读数据之前它会在几个状态中跳转。

void event_handler(const int fd, const short which, void *arg) {
    conn *c;

    c = (conn *)arg;

    c->which = which;

    /* sanity */
    if (fd != c->sfd) {
        conn_close(c);
        return;
    }

    drive_machine(c);

    /* wait for next event */
    return;
}


struct conn {
    int    sfd;//该conn对应的socket fd
    enum conn_states  state;//当前状态
    struct event event;//该conn对应的event
    short  ev_flags;//event当前监听的事件类型
    short  which;   /** which events were just triggered */ //触发event回调函数的原因

	//读缓冲区
    char   *rbuf;   /** buffer to read commands into */ 
	//有效数据的开始位置。从rbuf到rcurr之间的数据是已经处理的了,变成无效数据了
    char   *rcurr;  /** but if we parsed some already, this is where we stopped */
	//读缓冲区的总长度
    int    rsize;   /** total allocated size of rbuf */
	//有效数据的长度。初始值为0
    int    rbytes;  /** how much data, starting from rcur, do we have unparsed */


	...
   
    LIBEVENT_THREAD *thread;//这个conn属于哪个worker线程
};


static void drive_machine(conn *c) {
    bool stop = false;
    int sfd;
    int nreqs = settings.reqs_per_event;//20
    int res;
    const char *str;

	//drive_machine被调用会进行状态判断,并进行一些处理。但也可能发生状态的转换
	//此时就需要一个循环,当进行状态转换时,也能处理
    while (!stop) {

        switch(c->state) {
			...

        case conn_waiting://等待socket变成可读的
            if (!update_event(c, EV_READ | EV_PERSIST)) {//更新监听事件失败
                conn_set_state(c, conn_closing);
                break;
            }

            conn_set_state(c, conn_read);
			//居然stop循环,不过没关系,因为event的可读事件是水平触发的。
			//马上又会再次进入有限状态机,并且进入下面的conn_read case中。
            stop = true;
            break;

        case conn_new_cmd:

            --nreqs;
            if (nreqs >= 0) {//简单起见,不考虑nreqs小于0的情况
				//如果该conn的读缓冲区没有数据,那么将状态改成conn_waiting
				//如果该conn的读缓冲区有数据,  那么将状态改成conn_pase_cmd
                reset_cmd_handler(c);
            }
            break;

			...
        }
    }

    return;
}


static void reset_cmd_handler(conn *c) {
    c->cmd = -1;
    
	...
	//为了简单,这里假设没有数据
    if (c->rbytes > 0) {//读缓冲区里面有数据 
        conn_set_state(c, conn_parse_cmd);//解析读到的数据
    } else { 
        conn_set_state(c, conn_waiting);//否则等待数据的到来
    }
}

//设置conn的状态
static void conn_set_state(conn *c, enum conn_states state) {
	...

    if (state != c->state) {
        c->state = state;
    }
}


读取数据:

        在前面,conn的状态跳转到了conn_read。在case conn_read中,worker线程会调用try_read_network函数读取客户端发送的数据。try_read_network函数会尽可能地把所有的数据都读进conn的读缓存区中(当然也是有一个最大限度的)。

static void drive_machine(conn *c) {
    bool stop = false;
    int sfd;
    int nreqs = settings.reqs_per_event;//20
    int res;
    const char *str;

	//drive_machine被调用会进行状态判断,并进行一些处理。但也可能发生状态的转换
	//此时就需要一个循环,当进行状态转换时,也能处理
    while (!stop) {

        switch(c->state) {
			...

        case conn_read:
			//这里假定为TCP
            res = IS_UDP(c->transport) ? try_read_udp(c) : try_read_network(c);

            switch (res) {
			…
            case READ_DATA_RECEIVED://读取到了数据,接着就去解析数据
              
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值