简易流程图如下:
其中,fd10,为epoll的首地址
fd11,为server监听的fd0
fd12-fd14,为测试时的3个客户端接入时,生成的fd socket.
1、客户端链接
redis服务端启动时,在_anetTcpServer进行socket,bind,listen步骤时,建立一个监听的sock fd0。并把这个fd0 添加到epoll监听事件。设置的回调函数中,fe->rfileProc=acceptTcpHandler
if (mask & AE_READABLE) fe->rfileProc = proc;
if (mask & AE_WRITABLE) fe->wfileProc = proc;
当客户端有链接请求接入时,epoll事件触发,在
aeProcessEvents==> aeApiPoll,
if (e->events & EPOLLIN) mask |= AE_READABLE;
此时有了IN事件,当多个客户端接入时,都会触发同一个fd0
struct epoll_event *e = state->events+j;
if (e->events & EPOLLIN) mask |= AE_READABLE;
if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
if (e->events & EPOLLERR) mask |= AE_WRITABLE;
if (e->events & EPOLLHUP) mask |= AE_WRITABLE;
eventLoop->fired[j].fd = e->data.fd;
eventLoop->fired[j].mask = mask;
aeProcessEvents==>fe->rfileProc,此时回调函数acceptTcpHandler得以执行。
2、acceptTcpHandler中,准备接收客户端数据
accpet建立时,生成当前服务端-客户端四元组(srcip,srcport,dstip,dstport)的一个fd,
然后将此fd1再次添加到epoll队列中,同时,此时的回调函数,设置成readQueryFromClient,准备读客户端数据
client *createClient(int fd) {
client *c = zmalloc(sizeof(client));
/* passing -1 as fd it is possible to create a non connected client.
* This is useful since all the commands needs to be executed
* in the context of a client. When commands are executed in other
* contexts (for instance a Lua script) we need a non connected client. */
if (fd != -1) {
anetNonBlock(NULL,fd);
anetEnableTcpNoDelay(NULL,fd);
if (server.tcpkeepalive)
anetKeepAlive(NULL,fd,server.tcpkeepalive);
if (aeCreateFileEvent(server.el,fd,AE_READABLE,//此处的fd,是accept后,生成的fd1
readQueryFromClient, c) == AE_ERR)
3、读入客户端数据
在2中的epoll_ctl 添加事件时,主动设置了一个mask为IN事件,此时,epoll_wait会得到触发,产生一个新的IN事件。
回调函数readQueryFromClient继而被调用。在此函数中,
###
nread = read(fd, c->querybuf+qblen, readlen);
###
void processInputBuffer(client *c) {
server.current_client = c;
/* Keep processing while there is something in the input buffer */
while(c->qb_pos < sdslen(c->querybuf)) {
/* Return if clients are paused. */
这里的c->querybuf就是客户端执行的命令,比如这里的 get name
(gdb) p c->qb_pos
$35 = 23
(gdb) p c->querybuf
$36 = (sds) 0x7d6ca5 "*2\r\n$3\r\nget\r\n$4\r\nname\r\n"
4、接下来就是调用命令
call(c,CMD_CALL_FULL);
c->cmd->proc(c); 此处进行gdb,发现调用的函数,是getCommand
(gdb) p c->cmd->proc
$19 = (redisCommandProc *) 0x44bd50
(gdb) p c->argv[1]
$20 = (robj *) 0x7cb9a0
(gdb) p c->cmd->name
$21 = 0x4c80dd "get"
int getGenericCommand(client *c) {
robj *o;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
return C_OK;
if (o->type != OBJ_STRING) {
addReply(c,shared.wrongtypeerr);
return C_ERR;
} else {
addReplyBulk(c,o);
return C_OK;
}
}
此时经过key查找,找到name对应的内容是“huawei”
(gdb) p c->buf
$9 = "$6\r\nhuawei\r\n", '\000'
(gdb) n
5、给客户端响应内容 c->buf
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
int writeToClient
###
nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);
(gdb) p c->buf
$6 = "$6\r\nhuawei\r\n\r\nlrange\r\n:4\r\n\000\000@nJ\367\377\177\000\000!\000\000\000\000\000\000\000\270\247J\367\377\177\000\000\270\247J\367\377\177\000\000`\002\000\000\000\000\000\000P", '\000'
(gdb) n
986 nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);