专注于互联网--专注于架构

最新标签
网站地图
文章索引
Rss订阅

首页 »Java教程 » javasocket服务器:用Java Socket开发小型服务器 »正文

javasocket服务器:用Java Socket开发小型服务器

来源: 发布时间:星期四, 2009年2月12日 浏览:84次 评论:0


套接字()为两台计算机的间通信提供了种机制在James Gosling注意到Java 语言的前套接字就早已赫赫有名该语言只是让您不必了解底层操作系统细节就能有效地使用套接字

1 客户机/服务器模型
在饭店里菜单上各种具有异国情调食品映入你眼帘于是你要了份pizza几分钟后你用力咀嚼浇着融化乳酪和其他你喜欢配料热pizza你不知道也不想知道:侍者从那里弄来了pizza在制作过程中加进了什么以及配料是如何获得
上例中包含实体有:美味pizza、接受你定餐侍者、制作pizza厨房当然还有你你是定pizza顾客或客户制作pizza过程对于你而言是被封装请求在厨房中被处理pizza制作完成后由侍者端给你
你所看到就是个客户机/服务器模型客户机向服务器发送个请求或命令服务器处理客户机请求客户机和服务器的间通讯是客户机/服务器模型中个重要组成部分通常通过网络进行
客户机/服务器模型是个应用开发框架该框架是为了将数据表示和其内部处理和存储分离开来而设计客户机请求服务服务器为这些请求服务请求通过网络从客户机传递到服务器服务器所进行处理对客户机而言是隐藏个服务器可以为多台客户机服务

多台客户机访问服务器
服务器和客户机不定是硬件组件它们可以是工作啊同机器或区别机器上
考虑个航空定票系统中数据输入:数据----乘客名、航班号、飞行日期、目地等可以被输入到前端----客户机应用旦数据输入的后客户机将数据发送到后端----服务器端服务器处理数据并在数据库中保存数据客户机/服务器模型重要性在于所有数据都存放在同地点客户机从区别地方访问同数据源服务器对所有输入数据应用同样检验规则
万维网为‘为什么要将数据表示和其存储、处理分离开来’提供了个很好例子在Web上你无需控制最终用户用来访问你数据平台和软件Software你可以考虑编写出适用和每种潜在目标平台应用
‘客户机/服务器应用服务器部分’管理通过多个客户机访问服务器、多个用户共享资源表明‘客户机/服务器服务器部分’强大功能最好例子应该是Web服务器它通过Internet将HTML页传递给区别Web用户
Java 编程语言中最基本特点是在Java中创建代码可移植性具有其他语言所不具备代码可移植性Java允许用户只要编写次应用就可以在任何客户机系统上发布它并可以让客户机系统解释该这意味着:你只要写次代码就能使其在任何平台上运行

2 协议
当你同朋友交谈时你们遵循些暗含规则(或协议)例如:你们俩不能同时开始说话或连续不间断地说话如果你们这样作谁也不能理解对方所说东西当你说话时朋友倾听反的亦然你们以双方都能理解语言和速度进行对话
当计算机的间进行通讯时候也需要遵循规则数据以包形式从台机器发送到另这些规则管理数据打包、数据传输速度和重新 数据将其恢复成原始形式这些规则被称为网络协议网络协议是通过网络进行通讯系统所遵循系列规则和惯例连网软件Software通常实现有高低层次的分多层协议网络协议例子有:TCP/IP、UDP、Apple Talk和NetBEUI
Java提供了个丰富、支持网络类库这些类使得应用能方便地访问网络资源Java提供了两种通讯工具它们是:使用用户报文协议(UDP)报文和使用传输控制协议/因特网协议(TCP/IP)Sockets(套接字)
数据报包是个字节(发送)传送到另个(接受)由于数据报遵守UDP不保证发出数据包必须到达目数据报并不是可信赖因此仅当传送少量数据时才使用而且发送者和接受者的间距离间隔不大假如是网络交通高峰或接受正处理来自其他多个请求就有机会出现数据报包丢失
Sockets套接字用TCP来进行通讯套接字模型同其他模型相比优越性在于其不受客户请求来自何处影响只要客户机遵循TCP/IP协议服务器就会对它请求提供服务这意味着客户机可以是任何类型计算机客户机不再局限为UNIX、Windows、DOS或 Macosh平台因此网上所有遵循TCP/IP协议计算机可以通过套接字互相通讯

