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属性找到该契约的包名称,类名,调用的方法等属性。
Contracts.XML
<
Contracts
>
<
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
>
尽管目前在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文件,其意义就是在于使服务器的处理方法与客户端发送的请求实现分离。
系统的基本结构如图,客户端会以JSON方式{“ContractName”:“GetPeopleByAge”,“Params”:[23]}发送请求到服务器,服务器会利用“数据转换层”把接收到的请求转换成Contract对象。然后逻辑转换层会根据该Contract对象调用对应的方法,最后把计算结果以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对输入的数据进行处理。
代码
//
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
<
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的契约,客户端就可以通过这个契约在远程调用此方法获取计算结果。
代码
//
设置Person对象
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设置绑定
XML
<
Contracts
>
<
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实体类来承载契约。
代码
namespace
Model
{
[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;
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;
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对象。
测试
代码
Person实体例
namespace
Model
{
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();
}
public
IList
<
Person
>
GetPersonByAge(
int
age)
{
IList
<
Person
>
personList
=
personDal.GetPersonByAge(age);
if
(personList.Count
!=
0
)
return
personList;
else
return
new
List
<
Person
>
();
}
}
}
测试
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
<
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项目,再加入原代码即可以成功运行)
下载
JAVA与.NET的相互调用——利用JNBridge桥接模式实现远程通讯
JAVA与.NET的相互调用——TCP/IP套接字相互调用的基本架构
JAVA与.NET的相互调用——通过Web服务实现相互调用
对JAVA与.NET开发有兴趣的朋友欢迎加入QQ群:59557329