JAVA套接字(Socket)101七天系列—第七天【现实生活中的套接字】

时间:2021-10-15 20:43:38

现实生活中的套接字
 1. 介绍

我们到目前为止讨论过的示例已经涵盖了 Java 编程的套接字机制,但在“现实”的一些例子中如何使用它们呢?即便用了多线程和带有连接池,如此简单地使用套接字,在多数应用程序中仍然是不合适的。相反地,在构成您的问题域的模型的其它类中使用套接字可能是明智的。

最近我们在把一个应用程序从大型机/SNA 环境移植到 TCP/IP 环境时就是这样做的。该应用程序的工作是简化零售渠道(例如硬件商店)和金融机构之间的通信。我们的应用程序是中间人。同样地,它必须与一端的零售渠道和另一端的金融渠道通信。我们必须处理客户机通过套接字与服务器进行的交谈,我们还必须把域对象转换成套接字就绪的形式以进行传输。

我们不能在本教程中涵盖这个应用程序的所有细节,但我们将带您浏览一些高层概念。您可以据此对您自己的问题域做些推断。

 2. 客户机端

在客户机端,我们系统中的主角是 SocketClientSocketFacadeStreamAdapter。客户机端的 UML 如下图所示:

JAVA套接字(Socket)101七天系列—第七天【现实生活中的套接字】

我们创建了一个 ClientSocketFacade,它是Runnable 的并且拥有一个Socket 实例。我们的应用程序可以用一个特定的主机 IP 地址和端口号来实例化一个ClientSocketFacade,并在一个新Thread 中运行它。ClientSocketFacaderun() 方法调用 connect()connect() 惰性初始化一个Socket。有了 Socket 实例,我们的 ClientSocketFacade 就调用自己的receive()receive() 将造成阻塞直到服务器在Socket 上发送数据。一旦服务器发送数据,我们的ClientSocketFacade 就将醒来并处理传入的数据。数据的发送是直接的。我们的应用程序可以通过用一个StreamObject 调用send() 方法来简单地告诉它的ClientSocketFacade 把数据发送到服务器。

上述讨论中唯一遗漏的一个是 StreamAdapter。当应用程序告诉ClientSocketFacade 发送数据时,该 Facade 将委派StreamAdapter 的实例处理有关操作。ClientSocketFacade 委派StreamAdapter 的同一个实例处理接收数据的操作。StreamAdapter 把消息加工成最终格式并将它放到SocketOutputStream 上,并以逆过程处理从SocketInputStream 传入的消息。

例如,或许您的服务器需要知道发送中的消息的字节数。StreamAdapter 可以在发送之前计算消息的长度并将它附加在消息的前端。当服务器接收消息时,同样的StreamAdapter 能够剥离长度信息并读取正确数量的字节以构建一个StreamReadyObject


 

 3. 服务器端

服务器端的情形差不多

JAVA套接字(Socket)101七天系列—第七天【现实生活中的套接字】

我们把 ServerSocket 包装进ServerSocketFacadeServerSocketFacadeRunnable 的并且拥有一个ServerSocket 实例。我们的应用程序可以用一个特定的服务器端侦听端口和客户机连接的最大允许数目(缺省值是 50)来实例化一个ServerSocketFacade。应用程序然后在一个新Thread 中运行 Facade 以隐藏ServerSocket 的交互操作细节。

ServerSocketFacade 上的run() 方法调用 acceptConnections()acceptConnections() 创建一个新的ServerSocket,并调用ServerSocket 上的accept() 以造成阻塞直到有客户机请求一个连接。每当有客户机请求连接,我们的ServerSocketFacade 就醒来并通过调用handleSocket() 来把accept() 返回的新Socket 传递给 SocketHandler 的实例。SocketHandler 的分内工作是处理从客户机到服务器的新通道。


 

 4. 业务逻辑

一旦我们正确布置了这些 Socket Facade,实现应用程序的业务逻辑就变得容易多了。我们的应用程序使用 ClientSocketFacade 的一个实例来在 Socket 上把数据发送到服务器并取回响应。应用程序负责把我们的域对象转换成ClientSocketFacade 理解的格式并根据响应构建域对象。


 5. 发送消息到服务器