3 Sockets套接字
3.1 Sockets概况
在客户机/服务器应用服务器提供象处理数据库查询或修改数据库中数据的类服务发生在客户机和服务器的间通讯必须是可靠同时数据在客户机上次序应该和服务器发送出来次序相同
什么是套接字?
既然我们已经知道套接字扮演角色那么剩下问题是:什么是套接字?Bruce Eckel 在他Java 编程思想书中这样描述套接字:套接字是种软件Software抽象用于表达两台机器的间连接“终端”对于个给定连接每台机器上都有个套接字您也可以想象它们的间有条虚拟“电缆”“电缆”端都插入到套接字中当然机器的间物理硬件和电缆连接都是完全未知抽象全部目是使我们无须知道不必知道细节
简言的台机器上套接字和另台机器上套接字交谈就创建条通信通道员可以用该通道来在两台机器的间发送数据当您发送数据时TCP/IP 协议栈层都会添加适当报头信息来包装数据这些报头帮助协议栈把您数据送到目好消息是 Java 语言通过"流"为您代码提供数据从而隐藏了所有这些细节这也是为什么它们有时候被叫做流套接字(streaming )原因
把套接字想成两端电话上听筒我和您通过专用通道在我们电话听筒上讲话和聆听直到我们决定挂断电话对话才会结束(除非我们在使用蜂窝电话)而且我们各自电话线路都占线直到我们挂断电话
如果想在没有更高级机制如 ORB(以及 CORBA、RMI、IIOP 等等)开销情况下进行两台计算机的间通信那么套接字就适合您套接字低级细节相当棘手幸运Java 平台给了您些虽然简单但却强大更高级抽象使您可以容易地创建和使用套接字


传输控制协议(TCP)提供了条可靠、点对点通讯通道客户机/服务器应用可以用该通道互相通讯要通过TCP进行通讯客户机和服务器建立连接并绑定套接字套接字用于处理通过网络连接应用的间通讯客户机和服务器的间更深入通讯通过套接字完成
Java被设计成种连网语言它通过将连接功能封装到套接字类里而使得网络编程更加容易套接字类即Socket类(它创建个客户套接字)和ServerSocket类(它创建个服务器套接字)套接字类大致介绍如下:
l Socket是基类它支持TCP协议TCP是个可靠流网络连接协议Socket类提供了流输入/输出思路方法使得从套接字中读出数据和往套接字中写数据都很容易该类对于编写因特网上通讯而言是必不可少
l ServerSocket是个因特网服务用来监听客户请求ServerSocket实际上并不执行服务;而是创建了个Socket对象来代表客户机通讯由创建对象来完成

3.2 IP地址和端口
因特网服务器可以被认为是组套接字类它们提供了般称为服务附加功能服务例子有:电子邮件、远程登录Telnet、和通过网络传输文件文件传输协议(FTP)每种服务都和个端口相联系端口是个数值地址通过它来处理服务请求(就象请求Web页样)
TCP协议需要两个数据项:IP地址和端口号因此当键入http://www.jinnuo.com时你是如何进入金诺主页呢?
因特网协议(IP)提供每项网络设备这些设备都带有个称为IP地址逻辑地址由因特网协议提供IP地址具有特定形式每个IP地址都是32位数值表示4个范围在0到255的间8位数值金诺已经注册了它名字分配给http://www.jinnuo.comIP地址为192.168.0.110
注意:域名服务或DNS服务是将http://www.jinnuo.com翻译成192.168.0.110服务这使你可以键入http://www.jinnuo.com而不必记住IP地址想象如何可能记住所有需要访问站点IP地址!有趣个网络名可以映射到许多IP地址对于经常访问站点可能需要这功能这些站点容纳大量信息并需要多个IP地址来提供业务服务例如:192.168.0.110实际内部名称为http://www.jinnuo.comDNS可以将分配给jinnuo Ltd.系列IP地址翻译成http://www.jinnuo.com
如果没有指明端口号则使用服务文件中服务器端口每种协议有个缺省端口号在端口号未指明时使用该缺省端口号
端口号 应用
21 FTP.传输文件
23 Telnet.提供远程登录
25 SMTP.传递邮件信息
67 BOOTP.在启动时提供配置情况
80 HTTP.传输Web页
109 POP.使用户能访问远程系统中邮箱
让我们再来看下URL:http://www.jinnuo.com
URL部分(http)意味着你正在使用超文本传输协议(HTTP)该协议处理Web文档如果没有指明文件大多数Web服务器会取个叫index.html文件因此IP地址和端口既可以通过明确指出URL各部分来决定也可以由缺省值决定
4 创建Socket客户
我们将在本部分讨论举例将阐明在 Java 代码中如何使用 Socket 和 ServerSocket客户机用 Socket 连接到服务器服务器用 ServerSocket 在端口 1001 侦听客户机请求服务器 C: 驱动器上文件内容
创建 RemoteFileClient 类

