本文的理论和代码摘录于《疯狂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号端口肯定是没问题的,至于同一台机器上为什么没问题我就不得而知了,可能得要研究底层的网络协议去了。如果哪位大侠知道的话还望不吝指教。