Android Socket通信(二) --UDP,单播,广播和多播(组播)

系列文章:
Android Socket 系列更新计划
Android Socket通信(一) – 初识与相遇
Android Socket通信(二) --UDP,单播,广播和多播(组播)
Android Socket通信(三) – TCP 配置和传递基础数据
Android Socket通信(四) – UDP与TCP结合传输数据
Android Socket通信(五) – 实现一个多人聊天室

工程连接 : 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 单播、广播和组播就讲完了。

  • 12
    点赞
  • 70
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值