Socket – UDP 疾速入门
UDP 是什么
- 英语:User Datagram Protocol,缩写为 UDP。
- 一种
用户数据报协定
,又称用户数据报文协定
。 - 是一个简略的
面向数据报
的传输层
协定,正式标准为 RFC 768。 - 用户数据协定、非连贯协定。
为什么 UDP 是不牢靠的
- 它一旦把应用程序发给网络层的数据发送进来,就不保留数据备份。
- UDP 在 IP 数据报的头部仅仅退出了复用和数据校验字段。
- 发送端生产数据,接收端从网络中抓取数据。
- 构造简略、无校验、速度快、容易丢包、可播送。
UDP 能做什么
UDP 是面向音讯的协定,通信时不须要建设连贯,数据的传输天然是不牢靠的,UDP 个别用于多点通信和实时的数据业务,比方:
- 语音播送
- 视频
- TFTP(简略文件传送)
- SNMP(简略网络管理协定)
- RIP(路由信息协定,如报告股票市场,航空信息)
- DNS(域名解释)
- 重视速度晦涩
UDP 操作简略,而且仅须要较少的监护,因而通常用于局域网高可靠性的分散系统中 client/server 应用程序。例如视频会议零碎,并不要求音频视频数据相对的正确,只有保障连贯性就能够了,这种状况下显然应用 UDP 会更正当一些。
UDP 通信模型
UDP 通信模型中,在通信开始之前,不须要建设相干的链接,只须要发送数据即可,相似于生存中,” 写信 ””
外围 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 单播、播送、多播
- 单播:用于两个主机之间的端对端通信。
- 播送:用于一个主机对整个局域网上所有的主机上的数据通信。
- 多播:对一组特定的主机进行通信,而不是整个局域网上的所有主机。
IP 地址
- 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 类地址呢?为了更好地进行辨别,将每类地址的结尾局部设置为固定数值。如下:
从图中能够看出,每类 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 值。
局域网搜寻代码案例
/**
*
* 音讯构建
* @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;
}
}
}