Yar C Framework
(see also: Yar PHP framework, Yar Java framework)
Requirement
- libevent
- msgpack
Install
./configure --with-msgpack=/path-to-msgpack --with-event=/path-to-libevent
make
Example
you can find a example in example folder
Manual
Example
当Yar成功安装以后, example目录下有一个简单的例子, 会有助于学习基于Yar的开发.
服务端编程API
如果你是要基于Yar for C开发一个C服务, 那么Yar_Server是你要关心的API, Yar for C(以下简称Yar), 它基于libevent和msgpack, 为开发者提供了daemon, pre-fork, socket manipulation, logging, pack/unpack等作为一个Server常用的功能.
yar_server_init
int yar_server_init(char *hostname);
初始化Yar_Server, 参数为一个字符串的监听地址, 比如对于IPV4来说类似:localhost:8888, 127.0.0.1:8888, 必须包含端口号.
对于Unix domain socket来说, 类似: /tmp/yar.sock
如果成功返回1, 失败返回0.
我们也能看到, 并没有Server实例返回, 也就是说, 一个进程只能存在一个Server实例. 这点要注意.
yar_server_set_opt
int yar_server_set_opt(yar_server_opt opt, void *val);
设置Server的参数, 可选的参数有:
typedef enum _yar_server_opt {
YAR_STAND_ALONE, //是否单例启动, 一般用作调试的时候, 不会fork worker
YAR_READ_TIMEOUT, //读取请求的超时值
YAR_PARENT_INIT, //父进程初始化Hook
YAR_CHILD_INIT, //Woker进程初始化Hook
YAR_CHILD_USER, //Woker运行的用户名
YAR_CHILD_GROUP, //Woker运行的group名
YAR_CUSTOM_DATA, //回调函数自定义参数
YAR_MAX_CHILDREN, //pre-fork的worker数目, 必须在1~128之间,
//一般设置为和CPU核数相同即可
YAR_PID_FILE, //PID文件的产生路径, 默认为空
YAR_LOG_LEVEL, //LOG的级别, 默认为ALL
YAR_LOG_FILE //日志文件, 日志文件可以为普通文件, 也可以是ronolog
} yar_server_opt;
对于不同的选项, val应该是选项的一级指针, 比如, 设置timeout
#include "yar.h"
Int timeout = 5;
yar_server_setopt(YAR_READ_TIMEOUT, &timeout);
成功返回1, 失败返回0
YAR_STAND_ALONE
是否以单进程启动, 一般用在调试的时候, 因为默认的yar server会以daemon, 并且prefork一些子进程出来, 不利于开发调试.
YAR_READ_TIMEOUT,
请求和处理的超时值, 默认为5s
YAR_PARENT_INIT & YAR_CUSTOM_DATA
Yar server会prefork一些子进程, 这个hook容许我们在yar server prefork完成以后, 对于master进程会做一些初始化的工作, 如果我们设置了这个值, 那么yar在做master进程的初始化工作之后, 也会在master进程调用这个hook, 以方便我们做一些只有master进程需要做的初始化工作.
这个方法的原型是:
typedef void (*yar_init) (void *data);
这里要关心的就data, data是一个void* 指针, 如果我们通过YAR_CUSTOM_DATA设置过一个自定义的数据, 那么这个指针就是我们最初设置的这个数据的指针.
主要用作在整个yar_server运行过程中, 传递一些我们自定义的数据.
YAR_CHILD_INIT
同上, 不过是worker进程初始化的时候被调用. 函数原型也和parent init一样. 也支持自定义数据.
YAR_CHILD_USER & YAR_CHILD_GROUP
如果设置了, 那么我们的worker进程就会尝试setuid/setgid到这个用户/组运行.
YAR_MAX_CHILDREN
要prefork的子进程数目, 一般设置为和CPU的核数相当即可.
YAR_LOG_FILE & YAR_LOG_LEVEl
Yar server默认的时候会输出一些日志信息, 但是当yar以正常模式启动的时候, 会以daemon模式运行, 这样一来就无法输出日志信息到stderr/stdout.
所以, 需要设置一个文件/管道日志输出目的地.
这个选项支持文件, 或者管道, 对于文件自然没有什么好说, 对于管道的话, 主要结合cronolog来使用.
比如:
"|/home/huixinchen/local/cronolog/sbin/cronolog ./yar_server_%M.log"
就是说, 日志输出到当前目录的yar_server_*.log, *是当前分钟, 也就是说日志会以分钟做分割.
而对于log level来说, Yar 分为YAR_DEBUG, YAR_NOTICE, YAR_WARN, YAR_ERROR 5个级别的日志级别.
当要输出日志的级别大于log level则输出, 否不输出, 所以这个选项是用作过滤输出的.
yar_server_get_opt
void * yar_server_get_opt(yar_server_opt opt);
获取某个选项的值
成功返回指针的抽象指针, 失败返回NULL
yar_server_register_handler
int yar_server_register_handler(yar_server_handler *handlers);
注册一个服务函数, 服务函数的原型是:
typedef void (*yar_handler) (yar_request *request, yar_response *response, void *data);
而, yar_server_handler的定义是:
typedef struct _yar_server_handler {
char *name;
int len;
yar_handler handler;
} yar_server_handler;
其中name就是RPC调用的时候的方法名, 比如:
yar_server_handler example_handlers[] = {
{"default", sizeof("default") - 1, yar_handler_example},
{NULL, 0, NULL}
};
那么当客户端的RPC请求default方法的时候, yar_handler_example就会被调用, 去处理这个请求. 以PHP客户端为例:
<?php
$yar = new Yar_Client(“tcp://127.0.0.1”);
$yar->default($args); // yar_handler_example会处理该请求
?>
成功返回1, 失败返回0
yar_server_run
int yar_server_run();
开始运行Server, 这个调用将会开始pre-fork, listening, accpt, process流程.
除非Server被shutdown, 否则这个函数不会返回.
yar_server_shutdown
void yar_server_shutdown();
关闭Server, 这个会停止accpt新请求, worker会在处理完当前请求以后退出
yar_server_destroy
void yar_server_destroy();
销毁Server
客户端编程API
如果, 你是希望请求一个已有的Yar_Server, 那么Yar_Client是你要关心的, 它会请求一个Yar_Server, 并且得到返回.
yar_client_init
yar_client * yar_client_init(char *hostname);
实例化一个Yar_Client, 参数是目的地址
成功返回Yar_Client实例:
struct _yar_client {
int fd;
char *hostname;
yar_client_call call;
};
一般的, 我们不用关心这个结构体的内容, 只需要关心yar_client_call的原型:
typedef yar_response * (*yar_client_call)(yar_client *client, char *method, uint num_args, yar_packager *packager[]);
也就是说, 当得到一个Client实例以后, 我们就可以对Server发起调用, 比如我们调用Server的default方法, 并且有2个参数, 那么就类似
yar_client *client = yar_client_init("tcp://localhost:2222");
yar_response *response = client->call(client, "default", 2, args);
如果, 失败返回NULL, 比如Server不能连接等.
yar_client_destroy
void yar\_client\_destroy(yar\_client \*client);
调用完成后, 销毁一个Yar_Client实例
参数和返回值
Yar采用msgpack作为打包协议, 并且为开发者封装了一系列简单的API来实现对数据的打包解包
解包的相关API
观察之前的
typedef void (*yar_handler) (yar_request *request, yar_response *response, void *data),
我们看到, 当请求到来的时候, 我们Server端注册的处理函数被调用, 其中俩个参数分别为yar_request, 和yar_response.
这个时候, 我们首先要关心的是客户端调用传来了几个参数, 参数分别是什么, 参数的信息保存在request->in里面, 它是个yar_data 指针.
在这里我们不用关心yar_data的结构体的组成是什么, 因为我们只需要调用一系列API就可以得到参数.
Yar协议规定, 所有的参数都打包在一个数组里面, 所以request->in是一个数组.
于是, 如果我要检查当前的参数个数, 那么就调用:
yar_data_type yar_unpack_data_type(const yar_data *data, uint *size);
其中, yar_data_type是一个unum, 可选值是:
typedef enum _yar_data_type {
YAR_DATA_NULL = 1,
YAR_DATA_BOOL,
YAR_DATA_LONG,
YAR_DATA_ULONG,
YAR_DATA_DOUBLE,
YAR_DATA_STRING,
YAR_DATA_MAP,
YAR_DATA_ARRAY
} yar_data_type;
对于, string, map, 和array, yar_unpack_data_type的第一个参数将会返回他们的长度或者是元素个数, 比如对于
map {'k' => 'v'},
那么. 返回的size是1.
现在我们就知道怎么检查参数个数了吧, 假设我们的例子只接受3个参数
uint size = 0;
if (yar_unpack_data_type(request->in, &size) != YAR_DATA_ARRAY || size != 3) {
yar_response_set_error(response, YAR_ERROR, "参数检查失败, 只接受3个参数");
return ;
}
假设我们现在参数已经检查通过, 假设我们接受2个参数, 分别是俩个整数.
那么就通过如下形式获得相关参数内容:
uint arg[2], dummy;
yar_data *tmp;
const yar_data *parameters = yar_request_get_parameters(request);
yar_unpack_iterator *it = yar_unpack_iterator_init(parameters); //生成迭代器
int index = 0;
do {
tmp = yar_unpack_iterator_current(it);
if (yar_unpack_data_type(tmp, &dummy) != YAR_DATA_LONG) {
yar_response_set_error(response, YAR_ERROR, "参数检查失败, 只接受整数");
return ;
}
arg[index++] = *(long *)( yar_unpack_data_value(tmp));
} while(yar_unpack_iterator_next(it));
这样我们的arg就得到了俩个整数参数.
打包的相关API
当我们获得参数, 并且处理完请求以后, 我们需要返回数据给客户端, 这个时候我们就需要和打包的API打交道了. 他们是:
int yar_pack_push_array(yar_packager *packager, uint size);
int yar_pack_push_map(yar_packager *packager, uint size);
int yar_pack_push_null(yar_packager *packager);
int yar_pack_push_bool(yar_packager *packager, int val);
int yar_pack_push_long(yar_packager *packager, long num);
int yar_pack_push_ulong(yar_packager *packager, ulong num);
int yar_pack_push_double(yar_packager *packager, double num);
int yar_pack_push_string(yar_packager *packager, char *str, uint len);
int yar_pack_push_data(yar_packager *packager, yar_data *data);
int yar_pack_push_packager(yar_packager *packager, yar_packager *data);
int yar_pack_to_string(yar_packager *packager, yar_payload *payload);
void yar_pack_free(yar_packager *packager);
不要看函数很多, 但其实很简单. 打包的时候, 是一唯打包顺序, 什么是一维度顺序呢?
比如, 我们要打包如下的格式:
{
a => [b, c]
d => e
}
那么打包的过程就是:
yar_packager *pk = yar_pack_start_map( 2); //我们是一个2个kv的MAP
yar_pack_push_string(pk, "a", 1); //压入第一个key, a
yar_pack_push_array(pk, 2); //压入第一个key对应的一个2个元素的array
yar_push_string (pk, "b", 1); //第一个元素
yar_push_string (pk, "c", 1); //第二个元素 此时数组已经填充完毕
yar_push_string(pk, "d", 1); //压入第二个key d
yar_push_string(pk, "e", 1); //压入第二个key对应的e
来看一个实际的例子(在example/server.c)
这个API返回了一个3个kv的map给客户端, 第一个元素是status值是long 0
第二个元素是parameters, 传回了客户端请求的参数
第三个是一个map, 返回了一些随意的值.
最后调用yar_response_set_retval设置好返回值, 然后释放内存.
总结
最后, Yar的代码中包含了一个Server 和一个Client的例子, 在example目录下.
目前已经实现了PHP端请求C服务, C服务互相请求, 考虑到PHP只能通过HTTP协议提供RPC服务, 所以C请求PHP的, 还暂时没有开放.