天天看點

JAVA與.NET的互相調用——TCP/IP互相調用基本架構(附原代碼)

TCP/IP套接字的概念

TCP/IP(傳輸控制協定/網際協定)是網絡互連的通信協定,通過它可以實作各種異構網絡或異種機之間的互聯通信。TCP/IP是Transmission Control Protocol/Internet Protocol的簡寫,中文譯名為傳輸控制協定/網際網路互聯協定,又叫網絡通訊協定,這個協定是Internet最基本的協定、Internet國際網際網路絡的 基礎,簡單地說,就是由網絡層的IP協定和傳輸層的TCP協定組成的。TCP/IP 定義了電子裝置(比如計算機)如何連入網際網路,以及資料如何在它們之間傳輸的标準。TCP/IP是一個四層的分層體系結構。高層為傳輸控制協定,它負責聚 集資訊或把檔案拆分成更小的包。低層是網際協定,它處理每個包的位址部分,使這些包正确的到達目的地。 TCP/IP已成為當今計算機網絡最成熟、應用最廣的互聯協定。Internet采用的就是 TCP/IP協定,網絡上各種各樣的計算機上隻要安裝了TCP/IP協定,它們之間就能互相通信。

TCP/IP套接字通訊的開發

在 衆多的開發語言中,絕大部分的開發語言都支援TCP/IP協定通訊,開發過程也十分相像,先設定好Socket,然後由用戶端發送請求資訊,伺服器連接配接客 戶端接收到請求後再返還資訊。而在.NET系統當中則稍有不同,系統把Socket對象包裝在TcpClient對象内,對Socket對象的生命周期進 行管理。在開發過程當中,伺服器與用戶端的開發語言有所不同的情況經常發生,伺服器是在JDK1.6的環境下進行開發的,客戶卻要求使用.NET開發客戶 端,這往往會令開發人員感到困惑!下面在下使用JAVA為伺服器,.NET為用戶端為例子,為大家介紹一下如何使用TCP/IP協定進行JAVA  .NET之間的互相調用。像TCP/IP實作聊天室這樣的例子很多,開發起來也比較簡單,因為通訊雙方都是使用String來傳送資訊。而在真正建立 ERP、OA、CRM等系統的時候,通訊雙方都必須先建立一套統一的通訊契約,才能實作TCP/IP通訊,下面将為大家介紹一個比較典型的企業資訊通訊實 例。

資訊傳送方式

因為.NET與JAVA各有不同的特性,雙方不可能直接通過的序列化對象來傳輸資訊,常用的資訊交換方式有以下三種:

1. 最 笨拙也是最複雜的一種傳息方式,就是直接使用“頭檔案說明+字段屬性”的方式。 這是一個既原始又麻煩的通訊方式,因為每個契約都要以二進制的方式發送一個請求,就算是同一類契約,随着參數的不同,每個請求的長度也會發生改變。這樣的 傳息方式雖然是麻煩,但在不同開發語言互相調用的時候卻經常會看到,這可能是因為開發人員對兩種開發語言未能完全熟悉,是以倒置使用這最原始最簡單的開發 方式。

<a href="http://blog.51cto.com/attachment/201105/161551360.png" target="_blank"></a>

2. 使用XML的資訊傳送方式,這是最常見,使用最廣的資訊傳遞方式。在絕大多數的開發平台都會支援XML,是以XML在Web網絡傳訊過程中最為常見。但XML最大的一個缺點就是過于堪輿,耗費大量的傳輸流量。

3. 對 于XML的缺點,JSON應運而生而且發展迅速,JSON本是源于Javascript的,多數隻用于B/S的頁面開發,但随着技術的發展和多個開發語言 的支援,現今到處都可以看JSON的身影。因為JSON既提供一套跨平台的通訊方式,也免去XML複雜特性,受到各類型開發人員的歡迎。

伺服器端開發

通訊契約

首先建立一套伺服器與用戶端同時接受通訊契約, Contract 的name特性是契約的名稱,伺服器會通過此名稱在Contracts.xml檔案中找到該契約,然後根據output的package屬性,class屬性,method屬性找到該契約的包名稱,類名,調用的方法等屬性。