import java.io.*;
import java.net.*;
public RemoteFileClient {
protected BufferedReader Reader;
protected PrWriter Writer;
protected String hostIp;
protected hostPort;
//构造思路方法
public RemoteFileClient(String hostIp, hostPort) {
this.hostIp = hostIp;
this.hostPort=hostPort;
}
//向服务器请求文件内容
public String getFile(String fileNameToGet) {
StringBuffer fileLines = StringBuffer;
try {
Writer.prln(fileNameToGet);
Writer.flush;
String line = null;
while((line=Reader.readLine)!=null)
fileLines.append(line+"\n");
}
catch(IOException e) {
.out.prln("Error reading from file: "+fileNameToGet);
}
fileLines.toString;
}
//连接到远程服务器
public void UpConnection {
try {
Socket client = Socket(hostIp,hostPort);
Reader = BufferedReader( InputStreamReader(client.getInputStream));
Writer = PrWriter(client.getOutputStream);
}
catch(UnknownHostException e) {
.out.prln("Error1 ting up connection: unknown host at "+hostIp+":"+hostPort);
}
catch(IOException e) {
.out.prln("Error2 ting up connection: "+e);
}
}
//断开远程服务器
public void tearDownConnection {
try {
Writer.close;
Reader.close;
}catch(IOException e) {
.out.prln("Error tearing down connection: "+e);
}
}
public void (String args) {
RemoteFileClient remoteFileClient = RemoteFileClient("127.0.0.1",1001);
remoteFileClient.UpConnection;
StringBuffer fileContents = StringBuffer;
fileContents.append(remoteFileClient.getFile("RemoteFileServer.java"));
//remoteFileClient.tearDownConnection;
.out.prln(fileContents);
}
}
首先我们导入 java.net 和 java.iojava.net 包为您提供您需要套接字工具java.io 包为您提供对流进行读写工具这是您和 TCP 套接字通信途径
我们给我们类例子变量以支持对套接字流读写和存储我们将连接到远程主机详细信息


我们类构造器有两个参数:远程主机IP地址和端口号各而且构造器将它们赋给例子变量
我们类有 思路方法和 3个其它思路方法稍后我们将探究这些思路方法细节现在您只需知道 UpConnection 将连接到远程服务器getFile 将向远程服务器请求 fileNameToGet 内容以及 tearDownConnection 将从远程服务器上断开
实现
这里我们实现 思路方法它将创建 RemoteFileClient 并用它来获取远程文件内容然后打印结果 思路方法用主机 IP 地址和端口号例子化个新 RemoteFileClient(客户机)然后我们告诉客户机建立个到主机连接接着我们告诉客户机获取主机上个指定文件内容最后我们告诉客户机断开它到主机连接我们把文件内容打印到控制台只是为了证明切都是按计划进行
建立连接
这里我们实现 UpConnection 思路方法它将创建我们 Socket 并让我们访问该套接字流:

