twemproxy接收流程探索——twemproxy代码分析正编
本文旨在帮助大家探索出twemproxy接收流程的代码逻辑框架,有些具体的实现需要我们在未来抽空去探索或者大家自行探索。在这篇文章开始前,大家要做好一个小小的心理准备,由于twemproxy代码是一份优秀的c语言代码,为此,在twemproxy的代码中会大篇幅使用c指针。但是不论是普通类型的指针还是函数指针,都可以让我们这些c语言使用者大饱眼福,生出一种“原来还可以这样写!!!”的快感。
数据结构
在探索twemproxy接收流程之前,我们必须对一些我们会用到的数据结构进行说明,以便我们更好地去探索,这边在讲解结构时,仅仅讲解与twemproxy接收流程相关的代码,其他代码暂时不进行剖析。
mbuf
在nc_mbuf.h里
struct mbuf {
uint32_t magic; /* mbuf magic (const) 这个值不是很理解是什么意思,一般是0xdeadbeef*/
STAILQ_ENTRY(mbuf) next; /* next mbuf 下一块mbuf,代码里所有的mbuf几乎都是以单向链表的形式存储的*/
uint8_t *pos; /* read marker 表示这块mbuf已经读到那个字节了*/
uint8_t *last; /* write marker 表示这块mbuf已经写到哪个字节*/
uint8_t *start; /* start of buffer (const) 表示这块mbuf的起始位置*/
uint8_t *end; /* end of buffer (const) 表示这块mbuf的结束位置*/
};
STAILQ_HEAD(mhdr, mbuf); /*mhdr是mbuf单向队列的队列头部*/
这里要对mbuf解释几句,这里涉及到nc_mbuf.c里的代码:
1.mbuf的每一块可以通过配置规定其大小 ,可以说每一块mbuf的大小都是一个固定值,为此在生成时mbuf会去申请一个固定大小的内存,如果这个大小是mbuf_chunk_size,那么end = start + mbuf_chunk_size - sizeof(struct mbuf),为此start,end,以及magic都是定值。
2.mbuf在申请后一般不会被释放,在使用完后会被放入static struct mhdr free_mbufq这个队列中,一旦要使用mbuf时首先从free_mbufq中取出未使用的mbuf,如果这个队列为空时,它才会去向系统申请新的mbuf。
msg
在nc_message.h里
struct msg {
/*
...
*/
struct conn *owner; /* message owner - client | server 服务端或客户端连接*/
/*
...
*/
struct mhdr mhdr; /* message mbuf header mbuf单向队列的队列头部*/
uint32_t mlen; /* message length mbuf字节长度*/
/*
...
*/
uint8_t *pos; /* parser position marker 现在解析到哪个个字节*/
msg_parse_t parser; /* message parser 消息解析函数指针*/
msg_parse_result_t result; /* message parsing result 消息解析结果*/
/*
...
*/ };
msg是用来存储每一条发送过来的redis包的内容,一般一个msg对应一个redis包,所有收发网络数据都存储在mhdr中。
conn
在connection.h中
struct conn {
/*
...
*/
int sd; /* socket descriptor 套接字描述符*/
/*
...
*/
conn_recv_t recv; /* recv (read) handler 接收msg函数指针*/
conn_recv_next_t recv_next; /* recv next message handler 接收下一个msg的函数指针*/
conn_recv_done_t recv_done; /* read done handler 接收完成的函数指针*/
/*
...
*/
size_t recv_bytes; /* received (read) bytes 接收数据的字节数*/
size_t send_bytes; /* sent (written) bytes 发送数据的字节数*/
/*
...
*/
err_t err; /* connection errno 接受数据错误*/
unsigned recv_active:; /* recv active? 是否在接收数据*/
unsigned recv_ready:; /* recv ready? 是否准备接收数据*/
/*
...
*/
unsigned eof:; /* eof? aka passive close? 数据读到尾部*/
unsigned done:; /* done? aka close? 完成数据接收*/
unsigned redis:; /* redis? 网络协议是不是redis*/
/*
...
*/
};
conn是与服务端或客户端的连接,用于管理连接上的所有事件和网络数据
接收流程
首先看下主要流程,很简单的代码在nc_message.c中的msg_recv
rstatus_t
msg_recv(struct context *ctx, struct conn *conn)
{
rstatus_t status;
struct msg *msg; ASSERT(conn->recv_active); conn->recv_ready = ;//表示准备接收网络数据
do {
msg = conn->recv_next(ctx, conn, true);
if (msg == NULL) {
return NC_OK;
} status = msg_recv_chain(ctx, conn, msg);//接收函数链,在这个流程中会改变conn->recv_ready的值,表示本次接收流程终止
if (status != NC_OK) {
return status;
}
} while (conn->recv_ready);//一旦不准备接收网络数据,就停止 return NC_OK;
}
在这个代码中我们会发现一个conn->recv_next,目前我们只要知道它是准备接收下一个msg的函数,不需要知道他的具体实现,因为他在《twemproxy代码框架概述——剖析twemproxy代码前编》提到的客户层和服务层扮演的角色是不同的,为此,实现也是不同的,这里主要指的是《twemproxy代码框架概述——剖析twemproxy代码前编》提到的模块1和模块3,在这里我们居然看到了c语言的代码里出现了一个在面向对象语言中才有的特性——多态,在下面几篇文章的探索中会讲到,不小心做了广告,请无视上面的部分内容。
接下来我们来看msg_recv函数中的msg_recv_chain,同样也是一个框架
static rstatus_t
msg_recv_chain(struct context *ctx, struct conn *conn, struct msg *msg)
{
rstatus_t status;
struct msg *nmsg;
struct mbuf *mbuf;
size_t msize;
ssize_t n; mbuf = STAILQ_LAST(&msg->mhdr, mbuf, next);//找到目前收到mbuf队列的最后一个mbuf
//如果这个mbuf满了或者为空,则取得一个空的mbuf,加入到msg->mhdr队列中
if (mbuf == NULL || mbuf_full(mbuf)) {
mbuf = mbuf_get();
if (mbuf == NULL) {
return NC_ENOMEM;
}
mbuf_insert(&msg->mhdr, mbuf);
msg->pos = mbuf->pos;//这时解析指针指向该mbuf的读取指针
}
ASSERT(mbuf->end - mbuf->last > );
msize = mbuf_size(mbuf); //计算剩余的mbuf的值msize n = conn_recv(conn, mbuf->last, msize);//读取最大为msize的网络数据
if (n < ) {
if (n == NC_EAGAIN) {
return NC_OK;
}
return NC_ERROR;
} ASSERT((mbuf->last + n) <= mbuf->end); mbuf->last += n; //将写指针偏移到正确的位置
msg->mlen += (uint32_t)n;
//解析网络数据内容,在其中将网络数据分成不同的msg,因为网络包可能黏合,可能会接收到不同的redis包
for (;;) {
status = msg_parse(ctx, conn, msg);//解析网络数据完成分包
if (status != NC_OK) {
return status;
} /* get next message to parse */
nmsg = conn->recv_next(ctx, conn, false);
if (nmsg == NULL || nmsg == msg) {
/* no more data to parse */
break;
} msg = nmsg;//使指针指向下一个包
} return NC_OK;
}
在前面我们看到在代码中大量使用了断言ASSERT,如ASSERT(mbuf->end - mbuf->last > 0),就表示该内存还没有被写满,查看这些断言会使我们对代码有更好的认识。同时,它也是一个很好的代码习惯
接着就是在connection.c中的接受函数conn_recv,比较简单,一些对于收发网络数据遇到的情况的处理值得学习
ssize_t
conn_recv(struct conn *conn, void *buf, size_t size)
{
ssize_t n; ASSERT(buf != NULL);
ASSERT(size > );
ASSERT(conn->recv_ready); for (;;) {
n = nc_read(conn->sd, buf, size);//相当于read函数 log_debug(LOG_VERB, "recv on sd %d %zd of %zu", conn->sd, n, size);
//如果收到的数据不为空,一旦收到数据小于size,表示没有更多的数据能被读取,为此将conn->recv_ready = 0
if (n > ) {
if (n < (ssize_t) size) {
conn->recv_ready = ;
}
conn->recv_bytes += (size_t)n;
return n;
}
//如果收到的数据为空,表示没有更多的数据能被读取,为此将conn->recv_ready = 0
if (n == ) {
conn->recv_ready = ;
conn->eof = ;
log_debug(LOG_INFO, "recv on sd %d eof rb %zu sb %zu", conn->sd,
conn->recv_bytes, conn->send_bytes);
return n;
}
//如果收发数据出现不是EINTR的错误,表示收发数据断链或者遇到错误,为此也将conn->recv_ready = 0
if (errno == EINTR) {
log_debug(LOG_VERB, "recv on sd %d not ready - eintr", conn->sd);
continue;
} else if (errno == EAGAIN || errno == EWOULDBLOCK) {
conn->recv_ready = ;
log_debug(LOG_VERB, "recv on sd %d not ready - eagain", conn->sd);
return NC_EAGAIN;
} else {
conn->recv_ready = ;
conn->err = errno;
log_error("recv on sd %d failed: %s", conn->sd, strerror(errno));
return NC_ERROR;
}
} NOT_REACHED(); return NC_ERROR;
}
下面就是解析分包框架msg_parse
static rstatus_t
msg_parse(struct context *ctx, struct conn *conn, struct msg *msg)
{
rstatus_t status; if (msg_empty(msg)) {
/* no data to parse */
conn->recv_done(ctx, conn, msg, NULL);
return NC_OK;
} msg->parser(msg);//解析函数器,这个我们会在后续的文章中提到,即完整的redis协议解析流程 switch (msg->result) {
case MSG_PARSE_OK:
status = msg_parsed(ctx, conn, msg);//解析一个包完成,进行分包
break; case MSG_PARSE_REPAIR:
status = msg_repair(ctx, conn, msg);//将受到的网络数据分到不同的buffer中
break; case MSG_PARSE_AGAIN:
status = NC_OK;
break; default:
status = NC_ERROR;
conn->err = errno;
break;
} return conn->err != ? NC_ERROR : status;
}
在这个代码中我们又会发现一个conn->recv_done,目前我们只要知道它是接收结束的函数,同样不需要知道他的具体实现,因为它也是在《twemproxy代码框架概述——剖析twemproxy代码前编》提到的客户层和服务层扮演的角色是不同的,为此,实现也是不同的,这里主要指的是《twemproxy代码框架概述——剖析twemproxy代码前编》提到的模块1和模块3。
下面就是msg_parsed,用于解析一个包完成后分包
static rstatus_t
msg_parsed(struct context *ctx, struct conn *conn, struct msg *msg)
{
struct msg *nmsg;
struct mbuf *mbuf, *nbuf; mbuf = STAILQ_LAST(&msg->mhdr, mbuf, next);
if (msg->pos == mbuf->last) {//正好结束分包
/* no more data to parse */
conn->recv_done(ctx, conn, msg, NULL);
return NC_OK;
} /*
* Input mbuf has un-parsed data. Split mbuf of the current message msg
* into (mbuf, nbuf), where mbuf is the portion of the message that has
* been parsed and nbuf is the portion of the message that is un-parsed.
* Parse nbuf as a new message nmsg in the next iteration.
*/
//下面的所有工作就是把mbuf收到的网络数据,将不属于这个包msg的而属于下个包nmsg的内容分割出去放到下一个包nmsg
nbuf = mbuf_split(&msg->mhdr, msg->pos, NULL, NULL);
if (nbuf == NULL) {
return NC_ENOMEM;
} nmsg = msg_get(msg->owner, msg->request, conn->redis);
if (nmsg == NULL) {
mbuf_put(nbuf);
return NC_ENOMEM;
}
mbuf_insert(&nmsg->mhdr, nbuf);
nmsg->pos = nbuf->pos; /* update length of current (msg) and new message (nmsg)*/
nmsg->mlen = mbuf_length(nbuf);
msg->mlen -= nmsg->mlen; conn->recv_done(ctx, conn, msg, nmsg); return NC_OK;
}
上面的流程可以用图1表示,我们可以看到图1中的mbuf收到了两个包的数据,分别是一个包msg(红色)的结尾和一个包nmsg(黄色)的开始,根据我们前文的说法一个msg对应一个包,为此必须把这个mbuf分割到到两个msg中。
图1.分包示意图
最后是分muf的msg_repair
static rstatus_t
msg_repair(struct context *ctx, struct conn *conn, struct msg *msg)
{
struct mbuf *nbuf;
//取出一个新的nbuf去读取下轮的网络数据
nbuf = mbuf_split(&msg->mhdr, msg->pos, NULL, NULL);
if (nbuf == NULL) {
return NC_ENOMEM;
}
mbuf_insert(&msg->mhdr, nbuf);
msg->pos = nbuf->pos; return NC_OK;
}
在redis包中可能会存在多key的情况,一个msg中的mbuf具体是怎么存的,还需要完成对于redis协议的解读,我们才能明白为什么需要msg_repair,,在这里稍稍挖个坑。目前我们可以理解为它产生了一个新的nbuf去读下一轮的网络数据。
这样我们完成了整个接收流程的探索,至于发送流程需要在下几个篇章中完成。
总结
本文完成了对于twemproxy整个接收流程的探索,首先介绍了相关的数据结构——mbuf、msg以及conn,在下面的日子里我们会更多地去了解它们,在未来的解析中它们是主角,接着分析了接收流程中的各个函数msg_repair、msg_parse、msg_parsed、msg_recv_chain、msg_recv以及conn_recv,最后较为介绍了它们在接收中的作用,当然稍稍挖了几个坑,表示以后再填。下面我们会着重探索twemproxy的redis协议解析和twemproxy发送流程,敬请期待!!
另外,对于博文有问题的请大家在评论中留言与博主讨论,博主会及时回复的!!!!
twemproxy接收流程探索——twemproxy代码分析正编的更多相关文章
- twemproxy接收流程探索——剖析twemproxy代码正编
本文旨在帮助大家探索出twemproxy接收流程的代码逻辑框架,有些具体的实现需要我们在未来抽空去探索或者大家自行探索.在这篇文章开始前,大家要做好一个小小的心理准备,由于twemproxy代码是一份 ...
- twemproxy发送流程探索——剖析twemproxy代码正编
本文想要完成对twemproxy发送流程--msg_send的探索,对于twemproxy发送流程的数据结构已经在<twemproxy接收流程探索--剖析twemproxy代码正编>介绍过 ...
- 虚拟机创建流程中neutron代码分析(三)
前言: 当neutron-server创建了port信息,将port信息写入数据库中.流程返回到nova服务端,接着nova创建的流程继续走.在计算节点中neutron-agent同样要完成很多的工作 ...
- 虚拟机创建流程中neutron代码分析(二)
前言: 当nova服务发送了创建port的restful调用信息之后,在neutron服务中有相应的处理函数来处理调用.根据restful的工作原理,是按照 paste.ini文件中配置好的流程去处理 ...
- 虚拟机创建流程中neutron代码分析(一)
前言: 在openstack的学习当中有一说法就是网络占学习时间的百分之七十.这个说法或许有夸大的成分,但不可否认的是openstack中的 网络是及其重要的部分,并且难度也是相当大.试图通过nova ...
- twemproxy代理主干流程——剖析twemproxy代码正编
在twemproxy的发送和接收流程剖析中,我们已经完全弄清楚twemproxy如何将客户端以及服务端发来的包切分成msg,获得一个独立的msg后twemproxy应该如何处理?这是本文这次需要重点介 ...
- pf_ring DNA接收流程代码分析
经过一个月的学习,对pf_ring DNA的内核部分有了一些认识,本文侧重pf_ring对ixgbe的改动分析. 先说一说接收流程吧,流程如下: 其中,硬中断处理函数是ixgbe_msix_clean ...
- Openfire注册流程代码分析
Openfire注册流程代码分析 一.客户端/服务端注册用户流程 经过主机连接消息确认后,客户端共发送俩条XML完成注册过程.服务器返回两条XML. 注:IQ消息节点用于处理用户的注册.好友.分组.获 ...
- Ecshop的购物流程代码分析详细说明
Ecshop的购物流程代码分析详细说明 (2012-07-30 10:41:12) 转载▼ 标签: 购物车 结算中心 商品价格 ecshop ecshop购物流程 杂谈 分类: ECSHOP研究院 同 ...
随机推荐
- 编程算法 - 二部图确定 代码(C)
二部图确定 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 题目: 给定一个具有n个顶点的图. 要给图上每一个顶点染色, 而且要使相邻的顶点颜色不同. ...
- List<string>和string[]
List<string>和string[] List<string>是集合:string[]是数组: ///////////////////////////////////// ...
- VS2015安装
VS2015安装 Secondary Installer Setup Failed求解决方案 看到微软最近的一系列变化,着实让我等兴奋不已.VS2015下载地址就不说了.先来记录一下微软的几个变化吧. ...
- 使用CountDownLatch和CyclicBarrier处理并发线程
闲话不说,首先看一段代码: { IValueCallback remoteCallback = new IValueCallback.Stub() { <strong><span s ...
- [代码收藏]设为首页和加入收藏的JavaScript代码(兼容多浏览器)
其实不少非IE内核浏览器都仍不支持通过代码将网页设为主页和加入收藏的功能,因此说是兼容,其实只是一个try,catch后的提醒而已. 加入收藏: /* * author : 2010-12-27 11 ...
- MVC Bootstrap极速开发框架
ASP.NET MVC Bootstrap极速开发框架 前言 每次新开发项目都要从头开始设计?有木有一个通用的快速开发框架?并且得是ASP.NET MVC And Bootstrap?数据库不要手工 ...
- 如何给非AppCompatActivity添加Toolbar?--关于5.0新特性兼容5.0以下设备的探索
Android支持包22.1引进了AppCompatDelegate 最新22.1版本的支持包引入了大量酷炫的新特性,这些特性将允许我们轻易地将材料设计/API 21+的特性应用到之前的那些老的,不兼 ...
- Google Offer的经历+内推
8个月从CS菜鸟到拿到Google Offer的经历+内推 http://www.1point3acres.com/bbs/forum.php?mod=viewthread&tid=77453 ...
- C#编程总结
C#编程总结--总目录 多年的C#实战经历,希望通过一个系列课程对C#编程做系统总结. 总结过去,展望未来.新的一年,新的征程,新的开始! 希望我们在2014梦想成真,马到成功! 1.C#编程总结(一 ...
- [原]iOS Makefile Template
export DEVELOPER_DIR := $(shell xcode-select --print-path) PLATFORM="$(DEVELOPER_DIR)/Platforms ...