ANet
基于Redis网络模型的简易网络库,网络模块代码取自Redis源码。
Redis网络模型介绍
Redis网络模型是一个使用IO多路复用、单线程、非阻塞的模型。这个模型的优点在于单线程不用考虑加锁,如果在单核环境上可以将效率发挥到最大。
如何启动一个服务器
- 通过
aeCreateEventLoop
创建核心aeEventLoop
- 通过
anetTcpserver
完成socket() bind() listen()
- 通过
aeCreateFileEvent
给fd
注册相应的事件 -
aeMain
循环检测每个fd
是否有事件发生,如果有就交给相应的读/写处理程序。
附:各个文件介绍
文件 | 作用 |
---|---|
ae.c ae.h ae_epoll.c ae_select.c | Redis事件处理器的实现(Redis源码) |
anet.c anet.h | Redis网络库的实现(Redis源码) |
buffer.c buffer.h | 自行实现的buffer |
protocol.c protocol.h | 自行定义协议 |
define.h | 一些常量,比如listen的backlog大小 |
server.c server_test.c | 自定义的服务端和客户端程序 |
调试
服务端:
[root@localhost build]# ./server
listen on: 0.0.0.0:12345
accepted ip 127.0.0.1:42864
chat message: No.0 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.1 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.2 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.3 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.4 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.5 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.6 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.7 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.8 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.9 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.10 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.11 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.12 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.13 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.14 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.15 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.16 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.17 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.18 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.19 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.20 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.21 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.22 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.23 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.24 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.25 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.26 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.27 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.28 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.29 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.30 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.31 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.32 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.33 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.34 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.35 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.36 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.37 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.38 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.39 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.40 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.41 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.42 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.43 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.44 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.45 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.46 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.47 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.48 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.49 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.50 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.51 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.52 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.53 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.54 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.55 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.56 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.57 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.58 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
chat message: No.59 hey, hurley. I say: 1234567890ABCDEFGHIJKLMnopQRSTUVWXYZ.
client disconnect, close it.
^C
[root@localhost build]#
客户端
-rwxr-xr-x 1 root root 59048 5月 24 00:12 server_test
[root@localhost build]# ./server_test
connect error: creating socket: Connection refused
[root@localhost build]# pwd
/root/zsx/code/redis/ANet-master/build
[root@localhost build]# ./server_test
send package size: 70
slow send ...
more slow send ...
[root@localhost build]#
网络服务端
Redis单线程模型
通过aeCreateEventLoop
创建核心aeEventLoop
initServer流程:
initServer里面首先创建了一个EventLoop,然后监听Server的IP对应的端口号,假设我们监听的是127.0.0.1:3333这个IP:端口对,我们得到的一个Server Socket句柄,最后通过createFileEvent将我们得到的Server Socket句柄和我们关心的网络事件mask注册到EventLoop上面。
EventLoop的定义
/* State of an event based program 事件状态结构 */
typedef struct aeEventLoop {
int maxfd; /* 当前注册的最大文件描述符 */
int setsize; /* 监控的最大文件描述符数 */
long long timeEventNextId; /* 定时事件ID */
time_t lastTime; /* 最近一次处理事件的时间 */
aeFileEvent *events; /* 注册的事件表 数组 */
aeFiredEvent *fired; /* 已就绪事件表 */
aeTimeEvent *timeEventHead; /* 定时事件链表头节点 */
int stop; /* 是否停止循环 事件处理开关 */
void *apidata; /* 特定接口的特定数据 保存底层调用的多路复用库的事件状态 */
aeBeforeSleepProc *beforesleep; /* 在sleep之前执行的函数 */
} aeEventLoop; //事件轮询的状态结构
上面主要关注两个东西:events(注册的事件表 数组)和fired(已就绪事件表)。
events用于存放被注册的事件以及相应的句柄,fired用于存放当EventLoop线程从多路复用器轮询到有事件的句柄的时候,EventLoop线程会把它放入fired数组里面,然后处理。
事件注册示意图
我用上面的示意图描述createFileEvent做的事情,就是将Server Socket句柄和关心的事件mask以及当事件产生的时候的事件处理器accptHandler生成一个aeFileEvent注册到EventLoop的events的数组里面,当然在这之前会首先将事件注册到多路复用器上,也就是epoll、kqueue等这些组件上。事件注册完之后需要对多路复用器进行轮训,来分离我们关心切发生的事件,那就是最后一步,启动事件轮询器。
接收网络连接
上面的步骤完成了服务端的网络初始化,而且事件轮询器已经开始工作了,事件轮询器做什么事情呢,就是不断轮训多路复用器,看看之前注册的事件有没有发生,如果有发生,则将会将事件分离出来,放入EventLoop的fired数组中,然后处理这些事件。
很显然,上面注册的事件是客户端建立连接这个事件,因此当有两个客户端同时连接Redis服务器的时候,事件轮询器会从多路复用器上面分离出这个事件,同时调用acceptHandler来处理。acceptHandler做的事情主要是accept客户端的连接,创建socket句柄,然后将socket句柄和读事件注册到EventLoop的events数组里面,不一样的是对于客户端的事件处理器是readQueryClient。
accept客户端连接以及注册客户端连接句柄示意图
上面示意图表示了acceptHandler处理客户端连接,得到句柄之后再将这个句柄注册到多路复用器以及EventLoop上的示意图。之后再同样再处理下一个客户端的连接,这些都是串行的。
事件轮训
上面接收客户端这部分其实都发生在事件轮训的主循环里面:
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
}
Redis会不断的轮训多路复用器,将网络事件分离出来,如果是accept事件,则新接收客户端连接并将其注册到多路复用器以及EventLoop中,如果是查询事件,则通过读取客户端的命令进行相应的处理,这一切都是单线程,顺序的执行的,因此不会发生并发问题。
应用分析
Redis官网对Redis的读写性能测试结果达到10左右,这是非常吸引人的。Redis的单线程的行为主要是对内存的读写,这些操作其实用不了多少时间,因此瓶颈在网络I/O上面,我们一般提供较好的网络环境就可以提升Redis的吞吐量,比如提高网络带宽,除此之外还可以通过合并命令提交批处理请求来代替单条命令一次次请求从而减少网络开销,提高吞吐量。
原文地址:https://www.jb51.cc/wenti/3279781.html
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。