友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
第三电子书 返回本书目录 加入书签 我的书架 我的书签 TXT全本下载 『收藏到我的浏览器』

Java编程思想第4版[中文版](PDF格式)-第129部分

快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!


器,客户必须确切知道自己把数据报发到哪个地址。尽管有一个端口号,但没有为它分配因特网地址,因为 

它就驻留在“这”台机器内,所以知道自己的因特网地址是什么(目前是默认的 localhost)。在无限while 

循环中,套接字被告知接收数据(receive())。然后暂时挂起,直到一个数据报出现,再把它反馈回我们希 

望的接收人——DatagramPacket dp——里面。数据包(Packet)会被转换成一个字串,同时插入的还有数据 

包的起源因特网地址及套接字。这些信息会显示出来,然后添加一个额外的字串,指出自己已从服务器反馈 

回来了。  

大家可能会觉得有点儿迷惑。正如大家会看到的那样,许多不同的因特网地址和端口号都可能是消息的起源 



                                                                                          549 


…………………………………………………………Page 551……………………………………………………………

地——换言之,客户程序可能驻留在任何一台机器里(就这一次演示来说,它们都驻留在 localhost 里,但 

每个客户使用的端口编号是不同的)。为了将一条消息送回它真正的始发客户,需要知道那个客户的因特网 

地址以及端口号。幸运的是,所有这些资料均已非常周到地封装到发出消息的DatagramPacket 内部,所以我 

们要做的全部事情就是用getAddress()和getPort()把它们取出来。利用这些资料,可以构建 

DatagramPacket echo——它通过与接收用的相同的套接字发送回来。除此以外,一旦套接字发出数据报,就 

会添加“这”台机器的因特网地址及端口信息,所以当客户接收消息时,它可以利用 getAddress()和 

getPort()了解数据报来自何处。事实上,getAddress()和 getPort()唯一不能告诉我们数据报来自何处的前 

提是:我们创建一个待发送的数据报,并在正式发出之前调用了getAddress()和getPort()。到数据报正式 

发送的时候,这台机器的地址以及端口才会写入数据报。所以我们得到了运用数据报时一项重要的原则:不 

必跟踪一条消息的来源地!因为它肯定保存在数据报里。事实上,对程序来说,最可靠的做法是我们不要试 

图跟踪,而是无论如何都从目标数据报里提取出地址以及端口信息(就象这里做的那样)。  

为测试服务器的运转是否正常,下面这程序将创建大量客户(线程),它们都会将数据报包发给服务器,并 

等候服务器把它们原样反馈回来。  

  

//: ChatterClient。java  

// Tests the ChatterServer by starting multiple   

// clients; each of which sends datagrams。  

import java。lang。Thread;  

import java。*;  

import java。io。*;  

  

public class ChatterClient extends Thread {  

  // Can listen & send on the same socket:  

  private DatagramSocket s;  

  private InetAddress hostAddress;  

  private byte'' buf = new byte'1000';  

  private DatagramPacket dp =   

    new DatagramPacket(buf; buf。length);  

  private int id;  

  

