socket

创建 RemoteFileClient 类
import java.io.*;
import java.net.*;
public class RemoteFileClient {
    protected BufferedReader socketReader;
    protected PrintWriter socketWriter;
    protected String hostIp;
    protected int hostPort;
    //构造方法
    public RemoteFileClient(String hostIp, int hostPort) {
        this.hostIp = hostIp;
        this.hostPort=hostPort;
    }
    //向服务器请求文件的内容
    public String getFile(String fileNameToGet) {
        StringBuffer fileLines = new StringBuffer();
        try {
            socketWriter.println(fileNameToGet);           
            socketWriter.flush();
            String line = null;
            while((line=socketReader.readLine())!=null)
                fileLines.append(line+"\n");
        }
        catch(IOException e) {
            System.out.println("Error reading from file: "+fileNameToGet);
        }
        return fileLines.toString();
    }
    //连接到远程服务器
    public void setUpConnection() {
        try {
            Socket client = new Socket(hostIp,hostPort);
            socketReader = new BufferedReader(new InputStreamReader(client.getInputStream()));
            socketWriter = new PrintWriter(client.getOutputStream());
        }
        catch(UnknownHostException e) {
            System.out.println("Error1 setting up socket connection: unknown host at "+hostIp+":"+hostPort);
        }
        catch(IOException e) {
            System.out.println("Error2 setting up socket connection: "+e);
        }
    }
    //断开远程服务器
    public void tearDownConnection() {
        try {
            socketWriter.close();
            socketReader.close();
        }catch(IOException e) {           
            System.out.println("Error tearing down socket connection: "+e);
        }
    }
    public static void main(String args[]) {
        RemoteFileClient remoteFileClient = new RemoteFileClient("127.0.0.1",1001);
        remoteFileClient.setUpConnection();
        StringBuffer fileContents = new StringBuffer();
        fileContents.append(remoteFileClient.getFile("RemoteFileServer.java"));       
        //remoteFileClient.tearDownConnection();
        System.out.println(fileContents);
    }
}
首先我们导入 java.net 和 java.io。java.net 包为您提供您需要的套接字工具。java.io 包为您提供对流进行读写的工具,这是您与 TCP 套接字通信的唯一途径。
我们给我们的类实例变量以支持对套接字流的读写和存储我们将连接到的远程主机的详细信息。
我们类的构造器有两个参数:远程主机的IP地址和端口号各一个,而且构造器将它们赋给实例变量。
我们的类有一个 main() 方法和三个其它方法。稍后我们将探究这些方法的细节。现在您只需知道 setUpConnection() 将连接到远程服务器,getFile() 将向远程服务器请求 fileNameToGet 的内容以及 tearDownConnection() 将从远程服务器上断开。
实现 main()
这里我们实现 main() 方法,它将创建 RemoteFileClient 并用它来获取远程文件的内容,然后打印结果。main() 方法用主机的 IP 地址和端口号实例化一个新 RemoteFileClient(客户机)。然后,我们告诉客户机建立一个到主机的连接。接着,我们告诉客户机获取主机上一个指定文件的内容。最后,我们告诉客户机断开它到主机的连接。我们把文件内容打印到控制台,只是为了证明一切都是按计划进行的。
建立连接
这里我们实现 setUpConnection() 方法,它将创建我们的 Socket 并让我们访问该套接字的流:
    public void setUpConnection() {
        try {
            Socket client = new Socket(hostIp,hostPort);
            socketReader = new BufferedReader(new InputStreamReader(client.getInputStream()));
            socketWriter = new PrintWriter(client.getOutputStream());
        }
        catch(UnknownHostException e) {
            System.out.println("Error1 setting up socket connection: unknown host at "+hostIp+":"+hostPort);
        }
        catch(IOException e) {
            System.out.println("Error2 setting up socket connection: "+e);
        }
    }
