Jump to Navigation

一种PHP实现grpc协议调用grpc服务的方案

一种PHP实现grpc协议调用grpc服务的方案

原由

在前面的文章中,曾经提到grpc对PHP语言的支持情况。

主要是因为php-grpc的支持实现不完善,有明显的bug,支持落后于其他主要的开发语言。

从这个方向上考虑,如果要解决的话,需要完善php-grpc扩展,修复bug等,工作量可能比较大。

这段时间考虑到了一种临时替代方案,以解决在PHP中实现grpc协议调用服务的需求。

把使用go语言实现的客户端转换为PHP能调用的动态库

grpc对go语言的支持还是非常完善的,如果能够利用go的实现来支持PHP就好了。

刚开始只是考虑了一下,经过一些查找测试,发现这种方式真的是可行的,实际上已经有人实践过了。

最主要的是,这种方式还是go官方提供的,非常支持的方式,因为go语言有志于替代使用C/C++这类语言开发动态共享链接库。

这个实现方案的机制描述如下,

  • 正常使用go编写grpc客户端封装,能够使用简洁的代码调用客户端封装,完成grpc服务调用。
  • 在go编写的grpc客户端封装源代码中,添加特定的编译指令,让指定的函数能够在C语言中调用。
  • 使用特定的go build参数,把客户端封装编译为.so或者.a库。
  • 编写PHP扩展,调用以上步骤生成的函数,并为PHP提供相应的函数。
  • 在PHP中调用PHP扩展中的函数。

运行时调用过程大概可描述为:php/php-ext/go shared

如果go shared库编译为静态库,则运行时调用过程变为更简单的形式:php/php-ext

以下对实现过程中的几个步骤分别说明。

编写go代码,生成共享库

编写go代码与其他go代码并没有什么不同。

在写完go代码的基础上, 为要在.so共享库中抛出的函数加一行指令,

 package main
 import "C"
 //export grpc_call
 func grcp_call() {}

含义为,要把grpc_call函数抛出,让C语言可以调用到。

注意示例中的import "C"指令,这是//export指令需要的。

第二步,编译这个go源代码

 go build -buildmode=c-shared -o libeg1.so  eg1.go

这样能够编译出来libeg1.so动态库,并且还会生成一个C头文件libeg1.h。

libeg1.h是普通的头文件,里面定义了几个新的数据类型结构体,以及extern了要抛出的函数。

其实,可以通过一个命令,看看其中发生了什么。

 objdump -t  libeg1.so | grep "grpc_call"

这命令会输出类似以下的结果:

0000000000000000 l    df *ABS*  0000000000000000              shrpc_call.cgo2.c
 000000000051b7d0 g     F .text  0000000000000040              _cgoexp_8eedf923a481_shrpc_call
 000000000051b970 g     F .text  0000000000000aa0              main.shrpc_call
 0000000000956610 g     O .rodata        0000000000000008              main.shrpc_call·f
 000000000051b300 g     F .text  0000000000000078              shrpc_call

这是在动态库中的符号表,而前面处理过的shrpc_call已经出现了。

这是前面//export指令的结果,如果没有这个指令,看到的结果就会不同了。

另外,还有几个带有前缀的像main.shrpc_call函数,这其实是原始的go语言抛出的函数(不一定能够直接调用)。

现在已经有下一步需要的所有元素了,像动态库文件,头文件,以及正确的动态库符号表。

如果在同一个包的某个C模块中使用//export的函数,只需要#include "_cgo_exports.h",即可引用。

编写PHP扩展

这一步骤所需要的资料非常多,不再详细描述了,只写和调用go生成的动态库相关的部分。

#include "libeg1.h"

 shrpc_call();

 PHP_FUNCTION(shrpc_call);
 PHP_FE(shrpc_call);

编译成shrpc.so,加载到php.ini中后,即可在PHP语言中调用shrpc_call函数。

完整示例代码

  • go部分,https://git.mydomain.com/liuguangzhao/atapi/blob/master/src/shrpc/shrpc.go
  • php扩展部分,https://git.mydomain.com/liuguangzhao/atapi/blob/master/php-shrpc/shrpc.c
  • 编译指定部分,https://git.mydomain.com/liuguangzhao/atapi/blob/master/Makefile#L25

