Memcached--set 过程跟踪解析

看完memcahed好几天了,急需进行总结一番.

//set key 0 0 5 hello 

下面从主函数开始

 thread_init(settings.num_threads, main_base);//初始化事件线程,并进行循环的运行

Setup_thread(&threads[i]);//创建线程事件

thread_libevent_process//当管道有数据来时进行唤醒调用

{

....

  if (read(fd, buf, 1) != 1) 此处为阻塞的地方

.....

}

server_socket_unix(settings.socketpath,settings.access))//设置SOCKET进行监听

sfd = new_socket_unix()//创建一个sfd

Bind();

Listen();

conn_new(sfd,conn_listening,....,main_base)//创建对该sfd的监听事件

{

conn *c = conn_from_freelist();               //从空的连接队列中拿出一个,如果没有则重新申请一个conn空间

event_set(&c->event, sfd, event_flags, event_handler, (void *)c);//加入事件进行监听

  event_handler(const int fd, const short which, void *arg)//主要调用drive_machine()

drive_machine(c);//初始化时c->stateconn_listening

{

switch(c->state)

{

case conn_listening:

sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen)//这里一直等待客户端的连接

}

}

}

此时客户端进行连接Memcached telnet localhost 11211

 

drive_machine(c)中的sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen)被唤醒。

开始分发该sfd到线程事件中dispatch_conn_new(sfd,conn_new_cmd,.....)

//接收到一个连接时init stateconn_new_cmd,sfd为客户端连接fd

{

 CQ_ITEM *item = cqi_new();                 //cqi_freelist中取出一个cqi,如果没有则分配64CQ_ITEM在从其中拿.

int tid = (last_thread + 1) % settings.num_threads;     

//轮询选择线程事件

cq_push(thread->new_conn_queue, item);         //将该item加入到线程连接队列中

write(thread->notify_send_fd, "", 1)//向该线程事件所属管道中发送一字节表示有连接进来,唤醒该线程事件thread_libevent_process函数进行处理.

}

thread_libevent_process//当管道有数据来时进行唤醒调用

{

....

if (read(fd, buf, 1) != 1) 停止阻塞,读取一个字节数据

item = cq_pop(me->new_conn_queue); 

//从连接队列中取出一个item

conn *c = conn_new(item->sfd,item->init_state,item->event_flags,.... me->base)

//创建连接,并将该sfd 加入到该线程事件的base中进行监听处理.此时的item->init_stateconn_new_cmd

{

conn *c = conn_from_freelist(); //从空的连接队列中拿出一个,如果没有则重新申请一个conn空间  

fprintf(stderr, "<%d new ascii client connection.\n", sfd);//在服务器打印客户端连接信息

 event_set(...,sfd...,event_handler,(void *)c)

event_handler(const int fd, const short which, void *arg)

//主要调用drive_machine()

drive_machine(c)

//此时c->stateconn_new_cmd

case conn_new_cmd:

reset_cmd_handler(c);

{

conn_shrink(c);//缩短该连接所申请的一些不必要的空

  if (c->rbytes > 0) {

         conn_set_state(c, conn_parse_cmd);

     } else {

         conn_set_state(c, conn_waiting);

//现在是在此处,c->state设置为conn_waiting

     }

}

}

.....

}

