2021SC@SDUSC
目录
前言-libsearpc项目介绍及结构
项目结构
demo分析
searpc-demo-seaver.c
项目中重要概念
初始化searpc服务器
创建服务并注册函数
接受并处理请求
监听socket
其他
前言-libsearpc项目介绍及结构
根据文档以及个人研究,seafile的所有与数据库相关操作、文件系统操作都在seafile-server项目中,而seahub则是一个django服务器,也就是说,seafile有两个服务器,那么这两个服务器必须进行数据交换,整个服务才能正常进行。
那么承载着两个服务器之间的中间件就是seaprc、这是一个rpc框架,主要的工作是接受网络传输的数据,然后再两个端进行数据的格式化和解析,但是传输过程需要由用户处理。
应当注意的是,searpc本身并不能创建服务器和客户端,需要手动使用socket等库建立端口监听,客户端需要自行向服务端建立连接。
项目结构
searpc是基于GObject系统的C语言rpc框架
-
demo:searpc的测试demo
-
包含C语言实现的服务端与客户端,以及python实现的客户端
-
-
lib:存放searpc的具体实现代码
-
pysearpc:对searpc的python语言的封装,仅支持客户端函数
demo分析
searpc-demo-seaver.c
根据文档得知,在服务器端,需要进行的操作如下:
-
初始化searpc服务器
-
创建服务并注册函数
-
建立接受请求部分的相关代码
项目中重要概念
-
marshall:将函数参数从json形式中解包,调用rpc函数并且将返回结果封装为json数据的过程成为marshalling。将返回结果序列化为json的函数成为Marshall
-
signature:每个函数都有一个根据其返回类型以及参数列表定义的signature。知道一个函数的signature能够是我们使用其对应的Marshall来调用它并将其结果序列化
初始化searpc服务器
marshall:将函数参数从json形式中解包,调用rpc函数并且将返回结果封装为json数据的过程成为marshalling。将返回结果序列化为json的函数成为Marshall
signature:每个函数都有一个根据其返回类型以及参数列表定义的signature。知道一个函数的signature能够是我们使用其对应的Marshall来调用它并将其结果序列化
首先定义希望被调用的rpc函数
static int
searpc_strlen(const char *str)
{
if (str == NULL)
return -1;
else
return strlen(str);
}
static GList *
searpc_objlisttest(int count, int len, const char *str)
{
GList *ret = NULL;
int i;
for (i = 0; i != count; ++i)
{
TestObject *obj = g_object_new(TEST_OBJECT_TYPE, NULL);
obj->len = len;
g_free(obj->str);
obj->str = g_strdup(str);
if (len == strlen(str))
obj->equal = TRUE;
ret = g_list_prepend(ret, obj);
}
return ret;
}
其次,在rpc_table.py文件中定义了rpc函数的signature,其形式如下:
# [, [] ] func_table = [ [ "int", ["string"] ], [ "string", ["int", "string"] ], ]
通过makefile文件
searpc-signature.h searpc-marshal.h: rpc_table.py python searpc-codegen.py rpc_table.py
从而得到包含上述定义的函数signature以及对应的Marshall的文件:searpc-signature.h与 searpc-marshal.h
-
其中searpc-marshal.h也包含一个register_marshals函数
目前我们准备好了需要被调用的rpc函数及其signature与marshall,然后就可以初始化searpc服务器
#include "searpc-signature.h"
#include "searpc-marshal.h"
static void
init_rpc_service(void)
{
searpc_server_init(register_marshals);
}
创建服务并注册函数
为了能够通过rpc调用到函数,我们需要将创建好的函数定义在服务service中
-
service服务即为一组函数的集合
在init_rpc_service定义service并注册函数
static void
start_rpc_service(void)
{
searpc_server_init(register_marshals);
searpc_create_service("searpc-demo");
searpc_server_register_function("searpc-demo",
searpc_strlen,
"searpc_strlen",
searpc_signature_int__string());
searpc_server_register_function("searpc-demo",
searpc_objlisttest,
"searpc_objlisttest",
searpc_signature_objlist__int_int_string());
}
通过searpc_server_register_function方法,将一个函数注册为rpc函数
gboolean searpc_server_register_function (const char *service, void* func, const gchar *fname, gchar *signature);
-
service:被注册函数所在服务名
-
func:希望注册的函数的指针,即函数的标识符
-
fname:函数名
-
signature:用于获得对应Marshall的标识
接受并处理请求
服务器始终监听端口等待请求,当获取到一个有效请求后,服务器调用searpc_server_call_function(),从而能够实现以下过程:
-
解码json数据
-
根据函数名寻找到所需函数
-
根据参数列表调用函数
-
将结果序列化
gchar* searpc_server_call_function (const char *service, gchar *data, gsize len, gsize *ret_len)
-
service:服务名
-
data:接收到json数据流
-
len:data的长度
-
ret_len:将要返回的数据流长度的指针
但是从客户端获取到了json数据流并不包含服务名,因此需要从传输层解决这个问题
-
可以根据监听不同的sockets判断是哪个服务
-
也可以由客户端在传输时,将服务名插入到json数据之前,因此服务端可以先获取到服务名,再得到json数据
监听socket
在初始化searpc服务器后,创建一个socket;然后,将这个socket绑定到内存中并开始监听
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd < 0)
{
fprintf(stderr, "socket failed: %sn", strerror(errno));
exit(-1);
}
int on = 1;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0)
{
fprintf(stderr, "setsockopt of SO_REUSEADDR error: %sn", strerror(errno));
exit(-1);
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(12345);
ret = bind(listenfd, (struct sockaddr *)&server_addr,
sizeof(server_addr));
ret = listen(listenfd, 5);
当获取到一个正确的请求时,将发送的json数据流读入到pac,然后调用searpc_server_call_function,完成对请求的处理,并得到调用结果;然后同样使用socket将结果发送到客户端
GError *error = NULL;
clilen = sizeof(client_addr);
connfd = accept(listenfd, (struct sockaddr *)&client_addr, &clilen);
if (connfd < 0)
{
fprintf(stderr, "accept failed: %sn", strerror(errno));
continue;
}
pac = read_packet(connfd, buf);
if (pac == NULL)
{
fprintf(stderr, "read packet failed: %sn", strerror(errno));
exit(-1);
}
gsize ret_len;
int fcall_len = ntohs(pac->length);
char *res = searpc_server_call_function("searpc-demo", pac->data, fcall_len,
&ret_len);
pac_ret = (packet *)buf;
pac_ret->length = htons((uint16_t)ret_len);
memcpy(pac_ret->data, res, ret_len);
if (writen(connfd, buf, PACKET_HEADER_LENGTH + ret_len) == -1)
{
fprintf(stderr, "write packet failed: %sn", strerror(errno));
exit(-1);
}
}
其他
由于序列化过程使用的是json形式,根据头文件的引用可以推断出,在这个过程中使用了json-glib库
#include#include



