套接字(socket)为两台计算机之间的通信提供了一种机制,在James Gosling注意到Java 语言之前,套接字就早已赫赫有名。该语言只是让您不必了解底层操作系统的细节就能有效地使用套接字。
客户机/服务器模型                                                                                                                         
客户机/服务器模型是一个应用程序开发框架,该框架是为了将数据的表示与其内部的处理和存储分离开来而设计的。客户机请求服务,服务器为这些请求服务。请求通过网络从客户机传递到服务器。服务器所进行的处理对客户机而言是隐藏的。一个服务器可以为多台客户机服务。

‘客户机/服务器应用程序的服务器部分’管理通过多个客户机访问服务器的、多个用户共享的资源。表明‘客户机/服务器程序的服务器部分’强大功能的最好例子应该是Web服务器,它通过Internet将HTML页传递给不同的Web用户。

协议

当计算机之间进行通讯的时候,也需要遵循一定的规则。数据以包的形式从一台机器发送到另一台。这些规则管理数据打包、数据传输速度和重新 数据将其恢复成原始形式。这些规则被称为网络协议。网络协议是通过网络进行通讯的系统所遵循的一系列规则和惯例。连网软件通常实现有高低层次之分的多层协议。网络协议的例子有:TCP/IP、UDP、Apple Talk和NetBEUI。
Java提供了一个丰富的、支持网络的类库,这些类使得应用程序能方便地访问网络资源。Java提供了两种通讯工具。它们是:使用用户报文协议(UDP)的报文和使用传输控制协议/因特网协议(TCP/IP)的Sockets(套接字)。
数据报包是一个字节数组从一个程序(发送程序)传送到另一个(接受程序)。由于数据报遵守UDP,不保证发出的数据包必须到达目的地。数据报并不是可信赖 的。因此,仅当传送少量数据时才使用,而且发送者和接受者之间的距离间隔不大,假如是网络交通高峰,或接受程序正处理来自其他程序的多个请求,就有机会出 现数据报包的丢失。
Sockets套接字用TCP来进行通讯。套接字模型同其他模型相比,优越性在于其不受客户请求来自何处的影响。只要客户机遵循TCP/IP协议,服务器 就会对它的请求提供服务。这意味着客户机可以是任何类型的计算机。客户机不再局限为UNIX、Windows、DOS或Macintosh平台,因此,网 上所有遵循TCP/IP协议的计算机可以通过套接字互相通讯。

Sockets套接字
3.1 Sockets概况
在客户机/服务器应用程序中,服务器提供象处理数据库查询或修改数据库中的数据之类的服务。发生在客户机和服务器之间的通讯必须是可靠的,同时数据在客户机上的次序应该和服务器发送出来的次序相同。
什么是套接字? 
 
简言之,一台机器上的套接字与另一台机器上的套接字交谈就创建一条通信通道。程序员可以用该通道来在两台机器之间发送数据。当您发送数据时,TCP/IP 协议栈的每一层都会添加适当的报头信息来包装数据。这些报头帮助协议栈把您的数据送到目的地。好消息是 Java 语言通过"流"为您的代码提供数据,从而隐藏了所有这些细节,这也是为什么它们有时候被叫做流套接字(streaming socket)的原因。

Java被设计成一种连网语言。它通过将连接功能封装到套接字类里而使得网络编程更加容易。套接字类即Socket类(它创建一个客户套接字)和ServerSocket类(它创建一个服务器套接字)。套接字类大致介绍如下:
   Socket是基类,它支持TCP协议。TCP是一个可靠的流网络连接协议。Socket类提供了流输入/输出的方法,使得从套接字中读出数据和往套接字中写数据都很容易。该类对于编写因特网上的通讯程序而言是必不可少的。
   ServerSocket是一个因特网服务程序用来监听客户请求的类。ServerSocket实际上并不执行服务;而是创建了一个Socket对象来代表客户机。通讯由创建的对象来完成。