&lt;Contracts&gt; 

&lt;Contract name="GetPersonByAge"&gt; //name為契約名,伺服器與用戶端必須同時遵守此契約 

&lt;Input&gt; 

&lt;Description&gt;擷取Age等于此值的People對象集&lt;/Description&gt; //說明此契約内容 

&lt;/Input&gt; 

&lt;Output&gt; 

&lt;Package&gt;Manager&lt;/Package&gt; //接收到GetPersonByAge請求時所調用的包名稱 

&lt;Class&gt;PersonManager&lt;/Class&gt; //接收到GetPersonByAge請求時所調用的類名稱 

&lt;Method&gt;GetListByAge&lt;/Method&gt; //接收到GetPersonByAge請求時所調用的處理方法名稱 

&lt;/Output&gt; 

&lt;/Contract&gt; 

&lt;Contract name="GetPersonByID"&gt; 

&lt;Description&gt;擷取ID等于此值的People對象&lt;/Description&gt; 

  &lt;Package &gt;Manager&lt;/Package&gt; 

&lt;Class&gt;PersonManager&lt;/Class&gt; 

&lt;Method&gt;GetListByID&lt;/Method&gt; 

&lt;/Contracts&gt; 

以JSON方式實作資訊傳送

盡管目前在C/S的開發當中大部分還是使用序列化對象和分節字段的方式進行雙方通訊,但在這個執行個體當中,在下想以JSON通訊方式為例子來實作。首先,用戶端會使用額定格式的JSON向伺服器發送請求: 

{“ContractName”:“GetPeopleByAge”,“Params”:[23]}

ContractName代表着契約名稱,系統會根據此名稱在Contracts.xml檔案中找到Name等于GetPeopleByAge的Contract項。然後在對應Output的子項Package,Class,Method中查找到對應的包,類型和方法。

Params是用戶端傳輸過來的參數,伺服器端會調用對象的方法輸入參數23後,得到計算結果,最後把結果返還到用戶端。

在 這裡有兩點是值得注意的,第一點是JSON中的契約格式是固定的,伺服器與用戶端都必須遵守此契約,在ContractName中輸入是必須對應的契約名 稱,而在Params中輸入的必輸是一個參數的集合,哪怕裡面隻包含有一個參數。第二點是在Contracts.xml檔案,Output裡面的 Package,Class,Method是伺服器端自定義的,它隻是綁定了伺服器端實作GetPersonByAge契約的方法,而這些方法并不是固 定,伺服器可以根據系統的需要而修改。這個做法有點像Struts裡面的Struts.xml檔案和Spring裡面的Bean綁定,其意義就是要遵守 DIP依賴倒置原則,實作依賴注入。

基本結構

<a href="http://blog.51cto.com/attachment/201105/161625414.png" target="_blank"></a>

系統的基本結構如圖,用戶端會以JSON方式{“ContractName”:“GetPeopleByAge”,“Params”:[23]}發送請求到伺服器,伺服器會利用“資料轉換層”把接收到的請求轉換成Contract對象。然後邏輯轉換層會根據該Contract對象調用對應的方法,最後把計算結果以JSON方式傳回到用戶端。

注意在伺服器與用戶端資訊交換的過程中,都是使用JSON格式。

JSON資料轉換

而Implement類包含GetResult(Contract contract )方法,其作就是根據contract對象Package,Class,Method等屬性,調用“邏輯轉換層”的對應方法,最後把計算結果返還給InputControl。

伺服器端接收請求後就會直接調用InputControl對輸入的資料進行處理。

//Contract實體類,包含契約的package,class,method,params等多個屬性 

package Model; 

import org.json.JSONArray; 

