个人博客:戳我,戳我

许下的承诺

前两篇博客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压缩包

tar  zxvf  hessiancpp-hessaincpp-1.1.0.tar.gz 

然后进入顶层目录hessiancpp:

cd hessaincpp

你可以先查看此目录下的Makefile文件,然后试着执行:

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文件,可以看到第一行代码是:

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库。那么就去安装吧:

yum install boost boost-devel  boost-doc

完事之后再试着执行make all ,make install看看。如果没有错误,那么基本就是可以了,如果碰到了错误,那么没办法,只能一步步解决。

这里主要是需要编译生成的libsslpp.so这个动态库。

继续编译hessiancpp

回到之前对hessiancpp的编译,修改下Makefile文件中SSLPP这个宏的值,根据sslpp的编译修改。然后执行:

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。主要修改的代码是:

......
cout << "starting" << endl;
hessian_proxy proxy("192.168.242.188:8080", "/hessian_server/ServerMachineTest", false);
try
{ Object* hello_ret = proxy.call("hello",0);dump(hello_ret);Integer arg1(42);String  arg2("hahaha");Object* hello2_ret = proxy.call("hello_2",2,&arg1,&arg2);dump(hello2_ret);.....

此处稍微对上面的代码做一点解释,具体解释在后面的代码分析。首先根据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文件具体再研究。

单步调试

为了进行代码分析,最好的办法是单步跟踪:

gdb ./main

进入到call函数中:

Object* hessian_proxy::call(const string& method, int argc, ...) throw(io_exception, http_exception) {
va_list ap;
int narg = 0;// result, connection, hessian output
Object* result = NULL;
sslpp::http_connection con(_hostspec, _use_ssl);
hessian_output hout;// create method call
string mc = hout.start_call(method);// add parameters
va_start(ap, argc);
while (narg++ < argc) {Object* param = va_arg(ap, Object*);hout.set_parameter(mc, param);
}
// clean up vararg
va_end(ap);
// finish method call
hout.complete_call(mc);// call
_num_calls++;
_bytes_out += mc.length();
string raw_reply = con.POST(_url, mc, HESSIAN_HTTP_CONTENT_TYPE, HESSIAN_HTTP_USER_AGENT);
string hessian_reply = con.parse_reply(raw_reply);
_bytes_in += hessian_reply.length();
_call_size_map.insert(std::make_pair(method, hessian_reply.length()));
// test for compressed answer
unsigned short header = ((unsigned short)hessian_reply[1]) << 8;
header += ((unsigned short)hessian_reply[0]);
if (header == GZIP_MAGICK) {
// decompresszlibdec zdec;try {hessian_reply = zdec.decompress(hessian_reply);}catch (zlib_exception& e) {throw io_exception(e.what());}
}
// create a string_input_stream around the reply; note use of auto_ptr
auto_ptr<input_stream> sis(new string_input_stream(hessian_reply));
// read reply
hessian_input hin(sis);
hin.start_reply();
result = hin.get_result();
hin.complete_reply();
return result;
}

其中关于序列化的代码为:

.....
// create method call
string mc = hout.start_call(method);// add parameters
va_start(ap, argc);
while (narg++ < argc) {Object* param = va_arg(ap, Object*);hout.set_parameter(mc, param);
}
// clean up vararg
va_end(ap);
// finish method call
hout.complete_call(mc);

start_call(),set_parameter(),complete_call()三个函数完成了hessian的序列化。具体序列化的过程如下:

string hessian_output::start_call(const string& method_name) {
string mc("c");
mc.append(1, (char)1);
mc.append(1, (char)0);
return write_ascii_string(mc, method_name, 'm');
}

其实比较简单,字符’c’可能代表”call”或者”client”,然后是版本号’1’,然后追加了一个字符’0’,然后利用write_ascii_string()函数序列化接口函数名,如上面的”hello”。

然后:

string& hessian_output::set_parameter(string& call, Object* object) {return write_object(call, object);
}

然后进入write_object函数:

string& hessian_output::write_object(string& call, Object* object) {const char* cls = object->classname2();if (strcmp(cls, "Binary") == 0) {return write_binary(call, dynamic_cast<Binary*>(object));}if (strcmp(cls, "Boolean") == 0) {return write_boolean(call, dynamic_cast<Boolean*>(object));}if (strcmp(cls, "Date") == 0) {return write_date(call, dynamic_cast<Date*>(object));}if (strcmp(cls, "Double") == 0) {return write_double(call, dynamic_cast<Double*>(object));}if (strcmp(cls, "Fault") == 0) {return write_fault(call, dynamic_cast<Fault*>(object));}if (strcmp(cls, "Integer") == 0) {return write_integer(call, dynamic_cast<Integer*>(object));}if (strcmp(cls, "List") == 0) {return write_list(call, dynamic_cast<List*>(object));}if (strcmp(cls, "Long") == 0) {return write_long(call, dynamic_cast<Long*>(object));}if (strcmp(cls, "Map") == 0) {return write_map(call, dynamic_cast<Map*>(object));}if (strcmp(cls, "Null") == 0) {return write_null(call, NULL);
}
if (strcmp(cls, "Ref") == 0) {return write_ref(call, dynamic_cast<Ref*>(object));}if (strcmp(cls, "Remote") == 0) {return write_remote(call, dynamic_cast<Remote*>(object));}if (strcmp(cls, "String") == 0) {return write_string(call, dynamic_cast<String*>(object));}if (strcmp(cls, "Xml") == 0) {return write_xml(call, dynamic_cast<Xml*>(object));}// throw exception, should not get here, reallythrow io_exception(string("hessian_output::write_object(): unknown object class ").append(object->classname()));
}

这是这一步的核心代码。可见,hessian支持基本的几种序列化类型,根据不同的对象类型,调用不同的序列化函数。
此处关键的就是Object这个类,定义在wrappers.h头文件中,Object是基类,后面派生了几种基本的子类:Binary,Boolean,Date,Double,Integer,Long,Map,String…,各个子类里有关于这种类型的对象的具体序列化和反序列化方法。

回到call()函数,通过:

string raw_reply = con.POST(_url, mc, HESSIAN_HTTP_CONTENT_TYPE, HESSIAN_HTTP_USER_AGENT);

客户端把序列化后的hessian报文通过http发送给服务端,然后等待服务端的应答。

string hessian_reply = con.parse_reply(raw_reply);
_bytes_in += hessian_reply.length();
_call_size_map.insert(std::make_pair(method, hessian_reply.length()));

服务端的应答就保存在hessian_reply这个string中,接下来就是反序列化:

 ...hessian_input hin(sis);hin.start_reply();result = hin.get_result();hin.complete_reply();

同理,单步跟踪后核心函数式get_result()函数:

Object* hessian_input::get_result() throw(io_exception) {return read_object();
}Object* hessian_input::read_object() throw(io_exception) {int tag = read();return read_object(tag);
}Object* hessian_input::read_object(int tag) throw(io_exception) {switch (tag) {case 'b':case 'B': return new Binary(read_bytes(tag));case 'T':case 'F': return new Boolean(read_boolean(tag));case 'd': return new Date(read_date(tag));case 'D': return new Double(read_double(tag));case 'f': return new Fault(read_fault(tag));case 'I': return new Integer(read_int(tag));case 'V': return new List(read_list(tag));case 'L': return new Long(read_long(tag));case 'M': return new Map(read_map(tag));case 'N': return new Null();case 'R': return new Ref(read_ref(tag));case 's':case 'S': return new String(read_string(tag));case 'x':case 'X': return new Xml(read_xml(tag));default:throw io_exception(string("hessian_input::readObject(): tag ").append(1, (char) tag).append(" cannot be handled"));      }
}

反序列化的原理是根据不同的tag值调用相应的类型的反序列函数。

hessian报文

上述c++客户端序列化接口函数和其参数的结果如下:

从服务端返回来的hessian报文如下:

可见,hessian报文有很多不可见的二进制字符!

完了

上面就把c++版的hessaincpp的编译以及使用,以及源码分析都介绍了一遍,由于我现在是回过头来写这篇博客,会觉得很多地方简单,然后可能会觉得某些步骤或者代码分析不重要,就忽略了一部分。实际过程中,如果你碰到hessian,我的博客仅当参考,还需你自己探索。毕竟“绝知此事要躬行!”O(∩_∩)O

Blog:

  • rebootcat.com (默认)

  • email: linuxcode2niki@gmail.com

2016-11-22 于杭州
By 史矛革

hessiancpp编译和使用(C++版)相关推荐

  1. 关于编译WebRTC Linux/Android版源码的说明

     关于编译WebRTC Linux/Android版源码的说明 分享下,Linux/Android版本,默认必须在Ubuntu上编译,Debian和Ubuntu是同类型OS,应该也可以,不过我没试 ...

  2. 编译原理第三版课后答案

    编译原理第三版课后答案: https://wenku.baidu.com/aggs/7c792566f5335a8102d22053?index=3&wkts=1669819610140

  3. 编译原理(第3版-王生原)课后习题答案-第三章

    1.构造下列正规式相应的 DFA. (1)1(0|1) *101 (2)1(1010* |1(010)*1) *0 (3)a((a|b)* |ab*a)*b (4)b((ab)* bb)*ab 答案: ...

  4. MSP432编译环境搭建--KEIL版

    MSP432编译环境搭建--KEIL版 前言 一.KEIL5下载 二.安装步骤 1.安装芯片包 2.创建例程 (1)安装SIMPLELINK (2)直接找到官方所给例程 (3)使用例程 三.使用心得 ...

  5. 编译原理第三版清华pdf_清华网络科学与网络空间研究院考研经验分享

    清华网络科学与网络空间研究院考研经验分享 盛世清北,清北硕博摇篮,专注清华北大考研辅导近十年,盛世清北-北京大学考研辅导班开设清华土木工程考研辅导系列课程.上清华北大,就上盛世清北! 考试科目 参考书 ...

  6. gRPC编译和安装——Linux版

    一.安装 1.安装依赖环境 sudo apt-get install pkg-config sudo apt-get install autoconf automake libtool make g+ ...

  7. 编译原理第三版王生原pdf_CS143:编译原理 | 环境搭建HelloWorld

    本文使用 Zhihu On VSCode 创作并发布 本文是本人新开的坑的第一篇博客,另一个坑请看MIT 6.828 实现操作系统.从另一个坑的第一篇复制两段话: 写成博客的目的是防止自己走马观花,花 ...

  8. 嵌入式Linux内核配置、裁剪与编译浅析(ARM版)

    第一部分Linux内核裁减 (1)安装新内核: i)将新内核copy到/usr/src下并解压: #tar -zxvf linux-2.6.38.4.tar.gz ii) 将名为linux的符号链接删 ...

  9. App Inventor 2能编译出苹果iOS版App吗?

    App Inventor 2 编译iOS app? 如题,首先可以明确地说目前并不支持,只支持iOS版AI伴侣进行测试,但是AI伴侣的版本更新一直都是落后于安卓版的,导致测试时会有一些不兼容或一些奇怪 ...

