分享
 
 
 

菜鸟初学Java的备忘录(六)

王朝java/jsp·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

2003年1月21日 星期二 晴

通过程序建立了实际的概念之后,现在应该回到最开始的问题,Socket是什么?是实现计算机通信的一种方式,这毫无疑问.但如何能够用最容易理解的语言比较形象而又不偏颇的描述它的原理呢?

Bruce Eckel 在他的《Java 编程思想》一书中这样描述套接字:

套接字是一种软件抽象,用于表达两台机器之间的连接“终端”。对于一个给定的连接,每台机器上都有一个套接字,您也可以想象它们之间有一条虚拟的“电缆”,“电缆”的每一端都插入到套接字中。当然,机器之间的物理硬件和电缆连接都是完全未知的。抽象的全部目的是使我们无须知道不必知道的细节.

按我的理解,抽象点来说,一个Socket就是一个电话听筒,你有一个,和你通话的人也有一个,只不过其中有一个人的听筒叫ServerSocket,另一个人的听筒叫Socket.至于谁是ServerSocket,谁是Socket,这不重要,因为客户端和服务器端本来就是相对的,可以互相转化的.通话的两个人通过拿起两个听筒建立了一条通道,这条通道通不通就要看是不是双方都拿起听筒了,假如只有一方拿起听筒,那就只能听到一些嘟嘟的声音,证明通道不同.这里,拿起听筒的过程就是Socket初始化的过程.建立了通道之后,也就是大家都拿起听筒之后,通道两端的人就可以开始通话了.这里又有两个过程,即A对B说话,B接听,和B对A说话,A收听,这两个过程是通过两条线路完成的.传输在这两条线路上的,就是流.流隐藏了所有传输的细节,使得通信双方都认为,他们传过去的是声音,而不是编码.

前面写的服务器端的程序实际上是单任务版本,服务器对客户机的处理机制是在同一时间段内只能处理一个连接,因为handleConnection中采取的是不断循环的阻塞方法,检测到一个,就处理一个,然后再检测到一个,就再处理一个,如果有多个连接同时请求,那只能排队等候.这样的程序是无法在网络中应付多个连接的,因为你无法保证在同一时间内只有一个客户提出与服务器的连接请求,而用阻塞的方法应付多客户连接其速度之慢是可想而知的.

这样就催生了面向多连接的版本.显然,通过多线程可以来实现我们的要求.

由于要解决的是处理客户连接的问题,因此我们的工作只是在服务器端的程序当中修改.其原理不难推出,就是在检测到一个连接请求之后,马上建立一个线程去处理它,然后继续兼听下一个连接请求.所以,我们只需要将原来在handleConnection中的代码原封不动的放到线程的执行代码中,而在handleConnection中添加上新建线程的代码就可以了,十分简单.

同上一篇的风格一样,我们来观察各个部分的代码细节.

首先为这个多线程的版本创建类MultiThreadRemoteFileServer

看看这个类的定义

import java.io.*;

import java.net.*;

public class MultiThreadRemoteFileServer{

protected int listenPort;

public MultiThreadRemoteFileServer(int aListenPort){

}

public static void main(String[] args) {

}

public void acceptConnections() {

}

public void handleConnection(Socket incomingConnection) {

}

}

几乎和RemoteFileServer是一样的,唯一的区别是在我们现在创建的这个类中增加了一个构造函数,这是为了能够使得监听的端口号由我们自己来定.定义如下

public MultithreadedRemoteFileServer(int aListenPort) {

listenPort = aListenPort;

}

先来看main()

public static void main(String[] args) {

MultithreadedRemoteFileServer server = new MultithreadedRemoteFileServer(3000);

server.acceptConnections();

}

没有区别吧,和RemoteFileServer的main()函数,只是端口号在创建的时候由主程序指定而已。

我们主要关心的改动都在后面

现在看acceptConnection监听程序

public void acceptConnections() {

try {

ServerSocket server = new ServerSocket(listenPort, 5);//注意到没有,建立服务器Socket的时候多了一个参数,这个参数是用来指定能够同时申请连接的最大数目,缺省值是50

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);

}

}

改动的地方就一个,多了个参数.这里是它的工作机制。假设我们指定待发数(backlog 值)是5并且有五台客户机请求连接到我们的服务器。我们的服务器将着手处理第一个连接,但处理该连接需要很长时间。由于我们的待发值是 5,所以我们一次可以放五个请求到队列中。我们正在处理一个,所以这意味着还有其它五个正在等待。等待的和正在处理的一共有六个。当我们的服务器仍忙于接受一号连接(记住队列中还有 2—6 号)时,如果有第七个客户机提出连接申请,那么,该第七个客户机将遭到拒绝

接着看,我们的下一个改动显然是在处理监听到的线程的方法handleConnection中,前面已经说了,在多线程的版本中,我们检测到一个连接请求,就马上生成一个线程,然后就不用理它了,那么在这里就是新建线程的一句话.

public void handleConnection(Socket connectionToHandle) {

new Thread(new ConnectionHandler(connectionToHandle)).start();

}

我们注意到有一个新的类ConnectionHandler,这个类是Runnable的,即是一个接口类(这是用接口实现的一个线程,要是有不明白的话,可以去看看17号的关于线程的东西).我们用 ConnectionHandler 创建一个新 Thread 并启动它。正如我们刚才所说的,原来在RemoteFileServer的handleConnection中的代码统统原封不动的转移到了这个接口类ConnectionHandler的run()方法中来了.

那么我们来看看整个ConnectionHandler类的定义吧。

class ConnectionHandler implements Runnable {

protected Socket socketToHandle;

public ConnectionHandler(Socket aSocketToHandle) {

socketToHandle = aSocketToHandle;//通过构造函数,将待处理的Socket实例作为参数传送进来

}

public void run() {//原来对Socket的读/写的代码都在这里了

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);

}

}

}

ConnectionHandler 的 run() 方法所做的事情就是 RemoteFileServer 上的 handleConnection() 所做的事情。首先把 InputStream 和 OutputStream 分别包装(用 Socket 的 getOutputStream() 和 getInputStream())进 BufferedReader 和 PrintWriter。然后我们用这些代码逐行地读目标文件.由于InputStream中装的是文件路径,所以中间还需要使用FileReader流将文件路径包装,再经由BufferedReader包装读出.

我们的多线程服务器研究完了,同样,我们回顾一下创建和使用“多线程版”的服务器的步骤:

1.修改 acceptConnections() 以用缺省为 50(或任何您想要的大于 1 的指定数字)实例化 ServerSocket。

2. 修改 ServerSocket 的 handleConnection() 以用 ConnectionHandler 的一个实例生成一个新的 Thread。

3.借用 RemoteFileServer 的 handleConnection() 方法的代码实现 ConnectionHandler 类的run()函数。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有