下图显示我们的应用程序发送消息的 UML 交互作用图:

JAVA套接字(Socket)101七天系列—第七天【现实生活中的套接字】

为简单起见,我们未显示 aClientSocketFacade 向它的 Socket 实例请求其OutputStream 的交互作用(用 getOutputStream() 方法)。一旦我们有了一个 OutputStream 引用,我们就如图所示那样与它交互。请注意 ClientSocketFacade 对我们的应用程序隐藏了套接字交互作用的低级细节。我们的应用程序与aClientSocketFacade 交互,而不与任何更低级类交互,这些类使把字节放到 Socket OutputStream 上更容易。

 6. 接收来自服务器的消息

下图显示我们的应用程序接收消息的 UML 交互作用图:

 

 

JAVA套接字(Socket)101七天系列—第七天【现实生活中的套接字】

 

请注意我们的应用程序在一个 Thread 中运行 aClientSocketFacade。当aClientSocketFacade 启动时,它告诉自己在自己的 Socket 实例的 InputStream 上进行receive()receive() 方法调用 InputStream 自身的 read(byte[])read([]) 方法将造成阻塞直到它接收到数据,并把在 InputStream 接收到的数据放到一个 byte 数组中。当数据到来时,aClientSocketFacadeaStreamAdapteraDomainAdapter 构造(最终地)应用程序能够使用的域对象。接着它把该域对象传回给应用程序。再一次,我们的ClientSocketFacade 对应用程序隐藏了更低级细节,从而简化了应用层。


总结

Java 语言简化了套接字在应用程序中的使用。它的基础实际上是 java.net 包中的 SocketServerSocket 类。一旦您理解了表象背后发生的情况,就能容易地使用这些类。在现实生活中使用套接字只是这样一件事,即通过贯彻优秀的 OO 设计原则来保护应用程序中各层间的封装。我们为您展示了一些有帮助的类。这些类的结构对我们的应用程序隐藏了Socket 交互作用的低级细节 ― 使应用程序能只使用可插入的 ClientSocketFacadeServerSocketFacade。在有些地方(在 Facade 内),您仍然必须管理稍显杂乱的字节细节,但您只须做一次就可以了。更好的是,您可以在将来的项目中重用这些低级别的助手类。

 

参考资料

  • Bruce Eckel 的著作《Java 编程思想》,第 2 版(Prentice Hall,2000 年)提供了深入浅出学习 Java 的好途径。

  • Sun 有讲述套接字的优秀教程。跟随“All About Sockets”链接就行了。

  • 本教程的代码是用 VisualAge for Java,3.5 版开发的。下载您自己的 VisualAge for Java(现在是发行版 4 )副本,或者,如果您己经在使用 VAJ,那就请查看 VisualAgeDeveloper Domain 以获取各种技术协助。

  • 既然您想跟上 Java 套接字编程的发展,那么这篇关于 Visual Age for Java Developer Domain 的文章将教您透过公司防火墙设置对套接字的访问

  • Allen Holub 的 Java 工具箱专栏(位于 JavaWorld)提供有关于 Java 线程的优秀系列,值得一读。请从该系列的“关于线程体系结构的 Java 程序员指南”开始。“面向对象世界的线程,线程池,实现套接字‘accept’循环”是一篇特别好的文章,它更深入地研究关于Thread 带有连接池的问题。我们在本教程中没有研究得这么深,而是使PooledRemoteFileServerPooledConnectionHandler 能让您更容易些地学,但 Allen 谈到的战略是非常适合的。事实上,他用支持多用途、可配置服务器的回调机制的 Java 实现来处理ServerSocket 是一种强大的处理方式。

  • 要为您的 Java 应用程序获取多线程方面的技术协助,请访问位于 developerWorks 的、由 Java 线程专家 Brian Goetz 主持的多线程 Java 编程讨论论坛

  • Siva Visveswaran 在“连接池”(developerWorks,2000 年 10 月)中详细解释了连接带有连接池的问题。