《JavaTM程序设计语言基 础》第 2 部分
第 1 课:网络接口通信

[<<后退] [目录] [下一课>>]

《JavaTM 程序设计语言基础》第 1 部分 的结尾部分介绍了一个采用远程方法调用 (RMI)应用编程接口(API)的简单网络通信示例程序。RMI 示例程序允许多个 客户程序与同一个服务器程序通信,但这一点并没有清晰地在代码中表现出来, 因为 RMI API 是在网络接口(socket)和线程(thread)基础上生成的。

本课通过一个基于网络接口的简单程序来介绍网络接口和多线程程序设计的概 念。多线程程序可在同一时刻执行多项任务,如:监视多个客户程序的同时请求 等。

什么是网络接口和线程?

关于示例程序的说明

示例 1:服务器端程序

示例 1:客户端程序

示例 2:多线程服务程序

更多信息

注意: 生成放映幻灯片的线程化 Applet 程序部分介绍了如何在同一个程序中使用 多个线程的另一个示例。


什么是网络接口和线程?

网络接口是实现服务器程序与一个或多个客户程序之间双向通信的软件端点。 网络接口使服务器程序与运行该程序的计算机上特定的硬件端口关联起来,这样 在网络中的任何位置上,带有与该端口相关联的网络接口的客户程序都可以与这 个服务器程序实现通信。

服务器程序一般都向客户程序网络提供资源。客户程序向服务器程序发送请求, 而服务器程序则对该请求作出应答。

处理一个以上客户请求的方法之一是使服务器程序多线程化。多线程的服务器 为每一个从客户机接收到的通信创建一个线程。线程是一个独立于运行程序和其 他线程的指令序列。

通过使用线程,多线程化的服务器程序就可以接受来自客户机的连接,启动该 通信的线程并继续监视来自其它客户机的请求。

关于示例程序的说明

本课的示例程序涉及两个在 第 1 部分、 第 6 课:文件访问及许可 中介绍、由 FileIO.java 应用程序改编的客户/服务器程序对。

示例程序 1 设置了一个服务器程序和一个客户程序之间的客户/服务器通信过 程。服务器程序未多线程化,不能同时处理一个以上客户的请求。

示例程序 2 把服务器程序转化成多线程程序,从而可以处理来自一个以上客 户的请求。

示例程序 1:客户端工作情况

客户程序 会显示一个简单的用户界面,用于提示用户输入文本信息。在您 点击 Click Me 按钮时,文本信息就被发送给服务器程序。客户程 序希望得到服务器的响应,并会把接收到的响应信息打印到其标准输出设备上。

示例程序 1:服务器端工作情况

服务器程序会显示一个简单的用户界面,当用户点击 Click Me 按钮时,从客户机上接收来的文本信息就会显示出来。无论用户是否点击了 Click Me 按钮,服务器都会对所接收到的文本信息作出响应。

示例程序 1:编译和运行

若要运行示例程序,则必须首先启动服务器程序。否则,客户程序就无法创建 网络接口连接。以下是编译和运行示例程序的编译器和解释器命令。

  javac SocketServer.java
  javac SocketClient.java

  java SocketServer
  java SocketClient

示例程序 1:服务器端程序

服务器程序 在其 listenSocket 方法中创建 4321 端口上的 网络接口连接。它读取发送给它的数据,并在其 actionPerformed 方法中把该数据原样返回给服务器。

listenSocket 方法

listenSocket 方法生成一个 ServerSocket 对 象,它带有服务器程序监视客户通信时所用端口的端口号 。该端口号必须是可用 端口号,也就是说该端口号不能已为其它程序预留或本身是已经在用的端口号。 例如,Unix 系统把从 1 到 1023 的端口预留给管理功能,而只有大于 1024 的 端口可供使用。

