Socket - UDP快速入门

简介: Socket - UDP快速入门

Socket - UDP快速入门


UDP是什么

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


为什么UDP是不可靠的

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


UDP能做什么

UDP是面向消息的协议,通信时不需要建立连接,数据的传输自然是不可靠的,UDP一般用于多点通信和实时的数据业务,比如:


  • 语音广播
  • 视频
  • QQ
  • TFTP(简单文件传送)
  • SNMP(简单网络管理协议)
  • RIP(路由信息协议,如报告股票市场,航空信息)
  • DNS(域名解释)
  • 注重速度流畅


UDP操作简单,而且仅需要较少的监护,因此通常用于局域网高可靠性的分散系统中client/server应用程序。例如视频会议系统,并不要求音频视频数据绝对的正确,只要保证连贯性就可以了,这种情况下显然使用UDP会更合理一些。


UDP通信模型

UDP通信模型中,在通信开始之前,不需要建立相关的链接,只需要发送数据即可,类似于生活中,"写信""

1654699736270.png

核心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():关闭,释放资源。


DatagramPacket

  • 用于处理报文
  • 将byte数组、目标地址、目标端口等数据包装成报文或者将报文拆卸成byte数组。
  • DatagramPacket是UDP的发送实体,也是接受实体。
  • DatagramPacket(byte[] buf , int offset , int length , InetAddress address , int port ) :指定byte数组和使用区间,后两个指定目标机器地址与端口。
  • DatagramPacket(byte[] buf , int length , SocketAddress address) :SocketAddress相当于InetAddress + Port。
  • setData(byte[] buf , int offset , int length):传入byte数据和长度信息。
  • setData(byte buf):传入整个byte数组的信息。
  • seLength(int length):设置有效区间。
  • getData() 、getOffset() 、getLength() :返回对应上述信息。
  • setAddress(InetAddress addr):目标地址。
  • setPort(int port):目标端口。
  • 对应的getAddress()、getPort()。
  • setSocketAddress(SocketAddress address):传入InetAddress + Port。
  • getSocketAddress():获取socket地址。


UDP 单播、广播、多播

  • 单播:用于两个主机之间的端对端通信。
  • 广播:用于一个主机对整个局域网上所有的主机上的数据通信。
  • 多播:对一组特定的主机进行通信,而不是整个局域网上的所有主机。

1654699772819.png

IP地址

1654699791635.png

  • A类:前8位表示网络ID,后24位表示主机ID;该地址分配给政府机关单位使用。
  • B类:前16位表示网络ID,后16位表示主机ID;该地址分配给中等规模的企业使用。
  • C类:前24位表示网络ID,后8位表示主机ID;该地址分配给任何需要的人使用。
  • D类:不分网络ID和主机ID;该地址用于多播。
  • E类:不分网络ID和主机ID;该地址用于实验。


地址区分

IP地址被分类以后,如何判断一个IP地址是A类、B类还是C类地址呢?为了更好地进行区分,将每类地址的开头部分设置为固定数值。如下:

1654699817765.png

从图中可以看出,每类IP地址都是以32位的二进制格式显示的,每类地址的区别如下:


  • A类:网络ID的第一位以0开始的地址。
  • B类:网络ID的第一位以10开始的地址。
  • C类:网络ID的第一位以110开始的地址。
  • D类:地址以1110开始的地址。
  • E类:地址以11110开始的地址。


地址范围

由于每类地址的开头是固定的,因此每类地址都有自己的范围:


  • A类:IP地址范围为0.0.0.0~127.255.255.255。
  • B类:IP地址范围为128.0.0.0~191.255.255.255。
  • C类:IP地址范围为192.0.0.0~223.255.255.255。
  • D类:IP地址范围为224.0.0.0~239.255.255.255。
  • E类:IP地址范围为240.0.0.0~255.255.255.254。


广播地址

  • 255.255.255.255 为受限广播地址,如果向该地址的2000端口号发送一个信息,其实只有局域网内的设备能够收到。
  • C 网广播地址一般为:XXX.XXX.XXX.255(192.168.1.255)。
  • D类地址一般为多播预留。


IP地址构成

IP地址构成是使用短整型来存储的。一共四位,也就是一个int值。

1654699853084.png

局域网搜索代码案例

/**
 * 
 * 消息构建
 * @author Jack
 */
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;
    }
}
/**
 * UDP 提供者,用于提供服务
 * @author Jack
 */
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) {
            this.sn = sn;
        }
        @Override
        public void run() {
            System.out.println("UDPProvider Started.");
            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 form ip:" + ip + ", port:" + port + ", data:" + 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, receivePack.getAddress(), responsePort);
                        ds.send(responsePacket);
                    }
                }
            } catch (Exception ignored) {
            } finally {
                close();
            }
            // 完成
            System.out.println("UDPProvider Finished.");
        }
        private void close() {
            if (ds != null) {
                ds.close();
                ds = null;
            }
        }
        /**
         * 提供结束
         */
        void exit() {
            done = true;
            close();
        }
    }
}
/**
 * UDP 搜索者,用于搜索服务支持方
 * @author Jack
 */
public class UDPSearcher {
    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;
    }
    private static void sendBroadcast() throws IOException {
        System.out.println("UDPSearcher sendBroadcast started.");
        // 作为搜索方,让系统自动分配端口
        DatagramSocket ds = new DatagramSocket();
        // 构建一份请求数据
        String requestData = MessageCreator.buildWithPort(LISTEN_PORT);
        byte[] requestDataBytes = requestData.getBytes();
        // 直接构建packet
        DatagramPacket requestPacket = new DatagramPacket(requestDataBytes,
                requestDataBytes.length);
        // 20000端口, 广播地址
        requestPacket.setAddress(InetAddress.getByName("255.255.255.255"));
        requestPacket.setPort(20000);
        // 发送
        ds.send(requestPacket);
        ds.close();
        // 完成
        System.out.println("UDPSearcher sendBroadcast finished.");
    }
    private static class Device {
        final int port;
        final String ip;
        final String sn;
        private 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() {
            // 通知已启动
            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 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 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;
        }
    }
}


相关文章
|
3月前
|
存储 Python
Python网络编程基础(Socket编程) UDP 发送和接收数据
【4月更文挑战第10天】对于UDP客户端而言,发送数据是一个相对简单的过程。首先,你需要构建一个要发送的数据报,这通常是一个字节串(bytes)。然后,你可以调用socket对象的`sendto`方法,将数据报发送到指定的服务器地址和端口。
|
3月前
|
网络协议 网络性能优化 开发者
Python网络编程基础(Socket编程)UDP Socket编程
【4月更文挑战第8天】Python网络编程中,UDP与TCP协议各有特点。TCP提供可靠连接,确保数据顺序与完整性,适合文件传输等;UDP则无连接,速度快,常用于实时音视频,牺牲了数据可靠性。Python的socket库支持两者,开发者可根据需求选择。
|
3月前
|
存储 Python
Python网络编程基础(Socket编程)UDP客户端编程
【4月更文挑战第9天】在UDP通信中,客户端负责发送数据到服务器,并接收来自服务器的响应。与服务器不同,客户端通常不需要绑定到特定的地址和端口,因为它可以临时使用任何可用的端口来发送数据。下面,我们将详细讲解UDP客户端编程的基本步骤。
|
3月前
|
网络协议 Python
Python网络编程基础(Socket编程)创建UDP socket对象
【4月更文挑战第8天】在Python中创建UDP服务器涉及使用`socket`模块创建socket对象,如`udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)`,然后绑定到特定IP地址和端口,如`udp_socket.bind((&#39;localhost&#39;, 12345))`。服务器通过`recvfrom`在无限循环中监听和接收数据报。这只是基础,实际应用还需处理接收、解析、响应及错误处理等。接下来可学习如何利用socket对象进行数据交互以构建完整服务器。
|
3月前
|
网络协议 Java
Java的Socket编程:TCP/IP与UDP深入探索
Java的Socket编程:TCP/IP与UDP深入探索
42 0
|
2月前
|
存储 网络协议 数据处理
【Socket】解决UDP丢包问题
UDP(用户数据报协议)是一种无连接的传输层协议,因其不保证数据包的顺序到达和不具备内置重传机制,导致在网络拥塞、接收缓冲区溢出或发送频率过快等情况下容易出现丢包现象。为应对这些问题,可以在应用层实现重传机制、使用前向纠错码等方法。这些方法在一定程度上可以缓解UDP通信中的丢包问题,提高数据传输的可靠性和效率。
|
2月前
|
网络协议 应用服务中间件 网络性能优化
解析TCP /UDP协议的 socket 调用的过程
【6月更文挑战第2天】该文介绍了传输层的两种主要协议TCP和UDP的区别。TCP是面向连接、可靠的,提供顺序无错的数据传输,而UDP则是无连接、不可靠的,不保证数据顺序或不丢失。
|
3月前
|
网络协议 Linux
TCP 和 UDP 的 Socket 调用
【2月更文挑战第19天】
TCP 和 UDP 的 Socket 调用
|
3月前
|
Python
Python网络编程基础(Socket编程)UDP服务器编程
【4月更文挑战第8天】Python UDP服务器编程使用socket库创建UDP套接字,绑定到特定地址(如localhost:8000),通过`recvfrom`接收客户端数据报,显示数据长度、地址和内容。无连接的UDP协议使得服务器无法主动发送数据,通常需应用层实现请求-响应机制。当完成时,用`close`关闭套接字。
|
3月前
|
存储 网络协议 关系型数据库
Python从入门到精通:2.3.2数据库操作与网络编程——学习socket编程,实现简单的TCP/UDP通信
Python从入门到精通:2.3.2数据库操作与网络编程——学习socket编程,实现简单的TCP/UDP通信