【转】Java socket - 套接字基础

转自:http://caihx.javaeye.com/blog/605143

套接字基础

1. 介绍

多数程序员,不管他们是否使用 Java语言进行编码,都不想知道很多关于不同计算机上的应用程序彼此间如何通信的低级细节。程序员们希望处理更容易理解的更高级抽象。Java程序员希望能用他们熟悉的 Java 构造,通过直观接口与对象交互。

套接字在两个领域中都存在 ―我们宁愿避开的低级细节和我们更愿处理的抽象层。本教程讨论的低级细节将只限于理解抽象应用程序所必须的部分。

2. 计算机组网 101

计算机以一种非常简单的方式进行相互间的操作和通信。计算机芯片是以 1 和 0的形式存储并传输数据的开―闭转换器的集合。当计算机想共享数据时,它们所需做的全部就是以一致的速度、顺序、定时等等来回传输几百万比特和字节的数据流。每次想在两个应用程序之间进行信息通信时,您怎么会愿意担心那些细节呢?

为免除这些担心,我们需要每次都以相同方式完成该项工作的一组包协议。这将允许我们处理应用程序级的工作,而不必担心低级网络细节。这些成包协议称为 协议栈(stack) 。TCP/IP 是当今最常见的协议栈。多数协议栈(包括TCP/IP)都大致对应于国际标准化组织(International Standards Organization,ISO)的开放系统互连参考模型(Open Systems Interconnect Reference Model,OSIRM)。OSIRM认为在一个可靠的计算机组网中有七个逻辑层(见图)。各个地方的公司都对这个模型某些层的实现做了一些贡献,从生成电子信号(光脉冲、射频等等)到提供数据给应用程序。TCP/IP映射到 OSI 模型中的两层的情形如图所示。

我们不想涉及层的太多细节,但您应该知道套接字位于什么地方。

osimodel3

3. 套接字位于什么地方

套接字大致驻留在 OSI 模型的 会话层 (见图)。会话层夹在其上面向应用的层和其下的实时数据通信层之间。会话层为两台计算机之间的数据流提供管理和控制服务。作为该层的一部分,套接字提供一个隐藏从导线上获取比特和字节的复杂性的抽象。换句话说,套接字允许我们让应用程序表明它想发送一些字节即可传输数据。套接字隐藏了完成该项工作的具体细节。

当您打电话时,您的声音传到传感器,传感器把它转换成可以传输的电数据。电话机是人与电信网络的接口。您无须知道声音如何传输的细节,只要知道想打电话给谁就行了。同样地,套接字扮演隐藏在未知通道上传输 1 和 0 的复杂性的高级接口的角色。

osimodel2

4. 把套接字暴露给应用程序

使用套接字的代码工作于 表示层 。表示层提供 应用层 能够使用的信息的公共表示。假设您打算把应用程序连接到只能识别 EBCDIC 的旧的银行系统。应用程序的域对象以 ASCII 格式存储信息。在这种情况下,您得负责在表示层上编写把数据从 EBCDIC 转换成 ASCII 的代码,然后(比方说)给应用层提供域对象。应用层然后就可以用域对象来做它想做的任何事情。

您编写的套接字处理代码只存在于表示层中。您的应用层无须知道套接字如何工作的任何事情。

osimodel

5. 什么是套接字?

既然我们已经知道套接字扮演的角色,那么剩下的问题是:什么是套接字?Bruce Eckel 在他的 《Java 编程思想》 一书中这样描述套接字:

套接字是一种软件抽象,用于表达两台机器之间的连接“终端”。对于一个给定的连接,每台机器上都有一个套接字,您也可以想象它们之间有一条虚拟的“电缆”,“电缆”的每一端都插入到套接字中。当然,机器之间的物理硬件和电缆连接都是完全未知的。抽象的全部目的是使我们无须知道不必知道的细节。

简言之,一台机器上的套接字与另一台机器上的套接字交谈就创建一条通信通道。程序员可以用该通道来在两台机器之间发送数据。当您发送数据时,TCP/IP协议栈的每一层都会添加适当的报头信息来包装数据。这些报头帮助协议栈把您的数据送到目的地。好消息是Java语言通过 “流” 为您的代码提供数据,从而隐藏了所有这些细节,这也是为什么它们有时候被叫做 流套接字(streaming socket) 的原因。

把套接字想成两端电话上的听筒 ― 我和您通过专用通道在我们的电话听筒上讲话和聆听。直到我们决定挂断电话,对话才会结束(除非我们在使用蜂窝电话)。而且我们各自的电话线路都占线,直到我们挂断电话。

如果想在没有更高级机制如 ORB(以及 CORBA、RMI、IIOP等等)开销的情况下进行两台计算机之间的通信,那么套接字就适合您。套接字的低级细节相当棘手。幸运的是,Java平台给了您一些虽然简单但却强大的更高级抽象,使您可以容易地创建和使用套接字。

6. 套接字的类型

一般而言,Java 语言中的套接字有以下两种形式:

  • TCP 套接字(由 Socket 类实现,稍后我们将讨论这个类)
  • UDP 套接字(由 DatagramSocket 类实现)

TCP 和 UDP

扮演相同角色,但做法不同。两者都接收传输协议数据包并将其内容向前传送到表示层。TCP把消息分解成数据包 (数据报,datagrams) 并在接收端以正确的顺序把它们重新装配起来。TCP还处理对遗失数据包的重传请求。有了TCP,位于上层的层要担心的事情就少多了。UDP不提供装配和重传请求这些功能。它只是向前传送信息包。位于上层的层必须确保消息是完整的并且是以正确的顺序装配的。

一般而言,UDP强加给您的应用程序的性能开销更小,但只在应用程序不会突然交换大量数据并且不必装配大量数据报以完成一条消息的时候。否则,TCP才是最简单或许也是最高效的选择。

因为多数读者都喜欢 TCP 胜过 UDP,所以我们将把讨论限制在 Java 语言中面向TCP 的类。