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. 最 笨拙也是最復雜的一種傳息方式,就是直接使用“頭文件說明+字段屬性”的方式。 這是一個既原始又麻煩的通訊方式,因為每個契約都要以二進制的方式發送一個請求,就算是同一類契約,隨著參數的不同,每個請求的長度也會發生改變。這樣的 傳息方式雖然是麻煩,但在不同開發語言相互調用的時候卻經常會看到,這可能是因為開發人員對兩種開發語言未能完全熟悉,所以倒置使用這最原始最簡單的開發 方式。
2. 使用XML的信息傳送方式,這是最常見,使用最廣的信息傳遞方式。在絕大多數的開發平臺都會支持XML,所以XML在Web網絡傳訊過程中最為常見。但XML最大的一個缺點就是過于堪輿,耗費大量的傳輸流量。
3. 對 于XML的缺點,JSON應運而生而且發展迅速,JSON本是源于Javascript的,多數只用于B/S的頁面開發,但隨著技術的發展和多個開發語言 的支持,現今到處都可以看JSON的身影。因為JSON既提供一套跨平臺的通訊方式,也免去XML復雜特性,受到各類型開發人員的歡迎。
服務器端開發
- 通訊契約
首先建立一套服務器與客戶端同時接受通訊契約, Contract 的name特性是契約的名稱,服務器會通過此名稱在Contracts.xml文件中找到該契約,然后根據output的package屬性,class屬性,method屬性找到該契約的包名稱,類名,調用的方法等屬性。
<Contract name="GetPersonByAge"> //name為契約名,服務器與客戶端必須同時遵守此契約
<Input>
<Description>獲取Age等于此值的People對象集</Description> //說明此契約內容
</Input>
<Output>
<Package>Manager</Package> //接收到GetPersonByAge請求時所調用的包名稱
<Class>PersonManager</Class> //接收到GetPersonByAge請求時所調用的類名稱
<Method>GetListByAge</Method> //接收到GetPersonByAge請求時所調用的處理方法名稱
</Output>
</Contract>
<Contract name="GetPersonByID">
<Input>
<Description>獲取ID等于此值的People對象</Description>
</Input>
<Output>
<Package >Manager</Package>
<Class>PersonManager</Class>
<Method>GetListByID</Method>
</Output>
</Contract>
</Contracts>
- 以JSON方式實現信息傳送
盡管目前在C/S的開發當中大部分還是使用序列化對象和分節字段的方式進行雙方通訊,但在這個實例當中,在下想以JSON通訊方式為例子來實現。首先,客戶端會使用額定格式的JSON向服務器發送請求:
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文件,其意義就是在于使服務器的處理方法與客戶端發送的請求實現分離。
- 基本結構
系統的基本結構如圖,客戶端會以JSON方式{“ContractName”:“GetPeopleByAge”,“Params”:[23]}發送請求到服務器,服務器會利用“數據轉換層”把接收到的請求轉換成Contract對象。然后邏輯轉換層會根據該Contract對象調用對應的方法,最后把計算結果以JSON方式返回到客戶端。
注意在服務器與客戶端信息交換的過程中,都是使用JSON格式。

