2014-11-09

SSDB 源码分析 – 网络框架概述

Views: 27838 | 2 Comments

最近, 我对 SSDB 的代码进行了重构, 分离出了 libutil, libnet, libssdb 几个高度可复用的模块, 代码的模块化更清晰, 也更容易进行 SSDB 的源码分析.

这一次要分析的是网络模块. 网络模块包括一个服务器的代码框架和运行框架, 以及网络协议设计和解析.

协议

SSDB 的网络协议非常简单, 而且是业务无关的, 所以你可以把 SSDB 的网络协议应用于几乎所有类型的应用! 只要遵循 SSDB 的网络协议, 你就可以使用 ssdb-cli 命令行工具来连接, 与服务器交互, 或者方便调试. 你甚至可以使用 nc 命令来和任何一个支持 SSDB 网络协议的服务器交互.

虽然 SSDB 的网络协议很简单, 但是解析 SSDB 协议报文, 很多人的代码也往往写得不严谨, 不是没考虑到这种情况, 就是没考虑到那种情况. 最常见的错误是用 fgets() 之类的行级 IO 函数来读取.

关于 SSDB 协议报文的解析, 在协议文档中的例子很清楚, 就不再详解, 感兴趣的同学, 可以从几个方面想: 我为什么会这样写? 你会这样写吗? 你为什么会那样写?

网络服务器运行框架

SSDB 的网络服务器是一个多线程的服务器框架, 进行了良好封装, 使用起来很方便. 可以看看这个例子.

#include "server.h"
#include "../util/config.h"

DEF_PROC(hello);

int main(int argc, char **argv){
	Config conf;
	conf.set("server.port", "9000");
	NetworkServer serv;
	serv.init(conf);
	// register command procedure
	serv.proc_map.set_proc("hello", proc_hello);
	serv.serve();
	return 0;
}

int proc_hello(NetworkServer *net, Link *link, const Request &req, Response *resp){
	resp->push_back("ok");
	resp->push_back("world!");
	if(req.size() > 1){
		// The first argument starts at index 1, just like argv.
		resp->push_back(req[1].String());
	}
	return 0;
}

在 main 函数中, 启动了服务器, 并在

serv.serve();

阻塞一直处理, NetworkServer 内部对信号(signal)进行了处理, 所以你不能再对 signal 进行处理了.

serv.proc_map.set_proc("hello", proc_hello);

这行代码注册了命令的处理函数, 命令的名字是 "hello", 对应的处理函数是 proc_hello.

处理函数中, req 参数是数组形式的请求参数列表, 你可以理解为 main 函数的 argv 参数, req[0] 是命令, 之后的是参数列表.

参数 resp 也是以类似数组的形式返回响应, 你只要把想返回的数据添加到 resp 中, 网络框架会帮你做完所有的事.

启动起来服务器进程之后, 你就可以用 ssdb-cli 工具连接上去, 操作过程如下:

$ ./tools/ssdb-cli 9000
ssdb (cli) - ssdb command line tool.
Copyright (c) 2012-2014 ssdb.io

'h' or 'help' for help, 'q' to quit.

server version: 1.0

ssdb 127.0.0.1:9000> hello abc
ok ['world!', 'abc']
(0.000 sec)
ssdb 127.0.0.1:9000> 

SSDB 的服务器进程 ssdb-server, 主体功能就是定义了很多个处理函数.

网络请求处理流程

SSDB 的网络框架的核心函数之一是:

void NetworkServer::serve();

这个函数是 IO 主线程, 主要进行网络连接的管理, 如接受客户端的新连接, 对连接进行读或者写网络操作. 如果连接有数据要读, 读完后判断协议报文是否 ready 决定加入 ready 队列. 对于 ready 队列中的连接, 那么接下来就该被这个函数处理了:

void NetworkServer::proc(ProcJob *job);

这个函数里首先根据请求(ProcJob)查找相应的命令结构体(Command), 找到之后, 根据命令的属性决定是在当前线程(主线程)中处理, 还是放到一个任务队列中, 由工作者线程池处理.

这两个函数属 serve() 最为复杂, 因为它要判断连接的各种状态, 小心地进行处理. 所以函数的代码函数很多, 还包括几个子函数, 逻辑也不容易理解.

SSDB 使用网络框架使用 IO 多路复用机制来作为整个程序的主循环和主体结构, 而且由于 Linux 等 Posix 系统对于文件描述符都是可以进行 select 的, 所以, 工作者线程池和主线程之间的通信也可以被当成网络 IO 一样的逻辑, 这其中涉及到了一个名字叫 SelectableQueue 的数据结构. 主线程收到的请求通过 SelectableQueue 传递给工作者线程, 而工作者线程处理完请求后, 将响应再通过 SelectableQueue 传递回主线程.

Related posts:

  1. SSDB源码分析 – 服务器的启动过程
  2. 如何解读 SSDB 的 log
  3. SSDB 配置文件
  4. SSDB常规升级-更新到leveldb-1.14.0, Windows安装包
  5. 在PHP代码中使用LevelDB
Posted by ideawu at 2014-11-09 12:49:43 Tags:

2 Responses to "SSDB 源码分析 – 网络框架概述"

Leave a Comment