UDP协议之快速入门

目录

前言

一、什么是UDP协议

1、UDP的概念

2、为什么说UDP是不可靠的?

3、UDP的实际业务场景(就是它能做什么)

4、UDP报文头解析

5、UDP包最大长度

二、UDP核心API

1、API-DatagramSocket

2、API-DatagramPacket

三、UDP单播、广播、多播

1、单播、多播、广播的概念

2、IP地址类别

3、广播地址

4、IP地址的构成

5、广播地址运算

6、广播通信问题

四、实战案例

1、局域网搜索案例

2、局域网广播搜索


前言

上一篇我们已经说了关于TCP的一些知识,本篇是该系列的第二篇,带你认识UDP协议,我会首先分享一些关于UDP的基础知识,然后再来做一个小案例,加深一下对UDP的理解。

一、什么是UDP协议

1、UDP的概念

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

2、为什么说UDP是不可靠的?

我们平常应该都听说过UDP是不可靠的这句话,但是它究竟为甚不可靠呢,这句话又是个什么意思呢?下面就来说一下为什么:

因为UDP它本身并不是像TCP那样是面向连接的,它是非连接协议,它是客户端发送协议,服务器端从网络中抓取协议,抓取的时间以及发送的时间如果不同,可能会导致客户端发送的数据服务器端没有接收到,这个想必大家在大学学习计算机网络的时候都了解过,简单来说就是这玩意容易丢包,所以给大家造成了不靠谱的印象,在UDP中其实是没有标准的客户端与服务器端的。

总结起来就是以下几点:

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

3、UDP的实际业务场景(就是它能做什么)

  • DNS(域名和IP相互转换的一种协议)、TFTP(一种文件传输的协议)、SNMP(网络数据传输中的一个监控的协议)
  • 视频、音频、普通数据(无关紧要的数据)

4、UDP报文头解析

这张图是告诉我们UDP的报文头主要是做了什么事情,也就是UDP协议的前面加了哪些东西,可以看到它一共有64位,前16位是发送源端口,后面16位是目标端口,接着又是16位是字节长度,最后的16位是头部和数据校验字段。

5、UDP包最大长度

  • 16位——>2字节 存储长度信息
  • 2^16-1 = 64K-1 = 65536-1=65535
  • 自身协议占用:32+32 = 64位=8字节
  • 65535-8=65507byte

二、UDP核心API

1、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():关闭、释放资源

2、API-DatagramPacket

  • 用于处理报文
  • 将byte数组、目标地址、目标端口等数据包装成报文或者将报文拆卸成byte数组
  • 是UDP的发送实体,也是接收实体
  • DatagramPacket(byte[] buf,int offset,int length,InetAddress address,int port) 前面三个参数指定buf的使用区间,后面两个参数指定目标机器地址与端口
  • DatagramPacket(byte[] buf,int length,SocketAddress address) 前面两个参数指定buf的使用区间,SocketAddress相当于InetAddress+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、IP地址类别

3、广播地址

  • 255.255.255.255 为受限广播地址
  • C网广播地址一般为:XXX.XXX.XXX.255(192.168.1.255)
  • D类IP地址为多播预留

4、IP地址的构成

5、广播地址运算

这里举个栗子说明一下吧:

  • IP:192.168.124.7
  • 子网掩码:255.255.255.192
  • 网络地址:192.168.124.0
  • 广播地址:192.168.124.63
  • 255.255.255.192->11111111.11111111.11111111.11000000
  • 可划分网段:2^2 = 4 个
  • 即:0~63、64~127、128~191、192~255
  • 又由于我的IP是192.168.124.7,处在第一个网段,则广播地址取该网段最后一个地址即:192.168.124.63

6、广播通信问题

这里也是举个栗子说明一下:

主机一:192.168.124.7,子网掩码:255.255.255.192

主机二:192.168.124.100,子网掩码:255.255.255.192

这两者之间可以通信吗?。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。当然不能

主机一广播地址:192.168.124.63

主机二广播地址:192.168.124.127 两者不在一个网段内哦,所以无法通信!

四、实战案例

1、局域网搜索案例

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
 * UDP提供者,用于提供服务
 */
public class UDPProvider {
    public static void main(String[] args) throws IOException {
        System.out.println("UDPProvider---------开始.");
        //作为接收者,指定一个端口用于数据接收
        DatagramSocket ds = new DatagramSocket(20000);

        //构建接收实体
        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("UDPProvider receive from ip:" + ip
                + "\tport:" + port + "\tdata:" + data);

        //构建一份回送数据
        String responseData = "Receive data with len:" + dataLen;
        byte[] responseDataBytes = responseData.getBytes();
        DatagramPacket responsePack = new DatagramPacket(responseDataBytes,
                responseDataBytes.length, receivePack.getAddress(), receivePack.getPort());
        ds.send(responsePack);

        //完成
        System.out.println("UDPProvider---------完成.");
        ds.close();
    }
}
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
 * UDP搜索者,用于搜索服务支持方
 */
