Redis网络连接库对应的文件是networking.c,这个文件主要负责:
客户端的创建与释放.命令接收与命令回复.Redis通信协议分析.CLIENT 命令的实现. 2. 客户端的创建与释放 2.1 客户端的创建Redis服务器是一个同时与多个客户端建立连接的程序. 当客户端连接上服务器时,服务器会建立一个server.h/client结构来保存客户端的状态信息. server.h/client结构如下所示:
创建客户端的源码:
根据创建的文件描述符fd,可以创建用于不同场景下的client. 这个fd就是服务器接收客户端connect后所返回的文件描述符.
fd == -1,表示创建一个无网络连接的客户端。主要用于执行 lua 脚本时.fd != -1,表示接收到一个正常的客户端连接,则会创建一个有网络连接的客户端,也就是创建一个文件事件,来监听这个fd是否可读,当客户端发送数据,则事件被触发.创建客户端的过程,会将server.h/client结构的所有成员初始化,接下里会介绍部分重点的成员.
int id:服务器对于每一个连接进来的都会创建一个ID,客户端的ID从1开始。每次重启服务器会刷新. int fd:当前客户端状态描述符。分为无网络连接的客户端和有网络连接的客户端. int flags:客户端状态的标志. robj *name:默认创建的客户端是没有名字的,可以通过CLIENT SETNAME命令设置名字. 后面会介绍该命令的实现. int reqtype:请求协议的类型. 因为Redis服务器支持Telnet的连接,因此Telnet命令请求协议类型是PROTO_REQ_INLINE,而redis-cli命令请求的协议类型是PROTO_REQ_MULTIBULK.
用于保存服务器接受客户端命令的成员:
sds querybuf:保存客户端发来命令请求的输入缓冲区. 以Redis通信协议的方式保存. size_t querybuf_peak:保存输入缓冲区的峰值. int argc:命令参数个数. robj *argv:命令参数列表.
用于保存服务器给客户端回复的成员:
char buf[16*1024]:保存执行完命令所得命令回复信息的静态缓冲区,它的大小是固定的,所以主要保存的是一些比较短的回复. 分配client结构空间时,就会分配一个16K的大小. int bufpos:记录静态缓冲区的偏移量,也就是buf数组已经使用的字节数. list *reply:保存命令回复的链表. 因为静态缓冲区大小固定,主要保存固定长度的命令回复,当处理一些返回大量回复的命令,则会将命令回复以链表的形式连接起来. unsigned long long reply_bytes:保存回复链表的字节数. size_t sentlen:已发送回复的字节数.
2.2 客户端的释放客户端释放的函数是freeClient(),主要就是释放各种数据结构和清空一些缓冲区等操作,这里就不再列出源码.
我们可以重点关注一下异步释放客户端,源码如下:
设置异步释放客户端的目的主要是:防止底层函数正在向客户端的输出缓冲区写数据的时候,关闭客户端,这样是不安全的. Redis会安排客户端在serverCron()函数的安全时间释放它.
当然也可以取消异步释放,那么就会调用freeClient()函数立即释放,源码如下:
3. 命令接收与命令回复 3.1 命令接收当客户端连接上Redis服务器后,服务器会得到一个文件描述符fd,而且服务器会监听该文件描述符的读事件,这些在createClient()函数中. 那么当客户端发送了命令,触发了AE_READABLE事件,那么就会调用回调函数readQueryFromClient()来从文件描述符fd中读发来的命令,并保存在输入缓冲区querybuf中. 而这个回调函数就是我们在Redis事件处理一文中所提到的指向回调函数的指针rfileProc和wfileProc. 那么,我们先来分析readQueryFromClient函数.
实际上,这个readQueryFromClient()函数是read函数的封装,从文件描述符fd中读出数据到输入缓冲区querybuf中,并更新输入缓冲区的峰值querybuf_peak,而且会检查读的长度,如果大于了server.client_max_querybuf_len则会退出,而这个阀值在服务器初始化为PROTO_MAX_QUERYBUF_LEN (1024*1024*1024)也就是1G大小.
回忆之前的各种命令实现,都是通过client的argv和argc这两个成员来处理的. 因此,服务器还需要将输入缓冲区querybuf中的数据,处理成参数列表的对象,也就是上面的processInputBuffer()函数. 源码如下:
redis-cli命令请求的协议类型是PROTO_REQ_MULTIBULK,进而调用processMultibulkBuffer()函数来处理:
我们结合一个多条批量回复进行分析。一个多条批量回复以 *<argc> 为前缀,后跟多条不同的批量回复,其中 argc为这些批量回复的数量. 那么SET nmykey nmyvalue命令转换为Redis协议内容如下:
当进入processMultibulkBuffer()函数之后,如果是第一次执行该函数,那么argv中未读取的命令数量为0,也就是说参数列表为空,那么会执行if (c->multibulklen == 0)的代码,这里的代码会解析*3 ,将3保存到multibulklen中,表示后面的参数个数,然后根据参数个数,为argv分配空间.
接着,执行multibulklen次while循环,每次读一个参数,例如$3 SET ,也是先读出参数长度,保存在bulklen中,然后将参数SET保存构建成对象保存到参数列表中. 每次读一个参数,multibulklen就会减1,当等于0时,就表示命令的参数全部读取到参数列表完毕.
于是命令接收的整个过程完成.
3.2 命令回复命令回复的函数,也是事件处理程序的回调函数之一. 当服务器的client的回复缓冲区有数据,那么就会调用aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,sendReplyToClient, c)函数,将文件描述符fd和AE_WRITABLE事件关联起来,当客户端可写时,就会触发事件,调用sendReplyToClient()函数,执行写事件. 我们重点看这个函数的代码:
这个函数直接调用了writeToClient()函数:
这个函数实际上是对write()函数的封装,将静态回复缓冲区buf或回复链表reply中的数据循环写到文件描述符fd中. 如果写完了,则将当前客户端的AE_WRITABLE事件删除.
4. CLIENT命令的实现CLIENT相关的命令大致有6条:
下面是client命令的实现:
以上就是Redis源码与设计剖析之网络连接库的详细内容,更多关于Redis 网络连接库的资料请关注七叶笔记其它相关文章!