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

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

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


  private PrintWriter out;  

  private static int counter = 0;  

  private int id = counter++;  

  private static int threadcount = 0;  

  public static int threadCount() {   

    return threadcount;   

  }  

  public JabberClientThread(InetAddress addr) {  

    System。out。println(〃Making client 〃 + id);  

    threadcount++;  

    try {  

      socket =   

        new Socket(addr; MultiJabberServer。PORT);  

    } catch(IOException e) {  

      // If the creation of the socket fails;   

      // nothing needs to be cleaned up。  

    }  

    try {      

      in =   

        new BufferedReader(  

          new InputStreamReader(  

            socket。getInputStream()));  



                                                                                          545 


…………………………………………………………Page 547……………………………………………………………

      // Enable auto…flush:  

      out =   

        new PrintWriter(  

          new BufferedWriter(  

            new OutputStreamWriter(  

              socket。getOutputStream())); true);  

      start();  

    } catch(IOException e) {  

      // The socket should be closed on any   

      // failures other than the socket   

      // constructor:  

      try {  

        socket。close();  

      } catch(IOException e2) {}  

    }  

    // Otherwise the socket will be closed by  

    // the run() method of the thread。  

  }  

  public void run() {  

    try {  

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

        out。println(〃Client 〃 + id + 〃: 〃 + i);  

        String str = in。readLine();  

        System。out。println(str);  

      }  

      out。println(〃END〃);  

    } catch(IOException e) {  

    } finally {  

      // Always close it:  

      try {  

        socket。close();  

      } catch(IOExcept ion e) {}  

      threadcount…; // Ending this thread  

    }  

  }  

}  

  

public class MultiJabberClient {  

  static final int MAX_THREADS = 40;  

  public static void main(String'' args)   

      throws IOException; InterruptedException {  

    InetAddress addr =   

      InetAddress。getByName(null);  

    while(true) {  

      if(JabberClientThread。threadCount()   

         《 MAX_THREADS)  

        new JabberClientThread(addr);  

      Thread。currentThread()。sleep(100);  

    }  

  }  

} ///:~  

  



                                                                                             546 


…………………………………………………………Page 548……………………………………………………………

JabberClientThread 构建器获取一个 InetAddress,并用它打开一个套接字。大家可能已看出了这样的一个 

套路:Socket 肯定用于创建某种 Reader 以及/或者Writer (或者InputStream和/或 OutputStream)对 

象,这是运用Socket 的唯一方式(当然,我们可考虑编写一、两个类,令其自动完成这些操作,避免大量重 

复的代码编写工作)。同样地,start()执行线程的初始化,并调用run()。在这里,消息发送给服务器,而 

来自服务器的信息则在屏幕上回显出来。然而,线程的“存在时间”是有限的,最终都会结束。注意在套接 

字创建好以后,但在构建器完成之前,假若构建器失败,套接字会被清除。否则,为套接字调用 close()的 

责任便落到了run()方法的头上。  

threadcount跟踪计算目前存在的 JabberClientThread 对象的数量。它将作为构建器的一部分增值,并在 

run()退出时减值(run()退出意味着线程中止)。在MultiJabberClient。main()中,大家可以看到线程的数 

量会得到检查。若数量太多,则多余的暂时不创建。方法随后进入“休眠”状态。这样一来,一旦部分线程 

最后被中止,多作的那些线程就可以创建了。大家可试验一下逐渐增大MAX_THREADS,看看对于你使用的系 

统来说,建立多少线程(连接)才会使您的系统资源降低到危险程度。  



15。4 数据报  



大家迄今看到的例子使用的都是“传输控制协议”(TCP),亦称作“基于数据流的套接字”。根据该协议的 

设计宗旨,它具有高度的可靠性,而且能保证数据顺利抵达目的地。换言之,它允许重传那些由于各种原因 

半路“走失”的数据。而且收到字节的顺序与它们发出来时是一样的。当然,这种控制与可靠性需要我们付 

出一些代价:TCP 具有非常高的开销。  

还有另一种协议,名为“用户数据报协议”(UDP),它并不刻意追求数据包会完全发送出去,也不能担保它 

