提交 43c968f2 编写于 作者: W wizardforcel

ch17

上级 54b6b6c0
......@@ -50,11 +50,11 @@ Colossus/202.98.41.151
15.1.1 服务器和客户端
网络最基本的精神就是让两台机器连接到一起,并相互“交谈”或者“沟通”。一旦两台机器都发现了对方,就可以展开一次令人愉快的双向对话。但它们怎样才能“发现”对方呢?这就象在游乐园里那样:一台机器不得不停留在一个地方,听其他机器说:“嘿,你在哪里呢?”
网络最基本的精神就是让两台机器连接到一起,并相互“交谈”或者“沟通”。一旦两台机器都发现了对方,就可以展开一次令人愉快的双向对话。但它们怎样才能“发现”对方呢?这就象在游乐园里那样:一台机器不得不停留在一个地方,听其他机器说:“嘿,你在哪里呢?”
“停留在一个地方”的机器叫作“服务器”(Server);到处“找人”的机器则叫作“客户端”(Client)或者“客户”。它们之间的区别只有在客户端试图同服务器连接的时候才显得非常明显。一旦连通,就变成了一种双向通信,谁来扮演服务器或者客户端便显得不那么重要了。
所以服务器的主要任务是听建立连接的请求,这是由我们创建的特定服务器对象完成的。而客户端的任务是试着与一台服务器建立连接,这是由我们创建的特定客户端对象完成的。一旦连接建好,那么无论在服务器端还是客户端端,连接只是魔术般地变成了一个IO数据流对象。从这时开始,我们可以象读写一个普通的文件那样对待连接。所以一旦建好连接,我们只需象第10章那样使用自己熟悉的IO命令即可。这正是Java连网最方便的一个地方。
所以服务器的主要任务是听建立连接的请求,这是由我们创建的特定服务器对象完成的。而客户端的任务是试着与一台服务器建立连接,这是由我们创建的特定客户端对象完成的。一旦连接建好,那么无论在服务器端还是客户端端,连接只是魔术般地变成了一个IO数据流对象。从这时开始,我们可以象读写一个普通的文件那样对待连接。所以一旦建好连接,我们只需象第10章那样使用自己熟悉的IO命令即可。这正是Java连网最方便的一个地方。
1. 在没有网络的前提下测试程序
......
......@@ -2,11 +2,11 @@
“套接字”或者“插座”(`Socket`)也是一种软件形式的抽象,用于表达两台机器间一个连接的“终端”。针对一个特定的连接,每台机器上都有一个“套接字”,可以想象它们之间有一条虚拟的“线缆”。线缆的每一端都插入一个“套接字”或者“插座”里。当然,机器之间的物理性硬件以及电缆连接都是完全未知的。抽象的基本宗旨是让我们尽可能不必知道那些细节。
在Java中,我们创建一个套接字,用它建立与其他机器的连接。从套接字得到的结果是一个`InputStream`以及`OutputStream`(若使用恰当的转换器,则分别是`Reader``Writer`),以便将连接作为一个IO流对象对待。有两个基于数据流的套接字类:`ServerSocket`,服务器用它“听”进入的连接;以及`Socket`,客户用它初始一次连接。一旦客户(程序)申请建立一个套接字连接,`ServerSocket`就会返回(通过`accept()`方法)一个对应的服务器端套接字,以便进行直接通信。从此时起,我们就得到了真正的“套接字-套接字”连接,可以用同样的方式对待连接的两端,因为它们本来就是相同的!此时可以利用`getInputStream()`以及`getOutputStream()`从每个套接字产生对应的`InputStream``OutputStream`对象。这些数据流必须封装到缓冲区内。可按第10章介绍的方法对类进行格式化,就象对待其他任何流对象那样。
在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`确实会在主机上创建一个物理性的“服务器”或者监听用的套接字。这个套接字会监听进入的连接,然后利用`accept()`方法返回一个“已建立”套接字(本地和远程端点均已定义)。容易混淆的地方是这两个套接字(监听和已建立)都与相同的服务器套接字关联在一起。监听套接字只能接收新的连接请求,不能接收实际的数据包。所以尽管`ServerSocket`对于编程并无太大的意义,但它确实是“物理性”的。
创建一个`ServerSocket`时,只需为其赋予一个端口编号。不必把一个IP地址分配它,因为它已经在自己代表的那台机器上了。但在创建一个`Socket`时,却必须同时赋予IP地址以及要连接的端口编号(另一方面,从`ServerSocket.accept()`返回的`Socket`已经包含了所有这些信息)。
......
......@@ -13,7 +13,7 @@
现在讨论一下服务器应用(程序)的问题,我把它叫作`NameCollecor`(名字收集器)。假如多名用户同时尝试提交他们的E-mail地址,那么会发生什么情况呢?若`NameCollector`使用TCP/IP套接字,那么必须运用早先介绍的多线程机制来实现对多个客户的并发控制。但所有这些线程都试图把数据写到同一个文件里,其中保存了所有E-mail地址。这便要求我们设立一种锁定机制,保证多个线程不会同时访问那个文件。一个“信号机”可在这里帮助我们达到目的,但或许还有一种更简单的方式。
如果我们换用数据报,就不必使用多线程了。用单个数据报即可“听”进入的所有数据报。一旦监视到有进入的消息,程序就会进行适当的处理,并将答复数据作为一个数据报传回原先发出请求的那名接收者。若数据报半路上丢失了,则用户会注意到没有答复数据传回,所以可以重新提交请求。
如果我们换用数据报,就不必使用多线程了。用单个数据报即可“听”进入的所有数据报。一旦监视到有进入的消息,程序就会进行适当的处理,并将答复数据作为一个数据报传回原先发出请求的那名接收者。若数据报半路上丢失了,则用户会注意到没有答复数据传回,所以可以重新提交请求。
服务器应用收到一个数据报,并对它进行解读的时候,必须提取出其中的电子函件地址,并检查本机保存的数据文件,看看里面是否已经包含了那个地址(如果没有,则添加之)。所以我们现在遇到了一个新的问题。Java 1.0似乎没有足够的能力来方便地处理包含了电子函件地址的文件(Java 1.1则不然)。但是,用C轻易就可以解决这个问题。因此,我们在这儿有机会学习将一个非Java程序同Java程序连接的最简便方式。程序使用的`Runtime`对象包含了一个名为`exec()`的方法,它会独立机器上一个独立的程序,并返回一个`Process`(进程)对象。我们可以取得一个`OutputStream`,它同这个单独程序的标准输入连接在一起;并取得一个`InputStream`,它则同标准输出连接到一起。要做的全部事情就是用任何语言写一个程序,只要它能从标准输入中取得自己的输入数据,并将输出结果写入标准输出即可。如果有些问题不能用Java简便与快速地解决(或者想利用原有代码,不想改写),就可以考虑采用这种方法。亦可使用Java的“固有方法”(Native Method),但那要求更多的技巧,大家可以参考一下附录A。
......@@ -84,7 +84,7 @@ int main() {
2. Java程序
这个程序先启动上述的C程序,再建立必要的连接,以便同它“交谈”。随后,它创建一个数据报套接字,用它“监视”或者“听”来自程序片的数据报包。
这个程序先启动上述的C程序,再建立必要的连接,以便同它“交谈”。随后,它创建一个数据报套接字,用它“监视”或者“听”来自程序片的数据报包。
```
//: NameCollector.java
......@@ -325,7 +325,7 @@ public class NameSender extends Applet
程序片的UI(用户界面)非常简单。它包含了一个`TestField`(文本字段),以便我们键入一个电子函件地址;以及一个`Button`(按钮),用于将地址发给服务器。两个`Label`(标签)用于向用户报告状态信息。
到现在为止,大家已能判断出`DatagramSocket``InetAddress`、缓冲区以及`DatagramPacket`都属于网络连接中比较麻烦的部分。最后,大家可看到`run()`方法实现了线程部分,使程序片能够“听”由服务器传回的响应信息。
到现在为止,大家已能判断出`DatagramSocket``InetAddress`、缓冲区以及`DatagramPacket`都属于网络连接中比较麻烦的部分。最后,大家可看到`run()`方法实现了线程部分,使程序片能够“听”由服务器传回的响应信息。
`init()`方法用大家熟悉的布局工具设置GUI,然后创建`DatagramSocket`,它将同时用于数据报的收发。
......
此差异已折叠。
......@@ -19,10 +19,10 @@ public class DisplayMethods extends Applet {
Method[] m;
Constructor[] ctor;
String[] n = new String[0];
TextField
TextField
name = new TextField(40),
searchFor = new TextField(30);
Checkbox strip =
Checkbox strip =
new Checkbox("Strip Qualifiers");
TextArea results = new TextArea(40, 65);
public void init() {
......@@ -30,7 +30,7 @@ public class DisplayMethods extends Applet {
name.addTextListener(new NameL());
searchFor.addTextListener(new SearchForL());
strip.addItemListener(new StripL());
Panel
Panel
top = new Panel(),
lower = new Panel(),
p = new Panel();
......@@ -152,7 +152,7 @@ class StripQualifiers {
return s;
}
public static String strip(String qualified) {
StripQualifiers sq =
StripQualifiers sq =
new StripQualifiers(qualified);
String s = "", si;
while((si = sq.getNext()) != null) {
......@@ -166,14 +166,14 @@ class StripQualifiers {
} ///:~
```
程序中的有些东西已在以前见识过了。和本书的许多GUI程序一样,这既可作为一个独立的应用程序使用,亦可作为一个程序片(Applet)使用。此外,StripQualifiers类与它在第11章的表现是完全一样的。
程序中的有些东西已在以前见识过了。和本书的许多GUI程序一样,这既可作为一个独立的应用程序使用,亦可作为一个程序片(Applet)使用。此外,`StripQualifiers`类与它在第11章的表现是完全一样的。
GUI包含了一个名为name的“文本字段”(TextField),或在其中输入想查找的类名;还包含了另一个文本字段,名为searchFor,可选择性地在其中输入一定的文字,希望在方法列表中查找那些文字。Checkbox(复选框)允许我们指出最终希望在输出中使用完整的名字,还是将前面的各种限定信息删去。最后,结果显示于一个“文本区域”(TextArea)中。
GUI包含了一个名为`name`的“文本字段”(`TextField`),或在其中输入想查找的类名;还包含了另一个文本字段,名为`searchFor`,可选择性地在其中输入一定的文字,希望在方法列表中查找那些文字。`Checkbox`(复选框)允许我们指出最终希望在输出中使用完整的名字,还是将前面的各种限定信息删去。最后,结果显示于一个“文本区域”(`TextArea`)中。
大家会注意到这个程序未使用任何按钮或其他组件,不能用它们开始一次搜索。这是由于无论文本字段还是复选框都会受到它们的“侦听者(Listener)对象的监视。只要作出一项改变,结果列表便会立即更新。若改变了name字段中的文字,新的文字就会在NameL类中捕获。若文字不为空,则在Class.forName()中用于尝试查找类。当然,在文字键入期间,名字可能会变得不完整,而Class.forName()会失败,这意味着它会“抛”出一个异常。该异常会被捕获,TextArea会随之设为“Nomatch”(没有相符)。但只要键入了一个正确的名字(大小写也算在内),Class.forName()就会成功,而getMethods()和getConstructors()会分别返回由Method和Constructor对象构成的一个数组。这些数组中的每个对象都会通过toString()转变成一个字符串(这样便产生了完整的方法或构造器签名),而且两个列表都会合并到n中——一个独立的字符串数组。数组n属于DisplayMethods类的一名成员,并在调用reDisplay()时用于显示的更新。
大家会注意到这个程序未使用任何按钮或其他组件,不能用它们开始一次搜索。这是由于无论文本字段还是复选框都会受到它们的“监听者(`Listener`)对象的监视。只要作出一项改变,结果列表便会立即更新。若改变了`name`字段中的文字,新的文字就会在`NameL`类中捕获。若文字不为空,则在`Class.forName()`中用于尝试查找类。当然,在文字键入期间,名字可能会变得不完整,而`Class.forName()`会失败,这意味着它会“抛”出一个异常。该异常会被捕获,`TextArea`会随之设为`Nomatch`(不相符)。但只要键入了一个正确的名字(大小写也算在内),`Class.forName()`就会成功,而`getMethods()``getConstructors()`会分别返回由`Method``Constructor`对象构成的一个数组。这些数组中的每个对象都会通过`toString()`转变成一个字符串(这样便产生了完整的方法或构造器签名),而且两个列表都会合并到`n`中——一个独立的字符串数组。数组`n`属于`DisplayMethods`类的一名成员,并在调用`reDisplay()`时用于显示的更新。
若改变了Checkbox或searchFor组件,它们的“侦听者”会简单地调用reDisplay()。reDisplay()会创建一个临时数组,其中包含了名为rs的字符串(rs代表“结果集”——Result Set)。结果集要么直接从n复制(没有find关键字),要么选择性地从包含了find关键字的n中的字符串复制。最后会检查strip Checkbox,看看用户是不是希望将名字中多余的部分删除(默认为“是”)。若答案是肯定的,则用StripQualifiers.strip()做这件事情;反之,就将列表简单地显示出来。
若改变了`Checkbox``searchFor`组件,它们的“监听者”会简单地调用`reDisplay()``reDisplay()`会创建一个临时数组,其中包含了名为`rs`的字符串(`rs`代表“结果集”——`Result Set`)。结果集要么直接从`n`复制(没有`find`关键字),要么选择性地从包含了`find`关键字的`n`中的字符串复制。最后会检查`strip Checkbox`,看看用户是不是希望将名字中多余的部分删除(默认为“是”)。若答案是肯定的,则用`StripQualifiers.strip()`做这件事情;反之,就将列表简单地显示出来。
init()中,大家也许认为在设置布局时需要进行大量繁重的工作。事实上,组件的布置完全可能只需要极少的工作。但象这样使用BorderLayout的好处是它允许用户改变窗口的大小,并特别能使TextArea(文本区域)更大一些,这意味着我们可以改变大小,以便毋需滚动即可看到更长的名字。
`init()`中,大家也许认为在设置布局时需要进行大量繁重的工作。事实上,组件的布置完全可能只需要极少的工作。但象这样使用`BorderLayout`的好处是它允许用户改变窗口的大小,并特别能使`TextArea`(文本区域)更大一些,这意味着我们可以改变大小,以便毋需滚动即可看到更长的名字。
编程时,大家会发现特别有必要让这个工具处于运行状态,因为在试图判断要调用什么方法的时候,它提供了最好的方法之一。
# 17.5 练习
(1) (稍微有些难度)改写FieldOBeasts.java,使它的状态能够保持固定。加上一些按钮,允许用户保存和恢复不同的状态文件,并从它们断掉的地方开始继续运行。请先参考第10章的CADState.java,再决定具体怎样做。
(1) (稍微有些难度)改写`FieldOBeasts.java`,使它的状态能够保持固定。加上一些按钮,允许用户保存和恢复不同的状态文件,并从它们断掉的地方开始继续运行。请先参考第10章的`CADState.java`,再决定具体怎样做。
(2) (大作业)以FieldOBeasts.java作为起点,构造一个自动化交通仿真系统。
(2) (大作业)以`FieldOBeasts.java`作为起点,构造一个自动化交通仿真系统。
(3) (大作业)以ClassScanner.java作为起点,构造一个特殊的工具,用它找出那些虽然定义但从未用过的方法和字段。
(3) (大作业)以`ClassScanner.java`作为起点,构造一个特殊的工具,用它找出那些虽然定义但从未用过的方法和字段。
(4) (大作业)利用JDBC,构造一个联络管理程序。让这个程序以一个平面文件数据库为基础,其中包含了名字、地址、电话号码、E-mail地址等联系资料。应该能向数据库里方便地加入新名字。键入要查找的名字时,请采用在第15章的VLookup.java里介绍过的那种名字自动填充技术。
\ No newline at end of file
(4) (大作业)利用JDBC,构造一个联络管理程序。让这个程序以一个平面文件数据库为基础,其中包含了名字、地址、电话号码、E-mail地址等联系资料。应该能向数据库里方便地加入新名字。键入要查找的名字时,请采用在第15章的`VLookup.java`里介绍过的那种名字自动填充技术。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册