美文网首页网络编程
Socket进阶4之初识UDP

Socket进阶4之初识UDP

作者: 香沙小熊 | 来源:发表于2019-12-14 15:30 被阅读0次

    UDP是什么

    英语:User Datagram Protocol,缩写为UDP
    一种用户数据报协议,又称用户数据报文协议
    是一个简单的面向数据报的传输层协议,正式规范为RFC 768
    用户数据协议、非连接协议

    UDP不可靠

    • 它一旦把应用程序发给网络层的数据发送出去,就不留数据备份
    • UDP在IP数据报的头部仅仅加入了复用和数据校验(字段)
    • 发送端生产数据,接收端从网络中抓取数据
    • 结构简单、无校验、速度快、容易丢包、可广播

    UDP能做什么

    • DNS、TETP、SNMP
    • 视频、音频、普通数据(无关紧要数据)

    UDP包最大长度

    • 16位->2字节 存储长度信息
    • 2^16-1 = 64K-1 =65536 - 1 = 65535
    • 自身协议占用:32 + 32位 = 64位 = 8字节
    • udp包头占8字节, ip包头占20字节, 65535-28 = 65507

    API-DatagramSocket

    • 用于接收与发送UDP类
    • 负责发送某一个UDP包,或者接收UDP包
    • 不同于TCP,UDP并没有合并到Socket API中
    DatagramSocket()创建简单实例,不指定端口与IP
    DatagramSocket(int port)创建监听固定端口的实例
    DatagramSocket(int port,InetAddress localAddr)创建固定端口指定IP的实例
    receive(DatagramPacket d):接收
    send(DatagramPacket d):发送
    setSoTimeout(int timeout):设置超时,毫秒
    close() : 关闭、释放资源
    

    API-DatagramPacket

    • 用于处理报文
    • 将byte数组、目标地址、目标端口等数据包装成报文或者将报文拆卸成byte数组
     DatagramPacket(byte[] buf,int offset,int length,InetAddress address,int port)
    前面3个参数指定buf的使用区间
    后面2个参数指定目标机器地址与端口
    
     DatagramPacket(byte[] buf,int offset,int length,SocketAddress address)
    前面3个参数指定buf的使用区间
    SocketAddress相当于netAddress+Port
    
    setData(byte[] buf,int offset,int length)
    setData(byte[] buf)
    setLength(int length)
    getData()、getOffset()、getLength()
    setAddress(InetAddress iaddr)、setPort(int iport)
    getAddress()、getPort()
    setSocketAddress(SocketAddress address)
    getSocketAddress()
    

    UDP单播、广播、多播

    单播

    (1)简介
    两个节点之间的通信,一个发送者一个接收者
    (2)特点
    1、服务器及时响应客户机的请求。
    2、服务器针对每个客户不通的请求发送不通的数据,容易实现个性化服务
    3、允许在Internet宽带网上传输
    (3)应用
    你在收发电子邮件、浏览网页时,必须与邮件服务器、Web服务器建立连接
    (4)编程实现方式
    发送者指定接收者的地址(host和port)发送信息

    广播

    (1)简介
    一对所有,只能在子网中传播(在同一个路由器中传播),子网上的所有节点都能收到信息
    (2)特点
    1、网络设备简单,维护简单,布网成本低廉。
    2、由于服务器不用向每个客户机单独发送数据,所以服务器流量负载极低。
    3、不允许在Internet宽带网上传输
    (3)应用
    客户机通过DHCP自动获得IP地址的过程就是通过广播来实现的
    (4)广播风暴
    同一个子网内多点同时发送广播,会将带宽占满,造成网络拥塞。
    解决:不能根本解决,可以通过划分子网的方式,将广播限定在一定范围内,起到隔绝广播的目的。
    (5)编程实现方式
    255.255.255.255是广播地址,发送者指定广播地址发送信息,就是向整个子网发送广播,子网内的节点都会受到广播

    多播(组播)

    (1)简介
    一对多,一个发送者对多个接收
    (2)特点
    1、组播解决了单播和广播方式效率低的问题,它提高了数据传送效率,减少了骨干网络出现拥塞的可能性。
    2、允许在Internet宽带网上传输
    (3)应用
    网上视频会议、网上视频点播
    (4)编程实现方式
    多播IP地址就是D类IP地址。即224.0.0.0至239.255.255.255之间的IP地址。
    224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用。
    224.0.1.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效。
    239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。
    发送者和接受者都添加一组多播地址

    UDP局域网搜索实例-广播

    public class UDPProvider {
        public static void main(String[] args) throws IOException {
            System.out.println(DateUtil.getCurrentDateTime());
            System.out.println("UDPProvider Started.");
    
            //作为接受者,指定一个端口用于数据接受
            DatagramSocket ds = new DatagramSocket(UDP_PORT);
    
            //构建接受实体
            final byte[] buf = new byte[512];
            DatagramPacket receivePack = new DatagramPacket(buf,buf.length);
    
            //接收
            ds.receive(receivePack);
    
            //打印接收到的信息与发送者的信息
            //发送者的IP地址
            String ip = receivePack.getAddress().getHostAddress();
            /**
             * 会获得搜索方随机分配的端口
             */
            int port = receivePack.getPort();
            int dataLen = receivePack.getLength();
            String data = new String(receivePack.getData(),0,dataLen);
            System.out.println(DateUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss.SSS"));
            System.out.println("UDPProvider receive form ip:"+ip+ "\tport:"+port+ "\tdata:"+data);
    
            //构建一份回送数据
            String responseData = "Receive data with len:"+dataLen;
            byte[] responseDataBytes = responseData.getBytes();
            // 直接根据发送者构建一份回送消息
            DatagramPacket responsePacket = new DatagramPacket(responseDataBytes,responseDataBytes.length,receivePack.getAddress(),receivePack.getPort());
            ds.send(responsePacket);
    
            // 完成
            System.out.println(DateUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss.SSS"));
            System.out.println("UDPProvider Finished.");
            ds.close();
        }
    }
    
    public class UDPSearcher {
        public static void main(String[] args) throws IOException {
            System.out.println(DateUtil.getCurrentDateTime());
            System.out.println("UDPSearcher Started.");
    
            //作为搜索方,让系统自动分配端口
            DatagramSocket ds = new DatagramSocket();
    
            //构建一份回送数据
            String requestData = "HelloWorld!";
            byte[] requestDataBytes = requestData.getBytes();
            // 直接根据发送者构建一份回送消息
            DatagramPacket requestPacket = new DatagramPacket(requestDataBytes,
                    requestDataBytes.length);
            // 本机
            requestPacket.setAddress(InetAddress.getLocalHost());
            requestPacket.setPort(UDP_PORT);
            ds.send(requestPacket);
    
            //构建接受实体
            final byte[] buf = new byte[512];
            DatagramPacket receivePack = new DatagramPacket(buf,buf.length);
    
            //接收
            ds.receive(receivePack);
    
            //打印接收到的信息与发送者的信息
            //发送者的IP地址
            String ip = receivePack.getAddress().getHostAddress();
            int port = receivePack.getPort();
            int dataLen = receivePack.getLength();
            String data = new String(receivePack.getData(),0,dataLen);
            System.out.println(DateUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss.SSS"));
            System.out.println("UDPSearcher receive form ip:"+ip+ "\tport:"+port+ "\tdata:"+data);
    
            // 完成
            System.out.println(DateUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss.SSS"));
            System.out.println("UDPSearcher Finished.");
            ds.close();
        }
    }
    
    public class DateUtil {
    
        public static String getCurrentDateTime() {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            return LocalDateTime.now().format(formatter);
        }
    
        public static String getCurrentDateTime(String format) {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
            return LocalDateTime.now().format(formatter);
        }
    }
    
    2019-12-14 15:20:04
    UDPProvider Started.
    2019-12-14 15:20:10.067
    UDPProvider receive form ip:172.16.240.126  port:62991  data:HelloWorld!
    2019-12-14 15:20:10.069
    UDPProvider Finished.
    
    2019-12-14 15:20:10
    UDPSearcher Started.
    2019-12-14 15:20:10.069
    UDPSearcher receive form ip:172.16.240.126  port:20000  data:Receive data with len:11
    2019-12-14 15:20:10.070
    UDPSearcher Finished.
    

    UDP局域网搜索实例-多播(组播)

    一对多,一个发送者对多个接收
    发送端只发送广播消息已接入的接受端

    1.发送端UDPProvider先指定监听公共端口UDP_PORT
    2.接收端UDPSeacher通过发送端UDP_PORT,发送自己的端口LISTEN_PORT给发送端UDPProvider
    3.发送端UDPProvider通过接收端端口LISTEN_PORT,发送口令信息sn给接收端UDPSeacher
    4.接收端UDPSeacher通过口令规则拿到口令信息sn
    3.发送端拿到接收端的口令信息

    发送端

    public class UDPProvider {
        public static void main(String[] args) throws IOException {
            // 生成一份唯一标识
            String sn = UUID.randomUUID().toString();
            Provider provider = new Provider(sn);
            provider.start();
    
            // 读取任意键盘信息后可以退出
            System.in.read();
            provider.exit();
        }
    
        private static class Provider extends Thread {
            private final String sn;
            private boolean done = true;
            private DatagramSocket ds = null;
    
            public Provider(String sn) {
                super();
                this.sn = sn;
            }
    
            @Override
            public void run() {
                super.run();
                System.out.println(DateUtil.getCurrentDateTime());
                System.out.println("UDPProvider Started.");
                //作为接受者,指定一个端口用于数据接受
                try {
                    // 监听20000 端口
                    // 构建一个监听
                    ds = new DatagramSocket(UDP_PORT);
    
                    while (done) {
                        // 构建接收实体
                        final byte[] buf = new byte[512];
                        DatagramPacket receivePacket = new DatagramPacket(buf, 0, buf.length);
    
                        // 接收
                        ds.receive(receivePacket);
    
                        // 发送者的ip地址
                        /**
                         * 会获得搜索方随机分配的端口
                         */
                        String sendIp = receivePacket.getAddress().getHostAddress();
                        int sendPort = receivePacket.getPort();
                        int dataLength = receivePacket.getLength();
    
                        String data = new String(receivePacket.getData(), 0, dataLength);
    
                        System.out.println(DateUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss.SSS"));
                        System.out.println("UDPProvider receive form ip:" + sendIp + "\tport:" + sendPort + "\tdata:" + data);
    
                        /**
                         * 通过约定的口令
                         * 解析端口号
                         */
                        int responsePort = MessageCreator.parsePort(data);
                        if (responsePort != -1) {
                            //构建一份回送数据
                            String responseData = MessageCreator.buildWithSn(sn);
                            byte[] responseDataBytes = responseData.getBytes();
                            // 直接根据发送者构建一份回送消息
                            DatagramPacket responsePacket = new DatagramPacket(responseDataBytes,
                                    responseDataBytes.length,
                                    receivePacket.getAddress(),
                                    responsePort);
                            ds.send(responsePacket);
                        }
    
                    }
    
                } catch (IOException e) {
                    /**
                     * 如果错误信息
                     * 错误信息可以忽略
                     */
    
                    // e.printStackTrace();
                } finally {
                    close();
                }
                // 完成
                System.out.println(DateUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss.SSS"));
                System.out.println("UDPProvider Finished.");
    
            }
    
            private void close() {
                if (ds != null) {
                    ds.close();
                    ds = null;
                }
            }
    
            /**
             * 提供
             */
            void exit() {
                done = false;
                close();
            }
        }
    
    }
    

    发送端

    public class UDPSeacher {
        private static final int LISTEN_PORT = 30000;
    
        public static void main(String[] args) throws IOException, InterruptedException {
            System.out.println("UDPSearcher Started.");
            Listener listener = listen();
    
            // 发送消息
            sendBroadcast();
    
    
            //读取任意键盘信息后可以退出
            System.in.read();
    
            List<Device> devices = listener.getDevicesAndClose();
    
            for (Device device : devices) {
                System.out.println("Device:" + device.toString());
            }
    
            // 完成
            System.out.println("UDPSearcher Finished.");
        }
    
        /**
         * 不是永久监听
         * 读取任意信息并结束
         */
        private static Listener listen() throws InterruptedException {
            System.out.println("UDPSearcher start listen.");
            CountDownLatch countDownLatch = new CountDownLatch(1);
            Listener listener = new Listener(LISTEN_PORT, countDownLatch);
            listener.start();
            countDownLatch.await();
            return listener;
        }
    
        /**
         * 发送广播
         *
         * @throws Exception
         */
        private static void sendBroadcast() throws IOException {
            System.out.println(DateUtil.getCurrentDateTime());
            System.out.println("UDPSearcher sendBroadcast started.");
    
            //作为搜索方,让系统自动分配端口
            DatagramSocket ds = new DatagramSocket();
    
            //构建一份回送数据
            String requestData = MessageCreator.buildWithPort(LISTEN_PORT);
            byte[] responseDataBytes = requestData.getBytes();
            // 直接根据发送者构建一份回送消息
            DatagramPacket responsePacket = new DatagramPacket(responseDataBytes,
                    responseDataBytes.length);
            // 本机
            responsePacket.setAddress(InetAddress.getByName("255.255.255.255"));
            responsePacket.setPort(UDP_PORT);
    
            //发送
            ds.send(responsePacket);
            ds.close();
    
    
            // 完成
            System.out.println(DateUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss.SSS"));
            System.out.println("UDPSearcher sendBroadcast finished.");
    
        }
    
        /***
         * 设备信息不定
         */
        private static class Device {
            final int port;
            final String ip;
            final String sn;
    
            public Device(int port, String ip, String sn) {
                this.port = port;
                this.ip = ip;
                this.sn = sn;
            }
    
            @Override
            public String toString() {
                return "Device{" +
                        "port=" + port +
                        ", ip='" + ip + '\'' +
                        ", sn='" + sn + '\'' +
                        '}';
            }
    
    
        }
    
        private static class Listener extends Thread {
            private final int listenPort;
            private final CountDownLatch countDownLatch;
            private final List<Device> devices = new ArrayList<>();
            private boolean done = true;
            private DatagramSocket ds = null;
    
            public Listener(int listenPort, CountDownLatch countDownLatch) {
                super();
                this.listenPort = listenPort;
                this.countDownLatch = countDownLatch;
            }
    
    
            @Override
            public void run() {
                super.run();
    
                //通知已启动
                countDownLatch.countDown();
                try {
                    //监听回送端口
                    ds = new DatagramSocket(listenPort);
    
                    while (done) {
                        //作为接受者,指定一个端口用于数据接收
    
                        //构建接收实体
                        final byte[] buf = new byte[512];
                        DatagramPacket receivePack = new DatagramPacket(buf, buf.length);
    
                        //接收
                        ds.receive(receivePack);
    
                        //打印接收到的信息与发送者的信息
                        //发送者的IP地址
                        String ip = receivePack.getAddress().getHostAddress();
                        /**
                         * 会获得搜索方随机分配的端口
                         */
                        int port = receivePack.getPort();
                        int dataLen = receivePack.getLength();
                        String data = new String(receivePack.getData(), 0, dataLen);
                        System.out.println(DateUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss.SSS"));
                        System.out.println("UDPSearcher receive form ip:" + ip + "\tport:" + port + "\tdata:" + data);
    
                        String sn = MessageCreator.parseSn(data);
                        if (sn != null) {
                            Device device = new Device(port, ip, sn);
                            devices.add(device);
                        }
                    }
                } catch (Exception ignore) {
    
                } finally {
                    close();
                }
    
                // 完成
                System.out.println(DateUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss.SSS"));
                System.out.println("UDPSearcher listener finished.");
            }
    
            private void close() {
                if (ds != null) {
                    ds.close();
                    ds = null;
                }
            }
    
           public List<Device> getDevicesAndClose() {
                done = false;
                close();
                return devices;
            }
        }
    }
    
    public class Constant {
    
        public  static final int SERVER_PORT = 2000;
    
        public  static final int UDP_PORT = 20000;
    }
    

    UDPProvider 打印信息

    2019-12-14 20:24:20
    UDPProvider Started.
    2019-12-14 20:24:23.195
    UDPProvider receive form ip:172.16.240.126  port:53593  data:这是口令,请回电端口(Port): 30000
    

    UDPSeacher打印信息

    UDPSearcher Started.
    UDPSearcher start listen.
    2019-12-14 20:24:23
    UDPSearcher sendBroadcast started.
    2019-12-14 20:24:23.196
    UDPSearcher sendBroadcast finished.
    2019-12-14 20:24:23.198
    UDPSearcher receive form ip:172.16.240.126  port:20000  data:收到口令信息 我是(SN): a5b4a793-7ad0-4399-a099-2e50f0a9ca09
    

    相关文章

      网友评论

        本文标题:Socket进阶4之初识UDP

        本文链接:https://www.haomeiwen.com/subject/qokfnctx.html