rserve php,重磅 | RServe源代码解析(上)

原标题:重磅 | RServe源代码解析(上)

本文对Rserve的设计初衷、运行机制、核心对象和方法进行粗浅探讨,尝试为利用Rserve做深度产品开发的开发者提供初始帮助。主要分为四个章节,首先介绍研究Rserve代码需要的基础知识和Rserve的常用名词,第二章介绍了Rserve的工作流程、QAP1消息协议和主要的Command,第三章分析了Rserve服务器端的代码结构和核心函数(内容有些冗长),最后简要罗列Rserve的Java/C++/PHP客户端。

f853f3a8e5c558c57bf490655a42500f.png 简介

作为一个开放的数据分析工具软件,R提供了多种对外接口或扩展机制,1)针对算法扩展,提供了R package开发 (采用R / C/ C++语言),2)针对批次执行或单次大计算量情形,提供了Batch命令行模式,3)对于应用整合,提供了C/Fortran API,方便第三方应用嵌入利用R的分析功能。第3)种模式不仅限定了编程语言,还要求应用开发人员处理R动态库初始化、内存处理、错误机制等技术细节(很容易出bug),针对这样的限制,一种Client-Server的框架还是很有必要的,消除了开发阶段的技术限制和门槛,在运行状态,规避了R动态库初始化、结束等处理的时间开销。RServe就是基于TCP/IP的R语言Client-Server框架的一种实现。

从2002年第一个版本发布,RServe目前已经演化到1.8-4。在RServe中,每个连接(Connection)有一个独立的工作空间(workspace)和工作目录,支持远程链接、安全认证、文件传输等功能。Rserve工具包也实现了Java、C++、PHP等客户端,方便其他编程语言与R数据结构的友好转换,通过SDK接口函数(或通信协议)将需要的数据加载到R,按照客户端指令进行相应的计算,并将结果返回到客户端,所有的数据和对象在连接期间一直是保持(persistent)的(在连接期间是有状态的)。下面的Java代码演示了调用本地Rserve服务,生成一个长度为10的正态分布数组。

b8335737153fced8400ca756feb2bac4.png

如果把RServe作为一个工具,仅需了解如何利用RServe来开发第三方应用,可以看张丹的书或者RServe本身带的例子。但若要深度利用RServe,关心其运行机制与性能,则需要了解RServe的设计初衷及其设计机制,建议首先阅读RServe作者Simon Urbanek在2003年一篇论文(时间久远了一些,但基本思想和技术路线没有变化),再根据需要补充一些前置知识,最后去研究其源代码。本文对RServe源代码进行了粗浅解读,为研究RServe代码提供一些初始帮助(RServe的使用方法本文不再赘述),也欢迎大家补充更正,一起加深理解,更好地利用RServe。

RServe源代码阅读的难度主要来自于3个方面,1)RServe涉及R、动态链接库、网络编程、多线程等多方面的技术知识(以及不同操作系统下的差异细节),要求高;2)RServe的系统性的技术设计资料缺乏,除了Urbanek关于设计机制的论文外,很多技术细节散落在RServe官网的技术说明文档、RServe代码和RServe的版本演化说明文件;3)RServe源代码复杂,文件多,代码长(Rserv.c有5000余行代码),为处理不同操作系统和服务端口的差异,用了大量的宏(如#ifdef unix),在抽象时还用了不少函数指针(源代码分析工具很难提供帮助)。为此,下面首先列举一下背景知识和RServe代码常见术语,然后介绍RServe的设计工作逻辑与对象,在此基础上,对服务端的核心源代码逐步分解,最后简要介绍一下客户端的封装逻辑。 1 背景知识

1.1 研究Rserve代码的前置条件

RServe其本质还是利用R本身的动态链接库的能力,外加Connection、Session、线程/进程的处理逻辑。因此,对RServe的深入了解前提基础是1)R内部的机制;2)网络及Socket编程,3)多线程编程。