setUpConnection() 方法用主机的 IP 地址和端口号创建一个 Socket:
Socket client = new Socket(hostIp, hostPort);
我们把 Socket 的 InputStream 包装进 BufferedReader 以使我们能够读取流的行。然后,我们把 Socket 的 OutputStream 包装进 PrintWriter 以使我们能够发送文件请求到服务器:
socketReader = new BufferedReader(new InputStreamReader(client.getInputStream()));socketWriter = new PrintWriter(client.getOutputStream());
请记住我们的客户机和服务器只是来回传送字节。客户机和服务器都必须知道另一方即将发送的是什么以使它们能够作出适当的响应。在这个案例中,服务器知道我们将发送一条有效的文件路径。
当您实例化一个 Socket 时,将抛出 UnknownHostException。这里我们不特别处理它,但我们打印一些信息到控制台以告诉我们发生了什么错误。同样地,当我们试图获取 Socket 的 InputStream 或 OutputStream 时,如果抛出了一个一般 IOException,我们也打印一些信息到控制台。
与主机交谈
这里我们实现 getFile() 方法,它将告诉服务器我们想要什么文件并在服务器传回其内容时接收该内容。
    public String getFile(String fileNameToGet) {
        StringBuffer fileLines = new StringBuffer();
        try {
            socketWriter.println(fileNameToGet);           
            socketWriter.flush();
            String line = null;
            while((line=socketReader.readLine())!=null)
                fileLines.append(line+"\n");
        }
        catch(IOException e) {
            System.out.println("Error reading from file: "+fileNameToGet);
        }
        return fileLines.toString();
    }
对getFile()方法的调用要求一个有效的文件路径String。它首先创建名为fileLines的 StringBuffer,fileLines 用于存储我们读自服务器上的文件的每一行。
StringBuffer fileLines = new StringBuffer();
在 try{}catch{} 块中,我们用 PrintWriter 把请求发送到主机,PrintWriter 是我们在创建连接期间建立的。
    socketWriter.println(fileNameToGet);    socketWriter.flush();
请注意这里我们是 flush() 该 PrintWriter,而不是关闭它。这迫使数据被发送到服务器而不关闭 Socket。
一旦我们已经写到 Socket,我们就希望有一些响应。我们不得不在 Socket 的 InputStream 上等待它,我们通过在 while 循环中调用 BufferedReader 上的 readLine() 来达到这个目的。我们把每一个返回行附加到 fileLines StringBuffer(带有一个换行符以保护行):
    String line = null;    while((line=socketReader.readLine())!=null)        fileLines.append(line+"\n");
