本文共 41515 字,大约阅读时间需要 138 分钟。
选择下列任意链接开始学习。
我应该学习本教程吗? | 第 1 页(共2 页) |
套接字(socket)为两台计算机之间的通信提供了一种机制,在 James Gosling 注意到 Java 语言之前,套接字就早已赫赫有名。该语言只是让您不必了解底层操作系统的细节就能有效地使用套接字。多数着重讨论 Java 编码的书或者未涵盖这个主题,或者给读者留下很大的想象空间。本教程将告诉您开始在代码中有效地使用套接字时,您真正需要知道哪些知识。我们将专门讨论以下问题:
如果您能够描述如何使用 java.net
包中的类,那么本教程对您来说也许基础了点,虽然用它来提高一下还是不错的。如果您在 PC 和其它平台上使用套接字已经几年,那么最初的部分也许会使您觉得烦。但如果您不熟悉套接字,而且只是想知道什么是套接字以及如何在 Java 代码中有效地使用它们,那么本教程就是一个开始的好地方。
有关于本教程内容方面的问题,请与作者 Roy Miller(在 )或 Adam Williams(在)联系。 Roy Miller 和 Adam Williams 都是 RoleModel Software, Inc. 的软件开发者。他们在 Dallas Semiconductor 的 TINI Java 平台上共同开发了一个基于套接字的应用程序原型。Roy 和 Adam 目前正在使用套接字把 COBOL 金融交易系统移植到该 Java 平台上。 加盟 RoleModel 之前,Roy 在 Andersen Consulting(现为 Accenture)做了六年的软件开发和项目管理。他与别人合著的 Extreme Programming Applied: Playing to Win(Addison-Wesley XP 系列)将于 2001 年 10 月出版。 |
多数程序员,不管他们是否使用 Java 语言进行编码,都不想很多知道关于不同计算机上的应用程序彼此间如何通信的低级细节。程序员们希望处理更容易理解的更高级抽象。Java 程序员希望能用他们熟悉的 Java 构造,通过直观接口与对象交互。 套接字在两个领域中都存在 — 我们宁愿避开的低级细节和我们更愿处理的抽象层。本教程讨论的低级细节将只限于理解抽象应用程序所必须的部分。 |
计算机以一种非常简单的方式进行相互间的操作和通信。计算机芯片是以 1 和 0 的形式存储并传输数据的开—闭转换器的集合。当计算机想共享数据时,它们所需做的全部就是以一致的速度、顺序、定时等等来回传输几百万比特和字节的数据流。每次想在两个应用程序之间进行信息通信时,您怎么会愿意担心那些细节呢? 为免除这些担心,我们需要每次都以相同方式完成该项工作的一组包协议。这将允许我们处理应用程序级的工作,而不必担心低级网络细节。这些成包协议称为协议栈(stack)。TCP/IP 是当今最常见的协议栈。多数协议栈(包括 TCP/IP)都大致对应于国际标准化组织(International Standards Organization,ISO)的开放系统互连参考模型(Open Systems Interconnect Reference Model,OSIRM)。OSIRM 认为在一个可靠的计算机组网中有七个逻辑层(见图)。各个地方的公司都对这个模型某些层的实现做了一些贡献,从生成电子信号(光脉冲、射频等等)到提供数据给应用程序。TCP/IP 映射到 OSI 模型中的两层的情形如图所示。 我们不想涉及层的太多细节,但您应该知道套接字位于什么地方。 |
套接字大致驻留在 OSI 模型的会话层(见图)。会话层夹在其上面向应用的层和其下的实时数据通信层之间。会话层为两台计算机之间的数据流提供管理和控制服务。作为该层的一部分,套接字提供一个隐藏从导线上获取比特和字节的复杂性的抽象。换句话说,套接字允许我们让应用程序表明它想发送一些字节即可传输数据。套接字隐藏了完成该项工作的具体细节。 当您打电话时,您的声音传到传感器,传感器把它转换成可以传输的电数据。电话机是人与电信网络的接口。您无须知道声音如何传输的细节,只要知道想打电话给谁就行了。同样地,套接字扮演隐藏在未知通道上传输 1 和 0 的复杂性的高级接口的角色。 |
使用套接字的代码工作于表示层。表示层提供应用层能够使用的信息的公共表示。假设您打算把应用程序连接到只能识别 EBCDIC 的旧的银行系统。应用程序的域对象以 ASCII 格式存储信息。在这种情况下,您得负责在表示层上编写把数据从 EBCDIC 转换成 ASCII 的代码,然后(比方说)给应用层提供域对象。应用层然后就可以用域对象来做它想做的任何事情。 您编写的套接字处理代码只存在于表示层中。您的应用层无须知道套接字如何工作的任何事情。 |
既然我们已经知道套接字扮演的角色,那么剩下的问题是:什么是套接字?Bruce Eckel 在他的《Java 编程思想》一书中这样描述套接字: 套接字是一种软件抽象,用于表达两台机器之间的连接“终端”。对于一个给定的连接,每台机器上都有一个套接字,您也可以想象它们之间有一条虚拟的“电缆”,“电缆”的每一端都插入到套接字中。当然,机器之间的物理硬件和电缆连接都是完全未知的。抽象的全部目的是使我们无须知道不必知道的细节。 简言之,一台机器上的套接字与另一台机器上的套接字交谈就创建一条通信通道。程序员可以用该通道来在两台机器之间发送数据。当您发送数据时,TCP/IP 协议栈的每一层都会添加适当的报头信息来包装数据。这些报头帮助协议栈把您的数据送到目的地。好消息是 Java 语言通过"流"为您的代码提供数据,从而隐藏了所有这些细节,这也是为什么它们有时候被叫做流套接字(streaming socket)的原因。 把套接字想成两端电话上的听筒 — 我和您通过专用通道在我们的电话听筒上讲话和聆听。直到我们决定挂断电话,对话才会结束(除非我们在使用蜂窝电话)。而且我们各自的电话线路都占线,直到我们挂断电话。 如果想在没有更高级机制如 ORB(以及 CORBA、RMI、IIOP 等等)开销的情况下进行两台计算机之间的通信,那么套接字就适合您。套接字的低级细节相当棘手。幸运的是,Java 平台给了您一些虽然简单但却强大的更高级抽象,使您可以容易地创建和使用套接字。 |
一般而言,Java 语言中的套接字有以下两种形式:
TCP 和 UDP 扮演相同角色,但做法不同。两者都接收传输协议数据包并将其内容向前传送到表示层。TCP 把消息分解成数据包(数据报,datagrams)并在接收端以正确的顺序把它们重新装配起来。TCP 还处理对遗失数据包的重传请求。有了 TCP,位于上层的层要担心的事情就少多了。UDP 不提供装配和重传请求这些功能。它只是向前传送信息包。位于上层的层必须确保消息是完整的并且是以正确的顺序装配的。 一般而言,UDP 强加给您的应用程序的性能开销更小,但只在应用程序不会突然交换大量数据并且不必装配大量数据报以完成一条消息的时候。否则,TCP 才是最简单或许也是最高效的选择。 因为多数读者都喜欢 TCP 胜过 UDP,所以我们将把讨论限制在 Java 语言中面向 TCP 的类。 |
Java 平台在
|
连接到 URL 包括几个步骤:
接着,我们将看一些演示如何用 |
我们将从 import java.io.*;import java.net.*;public class URLClient { protected URLConnection connection; public static void main(String[] args) { } public String getDocumentAt(String urlString) { }} 要做的第一件事是导入 我们给我们的类一个实例变量以保存一个 我们的类有一个 |
public static void main(String[] args) { URLClient client = new URLClient(); String yahoo = client.getDocumentAt("http://www.yahoo.com"); System.out.println(yahoo);} 我们的 |
public String getDocumentAt(String urlString) { StringBuffer document = new StringBuffer(); try { URL url = new URL(urlString); URLConnection conn = url.openConnection(); BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line = null; while ((line = reader.readLine()) != null) document.append(line + "\n"); reader.close(); } catch (MalformedURLException e) { System.out.println("Unable to connect to URL: " + urlString); } catch (IOException e) { System.out.println("IOException when connecting to URL: " + urlString); } return document.toString();}
URLConnection conn = url.openConnection(); 一旦有了一个 BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); 有了 String line = null;while ((line = reader.readLine()) != null) document.append(line + "\n"); 对 我们在读完行之后关闭 reader.close(); 如果提供给 |
实际上, 在继续往前讲之前,让我们回顾一下创建和使用
您可在 URLClient 的代码清单 找到 |
import java.io.*;import java.net.*;public class URLClient { protected HttpURLConnection connection; public String getDocumentAt(String urlString) { StringBuffer document = new StringBuffer(); try { URL url = new URL(urlString); URLConnection conn = url.openConnection(); BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line = null; while ((line = reader.readLine()) != null) document.append(line + "\n"); reader.close(); } catch (MalformedURLException e) { System.out.println("Unable to connect to URL: " + urlString); } catch (IOException e) { System.out.println("IOException when connecting to URL: " + urlString); } return document.toString(); } public static void main(String[] args) { URLClient client = new URLClient(); String yahoo = client.getDocumentAt("http://www.yahoo.com"); System.out.println(yahoo); }} |
我们将在本部分讨论的示例将阐明在 Java 代码中如何使用 为清楚起见,我们把示例分解成客户机端和服务器端。最后我们将把它们组合起来以使您能看到整体模样。 我们在使用 JDK 1.2 的 IBM VisualAge for Java 3.5 上开发这些代码。要自己创建这个示例,您应有完好的 JDK 1.1.7 或更高版本。客户机和服务器将只在一台机器上运行,所以您不必担心是否有一个可用的网络。 |
这里是 import java.io.*;import java.net.*;public class RemoteFileClient { protected String hostIp; protected int hostPort; protected BufferedReader socketReader; protected PrintWriter socketWriter; public RemoteFileClient(String aHostIp, int aHostPort) { hostIp = aHostIp; hostPort = aHostPort; } public static void main(String[] args) { } public void setUpConnection() { } public String getFile(String fileNameToGet) { } public void tearDownConnection() { }} 首先我们导入 我们给我们的类实例变量以支持对套接字流的读写和存储我们将连接到的远程主机的详细信息。 我们类的构造器有两个参数:远程主机的 IP 地址和端口号各一个,而且构造器将它们赋给实例变量。 我们的类有一个 |
这里我们实现 public static void main(String[] args) { RemoteFileClient remoteFileClient = new RemoteFileClient("127.0.0.1", 3000); remoteFileClient.setUpConnection(); String fileContents = remoteFileClient.getFile("C:\\WINNT\\Temp\\RemoteFile.txt"); remoteFileClient.tearDownConnection(); System.out.println(fileContents);}
|
这里我们实现 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("Error setting up socket connection: unknown host at " + hostIp + ":" + hostPort); } catch (IOException e) { System.out.println("Error setting up socket connection: " + e); }}
Socket client = new Socket(hostIp, hostPort); 我们把 socketReader = new BufferedReader(new InputStreamReader(client.getInputStream()));socketWriter = new PrintWriter(client.getOutputStream()); 请记住我们的客户机和服务器只是来回传送字节。客户机和服务器都必须知道另一方即将发送的是什么以使它们能够作出适当的响应。在这个案例中,服务器知道我们将发送一条有效的文件路径。 当您实例化一个 |
这里我们实现 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();} 对 StringBuffer fileLines = new StringBuffer(); 在 socketWriter.println(fileNameToGet);socketWriter.flush(); 请注意这里我们是 一旦我们已经写到 String line = null;while ((line = socketReader.readLine()) != null) fileLines.append(line + "\n"); |
这里我们实现 public void tearDownConnection() { try { socketWriter.close(); socketReader.close(); } catch (IOException e) { System.out.println("Error tearing down socket connection: " + e); }}
|
我们的类研究完了。在我们继续往前讨论服务器端的情况之前,让我们回顾一下创建和使用
您可在 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 aHostIp, int aHostPort) { hostIp = aHostIp; hostPort = aHostPort; } 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 static void main(String[] args) { RemoteFileClient remoteFileClient = new RemoteFileClient("127.0.0.1", 3000); remoteFileClient.setUpConnection(); String fileContents = remoteFileClient.getFile("C:\\WINNT\\Temp\\RemoteFile.txt"); remoteFileClient.tearDownConnection(); System.out.println(fileContents); } 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("Error setting up socket connection: unknown host at " + hostIp + ":" + hostPort); } catch (IOException e) { System.out.println("Error 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); } }} |
这里是 import java.io.*;import java.net.*;public class RemoteFileServer { protected int listenPort = 3000; public static void main(String[] args) { } public void acceptConnections() { } public void handleConnection(Socket incomingConnection) { }} 跟客户机中一样,我们首先导入 我们的类有一个 |
这里我们实现 public static void main(String[] args) { RemoteFileServer server = new RemoteFileServer(); server.acceptConnections();} 服务器端的 |
这里我们实现 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); }}
无论何时如果您创建了一个无法绑定到指定端口(可能是因为别的什么控制了该端口)的 |
这里我们实现 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); }} 跟在客户机中一样,我们用 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())); 首先,我们使用 BufferedReader bufferedFileReader = new BufferedReader(fileReader); 这里我们把 接着,我们调用 请注意我们在完成从 |
在我们接着讨论另一个更实际的示例之前,让我们回顾一下创建和使用
您可在 RemoteFileServer 的代码清单 找到 |
import java.io.*;import java.net.*;public class RemoteFileServer { int listenPort; public RemoteFileServer(int aListenPort) { listenPort = aListenPort; } 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); } } 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); } } public static void main(String[] args) { RemoteFileServer server = new RemoteFileServer(3000); server.acceptConnections(); }} |
前面的示例教给您基础知识,但并不能令您更深入。如果您到此就停止了,那么您一次只能处理一台客户机。原因是 要开始同时处理多台客户机,并不需要对 |
这里我们实现改动过的 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); }} 新的 server 仍然需要 这里是它的工作机制。假设我们指定待发数(backlog 值)是 5 并且有五台客户机请求连接到我们的服务器。我们的服务器将着手处理第一个连接,但处理该连接需要很长时间。由于我们的待发值是 5,所以我们一次可以放五个请求到队列中。我们正在处理一个,所以这意味着还有其它五个正在等待。等待的和正在处理的一共有六个。当我们的服务器仍忙于接受一号连接(记住队列中还有 2—6 号)时,如果有第七个客户机提出连接申请,那么,该第七个客户机将遭到拒绝。我们将在带有连接池服务器示例中说明如何限定能同时连接的客户机数目。 |
这里我们将讨论 public void handleConnection(Socket connectionToHandle) { new Thread(new ConnectionHandler(connectionToHandle)).start();} 我们对 |
这里是 import java.io.*;import java.net.*;public class ConnectionHandler implements Runnable{ Socket socketToHandle; public ConnectionHandler(Socket aSocketToHandle) { socketToHandle = aSocketToHandle; } public void run() { }} 这个助手类相当简单。跟我们到目前为止的其它类一样,我们导入 类的构造器用一个 Socket 实例作参数并将它赋给 请注意该类实现了 |
这里我们实现 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); } }
FileReader fileReader = new FileReader(new File(streamReader.readLine())); BufferedReader bufferedFileReader = new BufferedReader(fileReader); String line = null; while ((line = bufferedFileReader.readLine()) != null) { streamWriter.println(line); } 请记住我们应该从客户机获取一条有效的文件路径,这样用该路径名构造一个新 |
我们的多线程服务器研究完了。在我们接着讨论带有连接池示例之前,让我们回顾一下创建和使用“多线程版”的服务器的步骤:
您可在 MultithreadedRemoteFileServer 的代码清单 上找到 |
import java.io.*;import java.net.*;public class MultithreadedRemoteFileServer { protected int listenPort; public MultithreadedRemoteFileServer(int aListenPort) { listenPort = aListenPort; } 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); } } public void handleConnection(Socket connectionToHandle) { new Thread(new ConnectionHandler(connectionToHandle)).start(); } public static void main(String[] args) { MultithreadedRemoteFileServer server = new MultithreadedRemoteFileServer(3000); server.acceptConnections(); }} |
import java.io.*;import java.net.*;public class ConnectionHandler implements Runnable { protected Socket socketToHandle; public ConnectionHandler(Socket aSocketToHandle) { socketToHandle = aSocketToHandle; } 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); } }} |
我们现在已经拥有的
幸运的是,跟在我们的多线程示例中一样,往代码中添加“池”不需要来一个大改动。事实上,应用程序的客户机端根本就不受影响。在服务器端,我们在服务器启动时创建一定数量的 请注意:我们将不会再次讨论 |
这里是 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 static void main(String[] args) { } public void setUpHandlers() { } public void acceptConnections() { } protected void handleConnection(Socket incomingConnection) { }} 请注意一下您现在应该熟悉了的
类的构造器用的参数是侦听端口和连接的最大数目 我们的类有一个 |
|
这里我们实现需作改动的 main()
方法,该方法将创建能够处理给定数目的客户机连接的 PooledRemoteFileServer
,并告诉它接受连接:
public static void main(String[] args) { PooledRemoteFileServer server = new PooledRemoteFileServer(3000, 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(); }}
|
这里我们实现需作改动的 protected void handleConnection(Socket connectionToHandle) {PooledConnectionHandler.processRequest(connectionToHandle);} 我们现在叫 这里是 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() { } public static void processRequest(Socket requestToHandle) { } public void run() { }} 这个助手类与
|
这里我们实现 public static void processRequest(Socket requestToHandle) { synchronized (pool) { pool.add(pool.size(), requestToHandle); pool.notifyAll(); }} 理解这个方法要求有一点关于 Java 的关键字 先来看一些定义:
因此,当对象 A 想使用对象 B 的
既然我们已经保证了我们是唯一“涉水”池中的人,我们就可以把传入的 pool.notifyAll();
|
这里我们实现 public void run() { while (true) { synchronized (pool) { while (pool.isEmpty()) { try { pool.wait(); } catch (InterruptedException e) { return; } } connection = (Socket) pool.remove(0); } handleConnection(); }} 回想一下在前一屏讲过的:一个 connection = (Socket) pool.remove(0); 处理程序一旦有一个连接可以使用,就调用 在我们的示例中,池中可能永远不会有多个连接,只是因为事情很快就被处理掉了。如果池中有一个以上连接,那么其它处理程序将不必等待新的连接被添加到池。当它们检查 还有另一件事需注意。当 |
这里我们实现需做改动的 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("Could not find requested file on the server."); } catch (IOException e) { System.out.println("Error handling a client: " + e); }} 跟在多线程服务器中不同,我们的 |
我们的带有连接池的服务器研究完了。让我们回顾一下创建和使用“池版”服务器的步骤:
您可在 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("Unable to bind to port " + listenPort); } catch (IOException e) { System.out.println("Unable to instantiate a ServerSocket on port: " + listenPort); } } protected void handleConnection(Socket connectionToHandle) { PooledConnectionHandler.processRequest(connectionToHandle); } public static void main(String[] args) { PooledRemoteFileServer server = new PooledRemoteFileServer(3000, 3); server.setUpHandlers(); server.acceptConnections(); } public void setUpHandlers() { for (int i = 0; i < maxConnections; i++) { PooledConnectionHandler currentHandler = new PooledConnectionHandler(); new Thread(currentHandler, "Handler " + i).start(); } }} |
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("Could not find requested file on the server."); } catch (IOException e) { System.out.println("Error handling a client: " + 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) { return; } } connection = (Socket) pool.remove(0); } handleConnection(); } }} |
我们到目前为止讨论过的示例已经涵盖了 Java 编程的套接字机制,但在“现实”的一些例子中如何使用它们呢?即便用了多线程和带有连接池,如此简单地使用套接字,在多数应用程序中仍然是不合适的。相反地,在构成您的问题域的模型的其它类中使用套接字可能是明智的。 最近我们在把一个应用程序从大型机/SNA 环境移植到 TCP/IP 环境时就是这样做的。该应用程序的工作是简化零售渠道(例如硬件商店)和金融机构之间的通信。我们的应用程序是中间人。同样地,它必须与一端的零售渠道和另一端的金融渠道通信。我们必须处理客户机通过套接字与服务器进行的交谈,我们还必须把域对象转换成套接字就绪的形式以进行传输。 我们不能在本教程中涵盖这个应用程序的所有细节,但我们将带您浏览一些高层概念。您可以据此对您自己的问题域做些推断。 |
在客户机端,我们系统中的主角是 我们创建了一个 上述讨论中唯一遗漏的一个是 例如,或许您的服务器需要知道发送中的消息的字节数。 |
服务器端的情形差不多: 我们把
|
一旦我们正确布置了这些 |
下图显示我们的应用程序发送消息的 UML 交互作用图: 为简单起见,我们未显示 |
下图显示我们的应用程序接收消息的 UML 交互作用图: 请注意我们的应用程序在一个 |
Java 语言简化了套接字在应用程序中的使用。它的基础实际上是 |
|
转载地址:http://rltci.baihongyu.com/