  public ChatterClient(int identifier) {  

    id = identifier;  

    try {  

      // Auto…assign port number:  

      s = new DatagramSocket();  

      hostAddress =   

        InetAddress。getByName(〃localhost〃);  

    } catch(UnknownHostException e) {  

      System。err。println(〃Cannot find host〃);  

      System。exit(1);  

    } catch(SocketException e) {  

      System。err。println(〃Can't open socket〃);  

      e。printStackTrace();  

      System。exit(1);  

    }   

    System。out。println(〃ChatterClient starting〃);  

  }  

  public void run() {  

    try {  

      for(int i = 0; i 《 25; i++) {  

        String outMessage = 〃Client #〃 +  

          id + 〃; message #〃 + i;  



                                                                                          550 


…………………………………………………………Page 552……………………………………………………………

        // Make and send a datagram:  

        s。send(Dgram。toDatagram(outMessage;  

          hostAddress;   

          ChatterServer。INPORT));  

        // Block until it echoes back:  

        s。receive(dp);  

        // Print out the echoed contents:  

        String rcvd = 〃Client #〃 + id +  

          〃; rcvd from 〃 +   

          dp。getAddress() + 〃; 〃 +   

          dp。getPort() + 〃: 〃 +  

          Dgram。toString(dp);  

        System。out。println(rcvd);  

      }  

    } catch(IOException e) {  

      e。printStackTrace();  

      System。exit(1);  

    }  

  }  

  public static void main(String'' args) {  

    for(int i = 0; i 《 10; i++)  

      new ChatterClient(i)。start();  

  }  

} ///:~  

  

ChatterClient 被创建成一个线程(Thread),所以可以用多个客户来“骚扰”服务器。从中可以看到,用 

于接收的DatagramPacket 和用于 ChatterServer 的那个是相似的。在构建器中,创建DatagramPacket 时没 

有附带任何参数(自变量),因为它不需要明确指出自己位于哪个特定编号的端口里。用于这个套接字的因 

特网地址将成为“这台机器”(比如 localhost),而且会自动分配端口编号,这从输出结果即可看出。同 

用于服务器的那个一样,这个 DatagramPacket 将同时用于发送和接收。  

hostAddress 是我们想与之通信的那台机器的因特网地址。在程序中,如果需要创建一个准备传出去的 

DatagramPacket,那么必须知道一个准确的因特网地址和端口号。可以肯定的是,主机必须位于一个已知的 

地址和端口号上,使客户能启动与主机的“会话”。  

每个线程都有自己独一无二的标识号(尽管自动分配给线程的端口号是也会提供一个唯一的标识符)。在 

run()中,我们创建了一个String 消息,其中包含了线程的标识编号以及该线程准备发送的消息编号。我们 

用这个字串创建一个数据报,发到主机上的指定地址;端口编号则直接从 ChatterServer 内的一个常数取 

得。一旦消息发出,receive()就会暂时被“堵塞”起来,直到服务器回复了这条消息。与消息附在一起的所 

有信息使我们知道回到这个特定线程的东西正是从始发消息中投递出去的。在这个例子中,尽管是一种“不 

可靠”协议,但仍然能够检查数据报是否到去过了它们该去的地方(这在 localhost 和LAN 环境中是成立 

的,但在非本地连接中却可能出现一些错误)。  

运行该程序时,大家会发现每个线程都会结束。这意味着发送到服务器的每个数据报包都会回转,并反馈回 

正确的接收者。如果不是这样,一个或更多的线程就会挂起并进入“堵塞”状态,直到它们的输入被显露出 

来。  

大家或许认为将文件从一台机器传到另一台的唯一正确方式是通过TCP 套接字,因为它们是“可靠”的。然 

而,由于数据报的速度非常快,所以它才是一种更好的选择。我们只需将文件分割成多个数据报,并为每个 

包编号。接收机器会取得这些数据包,并重新“组装”它们;一个“标题包”会告诉机器应该接收多少个 

包,以及组装所需的另一些重要信息。如果一个包在半路“走丢”了,接收机器会返回一个数据报,告诉发 

送者重传。  



15。5 一个 Web 应用  



现在让我们想想如何创建一个应用,令其在真实的 Web 环境中运行,它将把Java 的优势表现得淋漓尽致。这 

个应用的一部分是在Web 服务器上运行的一个 Java 程序,另一部分则是一个“程序片”或“小应用程序” 



                                                                                  551 


…………………………………………………………Page 553……………………………………………………………

 (Applet),从服务器下载至浏览器(即“客户”)。这个程序片从用户那里收集信息,并将其传回Web 服 

务器上运行的应用程序。程序的任务非常简单:程序片会询问用户的 E…mail 地址,并在验证这个地址合格后 