public class UDPSearcher {
    public static void main(String[] args) throws IOException {
        System.out.println("UDPSearcher---------开始.");
        //作为搜索方,无需指定端口,让系统自动分配
        DatagramSocket ds = new DatagramSocket();

        //构建一份请求数据
        String requestData = "Hello Socket!";
        byte[] requestDataBytes = requestData.getBytes();
        //直接构建packet
        DatagramPacket requestPack = new DatagramPacket(requestDataBytes,
                requestDataBytes.length);
        requestPack.setAddress(InetAddress.getLocalHost());
        requestPack.setPort(20000);

        //发送
        ds.send(requestPack);


        //构建接收实体
        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("UDPSearcher receive from ip:" + ip
                + "\tport:" + port + "\tdata:" + data);

        //完成
        System.out.println("UDPSearcher---------完成.");
        ds.close();
    }
}

上面两个类分别定义了服务接收方和搜索方,代码里面也都有注释,我就不多讲了,下面来看下结果:

2、局域网广播搜索

这个案例我们是在上个搜索的基础上来实现,所以是对代码做一些修改。

首先我们新建一个用于构建消息的类,代码如下:

/**
 * 消息构建者
 */
public class MessageCreator {
    private static final String SN_HEADER = "收到口令,我是(SN):";
    private static final String PORT_HEADER = "这是口令,请回送端口(Port)";

    public static String buildWithPort(int port) {
        return PORT_HEADER + port;
    }

    public static int parsePort(String data) {
        if (data.startsWith(PORT_HEADER)) {
            return Integer.parseInt(data.substring(PORT_HEADER.length()));
        }
        return -1;
    }

    public static String buildWithSn(String sn) {
        return SN_HEADER + sn;
    }

    public static String parseSn(String data) {
        if (data.startsWith(SN_HEADER)) {
            return data.substring(SN_HEADER.length());
        }
        return null;
    }
}

然后修改UDPProvider:

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.UUID;

/**
 * UDP提供者,用于提供服务
 */
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 = false;
        private DatagramSocket ds = null;

        public Provider(String sn) {
            super();
            this.sn = sn;
        }

        @Override
        public void run() {
            super.run();
            System.out.println("UDPProvider---------开始.");

            try {
                //监听20000端口
                ds = new DatagramSocket(20000);

                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("UDPProvider receive from ip:" + ip
                            + "\tport:" + port + "\tdata:" + data);

                    //解析端口号
                    int responsePort = MessageCreator.parsePort(data);

                    if (responsePort != -1) {
                        //构建一份回送数据
                        String responseData = MessageCreator.buildWithSn(sn);
                        byte[] responseDataBytes = responseData.getBytes();
                        DatagramPacket responsePack = new DatagramPacket(responseDataBytes,
                                responseDataBytes.length, receivePack.getAddress(),
                                responsePort);
                        ds.send(responsePack);
                    }
                }
            } catch (Exception ignored) {
            } finally {
                close();
            }
            //完成
            System.out.println("UDPProvider---------完成.");
        }

        /**
         * 提供结束
         */
        private void close() {
            if (ds != null) {
                ds.close();
                ds = null;
            }
        }

        void exit() {
            done = true;
            close();
        }
    }
}

最后还需要修改UDPSearcher:

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
 * UDP搜索者,用于搜索服务支持方
 */
public class UDPSearcher {
    private static final int LISTEN_PORT = 30000;

    public static void main(String[] args) throws IOException, InterruptedException {
        System.out.println("UDPSearcher---------开始.");
        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---------完成.");
    }

    private static Listener listen() throws InterruptedException {
        System.out.println("UDPSearcher start listener.");
        CountDownLatch countDownLatch = new CountDownLatch(1);
        Listener listener = new Listener(LISTEN_PORT,countDownLatch);
        listener.start();
        countDownLatch.await();
        return listener;
    }

    private static void sendBroadcast() throws IOException {
        System.out.println("UDPSearcher sendBroadcast---------开始.");
        //作为搜索方,无需指定端口,让系统自动分配
        DatagramSocket ds = new DatagramSocket();

        //构建一份请求数据
        String requestData = MessageCreator.buildWithPort(LISTEN_PORT);
        byte[] requestDataBytes = requestData.getBytes();
        //直接构建packet
        DatagramPacket requestPack = new DatagramPacket(requestDataBytes,
                requestDataBytes.length);
        //20000端口,广播地址
        requestPack.setAddress(InetAddress.getByName("255.255.255.255"));
        requestPack.setPort(20000);

        //发送
        ds.send(requestPack);
        ds.close();

        //完成
        System.out.println("UDPSearcher sendBroadcast---------完成.");
    }

    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 = false;
        private DatagramSocket ds = null;

        public Listener(int listenPort, CountDownLatch countDownLatch) {
            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("UDPSearcher receive from 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 ignored) {
            } finally {
                close();
            }
            System.out.println("UDPSearcher listener finished");
        }

        private void close() {
            if (ds != null) {
                ds.close();
                ds = null;
            }
        }

        List<Device> getDevicesAndClose() {
            done = true;
            close();
            return devices;
        }

    }
}

来看一下运行结果,对于UDPProvider来说它其实是无限循环一直存在的,我们可以多次发送,如下图所示:

发布了48 篇原创文章 · 获赞 47 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/JArchie520/article/details/105326306