断开连接
这里我们实现 tearDownConnection() 方法,它将在我们使用完毕连接后负责“清除”。tearDownConnection()方法只是分别关闭我们在Socket的InputStream和OutputStream上创建的 BufferedReader和PrintWriter。这样做会关闭我们从Socket获取的底层流,所以我们必须捕捉可能的 IOException。
总结一下客户机
我们的类研究完了。在我们继续往前讨论服务器端的情况之前,让我们回顾一下创建和使用 Socket 的步骤:
1.    用您想连接的机器的 IP 地址和端口实例化 Socket(如有问题则抛出 Exception)。
2.    获取 Socket 上的流以进行读写。
3.    把流包装进 BufferedReader/PrintWriter 的实例,如果这样做能使事情更简单的话。
4.    对 Socket 进行读写。
5.    关闭打开的流。
5 创建服务器Socket
创建 RemoteFileServer 类
import java.io.*;
import java.net.*;
public class RemoteFileServer {   
    int listenPort;
    public RemoteFileServer(int listenPort) {
        this.listenPort=listenPort;
    }
    //允许客户机连接到服务器,等待客户机请求   
    public void acceptConnections() {
        try {
            ServerSocket server = new ServerSocket(listenPort);
            Socket incomingConnection = null;
            while(true) {
                incomingConnection = server.accept();
                handleConnection(incomingConnection);
            }
        }
        catch(BindException e) {
            System.out.println("Unable to bind to port "+listenPort);
        }
        catch(IOException e) {
            System.out.println("Unable to instantiate a ServerSocket on port: "+listenPort); 
           
        }
    }
    //与客户机Socket交互以将客户机所请求的文件的内容发送到客户机
    public void handleConnection(Socket incomingConnection) {
        try {
            OutputStream outputToSocket = incomingConnection.getOutputStream();
            InputStream inputFromSocket = incomingConnection.getInputStream();
            BufferedReader streamReader = new BufferedReader(new InputStreamReader(inputFromSocket));
            FileReader fileReader = new FileReader(new File(streamReader.readLine()));
            BufferedReader bufferedFileReader = new BufferedReader(fileReader);
            PrintWriter streamWriter = new PrintWriter(incomingConnection.getOutputStream());
            String line = null;
            while((line=bufferedFileReader.readLine())!=null){
                streamWriter.println(line);
            }
            fileReader.close();
            streamWriter.close();
            streamReader.close();
        }
        catch(Exception e) {
            System.out.println("Error handling a client: "+e);
            e.printStackTrace();
        }
    }
    public static void main(String args[]) {
        RemoteFileServer server = new RemoteFileServer(1001);
        server.acceptConnections();
    }
}
跟客户机中一样,我们首先导入java.net的java.io。接着,我们给我们的类一个实例变量以保存端口,我们从该端口侦听进入的连接。缺省情况下,端口是1001。
我们的类有一个main()方法和两个其它方法。稍后我们将探究这些方法的细节。现在您只需知道acceptConnections()将允许客户机连接到服务器以及handleConnection()与客户机Socket交互以将您所请求的文件的内容发送到客户机。
实现 main()
这里我们实现main()方法,它将创建RemoteFileServer并告诉它接受连接:服务器端的main()方法中,我们实例化一个新RemoteFileServer,它将在侦听端口(1001)上侦听进入的连接请求。然后我们调用acceptConnections()来告诉该server进行侦听。
接受连接
这里我们实现 acceptConnections() 方法,它将创建一个 ServerSocket 并等待连接请求:
    public void acceptConnections() {
        try {
            ServerSocket server = new ServerSocket(listenPort);
            Socket incomingConnection = null;
            while(true) {
                incomingConnection = server.accept();
                handleConnection(incomingConnection);
            }
        }
        catch(BindException e) {
            System.out.println("Unable to bind to port "+listenPort);
        }
        catch(IOException e) {
            System.out.println("Unable to instantiate a ServerSocket on port: "+listenPort); 
           
        }
    }
acceptConnections()用欲侦听的端口号来创建ServerSocket。然后我们通过调用该ServerSocket的accept()来告诉它开始侦听。accept()方法将造成阻塞直到来了一个连接请求。此时,accept()返回一个新的Socket,这个Socket绑定到服务器上一个随机指定的端口,返回的Socket被传递给handleConnection()。请注意我们在一个无限循环中处理对连接的接受。这里不支持任何关机。
无论何时如果您创建了一个无法绑定到指定端口(可能是因为别的什么控制了该端口)的 ServerSocket,Java代码都将抛出一个错误。所以这里我们必须捕捉可能的BindException。就跟在客户机端上时一样,我们必须捕捉IOException,当我们试图在ServerSocket上接受连接时,它就会被抛出。请注意,您可以通过用毫秒数调用setSoTimeout()来为accept()调用设置超时,以避免实际长时间的等待。调用setSoTimeout()将使accept()经过指定占用时间后抛出IOException。
处理连接
这里我们实现handleConnection()方法,它将用连接的流来接收输入和写输出:
    public void handleConnection(Socket incomingConnection) {
        try {
            OutputStream outputToSocket = incomingConnection.getOutputStream();
            InputStream inputFromSocket = incomingConnection.getInputStream();
            BufferedReader streamReader = new BufferedReader(new InputStreamReader(inputFromSocket));
            FileReader fileReader = new FileReader(new File(streamReader.readLine()));
            BufferedReader bufferedFileReader = new BufferedReader(fileReader);
            PrintWriter streamWriter = new PrintWriter(incomingConnection.getOutputStream());
            String line = null;
            while((line=bufferedFileReader.readLine())!=null){
                streamWriter.println(line);
            }
            fileReader.close();
            streamWriter.close();
            streamReader.close();
        }
        catch(Exception e) {
            System.out.println("Error handling a client: "+e);
            e.printStackTrace();
        }
    }