最新文章

  1. Dockerfile文件创建centos:7,配置JDK8的环境变量,与运行springboot的jar包,的镜像
  2. 四天人工智能 python入门体验课_百度深度学习7天打卡营,用Python+AI识别“青你2”小姐姐的高颜值...
  3. php之简单使用数据库
  4. 【Servlet】获取并输出服务器获得的数据
  5. JAVA 从菜鸟成长为大牛的必经之路
  6. 令人期待的php7.4,PHP7.4新特性
  7. ES11新特性_Promise.allSettled---JavaScript_ECMAScript_ES6-ES11新特性工作笔记062
  8. 保持代码工整的 7 个小技巧
  9. 回顾JavsScript对象的克隆
  10. No SOURCES given to target: mpeg
  11. Unity播放序列帧,功能丰富
  12. Android手机打开开发者模式调试App
  13. 【HAVENT原创】kubernetes docker 常用指令
  14. 数字选择器NumberPicker使用教程
  15. 尹稚:中国城镇化战略研究
  16. python处理期货数据_用python中的Pandas库实现一个商品期货网格策略
  17. python 线程通信的几种方式_Python 线程、线程通信、多线程
  18. [Linux]: 开机运行指定的命令/自动运行命令-Ubuntu亲测
  19. 方法简单适合新手!把图片变音乐视频,上月收入7000多
  20. 内推网创始人黄小亮:拒绝猎头的P2P招聘

热门文章

  1. 十六进制字符转化为十进制数字
  2. 使用Python,OpenCV进行形态学操作
  3. 剑指offer: 面试题40. 最小的k个数
  4. 如何在本地安装tomcat、jdk并且配置tomcat环境变量(window7)
  5. 删除单链表中的重复节点(c语言版本)
  6. 在Ubuntu 14.04 64bit上安装思维导图工具XMind
  7. Unity优化手机游戏学习教程
  8. 怎么编写段错误(Segmentation fault)的程序
  9. grep 在HP-UX下的递归查找
  10. SMI in SNMP