XML-RPC 實現C++和C#交互


我們通常會面對這樣的問題:整合不同平台或不同類庫,這些類庫可能來自不同的語言,甚至不同的操作系統。 如何解決這類棘手的問題呢?

一.方案介紹

 

         解決不同語言交互的方法有不少,對我了解的windows系統和.NET平台,有以下幾種做法:

  • P/Invoke: 調用native cpp的方法,處在同一個內存區間,訪問方便,但包裝困難,可能拋出運行時異常。
  • 讀寫文件:通過一頭讀文件,一頭寫文件的方式實現交互。諸位別笑,本科時候我就用過這種方式解決問題。
  • 命名管道/socket: 通過字節數組的方式實現交互,命名管道是windows系統提供的功能,可提供安全快捷的程序間交互。socket不依賴於操作系統,只要給定包格式,在任何支持socket的語言平台下都能支持。但缺點也很明顯,如果交互復雜,那么解析這種byte[]數組將會非常復雜而且難以維護。
  • RPC: 又稱之為遠程過程調用,也是我們今天的主角。

      數據即程序,RPC說白了依舊是傳遞數據的過程,只是過程在代碼上更像函數調用。如下圖:

image

      目前主流的RPC有兩種: XML和JSON。 XML是曾經的主角,兼容性更好。但如今移動互聯網要求數據流量要小,而XML的缺點也隨之暴露出來,JSON由於節省數據(大大減少了包頭和標記的開銷),如今變得更受歡迎。新浪微博API,如今全部升級為JSON了。 RPC的實質是http協議,它封裝了底層實現的細節,能讓我們將注意力放在應用邏輯的實現,而非建立連接這樣的問題。

      RPC的優點很多,其中我最喜歡的是它的容器,聲明一個Array,里面可以塞任何你想要的數據,int,string,double,struct甚至另外一個array都可以。當然,不能傳遞抽象類或接口,畢竟不是同一內存區域。

      本文我們只介紹XML-RPC實現C++和C#兩個應用程序之間的交互。JSON的C#版本Jayrock對RPC的支持,尤其是對非ASP.NET環境幾乎沒有,連一篇像樣的文檔都找不到,所以我們僅僅討論XML-RPC。

   

二. 方案實現

               我們打算將C#作為客戶端,C++作為服務器端。

   1. C++的服務器實現

               我們在VS2010中新建C++工程,將附件中的XMLRPC.LIB靜態庫拷入當前工程文件夾,設置當前工程為release模式。同時在C++工程設置中,添加兩個lib引用: xmlrpc.lib, ws2_32.lib

之所以用release模式,是因為在debug模式下xmlrpc.lib庫會出現如下的編譯錯誤:花了很久時間都沒解決,如果有大神能幫助解決這個問題,請一定留言.

1>  All outputs are up-to-date.
1>XmlRpc.lib(XmlRpcServer.obj) : error LNK2038: mismatch detected for '_ITERATOR_DEBUG_LEVEL': value '0' doesn't match value '2' in HelloServer.obj
1>XmlRpc.lib(XmlRpcUtil.obj) : error LNK2038: mismatch detected for '_ITERATOR_DEBUG_LEVEL': value '0' doesn't match value '2' in HelloServer.obj
1>XmlRpc.lib(XmlRpcServerMethod.obj) : error LNK2038: mismatch detected for '_ITERATOR_DEBUG_LEVEL': value '0' doesn't match value '2' in HelloServer.obj
1>XmlRpc.lib(XmlRpcValue.obj) : error LNK2038: mismatch detected for '_ITERATOR_DEBUG_LEVEL': value '0' doesn't match value '2' in HelloServer.obj
1>XmlRpc.lib(XmlRpcDispatch.obj) : error LNK2038: mismatch detected for '_ITERATOR_DEBUG_LEVEL': value '0' doesn't match value '2' in HelloServer.obj
1>XmlRpc.lib(XmlRpcServerConnection.obj) : error LNK2038: mismatch detected for '_ITERATOR_DEBUG_LEVEL': value '0' doesn't match value '2' in HelloServer.obj
1>XmlRpc.lib(XmlRpcSocket.obj) : error LNK2038: mismatch detected for '_ITERATOR_DEBUG_LEVEL': value '0' doesn't match value '2' in HelloServer.obj
1>XmlRpc.lib(XmlRpcSource.obj) : error LNK2038: mismatch detected for '_ITERATOR_DEBUG_LEVEL': value '0' doesn't match value '2' in HelloServer.obj