跟在客户机中一样,我们用getOutputStream()和getInputStream()来获取与我们刚创建的Socket相关联的流。跟在客户机端一样,我们把InputStream包装进BufferedReader,把OutputStream包装进PrintWriter。在服务器端上,我们需要添加一些代码,用来读取目标文件和把内容逐行发送到客户机。这里是重要的代码:
    FileReader fileReader = new FileReader(new File(streamReader.readLine()));    BufferedReader bufferedFileReader = new BufferedReader(fileReader);    String line = null;    while((line=bufferedFileReader.readLine())!=null) {        streamWriter.println(line);    }
这些代码值得详细解释。让我们一点一点来看:
    FileReader fileReader = new FileReader(new File(streamReader.readLine()));
首先,我们使用Socket 的InputStream的BufferedReader。我们应该获取一条有效的文件路径,所以我们用该路径名构造一个新File。我们创建一个新FileReader来处理读文件的操作。
    BufferedReader bufferedFileReader = new BufferedReader(fileReader);
这里我们把FileReader包装进BufferedReader以使我们能够逐行地读该文件。
接着,我们调用BufferedReader的readLine()。这个调用将造成阻塞直到有字节到来。我们获取一些字节之后就把它们放到本地的line变量中,然后再写出到客户机上。完成读写操作之后,我们就关闭打开的流。
请注意我们在完成从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 类
import java.io.*;
import java.net.*;
public class MultithreadedRemoteFileServer {
    int listenPort;
    public MultithreadedRemoteFileServer(int listenPort) {       
        this.listenPort=listenPort;
    }
    //允许客户机连接到服务器,等待客户机请求   
    public void acceptConnections() {
        try {
            ServerSocket server = new ServerSocket(listenPort, 5);
            Socket incomingConnection = null;
            while(true) {
                incomingConnection = server.accept();
                handleConnection(incomingConnection);
            }
        }       
        catch(BindException e) {
            System.out.println("Unable to bind to port "+listenPort);
        }
        catch(IOException e) {           
            System.out.println("Unable to instantiate a ServerSocket on port: "+listenPort);       
        }
    }
    //与客户机Socket交互以将客户机所请求的文件的内容发送到客户机   
    public void handleConnection(Socket connectionToHandle) {
        new Thread(new ConnectionHandler(connectionToHandle)).start();
    }
    public static void main(String args[]) {
        MultithreadedRemoteFileServer server = new MultithreadedRemoteFileServer(1001);
        server.acceptConnections();
    }
}
这里我们实现改动过acceptConnections()方法,它将创建一个能够处理待发请求的ServerSocket,并告诉ServerSocket接受连接。
新的 server 仍然需要acceptConnections(),所以这些代码实际上是一样的。突出显示的行表示一个重大的不同。对这个多线程版,我们现在可以指定客户机请求的最大数目,这些请求都能在实例化ServerSocket期间处于待发状态。如果我们没有指定客户机请求的最大数目,则我们假设使用缺省值50。
这里是它的工作机制。假设我们指定待发数(backlog 值)是5并且有五台客户机请求连接到我们的服务器。我们的服务器将着手处理第一个连接,但处理该连接需要很长时间。由于我们的待发值是5,所以我们一次可以放五个请求到队列中。我们正在处理一个,所以这意味着还有其它五个正在等待。等待的和正在处理的一共有六个。当我们的服务器仍忙于接受一号连接(记住队列中还有 2?6 号)时,如果有第七个客户机提出连接申请,那么,该第七个客户机将遭到拒绝。我们将在带有连接池服务器示例中说明如何限定能同时连接的客户机数目。
处理连接:
    public void handleConnection(Socket connectionToHandle) {
        new Thread(new ConnectionHandler(connectionToHandle)).start();
    }