- JSON數據轉換
在服務器端,當接到到客戶端請求后,Transfer類負責把接收到的JSON數據轉換成Contract對象。在Transfer里面使用org.json工具包作為JSON的轉化工具,org.json工具包可于以下網址下載http://www.json.org/java/index.html。
而Implement類包含GetResult(Contract contract )方法,其作就是根據contract對象Package,Class,Method等屬性,調用“邏輯轉換層”的對應方法,最后把計算結果返還給InputControl。
服務器端接收請求后就會直接調用InputControl對輸入的數據進行處理。
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 < 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對象里面包中的類的某個方法,獲取計算結果
package Common;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.json.JSONArray;
import Model.*;
public class Implement {
private Contract contract;
private String fullName;
private static Map<String,Object> objects=new HashMap<String,Object>(); //保存對象實體
private static Map<String,Class> classes=new HashMap<String,Class>(); //保存類名
/*
* 先獲取對應的對象,再用反射模式調用對象的方法,獲取計算結果
*/
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獲取運算結果
package Common;
import java.io.DataInputStream;
import Model.Contract;
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();
return null;
}
}
}
最后,系統通過OutputControl類把計算結果返還給客戶端。
package Common;
import java.io.DataOutputStream;
public class OutputControl {
private DataOutputStream outputStream;
public OutputControl(DataOutputStream outputStream){
this.outputStream=outputStream;
}
public void Output(Object data){
try{
outputStream.writeBytes(data.toString());
outputStream.flush();
}catch(Exception ex){
ex.printStackTrace();
}
}
}
//運行系統進行測試
package Common;
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;
try {
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);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
- 邏輯轉換層
現在先開發一個例子作為參考,在完成客戶端開發的時候就可以進行測試。這個例子是先在Manager包里面設置好一個類PersonManager,PersonManager類中包含一個名為GetListByAge的方法。在Contracts.xml文件設置一個名為GetPersonByAge的契約,客戶端就可以通過這個契約在遠程調用此方法獲取計算結果。
package Model;
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.JSONArray;
import org.json.JSONException;
import Model.*;
public class PersonManager {
/*
* 測試數據
*/
private List<Person> GetList(){
List<Person> personList=new ArrayList<Person>();
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<Person> personList=GetList();
List<Person> resultList=new ArrayList<Person>();
for(int n=0;n<personList.size();n++){
if(personList.get(n).getAge()==age)
resultList.add(personList.get(n));
}
JSONArray jsonArray=new JSONArray(resultList);
return jsonArray;
}
}
然后在Contracts.xml設置綁定:
<Contract name="GetPersonByAge"> //契約名稱
<Input>
<Description>獲取Age等于此值的People對象集</Description> //文字說明
</Input>
<Output>
<Package>Manager</Package> //綁定包
<Class>PersonManager</Class> //綁定類
<Method>GetListByAge</Method> //綁定處理方法
</Output>
</Contract>
</Contracts>
綁定以后,在完成客戶端開發的時候就可以進行測試。使用這開發模式的好處在于利用JSON作用數據傳輸的橋梁,解決不同開發平臺之間數據難以同步的問題。使用JSON比XML更容易操作,可以減少傳輸流量,而且受到各開發語言的支持。使用Contracts.xml在服務器綁定處理方式,使服務器的處理方法與客戶端發送的請求實現分離。下面開始介紹一下客戶端的開發。
客戶端開發
客戶端的開發的開發相對簡單,因為契約是使用 {“ContractName”:“GetPeopleByAge”,“Params”:[23]} JSON方式進行傳送,所以先開發一個MessageEntity實體類來承載契約。
{
[DataContract]
public class MessageEntity
{
//契約名稱
[DataMember]
public string ContractName
{
get;
set;
}
//注意參數使用集合的方式來傳送
[DataMember]
public IList<Object> Params
{
get;
set;
}
}
}
然后開發一個MessageManager信息管理器來管理契約的傳送過程,因為Framework4.0里面,未能對JSON數據中集合的轉換提供一個簡單函數,所以在MessageManager里面使用了一個Newtonsoft.Json工具包,該工具包里面對JSON的操作有著強大支持,可以在http://www.codeplex.com/官方網站下載:
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;
lock (networkStream)
n = networkStream.Read(messageByte, 0, 1024);
if (n == 0)
return null;
//根據輸入的type對象,把二進制信息轉化為對應的對象
string jsonMessage = Encoding.ASCII.GetString(messageByte);
//利用Netonsoft.Json工具集將獲取的JSON數據轉化對象
object returnValue = JavaScriptConvert.DeserializeObject(jsonMessage, type);
return returnValue;
}
}
}
下面開發一個GetPersonByAge 契約作為例子:
using System.Collections.Generic;
using System.Text;
using Model;
using Common;
namespace DAL
{
public class PersonDAL
{
/// <summary>
/// 建立MessageEntity對象,注意輸入額定契約名稱及數據參數,獲取查詢結果
/// </summary>
/// <param name="age">Person的年齡</param>
/// <returns>獲取年齡等于此值的Person對象集</returns>
public IList<Person> GetPersonByAge(int age)
{
//先建立一個MessageEntity對象,設定其ContractName及參數集合
//注意ContractName的值必須與服務器端Contracts.xml文件中Contract 項的 name 特性相對應
MessageEntity messageEntity = new MessageEntity();
messageEntity.ContractName = "GetPersonByAge";
messageEntity.Params = new List<Object> { age };
//調用MessageManager的GetMessage方法獲取計算結果
IList<Person> personList = (List<Person>)MessageManager.GetMessage(messageEntity, typeof(List<Person>));
return personList;
}
}
}
PersonDAL類中的GetPersonByAge方法就是把契約封裝在MessageEntity當中,再利用MessageManager把契約發送到服務器端獲取運行結果,然后把結果轉換為JSON,最后利用Netonsoft.Json工具集的JavaScriptConvert類,把JSON轉換成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層
{
public class PersonBLL
{
private PersonDAL personDal;
public PersonBLL()
{
personDal = new PersonDAL();
}
public IList<Person> GetPersonByAge(int age)
{
IList<Person> personList=personDal.GetPersonByAge(age);
if (personList.Count != 0)
return personList;
else
return new List<Person>();
}
}
}
測試
{
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<Person> personList=personBll.GetPersonByAge(23);
if (personList.Count != 0)
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<Person>。
到此處,在下為大家介紹了利用JSON數據實現JAVA與.NET之間TCP/IP相互調用,其實以JSON的方式實現并不是唯一的選擇,只是在下想在慣常的用法之上,利用一下這個另類的方法,至于在開發結構上有不夠周全的地方敬請各位點評。至于以.NET為服務器,JAVA為客戶端的TCP/IP通訊實例與此例子極為相像,在此就不作介紹了。
原代碼 : (由于上傳空間有限,未能將JAVA項目的.metadata一并上傳,請運行時先建立JAVA Project項目,再加入原代碼即可以成功運行)下載