1、UDP是什么
- Internet 协议集支持一个无连接的传输协议,该协议称为用户数据报协议,又称用户数据报文协议,基于报文的协议(不同于TCP面向链接的协议)
- 是一种简单的面向数据报的传输层协议,正式规范RFC 768,提供面向事务的简单不可靠信息传送服务
为什么不可靠
- 一旦把应用程序发给网络层的数据发送出去,就不保留数据备份
- UDP在IP数据报的头部仅仅加入了复用和数据校验(字段)
- 发送端是产生数据,在接收端从网络中抓取数据
- 特点:结构简单、无校验、速度快、容易丢包、可广播
UDP能做什么
- DNS、TFTP、SNMP
- 视频、音频、普通数据(无关紧要数据)
2、UDP核心API讲解
API-DatagramSocket
- 此类表示用来发送和接收数据包的套接字,数据报套接字是包投递服务的发送或接收点
- 负责发送某一个UDP包,或者接收UDP包
- 不同于TCP,UDP并没有合并到Socket API中(UDP中没有区分客户端和服务器,DatagramSocket即是客户端也是服务端,可以发送也可以接收,不需要连接)
- DatagramSocket()创建简单实例,不指定端口与IP(使用它发送,他会自动赋予本地可用端口进行发送数据)
- DatagramSocket( int port)创建监听固定端口的实例
- DatagramSocket( int port,InetAddress localAddr)创建固定端口制定IP的实例
3、UDP单播、广播、多播
广播地址
- 255.255.255.255为受限广播地址
- c网广播地址一般为:xxx.xxx.xxx.255
- D类广播地址为多播预留
- 广播地址运算:子网掩码、网络地址、广播地址的计算 - yangyang的专栏 - CSDN博客 https://blog.csdn.net/u014465934/article/details/81146443
- 如何计算网络地址和广播地址 - lzh657083979的博客 - CSDN博客 https://blog.csdn.net/lzh657083979/article/details/77606217
4、局域网搜索案——A向B发送信息
- 4.1、实现UDP接收消息并回送功能
UDP提供者代码:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
/**
* UDP提供者,用于提供服务
*/
public class UDPprovider {
public static void main(String[] args) throws IOException {
System.out.println("UDPProvider started.");
//作为接受者,制定一个端口用于数据接收
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 form ip:"+ip+"\tport:"+port+"\tdata:"+data);
//构建一份回送数据
String responseData="\nreceive 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("UDPProvider finished.");
ds.close();
}
}
UDP搜索者代码:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
/**
* UDP搜索者用于搜索支持方
*/
public class UDPsearcher {
public static void main(String[] args) throws IOException {
System.out.println("UDPseacher started.");
//作为搜索方,让系统自动分配端口
DatagramSocket ds = new DatagramSocket();
//构建一份请求数据
String requestData="helloworld!";
//构建回送消息的实体
byte[] requestDataBytes=requestData.getBytes();
//直接构建packet
DatagramPacket requestPacket =new DatagramPacket(requestDataBytes,requestDataBytes.length);
//本机端口20000
requestPacket.setAddress(InetAddress.getLocalHost());
requestPacket.setPort(20000);
//发送
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("UDPseacher receive form ip:"+ip+"\tport:"+port+"\tdata:"+data);
//完成
System.out.println("UDPseacher finished.");
ds.close();
}
}
5、局域网搜索案例——A向局域网内所有设备发送信息
- UDP局域网广播发送的实现
- UDP局域网回送消息的实现
- A作为提供者,B作为接收者
UDP提供者,用于提供服务
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;
}
//Intellij Idea get/set/Override方法快捷键Alt+Insert,或直接ctrl+o
@Override
public void run() {
super.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 + "\tport:" + port + "\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,
receivePack.getAddress(),
responsePort);
ds.send(responsePacket);
}
}
}catch (Exception e){ //这个异常可以忽略
}finally {
close();
}
//完成
System.out.println("UDPProvider finished.");
}
private void close(){//socket进行结束操作
if(ds!=null){
ds.close();
ds=null;
}
}
/**
* 提供结束
*/
void exit(){
done=true;
close();
}
}
}
消息创建者,具备两个口令,一个搜索口令,一个回送口令
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搜索者用于搜索支持方
import java.io.IOException;
import java.net.*;
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("UDPseacher started.");
Lisener listener = listen();//开始一个监听
sendBroadcast();//监听之后发送一个广播
/* //作为搜索方,让系统自动分配端口
DatagramSocket ds = new DatagramSocket();
//构建一份请求数据
String requestData="helloworld!";
//构建回送消息的实体
byte[] requestDataBytes=requestData.getBytes();
//直接构建packet
DatagramPacket requestPacket =new DatagramPacket(requestDataBytes,requestDataBytes.length);
//本机端口20000
requestPacket.setAddress(InetAddress.getLocalHost());
requestPacket.setPort(20000);
//发送
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("UDPseacher receive form ip:"+ip+"\tport:"+port+"\tdata:"+data);*/
//ds.close();
//读取任意键盘信息后可以退出
System.in.read();
List<Device> devices = listener.getDevicesAndClose();//结束之前拿到设备信息,从监听器中拿我们的设备信息
for (Device device : devices) {//拿到设备之后,打印设备信息
System.out.println("Device:"+device.toString());
}
//完成
System.out.println("UDPseacher finished.");
}
//监听方法
private static Lisener listen() throws InterruptedException {//监听方法
System.out.println("UDPseacher start listen.");
CountDownLatch countDownLatch=new CountDownLatch(1);
Lisener lisener=new Lisener(LISTEN_PORT,countDownLatch);
lisener.start();//启动操作
countDownLatch.await();//等待启动完成
return lisener;
}
//发送广播的方法
public static void sendBroadcast() throws IOException {//发送广播的方法
System.out.println("UDPseacher 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("UDPseacher sendBroadcast finished.");
}
private static class Device{//设备类信息
final int port;//端口
final String ip;
final String sn;
//三者都是必须要的东西,因此加上final
private Device(int port, String ip, String sn) {
this.port = port;
this.ip = ip;
this.sn = sn;
}
//同时构建一个tostring方法:快捷键Alt+Insert选择tostring()再点击OK
@Override
public String toString() {
return "Device{" +
"port=" + port +
", ip='" + ip + '\'' +
", sn='" + sn + '\'' +
'}';
}
}
private static class Lisener extends Thread{
private final int listenPort;//需要传入的端口号
private final CountDownLatch countDownLatch;//让外面感知到已经开始监听了
private final List<Device> devices=new ArrayList<>();//设备链表
private boolean done=false;//结束符,默认为false
private DatagramSocket ds=null;//搜索者
public Lisener(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("UDPseacher receive form ip:"+ip+"\tport:"+port+"\tdata:"+data);
//解析操作,解析sn
String sn=MessageCreator.parseSn(data);
if(sn!=null){
Device device=new Device(port,ip,sn);//传入新设备端口号、ip及sn信息
devices.add(device);//sn解析成功,设备链表增加一个新的设备
}
}
}catch (Exception e){
}finally {
close();
}
System.out.println("UDPseacher listener finished");
}
private void close(){
if(ds!=null){
ds.close();
ds=null;
}
}
List<Device>getDevicesAndClose(){//拿并关闭的操作
done=true;
close();
return devices;
}
}
}
运行结果:
首先要打开提供方,再打开搜索方
在搜索方输入任意字符结束搜索和提供方: