网络互操作性是指实现技术不同的系统可靠、准确地交换信息的能力。这意味着底层硬件、操作系统和实现语言等因素可能因平台而异,但它们不会对这些系统的通信能力产生不利影响。
有几个因素会影响互操作性。这些范围从低级问题(例如原始数据类型使用的字节顺序)到高级技术(例如在很大程度上隐藏其大部分实现细节的 Web 服务)。在本章中,我们将探讨其中的许多因素。
我们首先讨论用于支持原始数据类型的字节顺序。这是数据传输的基础。不同的字节顺序将导致信息解释方式的显着差异。
接下来,我们将讨论 Java 应用程序如何与用不同语言编写的应用程序交互。这些可能是基于 JVM 的语言或与 Java 完全不同的语言。
基本的网络通信构造是套接字。该实体通常在 TCP/IP 环境中运行。我们将演示 Java 套接字如何与用不同语言(特别是 C#)编写的套接字交互。
对互操作性的最重要支持以网络服务为代表的通信标准的形式存在。这些应用程序支持使用标准化中间件的不同系统之间的通信。这些中间件实现隐藏了许多通信细节。
我们将研究以下互操作性主题:
- Java 如何处理字节顺序
- 与其他语言的接口
- 与套接字通信
- 使用中间件实现互操作
因此,让我们从讨论字节顺序及其如何影响互操作性开始。
Java中的字节顺序
有两种类型的字节顺序:big endian和little endian。这些术语指的是多字节数量在内存中的存储顺序。为了说明这一点,请考虑整数在内存中的存储方式。由于整数由 4 个字节组成,因此这些字节被分配到一个 4 字节的内存区域。但是,这些字节可以以不同的方式存储。Big endian 将最重要的字节放在最前面,而 little endian 将最不重要的字节放在最前面。
考虑以下整数的声明和初始化:
int number = 0x01234567;
在以下示例中,假设整数已分配给 address ,则使用 big endian 显示四个字节的内存1000:
地址 | 字节 |
1000 | 01 |
1001 | 23 |
1002 | 45 |
1003 | 67 |
下表显示了如何使用 little endian 存储整数:
地址 | 字节 |
1000 | 67 |
1001 | 45 |
1002 | 23 |
1003 | 01 |
字节序因机器而异,具体如下:
- 基于 Intel 的处理器使用小端
- ARM 处理器可能使用小端或大端
- 摩托罗拉 68K 处理器使用大端
- 摩托罗拉 PowerPC 使用大端
- Sun SPARK 处理器使用大端
发送数据(例如 ASCII 字符串)不是问题,因为这些字节是按连续顺序存储的。对于其他数据类型,例如浮点数和长整型,这可能是一个问题。
如果我们需要知道当前机器支持哪种表示,包中的ByteOder类java.nio可以确定当前的字节顺序。以下语句将显示当前平台的字节序:
System.out.println(ByteOrder.nativeOrder());
对于 Windows 平台,它将显示以下内容:
LITTLE_ENDIAN
在DataOutputStream类的方法自动使用大端。ByteBuffer默认情况下,该类还使用大端。但是,如下所示,可以指定顺序:
ByteBuffer buffer = ByteBuffer.allocate(4096); System.out.println(buffer.order()); buffer.order(ByteOrder.LITTLE_ENDIAN); System.out.println(buffer.order());
这将显示以下内容:
BIG_ENDIAN
LITTLE_ENDIAN
一旦建立,其他方法(例如slice方法)不会更改所使用的字节顺序,如下所示:
buffer.order(ByteOrder.LITTLE_ENDIAN); ByteBuffer slice = buffer.slice(); System.out.println(buffer.order());
输出如下:
LITTLE_ENDIAN
字节序通常在机器上自动处理。然而,当我们在使用不同字节序的机器之间传输数据时,我们可能会遇到问题。传输的字节在目的地的顺序可能是错误的。
网络通常使用大端,也称为网络字节顺序。通过套接字发送的任何数据都应使用大端。在 Java 应用程序之间发送信息时,字节序通常不是问题。但是,在与非 Java 技术交互时,字节序更为重要。
与其他语言的接口
有时,需要访问以不同语言编写的库。虽然这不仅仅是网络问题,但 Java 以多种方式提供支持。与其他语言的直接接口不会通过网络发生,而是发生在同一台机器上。我们将简要研究其中一些接口问题。
如果我们使用另一个 Java 库,那么我们只需要加载这些类。如果我们需要与非 Java 语言交互,那么我们可以使用Java Native Interface ( JNI ) API 或其他一些库。但是,如果这种语言是基于 JVM 的语言,那么过程就会容易得多。
与基于 JVM 的语言接口
在Java虚拟机(JVM)执行Java字节码。然而,这并不是唯一使用 JVM 的语言。其他语言包括以下语言:
- Nashorn:这使用 JavaScript
- Clojure:这是一种 Lisp 方言
- Groovy:这是一种脚本语言
- Scala:这结合了面向对象和函数式编程方法
- JRuby:这是 Ruby 的 Java 实现
- Jthon:这是Python 的 Java 实现
- Jacl:这是Tcl 的 Java 实现
- TuProlog:这是 Prolog 的基于 Java 的实现
可以在https://en.wikipedia.org/wiki/List_of_JVM_languages找到更完整的基于 JVM 的语言列表。使用相同的 JVM 库将有助于代码和库的共享。通常,不仅可以使用以不同的基于 JVM 的语言开发的库,而且还可以从以不同语言开发的类派生。
许多语言已被移植到 JVM,因为使用 JVM 比为不同平台创建多个编译器或解释器更容易。例如,由于这个原因,Ruby 和 Python 有 JVM 实现。这些语言可以利用 JVM 的可移植性及其即时( JIT ) 编译过程。除此之外,JVM 还拥有大量经过良好测试的代码库以供构建。
Nashorn是一个 JavaScript 引擎,它构建在 JVM 之上,并在 Java 8 中添加。这使得 JavaScript 代码可以很容易地集成到 Java 应用程序中。以下代码序列说明了此过程。获取 JavaScript 引擎的实例,然后执行 JavaScript 代码:
try { ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); engine.eval("print('Executing JavaScript code');"); } catch (ScriptException ex) { // Handle exceptions }
该序列的输出如下:
更复杂的 JavaScript 处理是可能的。有关此技术的更多详细信息,请访问https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/。
与非 JVM 语言的接口
访问不同语言代码的常用技术是通过 JNI API。此 API 提供了一种访问 C/C++ 代码的方法。这种方法有据可查,这里不再演示。但是,可以在http://www.ibm.com/developerworks/java/tutorials/j-jni/j-jni.html 中找到对该 API 的一个很好的介绍。
可以从 Java 访问 .NET 代码。一种技术使用 JNI 来访问 C#。有关如何访问 C++、托管 C++ 和 C# 代码的示例,请参见http://www.codeproject.com/Articles/13093/C-method-calls-within-a-Java-program。
通过简单的套接字进行通信
可以使用套接字在以不同语言编写的应用程序之间传输信息。套接字概念并不是 Java 独有的,并且已经在许多语言中实现。由于套接字在 TCP/IP 级别工作,因此它们可以毫不费力地进行通信。
主要的互操作性考虑涉及传输的数据。当两种不同语言之间数据的内部表示存在显着差异时,就会发生不兼容。这可能是由于在内部表示数据类型的方式以及特定数据类型是否存在于另一种语言中时使用大端与小端。例如,在 C 中没有不同的布尔数据类型。它使用整数表示。
在本节中,我们将使用 Java 开发一个服务器,并使用C#开发一个客户端。为了演示套接字的使用,将在这两个应用程序之间传输一个字符串。我们会发现,即使是简单的数据类型(如字符串)的传输也可能比看起来更困难。
Java服务器
服务器在JavaSocket
类中声明,如下所示。它看起来与本书中开发的回声服务器的先前版本非常相似。创建一个服务器套接字,然后阻塞,直到该accept
方法返回一个连接到客户端的套接字:
public class JavaSocket { public static void main(String[] args) { System.out.println("Server Started"); try (ServerSocket serverSocket = new ServerSocket(5000)) { Socket socket = serverSocket.accept(); System.out.println("Client connection completed"); ... socket.close(); } catch (IOException ex) { // Handle exceptions } System.out.println("Server Terminated"); } }
的Scanner
类用来读取被从客户端发送的消息。一个PrintWriter
实例用于回复客户端:
Scanner scanner = new Scanner(socket.getInputStream()); PrintWriter pw = new PrintWriter( socket.getOutputStream(), true);
该nextLine
方法检索一条消息,该消息被显示并发送回客户端:
String message = scanner.nextLine(); System.out.println("Server received: " + message); pw.println(message); System.out.println("Server sent: " + message);
然后服务器将终止。
现在,让我们检查一下 C# 应用程序。
C# 客户端
的CSharpClient
类,如下所示,实现了客户端。C# 在形式和语法上与Java相似,但类库通常不同。我们不会提供代码的详细说明,但会介绍应用程序的重要功能。
该using
语句对应于 Java 中的 import 语句。与Java类似,第一个执行的Main
方法是方法。C# 通常使用与 Java 不同的缩进样式和名称约定:
using System; using System.Net; using System.Net.Sockets; namespace CSharpSocket { class CSharpClient { public static void Main(string[] args) { Console.WriteLine("Client Started"); ... } } }
该IPEndPoint
变量代表的互联网地址,以及Socket
类,你可以期望,代表的插座。该Connect
方法连接到服务器:
IPEndPoint serverAddress = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 5000); Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); clientSocket.Connect(serverAddress);
所述Console
类的Write
方法显示在命令窗口中的信息。在这里,用户被提示发送一条消息到服务器。该ReadLine
方法读入用户输入:
Console.Write("Enter message: "); String message = Console.ReadLine();
该Send
方法将数据传输到服务器。但是,它需要将数据放入字节缓冲区,如下所示。消息和附加的回车/换行符被编码并插入到缓冲区中。需要附加字符,以便服务器可以正确读取字符串并知道字符串何时终止:
byte[] messageBuffer; messageBuffer = System.Text.Encoding.ASCII.GetBytes( message + "\n"); clientSocket.Send(messageBuffer);
该Receive
方法读取服务器的响应。与该Send
方法类似,它需要一个字节缓冲区。此缓冲区创建时大小为 32 字节。这限制了消息的大小,但我们将很快讨论如何克服这个限制:
byte[] receiveBuffer = new byte[32]; clientSocket.Receive(receiveBuffer);
接收缓冲区被转换为字符串并显示。开始和结束括号用于清楚地描述缓冲区:
String recievedMessage = System.Text.Encoding.ASCII.GetString( receiveBuffer); Console.WriteLine("Client received: [" + recievedMessage + "]");
套接字关闭,应用程序终止:
clientSocket.Close(); Console.WriteLine("Client Terminated");
正在运行的客户端/服务器
启动服务器,然后启动客户端。客户端的用户将被提示输入消息。输入消息。消息将被发送,响应将显示在客户端窗口中。
服务器输出显示在此处:
服务器启动
客户端连接完成
服务器收到:消息
服务器发送:消息
服务器终止
客户端显示如下:
客户端启动
输入消息:消息
客户收到:[消息
]
客户端终止
按任意键继续 。. .
您会注意到收到的消息比预期的要大。这是因为客户端的接收字节缓冲区是 32 字节长。此实现使用固定大小的缓冲区。由于服务器响应的大小可能并不总是已知的,因此缓冲区需要足够大以容纳响应。32 的大小用于限制服务器的输出。
可以通过多种方式克服这种限制。一种方法是在字符串末尾附加一个特殊字符,然后使用此标记来构建响应。另一种方法首先发送响应的长度,然后是响应。可以根据响应的长度分配接收缓冲区。
发送字符串对于传输格式化信息很有用。例如,发送的消息可能是 XML 或 JSON 文档。这将有助于传输更复杂的内容。
通过中间件的互操作性
在过去的 20 年中,网络技术有了很大的发展。低级套接字支持为大多数这些技术提供了基础。但是,它们通过多层软件对用户隐藏。这些层被称为中间件。
互操作性是通过中间件实现的,例如 JMI、SOAP 和 JAX-WS,仅举几例。Java EE 版本主要旨在支持这些中间件类型的技术。Java EE 始于servlets,这是一种用于支持网页的 Java 应用程序。它已经发展到包括Java Server Pages ( JSP ) 和最终的Faclets,这两者都隐藏了底层的 Servlet。
这些技术与为用户提供服务有关,无论他们是浏览器还是其他应用程序的人。用户不一定知道服务是如何实现的。通信是通过许多不同的标准实现的,数据经常被封装在语言中立的 XML 文档中。因此,服务器和客户端可以用不同的语言编写并在不同的执行环境中运行,从而促进互操作性。
虽然有许多可用的技术,但有两种常用方法可供使用:RESTful Web 服务和基于 SOAP 的 Web 服务。REpresentational State Transfer Web Services ( RESTful Web Services ) 使用 HTTP 和标准命令(PUT
、POST
、GET
、DELETE
)来支持网页和其他资源的分发。其目的是简化如何创建这些类型的服务。客户端和服务器之间的交互是无状态的。也就是说,先前处理的内容不会影响当前请求的处理方式。
基于 SOAP 的 Web 服务使用简单对象访问协议( SOAP ) 来交换结构化信息。它使用应用层协议,例如 HTTP 和 SMTP,并使用 XML 进行通信。我们将专注于 JAX-RS。
在对REST Web服务的Java API(JAX-RS)是支持发展的API的RESTful服务。它使用一系列注释将资源映射到 Java 实现。为了演示该技术的工作原理,我们将使用 NetBeans 创建一个简单的 RESTful 应用程序。
创建 RESTful 服务
我们将首先创建服务器,然后创建一个简单的基于控制台的应用程序来访问服务器。我们将使用 NetBeans IDE 8.0.2 来开发此服务。NetBeans 可以从https://netbeans.org/downloads/下载。选择Java EE 版。
安装NetBeans 后,启动它,然后从File |创建一个新项目。新建项目...菜单项。这将打开New Project对话框,如下所示。选择Java Web类别和Web 应用程序项目。然后,选择下一步按钮:
为项目命名。在下图中,我们使用SimpleRestfulService
了它的名字。选择一个合适的位置来保存项目,然后选择Next:
在服务器和设置步骤中,选择 GlassFish 服务器和 Java EE7 Web。GlassFish 是我们将用来托管服务的Web 服务器。在上下文路径字段将成为传递给服务器的URL的一部分。再次点击下一步:
我们可以从三种设计模式中选择一种来创建我们的 RESTful 服务。对于此示例,选择第一个Simple Root Resource,然后单击Next:
在“指定资源类”步骤中,完成对话框,如下所示。资源包是放置Java 类的地方。路径用于向用户标识资源。类名字段将是支持资源的 Java 类的名称。完成后,点击完成:
然后 IDE 将生成文件,包括ApplicationConfig.java
和SimpleRestfulService.java
文件。该ApplicationConfig.java
文件用于配置服务。我们的主要兴趣是SimpleRestfulService.java
文件。
在SimpleRestfulService
类中是getHtml
方法,如下复制。其目的是生成对GET
命令的响应。第一个注释将此方法指定HTTP GET
为使用命令时要调用的方法。第二个注释指定此方法的预期输出是 HTML 文本。IDE 生成的 return 语句已替换为简单的 HTML 响应:
@得到
@Produces("文本/html")
公共字符串 getHtml() {
返回
"<html><body><h1>你好,世界!!</body></h1></html>";
}
当使用GET
命令请求服务时,将返回 HTML 文本。所有中间步骤,包括套接字的使用,都是隐藏的,从而简化了开发过程。
测试 RESTful 服务
我们将开发一个客户端应用程序来访问此资源。但是,我们可以使用内置工具测试资源。要测试该服务,请右键单击Project Explorer窗口中的项目名称,然后选择Test RESTful Web Services菜单项。这将打开以下窗口。单击确定:
您可能会在 Windows 上收到如下安全警报。如果发生这种情况,请选择允许访问按钮:
您的默认浏览器将显示测试页面,如下所示。选择packt节点:
资源将出现在右侧,如下所示。这允许我们选择测试方法。由于GET
默认情况下已选择该命令,请单击“测试”按钮:
GET
然后将命令发送到服务器并显示响应,如下所示。
可以使用 JAX_RS执行更复杂的处理。但是,这说明了基本方法。
创建 RESTful 客户端
该RESTful服务可以被任何数调用的是写在各种语言的应用。在这里,我们将创建一个简单的 Java 客户端来访问此服务。
创建一个新项目并从Web Services类别中选择RESTful Java Client选项,如下所示。然后点击下一步:
的名称和位置的步骤会出现对话框,如图下面的截图。我们需要选择 RESTful 资源。我们可以通过单击“浏览...”按钮来执行此操作:
在可用的REST资源会出现对话框,如下图所示。展开我们的 RESTful 项目并选择资源,如下图所示,然后单击OK:
在完成对话框应显示如下。点击上完成:
的RestfulClient
那么类生成。我们对该getHtml
方法感兴趣,如下所示。这将从服务返回 HTML 文本:
public String getHtml() 抛出 ClientErrorException {
WebTarget 资源 = webTarget;
返回资源。
请求(javax.ws.rs.core.MediaType.TEXT_HTML)。
获取(字符串。类);
}
要测试应用程序,请添加以下main
调用该getHtml
方法的方法:
公共静态无效主(字符串 [] args){
RestfulClient restfulClient = new RestfulClient();
System.out.println(restfulClient.getHtml());
}
确保 GlassFish 服务器正在运行,然后执行该程序。输出如下:
<html><body><h1>简单的 Restful 示例</body></h1></html>
虽然我们通常不会在控制台中显示 HTML 文本,但这说明了我们用来从 RESTful 服务获取信息的过程。
概括
在本章中,我们探讨了许多影响网络互操作性的因素。在底层,字节顺序变得很重要。我们了解到系统要么使用大端字节序,要么使用小端字节序。该顺序可以由 Java 应用程序确定和控制。网络通信在传输数据时通常使用大端。
如果我们需要与其他语言交流,我们发现基于 JVM 的语言更容易使用,因为它们共享相同的字节代码库。如果我们需要与其他语言一起工作,那么通常使用 JNI。
套接字不是 Java 独有的概念。它通常用于 TCP/IP 环境,这意味着用一种语言编写的套接字可以轻松地与用不同语言编写的套接字通信。我们使用 Java 服务器和 C# 客户端演示了这种能力。
我们还探索了中间件如何通过抽象大部分低级通信细节来支持互操作性。使用 Web 服务等概念,我们了解到底层套接字交互的细节是隐藏的。我们使用 JAX-RS 演示了这一点,它支持 RESTful 方法,其中 HTTP 命令(例如 GET 和 POST)被映射到特定的 Java 功能。
在企业级应用程序中,网络互操作性是一个重要的考虑因素,在这些应用程序中,企业的功能使用各种技术进行分布。通过使用标准中间件协议和产品,这种互操作性成为可能。