接下来仍然是drive_machine(c){//此时c->stateconn_waiting

case conn_waiting:

update_event(c, EV_READ | EV_PERSIST)//由于状态进行了改变需要在事件中进行跟新

event_set(&c->event, c->sfd, new_flags, event_handler, (void *)c);//依然调用event_handler进行处理

conn_set_state(c, conn_read);//然后将c->state设置为conn_read


接下来仍然是回调drive_machine(c){//此时c->stateconn_read

case conn_read:

     res = IS_UDP(c->transport) ? try_read_udp(c) : try_read_network(c);//此处为try_read_network(c)

{

     if (c->rcurr != c->rbuf) {//此处应该是将还未处理完的数据拷贝至rbuf

        if (c->rbytes != 0) /* otherwise there's nothing to copy */

     memmove(c->rbuf, c->rcurr, c->rbytes);//rcurr中的rbytes个数据拷贝到rbuf

        c->rcurr = c->rbuf;

  int avail = c->rsize - c->rbytes;//还剩下可以利用的bites数来存储

res = read(c->sfd, c->rbuf + c->rbytes, avail);

//此处用来等待客户发送命令过来,如果客户还没发送则阻塞在此处。最终是把客户命令存储在c->rbuf

    }

switch (res) //try_read_network(c)读取完毕后的返回值

case READ_DATA_RECEIVED:

      conn_set_state(c, conn_parse_cmd);//开始解析命令

}

 

}

此时连接的客户端开始发送命令:

set  key 0 0 5

Hello

try_read_network(c)中的res = read(c->sfd, c->rbuf + c->rbytes, avail);被唤醒读取完成后返回READ_DATA_RECEIVED.开始进行解析命令。

drive_machine(c){//此时c->stateconn_parse_cmd

conn_parse_cmd

  if (try_read_command(c) == 0) {

  /* wee need more data! */

  conn_set_state(c, conn_waiting);

  }

try_read_command(conn *c)

c->protocol = ascii_prot;//此处为ascii

el = memchr(c->rcurr, '\n', c->rbytes);//找到第一个'\n'的位置

        //c->rcurr="set key 0 0 5\r\nhello\r\n"el="\nhello\r\n"

  cont = el + 1;//去掉/ncont="hello\r\n" ,cont为该value

        if ((el - c->rcurr) > 1 && *(el - 1) == '\r') {

            el--;

        }

        *el = '\0';//c->rcurr="set key 0 0 5\r\nhello\r\n"后面的\r\nhello\r\n去掉,即c->rcurr=set key 0 0 5.其中c->rbuf中后面的\r\nhello\r\n也去掉了

process_command(c, c->rcurr);//rcurr中的数据分段存入tokens

{

token_t tokens[MAX_TOKENS];

size_t ntokens;

add_msghdr(c)//确保还有空间用来存储回复给客户端的信息

ntokens = tokenize_command(command, tokens, MAX_TOKENS);

//解析命令,将命令分段存在tokens,解析完成后//toknes[0]=”set”,toknes[1]=”key”,toknes[2]=”0”,toknes[3]=”0”

//toknes[4]=”5”,ntokens=6 

(strcmp(tokens[COMMAND_TOKEN].value, "set") == 0 && (comm = NREAD_SET))

process_update_command(c, tokens, ntokens, comm, false);//调用该函数

{

     key = tokens[KEY_TOKEN].value; //key

     nkey = tokens[KEY_TOKEN].length;

  it = item_alloc(key, nkey, flags, realtime(exptime), vlen);//分配一个item空间

ITEM_set_cas(it, req_cas_id);//设置cas

     c->item = it;              //

     c->ritem = ITEM_data(it); //itemvalue缓存,存储了itemvalue值  //什么时候将value存进去的?这里是获取该value的位置?

     c->rlbytes = it->nbytes;

     c->cmd = comm;

     conn_set_state(c, conn_nread);//将状态设置为 conn_nread

}

}

c->rbytes -= (cont - c->rcurr);//此处为7也就是“hello\r\n”的大小

  c->rcurr = cont;//c->rcurr=cont="hello\r\n"

}

然后再调用drive_machine(conn *c)//c->state=conn_nread

{

case conn_nread:

   if (c->rlbytes == 0) {//此时c->rlbytes=7.c->rbytes=7

      complete_nread(c);

      break;

   }//如果不等于0则表示还要接收客户数据或者value值还未读取存入,本次为读取存入value值到c->ritem也即ITEM_data(it)中 

 if (c->rbytes > 0) {

 int tocopy = c->rbytes > c->rlbytes ? c->rlbytes : c->rbytes;

 if (c->ritem != c->rcurr) {

  memmove(c->ritem, c->rcurr, tocopy);//value拷贝至c->ritem

                }

                c->ritem += tocopy;

                c->rlbytes -= tocopy;

                c->rcurr += tocopy;

                c->rbytes -= tocopy;

                if (c->rlbytes == 0) {

                    break;//终端后经过回调继续来到这里,进入complete_nread(c);

                }

res = read(c->sfd, c->ritem, c->rlbytes);//此处为读取后面未接受完的数据,本次不运行至这里.

            }

}

complete_nread(c):

{

complete_nread_ascii(c);

{

   item *it = c->item; //存储数据的item的位置即key

    int comm = c->cmd;  //set

ret = store_item(it, comm, c);

//即调用do_store_item(item, comm, c);本次操作中调用do_item_link(it);其中将该item进行存储,加入到hashtable

      switch (ret) {

    case STORED:

          out_string(c, "STORED");如果不需要回复给客户端则将c->state=conn_new_cmd.进行返回监听新的命令

{

     if (c->noreply) 

     conn_set_state(c, conn_new_cmd);

      return;

    }

add_msghdr(c);//msg增加头部

memcpy(c->wbuf, str, len);

//c->wbuf是输出到客户端的数据,例如set ...c->wbuf="STORE\r\n"

     memcpy(c->wbuf + len, "\r\n", 2);

     c->wbytes = len + 2;

     c->wcurr = c->wbuf;

     conn_set_state(c, conn_write);

//然后将状态转换成conn_write

    c->write_and_go = conn_new_cmd;

//结束conn_write后则进入 conn_new_cmd继续监听命令

    return;

}

          break;

}

}

static void drive_machine(conn *c){//c->state=conn_write

case conn_write:

add_iov(c, c->wcurr, c->wbytes)//将数据写入msg

{

.....

        m = &c->msglist[c->msgused - 1]; m->msg_iov[m->msg_iovlen].iov_base = (void *)buf;

/ /STORE/r/n存储准备发送

        m->msg_iov[m->msg_iovlen].iov_len = len;

        c->msgbytes += len;

        c->iovused++;

        m->msg_iovlen++;

......

}//返回成功购没有进行break,接着进入conn_mwrite

case conn_mwrite:

switch (transmit(c))

/进行传输  res = sendmsg(c->sfd, m, 0);//发送信息

//此时客户端会接受到信息STORE/r/n

else if (c->state == conn_write) {

....

                    conn_set_state(c, c->write_and_go);

//接着转换该连接状态。本次为conn_new_cmd,就行监听客户端的下一个命令

}

以上就是从客户端开始连接到发送set key 0 0 5\r\nhello\r\n 命令的全过程


下面来对上面的c->state进行总结:

首先服务器监听自身 fd,主线程的c->state一直为conn_listening,

当客户端进行连接后,分发客户端sfd到一个线程事件中,该c->state=conn_new_cmd.

然后将c->state=conn_waiting进行等待客户端发送命令。

接受到命令后.c->state=conn_read进行读取命令到rbuf中。

然后c->state=conn_parse_cmd开始进行解析命令,将key存放到c->item中,c->ritem 指向value的存放地址,c->cmd存放命令set.

接着c->state=conn_nread,将item和value进行存储.

将发送给客户端的数据存储到msg中,c->state=conn_write.c->write_and_go = conn_new_cmd,

接着将数据STORE\r\n发送给客户.最后c->state=c->write_and_go = conn_new_cmd.

进入下一个循环。


 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值