 (没有包含空格,而且有一个@符号),将该 E…mail 发送给 Web 服务器。服务器上运行的程序则会捕获传回 

的数据,检查一个包含了所有E…mail 地址的数据文件。如果那个地址已包含在文件里,则向浏览器反馈一条 

消息,说明这一情况。该消息由程序片负责显示。若是一个新地址,则将其置入列表,并通知程序片已成功 

添加了电子函件地址。  

若采用传统方式来解决这个问题,我们要创建一个包含了文本字段及一个“提交”(Submit)按钮的HTML 

页。用户可在文本字段里键入自己喜欢的任何内容,并毫无阻碍地提交给服务器(在客户端不进行任何检 

查)。提交数据的同时,Web 页也会告诉服务器应对数据采取什么样的操作——知会“通用网关接口” 

 (CGI)程序,收到这些数据后立即运行服务器。这种CGI 程序通常是用 Perl 或C 写的(有时也用C++,但 

要求服务器支持),而且必须能控制一切可能出现的情况。它首先会检查数据,判断是否采用了正确的格 

式。若答案是否定的,则CGI 程序必须创建一个 HTML 页,对遇到的问题进行描述。这个页会转交给服务器, 

再由服务器反馈回用户。用户看到出错提示后,必须再试一遍提交,直到通过为止。若数据正确,CGI 程序 

会打开数据文件,要么把电子函件地址加入文件,要么指出该地址已在数据文件里了。无论哪种情况,都必 

须格式化一个恰当的HTML 页,以便服务器返回给用户。  

作为Java 程序员,上述解决问题的方法显得非常笨拙。而且很自然地,我们希望一切工作都用Java 完成。 

首先,我们会用一个Java 程序片负责客户端的数据有效性校验,避免数据在服务器和客户之间传来传去,浪 

费时间和带宽,同时减轻服务器额外构建HTML 页的负担。然后跳过Perl CGI 脚本,换成在服务器上运行一 

个Java 应用。事实上,我们在这儿已完全跳过了 Web 服务器,仅仅需要从程序片到服务器上运行的 Java 应 

用之间建立一个连接即可。  

正如大家不久就会体验到的那样,尽管看起来非常简单,但实际上有一些意想不到的问题使局面显得稍微有 

些复杂。用 Java 1。1 写程序片是最理想的,但实际上却经常行不通。到本书写作的时候,拥有 Java 1。1 能 

力的浏览器仍为数不多,而且即使这类浏览器现在非常流行,仍需考虑照顾一下那些升级缓慢的人。所以从 

安全的角度看,程序片代码最好只用Java 1。0 编写。基于这一前提,我们不能用 JAR 文件来合并(压缩)程 

序片中的。class 文件。所以,我们应尽可能减少。class 文件的使用数量,以缩短下载时间。  

好了,再来说说我用的 Web 服务器(写这个示范程序时用的就是它)。它确实支持Java,但仅限于 Java  

1。0!所以服务器应用也必须用Java 1。0 编写。  



15。5。1 服务器应用  



现在讨论一下服务器应用(程序)的问题,我把它叫作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 。  

  

1。 C 程序  

这个非 Java 应用是用 C 写成,因为 Java 不适合作 CGI 编程;起码启动的时间不能让人满意。它的任务是管 

理电子函件(E…mail )地址的一个列表。标准输入会接受一个E…mail 地址,程序会检查列表中的名字,判断 

是否存在那个地址。若不存在,就将其加入,并报告操作成功。但假如名字已在列表里了,就需要指出这一 



                                                                552 


…………………………………………………………Page 554……………………………………………………………

点,避免重复加入。大家不必担心自己不能完全理解下列代码的含义。它仅仅是一个演示程序,告诉你如何 

用其他语言写一个程序,并从 Java 中调用它。在这里具体采用何种语言并不重
返回目录 上一页 下一页 回到顶部 1 1
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!