public void listenSocket(){
  try{
    server = new ServerSocket(4321); 
  } catch (IOException e) {
    System.out.println("Could not listen on port 4321");
    System.exit(-1);
  }
然后,listenSocket 方法为发出请求的客户机生成一个 Socket 连接。本程序代码在客户机启动并请求连接到运行服务器程序的主机和端口。当成 功创建连接后,server.accept 方法就返回一个新的 Socket 对象。

  try{
    client = server.accept();
  } catch (IOException e) {
    System.out.println("Accept failed: 4321");
    System.exit(-1);
  }
之后,listenSocket 方法将生成一个 BufferedReader 对象来读取客户程序通过网络接口连接发送的数据。该方法还生成一个 PrintWriter 对象来把从客户机接收到的数据返回给服务器。

  try{
   in = new BufferedReader(new InputStreamReader(
			   client.getInputStream()));
   out = new PrintWriter(client.getOutputStream(), 
                         true);
  } catch (IOException e) {
    System.out.println("Read failed");
    System.exit(-1);
  }
}
最后,listenSocket 方法从输入流中循环读取客户机上输入的数 据,并把所读取的数据写入到输出流中返回。

    while(true){
      try{
        line = in.readLine();
//Send data back to client
        out.println(line);
      } catch (IOException e) {
        System.out.println("Read failed");
        System.exit(-1);
      }
    }

actionPerformed 方法

方法 actionPerformed 由 Java 平台调用,以监视按钮点击等 动作事件。这个 actionPerformed 方法利用存放在 line 对象中的文本给 textArea 对象赋初值,这样检索到的文本信息就 能够在终端用户处显示出来。

public void actionPerformed(ActionEvent event) {
   Object source = event.getSource();

   if(source == button){
       textArea.setText(line);
   }
}

示例程序 1:客户端程序

客户程序在其 listenSocket 方法中创建与特定主机和端口号 上的服务器程序的连接,并在其 actionPerformed 方法中把终端用 户输入的数据发送给服务器程序。actionPerformed 方法也接收服 务器返回的数据,并将其输出到命令行中。

listenSocket 方法

listenSocket 方法首先生成一个带有服务器程序用以监视客户 连接请求的计算机和端口的名称(kq6py)以及端口号(4321)。 接着,该方法再生成一个 PrintWriter 对象,以通过网络接口连 接向服务器程序发送数据。该方法还会生成一个 BufferedReader 对象用来读取服务器返回给客户机的文本信息。

public void listenSocket(){
//Create socket connection
   try{
     socket = new Socket("kq6py", 4321);
     out = new PrintWriter(socket.getOutputStream(), 
                 true);
     in = new BufferedReader(new InputStreamReader(
	        socket.getInputStream()));
   } catch (UnknownHostException e) {
     System.out.println("Unknown host: kq6py");
     System.exit(1);
   } catch  (IOException e) {
     System.out.println("No I/O");
     System.exit(1);
   }
}

actionPerformed 方法

actionPerformed 方法供 Java 平台调用以监视按钮点击等动 作事件。这个 actionPerformed 方法用于获取 Textfield 对象中的文本信息并把得到的信息传递给 PrintWriter 对象,然后 利用该对象并通过网络接口连接把信息发送给服务器程序。

之后,actionPerformed 方法把 Textfield 对象 置空,使之做好接收其它终端用户输入信息的准备。最后,本方法接收服务器发回 的文本信息,并打印输出。

public void actionPerformed(ActionEvent event){
   Object source = event.getSource();

   if(source == button){
//Send data over socket
      String text = textField.getText();
      out.println(text);
      textField.setText(new String(""));
      out.println(text);
   }
//Receive text from server
   try{
     String line = in.readLine();
     System.out.println("Text received: " + line);
   } catch (IOException e){
     System.out.println("Read failed");
     System.exit(1);
   }
}  

示例程序 2:多线程服务程序

示例程序现阶段只能在服务器程序和一个客户程序之间工作。若要实现多客户 连接,就必须把服务器程序转化为一个 多线程化服务器 程序。

