【Socket网络编程】【2】UDP快速入门
1、一种数据报协议,并非面向连接。它一旦把应用程序发给网络层的数据发送出去,就不保留数据备份。
可应用于:DNS, TFTP, SNMP,实时传输
2、UDP包的最大长度是65507 字节
3、UDP的核心API介绍
(1)DatagramSocket:用于接收与发送UDP包的类
DatagramSocket():创建简单实例,不指定端口和ip。如果使用这个实例来发送UDP包,它会复用本地可用的端口,ip是本机的ip。
DatagramSocket(int port):创建监听固定端口的实例。表示可以通过这个port收到回复的信息
DatagramSocket(int port, InetAddress localAddr):创建固定端口和ip的实例
receive(DatagramPacket d):接收UDP数据包。DatagramPacket顾名思义,就是UDP数据包的一个封装类
send(DatagramPacket d):发送UDP数据包
setSoTimeout(int timeuot):设置超时间,单位:毫秒
close():关闭、释放资源
(2)DatagramPacket:UDP数据包的一个封装类,是UDP发送和接收的实体类,同时它具备一些方法,能够将byte数组、目标地址、目标端口等数据 封装成UDP数据包,或者将UDP数据包拆卸成byte数组
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, in port):offset是byte数组的使用用部分的起始索引。前面3个参数指定的是buf的使用区间。后面两个参数指定的是目标机器的ip和端口,仅仅是发送时有效,接收时是无效的
DatagramPacket(byte[] buf, int offset, SocketAddress address):SocketAddress相当于就是InetAddress + port的封装
setData(byte[] buf, int offset, int length):往UDP数据包装数据的方法
getData()、getOffset()、getLength()、getSocketAddress()
setAddress(InetAddress address)、setPort(int port)、setSocketAddress()
4、UDP 单播、广播、多播(或称为:组播)
5、广播地址的运算以及理解(需要了解计算机网络课程中的划分子网和构造超网的内容,没学过的跳过)
已知
ip:192.168.124.7
子网掩码:255.255.255.192
(或写成:192.168.124.7 / 26)
求网络地址和广播地址。
解:网络地址 = ip & 子网掩码
由于前3个字节都是255,ip的前3个字节与之相与等于本身。故下面算第4个字节:
7 = 00000111
192 = 11000000
7 & 192 = 0
所以网络地址: 192.168.124.0
这是一个C类网络地址,但是它的子网掩码并非是默认的255.255.255.0,而是255.255.255.192,转化为二进制来看,就是主机号位借了2位来作为子网号。所以前24位是网络号,中间2位是子网号,最后6位是主机号。
所以该C类网络,最多可划分2^2=4个子网。这4个子网,可用的ip地址分别为(省略前面3个字节):0~63、64~127、128~191、192~255
很显然,192.168.124.7这个ip处于第一个子网段,它的广播地址为:192.168.124.63(即第一个子网最大的ip地址)
注意:处于不同子网段的主机也是不能广播给对方的。原因其实也很简单,因为处于不同子网段的主机的广播地址都不一样,自然广播给对方啦。
6、在IDEA上,案例实操:局域网搜索案例
(1)UDP接收消息并回送(模拟UDP单播)
(2)UDP局域网广播发送(模拟UDP广播)
7、代码
(1)UDP接收消息并回送(模拟UDP单播)
先启动UDPProvider,再启动UDPSearcher
UDPProvider.java
package UDPDemo;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
/**
* @author passerbyYSQ
* @create 2020-06-16 23:23
*/
public class UDPProvider {
public static void main(String[] args) throws IOException {
System.out.println("UDPProvider started.");
// 注意此处的port是监听本机的端口2000
DatagramSocket ds = new DatagramSocket(20000);
// 构建接收的实体
final byte[] buf = new byte[512];
DatagramPacket receivePkt = new DatagramPacket(buf, buf.length);
// 接收。阻塞。
ds.receive(receivePkt);
// 从UDP数据包中获取发送者的ip和端口
String senderIp = receivePkt.getAddress().getHostAddress();
int port = receivePkt.getPort();
int dataLen = receivePkt.getLength();
String dataStr = new String(receivePkt.getData(), 0, dataLen);
System.out.println("UDPProvider receive from ip: " + senderIp
+ "\tport: " + port + "\tdata: " + dataStr);
// 构建一份回送的数据包
String responseData = "Receive data with len: " + dataLen;
byte[] responseBytes = responseData.getBytes();
DatagramPacket responsePkt = new DatagramPacket(responseBytes,
responseBytes.length, receivePkt.getAddress(), receivePkt.getPort());
ds.send(responsePkt);
// 结束
System.out.println("UDPProvider finished.");
ds.close();
}
}
UDPSearcher.java
package UDPDemo;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
/**
* @author passerbyYSQ
* @create 2020-06-16 23:23
*/
public class UDPSearcher {
public static void main(String[] args) throws IOException {
System.out.println("UDPSearcher started.");
// 让系统随机分配端口,用于发送数据包
DatagramSocket ds = new DatagramSocket();
// 构建一份回送的数据包
String requestData = "Hello World";
byte[] requestBytes = requestData.getBytes();
DatagramPacket requestPkt = new DatagramPacket(requestBytes, requestBytes.length);
requestPkt.setAddress(InetAddress.getLocalHost()); // 发送给本机
requestPkt.setPort(20000); // 发送给20000
// 发送
ds.send(requestPkt);
// 构建接收的实体
final byte[] buf = new byte[512];
DatagramPacket receivePkt = new DatagramPacket(buf, buf.length);
// 接收
ds.receive(receivePkt);
// 从UDP数据包中获取发送者的ip和端口
String senderIp = receivePkt.getAddress().getHostAddress();
int port = receivePkt.getPort();
int dataLen = receivePkt.getLength();
String dataStr = new String(receivePkt.getData(), 0, dataLen);
System.out.println("UDPSearcher receive from ip: " + senderIp
+ "\tport: " + port + "\tdata: " + dataStr);
// 结束
System.out.println("UDPSearcher finished.");
ds.close();
}
}
(2)UDP局域网广播发送(模拟UDP广播)
先启动UDPProvider(可以启动多个),再启动UDPSearcher
UDPProvider.java
package UDPDemo2;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.UUID;
/**
* 接收广播的多方(B,有多个)
*
* @author passerbyYSQ
* @create 2020-06-16 23:23
*/
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;
// 用于接收和发送UDP数据包
private DatagramSocket ds = null;
public Provider(String sn) {
super();
this.sn = sn;
}
@Override
public void run() {
super.run();
System.out.println("UDPProvider started.");
try {
// 注意此处的port是监听本机的端口2000
ds = new DatagramSocket(20000);
while (!done) {
// 构建接收的实体
final byte[] buf = new byte[512];
DatagramPacket receivePkt = new DatagramPacket(buf, buf.length);
// 接收。阻塞。
ds.receive(receivePkt);
// 从UDP数据包中获取发送者的ip和端口
String senderIp = receivePkt.getAddress().getHostAddress();
int port = receivePkt.getPort();
int dataLen = receivePkt.getLength();
String dataStr = new String(receivePkt.getData(), 0, dataLen);
System.out.println("UDPProvider receive from ip: " + senderIp
+ "\tport: " + port + "\tdata: " + dataStr);
// 解析端口
int responsePort = MessageCreator.parsePort(dataStr);
if (responsePort != -1) {
// 构建一份回送的数据包
String responseData = MessageCreator.buildWithSn(sn);
byte[] responseBytes = responseData.getBytes();
DatagramPacket responsePkt = new DatagramPacket(responseBytes,
responseBytes.length, receivePkt.getAddress(),
responsePort); // 注意回送到约定的端口,而不是receivePkt中的端口
ds.send(responsePkt);
}
}
} catch (Exception ignored) {
//e.printStackTrace();
// ds.receive(receivePkt); 接收处于阻塞,状态,此时ds.close会抛出异常
// 此异常忽略,不打印
} finally {
close();
}
// 结束
System.out.println("UDPProvider finished.");
}
// 停止监听。给外部调用
void exit() {
done = true;
close();
}
private void close() {
if (ds != null) {
ds.close();
ds = null;
}
}
}
}
UDPSearcher.java
package UDPDemo2;
import java.io.IOException;
import java.net.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* 发送广播的一方(A)
* @author passerbyYSQ
* @create 2020-06-16 23:23
*/
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("devices:" + device.toString());
}
System.out.println();
System.out.println("UDPSearcher finished.");
}
private static Listener listen() throws InterruptedException {
System.out.println("UDPSearcher listener started.");
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[] requestBytes = requestData.getBytes();
DatagramPacket requestPkt = new DatagramPacket(requestBytes, requestBytes.length);
requestPkt.setAddress(InetAddress.getByName("255.255.255.255")); // 发送给广播地址
requestPkt.setPort(20000); // 发送给20000
// 发送
ds.send(requestPkt);
ds.close();
// 结束
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 = false;
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 receivePkt = new DatagramPacket(buf, buf.length);
// 接收
ds.receive(receivePkt);
// 从UDP数据包中获取发送者的ip和端口
String senderIp = receivePkt.getAddress().getHostAddress();
int port = receivePkt.getPort();
int dataLen = receivePkt.getLength();
String dataStr = new String(receivePkt.getData(), 0, dataLen);
System.out.println("UDPSearcher receive from ip: " + senderIp
+ "\tport: " + port + "\tdata: " + dataStr);
// 解析
String sn = MessageCreator.parseSn(dataStr);
if (sn != null) {
Device device = new Device(port, senderIp, sn);
devices.add(device);
}
}
} catch (Exception ignored) {
//e.printStackTrace();
} 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;
}
}
}
关于CountDownLatch:https://www.iteye.com/blog/zapldy-746458
本文地址:https://blog.csdn.net/qq_43290318/article/details/106796367