image1

 

再將對應的庫頭文件加入到項目中,完成后如下圖所示:

image

現在C#程序員普遍已經忘掉了怎么編譯和使用C++靜態庫,我也是其中之一,如果你在上面幾步遇到困難,不妨查看相關資料。

接下來,我們將開始編寫工作代碼:

首先是添加頭文件引用,一般只要引用XMLRPC.H和XMLRPCVALUE.H即可, 同時聲明一個全局的XmlRpcServer 服務。(實在太不習慣C++中不new就能創建實例的語法了)

#include  "RPC/XmlRpc.h"
#include "RPC/XmlRpcValue.h"
#include <iostream>
#include <stdlib.h>

using namespace XmlRpc;

// The server
XmlRpcServer s;

添加主函數:

int main()
{
    /*if (argc != 2) {
        std::cerr << "Usage: HelloServer serverPort\n";
        return -1;
    }*/
    //int port = atoi(argv[1]);
    int port=2567;
    XmlRpc::setVerbosity(3);

    // Create the server socket on the specified port
    s.bindAndListen(port);

    // Enable introspection
    s.enableIntrospection(true);

    // Wait for requests indefinitely
    s.work(-1.0);

    return 0;
}
      設置端口,綁定端口和啟動.這些都沒什么好說的。值得注意的是調試等級,setVerbosity。等級越高,輸出信息越詳細,最高等級可輸出完整的xml交互文件供調試,但會嚴重拖累系統速度的。在你測試功能完畢后,不妨將其設為0。

       接下來我們創建幾個類,來實現服務端功能:

// A variable number of arguments are passed, all doubles, result is their sum.
class Sum : public XmlRpcServerMethod
{
public:
    Sum(XmlRpcServer* s) : XmlRpcServerMethod("Sum", s) {}

    void execute(XmlRpcValue& params, XmlRpcValue& result)
    {
        int nArgs = params.size();
        double sum = 0.0;
        for (int i=0; i<nArgs; ++i)
            sum += double(params[i]);
        result = sum;
    }
} sum(&s);

      所有功能都以繼承於XmlRpcServerMethod類,同時改寫其execute函數。 有點意思的是,這個XmlRPCValue數據類型,是類似C#的Dictionary,或JAVA的hashset。  你可以通過類似C#索引器(字典)的方式,添加或讀取該結構中的內容。比如上面的params[i]。由於代碼簡單,就不多做詳細解釋。

      類后跟了一個實例,sum(&s),這樣就在服務器中注冊了該功能。

    再創建一個類,來實現字符串操作:

// One argument is passed, result is "Hello, " + arg.
class HelloName : public XmlRpcServerMethod
{
public:
    HelloName(XmlRpcServer* s) : XmlRpcServerMethod("HelloName", s) {}

    void execute(XmlRpcValue& params, XmlRpcValue& result)
    {
        std::string resultString = "Hello, ";
        resultString += std::string(params[0]);
        result = resultString;
    }
} helloName(&s);

    也不多做解釋了。

    我們再看一下,怎么存取RPC中的字典和數組,這才是精髓部分:

class StructData : public XmlRpcServerMethod
{
public:
    StructData(XmlRpcServer* s) : XmlRpcServerMethod("GetStruct", s) {}