第一客户机
第二客户机
第三客户机
多线程化服务器程序为每个客户请求生成一个新线程。这样,每个客户机就有了自 己与服务器来往传递数据的连接。当运行多个线程时,用户必须确保一个线程不会 受到其它线程中数据的干扰。

本例中,listenSocket 方法在 server.accept 调上循环以等待客户连接,并为所接收到的每一个客户连接请求生成 ClientWorker 类的实例。用于显示从客户连接接收文本信息的 textArea 变量将通过已接收到的客户连接被传递给 ClientWorker 实例变量。

public void listenSocket(){
  try{
    server = new ServerSocket(4444);
  } catch (IOException e) {
    System.out.println("Could not listen on port 4444");
    System.exit(-1);
  }
  while(true){
    ClientWorker w;
    try{
//server.accept returns a client connection
      w = new ClientWorker(server.accept(), textArea);
      Thread t = new Thread(w);
      t.start();
    } catch (IOException e) {
      System.out.println("Accept failed: 4444");
      System.exit(-1);
    }
  }
}

这一服务器程序对非线程化服务器程序所做的重要修改是,lineclient 变量不再是服务器类的实例变量,而只能在 ClientWorkerr 类内部处理。

ClientWorker 类实现 Runnable 接口,它有一 个名为 run 的方法。run 方法在每个线程中单独执 行。若有三个客户机请求连接,就要生成三个 ClientWorker 实例, 每个 ClientWorker 实例变量启动一个线程,而 run 方法则被每个线程所执行。

本例中,run 方法生成输入缓冲区及输出书写器、在输入流上 循环执行以等待客户机输入、把接收到的数据发回到客户机并把文本信息输出到 文本区中。

class ClientWorker implements Runnable {
  private Socket client;
  private JTextArea textArea;

//Constructor
  ClientWorker(Socket client, JTextArea textArea) {
    this.client = client;
    this.textArea = textArea;
  }

  public void run(){
    String line;
    BufferedReader in = null;
    PrintWriter out = null;
    try{
      in = new BufferedReader(new 
        InputStreamReader(client.getInputStream()));
      out = new 
        PrintWriter(client.getOutputStream(), true);
    } catch (IOException e) {
      System.out.println("in or out failed");
      System.exit(-1);
    }

    while(true){
      try{
        line = in.readLine();
//Send data back to client
        out.println(line);
//Append data to text area
        textArea.append(line);
       }catch (IOException e) {
        System.out.println("Read failed");
        System.exit(-1);
       }
    }
  }
}
JTextArea.append 方法是线程安全的,即它允许一个线程在其它线 程开始附加操作前完成自身的操作。这就可以避免线程全部或部分覆盖掉已插入文 本的信息串而破坏输出。如果 JTextArea.append 方法不具备线程 安全特征,您就需要把对 textArea.append(line) 的调用限制在 synchronized 方法之内,并把 run 方法对 textArea.append(line) 的调用更换为对 appendText(line) 的调用。
  public synchronized void appendText(line){
    textArea.append(line);
  }
关键字 synchronized 的含义是,当本线程锁定 textArea 时,其它线程不能修改该 textArea,直到本线程完成操作为止。

在程序为获得清除并释放资源的机会而退出前,方法 finalize() 由Java 虚拟机(JVM)* 调用。多线程化程序应在退出前关闭它们所使用的所有 Files(文件)和 Sockets(网络接口),这样才不 会出现资源不足的问题。finalize() 方法中对 server.close() 的调用可关闭该程序中各个线程所使用的 Socket 连接。

  protected void finalize(){
//Objects created in run method are finalized when
//program terminates and thread exits
     try{
        server.close();
    } catch (IOException e) {
        System.out.println("Could not close socket");
        System.exit(-1);
    }
  }

更多信息

有关网络接口的详细介绍见J ava 教程 网络接口详细说明 一节。

[TOP]

 

常见问答
下载中心
产品简介
 
 
Solaris论坛
 
   
 
null