public void UpConnection {
try {
Socket client = Socket(hostIp,hostPort);
Reader = BufferedReader( InputStreamReader(client.getInputStream));
Writer = PrWriter(client.getOutputStream);
}
catch(UnknownHostException e) {
.out.prln("Error1 ting up connection: unknown host at "+hostIp+":"+hostPort);
}
catch(IOException e) {
.out.prln("Error2 ting up connection: "+e);
}
}
UpConnection 思路方法用主机 IP 地址和端口号创建个 Socket:
Socket client = Socket(hostIp, hostPort);
我们把 Socket InputStream 包装进 BufferedReader 以使我们能够读取流然后我们把 Socket OutputStream 包装进 PrWriter 以使我们能够发送文件请求到服务器:
Reader = BufferedReader( InputStreamReader(client.getInputStream));Writer = PrWriter(client.getOutputStream);
请记住我们客户机和服务器只是来回传送字节客户机和服务器都必须知道另方即将发送是什么以使它们能够作出适当响应在这个案例中服务器知道我们将发送条有效文件路径
当您例子化个 Socket 时将抛出 UnknownHostException这里我们不特别处理它但我们打印些信息到控制台以告诉我们发生了什么同样地当我们试图获取 Socket InputStream 或 OutputStream 时如果抛出了般 IOException我们也打印些信息到控制台
和主机交谈
这里我们实现 getFile 思路方法它将告诉服务器我们想要什么文件并在服务器传回其内容时接收该内容

public String getFile(String fileNameToGet) {
StringBuffer fileLines = StringBuffer;
try {
Writer.prln(fileNameToGet);
Writer.flush;
String line = null;
while((line=Reader.readLine)!=null)
fileLines.append(line+"\n");
}
catch(IOException e) {
.out.prln("Error reading from file: "+fileNameToGet);
}
fileLines.toString;
}


对getFile思路方法要求个有效文件路径String它首先创建名为fileLines StringBufferfileLines 用于存储我们读自服务器上文件
StringBuffer fileLines = StringBuffer;
在 try{}catch{} 块中我们用 PrWriter 把请求发送到主机PrWriter 是我们在创建连接期间建立
Writer.prln(fileNameToGet); Writer.flush;
请注意这里我们是 flush 该 PrWriter而不是关闭它这迫使数据被发送到服务器而不关闭 Socket
旦我们已经写到 Socket我们就希望有些响应我们不得不在 Socket InputStream 上等待它我们通过在 while 循环中 BufferedReader 上 readLine 来达到这个目我们把每个返回行附加到 fileLines StringBuffer(带有个换行符以保护行):
String line = null; while((line=Reader.readLine)!=null) fileLines.append(line+"\n");
断开连接
这里我们实现 tearDownConnection 思路方法它将在我们使用完毕连接后负责“清除”tearDownConnection思路方法只是分别关闭我们在SocketInputStream和OutputStream上创建 BufferedReader和PrWriter这样做会关闭我们从Socket获取底层流所以我们必须捕捉可能 IOException
整理总结下客户机
我们类研究完了在我们继续往前讨论服务器端情况的前让我们回顾下创建和使用 Socket 步骤:
1. 用您想连接机器 IP 地址和端口例子化 Socket(如有问题则抛出 Exception)
2. 获取 Socket 上流以进行读写
3. 把流包装进 BufferedReader/PrWriter 例子如果这样做能使事情更简单
4. 对 Socket 进行读写
5. 关闭打开
5 创建服务器Socket
创建 RemoteFileServer 类