3.2 IP地址和端口
因特网服务器可以被认为是一组套接字类,它们提供了一般称为服务的附加功能。服务的例子有:电子邮件、远程登录的Telnet、和通过网络传输文件的文件 传输协议(FTP)。每种服务都与一个端口相联系。端口是一个数值地址,通过它来处理服务请求(就象请求Web页一样)。
TCP协议需要两个数据项:IP地址和端口号。
因特网协议(IP)提供每一项网络设备。这些设备都带有一个称为IP地址的逻辑地址。由因特网协议提供的IP地址具有特定的形式。每个IP地址都是32位的数值,表示4个范围在0到255之间的8位数值金诺已经注册了它的名字,分配给http://www.jinnuo.com的IP地址为192.168.0.110。
注意:域名服务或DNS服务是将http://www.jinnuo.com翻译成192.168.0.110的服务。有趣的是一个网络名可以映射到许多IP地址。对于经常访问的站点可能需要这一功 能,因为这些站点容纳大量的信息,并需要多个IP地址来提供业务服务。
如果没有指明端口号,则使用服务文件中服务器的端口。每种协议有一个缺省的端口号,在端口号未指明时使用该缺省端口号。
端口号    应用
21    FTP.传输文件
23    Telnet.提供远程登录
25    SMTP.传递邮件信息
67    BOOTP.在启动时提供配置情况
80    HTTP.传输Web页
109    POP.使用户能访问远程系统中的邮箱
让我们再来看一下URL:http://www.jinnuo.com
URL的第一部分(http)意味着你正在使用超文本传输协议(HTTP),该协议处理Web文档。如果没有指明文件,大多数的Web服务器会取一个叫index.html文件。因此,IP地址和端口既可以通过明确指出URL各部分来决定,也可以由缺省值决定。
创建Socket客户
我们将在本部分讨论的示例将阐明在 Java 代码中如何使用 Socket 和 ServerSocket。客户机用 Socket 连接到服务器。服务器用 ServerSocket 在端口 1001 侦听。客户机请求服务器 C: 驱动器上的文件内容。
创建 RemoteFileClient 
  1. import java.io.*; 
  2. import java.net.*; 
  3. public class RemoteFileClient { 
  4.     protected BufferedReader socketReader; 
  5.     protected PrintWriter socketWriter; 
  6.     protected String hostIp; 
  7.     protected int hostPort; 
  8.     //构造方法 
  9.     public RemoteFileClient(String hostIp, int hostPort) { 
  10.         this.hostIp = hostIp; 
  11.         this.hostPort=hostPort;  
  12.     } 
  13.     //向服务器请求文件的内容 
  14.     public String getFile(String fileNameToGet) { 
  15.         StringBuffer fileLines = new StringBuffer(); 
  16.         try { 
  17.             socketWriter.println(fileNameToGet);             
  18.             socketWriter.flush(); 
  19.             String line = null
  20.             while((line=socketReader.readLine())!=null
  21.                 fileLines.append(line+"\n"); 
  22.         } 
  23.         catch(IOException e) { 
  24.             System.out.println("Error reading from file: "+fileNameToGet); 
  25.         } 
  26.         return fileLines.toString(); 
  27.     } 
  28.     //连接到远程服务器 
  29.     public void setUpConnection() { 
  30.         try { 
  31.             Socket client = new Socket(hostIp,hostPort); 
  32.             socketReader = new BufferedReader(new InputStreamReader(client.getInputStream())); 
  33.             socketWriter = new PrintWriter(client.getOutputStream()); 
  34.         } 
  35.         catch(UnknownHostException e) { 
  36.             System.out.println("Error1 setting up socket connection: unknown host at "+hostIp+":"+hostPort); 
  37.         } 
  38.         catch(IOException e) { 
  39.             System.out.println("Error2 setting up socket connection: "+e); 
  40.         } 
  41.     } 
  42.     //断开远程服务器 
  43.     public void tearDownConnection() { 
  44.         try { 
  45.             socketWriter.close();  
  46.             socketReader.close(); 
  47.         }catch(IOException e) {             
  48.             System.out.println("Error tearing down socket connection: "+e); 
  49.         } 
  50.     } 
  51.     public static void main(String args[]) { 
  52.         RemoteFileClient remoteFileClient = new RemoteFileClient("127.0.0.1",1001); 
  53.         remoteFileClient.setUpConnection(); 
  54.         StringBuffer fileContents = new StringBuffer(); 
  55.         fileContents.append(remoteFileClient.getFile("RemoteFileServer.java"));         
  56.         //remoteFileClient.tearDownConnection(); 
  57.         System.out.println(fileContents); 
  58.     } 

我们类的构造器有两个参数:远程主机的IP地址和端口号各一个,而且构造器将它们赋给实例变量。
我们的类有一个 main() 方法和三个其它方法。稍后我们将探究这些方法的细节。现在您只需知道 setUpConnection() 将连接到远程服务器,getFile() 将向远程服务器请求 fileNameToGet 的内容以及 tearDownConnection() 将从远程服务器上断开。

请记住我们的客户机和服务器只是来回传送字节。客户机和服务器都必须知道另一方即将发送的是什么以使它们能够作出适当的响应。在这个案例中,服务器知道我们将发送一条有效的文件路径。
当您实例化一个 Socket 时,将抛出 UnknownHostException。这里我们不特别处理它,但我们打印一些信息到控制台以告诉我们发生了什么错误。同样地,当我们试图获取 Socket 的 InputStream 或 OutputStream 时,如果抛出了一个一般 IOException,我们也打印一些信息到控制台。

总结一下客户机
我们的类研究完了。在我们继续往前讨论服务器端的情况之前,让我们回顾一下创建和使用 Socket 的步骤:
1.    用您想连接的机器的 IP 地址和端口实例化 Socket(如有问题则抛出 Exception)。
2.    获取 Socket 上的流以进行读写。
3.    把流包装进 BufferedReader/PrintWriter 的实例,如果这样做能使事情更简单的话。
4.    对 Socket 进行读写。
5.    关闭打开的流。
 
5.创建服务器Socket
创建 RemoteFileServer 
  1. import java.io.*; 
  2. import java.net.*; 
  3. public class RemoteFileServer {     
  4.     int listenPort; 
  5.     public RemoteFileServer(int listenPort) { 
  6.         this.listenPort=listenPort; 
  7.     } 
  8.     //允许客户机连接到服务器,等待客户机请求     
  9.     public void acceptConnections() { 
  10.         try { 
  11.             ServerSocket server = new ServerSocket(listenPort); 
  12.             Socket incomingConnection = null
  13.             while(true) { 
  14.                 incomingConnection = server.accept(); //accept()返回一个新的Socket,这个 Socket绑定到服务器上一个随机指定的端口
  15.                 handleConnection(incomingConnection); 
  16.             } 
  17.         } 
  18.         catch(BindException e) { 
  19.             System.out.println("Unable to bind to port "+listenPort); 
  20.         } 
  21.         catch(IOException e) { 
  22.             System.out.println("Unable to instantiate a ServerSocket on port: "+listenPort);   
  23.              
  24.         } 
  25.     } 
  26.     //与客户机Socket交互以将客户机所请求的文件的内容发送到客户机 
  27.     public void handleConnection(Socket incomingConnection) { 
  28.         try { 
  29.             OutputStream outputToSocket = incomingConnection.getOutputStream();  
  30.             InputStream inputFromSocket = incomingConnection.getInputStream(); 
  31.             BufferedReader streamReader = new BufferedReader(new InputStreamReader(inputFromSocket)); 
  32.             FileReader fileReader = new FileReader(new File(streamReader.readLine())); 
  33.             BufferedReader bufferedFileReader = new BufferedReader(fileReader);  
  34.             PrintWriter streamWriter = new PrintWriter(incomingConnection.getOutputStream()); 
  35.             String line = null
  36.             while((line=bufferedFileReader.readLine())!=null){ 
  37.                 streamWriter.println(line); 
  38.             } 
  39.             fileReader.close(); 
  40.             streamWriter.close(); 
  41.             streamReader.close(); 
  42.         } 
  43.         catch(Exception e) { 
  44.             System.out.println("Error handling a client: "+e); 
  45.             e.printStackTrace();  
  46.         } 
  47.     } 
  48.     public static void main(String args[]) { 
  49.         RemoteFileServer server = new RemoteFileServer(1001); 
  50.         server.acceptConnections(); 
  51.     } 

我们的类有一个main()方法和两个其它方法。acceptConnections()将允许客户机连接到服务器以及handleConnection()与客户机Socket交互以将您所请求的文件的内容发送到客户机。
实现 main()
这里我们实现main()方法,它将创建RemoteFileServer并告诉它接受连接:服务器端的main()方法中,我们实例化一个新 RemoteFileServer,它将在侦听端口(1001)上侦听进入的连接请求。然后我们调用acceptConnections()来告诉该 server进行侦听。
请注意,您可以通过用毫秒数调用setSoTimeout()来为accept()调用设置超时, 以避免实际长时间的等待。调用setSoTimeout()将使accept()经过指定占用时间后抛出IOException。

请注意我们在完成从Socket的读操作之后关闭streamWriter和streamReader。您或许会问我们为什么不在读取文件名之后立刻关闭 streamReader。原因是当您这样做时,您的客户机将不会获取任何数据。如果您在关闭streamWriter之前关闭 streamReader,则您可以往Socket写任何东西,但却没有任何数据能通过通道(通道被关闭了)。
总结一下服务器
在我们接着讨论另一个更实际的示例之前,让我们回顾一下创建和使用ServerSocket的步骤:
1.    用一个您想让它侦听传入客户机连接的端口来实例化一个ServerSocket(如有问题则抛出 Exception)。
2.    调用ServerSocket的accept()以在等待连接期间造成阻塞。
3.    获取位于该底层Socket的流以进行读写操作。
4.    按使事情简单化的原则包装流。
5.    对Socket进行读写。
6.    关闭打开的流(并请记住,永远不要在关闭Writer之前关闭Reader)。 

 

6. 创建多线程Socket服务器

前面的示例教给您基础知识,但并不能令您更深入。如果您到此就停止了,那么您一次只能处理一台客户机。原因是handleConnection()是一个 阻塞方法。只有当它完成了对当前连接的处理时,服务器才能接受另一个客户机。在多数时候,您将需要(也有必要)一个多线程服务器。
创建 MultithreadedRemoteFileServer 
  1. import java.io.*; 
  2. import java.net.*; 
  3. public class MultithreadedRemoteFileServer { 
  4.     int listenPort; 
  5.     public MultithreadedRemoteFileServer(int listenPort) {         
  6.         this.listenPort=listenPort; 
  7.     } 
  8.     //允许客户机连接到服务器,等待客户机请求     
  9.     public void acceptConnections() { 
  10.         try { 
  11.             ServerSocket server = new ServerSocket(listenPort, 5); 
  12.             Socket incomingConnection = null
  13.             while(true) { 
  14.                 incomingConnection = server.accept();  
  15.                 handleConnection(incomingConnection); 
  16.             } 
  17.         }         
  18.         catch(BindException e) { 
  19.             System.out.println("Unable to bind to port "+listenPort); 
  20.         }  
  21.         catch(IOException e) {             
  22.             System.out.println("Unable to instantiate a ServerSocket on port: "+listenPort);         
  23.         } 
  24.     } 
  25.     //与客户机Socket交互以将客户机所请求的文件的内容发送到客户机     
  26.     public void handleConnection(Socket connectionToHandle) {  
  27.         new Thread(new ConnectionHandler(connectionToHandle)).start(); 
  28.     } 
  29.     public static void main(String args[]) { 
  30.         MultithreadedRemoteFileServer server = new MultithreadedRemoteFileServer(1001); 
  31.         server.acceptConnections(); 
  32.     } 

我们仍然在服务器接受一个连接之后调用handleConnection(),但现在我们把该Socket传递给ConnectionHandler的一个实例,它是 Runnable的。我们用ConnectionHandler创建一个新 Thread 并启动它。ConnectionHandler的run()方法包Socket读/写和读File的代码,这些代码原来在RemoteFileServer的handleConnection()中。
创建 ConnectionHandler 
  1. import java.io.*; 
  2. import java.net.*; 
  3. public class ConnectionHandler implements Runnable { 
  4.     protected Socket socketToHandle; 
  5.     public ConnectionHandler(Socket socketToHandle) { 
  6.         this.socketToHandle=socketToHandle; 
  7.     } 
  8.     public void run() { 
  9.         try { 
  10.             PrintWriter streamWriter = new PrintWriter(socketToHandle.getOutputStream());  
  11.             BufferedReader streamReader = new BufferedReader(new InputStreamReader(socketToHandle.getInputStream())); 
  12.             String fileToRead = streamReader.readLine(); 
  13.             BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead));  
  14.             String line =null
  15.             while((line=fileReader.readLine())!=null) { 
  16.                 streamWriter.println(line); 
  17.             } 
  18.             fileReader.close(); 
  19.             streamWriter.close(); 
  20.             streamReader.close(); 
  21.         } 
  22.         catch(Exception e) { 
  23.             System.out.println("Error handling a client: "+e); 
  24.         e.printStackTrace(); 
  25.         } 
  26.     } 


请记住我们应该从客户机获取一条有效的文件路径,这样用该路径名构造一个新File,把它包装进FileReader以处理读文件的操作,然后把它包装进 BufferedReader以让我们逐行地读该文件。我们while循环中调用BufferedReader上的readLine()直到不再有要读的 行。请记注,对readLine()的调用将造成阻塞,直到有字节来到为止。我们获取一些字节之后就把它们放到本地的line变量中,然后写出到客户机 上。完成读写操作之后,我们关闭打开的流。
总结一下多线程服务器
让我们回顾一下创建和使用“多线程版”的服务器的步骤:
1.    修改 acceptConnections() 以用缺省为 50(或任何您想要的大于 的指定数字)实例化 ServerSocket。
2.    修改 ServerSocket 的 handleConnection() 以用 ConnectionHandler 的一个实例生成一个新的 Thread。
3.    借用 RemoteFileServer 的 handleConnection() 方法的代码实现 ConnectionHandler 类。 

创建带有连接池的Socket服务器

我们现在已经拥有的 MultithreadedServer 每当有客户机申请一个连接时都在一个新Thread中创建一个新 ConnectionHandler。这意味着可能有一捆Thread“躺”在我们周围。而且创建Thread的系统开销并不是微不足道的。如果性能成为 了问题(也请不要事到临头才意识到它),更高效地处理我们的服务器是件好事。那么,我们如何更高效地管理服务器端呢?我们可以维护一个进入的连接池,一定 数量的ConnectionHandler将为它提供服务。这种设计能带来以下好处:
   它限定了允许同时连接的数目。 
   我们只需启动ConnectionHandler Thread一次。 
在服务器端,我们在服务器启动时创建一定数量的 ConnectionHandler,我们把进入的连接放入“池”中并让ConnectionHandler打理剩下的事情。这种设计中有很多我们不打算讨论的可能存在的技巧。例如,我们可以通过限定允许在“池”中建立的连接的数目来拒绝客户机。
创建 PooledRemoteFileServer 
  1. import java.io.*; 
  2. import java.net.*; 
  3. import java.util.*; 
  4. public class PooledRemoteFileServer { 
  5.     protected int maxConnections; 
  6.     protected int listenPort; 
  7.     protected ServerSocket serverSocket; 
  8.     public PooledRemoteFileServer(int aListenPort, int maxConnections) { 
  9.         listenPort= aListenPort; 
  10.         this.maxConnections = maxConnections; 
  11.     } 
  12.     public void acceptConnections() { 
  13.         try { 
  14.             ServerSocket server = new ServerSocket(listenPort, 5); 
  15.             Socket incomingConnection = null
  16.             while(true) { 
  17.                 incomingConnection = server.accept(); 
  18.                 handleConnection(incomingConnection); 
  19.             } 
  20.         } 
  21.         catch(BindException e) { 
  22.             System.out.println(""); 
  23.         } 
  24.         catch(IOException e) { 
  25.             System.out.println(""+listenPort); 
  26.         } 
  27.     } 
  28.     protected void handleConnection(Socket connectionToHandle) { 
  29.         PooledConnectionHandler.proce***equest(connectionToHandle); 
  30.     } 
  31.     public void setUpHandlers() { 
  32.         for(int i=0; i<maxConnections; i++) { 
  33.             PooledConnectionHandler currentHandler = new PooledConnectionHandler(); 
  34.             new Thread(currentHandler, "Handler " + i).start(); 
  35.         } 
  36.     } 
  37.     public static void main(String args[]) { 
  38.         PooledRemoteFileServer server = new PooledRemoteFileServer(10013); 
  39.         server.setUpHandlers();  
  40.         server.acceptConnections(); 
  41.     } 

我们给类以下实例变量以保存:
   我们的服务器能同时处理的活动客户机连接的最大数目
   进入的连接的侦听端口(我们没有指定缺省值,但如果您想这样做,并不会受到限制)
   将接受客户机连接请求的 ServerSocket 
类的构造器用的参数是侦听端口和连接的最大数目
我们的类有一个 main() 方法和三个其它方法。setUpHandlers()创 建数目为maxConnections的大量PooledConnectionHandler,而其它两个方法则与我们前面已经看到的相 似:acceptConnections()在ServerSocket上侦听传入的客户机连接,而handleConnection则在客户机连接一旦 被建立后就实际处理它。
 
我们的main()方法很简单。我们实例化一个新的PooledRemoteFileServer,它将通过调用 setUpHandlers()来建立三个PooledConnectionHandler。一旦服务器就绪,我们就告诉它 acceptConnections()。
建立连接处理程序
   public void setUpHandlers() {
        for(int i=0; i<maxConnections; i++) {
            PooledConnectionHandler currentHandler new PooledConnectionHandler();
            new Thread(currentHandler, "Handler i).start();
        }
    }
setUpHandlers()方法创建maxConnections(例如 3)个PooledConnectionHandler并在新 Thread中激活它们。用实现了Runnable的对象来创建Thread使我们可以在Thread调用start()并且可以期望在Runnable 上调用了run()。换句话说,我们的PooledConnectionHandler将等着处理进入的连接,每个都在它自己的Thread中进行。我们 在示例中只创建三个Thread,而且一旦服务器运行,这就不能被改变。
处理连接
这里我们实现需作改动的handleConnections()方法,它将委派PooledConnectionHandler处理连接:
    protected void handleConnection(Socket connectionToHandle) {
        PooledConnectionHandler.proce***equest(connectionToHandle);
    }
我们现在叫 PooledConnectionHandler 处理所有进入的连接(proce***equest() 是一个静态方法)。
创建 PooledRemoteFileServer 
import java.io.*;
import java.net.*;
import java.util.*;
public class PooledConnectionHandler implements Runnable {
    protected Socket connection;
    protected static List pool new LinkedList();
    public PooledConnectionHandler() {}
    public void  handleConnection() {
        try {
            PrintWriter streamWriter new PrintWriter(connection.getOutputStream()); 
            BufferedReader streamReader new BufferedReader(new InputStreamReader(connection.getInputStream())); 
            String fileToRead streamReader.readLine();
            BufferedReader fileReader new BufferedReader(new FileReader(fileToRead));
            String line null;
            while((line=fileReader.readLine())!=null)
                streamWriter.println(line); 
            fileReader.close();
            streamWriter.close();
            streamReader.close();
        }
        catch(FileNotFoundException e) {
            System.out.println("");
        }
        catch(IOException e) {
            System.out.println(""+e);
        }
    }
    public static void proce***equest(Socket requestToHandle) {
        synchronized(pool) {
            pool.add(pool.size(), requestToHandle);
            pool.notifyAll();
        }
    }
    public void run() {
        while(true) {
            synchronized(pool) {
                while(pool.isEmpty()) {
                    try {
                        pool.wait();
                    }
                    catch(InterruptedException e) {
                        e.printStackTrace(); 
                    }
                }
                connection= (Socket)pool.remove(0); 
            }
            handleConnection();
        }
    }
}
这个助手类与 ConnectionHandler 非常相似,但它带有处理连接池的手段。该类有两个实例变量:
   connection 是当前正在处理的 Socket 
   名为 pool 的静态 LinkedList 保存需被处理的连接 
填充连接池
这里我们实现PooledConnectionHandler上的proce***equest()方法,它将把传入请求添加到池中,并告诉其它正在等待的对象该池已经有一些内容:
    public static void proce***equest(Socket requestToHandle) {
        synchronized(pool) {
            pool.add(pool.size(), requestToHandle);
            pool.notifyAll();
        }
    }
synchronized 块是个稍微有些不同的东西。您可以同步任何对象上的一个块,而不只是在本身的某个方法中含有该块的对象。在我们的示例中,proce***equest() 方法包含有一个 pool(请记住它是一个 LinkedList,保存等待处理的连接池)的 synchronized块。我们这样做的原因是确保没有别人能跟我们同时修改连接池。
既然我们已经保证了我们是唯一“涉水”池中的人,我们就可以把传入的Socket添加到LinkedList的尾端。一旦我们添加了新的连接,我们就用以下代码通知其它正在等待该池的Thread,池现在已经可用:
    pool.notifyAll();
Object的所有子类都继承这个notifyAll()方法。这个方法,连同我们下一屏将要讨论的wait()方法一起,就使一个Thread能够让另一个Thread知道一些条件已经具备。这意味着该第二个Thread一定正在等待那些条件的满足。
从池中获取连接
这里我们实现PooledConnectionHandler上需作改动的run()方法,它将在连接池上等待,并且池中一有连接就处理它:
    public void run() {
        while(true) {
            synchronized(pool) {
                while(pool.isEmpty()) {
                    try {
                        pool.wait();
                    }
                    catch(InterruptedException e) {
                        e.printStackTrace(); 
                    }
                }
                connection= (Socket)pool.remove(0); 
            }
            handleConnection();
        }
    }
回想一下在前面讲过的:一个Thread正在等待有人通知它连接池方面的条件已经满足了。在我们的示例中,请记住我们有三个 PooledConnectionHandler在等待使用池中的连接。每个PooledConnectionHandler都在它自已的Thread中 运行,并通过调用pool.wait()产生阻塞。当我们的proce***equest()在连接池上调用notifyAll()时,所有正在等待的 PooledConnectionHandler都将得到“池已经可用”的通知。然后各自继续前行调用pool.wait(),并重新检查 while(pool.isEmpty())循环条件。除了一个处理程序,其它池对所有处理程序都将是空的,因此,在调用pool.wait()时,除了 一个处理程序,其它所有处理程序都将再次产生阻塞。恰巧碰上非空池的处理程序将跳出while(pool.isEmpty())循环并攫取池中的第一个连 接:
    connection= (Socket)pool.remove(0);
处理程序一旦有一个连接可以使用,就调用 handleConnection() 处理它。
在我们的示例中,池中可能永远不会有多个连接,只是因为事情很快就被处理掉了。如果池中有一个以上连接,那么其它处理程序将不必等待新的连接被添加到池。当它们检查pool.isEmpty()条件时,将发现其值为假,然后就从池中攫取一个连接并处理它。
还有另一件事需注意。当run()拥有池的互斥锁时,proce***equest()如何能够把连接放到池中呢?答案是对池上的wait()的调用释放锁,而wait()接着就在自己返回之前再次攫取该锁。这就使得池对象的其它同步代码可以获取该锁。
处理连接:再一次
这里我们实现需做改动的handleConnection()方法,该方法将攫取连接的流,使用它们,并在任务完成之后清除它们:
    public void  handleConnection() {
        try {
            PrintWriter streamWriter new PrintWriter(connection.getOutputStream()); 
            BufferedReader streamReader new BufferedReader(new InputStreamReader(connection.getInputStream())); 
            String fileToRead streamReader.readLine();
            BufferedReader fileReader new BufferedReader(new FileReader(fileToRead));
            String line null;
            while((line=fileReader.readLine())!=null)
                streamWriter.println(line); 
            fileReader.close();
            streamWriter.close();
            streamReader.close();
        }
        catch(FileNotFoundException e) {
            System.out.println("");
        }
        catch(IOException e) {
            System.out.println(""+e);
        }
    }
跟在多线程服务器中不同,我们的PooledConnectionHandler有一个handleConnection()方法。这个方法的代码跟非池 式的ConnectionHandler上的run()方法的代码完全一样。首先,我们把OutputStream和InputStream分别包装进 (用Socket上的getOutputStream()和getInputStream())BufferedReader和PrintWriter。 然后我们逐行读目标文件,就象我们在多线程示例中做的那样。再一次,我们获取一些字节之后就把它们放到本地的line变量中,然后写出到客户机。完成读写 操作之后,我们关闭FileReader和打开的流。
总结一下带有连接池的服务器
让我们回顾一下创建和使用“池版”服务器的步骤:
1.    创建一个新种类的连接处理程序(我们称之为 PooledConnectionHandler)来处理池中的连接。
2.    修改服务器以创建和使用一组 PooledConnectionHandler。 

Java 语言简化了套接字在应用程序中的使用。它的基础实际上是 java.net 包中的 Socket 和 ServerSocket 类。一旦您理解了表象背后发生的情况,就能容易地使用这些类。在现实生活中使用套接字只是这样一件事,即通过贯彻优秀的 OO 设计原则来保护应用程序中各层间的封装。我们为您展示了一些有帮助的类。这些类的结构对我们的应用程序隐藏了 Socket 交互作用的低级细节 使应用程序能只使用可插入的 ClientSocketFacade 和 ServerSocketFacade。在有些地方(在 Facade 内),您仍然必须管理稍显杂乱的字节细节,但您只须做一次就可以了。更好的是,您可以在将来的项目中重用这些低级别的助手类。