Java编程思想第4版[中文版](PDF格式)-第140部分
按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
542
…………………………………………………………Page 544……………………………………………………………
己的数据发到哪儿。我们在服务器端的示范输出中可以体会到这一情况:
Socket'addr=127。0。0。1;port=1077;localport=8080'
这意味着服务器刚才已接受了来自 127。0。0。1 这台机器的端口 1077 的连接,同时监听自己的本地端口
(8080 )。而在客户端:
Socket'addr=localhost/127。0。0。1;PORT=8080;localport=1077'
这意味着客户已用自己的本地端口 1077 与 127。0。0。1 机器上的端口 8080 建立了 连接。
大家会注意到每次重新启动客户程序的时候,本地端口的编号都会增加。这个编号从 1025 (刚好在系统保留
的1…1024 之外)开始,并会一直增加下去,除非我们重启机器。若重新启动机器,端口号仍然会从 1025 开
始增值(在 Unix 机器中,一旦超过保留的套按字范围,数字就会再次从最小的可用数字开始)。
创建好 Socket 对象后,将其转换成BufferedReader 和 PrintWriter 的过程便与在服务器中相同(同样地,
两种情况下都要从一个 Socket 开始)。在这里,客户通过发出字串〃howdy〃,并在后面跟随一个数字,从而
初始化通信。注意缓冲区必须再次刷新(这是自动发生的,通过传递给PrintWriter 构建器的第二个参
数)。若缓冲区没有刷新,那么整个会话(通信)都会被挂起,因为用于初始化的“howdy”永远不会发送出
去(缓冲区不够满,不足以造成发送动作的自动进行)。从服务器返回的每一行都会写入System。out,以验
证一切都在正常运转。为中止会话,需要发出一个〃END〃。若客户程序简单地挂起,那么服务器会“掷”出一
个违例。
大家在这里可以看到我们采用了同样的措施来确保由Socket 代表的网络资源得到正确的清除,这是用一个
try…finally块实现的。
套接字建立了一个“专用”连接,它会一直持续到明确断开连接为止(专用连接也可能间接性地断开,前提
是某一端或者中间的某条链路出现故障而崩溃)。这意味着参与连接的双方都被锁定在通信中,而且无论是
否有数据传递,连接都会连续处于开放状态。从表面看,这似乎是一种合理的连网方式。然而,它也为网络
带来了额外的开销。本章后面会介绍进行连网的另一种方式。采用那种方式,连接的建立只是暂时的。
15。3 服务多个客户
JabberServer 可以正常工作,但每次只能为一个客户程序提供服务。在典型的服务器中,我们希望同时能处
理多个客户的请求。解决这个问题的关键就是多线程处理机制。而对于那些本身不支持多线程的语言,达到
这个要求无疑是异常困难的。通过第 14 章的学习,大家已经知道Java 已对多线程的处理进行了尽可能的简
化。由于Java 的线程处理方式非常直接,所以让服务器控制多名客户并不是件难事。
最基本的方法是在服务器(程序)里创建单个 ServerSocket,并调用accept()来等候一个新连接。一旦
accept()返回,我们就取得结果获得的 Socket,并用它新建一个线程,令其只为那个特定的客户服务。然后
再调用 accept() ,等候下一次新的连接请求。
对于下面这段服务器代码,大家可发现它与JabberServer。java 例子非常相似,只是为一个特定的客户提供
服务的所有操作都已移入一个独立的线程类中:
//: MultiJabberServer。java
// A server that uses multithreading to handle
// any number of clients。
import java。io。*;
import java。*;
class ServeOneJabber extends Thread {
private Socket socket;
private BufferedReader in;
private PrintWriter out;
public ServeOneJabber(Socket s)
throws IOException {
socket = s;
in =
new BufferedReader(
new InputStreamReader(
socket。getInputStream()));
// Enable auto…flush:
543
…………………………………………………………Page 545……………………………………………………………
out =
new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
socket。getOutputStream())); true);
// If any of the above calls throw an
// exception; the caller is responsible for
// closing the socket。 Otherwise the thread
// will close it。
start(); // Calls run()
}
public void run() {
try {
while (true) {
String str = in。readLine();
if (str。equals(〃END〃)) break;
System。out。println(〃Echoing: 〃 + str);
out。println(str);
}
System。out。println(〃closing。。。〃);
} catch (IOException e) {
} finally {
try {
socket。close();
} catch(IOException e) {}
}
}
}
public class MultiJabberServer {
static final int PORT = 8080;
public static void main(String'' args)
throws IOException {
ServerSocket s = new ServerSocket(PORT);
System。out。println(〃Server Started〃);
try {
while(true) {
// Blocks until a connection occurs:
Socket socket = s。accept();
try {
new ServeOneJabber(socket);
} catch(IOException e) {
// If it fails; close the socket;
// otherwise the thread will close it:
socket。close();
}
}
} finally {
s。close();
}
}
544
…………………………………………………………Page 546……………………………………………………………
} ///:~
每次有新客户请求建立一个连接时,ServeOneJabber 线程都会取得由accept()在 main() 中生成的Socket 对
象。然后和往常一样,它创建一个 BufferedReader,并用Socket 自动刷新PrintWriter 对象。最后,它调
用Thread 的特殊方法 start(),令其进行线程的初始化,然后调用run()。这里采取的操作与前例是一样
的:从套扫字读入某些东西,然后把它原样反馈回去,直到遇到一个特殊的〃END〃结束标志为止。
同样地,套接字的清除必须进行谨慎的设计。就目前这种情况来说,套接字是在ServeOneJabber 外部创建
的,所以清除工作可以“共享”。若ServeOneJabber 构建器失败,那么只需向调用者“掷”出一个违例即
可,然后由调用者负责线程的清除。但假如构建器成功,那么必须由 ServeOneJabber 对象负责线程的清除,
这是在它的 run()里进行的。
请注意MultiJabberServer 有多么简单。和以前一样,我们创建一个 ServerSocket,并调用accept()允许一
个新连接的建立。但这一次,accept() 的返回值(一个套接字)将传递给用于ServeOneJabber 的构建器,由
它创建一个新线程,并对那个连接进行控制。连接中断后,线程便可简单地消失。
如果ServerSocket 创建失败,则再一次通过 main()掷出违例。如果成功,则位于外层的 try…finally代码
块可以担保正确的清除。位于内层的try…catch 块只负责防范 ServeOneJabber 构建器的失败;若构建器成
功,则 ServeOneJabber 线程会将对应的套接字关掉。
为了证实服务器代码确实能为多名客户提供服务,下面这个程序将创建许多客户(使用线程),并同相同的
服务器建立连接。每个线程的“存在时间”都是有限的。一旦到期,就留出空间以便创建一个新线程。允许
创建的线程的最大数量是由final int maxthreads 决定的。大家会注意到这个值非常关键,因为假如把它设
得很大,线程便有可能耗尽资源,并产生不可预知的程序错误。
//: MultiJabberClient。java
// Client that tests the MultiJabberServer
// by starting up multiple clients。
import java。*;
import java。io。*;
class JabberClientThread extends Thread {
private Socket socket;
private BufferedReader in;
private PrintWriter out;
private static int counter = 0;
private int id = counter++;
private static int threadcount = 0;
public static int threadCount() {
return threadcount;
}
public JabberClientThread(InetAddress addr) {
System。out。println(〃Making client 〃 + id);
threadcount++;
try {
socket =
new Socket(addr; MultiJabberServer。PORT);
} catch(IOException e) {
// If the creation of the socket fails;
// nothing needs to be cleaned up。
}
try {
in =
new BufferedReader(
new InputStreamReader(
socket。getInputStream()));
545
…………………………………………………………Page 547……………………………………………………………
// Enable auto…flush:
out =
new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
socket。getOutputStream())); true);
start();
} catch(IOException e) {
// The socket should be closed on any
// failures other than the socket
// constructor:
try {
socket。close();
} catch(IOException e2) {}
}
// Otherwise the socket will be closed by
// the run() method of the thread。
}
public void run() {
try {
for(int i = 0; i 《 25; i++) {
out。println(〃Client 〃 + id + 〃: 〃 + i);
String str = in。readLine();
System。out。println(str);
}
out。println(〃END〃);
} catch(IOException e) {
} finally {
// Always close it:
try {
socket。close();
} catch(IOExcept ion e) {}
threadcount…; // Ending this thread
}
}
}
public class MultiJabberClient {
static final int MAX_THREADS = 40;
public static void main(String'' args)
throws IOException; InterruptedException {
InetAddress addr =
InetAddress。getByName(null);
while(true) {
if(JabberClientThread。threadCount()
《 MAX_THREADS)
new JabberClientThread(addr);
Thread。currentThread()。sleep(100);
}
}
} ///:~
546
…………………………………………………………Page 548……………………………………………………………
JabberClientThread 构建器获取一个 InetAddress,并用它打开一个套接字。大家可能已看出了这样的一个
套路:Socket 肯定用于创建某种 Reader 以及/或者Writer (或者InputStream和/或 OutputStream)对
象,这是运用Socket 的唯一方式(当然,我们可考虑编写一、两个类,令其自动完成这些操作,避免大量重
复的代码编写工作)。同样地,start()执行线程的初始化,并调用run()。在这里,消息发送给服务器,而
来自服务器的信息则在屏幕上回显出来。然而,线程的“存在时间”是有限的,最终都会结束。注意在套接
字创建好以后,但在构建器完成之前,假若构建器失败,套接字会被清除