了解R的内置数据类型(SEXP),以及R动态链接库提供的一些基础函数(比如R_tryEval),大家可以阅读[R Internal](https://cran.r-project.org/doc/manuals/R-ints.html)、[R External](https://cran.r-project.org/doc/manuals/R-exts.html)文档的相关章节。一个简单的测试条件就是能读懂如下程序: #include #include static void doSplinesExample(); int main(int argc, char *argv[]) { Rf_initEmbeddedR(argc, argv); doSplinesExample(); Rf_endEmbeddedR(0); return 0; } static void doSplinesExample() { SEXP e, result; int errorOccurred; // create and evaluate 'library(splines)' PROTECT(e = lang2(install("library"), mkString("splines"))); R_tryEval(e, R_GlobalEnv, &errorOccurred); if (errorOccurred) { // handle error } UNPROTECT(1); // 'options(FALSE)' ... PROTECT(e = lang2(install("options"), ScalarLogical(0))); // ... modified to 'options(example.ask=FALSE)' (this is obscure) SET_TAG(CDR(e), install("example.ask")); R_tryEval(e, R_GlobalEnv, NULL); UNPROTECT(1); // 'example("ns")' PROTECT(e = lang2(install("example"), mkString("ns"))); R_tryEval(e, R_GlobalEnv, &errorOccurred); UNPROTECT(1); }

对网络编程,特别是socket编程,包括客户端socket、服务器端socket、安全(如会话管理、SSLServerSocket)及非阻塞I/O机制(如缓冲区、通道、就绪选择)等。

另外,建议熟悉一下unix下的fork()函数知识,了解进程创建过程、父子进程间关系等基础知识。

1.2 名词术语

4602aa1728311b3f3e743fe6252a4879.png 2 RServe的核心对象与逻辑

2.1 典型工作流程

RServe典型的调用流程如下图所示:

109366d443cc67e7938e068e225eb07b.png

RServe可以在R环境或命令行(R CMD Rserve)启动,附带对应的配置参数或配置文件(详情请参阅[RServe的说明文档](http://www.rforge.net/Rserve/doc.html))。Rserve代码中调用了R动态库,在RServe启动期间,初始化R环境。

当一个新的连接请求接受后,Rserve用fork()创建一个新的进程(这样每个连接是一个独立的数据空间),并在RServe工作目录(默认是/tmp/Rserv)下为该连接创建一个子工作目录(目录名字格式为conn*X*,其中*X*是连接唯一ID)。相对于Unix操作系统,Rserve在Windows操作系统下有一定的局限性。主要是因为Windows下没有类似fork的命令来快速复制创建一个新的RServe进程。Windows版本的RServe实例在一个时刻只支持一个连接,同一个RServe实例的所有连接有一个共同的工作目录。当然,在Windows下可以用外部的分布式计算框架,启动多个RServe实例来支持多个连接。

RServe完成连接的初始化后,将向客户端返回一个32字节的消息,描述了RServe的能力,每个属性4个字节,采用明文(不能用特殊字符)。消息的格式如下

14e73c6d0e096a34bbd6c7dcdbdae7ee.png

其他属性包括:

· "R151" - R版本号 (这里是 1.5.1)

· "ARpt" - authorization required(需要认证),如果第一个数据包不是CMD_login,连接将被关闭。这里,"pt"=plain text(明文), "uc"=unix crypt, "m5"=MD5

· "K***" - 认证密码 (*** is the key)

· "TLS" - 切换到TLS

当连接关闭后,连接子工作目录如果为空,RServe将自动回收,但若非空,RServe将不会移除该目录,因为该目录中可能存放着其他本地应用(如Web服务器)需要访问的数据资源(比如图片),有本地应用负责该工作目录的移除工作。

2.2 通信协议

QAP是Rserve最初也是最根本的通信协议,下节将详细介绍。从版本1.7.0开始,Rserve还支持HTTP、HTTPS、WebSockets、TLS/QAP(并且支持通过CMD_switch从QAP到TLS/QAP的切换)。

· Rserve HTTP与R内置http server功能类似,只不过通过fork()支持多个并行连接,并通过.http.request()来处理新的连接。

· Websockets主要为HTML5网页浏览器与R建立一个长连接。它由2种协议模式,WebSocket(\*, "QAP") 对原始QAP做了websocket封装,要求浏览器客户端支持二进制的websockets协议(version 01或更高),数据传输效率高,可以通过ArrayBuffers直接进行GPU或CPU计算。WebSocket(*, "text")用明文方式。

另外,RServe允许HTTP server升级到同端口的Websockets(如果设置了http.upgrade.websockets enable)。

2.3 QAP1协议

为提升RServe与客户端间的数据传输效率,Rserve默认采用QAP1 (quad attributes protocol v1) 二进制信息数据协议(message oriented protocol)。发起方(客户端)发出的请求(requeset)和服务器端的响应(response)都采用这种消息形式。每条消息由header part和data part组成 (data part可能为空)。

Header part由16个字节构成,数据结构如下表所示:

5931b91898aaf1f92f24d989243bb50b.png

Data part包括了命令所需的额外参数信息。每个参数包括4字节的header:

· [0] (byte) 参数类型

· [1] (3 byte int) 参数长度

RServe支持的参数类型请参阅Rsrv.h,例如:

· DT_INT (4 bytes) integer

· DT_STRING (n bytes) null terminated string

· DT_BYTESTREAM (n bytes) any binary data

· DT_SEXP R's encoded SEXP

所有的int或double类型在消息传输中都采用Little endian(低位在前,高位在后,Intel等处理器采用)形式,例如int=0x12345678将被表示为char[4]=(0x78,0x56,x34,0x12),转换函数/宏在Rsrv.h中定义。

Header part(16个字节)必须作为一个整体传输,而Data part可以根据需要切分到多个(packets),因此一条消息有 length+16个字节构成 (这里,length是data part的大小)。

2.4 命令(Command)

RServe支持的命令可以分为如下5类:

1. user authentication (用户认证)

2. evaluation of R expressions (R表达式评估)

3. assignment of values to R symbols (赋值)

4. file transfer (文件传输)

5. server shutdown (关闭RServe服务)

客户端Command具体包括([ ]表示参数是可选的):

9fec4ba5165b90e55d37b060a45db6f0.png

CMD_RESP掩码(mask)为所有的响应消息预留。响应消息对应的command包括RESP_OK和RESP_ERR(24 bits)和状态码(8 bits)。

CMD_ctrlEval、CMD_ctrlSource这2个控制命令的执行结果(可能会改变一些变量的数值)将影响所有后续的其他客户端连接。另外,控制命令是异步执行的(返回状态RESP_OK仅仅表示成功进入执行队列,并不代表执行成功),当完成当前客户请求后才可能开始执行,在控制命令执行期间,新的客户连接将被放入队列。因此,控制命令最好是执行时间比较短,避免造成大量的客户请求被缓存。

OC模式是为了保证执行指令的安全而设立,表达式只在闭包(closure)内的环境执行。在此模式下,除了CMD_OCcall,其他命令都被禁止。在一个新连接建立后,服务端并不像普通模式下返回一个32字节的ID字符串,而是返回一个正常的QAP1消息(CMD_OCinit),消息至少有16个字节,保证客户端可以像普通ID字符串类似处理。每个CMD_OCcall是DT_SEXP(对LANGSXP编码)和一个OCref对象构成的闭包,在对表达式计算之前,Rserve需根据OCref对象进行表达式重构(de-reference)。

CMD_switch的作用是从QAP到TLS/QAP的切换。

f95d0a26090a2bf7f845d0a9483d40ce.png

参考文献

1. 张丹. R的极客理想:工具篇,2014,机械工业出版社

2. Simon Urbanek. A Fast Way to Provide R Functionality to Applications, Proceedings of the 3rd International Workshop on Distributed Statistical Computing (DSC 2003), March 20–22, Vienna, Austria

Simon Urbanek

2. https://cran.r-project.org/doc/manuals/R-ints.html

3. https://cran.r-project.org/doc/manuals/R-exts.html

4. Elliotte Rusty Harold,Java网络编程, O'Reilly Media Inc.、中国电力出版社, 2013.

5. W.Richard Stevens,Bill Fenner,Andrew M. Rudoff. UNIX网络编程 卷1 套接字联网API(第3版),北京邮电出版社

6. http://www.rforge.net/Rserve/doc.html

7. http://www.rforge.net/Rserve/dev.html

8. http://blog.csdn.NET/liang13664759/article/details/1771246返回搜狐,查看更多

责任编辑:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值