    void execute(XmlRpcValue& params, XmlRpcValue& result)
    {
         
    XmlRpcValue A;
    A.setSize(2);

    A[0]["a"]=123;
    A[0]["b"]=456;
    A[1]["a"]=43;
    A[1]["b"]=425;
         
        result["a"]=A;
        result["b"]=123;
    }
} structData(&s);

   設置字典時,是不需要指定其size的,但若設定的是數組,則必須使用setsize方法設定其大小。字典的值也可包含另外一個XmlRpcValue 結構體。

   RPC中可以很好的處理字符串,int,double, datetime類型,但枚舉類型的支持並不好,我建議直接傳int.

  

  完成了這三個服務后,我們來編寫C#的客戶端。

 

    2. C# RPC客戶端實現:

         新建一個C#工程,創建以下的接口類:

public interface IRPCMethod
{
  [XmlRpcMethod("HelloName")]
  string HelloName(string Name);

  [XmlRpcMethod("Sum")]
  double Sum(double a,double b);

  [XmlRpcMethod("GetStruct")]
   XmlRpcStruct GetStruct(); 
}

當然,要引用CookComputing.XmlRpc庫。 使用接口類的作用是剝離RPC對系統的影響,讓系統可以“透明的”調用RPC代碼。值得注意的是,名稱必須與C++中的名稱一致,否則會出現找不到方法的異常。

    我們注冊客戶端服務:

var chnl = new HttpChannel(null, new XmlRpcClientFormatterSinkProvider(), null);
        ChannelServices.RegisterChannel(chnl, false);

        var svr = (IRPCMethod)Activator.GetObject(typeof(IRPCMethod), "http://localhost:2567/");
        Console.WriteLine("成功注冊信道");
        string ret = svr.HelloName("haha");
        Console.WriteLine("調用helloName方法:" + ret);
        double result = svr.Sum(23, 18);
        Console.WriteLine("調用Sum方法:" + result.ToString());

        XmlRpcStruct result2 = svr.GetStruct();

   至於result2結構體,你可以通過調試來查看具體的運行結果。

   image

在服務器端,可以看到調用所花費的流量和方法名稱。

三. 其他

這里我們關注一些額外的問題:

1.流量

      RPC的一種場景是本地不同程序調用,這種情況下速度很快。但在跨機器或是移動設備上,就必須考慮流量因素了。XML的“性價比”並不高:

<?xml version="1.0"?>
<methodCall>
    <methodName>echo</methodName>
    <params>
        <param><value><string>Hello Chris</string></value></param>
        <param><value><i4>123</i4></value></param>
        </params>
    </methodCall>

  實際的有用數據,僅占所有字節數的5%,甚至更少,除非是大批量的傳輸本文數據。如果是流量敏感,推薦使用JSON.

    2. 性能

    我們當然要關心,RPC在本機調用會有多快?和哪些因素敏感? 筆者配置是i7 2600K,  8GB DDR, Gbps網絡適配器,VS的debug模式。在執行Sum操作時,一千次耗時4.3ms。 在執行更復雜的結構體傳遞(大概有20個double,三個string,兩個int時), 千次耗時5.8ms。

       因此,可以得知,XML的轉換和解析幾乎不耗時,建立連接后,執行一次在ms量級,對數據結構復雜程度不敏感,因此,若是實時性敏感應用,建議一次性多傳些數據。

  3. 兼容性

       筆者發現,RPC的兼容性並不太好,在和JAVA采用RPC交互時,就遇到了困難,”XML解析異常”。JSON的的兼容性不見得比XML更好,實際操作更是問題多多。涉及RPC的社區,普遍文檔較少,例子不全,出現問題也不好排查,更沒有太多跨語言的RPC實例。因此,如果你能用P/Invoke, 還是推薦用直接調用的做法。 RPC肯定還是比byte流的socket方便很多。

   四 .總結和源代碼下載

       RPC將原本復雜的數據傳輸問題簡化了,使我們從復雜的數據包結構,JAVA和C的double編碼和socket傳輸中脫離出來,提供了更簡單方便的方案。但必須看到,它並不完善,我們只能一步步的探索。 

     另外想問一句,Unity3D的RPC是何種格式?

      有任何問題,歡迎隨時交流。

      源代碼下載:

     http://files.cnblogs.com/buptzym/XML_RPC.rar


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM