Socket进阶4之初识UDP

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容