我们对RemoteFileServer所做的大改动就体现在这个方法上。我们仍然在服务器接受一个连接之后调用handleConnection(),但现在我们把该Socket传递给ConnectionHandler的一个实例,它是 Runnable的。我们用ConnectionHandler创建一个新 Thread 并启动它。ConnectionHandler的run()方法包Socket读/写和读File的代码,这些代码原来在RemoteFileServer的handleConnection()中。
创建 ConnectionHandler 类
import java.io.*;
import java.net.*;
public class ConnectionHandler implements Runnable {
    protected Socket socketToHandle;
    public ConnectionHandler(Socket socketToHandle) {
        this.socketToHandle=socketToHandle;
    }
    public void run() {
        try {
            PrintWriter streamWriter = new PrintWriter(socketToHandle.getOutputStream());
            BufferedReader streamReader = new BufferedReader(new InputStreamReader(socketToHandle.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(Exception e) {
            System.out.println("Error handling a client: "+e);
        e.printStackTrace();
        }
    }
}
这个助手类相当简单。跟我们到目前为止的其它类一样,我们导入java.net和java.io。该类只有一个实例变量socketToHandle,它保存由该实例处理的Socket。
类的构造器用一个Socket实例作参数并将它赋给socketToHandle。
请注意该类实现了Runnable接口。实现这个接口的类都必须实现run()方法。这里我们实现run()方法,它将攫取我们的连接的流,用它来读写该连接,并在任务完成之后关闭它。ConnectionHandler的run()方法所做的事情就是RemoteFileServer上的handleConnection()所做的事情。首先,我们把InputStream和OutputStream分别包装(用Socket的getOutputStream()和 getInputStream())进BufferedReader和PrintWriter。然后我们用这些代码逐行地读目标文件:
PrintWriter streamWriter = new PrintWriter(socketToHandle.getOutputStream());
            BufferedReader streamReader = new BufferedReader(new InputStreamReader(socketToHandle.getInputStream()));
            String fileToRead = streamReader.readLine();
            BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead));
            String line =null;
            while((line=fileReader.readLine())!=null) {
                streamWriter.println(line);
            }
请记住我们应该从客户机获取一条有效的文件路径,这样用该路径名构造一个新File,把它包装进FileReader以处理读文件的操作,然后把它包装进BufferedReader以让我们逐行地读该文件。我们while循环中调用BufferedReader上的readLine()直到不再有要读的行。请记注,对readLine()的调用将造成阻塞,直到有字节来到为止。我们获取一些字节之后就把它们放到本地的line变量中,然后写出到客户机上。完成读写操作之后,我们关闭打开的流。
总结一下多线程服务器
让我们回顾一下创建和使用“多线程版”的服务器的步骤:
1.    修改 acceptConnections() 以用缺省为 50(或任何您想要的大于 1 的指定数字)实例化 ServerSocket。
2.    修改 ServerSocket 的 handleConnection() 以用 ConnectionHandler 的一个实例生成一个新的 Thread。
3.    借用 RemoteFileServer 的 handleConnection() 方法的代码实现 ConnectionHandler 类。
7 创建带有连接池的Socket服务器
我们现在已经拥有的 MultithreadedServer 每当有客户机申请一个连接时都在一个新Thread中创建一个新ConnectionHandler。这意味着可能有一捆Thread“躺”在我们周围。而且创建Thread的系统开销并不是微不足道的。如果性能成为了问题(也请不要事到临头才意识到它),更高效地处理我们的服务器是件好事。那么,我们如何更高效地管理服务器端呢?我们可以维护一个进入的连接池,一定数量的ConnectionHandler将为它提供服务。这种设计能带来以下好处:
•    它限定了允许同时连接的数目。
•    我们只需启动ConnectionHandler Thread一次。
幸运的是,跟在我们的多线程示例中一样,往代码中添加“池”不需要来一个大改动。事实上,应用程序的客户机端根本就不受影响。在服务器端,我们在服务器启动时创建一定数量的 ConnectionHandler,我们把进入的连接放入“池”中并让ConnectionHandler打理剩下的事情。这种设计中有很多我们不打算讨论的可能存在的技巧。例如,我们可以通过限定允许在“池”中建立的连接的数目来拒绝客户机。
请注意:我们将不会再次讨论acceptConnections()。这个方法跟前面示例中的完全一样。它无限循环地调用ServerSocket上的 accept() 并把连接传递到handleConnection()。
创建 PooledRemoteFileServer 类
import java.io.*;
import java.net.*;
import java.util.*;
public class PooledRemoteFileServer {
    protected int maxConnections;
    protected int listenPort;
    protected ServerSocket serverSocket;
    public PooledRemoteFileServer(int aListenPort, int maxConnections) {
        listenPort= aListenPort;
        this.maxConnections = maxConnections;
    }
    public void acceptConnections() {
        try {
            ServerSocket server = new ServerSocket(listenPort, 5);
            Socket incomingConnection = null;
            while(true) {
                incomingConnection = server.accept();
                handleConnection(incomingConnection);
            }
        }
        catch(BindException e) {
            System.out.println("");
        }
        catch(IOException e) {
            System.out.println(""+listenPort);
        }
    }
    protected void handleConnection(Socket connectionToHandle) {
        PooledConnectionHandler.processRequest(connectionToHandle);
    }
    public void setUpHandlers() {
        for(int i=0; i<maxConnections; i++) {
            PooledConnectionHandler currentHandler = new PooledConnectionHandler();
            new Thread(currentHandler, "Handler " + i).start();
        }
    }
    public static void main(String args[]) {
        PooledRemoteFileServer server = new PooledRemoteFileServer(1001, 3);
        server.setUpHandlers();
        server.acceptConnections();
    }
}
请注意一下您现在应该熟悉了的 import 语句。我们给类以下实例变量以保存:
•    我们的服务器能同时处理的活动客户机连接的最大数目
•    进入的连接的侦听端口(我们没有指定缺省值,但如果您想这样做,并不会受到限制)
•    将接受客户机连接请求的 ServerSocket
类的构造器用的参数是侦听端口和连接的最大数目
我们的类有一个 main() 方法和三个其它方法。稍后我们将探究这些方法的细节。现在只须知道setUpHandlers()创建数目为maxConnections的大量PooledConnectionHandler,而其它两个方法则与我们前面已经看到的相似:acceptConnections()在ServerSocket上侦听传入的客户机连接,而handleConnection则在客户机连接一旦被建立后就实际处理它。
实现 main()
这里我们实现需作改动的main()方法,该方法将创建能够处理给定数目的客户机连接的PooledRemoteFileServer,并告诉它接受连接:
    public static void main(String args[]) {
        PooledRemoteFileServer server = new PooledRemoteFileServer(1001, 3);
        server.setUpHandlers();
        server.acceptConnections();
    }
我们的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.processRequest(connectionToHandle);
    }
