hessiancpp编译和使用(C++版)
個人博客:戳我,戳我
許下的承諾
前兩篇博客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++版)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Hessian源码分析(java)
- 下一篇: 使用docker制作hexo镜像