按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
或者“客户”。它们之间的区别只有在客户机试图同服务器连接的时候才显得非常明显。一旦连通,就变成
了一种双向通信,谁来扮演服务器或者客户机便显得不那么重要了。
所以服务器的主要任务是侦听建立连接的请求,这是由我们创建的特定服务器对象完成的。而客户机的任务
是试着与一台服务器建立连接,这是由我们创建的特定客户机对象完成的。一旦连接建好,那么无论在服务
器端还是客户机端,连接只是魔术般地变成了一个 IO数据流对象。从这时开始,我们可以象读写一个普通的
文件那样对待连接。所以一旦建好连接,我们只需象第 10章那样使用自己熟悉的 IO 命令即可。这正是 Java
连网最方便的一个地方。
1。 在没有网络的前提下测试程序
由于多种潜在的原因,我们可能没有一台客户机、服务器以及一个网络来测试自己做好的程序。我们也许是
在一个课堂环境中进行练习,或者写出的是一个不十分可靠的网络应用,还能拿到网络上去。IP 的设计者注
意到了这个问题,并建立了一个特殊的地址——localhost——来满足非网络环境中的测试要求。在 Java 中
产生这个地址最一般的做法是:
InetAddress addr = InetAddress。getByName(null);
如果向 getByName()传递一个null (空)值,就默认为使用localhost。我们用InetAddress 对特定的机器
进行索引,而且必须在进行进一步的操作之前得到这个 InetAddress (互联网地址)。我们不可以操纵一个
InetAddress 的内容(但可把它打印出来,就象下一个例子要演示的那样)。创建 InetAddress 的唯一途径
就是那个类的static (静态)成员方法getByName() (这是最常用的)、getAllByName()或者
getLocalHost()。
为得到本地主机地址,亦可向其直接传递字串〃localhost〃:
InetAddress。getByName(〃localhost〃);
或者使用它的保留 IP 地址(四点形式),就象下面这样:
InetAddress。getByName(〃127。0。0。1〃);
这三种方法得到的结果是一样的。
538
…………………………………………………………Page 540……………………………………………………………
15。1。2 端口:机器内独一无二的场所
有些时候,一个 IP地址并不足以完整标识一个服务器。这是由于在一台物理性的机器中,往往运行着多个服
务器(程序)。由 IP 表达的每台机器也包含了“端口”(Port )。我们设置一个客户机或者服务器的时候,
必须选择一个无论客户机还是服务器都认可连接的端口。就象我们去拜会某人时,IP 地址是他居住的房子,
而端口是他在的那个房间。
注意端口并不是机器上一个物理上存在的场所,而是一种软件抽象(主要是为了表述的方便)。客户程序知
道如何通过机器的 IP 地址同它连接,但怎样才能同自己真正需要的那种服务连接呢(一般每个端口都运行着
一种服务,一台机器可能提供了多种服务,比如HTTP 和 FTP 等等)?端口编号在这里扮演了重要的角色,它
是必需的一种二级定址措施。也就是说,我们请求一个特定的端口,便相当于请求与那个端口编号关联的服
务。“报时”便是服务的一个典型例子。通常,每个服务都同一台特定服务器机器上的一个独一无二的端口
编号关联在一起。客户程序必须事先知道自己要求的那项服务的运行端口号。
系统服务保留了使用端口 1 到端口 1024 的权力,所以不应让自己设计的服务占用这些以及其他任何已知正在
使用的端口。本书的第一个例子将使用端口8080 (为追忆我的第一台机器使用的老式8 位 Intel 8080芯
片,那是一部使用CP/M 操作系统的机子)。
15。2 套接字
“套接字”或者“插座”(Socket)也是一种软件形式的抽象,用于表达两台机器间一个连接的“终端”。
针对一个特定的连接,每台机器上都有一个“套接字”,可以想象它们之间有一条虚拟的“线缆”。线缆的
每一端都插入一个“套接字”或者“插座”里。当然,机器之间的物理性硬件以及电缆连接都是完全未知
的。抽象的基本宗旨是让我们尽可能不必知道那些细节。
在Java 中,我们创建一个套接字,用它建立与其他机器的连接。从套接字得到的结果是一个 InputStream 以
及OutputStream (若使用恰当的转换器,则分别是Reader 和 Writer),以便将连接作为一个IO流对象对
待。有两个基于数据流的套接字类:ServerSocket,服务器用它“侦听”进入的连接;以及Socket,客户用
它初始一次连接。一旦客户(程序)申请建立一个套接字连接,ServerSocket 就会返回(通过accept()方
法)一个对应的服务器端套接字,以便进行直接通信。从此时起,我们就得到了真正的“套接字-套接字”
连接,可以用同样的方式对待连接的两端,因为它们本来就是相同的!此时可以利用 getInputStream()以及
getOutputStream()从每个套接字产生对应的 InputStream 和OutputStream 对象。这些数据流必须封装到缓
冲区内。可按第 10章介绍的方法对类进行格式化,就象对待其他任何流对象那样。
对于Java 库的命名机制,ServerSocket (服务器套接字)的使用无疑是容易产生混淆的又一个例证。大家可
能认为ServerSocket 最好叫作“ServerConnector”(服务器连接器),或者其他什么名字,只是不要在其
中安插一个“Socket”。也可能以为ServerSocket 和 Socket 都应从一些通用的基础类继承。事实上,这两
种类确实包含了几个通用的方法,但还不够资格把它们赋给一个通用的基础类。相反,ServerSocket 的主要
任务是在那里耐心地等候其他机器同它连接,再返回一个实际的Socket。这正是“ServerSocket”这个命名
不恰当的地方,因为它的目标不是真的成为一个Socket,而是在其他人同它连接的时候产生一个Socket 对
象。
然而,ServerSocket 确实会在主机上创建一个物理性的“服务器”或者侦听用的套接字。这个套接字会侦听
进入的连接,然后利用 accept()方法返回一个“已建立”套接字(本地和远程端点均已定义)。容易混淆的
地方是这两个套接字(侦听和已建立)都与相同的服务器套接字关联在一起。侦听套接字只能接收新的连接
请求,不能接收实际的数据包。所以尽管 ServerSocket 对于编程并无太大的意义,但它确实是“物理性”
的。
创建一个ServerSocket 时,只需为其赋予一个端口编号。不必把一个 IP 地址分配它,因为它已经在自己代
表的那台机器上了。但在创建一个 Socket 时,却必须同时赋予IP 地址以及要连接的端口编号(另一方面,
从ServerSocket。accept()返回的 Socket 已经包含了所有这些信息)。
15。2。1 一个简单的服务器和客户机程序
这个例子将以最简单的方式运用套接字对服务器和客户机进行操作。服务器的全部工作就是等候建立一个连
接,然后用那个连接产生的Socket 创建一个 InputStream 以及一个OutputStream。在这之后,它从
InputStream读入的所有东西都会反馈给OutputStream,直到接收到行中止(END)为止,最后关闭连接。
客户机连接与服务器的连接,然后创建一个OutputStream。文本行通过OutputStream 发送。客户机也会创
建一个 InputStream,用它收听服务器说些什么(本例只不过是反馈回来的同样的字句)。
服务器与客户机(程序)都使用同样的端口号,而且客户机利用本地主机地址连接位于同一台机器中的服务
539
…………………………………………………………Page 541……………………………………………………………
器(程序),所以不必在一个物理性的网络里完成测试(在某些配置环境中,可能需要同真正的网络建立连
接,否则程序不能工作——尽管实际并不通过那个网络通信)。
下面是服务器程序:
//: JabberServer。java
// Very simple server that just
// echoes whatever the client sends。
import java。io。*;
import java。*;
public class JabberServer {
// Choose a port outside of the range 1…1024:
public static final int PORT = 8080;
public static void main(String'' args)
throws IOException {
ServerSocket s = new ServerSocket(PORT);
System。out。println(〃Started: 〃 + s);
try {
// Blocks until a connection occurs:
Socket socket = s。accept();
try {
System。out。println(
〃Connection accepted: 〃+ socket);
BufferedReader in =
new BufferedReader(
new InputStreamReader(
socket。getInputStream()));
// Output is automatically flushed
// by PrintWriter:
PrintWriter out =
new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
socket。getOutputStream()));true);
while (true) {
String str = in。readLine();
if (str。equals(〃END〃)) break;
System。out。println(〃Echoing: 〃 + str);
out。println(str);
}
// Always close the two sockets。。。
} finally {
System。out。println(〃closing。。。〃);
socket。close();
}
} finally {
s。close();
}
}
} ///:~
可以看到,ServerSocket 需要的只是一个端口编号,不需要 IP地址(因为它就在这台机器上运行)。调用
540
…………………………………………………………Page 542……………………………………………………………
accept()时,方法会暂时陷入停顿状态(堵塞),直到某个客户尝试同它建立连接。换言之,尽管