本文的理论和代码摘录于《疯狂Java讲义》[url]http://book.51cto.com/art/201203/322560.htm[/url],测试部分的截图是本人增加的。
1.理论
DatagramSocket只允许数据报发送给指定的目标地址,而MulticastSocket可以将数据报以广播方式发送到多个客户端。
若要使用多点广播,则需要让一个数据报标有一组目标主机地址,当数据报发出后,整个组的所有主机都能收到该数据报。IP多点广播实现了将单一信息发送到多个接收者的广播,其思想是设置一组特殊网络地址作为多点广播地址,每一个多点广播地址都被看做一个组,当客户端需要发送、接收广播信息时,加入到该组即可。
多播组通过 D 类 IP 地址和标准 UDP 端口号指定。D 类 IP 地址在 224.0.0.0 和 239.255.255.255 的范围内(包括两者)。地址 224.0.0.0 被保留,不应使用。多点广播示意图如图所示。
[img]http://dl2.iteye.com/upload/attachment/0096/0431/9886b0e7-8adf-3f64-9118-970ddac1b2db.jpg[/img]
MulticastSocket是DatagramSocket的一个子类,当要发送一个数据报时,可以使用随机端口创建MulticastSocket,也可以在指定端口创建MulticastSocket。
如果创建仅用于发送数据报的MulticastSocket对象,则使用默认地址、随机端口即可。但如果创建接收用的MulticastSocket对象,则该MulticastSocket对象必须具有指定端口,否则发送方无法确定发送数据报的目标端口。
MulticastSocket比DatagramSocket多了一个setTimeToLive(int ttl)方法,该ttl参数用于设置数据报最多可以跨过多少个网络。
当ttl的值为0时,指定数据报应停留在本地主机;
当ttl的值为1时,指定数据报发送到本地局域网;
当ttl的值为32时,意味着只能发送到本站点的网络上;
当ttl的值为64时,意味着数据报应保留在本地区;
当ttl的值为128时,意味着数据报应保留在本大洲;
当ttl的值为255时,意味着数据报可发送到所有地方;
在默认情况下,该ttl的值为1。
2.编码
下面程序使用MulticastSocket实现了一个基于广播的多人聊天室。程序只需要一个MulticastSocket,两个线程,其中MulticastSocket既用于发送,也用于接收;一个线程负责接收用户键盘输入,并向Multicast Socket发送数据,另一个线程则负责从MulticastSocket中读取数据。
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.util.Scanner;
public class MulticastSocketTest implements Runnable {
// 使用常量作为本程序的多点广播IP地址
private static final String BROADCAST_IP = "230.0.0.1";
// 使用常量作为本程序的多点广播目的地端口
public static final int BROADCAST_PORT = 30000;
// 定义每个数据报的最大大小为4KB
private static final int DATA_LEN = 4096;
// 定义本程序的MulticastSocket实例
private MulticastSocket socket = null;
private InetAddress broadcastAddress = null;
// 定义接收网络数据的字节数组
byte[] inBuff = new byte[DATA_LEN];
// 以指定字节数组创建准备接收数据的DatagramPacket对象
private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length);
// 定义一个用于发送的DatagramPacket对象
private DatagramPacket outPacket = null;
public void init() throws IOException {
try {
// 创建键盘输入流
Scanner scan = new Scanner(System.in);
// 创建用于发送、接收数据的MulticastSocket对象
// 由于该MulticastSocket对象需要接收数据,所以有指定端口
socket = new MulticastSocket(BROADCAST_PORT);
broadcastAddress = InetAddress.getByName(BROADCAST_IP);
// 将该socket加入指定的多点广播地址
socket.joinGroup(broadcastAddress);
// 设置本MulticastSocket发送的数据报会被回送到自身
socket.setLoopbackMode(false);
// 初始化发送用的DatagramSocket,它包含一个长度为0的字节数组
outPacket = new DatagramPacket(new byte[0], 0, broadcastAddress,
BROADCAST_PORT);
// 启动以本实例的run()方法作为线程执行体的线程
new Thread(this).start();
// 不断地读取键盘输入
while (scan.hasNextLine()) {
// 将键盘输入的一行字符串转换成字节数组
byte[] buff = scan.nextLine().getBytes();
// 设置发送用的DatagramPacket里的字节数据
outPacket.setData(buff);
// 发送数据报
socket.send(outPacket);
}
} finally {
socket.close();
}
}
public void run() {
try {
while (true) {
// 读取Socket中的数据,读到的数据放在inPacket所封装的字节数组里
socket.receive(inPacket);
// 打印输出从socket中读取的内容
System.out.println("聊天信息:"
+ new String(inBuff, 0, inPacket.getLength()));
}
}
// 捕获异常
catch (IOException ex) {
ex.printStackTrace();
try {
if (socket != null) {
// 让该Socket离开该多点IP广播地址
socket.leaveGroup(broadcastAddress);
// 关闭该Socket对象
socket.close();
}
System.exit(1);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
new MulticastSocketTest().init();
}
}
3.测试
下面我们测试一下,打开3个MulticastSocketTest。随便哪个Client发条消息,所有Client包括自己都可以收到这条消息。
用Process Explorer监控一下端口可以发现,每个程序都开启了30000端口。
[img]http://dl2.iteye.com/upload/attachment/0096/0407/24de0985-13ba-3d81-8f49-fe80eaa8ae5f.png[/img]
[img]http://dl2.iteye.com/upload/attachment/0096/0405/28cb3771-fde4-3d72-b031-eeb4cb593b80.png[/img]
这点有点意外,因为我们发现同时开2个UdpServer是会报错的,说端口已经被绑定。而MulticastSocket则不会报错。我们也可以换个角度思考问题,一个数据报被要求发送到多播地址的30000号端口,这样的话其他成员要接收到这个数据的话,只有去30000号端口去拿了,其他端口是拿不到的。局域网中如果是多台计算机的话,每台各自开启一个30000号端口肯定是没问题的,至于同一台机器上为什么没问题我就不得而知了,可能得要研究底层的网络协议去了。如果哪位大侠知道的话还望不吝指教。