按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
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()。在这里,消息发送给服务器,而
来自服务器的信息则在屏幕上回显出来。然而,线程的“存在时间”是有限的,最终都会结束。注意在套接
字创建好以后,但在构建器完成之前,假若构建器失败,套接字会被清除。否则,为套接字调用 close()的
责任便落到了run()方法的头上。
threadcount跟踪计算目前存在的 JabberClientThread 对象的数量。它将作为构建器的一部分增值,并在
run()退出时减值(run()退出意味着线程中止)。在MultiJabberClient。main()中,大家可以看到线程的数
量会得到检查。若数量太多,则多余的暂时不创建。方法随后进入“休眠”状态。这样一来,一旦部分线程
最后被中止,多作的那些线程就可以创建了。大家可试验一下逐渐增大MAX_THREADS,看看对于你使用的系
统来说,建立多少线程(连接)才会使您的系统资源降低到危险程度。
15。4 数据报
大家迄今看到的例子使用的都是“传输控制协议”(TCP),亦称作“基于数据流的套接字”。根据该协议的
设计宗旨,它具有高度的可靠性,而且能保证数据顺利抵达目的地。换言之,它允许重传那些由于各种原因
半路“走失”的数据。而且收到字节的顺序与它们发出来时是一样的。当然,这种控制与可靠性需要我们付
出一些代价:TCP 具有非常高的开销。
还有另一种协议,名为“用户数据报协议”(UDP),它并不刻意追求数据包会完全发送出去,也不能担保它
们抵达的顺序与它们发出时一样。我们认为这是一种“不可靠协议”(TCP 当然是“可靠协议”)。听起来
似乎很糟,但由于它的速度快得多,所以经常还是有用武之地的。对某些应用来说,比如声音信号的传输,
如果少量数据包在半路上丢失了,那么用不着太在意,因为传输的速度显得更重要一些。大多数互联网游
戏,如Diablo,采用的也是UDP 协议通信,因为网络通信的快慢是游戏是否流畅的决定性因素。也可以想想
一台报时服务器,如果某条消息丢失了,那么也真的不必过份紧张。另外,有些应用也许能向服务器传回一
条UDP 消息,以便以后能够恢复。如果在适当的时间里没有响应,消息就会丢失。
Java 对数据报的支持与它对 TCP 套接字的支持大致相同,但也存在一个明显的区别。对数据报来说,我们在
客户和服务器程序都可以放置一个 DatagramSocket (数据报套接字),但与ServerSocket 不同,前者不会
干巴巴地等待建立一个连接的请求。这是由于不再存在“连接”,取而代之的是一个数据报陈列出来。另一
项本质的区别的是对TCP 套接字来说,一旦我们建好了连接,便不再需要关心谁向谁“说话”——只需通过
会话流来回传送数据即可。但对数据报来说,它的数据包必须知道自己来自何处,以及打算去哪里。这意味
着我们必须知道每个数据报包的这些信息,否则信息就不能正常地传递。
DatagramSocket 用于收发数据包,而DatagramPacket 包含了具体的信息。准备接收一个数据报时,只需提
供一个缓冲区,以便安置接收到的数据。数据包抵达时,通过 DatagramSocket,作为信息起源地的因特网地
址以及端口编号会自动得到初化。所以一个用于接收数据报的 DatagramPacket 构建器是:
DatagramPacket(buf; buf。length)
其中,buf 是一个字节数组。既然 buf 是个数组,大家可能会奇怪为什么构建器自己不能调查出数组的长度
呢?实际上我也有同感,唯一能猜到的原因就是C 风格的编程使然,那里的数组不能自己告诉我们它有多
大。
可以重复使用数据报的接收代码,不必每次都建一个新的。每次用它的时候(再生),缓冲区内的数据都会
被覆盖。
缓冲区的最大容量仅受限于允许的数据报包大小,这个限制位于比64KB 稍小的地方。但在许多应用程序中,
我们都宁愿它变得还要小一些,特别是在发送数据的时候。具体选择的数据包大小取决于应用程序的特定要
求。
发出一个数据报时,DatagramPacket 不仅需要包含正式的数据,也要包含因特网地址以及端口号,以决定它
的目的地。所以用于输出DatagramPacket 的构建器是:
DatagramPacket(buf; length; inetAddress; port)
这一次,buf (一个字节数组)已经包含了我们想发出的数据。length可以是 buf 的长度,但也可以更短一
些,意味着我们只想发出那么多的字节。另两个参数分别代表数据包要到达的因特网地址以及目标机器的一
个目标端口(注释②)。
②:我们认为TCP 和 UDP 端口是相互独立的。也就是说,可以在端口8080 同时运行一个TCP 和 UDP 服务程
序,两者之间不会产生冲突。
547
…………………………………………………………Page 549……………………………………………………………
大家也许认为两个构建器创建了两个不同的对象:一个用于接收数据报,另一个用于发送它们。如果是好的
面向对象的设计方案,会建议把它们创建成两个不同的类,而不是具有不同的行为的一个类(具体行为取决
于我们如何构建对象)。这也许会成为一个严重的问题,但幸运的是,DatagramPacket 的使用相当简单,我
们不需要在这个问题上纠缠不清。这一点在下例里将有很明确的说明。