按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
们不需要在这个问题上纠缠不清。这一点在下例里将有很明确的说明。该例类似于前面针对 TCP 套接字的
MultiJabberServer 和MultiJabberClient 例子。多个客户都会将数据报发给服务器,后者会将其反馈回最
初发出消息的同样的客户。
为简化从一个String 里创建 DatagramPacket 的工作(或者从DatagramPacket 里创建 String),这个例子
首先用到了一个工具类,名为Dgram:
//: Dgram。java
// A utility class to convert back and forth
// Between Strings and DataGramPackets。
import java。*;
public class Dgram {
public static DatagramPacket toDatagram(
String s; InetAddress destIA; int destPort) {
// Deprecated in Java 1。1; but it works:
byte'' buf = new byte's。length() + 1';
s。getBytes(0; s。length(); buf; 0);
// The correct Java 1。1 approach; but it's
// Broken (it truncates the String):
// byte'' buf = s。getBytes();
return new DatagramPacket(buf; buf。length;
destIA; destPort);
}
public static String toString(DatagramPacket p){
// The Java 1。0 approach:
// return new String(p。getData();
// 0; 0; p。getLength());
// The Java 1。1 approach:
return
new String(p。getData(); 0; p。getLength());
}
} ///:~
Dgram 的第一个方法采用一个String、一个 InetAddress 以及一个端口号作为自己的参数,将String 的内容
复制到一个字节缓冲区,再将缓冲区传递进入 DatagramPacket 构建器,从而构建一个 DatagramPacket。注
意缓冲区分配时的〃+1〃——这对防止截尾现象是非常重要的。String 的getByte()方法属于一种特殊操作,
能将一个字串包含的char 复制进入一个字节缓冲。该方法现在已被“反对”使用;Java 1。1 有一个“更
好”的办法来做这个工作,但在这里却被当作注释屏蔽掉了,因为它会截掉String 的部分内容。所以尽管我
们在Java 1。1 下编译该程序时会得到一条“反对”消息,但它的行为仍然是正确无误的(这个错误应该在你
读到这里的时候修正了)。
Dgram。toString()方法同时展示了Java 1。0 的方法和Java 1。1 的方法(两者是不同的,因为有一种新类型
的String 构建器)。
下面是用于数据报演示的服务器代码:
//: ChatterServer。java
// A server that echoes datagrams
import java。*;
import java。io。*;
import java。util。*;
548
…………………………………………………………Page 550……………………………………………………………
public class ChatterServer {
static final int INPORT = 1711;
private byte'' buf = new byte'1000';
private DatagramPacket dp =
new DatagramPacket(buf; buf。length);
// Can listen & send on the same socket:
private DatagramSocket socket;
public ChatterServer() {
try {
socket = new DatagramSocket(INPORT);
System。out。println(〃Server started〃);
while(true) {
// Block until a datagram appears:
socket。receive(dp);
String rcvd = Dgram。toString(dp) +
〃; from address: 〃 + dp。getAddress() +
〃; port: 〃 + dp。getPort();
System。out。println(rcvd);
String echoString =
〃Echoed: 〃 + rcvd;
// Extract the address and port from the
// received datagram to find out where to
// send it back:
DatagramPacket echo =
Dgram。toDatagram(echoString;
dp。getAddress(); dp。getPort());
socket。send(echo);
}
} catch(SocketException e) {
System。err。println(〃Can't open socket〃);
System。exit(1);
} catch(IOException e) {
System。err。println(〃munication error〃);
e。printStackTrace();
}
}
public static void main(String'' args) {
new ChatterServer();
}
} ///:~
ChatterServer 创建了一个用来接收消息的DatagramSocket (数据报套接字),而不是在我们每次准备接收
一条新消息时都新建一个。这个单一的DatagramSocket 可以重复使用。它有一个端口号,因为这属于服务
器,客户必须确切知道自己把数据报发到哪个地址。尽管有一个端口号,但没有为它分配因特网地址,因为
它就驻留在“这”台机器内,所以知道自己的因特网地址是什么(目前是默认的 localhost)。在无限while
循环中,套接字被告知接收数据(receive())。然后暂时挂起,直到一个数据报出现,再把它反馈回我们希
望的接收人——DatagramPacket dp——里面。数据包(Packet)会被转换成一个字串,同时插入的还有数据
包的起源因特网地址及套接字。这些信息会显示出来,然后添加一个额外的字串,指出自己已从服务器反馈
回来了。
大家可能会觉得有点儿迷惑。正如大家会看到的那样,许多不同的因特网地址和端口号都可能是消息的起源
549
…………………………………………………………Page 551……………………………………………………………
地——换言之,客户程序可能驻留在任何一台机器里(就这一次演示来说,它们都驻留在 localhost 里,但
每个客户使用的端口编号是不同的)。为了将一条消息送回它真正的始发客户,需要知道那个客户的因特网
地址以及端口号。幸运的是,所有这些资料均已非常周到地封装到发出消息的DatagramPacket 内部,所以我
们要做的全部事情就是用getAddress()和getPort()把它们取出来。利用这些资料,可以构建
DatagramPacket echo——它通过与接收用的相同的套接字发送回来。除此以外,一旦套接字发出数据报,就
会添加“这”台机器的因特网地址及端口信息,所以当客户接收消息时,它可以利用 getAddress()和
getPort()了解数据报来自何处。事实上,getAddress()和 getPort()唯一不能告诉我们数据报来自何处的前
提是:我们创建一个待发送的数据报,并在正式发出之前调用了getAddress()和getPort()。到数据报正式
发送的时候,这台机器的地址以及端口才会写入数据报。所以我们得到了运用数据报时一项重要的原则:不
必跟踪一条消息的来源地!因为它肯定保存在数据报里。事实上,对程序来说,最可靠的做法是我们不要试
图跟踪,而是无论如何都从目标数据报里提取出地址以及端口信息(就象这里做的那样)。
为测试服务器的运转是否正常,下面这程序将创建大量客户(线程),它们都会将数据报包发给服务器,并
等候服务器把它们原样反馈回来。
//: ChatterClient。java
// Tests the ChatterServer by starting multiple
// clients; each of which sends datagrams。
import java。lang。Thread;
import java。*;
import java。io。