Android Socket通信(二) --UDP,單播,廣播和多播(組播)

系列文章:
Android 使用socket.io 實現實時通信
Android Socket通信(二) --UDP,單播,廣播和多播(組播)
本系列代碼鏈接:https://github.com/LillteZheng/SocketDemo

在上章中,我們學習了 socket 的基本使用。但都是基於TCP的,這篇,我們來學習一些 Socket 的UDP 的操作,並瞭解多播和廣播的概念,爲接下來的局域網文件傳輸,打上一個很好的基礎。
通過這邊文章你將學習到:

  • 認識 UDP 的基本概念
  • 學習 UDP 最基礎的demo
  • 瞭解ip分組和廣播等概念
  • 學習 UDP 的廣播和多播實例

一、認識 UDP

與 TCP 不同,UDP 是一個面向數據包的傳輸層協議,進程的每一個輸出操作都正好產生一個UDP數據報,並組裝成一份待發送的IP數據報。格式如下:
在這裏插入圖片描述
IP數據報的最大長度爲 65535 字節 ,除去首字IP 的20 字節和 UDP首部8個字節,實際上,UDP 能傳輸的最大字節數爲 65507,個字節;當我們的數據超過這個長度時,則需要考慮分包的問題,這個問題,我們在後面的實戰中再去分析。

UDP 的傳輸是不可靠的,它只負責把數據傳輸出去,並不會去考慮接收端是否能接受到。在大多不需要考慮應答的應用中,我們會優先考慮 UDP,比如多人聊天;當然,我們還可以 UDP 與 TCP 一起輔助,利用各自的優點來實現特殊的功能,比如文件快傳。

二、DatagramSocket 和 DatagramPacket

socket 的 udp 的 api 並沒有包含在 socket api 中,而是通過 DatagramSocket 和 DatagramPacket 來實現的。
我們知道兩臺計算機的通信,無論是 TCP 還是 UDP ,都需要知道 IP 和 端口。同樣,在 UDP 的單播實例中,實現步驟也是一樣的,具體參考以下流程圖:
在這裏插入圖片描述

2.1 單播

單播即點對點的通信,這個也比較好理解,ip 和 端口都是確定的。
所以,UDP 的服務端代碼如下:

/**
 * created by zhengshaorui
 * time on 2019/6/19
 * upd 服務端,用來接收客戶端信息
 */
public class UdpServer {

    public static void main(String[] args) throws IOException {
        System.out.println("UDP 服務端已經啓動");
        //1.獲取 datagramSocket 實例,並監聽某個端口
        DatagramSocket socket = new DatagramSocket(Constans.PORT);
        //2.創建一個 udp 的數據包
        byte[] buf = new byte[512];
        DatagramPacket packet = new DatagramPacket(buf,buf.length);
        //3.開始阻塞獲取udp數據包
        socket.receive(packet);

        //拿到發送端的一些信息
        String ip = packet.getAddress().getHostAddress();
        int port = packet.getPort();
        int length = packet.getLength();

        String msg = new String(buf,0,length);
        System.out.println("客戶端: "+ip+"\tport: "+port+"\t信息: "+msg);

        /**
         * 給客戶端發送消息
         */
        byte[] receiveMsg = ("長度: "+msg.length()).getBytes();
        DatagramPacket receivePacket = new DatagramPacket(receiveMsg,
                receiveMsg.length,
                packet.getAddress(), //目標地址
                port);               //目標端口

        socket.send(receivePacket);
        //關閉資源
        socket.close();
        System.out.println("結束");
    }
}

客戶端代碼:

/**
 * created by zhengshaorui
 * time on 2019/6/19
 * udp 的客戶端
 */
public class UdpClient {
    public static void main(String[] args) throws IOException {
        System.out.println("UDP 客戶端已經啓動");
        //1.獲取 datagramSocket 實例,不創建端口,客戶端的端口由系統隨機分配
        DatagramSocket socket = new DatagramSocket();
        //2.創建一個 udp 的數據包
        byte[] buf = "hello world".getBytes();

        DatagramPacket packet = new DatagramPacket(buf,
                buf.length,
                InetAddress.getLocalHost(),
                Constans.PORT);
        //給服務端發送數據
        socket.send(packet);

        /**
         * 接收服務端消息
         */
        byte[] receiveMsg = new byte[512];
        DatagramPacket receivePacket = new DatagramPacket(receiveMsg, receiveMsg.length);
        //開始接收
        socket.receive(receivePacket);
        String address = packet.getAddress().getHostAddress();
        int port = packet.getPort();
        int length = packet.getLength();

        String msg = new String(receiveMsg,0,length);
        System.out.println("服務端: "+address+"\tport: "+port+"\t信息: "+msg);
        //關閉資源
        socket.close();
        System.out.println("結束");
    }
}

運行如下:
在這裏插入圖片描述
在這裏插入圖片描述

2.2、IP地址分類和IP構成

在學習廣播之前,我們先來看看網上對 ip 的分類的劃分:
在這裏插入圖片描述
可以看到,類別的不同,可以通過子網掩碼來區分。我們常用的則爲 C 端的ip地址。
然後來看看一些udp廣播多播的知識:

  1. 255.255.255.255 爲受限廣播,即所有網段都能收到,但路由並不會去轉發該廣播,畢竟所有網段都會接受,所以只有本局域網能夠接收到。
  2. x.x.x.255 爲 C 類廣播,只有該網段下的才能收到 ,比如 192.168.33.255,那麼 192.168.33.x 下的所有網段都能接收到。
  3. 多播廣播,這個爲多播預留的地址,後面在多播的時候,我們再去理解

接着繼續看 ip 地址 的構成,它是由 32 byte 組成:
在這裏插入圖片描述
上面看到了受限廣播地址,即 255.255.255.255 ,當使用這個地址作爲廣播地址時,路由器的其他設備都能監聽到,但如果 A 路由器和B路由器想要之間也能通信,則需要計算器出來的地址,比如:
A設備:ip爲192.168.134.7 ,子網掩碼爲 255.255.255.192
B設備:ip爲192.168.134.100 ,子網掩碼也是 255.255.255.192
看到 A 與 B 的子網掩碼是一樣,但其實還是不能通信,因爲 A與B 的通信地址不一樣,因爲A的地址爲 192.168.134.63 而,B 則爲 192.168.134.127 ,本來廣播地址不同,也不能通信,關於廣播地址,ip、子網和網絡地址等信息,可以查看:
http://network.51cto.com/art/200512/14357.htm

2.3 廣播

理解了上面的知識點,接着我們來看看實現一下廣播的代碼,大致思路如下,pc 端的 client 發送一個 255.255.255.255 的受限廣播,pc 端和 Android 接受之後,發送迴應給客戶端,客戶端在接受之後,打印相關信息,並可以按任意鍵退出,並打印出已經搜索到的設備信息.
受限是客戶端:
發送廣播

/**
     * 發送廣播,我們只需要把ip改成 255.255.255.255 即可
     */
    private static void sendBroadcast()  {
        try {
            System.out.println("開始發送廣播");
            //1.獲取 datagramSocket 實例,不創建端口,客戶端的端口由系統隨機分配
            DatagramSocket socket = new DatagramSocket();
            //2.創建一個 udp 的數據包
            byte[] buf = "hello world".getBytes();

            DatagramPacket packet = new DatagramPacket(buf,
                    buf.length,
                    //InetAddress.getByName("172.16.29.255"),
                    InetAddress.getByName("255.255.255.255"),
                    Constans.PORT);
            //給服務端發送數據
            socket.send(packet);
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println(e.toString());
        }
    }

接着,監聽服務端發送回來的信息:

/**
     * 監聽服務端發送回來的數據並打印出來
     */
    private static class ResposeListener extends Thread{
        private int port;
        private boolean isFinish = false;
        DatagramSocket socket;
        List<Device> devices = new ArrayList<>();
        public ResposeListener(int port) {
            this.port = port;
        }

        @Override
        public void run() {
            super.run();
            try {
                socket = new DatagramSocket(port);
                while(!isFinish) {
                    //監聽回送端口
                    byte[] buf = new byte[512];
                    DatagramPacket packet = new DatagramPacket(buf, buf.length);
                    //拿數據
                    socket.receive(packet);

                    //拿到發送端的一些信息
                    String ip = packet.getAddress().getHostAddress();
                    int port = packet.getPort();
                    int length = packet.getLength();

                    String msg = new String(buf, 0, length);
                    System.out.println("監聽到: " + ip + "\tport: " + port + "\t信息: " + msg);

                    if (msg.length() > 0) {
                        Device device = new Device(ip, port, msg);
                        devices.add(device);
                    }

                }

            }catch (Exception e){
            }finally {
                exit();
            }
        }

        public void exit(){
            if (socket != null){
                socket.close();
                socket = null;
            }
            isFinish = true;
        }

        public List<Device> getDevices(){
            exit();
            return devices;
        }
    }

接着寫服務端的:

static class Provider extends Thread{
        private String sn;
        private DatagramSocket socket ;
        private boolean isFinish = false;
        public Provider(String sn) {
            this.sn = sn;
        }

