Google Code Prettify

2015年9月8日 星期二

NIO.2: UDP 網路程式設計 (multicast)

同樣是採用 UDP 通訊協定,multicast 和前一篇 (UDP 網路程式設計 (request / response)) 點對點的傳播方式有什麼不同?
  1. multicast 是多點傳播,在 IPv4 時有廣播和群播,到了 IPv6 只有群播。群播即事先加入群組的消費者,就可以收到生產者廣播出來的資料。
  2. multicast 的參數中有個 TTL 值,這個值介於 1~255 之間,當封包每通過一個 router,就會被減 1 (有些 router 會減 2 或更多),當值為 0 時即停止傳播。
UDP 雖然是不保證送達的通訊協定,但是少了確認的封包,速度比 TCP 快約 3 倍,當一個生產者要同時送給非常多消費者時,TCP 很快就會拖垮伺服器的效能,UDP 則可以在消費者判斷漏收了資料時,才要求生產者重送,大量減少網路流量,又能保證送達,當然,程式會複雜許多。

在 NIO.2 中,UDP 程式都是使用 DatagramChannel 這個類別,回顧前一篇的類別圖會發現,點對點傳播時沒有用到 MulticastChannel 介面所定義的 method,從名稱就知道,這是給 multicast 使用的。

在開始說明 multicast 程式前,請先執行如下 oracle 附的程式:
import java.io.*;
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 }

沒有留言:

張貼留言