import java.io.*;
import java.net.*;
public RemoteFileServer {
listenPort;
public RemoteFileServer( listenPort) {
this.listenPort=listenPort;
}
//允许客户机连接到服务器,等待客户机请求
public void acceptConnections {
try {
ServerSocket server = ServerSocket(listenPort);
Socket incomingConnection = null;
while(true) {
incomingConnection = server.accept;
handleConnection(incomingConnection);
}
}
catch(BindException e) {
.out.prln("Unable to bind to port "+listenPort);
}
catch(IOException e) {
.out.prln("Unable to instantiate a ServerSocket _disibledevent=>



}
}
//和客户机Socket交互以将客户机所请求文件内容发送到客户机
public void handleConnection(Socket incomingConnection) {
try {
OutputStream outputToSocket = incomingConnection.getOutputStream;
InputStream inputFromSocket = incomingConnection.getInputStream;
BufferedReader streamReader = BufferedReader( InputStreamReader(inputFromSocket));
FileReader fileReader = FileReader( File(streamReader.readLine));
BufferedReader bufferedFileReader = BufferedReader(fileReader);
PrWriter streamWriter = PrWriter(incomingConnection.getOutputStream);
String line = null;
while((line=bufferedFileReader.readLine)!=null){
streamWriter.prln(line);
}
fileReader.close;
streamWriter.close;
streamReader.close;
}
catch(Exception e) {
.out.prln("Error handling a client: "+e);
e.prStackTrace;
}
}
public void (String args) {
RemoteFileServer server = RemoteFileServer(1001);
server.acceptConnections;
}
}
跟客户机中我们首先导入java.netjava.io接着我们给我们个例子变量以保存端口我们从该端口侦听进入连接缺省情况下端口是1001
我们类有思路方法和两个其它思路方法稍后我们将探究这些思路方法细节现在您只需知道acceptConnections将允许客户机连接到服务器以及handleConnection和客户机Socket交互以将您所请求文件内容发送到客户机
实现
这里我们实现思路方法它将创建RemoteFileServer并告诉它接受连接:服务器端思路方法中我们例子化个新 RemoteFileServer它将在侦听端口(1001)上侦听进入连接请求然后我们acceptConnections来告诉该 server进行侦听
接受连接
这里我们实现 acceptConnections 思路方法它将创建个 ServerSocket 并等待连接请求:

public void acceptConnections {
try {
ServerSocket server = ServerSocket(listenPort);
Socket incomingConnection = null;
while(true) {
incomingConnection = server.accept;
handleConnection(incomingConnection);
}
}
catch(BindException e) {
.out.prln("Unable to bind to port "+listenPort);
}
catch(IOException e) {
.out.prln("Unable to instantiate a ServerSocket _disibledevent=>InputStream inputFromSocket = incomingConnection.getInputStream;
BufferedReader streamReader = BufferedReader( InputStreamReader(inputFromSocket));
FileReader fileReader = FileReader( File(streamReader.readLine));
BufferedReader bufferedFileReader = BufferedReader(fileReader);
PrWriter streamWriter = PrWriter(incomingConnection.getOutputStream);
String line = null;
while((line=bufferedFileReader.readLine)!=null){
streamWriter.prln(line);
}
fileReader.close;
streamWriter.close;
streamReader.close;
}
catch(Exception e) {
.out.prln("Error handling a client: "+e);
e.prStackTrace;
}
}


跟在客户机中我们用getOutputStream和getInputStream来获取和我们刚创建Socket相关联跟在客户机端我们把InputStream包装进BufferedReader把OutputStream包装进PrWriter在服务器端上我们需要添加些代码用来读取目标文件和把内容逐行发送到客户机这里是重要代码:
FileReader fileReader = FileReader( File(streamReader.readLine)); BufferedReader bufferedFileReader = BufferedReader(fileReader); String line = null; while((line=bufferedFileReader.readLine)!=null) { streamWriter.prln(line); }
这些代码值得详细解释让我们点来看:
FileReader fileReader = FileReader( File(streamReader.readLine));
首先我们使用Socket InputStreamBufferedReader我们应该获取条有效文件路径所以我们用该路径名构造个新File我们创建个新FileReader来处理读文件操作
BufferedReader bufferedFileReader = BufferedReader(fileReader);


这里我们把FileReader包装进BufferedReader以使我们能够逐行地读该文件
接着我们BufferedReaderreadLine这个将造成阻塞直到有字节到来我们获取些字节的后就把它们放到本地line变量中然后再写出到客户机上完成读写操作的后我们就关闭打开
请注意我们在完成从Socket读操作的后关闭streamWriter和streamReader您或许会问我们为什么不在读取文件名的后立刻关闭 streamReader原因是当您这样做时客户机将不会获取任何数据如果您在关闭streamWriter的前关闭 streamReader则您可以往Socket写任何东西但却没有任何数据能通过通道(通道被关闭了)
整理总结下服务器
在我们接着讨论另个更实际举例的前让我们回顾下创建和使用ServerSocket步骤:
1. 用个您想让它侦听传入客户机连接端口来例子化个ServerSocket(如有问题则抛出 Exception)
2. ServerSocketaccept以在等待连接期间造成阻塞
3. 获取位于该底层Socket流以进行读写操作
4. 按使事情简单化原则包装流
5. 对Socket进行读写
6. 关闭打开流(并请记住永远不要在关闭Writer的前关闭Reader)
6 创建多线程Socket服务器
前面举例教给您基础知识但并不能令您更深入如果您到此就停止了那么您次只能处理台客户机原因是handleConnection个阻塞思路方法只有当它完成了对当前连接处理时服务器才能接受另个客户机在多数时候您将需要(也有必要)个多线程服务器
创建 MultithreadedRemoteFileServer 类

import java.io.*;
import java.net.*;
public MultithreadedRemoteFileServer {
listenPort;
public MultithreadedRemoteFileServer( listenPort) {
this.listenPort=listenPort;
}
//允许客户机连接到服务器,等待客户机请求
public void acceptConnections {
try {
ServerSocket server = ServerSocket(listenPort, 5);
Socket incomingConnection = null;
while(true) {
incomingConnection = server.accept;
handleConnection(incomingConnection);
}
}
catch(BindException e) {
.out.prln("Unable to bind to port "+listenPort);
}
catch(IOException e) {
.out.prln("Unable to instantiate a ServerSocket _disibledevent=>server.acceptConnections;
}
}
这里我们实现改动过acceptConnections思路方法它将创建个能够处理待发请求ServerSocket并告诉ServerSocket接受连接
server 仍然需要acceptConnections所以这些代码实际上是突出显示行表示个重大区别对这个多线程版我们现在可以指定客户机请求最大数目这些请求都能在例子化ServerSocket期间处于待发状态如果我们没有指定客户机请求最大数目则我们假设使用缺省值50
这里是它工作机制假设我们指定待发数(backlog 值)是5并且有 5台客户机请求连接到我们服务器我们服务器将着手处理第个连接但处理该连接需要很长时间由于我们待发值是5所以我们次可以放 5个请求到队列中我们正在处理所以这意味着还有其它 5个正在等待等待和正在处理共有 6个当我们服务器仍忙于接受号连接(记住队列中还有 2?6 号)时如果有第 7个客户机提出连接申请那么该第 7个客户机将遭到拒绝我们将在带有连接池服务器举例中介绍说明如何限定能同时连接客户机数目
处理连接:
public void handleConnection(Socket connectionToHandle) {
Thread( ConnectionHandler(connectionToHandle)).start;
}
我们对RemoteFileServer所做大改动就体现在这个思路方法上我们仍然在服务器接受个连接的后handleConnection但现在我们把该Socket传递给ConnectionHandler个例子它是 Runnable我们用ConnectionHandler创建个新 Thread 并启动它ConnectionHandlerrun思路方法包Socket读/写和读File代码这些代码原来在 RemoteFileServerhandleConnection
创建 ConnectionHandler 类

import java.io.*;
import java.net.*;
public ConnectionHandler implements Runnable {
protected Socket ToHandle;
public ConnectionHandler(Socket ToHandle) {
this.ToHandle=ToHandle;
}
public void run {
try {
PrWriter streamWriter = PrWriter(ToHandle.getOutputStream);
BufferedReader streamReader = BufferedReader( InputStreamReader(ToHandle.getInputStream));
String fileToRead = streamReader.readLine;
BufferedReader fileReader = BufferedReader( FileReader(fileToRead));
String line =null;
while((line=fileReader.readLine)!=null) {
streamWriter.prln(line);
}
fileReader.close;
streamWriter.close;
streamReader.close;
}
catch(Exception e) {
.out.prln("Error handling a client: "+e);
e.prStackTrace;
}
}
}
这个助手类相当简单跟我们到目前为止其它类我们导入java.net和java.io该类只有个例子变量ToHandle它保存由该例子处理Socket


构造器用个Socket例子作参数并将它赋给ToHandle
请注意该类实现了Runnable接口实现这个接口类都必须实现run思路方法这里我们实现run思路方法它将攫取我们连接用它来读写该连接并在任务完成的后关闭它ConnectionHandlerrun思路方法所做事情就是RemoteFileServer上 handleConnection所做事情首先我们把InputStream和OutputStream分别包装(用Socket getOutputStream和 getInputStream)进BufferedReader和PrWriter然后我们用这些代码逐行地读目标文件:

PrWriter streamWriter = PrWriter(ToHandle.getOutputStream);
BufferedReader streamReader = BufferedReader( InputStreamReader(ToHandle.getInputStream));
String fileToRead = streamReader.readLine;
BufferedReader fileReader = BufferedReader( FileReader(fileToRead));
String line =null;
while((line=fileReader.readLine)!=null) {
streamWriter.prln(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 PooledRemoteFileServer {
protected maxConnections;
protected listenPort;
protected ServerSocket serverSocket;
public PooledRemoteFileServer( aListenPort, maxConnections) {
listenPort= aListenPort;
this.maxConnections = maxConnections;
}
public void acceptConnections {
try {
ServerSocket server = ServerSocket(listenPort, 5);
Socket incomingConnection = null;
while(true) {
incomingConnection = server.accept;
handleConnection(incomingConnection);
}
}
catch(BindException e) {
.out.prln("");
}
catch(IOException e) {
.out.prln(""+listenPort);
}
}
protected void handleConnection(Socket connectionToHandle) {
PooledConnectionHandler.processRequest(connectionToHandle);
}
public void UpHandlers {
for( i=0; i<maxConnections; i) {
PooledConnectionHandler currentHandler = PooledConnectionHandler;
Thread(currentHandler, "Handler " + i).start;
}
}
public void (String args) {
PooledRemoteFileServer server = PooledRemoteFileServer(1001, 3);
server.UpHandlers;
server.acceptConnections;
}
}
请注意下您现在应该熟悉了 import 语句我们给类以下例子变量以保存:
• 我们服务器能同时处理活动客户机连接最大数目
• 进入连接侦听端口(我们没有指定缺省值但如果您想这样做并不会受到限制)
• 将接受客户机连接请求 ServerSocket
构造器用参数是侦听端口和连接最大数目
我们类有 思路方法和 3个其它思路方法稍后我们将探究这些思路方法细节现在只须知道UpHandlers创建数目为 maxConnections大量PooledConnectionHandler而其它两个思路方法则和我们前面已经看到相似:acceptConnections在ServerSocket上侦听传入客户机连接而handleConnection则在客户机连接旦被建立后就实际处理它
实现
这里我们实现需作改动思路方法该思路方法将创建能够处理给定数目客户机连接PooledRemoteFileServer并告诉它接受连接:


public void (String args) {
PooledRemoteFileServer server = PooledRemoteFileServer(1001, 3);
server.UpHandlers;
server.acceptConnections;
}
我们思路方法很简单我们例子化个新PooledRemoteFileServer它将通过UpHandlers来建立 3个 PooledConnectionHandler旦服务器就绪我们就告诉它acceptConnections
建立连接处理
public void UpHandlers {
for( i=0; i<maxConnections; i) {
PooledConnectionHandler currentHandler = PooledConnectionHandler;
Thread(currentHandler, "Handler " + i).start;
}
}
UpHandlers 思路方法创建maxConnections(例如 3)个PooledConnectionHandler并在新Thread中激活它们用实现了 Runnable对象来创建Thread使我们可以在Threadstart并且可以期望在Runnable上了run换句话说我们PooledConnectionHandler将等着处理进入连接每个都在它自己Thread中进行我们在举例中只创建 3个Thread而且旦服务器运行这就不能被改变
处理连接
这里我们实现需作改动handleConnections思路方法它将委派PooledConnectionHandler处理连接:
protected void handleConnection(Socket connectionToHandle) {
PooledConnectionHandler.processRequest(connectionToHandle);
}
我们现在叫 PooledConnectionHandler 处理所有进入连接(processRequest个静态思路方法)
创建 PooledRemoteFileServer 类

import java.io.*;
import java.net.*;
import java.util.*;
public PooledConnectionHandler implements Runnable {
protected Socket connection;
protected List pool = LinkedList;
public PooledConnectionHandler {}
public void handleConnection {
try {
PrWriter streamWriter = PrWriter(connection.getOutputStream);
BufferedReader streamReader = BufferedReader( InputStreamReader(connection.getInputStream));
String fileToRead = streamReader.readLine;
BufferedReader fileReader = BufferedReader( FileReader(fileToRead));
String line = null;
while((line=fileReader.readLine)!=null)
streamWriter.prln(line);
fileReader.close;
streamWriter.close;
streamReader.close;
}
catch(FileNotFoundException e) {
.out.prln("");
}
catch(IOException e) {
.out.prln(""+e);
}
}
public void processRequest(Socket requestToHandle) {
synchronized(pool) {
pool.add(pool.size, requestToHandle);
pool.notyAll;
}
}
public void run {
while(true) {
synchronized(pool) {
while(pool.isEmpty) {
try {
pool.wait;
}
catch(InterruptedException e) {
e.prStackTrace;
}
}
connection= (Socket)pool.remove(0);
}
handleConnection;
}
}
}

这个助手类和 ConnectionHandler 非常相似但它带有处理连接池手段该类有两个例子变量:
• connection 是当前正在处理 Socket
• 名为 pool 静态 LinkedList 保存需被处理连接
填充连接池
这里我们实现PooledConnectionHandler上processRequest思路方法它将把传入请求添加到池中并告诉其它正在等待对象该池已经有些内容:
public void processRequest(Socket requestToHandle) {
synchronized(pool) {
pool.add(pool.size, requestToHandle);
pool.notyAll;
}
}
synchronized 块是个稍微有些区别东西您可以同步任何对象上个块而不只是在本身某个思路方法中含有该块对象在我们举例中processRequest 思路方法包含有个 pool(请记住它是个 LinkedList保存等待处理连接池) synchronized块我们这样做原因是确保没有别人能跟我们同时修改连接池
既然我们已经保证了我们是唯“涉水”池中我们就可以把传入Socket添加到LinkedList尾端旦我们添加了新连接我们就用以下代码通知其它正在等待该池Thread池现在已经可用:
pool.notyAll;
Object所有子类都继承这个notyAll思路方法这个思路方法连同我们下屏将要讨论wait思路方法就使个Thread能够让另个Thread知道些条件已经具备这意味着该第 2个Thread定正在等待那些条件满足
从池中获取连接
这里我们实现PooledConnectionHandler上需作改动run思路方法它将在连接池上等待并且池中有连接就处理它:
public void run {
while(true) {
synchronized(pool) {
while(pool.isEmpty) {
try {
pool.wait;
}
catch(InterruptedException e) {
e.prStackTrace;
}
}
connection= (Socket)pool.remove(0);
}
handleConnection;
}
}
回想下在前面讲过:个Thread正在等待有人通知它连接池方面条件已经满足了在我们举例中请记住我们有 3个 PooledConnectionHandler在等待使用池中连接每个PooledConnectionHandler都在它自已Thread中运行并通过pool.wait产生阻塞当我们processRequest在连接池上notyAll所有正在等待 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 {
PrWriter streamWriter = PrWriter(connection.getOutputStream);
BufferedReader streamReader = BufferedReader( InputStreamReader(connection.getInputStream));
String fileToRead = streamReader.readLine;
BufferedReader fileReader = BufferedReader( FileReader(fileToRead));
String line = null;
while((line=fileReader.readLine)!=null)
streamWriter.prln(line);
fileReader.close;
streamWriter.close;
streamReader.close;
}
catch(FileNotFoundException e) {
.out.prln("");
}
catch(IOException e) {
.out.prln(""+e);
}
}

跟在多线程服务器中区别我们PooledConnectionHandler有个handleConnection思路方法这个思路方法代码跟非池式ConnectionHandler上run思路方法代码完全首先我们把OutputStream和InputStream分别包装进(用Socket上getOutputStream和getInputStream)BufferedReader和PrWriter然后我们逐行读目标文件就象我们在多线程举例中做那样我们获取些字节的后就把它们放到本地line变量中然后写出到客户机完成读写操作的后我们关闭FileReader和打开

整理总结下带有连接池服务器
让我们回顾下创建和使用“池版”服务器步骤:
1. 创建个新种类连接处理(我们称的为 PooledConnectionHandler)来处理池中连接
2. 修改服务器以创建和使用组 PooledConnectionHandler

Java 语言简化了套接字在应用使用基础实际上是 java.net 包中 Socket 和 ServerSocket 类旦您理解了表象背后发生情况就能容易地使用这些类在现实生活中使用套接字只是这样件事即通过贯彻优秀 OO 设计原则来保护应用中各层间封装我们为您展示了些有帮助这些类结构对我们应用隐藏了 Socket 交互作用低级细节 ? 使应用能只使用可插入 ClientSocketFacade 和 ServerSocketFacade在有些地方(在 Facade 内)您仍然必须管理稍显杂乱字节细节但您只须做次就可以了更好您可以在将来项目中重用这些低级别助手类
0

相关文章

读者评论

发表评论

  • 昵称:
  • 内容: