[疯狂Java]UDP:MulticastSocket实现组播

1. 组播——多点广播:

    1) 可以看到如果使用DatagramSocket很那实现广播的功能,至少需要用一个Set集合来保存用户发来的数据报(或是取出其中的字段加以保存),但是有一个严重的问题是UDP并不记录客户端的状态,上一秒发送了数据报下一秒也许客户端就关闭socket了,这些事情服务器端无法知晓(因为UDP是无连接),如果是TCP一旦客户端断开了连接那么服务器端的socket就会抛出异常(基于连接的);

    2) IP协议对组播的支持:

         i. 组播就是多点广播的俗称,即一群机器组成一个组,里面任意一个人发送的消息可以同时广播到其它所有机器上(当然也可以包括自己,如果自己也收到则叫做回环);

         ii. 之前我们用TCP编写过一个群聊的应用,其实也是一种另类的组播,那时我们将所有客户端的信息都保存在服务器上,广播和私聊都是通过服务器实现的;

         iii. 但是这种在程序中我们自己保存客户端信息的方式非常繁琐,有没有方法允许不记录组内机器的信息而自动实现广播呢?答案是肯定的,IP协议本身就支持这种功能;

         iv. IP地址支持一种特殊的IP地址,叫做多点广播地址,简称广播IP,想要参与组播的所有机器可以“加入”这个地址,任意一台机器只要向该地址发送信息,那么该信息就会自动广播给所有“加入”到该地址的机器上,也就是说这个神秘的广播IP地址仿佛是一个虚拟的服务器,它履行着我们之前TCP群聊的服务器的功能;

         v. 只不过IP协议底层确实是用服务器的方法实现广播IP的功能的;


2. 那么在Java中如何调用IP协议的多点广播功能呢?

    1) 首先IP协议规定多点广播地址的范围是224.0.0.0 ~ 239.255.255.255,协议软件底层设定好的,如果超出这个返回就会抛出异常;

    2) 其次是参与组播的所有主机的socket必须是MulticastSocket类的对象,它是DatagramSocket的子类,因此组播socket也是UDP协议的一种!!

    3) 也就是说单点(一对一)UDP通信就用DatagramSocket,组播群体通信就得使用MulticastSocket,如果程序中同时需要这两种通信方式,那么就得同时用这两种socket,但这两种socket不能共享一个端口,必须分别指定,否则会抛出异常;

!!这是IP协议的规定,组播端口和单点端口必须不同!!

!!这是因为单点和组播无法在同一个端口上监听(receive)!!

    4) MulticastSocket的构造器和DatagramSocket以及其它所有Socket大同小异,无非都是绑定本机的IP和端口罢了;

    5) 要想加入组播就必须先让自己的socket“加入”到目标广播IP中,直接调用MulticastSocket对象的joinGroup方法加入到该IP地址中即可(该方法的参数就是广播IP地址);

    6) 接下来的发送和接受信息就和DatagramSocket一模一样,都是定义一下DatagramPacket,其余完全一样,只不过发送的消息会被广播到组内所有机器,接受的消息时其它机器的广播消息罢了;

!!发送数据报时的目的地端口和MulticastSocket绑定的端口一样!!其实在IP协议底层,MulticastSocket(成员socket)既是客户端也是服务器端(广播IP);


3. 具体方法:

    1) MulticastSocket构造器:

         i. MulticastSocket(); // 使用默认IP和端口绑定

         ii. MulticastSocket(int port); // 使用默认IP和指定端口

         iii. MulticastSocket(SocketAddress addr); // 同时指定

     2) 加入和退出一个组播IP:

         i. void joinGroup(InetAddress mcastaddr);

         ii. void leaveGroup(InetAddress mcastaddr); // 可以看到很繁琐,退出时还要指定组播IP而不能自动(无参),可以看到所有UDP协议的方法还是显得非常底层

     3) 关闭回环开启广播功能:接下来一定要这样调用才能开启广播功能,只要记住这点就行了!

         !!sock.setLoopbackMode(false);!!

     4) 剩下的发送接收数据报就和DatagramSocket一模一样了,此时只要将MulticastSocket完全当成DatagramSocket使用就行了;


4. 示例:

public class Test {

	private final static String BC_IP = "230.0.0.1"; // 组播地址
	private final static int BC_PORT = 30000; // 组播端口
	private final static int PACK_SIZE = 4096;

	public void init() throws IOException {
		MulticastSocket sock = new MulticastSocket(BC_PORT);
		InetAddress bcAddr = InetAddress.getByName(BC_IP);
		try (Scanner scan = new Scanner(System.in)) {
			// 创建socket并加入组播地址
			sock.joinGroup(bcAddr);
			sock.setLoopbackMode(false); // 必须是false才能开启广播功能!!
			
			new Thread(() -> { // 接受广播消息的线程
				try {
					DatagramPacket inpack = new DatagramPacket(new byte[PACK_SIZE], PACK_SIZE);
					while (true) {
						sock.receive(inpack);
						System.out.println("广播消息:" + new String(inpack.getData(), 0, inpack.getLength()));
					}
				} catch (IOException e) {
					e.printStackTrace();
					if (sock != null) {
						try {
							sock.leaveGroup(bcAddr);
						} catch (Exception e1) {
							// TODO Auto-generated catch block
							e1.printStackTrace();
						}
						sock.close();
					}
					System.exit(1);
				}
			}).start();
			
			// 主线程接受控制台输入并广播出去
			DatagramPacket outpack = new DatagramPacket(new byte[0], 0, bcAddr, BC_PORT); // 目的端口和MulticastSocket端口一样!!
			while (scan.hasNextLine()) {
				byte[] buf = scan.nextLine().getBytes();
				outpack.setData(buf);
				sock.send(outpack);
			}
		}
		finally { // 最终关闭程序之前一定要关闭socket
			sock.close();
		}
	}

	public static void main(String[] args) throws NumberFormatException, IOException {
		// TODO Auto-generated method stub
		new Test().init();
	}

}
!UDP没有C/S一说,因此该程序即使客户端也是服务器端,多开几个就能相互通信了;

你可能感兴趣的:(UDP,疯狂Java,multicastsocket,组播)