友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
Java编程思想第4版[中文版](PDF格式)-第127部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
因为客户和服务器在采取下一步操作之前都要等待一行文本内容的到达。若刷新没有发生,那么信息不会进
入网络,除非缓冲区满(溢出),这会为本例带来许多问题。
编写网络应用程序时,需要特别注意自动刷新机制的使用。每次刷新缓冲区时,必须创建和发出一个数据包
(数据封)。就目前的情况来说,这正是我们所希望的,因为假如包内包含了还没有发出的文本行,服务器
和客户机之间的相互“握手”就会停止。换句话说,一行的末尾就是一条消息的末尾。但在其他许多情况
下,消息并不是用行分隔的,所以不如不用自动刷新机制,而用内建的缓冲区判决机制来决定何时发送一个
数据包。这样一来,我们可以发出较大的数据包,而且处理进程也能加快。
注意和我们打开的几乎所有数据流一样,它们都要进行缓冲处理。本章末尾有一个练习,清楚展现了假如我
们不对数据流进行缓冲,那么会得到什么样的后果(速度会变慢)。
无限while 循环从BufferedReader in 内读取文本行,并将信息写入System。out,然后写入
PrintWriter。out。注意这可以是任何数据流,它们只是在表面上同网络连接。
客户程序发出包含了〃END〃的行后,程序会中止循环,并关闭 Socket。
下面是客户程序的源码:
//: JabberClient。java
// Very simple client that just sends
// lines to the server and reads lines
// that the server sends。
import java。*;
import java。io。*;
public class JabberClient {
public static void main(String'' args)
throws IOException {
// Passing null to getByName() produces the
// special 〃Local Loopback〃 IP address; for
541
…………………………………………………………Page 543……………………………………………………………
// testing on one machine w/o a network:
InetAddress addr =
InetAddress。getByName(null);
// Alternatively; you can use
// the address or name:
// InetAddress addr =
// InetAddress。getByName(〃127。0。0。1〃);
// InetAddress addr =
// InetAddress。getByName(〃localhost〃);
System。out。println(〃addr = 〃 + addr);
Socket socket =
new Socket(addr; JabberServer。PORT);
// Guard everything in a try…finally to make
// sure that the socket is closed:
try {
System。out。println(〃socket = 〃 + socket);
BufferedReader in =
new BufferedReader(
new InputStreamReader(
socket。getInputStream()));
// Output is automatically flushed
// by PrintWriter:
PrintWriter out =
new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
socket。getOutputStream()));true);
for(int i = 0; i 《 10; i ++) {
out。println(〃howdy 〃 + i);
String str = in。readLine();
System。out。println(str);
}
out。println(〃END〃);
} finally {
System。out。println(〃closing。。。〃);
socket。close();
}
}
} ///:~
在main()中,大家可看到获得本地主机 IP地址的 InetAddress 的三种途径:使用 null,使用 localhost,
或者直接使用保留地址 127。0。0。1。当然,如果想通过网络同一台远程主机连接,也可以换用那台机器的IP
地址。打印出 InetAddress addr 后(通过对 toString()方法的自动调用),结果如下:
localhost/127。0。0。1
通过向 getByName()传递一个null,它会默认寻找 localhost,并生成特殊的保留地址127。0。0。1。注意在名
为 socket 的套接字创建时,同时使用了 InetAddress 以及端口号。打印这样的某个Socket 对象时,为了真
正理解它的含义,请记住一次独一无二的因特网连接是用下述四种数据标识的:clientHost (客户主机)、
clientPortNumber (客户端口号)、serverHost (服务主机)以及serverPortNumber (服务端口号)。服务
程序启动后,会在本地主机(127。0。0。1)上建立为它分配的端口(8080 )。一旦客户程序发出请求,机器上
下一个可用的端口就会分配给它(这种情况下是 1077),这一行动也在与服务程序相同的机器
(127。0。0。1)上进行。现在,为了使数据能在客户及服务程序之间来回传送,每一端都需要知道把数据发到
哪里。所以在同一个“已知”服务程序连接的时候,客户会发出一个“返回地址”,使服务器程序知道将自
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
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!