socket与服务器2.docx
《socket与服务器2.docx》由会员分享,可在线阅读,更多相关《socket与服务器2.docx(9页珍藏版)》请在冰豆网上搜索。
socket与服务器2
6创建多线程Socket服务器
前面的示例教给您基础知识,但并不能令您更深入。
如果您到此就停止了,那么您一次只能处理一台客户机。
原因是handleConnection()是一个阻塞方法。
只有当它完成了对当前连接的处理时,服务器才能接受另一个客户机。
在多数时候,您将需要(也有必要)一个多线程服务器。
创建MultithreadedRemoteFileServer类
importjava.io.*;
import.*;
publicclassMultithreadedRemoteFileServer{
intlistenPort;
publicMultithreadedRemoteFileServer(intlistenPort){
this.listenPort=listenPort;
}
//允许客户机连接到服务器,等待客户机请求
publicvoidacceptConnections(){
try{
ServerSocketserver=newServerSocket(listenPort,5);
SocketincomingConnection=null;
while(true){
incomingConnection=server.accept();
handleConnection(incomingConnection);
}
}
catch(BindExceptione){
System.out.println("Unabletobindtoport"+listenPort);
}
catch(IOExceptione){
System.out.println("UnabletoinstantiateaServerSocketonport:
"+listenPort);
}
}
//与客户机Socket交互以将客户机所请求的文件的内容发送到客户机
publicvoidhandleConnection(SocketconnectionToHandle){
newThread(newConnectionHandler(connectionToHandle)).start();
}
publicstaticvoidmain(Stringargs[]){
MultithreadedRemoteFileServerserver=newMultithreadedRemoteFileServer(1001);
server.acceptConnections();
}
}
这里我们实现改动过acceptConnections()方法,它将创建一个能够处理待发请求的ServerSocket,并告诉ServerSocket接受连接。
新的server仍然需要acceptConnections(),所以这些代码实际上是一样的。
突出显示的行表示一个重大的不同。
对这个多线程版,我们现在可以指定客户机请求的最大数目,这些请求都能在实例化ServerSocket期间处于待发状态。
如果我们没有指定客户机请求的最大数目,则我们假设使用缺省值50。
这里是它的工作机制。
假设我们指定待发数(backlog值)是5并且有五台客户机请求连接到我们的服务器。
我们的服务器将着手处理第一个连接,但处理该连接需要很长时间。
由于我们的待发值是5,所以我们一次可以放五个请求到队列中。
我们正在处理一个,所以这意味着还有其它五个正在等待。
等待的和正在处理的一共有六个。
当我们的服务器仍忙于接受一号连接(记住队列中还有2?
6号)时,如果有第七个客户机提出连接申请,那么,该第七个客户机将遭到拒绝。
我们将在带有连接池服务器示例中说明如何限定能同时连接的客户机数目。
处理连接:
publicvoidhandleConnection(SocketconnectionToHandle){
newThread(newConnectionHandler(connectionToHandle)).start();
}
我们对RemoteFileServer所做的大改动就体现在这个方法上。
我们仍然在服务器接受一个连接之后调用handleConnection(),但现在我们把该Socket传递给ConnectionHandler的一个实例,它是Runnable的。
我们用ConnectionHandler创建一个新Thread并启动它。
ConnectionHandler的run()方法包Socket读/写和读File的代码,这些代码原来在RemoteFileServer的handleConnection()中。
创建ConnectionHandler类
importjava.io.*;
import.*;
publicclassConnectionHandlerimplementsRunnable{
protectedSocketsocketToHandle;
publicConnectionHandler(SocketsocketToHandle){
this.socketToHandle=socketToHandle;
}
publicvoidrun(){
try{
PrintWriterstreamWriter=newPrintWriter(socketToHandle.getOutputStream());
BufferedReaderstreamReader=newBufferedReader(newInputStreamReader(socketToHandle.getInputStream()));
StringfileToRead=streamReader.readLine();
BufferedReaderfileReader=newBufferedReader(newFileReader(fileToRead));
Stringline=null;
while((line=fileReader.readLine())!
=null){
streamWriter.println(line);
}
fileReader.close();
streamWriter.close();
streamReader.close();
}
catch(Exceptione){
System.out.println("Errorhandlingaclient:
"+e);
e.printStackTrace();
}
}
}
这个助手类相当简单。
跟我们到目前为止的其它类一样,我们导入和java.io。
该类只有一个实例变量socketToHandle,它保存由该实例处理的Socket。
类的构造器用一个Socket实例作参数并将它赋给socketToHandle。
请注意该类实现了Runnable接口。
实现这个接口的类都必须实现run()方法。
这里我们实现run()方法,它将攫取我们的连接的流,用它来读写该连接,并在任务完成之后关闭它。
ConnectionHandler的run()方法所做的事情就是RemoteFileServer上的handleConnection()所做的事情。
首先,我们把InputStream和OutputStream分别包装(用Socket的getOutputStream()和getInputStream())进BufferedReader和PrintWriter。
然后我们用这些代码逐行地读目标文件:
PrintWriterstreamWriter=newPrintWriter(socketToHandle.getOutputStream());
BufferedReaderstreamReader=newBufferedReader(newInputStreamReader(socketToHandle.getInputStream()));
StringfileToRead=streamReader.readLine();
BufferedReaderfileReader=newBufferedReader(newFileReader(fileToRead));
Stringline=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将为它提供服务。
这种设计能带来以下好处:
• 它限定了允许同时连接的数目。
• 我们只需启动ConnectionHandlerThread一次。
幸运的是,跟在我们的多线程示例中一样,往代码中添加“池”不需要来一个大改动。
事实上,应用程序的客户机端根本就不受影响。
在服务器端,我们在服务器启动时创建一定数量的ConnectionHandler,我们把进入的连接放入“池”中并让ConnectionHandler打理剩下的事情。
这种设计中有很多我们不打算讨论的可能存在的技巧。
例如,我们可以通过限定允许在“池”中建立的连接的数目来拒绝客户机。
请注意:
我们将不会再次讨论acceptConnections()。
这个方法跟前面示例中的完全一样。
它无限循环地调用ServerSocket上的accept()并把连接传递到handleConnection()。
创建PooledRemoteFileServer类
importjava.io.*;
import.*;
importjava.util.*;
publicclassPooledRemoteFileServer{
protectedintmaxConnections;
protectedintlistenPort;
protectedServerSocketserverSocket;
publicPooledRemoteFileServer(intaListenPort,intmaxConnections){
listenPort=aListenPort;
this.maxConnections=maxConnections;
}
publicvoidacceptConnections(){
try{
ServerSocketserver=newServerSocket(listenPort,5);
SocketincomingConnection=null;
while(true){
incomingConnection=server.accept();
handleConnection(incomingConnection);
}
}
catch(BindExceptione){
System.out.println("");
}
catch(IOExceptione){
System.out.println(""+listenPort);
}
}
protectedvoidhandleConnection(SocketconnectionToHandle){
PooledConnectionHandler.processRequest(connectionToHandle);
}
publicvoidsetUpHandlers(){
for(inti=0;i PooledConnectionHandlercurrentHandler=newPooledConnectionHandler();
newThread(currentHandler,"Handler"+i).start();
}
}
publicstaticvoidmain(Stringargs[]){
PooledRemoteFileServerserver=newPooledRemoteFileServer(1001,3);
server.setUpHandlers();
server.acceptConnections();
}
}
请注意一下您现在应该熟悉了的import语句。
我们给类以下实例变量以保存:
• 我们的服务器能同时处理的活动客户机连接的最大数目
• 进入的连接的侦听端口(我们没有指定缺省值,但如果您想这样做,并不会受到限制)
• 将接受客户机连接请求的ServerSocket
类的构造器用的参数是侦听端口和连接的最大数目
我们的类有一个main()方法和三个其它方法。
稍后我们将探究这些方法的细节。
现在只须知道setUpHandlers()创建数目为maxConnections的大量PooledConnectionHandler,而其它两个方法则与我们前面已经看到的相似:
acceptConnections()在ServerSocket上侦听传入的客户机连接,而handleConnection则在客户机连接一旦被建立后就实际处理它。
实现main()
这里我们实现需作改动的main()方法,该方法将创建能够处理给定数目的客户机连接的PooledRemoteFileServer,并告诉它接受连接:
publicstaticvoidmain(Stringargs[]){
PooledRemoteFileServerserver=newPooledRemoteFileServer(1001,3);
server.setUpHandlers();
server.acceptConnections();
}
我们的main()方法很简单。
我们实例化一个新的PooledRemoteFileServer,它将通过调用setUpHandlers()来建立三个PooledConnectionHandler。
一旦服务器就绪,我们就告诉它acceptConnections()。
建立连接处理程序
publicvoidsetUpHandlers(){
for(inti=0;i PooledConnectionHandlercurrentHandler=newPooledConnectionHandler();
newThread(currentHandler,"Handler"+i).start();
}
}
setUpHandlers()方法创建maxConnections(例如3)个PooledConnectionHandler并在新Thread中激活它们。
用实现了Runnable的对象来创建Thread使我们可以在Thread调用start()并且可以期望在Runnable上调用了run()。
换句话说,我们的PooledConnectionHandler将等着处理进入的连接,每个都在它自己的Thread中进行。
我们在示例中只创建三个Thread,而且一旦服务器运行,这就不能被改变。
处理连接
这里我们实现需作改动的handleConnections()方法,它将委派PooledConnectionHandler处理连接:
protectedvoidhandleConnection(SocketconnectionToHandle){
PooledConnectionHandler.processRequest(connectionToHandle);
}
我们现在叫PooledConnectionHandler处理所有进入的连接(processRequest()是一个静态方法)。
创建PooledRemoteFileServer类
importjava.io.*;
import.*;
importjava.util.*;
publicclassPooledConnectionHandlerimplementsRunnable{
protectedSocketconnection;
protectedstaticListpool=newLinkedList();
publicPooledConnectionHandler(){}
publicvoid handleConnection(){
try{
PrintWriterstreamWriter=newPrintWriter(connection.getOutputStream());
BufferedReaderstreamReader=newBufferedReader(newInputStreamReader(connection.getInputStream()));
StringfileToRead=streamReader.readLine();
BufferedReaderfileReader=newBufferedReader(newFileReader(fileToRead));
Stringline=null;
while((line=fileReader.readLine())!
=null)
streamWriter.println(line);
fileReader.close();
streamWriter.close();
streamReader.close();
}
catch(FileNotFoundExceptione){
System.out.println("");
}
catch(IOExceptione){
System.out.println(""+e);
}
}
publicstaticvoidprocessRequest(SocketrequestToHandle){
synchronized(pool){
pool.add(pool.size(),requestToHandle);
pool.no