Saul's blog Saul's blog
首页
后端
分布式
前端
更多
分类
标签
归档
友情链接
关于
GitHub (opens new window)

Saul.J.Wu

立身之本,不在高低。
首页
后端
分布式
前端
更多
分类
标签
归档
友情链接
关于
GitHub (opens new window)
  • Java入门基础

  • Java核心基础

    • 多线程

    • Java常用类

    • 枚举类与注解

    • Java集合

    • 数据结构与算法

    • 泛型

    • IO流

    • 网络编程

      • 网络编程基础
      • InetAddress类
      • TCP编程
      • UDP编程
        • 服务器端
        • 客户端
        • 测试
        • 小结
      • 发送Email
      • 接收Email
      • URL编程
      • HTTP编程
      • 计算机网络常见面试题
    • 反射

    • 函数式编程

  • 设计模式

  • Web开发

  • SpringBoot

  • 微服务

  • Elasticsearch

  • 运维

  • 后端
  • Java核心基础
  • 网络编程
SaulJWu
2021-01-10

UDP编程

和TCP编程相比,UDP编程就简单得多,因为UDP没有创建连接,数据包也是一次收发一个,所以没有流的概念。

在Java中使用UDP编程,仍然需要使用Socket,因为应用程序在使用UDP时必须指定网络接口(IP)和端口号。注意:UDP端口和TCP端口虽然都使用0~65535,但他们是两套独立的端口,即一个应用程序用TCP占用了端口1234,不影响另一个应用程序用UDP占用端口1234。

# 服务器端

在服务器端,使用UDP也需要监听指定的端口。Java提供了DatagramSocket来实现这个功能,代码如下:

DatagramSocket ds = new DatagramSocket(6666); // 监听指定端口
for (;;) { // 无限循环
    // 数据缓冲区:
    byte[] buffer = new byte[1024];
    DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
    ds.receive(packet); // 收取一个UDP数据包
    // 收取到的数据存储在buffer中,由packet.getOffset(), packet.getLength()指定起始位置和长度
    // 将其按UTF-8编码转换为String:
    String s = new String(packet.getData(), packet.getOffset(), packet.getLength(), StandardCharsets.UTF_8);
    System.out.println("服务端收到了信息:" + s);
    // 发送数据:
    byte[] data = "ACK".getBytes(StandardCharsets.UTF_8);
    packet.setData(data);
    ds.send(packet);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

服务器端首先使用如下语句在指定的端口监听UDP数据包:

DatagramSocket ds = new DatagramSocket(6666);
1

如果没有其他应用程序占据这个端口,那么监听成功,我们就使用一个无限循环来处理收到的UDP数据包:

for (;;) {
    ...
}
1
2
3

要接收一个UDP数据包,需要准备一个byte[]缓冲区,并通过DatagramPacket实现接收:

byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
ds.receive(packet);
1
2
3

假设我们收取到的是一个String,那么,通过DatagramPacket返回的packet.getOffset()和packet.getLength()确定数据在缓冲区的起止位置:

String s = new String(packet.getData(), packet.getOffset(), packet.getLength(), StandardCharsets.UTF_8);
1

当服务器收到一个DatagramPacket后,通常必须立刻回复一个或多个UDP包,因为客户端地址在DatagramPacket中,每次收到的DatagramPacket可能是不同的客户端,如果不回复,客户端就收不到任何UDP包。

发送UDP包也是通过DatagramPacket实现的,发送代码非常简单:

byte[] data = ...
packet.setData(data);
ds.send(packet);
1
2
3

# 客户端

和服务器端相比,客户端使用UDP时,只需要直接向服务器端发送UDP包,然后接收返回的UDP包:

DatagramSocket ds = new DatagramSocket();
ds.setSoTimeout(1000);
ds.connect(InetAddress.getByName("localhost"), 6666); // 连接指定服务器和端口
// 发送:
byte[] data = "Hello".getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length);
ds.send(packet);
// 接收:
byte[] buffer = new byte[1024];
packet = new DatagramPacket(buffer, buffer.length);
ds.receive(packet);
String resp = new String(packet.getData(), packet.getOffset(), packet.getLength());
System.out.println("客户端收到了信息:" + resp);
ds.disconnect();
1
2
3
4
5
6
7
8
9
10
11
12
13
14

客户端打开一个DatagramSocket使用以下代码:

DatagramSocket ds = new DatagramSocket();
ds.setSoTimeout(1000);
ds.connect(InetAddress.getByName("localhost"), 6666);
1
2
3

客户端创建DatagramSocket实例时并不需要指定端口,而是由操作系统自动指定一个当前未使用的端口。紧接着,调用setSoTimeout(1000)设定超时1秒,意思是后续接收UDP包时,等待时间最多不会超过1秒,否则在没有收到UDP包时,客户端会无限等待下去。这一点和服务器端不一样,服务器端可以无限等待,因为它本来就被设计成长时间运行。

注意到客户端的DatagramSocket还调用了一个connect()方法“连接”到指定的服务器端。不是说UDP是无连接的协议吗?为啥这里需要connect()?

这个connect()方法不是真连接,它是为了在客户端的DatagramSocket实例中保存服务器端的IP和端口号,确保这个DatagramSocket实例只能往指定的地址和端口发送UDP包,不能往其他地址和端口发送。这么做不是UDP的限制,而是Java内置了安全检查。

如果客户端希望向两个不同的服务器发送UDP包,那么它必须创建两个DatagramSocket实例。

后续的收发数据和服务器端是一致的。通常来说,客户端必须先发UDP包,因为客户端不发UDP包,服务器端就根本不知道客户端的地址和端口号。

如果客户端认为通信结束,就可以调用disconnect()断开连接:

ds.disconnect();
1

注意到disconnect()也不是真正地断开连接,它只是清除了客户端DatagramSocket实例记录的远程服务器地址和端口号,这样,DatagramSocket实例就可以连接另一个服务器端。

# 测试

我先启动服务端,然后启动客户端,结果:

服务端收到了信息:Hello
客户端收到了信息:ACK
1
2

服务端因为是无限循环,一直启动着,要手动关闭。

# 小结

使用UDP协议通信时,服务器和客户端双方无需建立连接:

  • 服务器端用DatagramSocket(port)监听端口;
  • 客户端使用DatagramSocket.connect()指定远程地址和端口;
  • 双方通过receive()和send()读写数据;
  • DatagramSocket没有IO流接口,数据被直接写入byte[]缓冲区。
帮我改善此页面 (opens new window)
#UDP#DatagramSocket
上次更新: 2021/01/10, 07:47:36
TCP编程
发送Email

← TCP编程 发送Email→

最近更新
01
zabbix学习笔记二
02-28
02
zabbix学习笔记一
02-10
03
Linux访问不了github
12-08
更多文章>
Theme by Vdoing | Copyright © 2020-2022 Saul.J.Wu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式