C# WebService調用的三大難題
一、用C#實現WebService是相當簡單的事情,我們只要創建一個Web服務程序,在方法名上面加上[WebMethod],部署到IIS上,就能像訪問Web站點一樣訪問WebService。
用C#編寫客戶端時,只需要將WebService添加到引用,就能像調用本地方法一樣去C# WebService調用。像這樣的例子也比比皆是,在這就不多講。
二、用C++實現WebService,一般會用到gsoap,具體方法見:http://www.cppblog.com/qiujian5628/archive/2008/06/19/54019.html
三、當做完了這些之后,并不代表WebService就能相互通訊了,現在我簡單列舉一下問題:
1、C#提供的WebService的URL一般形如:http://localhost/WebService.asmx,但是,C++能提供的只能是:http://localhost/。C++做客戶端的時候調用沒有問題,但是當C#做客戶端的時候,引用C++提供的RUL時,會提示沒用執行方法(HTTP GET method not implemented)。做C#開發的大部分會認為C++方提供的不是WebService,或者說提供的WebService根本就不全,都不帶.asmx文件。做C++開發的會認為他傳輸的數據符合soap協議,靠http傳輸數據,他就是WebService。
2、當我們解決了第一步后,緊接著會發現另外一個問題。當我們需要傳輸自定義數據類型時(在C++中稱結構體,在C#中稱實體),從C++返回的信息中,C#無法構建出實體類。
3、當傳輸的信息中帶有中文字符時,亂碼滿天飛。
四、為了解決這些問題,我們先簡單了解一下WebService。
Web Service互操作協議棧:
〈A〉、服務發現 (UDDI)
〈B〉、服務描述(WSDL)
〈C〉、服務調用(SOAP)
〈D〉、消息編碼 (XML)
〈E〉、傳輸網絡層(HTTP, TCP/IP)
其中WSDL描述WebService都有什么方法、方法有什么參數,什么返回值等。SOAP(簡單對象訪問協議(Simple Object Access Protocol)是一種輕量的、簡單的、基于XML的協議。傳輸的數據就需要遵循這個協議。我比較簡單得認為傳輸的數據需要遵循這種格式。
借用微軟的這個圖描述下WebService的調用過程:
五、開始解決問題。作為.NET開發人員,我們根本就接觸不到底層的東西,全被封裝了。
C++做的確實是WebService,只是他們需要給提供一個描述文檔,即.WSDL文件。使用.NET提供的wsdl.exe工具,使用命令:wsdl /o: c:\webservice.cs c:\webservice.wsdl。通過webservice.wsdl文檔,生成代理類,將代理類寫入webservice.cs文件中。我們拷貝這個cs文件到項目中,將URL指向http://localhost/,就能像以往那樣使用WebService了。
當出現無法傳遞復雜類型數據時,是因為使用gsoap生成的wsdl文件與.Net中生成的wsdl文件不一樣。具體代碼如下:
- 〈!--operationresponseelement--〉
- 〈elementname="result"〉
- 〈complexType〉
- 〈sequence〉
- 〈elementname="a"type="xsd:int"
- minOccurs="1"maxOccurs="1"/〉
- 〈elementname="b"type="xsd:int"
- minOccurs="1"maxOccurs="1"/〉
- 〈/sequence〉
- 〈/complexType〉
- 〈/element〉
- 以上為gsoap生成的。返回實體result,
- 實體有兩個屬性:a,b。
- 〈s:elementname="TestResponse"〉
- 〈s:complexType〉
- 〈s:sequence〉
- 〈s:elementminOccurs="0"maxOccurs="1"
- name="TestResult"type="tns:result"/〉
- 〈/s:sequence〉
- 〈/s:complexType〉
- 〈/s:element〉
- 〈s:complexTypename="result"〉
- 〈s:sequence〉
- 〈s:elementminOccurs="1"maxOccurs="1"
- name="a"type="s:int"/〉
- 〈s:elementminOccurs="1"maxOccurs="1"
- name="b"type="s:int"/〉
- 〈/s:sequence〉
- 〈/s:complexType〉
- 以上是.NET生成的。
- 在下面的文件中,多出
- 〈s:elementname="TestResponse"〉
- 〈s:complexType〉
- 〈s:sequence〉
- 〈s:elementminOccurs="0"maxOccurs="1"
- name="TestResult"type="tns:result"/〉
- 〈/s:sequence〉
- 〈/s:complexType〉
- 〈/s:element〉
這個便是.NET中用來構造實體的。當我們出現情況4.2時,gsoap中盡量使用.NET生成的wsdl文檔,生成.h文件,以避免C++中的結構無法在C#中轉換成實體。
第三個問題,我們是通過將中文轉換成16進制后傳輸過來,然后再轉換成中文。下面提供C#轉換的代碼:
- ///〈 summary 〉
- ///從16進制轉換成漢字
- ///〈 /summary 〉
- ///〈 paramname="hex" 〉〈 /param 〉
- ///〈 returns 〉〈 /returns 〉
- publicstaticstringGetChsFromHex(stringhex)
- {
- if(hex==null)
- thrownewArgumentNullException("hex");
- if(hex.Length%2!=0)
- {
- hex+="20";//空格
- //thrownewArgumentException
- ("hexisnotavalidnumber!","hex");
- }
- //需要將hex轉換成byte數組。
- byte[]bytes=newbyte[hex.Length/2];
- for(inti=0;i〈 bytes.Length;i++)
- {
- try
- {
- //每兩個字符是一個byte。
- bytes[i]=byte.Parse(hex.Substring(i*2,2),
- System.Globalization.NumberStyles.HexNumber);
- }
- catch
- {
- //Rethrowanexceptionwithcustommessage.
- thrownewArgumentException("
- hexisnotavalidhexnumber!","hex");
- }
- }
- //獲得GB2312,ChineseSimplified。
- System.Text.Encodingchs=System.Text.Encoding.
- GetEncoding("gb2312");
- returnchs.GetString(bytes);
- }
- ///〈 summary 〉
- ///從漢字轉換到16進制
- ///〈 /summary 〉
- ///〈 paramname="s" 〉〈 /param 〉
- ///〈 returns 〉〈 /returns 〉
- publicstaticstringGetHexFromChs(strings)
- {
- if((s.Length%2)!=0)
- {
- s+="";//空格
- //thrownewArgumentException("
- sisnotvalidchinesestring!");
- }
- System.Text.Encodingchs=System.Text.
- Encoding.GetEncoding("gb2312");
- byte[]bytes=chs.GetBytes(s);
- stringstr="";
- for(inti=0;i〈 bytes.Length;i++)
- {
- str+=string.Format("{0:X}",bytes[i]);
- }
- returnstr;
- }
注:以上來轉換代碼源于網絡,C++中轉換的代碼也可以在網上找到,C++與C# WebService調用經過以上的步驟就能基本實現。
三大難題到此結束,其實在整個過程中還有個最大的難題,那就是人與人的交流。因為一方使用C++,一方使用C#,語言不同,各自想問題的方式也不一樣,所以需要相互理解,相互站在對方的角度想問題。多交流、多溝通才是解決問題之道。請不要抱怨C#弱智,也請不要怪C++繁瑣,語言既然存在則有他的價值。
【編輯推薦】