- multicast 是多點傳播,在 IPv4 時有廣播和群播,到了 IPv6 只有群播。群播即事先加入群組的消費者,就可以收到生產者廣播出來的資料。
- multicast 的參數中有個 TTL 值,這個值介於 1~255 之間,當封包每通過一個 router,就會被減 1 (有些 router 會減 2 或更多),當值為 0 時即停止傳播。
在 NIO.2 中,UDP 程式都是使用 DatagramChannel 這個類別,回顧前一篇的類別圖會發現,點對點傳播時沒有用到 MulticastChannel 介面所定義的 method,從名稱就知道,這是給 multicast 使用的。
在開始說明 multicast 程式前,請先執行如下 oracle 附的程式:
import java.net.*; import java.util.*; import static java.lang.System.out; public class ListNets { public static void main(String args[]) throws SocketException { Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces(); for (NetworkInterface netint : Collections.list(nets)) displayInterfaceInformation(netint); } static void displayInterfaceInformation(NetworkInterface netint) throws SocketException { out.printf("Display name: %s\n", netint.getDisplayName()); out.printf("Name: %s\n", netint.getName()); Enumeration<InetAddress> inetAddresses = netint.getInetAddresses(); for (InetAddress inetAddress : Collections.list(inetAddresses)) { out.printf("InetAddress: %s\n", inetAddress); } out.printf("\n"); } }這個程式會列出電腦上所有的網卡及其名稱,這個非常重要,因為 multicast 程式需指明要從那一張網卡收、送資料,接下來看看程式,底下的程式 server 會送出 10 次的系統時間,client 收到後將它輸出到 console,先看輸出結果,應該會類似如下:
- Server
1 package idv.steven.udp; 2 3 import java.io.IOException; 4 import java.net.InetAddress; 5 import java.net.InetSocketAddress; 6 import java.net.NetworkInterface; 7 import java.net.StandardProtocolFamily; 8 import java.net.StandardSocketOptions; 9 import java.nio.ByteBuffer; 10 import java.nio.channels.DatagramChannel; 11 import java.util.Date; 12 13 public class MulticastServer { 14 public static void main(String[] args) { 15 final int DEFAULT_PORT = 5555; 16 final String GROUP = "225.5.6.6"; 17 ByteBuffer datetime; 18 19 // 開啟一個 IPv4 的資料封包通道 20 try (DatagramChannel datagramChannel = DatagramChannel.open(StandardProtocolFamily.INET)) { 21 // 檢查是否成功開啟 22 if (datagramChannel.isOpen()) { 23 // 取得名稱為 eth3 的網卡 24 NetworkInterface networkInterface = NetworkInterface.getByName("eth3"); 25 // 設定 multicast 要使用的網卡 26 datagramChannel.setOption(StandardSocketOptions.IP_MULTICAST_IF, networkInterface); 27 datagramChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true); 28 // 將 DatagramChannel 繫結到指定的埠號,要注意,這時還不需要指定 IP 群組。 29 datagramChannel.bind(new InetSocketAddress(DEFAULT_PORT)); 30 31 for(int i=0; i<10; i++) { 32 // 每 10 秒送出一筆資料 33 try { 34 Thread.sleep(10000); // 睡 10 秒 35 } catch (InterruptedException ex) {} 36 37 datetime = ByteBuffer.wrap(new Date().toString().getBytes()); 38 // 送出時才指定要送到那個 IP 群組,這表示 bind 後, 39 // 還是可以看資料特性,送到不同 IP 群組。 40 datagramChannel.send(datetime, new 41 InetSocketAddress(InetAddress.getByName(GROUP), DEFAULT_PORT)); 42 datetime.flip(); 43 } 44 } else { 45 System.out.println("通道開啟失敗"); 46 } 47 } catch (IOException ex) { 48 ex.printStackTrace(); 49 } 50 } 51 }
- Client
1 package idv.steven.udp; 2 3 import java.io.IOException; 4 import java.net.InetAddress; 5 import java.net.InetSocketAddress; 6 import java.net.NetworkInterface; 7 import java.net.StandardProtocolFamily; 8 import java.net.StandardSocketOptions; 9 import java.nio.ByteBuffer; 10 import java.nio.CharBuffer; 11 import java.nio.channels.DatagramChannel; 12 import java.nio.channels.MembershipKey; 13 import java.nio.charset.Charset; 14 import java.nio.charset.CharsetDecoder; 15 16 public class MulticastClient { 17 18 public static void main(String[] args) { 19 final int DEFAULT_PORT = 5555; 20 final int MAX_PACKET_SIZE = 65507; 21 final String GROUP = "225.5.6.6"; 22 23 CharBuffer charBuffer = null; 24 Charset charset = Charset.defaultCharset(); 25 CharsetDecoder decoder = charset.newDecoder(); 26 ByteBuffer datetime = ByteBuffer.allocateDirect(MAX_PACKET_SIZE); 27 28 try (DatagramChannel datagramChannel = DatagramChannel.open(StandardProtocolFamily.INET)) { 29 // 要傾聽的 IP 群組 30 InetAddress group = InetAddress.getByName(GROUP); 31 // 檢查是否為合法的多址傳播群組 32 if (group.isMulticastAddress()) { 33 // 檢查是否成功開啟通道 34 if (datagramChannel.isOpen()) { 35 // 設定要傾聽的網卡 36 NetworkInterface networkInterface = NetworkInterface.getByName("eth3"); 37 datagramChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true); 38 // 將 DatagramChannel 繫結到指定的埠號,要注意,這時還不需要指定 IP 群組。 39 datagramChannel.bind(new InetSocketAddress(DEFAULT_PORT)); 40 // 加入要傾聽的群組 41 MembershipKey key = datagramChannel.join(group, networkInterface); 42 43 while (true) { 44 if (key.isValid()) { 45 // 開啟等待 server 端送來的封包 46 datagramChannel.receive(datetime); 47 datetime.flip(); 48 charBuffer = decoder.decode(datetime); 49 System.out.println(charBuffer.toString()); 50 datetime.clear(); 51 } else { 52 break; 53 } 54 } 55 } else { 56 System.out.println("通道無法開啟"); 57 } 58 } else { 59 System.out.println("這個 IP 不是合法的多址傳播 IP"); 60 } 61 } catch (IOException ex) { 62 ex.printStackTrace(); 63 } 64 } 65 }