用Java Socket开发高并发小型服务器
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
用Java Socket开发高并发小型办事器
Java Socket
套接字(socket)为两台盘算机之间的通信提供了一种机制,在James Gosling注意到Java 语言之前,套接字就早已大名鼎鼎。
该语言只是让您不必了解底层操纵系统的细节就能有效地使用套接字。
1 客户机/办事器模型
在饭馆里,菜单上种种具有异国情调的食品映入你的眼帘,于是你要了一份pizza。
几分钟后,你用力咀嚼浇着融化的乳酪和其他你喜欢的配料的热pizza。
你不知道,也不想知道:侍者从那里弄来了pizza,在制作历程中加进了什么,以及配料是如何得到的。
上例中包罗的实体有:美味的pizza、担当你定餐的侍者、制作pizza的厨房,固然另有你。
你是定pizza的主顾或客户。
制作pizza的历程对付你而言是被封装的。
你的请求在厨房中被处置惩罚,pizza制作完成后,由侍者端给你。
你所看到的就是一个客户机/办事器模型。
客户机向办事器发送一个请求或命令。
办事器处置惩罚客户机的请求。
客户机和办事器之间的通讯是客户机/办事器模型中的一个重要组成部门,通常通过网络进行。
客户机/办事器模型是一个应用步伐开发框架,该框架是为了将数据的表现与其内部的处置惩罚和存储分散开来而设计的。
客户机请求办事,办事器为这些请求办事。
请求通过网络从客户机通报到办事器。
办事器所进行的处置惩罚对客户机而言是隐藏的。
一个办事器可以为多台客户机办事。
多台客户机访问办事器
办事器和客户机不一定是硬件组件。
它们可以是事情啊同一呆板或差别呆板上的步伐。
、
考虑一个航空定票系统中的数据输入步伐:数据----搭客名、航班号、飞行日期、目的地等可以被输入到前端----客户机的应用步伐中。
一旦数据输入之后,客户机将数据发送到后端----办事器端。
办事器处置惩罚数据并在数据库中生存数据。
客户机/办事器模型的重要性在于所有的数据都存放在同一所在。
客户机从差别的地方访问同一数据源,办事器对所有的输入数据应用同样的查验规矩。
万维网为‘为什么要将数据的表现与其存储、处置惩罚分散开来’提供了一个很好的例子。
在Web上,你无需控制最终用户用来访问你数据的平台和软件。
你可以考虑编写出适用与每一种潜在的目标平台的应用步伐。
‘客户机/办事器应用步伐的办事器部门’治理通过多个客户机访问办事器的、多个用户共享的资源。
表明‘客户机/办事器步伐的办事器部门’强大功效的最好例子应该是Web办事器,它通过Internet将HTML页通报给差别的Web用户。
Java编程语言中最根本的特点是在Java中创建的步伐的代码的可移植性。
因为具有其他语言所不具备的代码可移植性,Java允许用户只要编写一次应用步伐,就可以在任何客户机系统上公布它,并可以让客户机系统解释该步伐。
这意味着:你只要写一次代码,就能使其在任何平台上运行。
2 协议
当你同朋友攀谈时,你们遵循一些暗含的规矩(或协议)。
例如:你们俩不能同时开始说话,或连续不中断地说话。
如果你们这样作的话,谁也不能理解对方所说的东西。
当你说话时,你的朋友倾听,反之亦然。
你们以双方都能理解的语言和速度进行对话。
当盘算机之间进行通讯的时候,也需要遵循一定的规矩。
数据以包的形式从一台呆板发送到另一台。
这些规矩治理数据打包、数据传输速度和重新数据将其规复成原始形式。
这些规矩被称为网络协议。
网络协议是通过网络进行通讯的系统所遵循的一系列规矩和老例。
连网软件通常实现有崎岖条理之分的多层协议。
网络协议的例子有:TCP/IP、UDP、Apple Talk 和NetBEUI。
Java提供了一个富厚的、支持网络的类库,这些类使得应用步伐能方便地访问网络资源。
Java提供了两种通讯东西。
它们是:使用用户报文协议(UDP)的报文和使用传输控制协议/因特网协议(TCP/IP)的Sockets(套接字)。
数据报包是一个字节数组从一个步伐(发送步伐)传送到另一个(担当步伐)。
由于数据报遵守UDP,不包管发出的数据包必须到达目的地。
数据报并不是可信赖的。
因此,仅当传送少量数据时才使用,并且发送者和担当者之间的距离隔断不大,如果是网络交通岑岭,或担当步伐正处置惩罚来自其他步伐的多个请求,就有时机出现数据报包的丢失。
Sockets套接字用TCP来进行通讯。
套接字模型同其他模型相比,优越性在于其不受客户
请求来自那边的影响。
只要客户机遵循TCP/IP协议,办事器就会对它的请求提供办事。
这意味着客户机可以是任何类型的盘算机。
客户机不再局限为UNIX、Windows、DOS或Macintosh平台,因此,网上所有遵循TCP/IP协议的盘算机可以通过套接字相互通讯。
3 Sockets套接字
3.1 Sockets表面
在客户机/办事器应用步伐中,办事器提供象处置惩罚数据库查询或修改数据库中的数据之类的办事。
产生在客户机和办事器之间的通讯必须是可靠的,同时数据在客户机上的序次应该和办事器发送出来的序次相同。
什么是套接字?
既然我们已经知道套接字饰演的脚色,那么剩下的问题是:什么是套接字?Bruce Eckel 在他的《Java 编程思想》一书中这样描述套接字:套接字是一种软件抽象,用于表达两台呆板之间的连接“终端”。
对付一个给定的连接,每台呆板上都有一个套接字,您也可以想象它们之间有一条虚拟的“电缆”,“电缆”的每一端都插入到套接字中。
固然,呆板之间的物理硬件和电缆连接都是完全未知的。
抽象的全部目的是使我们无须知道不必知道的细节。
简言之,一台呆板上的套接字与另一台呆板上的套接字攀谈就创建一条通信通道。
步伐员可以用该通道来在两台呆板之间发送数据。
当您发送数据时,TCP/IP 协议栈的每一层都市添加适当的报头信息来包装数据。
这些报头资助协议栈把您的数据送到目的地。
好消息是Java 语言通过"流"为您的代码提供数据,从而隐藏了所有这些细节,这也是为什么它们有时候被叫做流套接字(streaming socket)的原因。
把套接字想成两端电话上的听筒,我和您通过专用通道在我们的电话听筒上发言和聆听。
直到我们决定挂断电话,对话才会结束(除非我们在使用蜂窝电话)。
并且我们各自的电话线路都占线,直到我们挂断电话。
如果想在没有更高级机制如ORB(以及CORBA、RMI、IIOP 等等)开销的情况下进行两台盘算机之间的通信,那么套接字就适合您。
套接字的低级细节相当棘手。
幸运的是,Java 平台给了您一些虽然简朴但却强大的更高级抽象,使您可以容易地创建和使用套接字。
传输控制协议(TCP)提供了一条可靠的、点对点的通讯通道,客户机/办事器应用步伐可以用该通道相互通讯。
要通过TCP进行通讯,客户机和办事器步伐创建连接并绑定套接字。
套接字用于处置惩罚通过网络连接的应用步伐之间的通讯。
客户机和办事器之间更深入的通讯通过套接字完成。
Java被设计成一种连网语言。
它通过将连接功效封装到套接字类里而使得网络编程越发容易。
套接字类即Socket类(它创建一个客户套接字)和ServerSocket类(它创建一个办事器套接字)。
套接字类大抵介绍如下:
l Socket是基类,它支持TCP协议。
TCP是一个可靠的流网络连接协议。
Socket类提供了流输入/输出的要领,使得从套接字中读出数据和往套接字中写数据都很容易。
该类对付编写因特网上的通讯步伐而言是必不可少的。
l ServerSocket是一个因特网办事步伐用来监听客户请求的类。
ServerSocket实际上并不执行办事;而是创建了一个Socket东西来代表客户机。
通讯由创建的东西来完成。
3.2 IP地点和端口
因特网办事器可以被认为是一组套接字类,它们提供了一般称为办事的附加功效。
办事的例子有:电子邮件、远程登录的T elnet、和通过网络传输文件的文件传输协议(FTP)。
每种办事都与一个端口相联系。
端口是一个数值地点,通过它来处置惩罚办事请求(就象请求Web页一样)。
TCP协议需要两个数据项:IP地点和端口号。
因此,当键入时,你是如何进入金诺的主页呢?
因特网协议(IP)提供每一项网络设备。
这些设备都带有一个称为IP地点的逻辑地点。
由因特网协议提供的IP地点具有特定的形式。
每个IP地点都是32位的数值,表现4个范畴在0到255之间的8位数值金诺已经注册了它的名字,分派给的IP地点为192.168.0.110。
注意:域名办事或DNS办事是将翻译成192.168.0.110的办事。
这使你可以键入。
如果没有指明端口号,则使用办事文件中办事器的端口。
每种协议有一个缺省的端口号,在端口号未指明时使用该缺省端口号。
端口号应用
让我们再来看一下URL:
URL的第一部门(http)意味着你正在使用超文本传输协议(HTTP),该协议处置惩罚Web 文档。
如果没有指明文件,大多数的Web办事器会取一个叫index.html文件。
因此,IP地点和端口既可以通过明确指出URL各部门来决定,也可以由缺省值决定。
4 创建Socket客户
我们将在本部门讨论的示例将分析在Java 代码中如何使用Socket 和ServerSocket。
客户机用Socket 连接到办事器。
办事器用ServerSocket 在端口1001 侦听。
客户机请求办事器C: 驱动器上的文件内容。
创建RemoteFileClient 类
import java.io.*;
import .*;
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 fileNameT oGet) {
StringBuffer fileLines = new StringBuffer();
try {
socketWriter.println(fileNameT oGet);
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.io。
包为您提供您需要的套接字东西。
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 fileNameT oGet) {
StringBuffer fileLines = new StringBuffer();
try {
socketWriter.println(fileNameT oGet);
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 .*;
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.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 .*;
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 .*;
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(fileT oRead));
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.io。
该类只有一个实例变量socketToHandle,它生存由该实例处置惩罚的Socket。
类的结构器用一个Socket实例作参数并将它赋给socketToHandle。
请注意该类实现了Runnable接口。
实现这个接口的类都必须实现run()要领。
这里我们实现run()要领,它将攫取我们的连接的流,用它来读写该连接,并在任务完成之后封闭它。
ConnectionHandler的run()要领所做的事情就是RemoteFileServer上的handleConnection()所做的事情。
首先,我们把InputStream和OutputStream分别包装(用
Socket的getOutputStream()和getInputStream())进BufferedReader和PrintWriter。
然后我们用这些代码逐行地读目标文件:
PrintWriter streamWriter = new PrintWriter(socketT oHandle.getOutputStream());
BufferedReader streamReader = new BufferedReader(new InputStreamReader(socketToHandle.getInputStream()));
String fileToRead = streamReader.readLine();
BufferedReader fileReader = new BufferedReader(new
FileReader(fileT oRead));
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 .*;
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 {。