我们现在叫 PooledConnectionHandler 处理所有进入的连接(processRequest() 是一个静态方法)。
创建 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 processRequest(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上的processRequest()方法,它将把传入请求添加到池中,并告诉其它正在等待的对象该池已经有一些内容:
    public static void processRequest(Socket requestToHandle) {
        synchronized(pool) {
            pool.add(pool.size(), requestToHandle);
            pool.notifyAll();
        }
    }
synchronized 块是个稍微有些不同的东西。您可以同步任何对象上的一个块,而不只是在本身的某个方法中含有该块的对象。在我们的示例中,processRequest() 方法包含有一个 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()产生阻塞。当我们的processRequest()在连接池上调用notifyAll()时,所有正在等待的PooledConnectionHandler都将得到“池已经可用”的通知。然后各自继续前行调用pool.wait(),并重新检查while(pool.isEmpty())循环条件。除了一个处理程序,其它池对所有处理程序都将是空的,因此,在调用pool.wait()时,除了一个处理程序,其它所有处理程序都将再次产生阻塞。恰巧碰上非空池的处理程序将跳出while(pool.isEmpty())循环并攫取池中的第一个连接:
    connection= (Socket)pool.remove(0);
处理程序一旦有一个连接可以使用,就调用 handleConnection() 处理它。
在我们的示例中,池中可能永远不会有多个连接,只是因为事情很快就被处理掉了。如果池中有一个以上连接,那么其它处理程序将不必等待新的连接被添加到池。当它们检查pool.isEmpty()条件时,将发现其值为假,然后就从池中攫取一个连接并处理它。
还有另一件事需注意。当run()拥有池的互斥锁时,processRequest()如何能够把连接放到池中呢?答案是对池上的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 内),您仍然必须管理稍显杂乱的字节细节,但您只须做一次就可以了。更好的是,您可以在将来的项目中重用这些低级别的助手类。

你可能感兴趣的:(thread,多线程,应用服务器,.net,socket)