        @Override
        public void run() {
            super.run();

            System.out.println("UDP 服務端已經啓動");
            try {
                //1.獲取 datagramSocket 實例,並監聽某個端口
                socket = new DatagramSocket(Constans.PORT);
                while (!isFinish) {
                    //2.創建一個 udp 的數據包
                    byte[] buf = new byte[512];
                    DatagramPacket packet = new DatagramPacket(buf, buf.length);
                    //3.開始阻塞獲取udp數據包
                    socket.receive(packet);

                    //拿到發送端的一些信息
                    String ip = packet.getAddress().getHostAddress();
                    int port = packet.getPort();
                    int length = packet.getLength();

                    String msg = new String(buf, 0, length);
                    System.out.println("客戶端: " + ip + "\tport: " + port + "\t信息: " + msg);

                    /**
                     * 給客戶端發送消息
                     */
                    byte[] receiveMsg = "我是設備A".getBytes();
                    DatagramPacket receivePacket = new DatagramPacket(receiveMsg,
                            receiveMsg.length,
                            packet.getAddress(), //目標地址
                            Constans.BROADCAST_PORT);      //廣播端口

                    socket.send(receivePacket);
                }
                //關閉資源
                socket.close();
                System.out.println("結束");
            } catch (IOException e) {
              //  e.printStackTrace();
                //忽略錯誤
            }finally {
                exit();
            }
        }
        public void exit(){
            if (socket != null){
                socket.close();
                socket = null;
            }
            isFinish = true;
        }
    }

服務端也比較簡單,監聽即可。
接着看看Android端的,其實跟pc 的服務端一致,開發線程監聽廣播和發送即可,由於代碼差不多,就不貼出來,具體看工程代碼。
運行結果如下:
pc 服務端:
在這裏插入圖片描述
Android 服務端:
在這裏插入圖片描述
pc 客戶端:
在這裏插入圖片描述
這樣,我們的 廣播就寫好了。

2.4 多播(組播)

從上面我們已經知道,在 ip 的分配中,有專門的一組留給組播的ip,從這裏也可以看到,在效率上,組播比廣播要優越得多。
組播組由D類IP地址和標準UDP端口號指定。 D類IP地址範圍爲224.0.0.0至239.255.255.255 (含)。 地址224.0.0.0是保留的,不應該使用。
而組播使用的socket api 爲:MulticastSocket ,MulticastSocket 是 DatagramSocket 的子類,我們可以通過 joinGroup 來把某個地址加入到組播中;
當向多播組發送消息時,向該主機和端口發送的所有訂閱的收件人都將收到消息(在數據包的生存時間內,請參見下文)。 套接字不需要是組播組的成員來向其發送消息。
當一個套接字訂閱一個組播組/端口時,它接收其他主機發送到組/端口的數據報,組和端口的所有其他成員也同樣。 套接字通過leaveGroup(InetAddress addr)方法放棄組中的成員資格。 多個MulticastSocket可以同時訂閱組播組和端口,並且它們都將接收組數據報。

瞭解上面的知識後,我們做個試驗,服務端接受其他組播成員加入,並打印出來,客戶端則發送數據並接受服務端數據:
服務器

 group = InetAddress.getByName("224.5.6.7");
                if (!group.isMulticastAddress()){
                    throw new RuntimeException("please use multicast ip 224.0.0.0 to 239.255.255.255 ");
                }
                System.out.println("組播服務端啓動完成");
                socket = new MulticastSocket(Constans.PORT);
                //把組員加進來
                socket.joinGroup(group);

                while (!isFinish) {

                    //新建一個 package 來接受數據
                    byte[] bytes = new byte[512];
                    DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
                    socket.receive(packet);
                    String ip = packet.getAddress().getHostAddress();
                    int port = packet.getPort();
                    String msg = new String(bytes);
                    System.out.println("get client : " + ip + "\t port: " + port + "\tmsg: " + msg);
                    String receiveMsg = "hello "+msg;

                    byte[] buf = receiveMsg.getBytes();
                    DatagramPacket receivePacket = new DatagramPacket(buf,
                            buf.length,
                            packet.getAddress(),
                            port);
                    socket.send(receivePacket);
                }

客戶端

try {
            InetAddress group = InetAddress.getByName("224.5.6.7");
            if (!group.isMulticastAddress()){
                throw new RuntimeException("please use multicast ip 224.0.0.0 to 239.255.255.255 ");
            }
            System.out.println("客戶端A已啓動");
            //客戶端的端口由系統自行指定
            MulticastSocket socket = new MulticastSocket();

            byte[] buf = (Constans.SN_HEADER+"A").getBytes();
            DatagramPacket packet = new DatagramPacket(buf,
                    buf.length,
                    InetAddress.getLocalHost(),
                    Constans.PORT);
            socket.send(packet);

            byte[] bytes = new byte[512];
            DatagramPacket receivePacket = new DatagramPacket(bytes,bytes.length);
            socket.receive(receivePacket);
            String ip = receivePacket.getAddress().getHostAddress();
            int port = receivePacket.getPort();
            String msg = new String(bytes);
            System.out.println("get server : "+ip+"\t port: "+port+"\tmsg: "+msg);
            socket.close();

        } catch (Exception e) {
            e.printStackTrace();
        }

打印如下:
在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述

自此,我們的 udp 單播、廣播和組播就講完了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章