们抵达的顺序与它们发出时一样。我们认为这是一种“不可靠协议”(TCP 当然是“可靠协议”)。听起来 

似乎很糟,但由于它的速度快得多,所以经常还是有用武之地的。对某些应用来说,比如声音信号的传输, 

如果少量数据包在半路上丢失了,那么用不着太在意,因为传输的速度显得更重要一些。大多数互联网游 

戏,如Diablo,采用的也是UDP 协议通信,因为网络通信的快慢是游戏是否流畅的决定性因素。也可以想想 

一台报时服务器,如果某条消息丢失了,那么也真的不必过份紧张。另外,有些应用也许能向服务器传回一 

条UDP 消息,以便以后能够恢复。如果在适当的时间里没有响应,消息就会丢失。  

Java 对数据报的支持与它对 TCP 套接字的支持大致相同,但也存在一个明显的区别。对数据报来说,我们在 

客户和服务器程序都可以放置一个 DatagramSocket (数据报套接字),但与ServerSocket 不同,前者不会 

干巴巴地等待建立一个连接的请求。这是由于不再存在“连接”,取而代之的是一个数据报陈列出来。另一 

项本质的区别的是对TCP 套接字来说,一旦我们建好了连接,便不再需要关心谁向谁“说话”——只需通过 

会话流来回传送数据即可。但对数据报来说,它的数据包必须知道自己来自何处,以及打算去哪里。这意味 

着我们必须知道每个数据报包的这些信息,否则信息就不能正常地传递。  

DatagramSocket 用于收发数据包,而DatagramPacket 包含了具体的信息。准备接收一个数据报时,只需提 

供一个缓冲区,以便安置接收到的数据。数据包抵达时,通过 DatagramSocket,作为信息起源地的因特网地 

址以及端口编号会自动得到初化。所以一个用于接收数据报的 DatagramPacket 构建器是:  

DatagramPacket(buf; buf。length)  

其中,buf 是一个字节数组。既然 buf 是个数组,大家可能会奇怪为什么构建器自己不能调查出数组的长度 

呢?实际上我也有同感,唯一能猜到的原因就是C 风格的编程使然,那里的数组不能自己告诉我们它有多 

大。  

可以重复使用数据报的接收代码,不必每次都建一个新的。每次用它的时候(再生),缓冲区内的数据都会 

被覆盖。  

缓冲区的最大容量仅受限于允许的数据报包大小,这个限制位于比64KB 稍小的地方。但在许多应用程序中, 

我们都宁愿它变得还要小一些,特别是在发送数据的时候。具体选择的数据包大小取决于应用程序的特定要 

求。  

发出一个数据报时,DatagramPacket 不仅需要包含正式的数据,也要包含因特网地址以及端口号,以决定它 

的目的地。所以用于输出DatagramPacket 的构建器是:  

DatagramPacket(buf; length; inetAddress; port)  

这一次,buf (一个字节数组)已经包含了我们想发出的数据。length可以是 buf 的长度,但也可以更短一 

些,意味着我们只想发出那么多的字节。另两个参数分别代表数据包要到达的因特网地址以及目标机器的一 

个目标端口(注释②)。  

  

②:我们认为TCP 和 UDP 端口是相互独立的。也就是说,可以在端口8080 同时运行一个TCP 和 UDP 服务程 

序,两者之间不会产生冲突。  



                                                                 547 


…………………………………………………………Page 549……………………………………………………………

  

大家也许认为两个构建器创建了两个不同的对象:一个用于接收数据报,另一个用于发送它们。如果是好的 

面向对象的设计方案,会建议把它们创建成两个不同的类,而不是具有不同的行为的一个类(具体行为取决 

于我们如何构建对象)。这也许会成为一个严重的问题,但幸运的是,DatagramPacket 的使用相当简单,我 

们不需要在这个问题上纠缠不清。这一点在下例里将有很明确的说明。该例类似于前面针对 TCP 套接字的 

MultiJabberServer 和MultiJabberClient 例子。多个客户都会将数据报发给服务器,后者会将其反馈回最 

