| 网站首页 | 文章中心 | 电子书下载 | 矢量图库 | 视频教程 | 素材下载 | 程序代码下载 | JS代码 | 论坛 | 
常用软件类:
|杀毒安全 |联络聊天 |网络软件 |多媒体类 |系统工具 |图形图像 |系统工具 |应用软件 |行业软件
开发设计类:
|动画制作 |图像处理 |3D设计 |操作系统 |站长学院 |网络相关 |WEB设计 |数据库类 |程序开发
Java 套接字(Socket)详解

作者:未知    文章来源:网络    点击数:    更新时间:2007-6-3
 

 

总结一下多线程服务器

让我们回顾一下创建和使用“多线程版”的服务器的步骤:

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.*;
importjava.net.*;
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;inewThread(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<maxConnections;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.*;
importjava.net.*;
importjava.util.*;
publicclassPooledConnectionHandlerimplementsRunnable{protectedSocketconnection;
protectedstaticListpool=newLinkedList();
publicPooledConnectionHandler(){}publicvoidhandleConnection(){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.notifyAll();}}
publicvoidrun(){while(true){synchronized(pool){while(pool.isEmpty()){try{pool.wait();}
catch(InterruptedExceptione){e.printStackTrace();}}connection=(Socket)pool.remove(0);}
handleConnection();}}}

这个助手类与ConnectionHandler非常相似,但它带有处理连接池的手段。该类有两个实例变量:

•connection是当前正在处理的Socket

•名为pool的静态LinkedList保存需被处理的连接

填充连接池

这里我们实现PooledConnectionHandler上的processRequest()方法,它将把传入请求添加到池中,并告诉其它正在等待的对象该池已经有一些内容:

publicstaticvoidprocessRequest(SocketrequestToHandle){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()方法,它将在连接池上等待,并且池中一有连接就处理它:
publicvoidrun(){while(true){synchronized(pool){while(pool.isEmpty()){try{pool.wait();}
catch(InterruptedExceptione){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()方法,该方法将攫取连接的流,使用它们,并在任务完成之后清除它们:

publicvoidhandleConnection()
{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);}}

跟在多线程服务器中不同,我们的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内),您仍然必须管理稍显杂乱的字节细节,但您只须做一次就可以了。更好的是,您可以在将来的项目中重用这些低级别的助手类。

上一页  [1] [2] [3] [4] 


  • 上一篇文章:

  • 下一篇文章: 没有了
  • 相关文章