public class Contract { 

private String package1; 

private String class1; 

private String method; 

private JSONArray params; 

public void setPackage1(String package1) { 

this.package1 = package1; 

public String getPackage1() { 

return package1; 

public void setClass1(String class1) { 

this.class1 = class1; 

public String getClass1() { 

return class1; 

public void setMethod(String method) { 

this.method = method; 

public String getMethod() { 

return method; 

public void setParams(JSONArray params) { 

this.params = params; 

public JSONArray getParams() { 

return params; 

//把輸入的String字元串轉化為Contract對象 

//在這裡使用org.json工具包作為JSON的轉化工具,org.json工具包可于以下網址下載下傳http://www.json.org/java/index.html 

package Common; 

import java.io.File; 

import java.io.IOException; 

import javax.xml.parsers.DocumentBuilder; 

import javax.xml.parsers.DocumentBuilderFactory; 

import javax.xml.parsers.ParserConfigurationException; 

import Model.Contract; 

import org.json.*; 

import org.w3c.dom.Document; 

import org.w3c.dom.Element; 

import org.w3c.dom.NodeList; 

import org.xml.sax.SAXException; 

public class Transfer { 

private Transfer(){} 

private static String contractName=null; 

private static Contract contract=new Contract(); 

private static JSONObject jsonObject=null; 

public static Contract GetContract(String data) throws Exception, JSONException, ParserConfigurationException, SAXException, IOException{ 

jsonObject=new JSONObject(data); //把字元串轉化為JSONOject對象 

GetContractName(); //擷取契約名稱 

GetProperty(); //擷取契約的package,class,method屬性 

GetParams(); //擷取契約的參數集 

return contract; 

/* 

* 擷取契約對應的包名稱,類名稱,方法名稱 

*/ 

private static void GetProperty() throws Exception{ 

File file = new File("Contracts.xml"); 

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 

DocumentBuilder builder = factory.newDocumentBuilder(); 

Document doc = builder.parse(file); 

NodeList nodeList = doc.getElementsByTagName("Contract"); 

Element contractElement=null; 

for (int i = 0; i &lt; nodeList.getLength(); i++) { 

if(nodeList.item(i).getAttributes().item(0).getNodeValue().equals(contractName)){ 

contractElement=(Element)nodeList.item(i); 

break; 

if(contractElement!=null){ 

Element outputElement=(Element)contractElement.getElementsByTagName("Output").item(0); 

contract.setPackage1(outputElement.getElementsByTagName("Package").item(0).getTextContent()); 

//擷取包名稱 

contract.setClass1(outputElement.getElementsByTagName("Class").item(0).getTextContent()); 

//擷取類名稱 

contract.setMethod(outputElement.getElementsByTagName("Method").item(0).getTextContent()); 

//擷取方法名 

else 

throw new Exception("未能找到對象的契約"); 

* 擷取契約名稱 

private static void GetContractName() throws JSONException{ 

contractName=jsonObject.getString("ContractName"); 

* 擷取輸入參數 

private static void GetParams() throws JSONException{ 

contract.setParams(jsonObject.getJSONArray("Params")); 

//調用Contract對象裡面包中的類的某個方法,擷取計算結果 

import java.lang.reflect.Method; 

import java.util.HashMap; 

import java.util.Map; 

import Model.*; 

public class Implement { 

private Contract contract; 

private String fullName; 

private static Map&lt;String,Object&gt; objects=new HashMap&lt;String,Object&gt;(); //儲存對象實體 

private static Map&lt;String,Class&gt; classes=new HashMap&lt;String,Class&gt;(); //儲存類名 

* 先擷取對應的對象,再用反射模式調用對象的方法,擷取計算結果 

public Object GetResult(Contract contract){ 

this.contract=contract; 

this.fullName=contract.getPackage1()+"."+contract.getClass1(); 

try { 

Object manager=GetObject(); 

Class theClass=classes.get(fullName); 

Method method=theClass.getDeclaredMethod(contract.getMethod(),JSONArray.class); 

return method.invoke(manager, contract.getParams()); 

} catch (Exception e) { 

// TODO Auto-generated catch block 

e.printStackTrace(); 

return null; 

* 多次使用反射建立擷取對象會損耗一定性能,是以此處使用單體模式擷取對應的對象 

private Object GetObject() throws InstantiationException, IllegalAccessException, ClassNotFoundException{ 

if(!objects.containsKey(fullName)){ 

Class theClass = Class.forName(fullName); 

classes.put(fullName,theClass); 

Object manager=theClass.newInstance(); 

objects.put(fullName, manager); 

return objects.get(fullName); 

//直接把接收到的二進制資料轉換成String,然後通過Transfer把String轉化為Contract對象,最後通過Implement擷取運算結果 

import java.io.DataInputStream; 

public class InputControl { 

private DataInputStream inputStream; 

public InputControl(DataInputStream inputStream){ 

this.inputStream=inputStream; 

* 直接把接收到的二進制資料轉換成String,然後通過Transfer把String轉化為Contract對象,最後通過Implement對象擷取運算結果 

public Object GetResult(){ 

byte[] byteMessage=new byte[1024]; //在此處隻擷取測試資料,在真正運作時應使用分批緩存的方式 

try{ 

int n=inputStream.read(byteMessage); 

String message=new String(byteMessage,"ASCII"); 

Contract contract=Transfer.GetContract(message); 

Implement implement=new Implement(); 

Object result=implement.GetResult(contract); 

return result; 

catch(Exception ex){ 

ex.printStackTrace(); 

最後,系統通過OutputControl類把計算結果返還給用戶端。

//把計算結果傳回到用戶端 

import java.io.DataOutputStream; 

public class OutputControl { 

private DataOutputStream outputStream; 

public OutputControl(DataOutputStream outputStream){ 

this.outputStream=outputStream; 

public void Output(Object data){ 

outputStream.writeBytes(data.toString()); 

outputStream.flush(); 

}catch(Exception ex){ 

//運作系統進行測試 

import java.io.*; 

import java.net.*; 

public class Program { 

private static ServerSocket serverSocket; 

public static void main(String[] args) throws ClassNotFoundException { 

// TODO Auto-generated method stub 

Socket socket; 

serverSocket=new ServerSocket(5100); //激活5100端口 

while(true){ //循環捕捉請求 

socket=serverSocket.accept(); 

DataOutputStream outStream=new DataOutputStream(socket.getOutputStream()); //擷取DataOutputStream輸出流 

DataInputStream inputStream=new DataInputStream(socket.getInputStream()); //擷取DataInputStream輸入流 

//調用InputControl對象擷取運算結果 

InputControl inputControl=new InputControl(inputStream); 

Object result=inputControl.GetResult(); 

//調用OutputControl對象輸入運算結果 

OutputControl outputControl=new OutputControl(outStream); 

outputControl.Output(result); 

邏輯轉換層

現在先開發一個例子作為參考,在完成用戶端開發的時候就可以進行測試。這個例子是先在Manager包裡面設定好一個類 PersonManager,PersonManager類中包含一個名為GetListByAge的方法。在Contracts.xml檔案設定一個名 為GetPersonByAge的契約,用戶端就可以通過這個契約在遠端調用此方法擷取計算結果。

//設定Person對象 

public class Person { 

private int id; 

private String name; 

private int age; 

public void setId(int id) { 

this.id = id; 

public int getId() { 

return id; 

public void setName(String name) { 

this.name = name; 

public String getName() { 

return name; 

public void setAge(int age) { 

this.age = age; 

public int getAge() { 

return age; 

//開發PersonManager 

package Manager; 

import java.util.ArrayList; 

import java.util.List; 

import org.json.JSONException; 

public class PersonManager { 

* 測試資料 

private List&lt;Person&gt; GetList(){ 

List&lt;Person&gt; personList=new ArrayList&lt;Person&gt;(); 

Person person1=new Person(); 

person1.setId(0); 

person1.setAge(23); 

person1.setName("Mike"); 

personList.add(person1); 

Person person2=new Person(); 

person2.setId(1); 

person2.setAge(29); 

person2.setName("Leslie"); 

personList.add(person2); 

Person person3=new Person(); 

person3.setId(2); 

person3.setAge(21); 

person3.setName("Jack"); 

personList.add(person3); 

Person person4=new Person(); 

person4.setId(3); 

person4.setAge(23); 

person4.setName("Rose"); 

personList.add(person4); 

return personList; 

* 擷取年齡等于age參數的Person,因為資料将返還給用戶端,是以這時把輸出資料轉化為JSONArray 

public JSONArray GetListByAge(JSONArray jsonList) throws JSONException{ 

int age=jsonList.getInt(0); //因為輸入參數為一個集合params,是以即使隻包括一個參數,也是通過要jsonList的第一個參數來擷取的。 

List&lt;Person&gt; personList=GetList(); 

List&lt;Person&gt; resultList=new ArrayList&lt;Person&gt;(); 

for(int n=0;n&lt;personList.size();n++){ 

if(personList.get(n).getAge()==age) 

resultList.add(personList.get(n)); 

JSONArray jsonArray=new JSONArray(resultList); 

return jsonArray; 

然後在Contracts.xml設定綁定

&lt;Contract name="GetPersonByAge"&gt; //契約名稱 

&lt;Description&gt;擷取Age等于此值的People對象集&lt;/Description&gt; //文字說明 

&lt;Package&gt;Manager&lt;/Package&gt; //綁定包 

&lt;Class&gt;PersonManager&lt;/Class&gt; //綁定類 

&lt;Method&gt;GetListByAge&lt;/Method&gt; //綁定處理方法 

綁定以後,在完成用戶端開發的時候就可以進行測試。使用這開發模式的好處在于利用JSON作用資料傳輸的橋梁,解決不同開發平台之間資料難以同步的 問題。使用JSON比XML更容易操作,可以減少傳輸流量,而且受到各開發語言的支援。使用Contracts.xml在伺服器綁定處理方式,遵守了 DIP的開發原則,使伺服器的處理方法與用戶端發送的請求實作分離。下面開始介紹一下用戶端的開發。

用戶端開發

用戶端的開發的開發相對簡單,因為契約是使用   {“ContractName”:“GetPeopleByAge”,“Params”:[23]}    JSON方式進行傳送,是以先開發一個MessageEntity實體類來承載契約。

namespace Model 

[DataContract] 

public class MessageEntity 

//契約名稱 

[DataMember] 

public string ContractName 

get; 

set; 

//注意參數使用集合的方式來傳送 

public IList&lt;Object&gt; Params 

然後開發一個MessageManager資訊管理器來管理契約的傳送過程,因為Framework4.0裡面,未能對JSON資料中集合的轉換提供一個簡單函數,是以在MessageManager裡面使用了一個Newtonsoft.Json工具包,該工具包裡面對JSON的操作有着強大支援,可以在http://www.codeplex.com/官方網站下載下傳

using System; 

using System.Collections.Generic; 

using System.Linq; 

using System.Text; 

using System.Net.Sockets; 

using System.Runtime.Serialization.Json; 

using System.IO; 

using System.Threading; 

using Model; 

using Newtonsoft.Json; 

namespace Common 

public class MessageManager 

private static TcpClient _tcpClient; 

//設定tcpClient對象 

public static TcpClient TcpClient 

set { _tcpClient = value; } 

//此處隻使用靜态方法實作資料傳送,發送請求後使用Thread.Sleep等待運算結果,這樣存在一定風險,也會降低效率 

//在大型的開發當中應該進一步改善,把資訊發送與資訊接收分開處理 

public static object GetMessage(MessageEntity message, Type type) 

NetworkStream networkStream = _tcpClient.GetStream(); 

//利用DataContractJsonSerializer将MessageEntity對象實作序列化,發送到伺服器 

DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(MessageEntity)); 

lock (networkStream) 

jsonSerializer.WriteObject(networkStream, message); 

networkStream.Flush(); 

Thread.Sleep(500); 

//擷取回傳資訊,這裡設定接收值1024個位元組 

//在實際的開發當中應該使用分批緩存的方式實作資料接收 

byte[] messageByte = new byte[1024]; 

int n = 0; 

n = networkStream.Read(messageByte, 0, 1024); 

if (n == 0) 

//根據輸入的type對象,把二進制資訊轉化為對應的對象 

string jsonMessage = Encoding.ASCII.GetString(messageByte); 

//利用Netonsoft.Json工具集将擷取的JSON資料轉化對象 

object returnValue = JavaScriptConvert.DeserializeObject(jsonMessage, type); 

return returnValue; 

下面開發一個GetPersonByAge 契約作為例子

using Common; 

namespace DAL 

public class PersonDAL 

/// &lt;summary&gt; 

/// 建立MessageEntity對象,注意輸入額定契約名稱及資料參數,擷取查詢結果 

/// &lt;/summary&gt; 

/// &lt;param name="age"&gt;Person的年齡&lt;/param&gt; 

/// &lt;returns&gt;擷取年齡等于此值的Person對象集&lt;/returns&gt; 

public IList&lt;Person&gt; GetPersonByAge(int age) 

//先建立一個MessageEntity對象,設定其ContractName及參數集合 

//注意ContractName的值必須與伺服器端Contracts.xml檔案中Contract 項的 name 特性相對應 

MessageEntity messageEntity = new MessageEntity(); 

messageEntity.ContractName = "GetPersonByAge"; 

messageEntity.Params = new List&lt;Object&gt; { age }; 

//調用MessageManager的GetMessage方法擷取計算結果 

IList&lt;Person&gt; personList = (List&lt;Person&gt;)MessageManager.GetMessage(messageEntity, typeof(List&lt;Person&gt;)); 

PersonDAL 類中的GetPersonByAge方法就是把契約封裝在MessageEntity當中,再利用MessageManager把契約發送到伺服器端擷取 運作結果,然後把結果轉換為JSON,最後利用Netonsoft.Json工具集的JavaScriptConvert類,把JSON轉換成 Person對象。

測試

Person實體例 

public class Person 

private int _id; 

private string _name; 

private int _age; 

public int id 

get { return _id; } 

set { _id = value; } 

public int age 

get { return _age; } 

set { _age = value; } 

public string name 

get { return _name; } 

set { _name = value; } 

直接調用DAL層 

namespace BLL 

public class PersonBLL 

private PersonDAL personDal; 

public PersonBLL() 

personDal = new PersonDAL(); 

IList&lt;Person&gt; personList=personDal.GetPersonByAge(age); 

if (personList.Count != 0) 

return new List&lt;Person&gt;(); 

測試 

class Program 

private static TcpClient tcpClient = new TcpClient(); 

static void Main(string[] args) 

tcpClient.Connect("127.0.0.1", 5100); 

MessageManager.TcpClient = tcpClient; 

PersonBLL personBll = new PersonBLL(); 

IList&lt;Person&gt; personList=personBll.GetPersonByAge(23); 

Console.WriteLine(personList.Count.ToString()); 

Console.ReadKey(); 

}

注意測試是輸入的查詢條件轉換成JSON後是 {“ContractName”:“GetPeopleByAge”,“Params”:[23]},而這種  “ContractName":  "契約名","Params": {參數,參數,...}    傳送格式是固定不可改變的。當擷取查詢結果  "[{\"id\":0,\"age\":23,\"name\":\"Mike\"},{\"id\":3,\"age\":23,\"name\":\"Rose\"}] 後 ,MessageManager将通過Newtonsoft.Json把返還值轉換為List&lt;Person&gt;。

到此處,在下為大家介紹了利用JSON資料實作JAVA與.NET之間TCP/IP互相調用,其實以JSON的方式實作并不是唯一的選擇,隻是在下 想在慣常的用法之上,利用一下這個另類的方法,至于在開發結構上有不夠周全的地方敬請各位點評。至于以.NET為伺服器,JAVA為用戶端的TCP/IP 通訊執行個體與此例子極為相像,在此就不作介紹了。

原代碼 : (由于上傳空間有限,未能将JAVA項目的.metadata一并上傳,請運作時先建立JAVA Project項目,再加入原代碼即可以成功運作)

<a href="http://files.cnblogs.com/leslies2/JAVA%E4%B8%8E.NET%E7%9A%84%E7%9B%B8%E4%BA%92%E8%B0%83%E7%94%A8.rar">下載下傳</a>

<a href="http://www.cnblogs.com/leslies2/archive/2011/04/24/2024707.html">JAVA與.NET的互相調用——通過Web服務實作互相調用(附原代碼)</a>

本文轉自 leslies2  51CTO部落格,原文連結:http://blog.51cto.com/79100812/560654