Java编程思想第4版[中文版](PDF格式)-第139部分
按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
冲区内。可按第 10章介绍的方法对类进行格式化,就象对待其他任何流对象那样。
对于Java 库的命名机制,ServerSocket (服务器套接字)的使用无疑是容易产生混淆的又一个例证。大家可
能认为ServerSocket 最好叫作“ServerConnector”(服务器连接器),或者其他什么名字,只是不要在其
中安插一个“Socket”。也可能以为ServerSocket 和 Socket 都应从一些通用的基础类继承。事实上,这两
种类确实包含了几个通用的方法,但还不够资格把它们赋给一个通用的基础类。相反,ServerSocket 的主要
任务是在那里耐心地等候其他机器同它连接,再返回一个实际的Socket。这正是“ServerSocket”这个命名
不恰当的地方,因为它的目标不是真的成为一个Socket,而是在其他人同它连接的时候产生一个Socket 对
象。
然而,ServerSocket 确实会在主机上创建一个物理性的“服务器”或者侦听用的套接字。这个套接字会侦听
进入的连接,然后利用 accept()方法返回一个“已建立”套接字(本地和远程端点均已定义)。容易混淆的
地方是这两个套接字(侦听和已建立)都与相同的服务器套接字关联在一起。侦听套接字只能接收新的连接
请求,不能接收实际的数据包。所以尽管 ServerSocket 对于编程并无太大的意义,但它确实是“物理性”
的。
创建一个ServerSocket 时,只需为其赋予一个端口编号。不必把一个 IP 地址分配它,因为它已经在自己代
表的那台机器上了。但在创建一个 Socket 时,却必须同时赋予IP 地址以及要连接的端口编号(另一方面,
从ServerSocket。accept()返回的 Socket 已经包含了所有这些信息)。
15。2。1 一个简单的服务器和客户机程序
这个例子将以最简单的方式运用套接字对服务器和客户机进行操作。服务器的全部工作就是等候建立一个连
接,然后用那个连接产生的Socket 创建一个 InputStream 以及一个OutputStream。在这之后,它从
InputStream读入的所有东西都会反馈给OutputStream,直到接收到行中止(END)为止,最后关闭连接。
客户机连接与服务器的连接,然后创建一个OutputStream。文本行通过OutputStream 发送。客户机也会创
建一个 InputStream,用它收听服务器说些什么(本例只不过是反馈回来的同样的字句)。
服务器与客户机(程序)都使用同样的端口号,而且客户机利用本地主机地址连接位于同一台机器中的服务
539
…………………………………………………………Page 541……………………………………………………………
器(程序),所以不必在一个物理性的网络里完成测试(在某些配置环境中,可能需要同真正的网络建立连
接,否则程序不能工作——尽管实际并不通过那个网络通信)。
下面是服务器程序:
//: JabberServer。java
// Very simple server that just
// echoes whatever the client sends。
import java。io。*;
import java。*;
public class JabberServer {
// Choose a port outside of the range 1…1024:
public static final int PORT = 8080;
public static void main(String'' args)
throws IOException {
ServerSocket s = new ServerSocket(PORT);
System。out。println(〃Started: 〃 + s);
try {
// Blocks until a connection occurs:
Socket socket = s。accept();
try {
System。out。println(
〃Connection accepted: 〃+ 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);
while (true) {
String str = in。readLine();
if (str。equals(〃END〃)) break;
System。out。println(〃Echoing: 〃 + str);
out。println(str);
}
// Always close the two sockets。。。
} finally {
System。out。println(〃closing。。。〃);
socket。close();
}
} finally {
s。close();
}
}
} ///:~
可以看到,ServerSocket 需要的只是一个端口编号,不需要 IP地址(因为它就在这台机器上运行)。调用
540
…………………………………………………………Page 542……………………………………………………………
accept()时,方法会暂时陷入停顿状态(堵塞),直到某个客户尝试同它建立连接。换言之,尽管它在那里
等候连接,但其他进程仍能正常运行(参考第 14章)。建好一个连接以后,accept()就会返回一个 Socket
对象,它是那个连接的代表。
清除套接字的责任在这里得到了很艺术的处理。假如ServerSocket 构建器失败,则程序简单地退出(注意必
须保证 ServerSocket 的构建器在失败之后不会留下任何打开的网络套接字)。针对这种情况,main()会
“掷”出一个IOException 违例,所以不必使用一个try 块。若 ServerSocket 构建器成功执行,则其他所有
方法调用都必须到一个 try…finally代码块里寻求保护,以确保无论块以什么方式留下,ServerSocket 都能
正确地关闭。
同样的道理也适用于由 accept()返回的 Socket。若accept() 失败,那么我们必须保证Socket 不再存在或者
含有任何资源,以便不必清除它们。但假若执行成功,则后续的语句必须进入一个try…finally 块内,以保
障在它们失败的情况下,Socket 仍能得到正确的清除。由于套接字使用了重要的非内存资源,所以在这里必
须特别谨慎,必须自己动手将它们清除(Java 中没有提供“破坏器”来帮助我们做这件事情)。
无论ServerSocket 还是由 accept()产生的 Socket 都打印到 System。out 里。这意味着它们的 toString方法
会得到自动调用。这样便产生了:
ServerSocket'addr=0。0。0。0;PORT=0;localport=8080'
Socket'addr=127。0。0。1;PORT=1077;localport=8080'
大家不久就会看到它们如何与客户程序做的事情配合。
程序的下一部分看来似乎仅仅是打开文件,以便读取和写入,只是 InputStream和 OutputStream 是从
Socket 对象创建的。利用两个“转换器”类 InputStreamReader 和OutputStreamWriter ,InputStream和
OutputStream 对象已经分别转换成为 Java 1。1 的Reader 和 Writer 对象。也可以直接使用 Java1。0 的
InputStream和 OutputStream 类,但对输出来说,使用Writer 方式具有明显的优势。这一优势是通过
PrintWriter 表现出来的,它有一个过载的构建器,能获取第二个参数——一个布尔值标志,指向是否在每
一次println()结束的时候自动刷新输出(但不适用于print()语句)。每次写入了输出内容后(写进
out),它的缓冲区必须刷新,使信息能正式通过网络传递出去。对目前这个例子来说,刷新显得尤为重要,
因为客户和服务器在采取下一步操作之前都要等待一行文本内容的到达。若刷新没有发生,那么信息不会进
入网络,除非缓冲区满(溢出),这会为本例带来许多问题。
编写网络应用程序时,需要特别注意自动刷新机制的使用。每次刷新缓冲区时,必须创建和发出一个数据包
(数据封)。就目前的情况来说,这正是我们所希望的,因为假如包内包含了还没有发出的文本行,服务器
和客户机之间的相互“握手”就会停止。换句话说,一行的末尾就是一条消息的末尾。但在其他许多情况
下,消息并不是用行分隔的,所以不如不用自动刷新机制,而用内建的缓冲区判决机制来决定何时发送一个
数据包。这样一来,我们可以发出较大的数据包,而且处理进程也能加快。
注意和我们打开的几乎所有数据流一样,它们都要进行缓冲处理。本章末尾有一个练习,清楚展现了假如我
们不对数据流进行缓冲,那么会得到什么样的后果(速度会变慢)。
无限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)上进行。现在,为了使数据能在客户及服务程序之间来回传送,每一端都需要知道把数据发到
哪里。所以在同一个“已知”服务程序连接的时候,客户会发出一个“返回地址”,使服务器程序知道将自