小结

这种方式,具有几个优点,

  • 能让PHP用上grpc协议直接与grpc服务端通信,不再需要服务端支持其他的协议,像HTTP。
  • 大多数工作全部使用go语言实现,在开发语言这个角度上还比较统一,方便重用代码及后续改进。
  • PHP端部署与调用仍旧非常简单,不像官方支持PHP的方式,需要安装扩展,安装PHP封装等那么麻烦。
  • ...

当然这种方式严重依赖go生成的共享库代码的可靠性,还需要更多的测试。

even more

除了提供给PHP客户端调用grpc服务的功能,是否能够以这种方式为PHP提供实现grpc服务端的功能呢?

应该有可能,但还需要做些工作,做些考虑。

谢谢。

======================= 使用go编写php扩展的探索过程笔记

go写php扩展???

能用不?

编译.go成C库的两种试,

使用动态链接方式

把.go编译成.so,再把.c编译为a.out,

这时a.out非常小,运行时需要.so。

这种方式编译速度稍慢。

使用动态静态链接方式

把.go编译成.a,再把.c编译为a.out,

这时a.out比较大,运行时不需要.so了。

这种方式编译速度挺快的。

注意事项

  • 包名必须是package main;
  • 必须有func main(){} 空函数
  • 要抛出的函数,需要使用//export 。注意//后不能有空格。
  • 如果是非main包中的其他包会怎么样呢? 对于非main包中的函数,即使使用//export也不会抛出。在C中无法调用到。
  • //export名字必须与函数名一致。
  • 如果要使用两个这种库呢?完蛋。.h中的文件冲突,有可能symbol名也冲突。

编译命令,

gosh: go build -v -buildmode=c-archive -o lib/libshrpc.a shrpc ranlib -U lib/libshrpc.a gcc -o tgosh testgosh.c -I./lib -L./lib lib/libshrpc.a -lpthread

gosh2: go build -v -buildmode=c-shared -o lib/libshrpc.so shrpc gcc -o tgosh testgosh.c -I./lib -L./lib -lshrpc -lpthread

常见错误:

/usr/lib/go/pkg/linux_amd64_dynlink/libstd.so: undefined reference to `main.main'

而实际上并不需要-lstd链接这个动态库的。

如果要再深入一个层次来说,这个libstd.so库确实没有main.main(这个符号来自func main(){})。

第二个错误是,在C程序中,程序退出不会崩溃,而在PHP扩展中,PHP程序退出时崩溃。 还是有人遇到这个问题的,完全一致的现象,在gdb中跑的时候,程序退出时崩溃,不用gdb的时候退出返回值倒没有问题,也没有生成coredump文件。

而且竟然解决掉了,https://github.com/golang/go/issues/12582

第三个,archive方式生成的go共享库,没有使用-fPIC,不能用于开发PHP扩展。 relocation R_X86_64_TPOFF32 against `runtime.tlsg' can not be used when making a shared object; recompile with -fPIC

使用需要给 -buildmode=c-archive 加个-linkshared参数,生成的.a文件就是relocation的了,可用于链接进.so了和正常的程序中。测试.so使用正常,并且该.so是一个PHP扩展。

完整的构建命令:go build -v -buildmode=c-archive -linkshared -o lib/libgoshrpc.a shrpc

参考这个issue:https://github.com/golang/go/issues/9652

其他

还有一个编译libstd.so的命令,但后来好像没有用到

go install -v -buildmode=shared -linkshared std 生成所有go核心包的.a库文件,和 该命令会在一个libstd.so共享库文件。

资料:

http://blog.ralch.com/tutorial/golang-sharing-libraries/

go help buildmode

http://www.slideshare.net/do_aki/writing-php-extensions-in-golang

https://github.com/golang/go/issues/11768

Go Execution Modes

*** https://docs.google.com/document/d/1nr-TQHw_er6GOQRsF6T43GGhFDelrAP0NqSS_00RgZQ/edit

添加新评论

Plain text

  • 不允许HTML标记。
  • 自动将网址与电子邮件地址转变为链接。
  • 自动断行和分段。
CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Enter the characters shown in the image.


Main menu 2

Story | by Dr. Radut