初发出消息的同样的客户。  

为简化从一个String 里创建 DatagramPacket 的工作(或者从DatagramPacket 里创建 String),这个例子 

首先用到了一个工具类,名为Dgram:  

  

//: Dgram。java  

// A utility class to convert back and forth  

// Between Strings and DataGramPackets。  

import java。*;  

  

public class Dgram {  

  public static DatagramPacket toDatagram(  

    String s; InetAddress destIA; int destPort) {  

    // Deprecated in Java 1。1; but it works:  

    byte'' buf = new byte's。length() + 1';  

    s。getBytes(0; s。length(); buf; 0);  

    // The correct Java 1。1 approach; but it's  

    // Broken (it truncates the String):  

    // byte'' buf = s。getBytes();  

    return new DatagramPacket(buf; buf。length;   

      destIA; destPort);  

  }  

  public static String toString(DatagramPacket p){  

    // The Java 1。0 approach:  

    // return new String(p。getData();   

    //  0; 0; p。getLength());  

    // The Java 1。1 approach:  

    return   

      new String(p。getData(); 0; p。getLength());  

  }  

} ///:~  

  

Dgram 的第一个方法采用一个String、一个 InetAddress 以及一个端口号作为自己的参数,将String 的内容 

复制到一个字节缓冲区,再将缓冲区传递进入 DatagramPacket 构建器,从而构建一个 DatagramPacket。注 

意缓冲区分配时的〃+1〃——这对防止截尾现象是非常重要的。String 的getByte()方法属于一种特殊操作, 

能将一个字串包含的char 复制进入一个字节缓冲。该方法现在已被“反对”使用;Java 1。1 有一个“更 

好”的办法来做这个工作,但在这里却被当作注释屏蔽掉了,因为它会截掉String 的部分内容。所以尽管我 

们在Java 1。1 下编译该程序时会得到一条“反对”消息,但它的行为仍然是正确无误的(这个错误应该在你 

读到这里的时候修正了)。  

Dgram。toString()方法同时展示了Java 1。0 的方法和Java 1。1 的方法(两者是不同的,因为有一种新类型 

的String 构建器)。  

下面是用于数据报演示的服务器代码:  

//: ChatterServer。java  

// A server that echoes datagrams  

import java。*;  

import java。io。*;  

import java。util。*;  



                                                                                          548 


…………………………………………………………Page 550……………………………………………………………

  

public class ChatterServer {  

  static final int INPORT = 1711;  

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

  private DatagramPacket dp =   

    new DatagramPacket(buf; buf。length);  

  // Can listen & send on the same socket:  

  private DatagramSocket socket;  

  

  public ChatterServer() {  

    try {  

      socket = new DatagramSocket(INPORT);  

      System。out。println(〃Server started〃);  

      while(true) {  

        // Block until a datagram appears:  

        socket。receive(dp);  

        String rcvd = Dgram。toString(dp) +  

          〃; from address: 〃 + dp。getAddress() +  

          〃; port: 〃 + dp。getPort();  

        System。out。println(rcvd);  

        String echoString =   

          〃Echoed: 〃 + rcvd;  

        // Extract the address and port from the  

        // received datagram to find out where to  

        // send it back:  

        DatagramPacket echo =   

          Dgram。toDatagram(echoString;  

            dp。getAddress(); dp。getPort());  

        socket。send(echo);  

      }  

    } catch(SocketException e) {  

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

      System。exit(1);  

    } catch(IOException e) {  

      System。err。println(〃munication error〃);  

      e。printStackTrace();  

    }  

  }  

  public static void main(String'' args) {  

    new ChatterServer();  

  }  

} ///:~  

  

ChatterServer 创建了一个用来接收消息的DatagramSocket (数据报套接字),而不是在我们每次准备接收 

一条新消息时都新建一个。这个单一的DatagramSocket 可以重复使用。它有一个端口号,因为这属于服务 

器,客户必须确切知道自己把数据报发到哪个地址。尽管有一个端口号,但没有为它分配因特网地址
返回目录 上一页 下一页 回到顶部 1 1
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!