许下的承诺
前两篇博客Hessian通信案例(java)和Hessian源码分析(java)介绍了Java版的hessian的使用以及源码分析。当时也说过打算写一下C++版的hessian的使用和源码分析,现在就是兑现承诺的时候了。其实我项目中实际用到的是C++版的hessian,java版的hessian是我最初接触用来理解hessian,并进行联调测试的部分。
hessian的官网上也提供了C++版的hessian的实现。只不过当时在网上找到的说法是hessiancpp的活跃度比较低,而且编译出问题很多,但是还是决定自己亲自一试,毕竟”绝知此事要躬行”嘛,嘿嘿!当然也有其他版本的C++实现,比如hessianorb项目。
Hessiancpp编译
现在是回过头来写,故早先编译碰到的问题我已经没办法复现,此处就只记录正确的编译步骤和方法。
下载hessiancpp
到官网下载源码包:https://sourceforge.net/projects/hessiancpp/,我自己当时的版本是hessiancpp-hessaincpp-1.1.0.tar.gz。
解压编译
解压上面下载的tar.gz压缩包
1 | tar zxvf hessiancpp-hessaincpp-1.1.0.tar.gz |
然后进入顶层目录hessiancpp:
1 | cd hessaincpp |
你可以先查看此目录下的Makefile文件,然后试着执行:
1 | make all |
结果,不出所料,编译出错,错误如下:
In file included from hessian_proxy.cpp:19:0:
hessian_proxy.h:31:19: 致命错误:ssl++.h:没有那个文件或目录
编译中断。
make: *** [hessian_proxy.o] 错误 1
可见缺少了一个叫做ssl++.h的头文件,我们再仔细查看Makefile文件,可以看到第一行代码是:
1 | SSLPP=../sslpp |
显然需要另外一个库。这个库就是提供http代理的功能。网上有人碰到这个问题后,选择了libcurl重写一个http代理,据说还可行。
言归正传,我们需要一个叫做sslpp的库。
下载hessian-sslpp
去github下载: https://github.com/ksturner/hessian/tree/master/sslpp
常规操作,解压,然后进入顶层目录,然后查看Makefile文件,然后尝试执行make all编译。果然又出错了。
查看INSTALL文件:
SSLPP was developed on a Fedora Core 2 x86_64 system, using
- GCC 3.3.3
- OpenSSL 0.9.7a
- BOOST 1.31.0
- shared library
可以看到编译SSLPP需要的依赖。我碰到的编译错误是没有安装BOOST库。那么就去安装吧:
1 | yum install boost boost-devel boost-doc |
完事之后再试着执行make all ,make install看看。如果没有错误,那么基本就是可以了,如果碰到了错误,那么没办法,只能一步步解决。
这里主要是需要编译生成的libsslpp.so这个动态库。
继续编译hessiancpp
回到之前对hessiancpp的编译,修改下Makefile文件中SSLPP这个宏的值,根据sslpp的编译修改。然后执行:
1 | make all |
正常情况就不会有问题了,编译成功。在当前目录下生成了一个libhessian.so和一个main可执行程序和main_dyn可执行程序。
大功告成!!!
使用hessiancpp
首先要明白,hessiancpp只是实现了hessian的客户端,具体就是实现了hessian的序列化和反序列算法以及使用sslpp作为一个http代理客户端。故如果要使用hessiancpp,还需要配合一个hessian服务端,这里就用之前博文中介绍过的java 版hessian server作为服务端。
启动hessian server
假设服务端提供了两个接口函数,具体请看下图:
函数功能都是返回”Hello, world,my name is nick!”。
需要注意的是,此时这个服务端的地址为:* http://[IP]:8080/hessian_server/ServerMachineTest *
然后,启动服务端。
修改客户端代码
回到hessiancpp目录下,修改main.cpp。主要修改的代码是:
1 | ...... |
此处稍微对上面的代码做一点解释,具体解释在后面的代码分析。首先根据url构造一个http代理proxy,然后执行call函数,类似java版hessain里边的invoke函数,call函数的参数就是接口方法名以及接口函数的参数个数,以及参数本身。 dump()函数是已经实现好的,主要功能是打印输出。
编译,执行,可以看到结果如下:
至此,完成了c++版的hessian客户端和java版的hessian服务端的通信。
hessiancpp源码分析
hessiancpp目录下可以看到的文件有hessian_input.* hessian_output.* hessain_proxy.* wrappers.* zlibdec.* 等,大概可以猜测到hessian_input.cpp和hessian_output.cpp分别是接收处理和发送处理的代码,也即反序列化和序列化的代码,hessian_proxy.cpp是http代理的代码,负责发送和接收hessian报文。剩下的cpp文件具体再研究。
单步调试
为了进行代码分析,最好的办法是单步跟踪:
1 | gdb ./main |
进入到call函数中:
1 | Object* hessian_proxy::call(const string& method, int argc, ...) throw(io_exception, http_exception) { |
其中关于序列化的代码为:
1 | ..... |
start_call(),set_parameter(),complete_call()三个函数完成了hessian的序列化。具体序列化的过程如下:
1 | string hessian_output::start_call(const string& method_name) { |
其实比较简单,字符’c’可能代表”call”或者”client”,然后是版本号’1’,然后追加了一个字符’0’,然后利用write_ascii_string()函数序列化接口函数名,如上面的”hello”。
然后:
1 | string& hessian_output::set_parameter(string& call, Object* object) { |
然后进入write_object函数:
1 | string& hessian_output::write_object(string& call, Object* object) { |
这是这一步的核心代码。可见,hessian支持基本的几种序列化类型,根据不同的对象类型,调用不同的序列化函数。
此处关键的就是Object这个类,定义在wrappers.h头文件中,Object是基类,后面派生了几种基本的子类:Binary,Boolean,Date,Double,Integer,Long,Map,String…,各个子类里有关于这种类型的对象的具体序列化和反序列化方法。
回到call()函数,通过:
1 | string raw_reply = con.POST(_url, mc, HESSIAN_HTTP_CONTENT_TYPE, HESSIAN_HTTP_USER_AGENT); |
客户端把序列化后的hessian报文通过http发送给服务端,然后等待服务端的应答。
1 | string hessian_reply = con.parse_reply(raw_reply); |
服务端的应答就保存在hessian_reply这个string中,接下来就是反序列化:
1 | ... |
同理,单步跟踪后核心函数式get_result()函数:
1 | Object* hessian_input::get_result() throw(io_exception) { |
反序列化的原理是根据不同的tag值调用相应的类型的反序列函数。
hessian报文
上述c++客户端序列化接口函数和其参数的结果如下:
从服务端返回来的hessian报文如下:
可见,hessian报文有很多不可见的二进制字符!
完了
上面就把c++版的hessaincpp的编译以及使用,以及源码分析都介绍了一遍,由于我现在是回过头来写这篇博客,会觉得很多地方简单,然后可能会觉得某些步骤或者代码分析不重要,就忽略了一部分。实际过程中,如果你碰到hessian,我的博客仅当参考,还需你自己探索。毕竟“绝知此事要躬行!”O(∩_∩)O
Blog:
rebootcat.com (默认)
email: linuxcode2niki@gmail.com
2016-11-22 于杭州
By 史矛革