文章目录
- 0. 概述
- 1. UDP通信中的对象
- 1.1 DatagramPacket:数据报包对象
- 1.2 DatagramSocket:发送端和接收端对象
- 2. UDP通信之一发一收
- 2.1 客户端(发送端)实现步骤
- 2.2 接收端实现步骤
- 3. UDP通信之模拟多发多收
- 3.1 发送端实现步骤
- 3.2 接收端实现步骤
- 4. UDP的三种通信方式
- 4.1 广播实现步骤
- 4.2 组播实现步骤
- 5. 实时通信
0. 概述
从技术意义上来讲,因为TCP要建立连接,而UDP直接"扔菜"就可以了
因此只有TCP协议有分出了Server服务端和Client客户端,对于UDP协议,是没有Server和Client的.
UDP协议特点:
- UDP是一种无连接、不可靠传输的协议
- 将数据源IP、目的地IP和端口以及数据封装成数据包,大小限制在64KB内,直接发送出去即可
因此,打个比方,UDP协议就像小视频里隔着马路把菜扔过去
其中需要人(DatagramSocket发送端接收端对象)来扔菜(数据),同时还需要一个菜盘子(DatagramPacket数据包对象)装菜
1. UDP通信中的对象
1.1 DatagramPacket:数据报包对象
- 可以当成装菜的盘子
- 表示数据报的数据包
- 查看源码发现这个类是被final修饰的不可变对象
- 构造器:
构造器 | 说明 |
public DatagramPacket(byte[] buf, int length, InetAddress address, int port) | 创建发送端数据包对象, buf:要发送的内容,字节数组, length:要发送内容的字节长度, address:接收端的IP地址对象 port:接收端的端口号 |
public DatagramPacket(byte[] buf, int length) | 创建接收端的数据包对象, buf:用来存储接收的内容, length:能够接收内容的长度 |
- 常用API:
方法 | 说明 |
public int getLength() | 返回获得实际接收到的字节个数或者要发送的数据长度 |
byte[] getData() | 返回数据包中的数据 |
InetAddress getAddress() | 返回发送或接收数据报的计算机IP地址 如果未设置,则返回该机器的IP地址 |
SocketAddress getSocketAddress () | 返回发送或接收数据报的远程主机的SocketAddress (通常是IP地址加端口号) |
int getPort() | 返回发送数据或接收数据的远程主机上的端口号,如果未设置则返回0 |
1.2 DatagramSocket:发送端和接收端对象
- 可以理解为扔菜的人
- 用于发送和接收数据报的套接字
- 实现了java.io.Closeable接口,可以在try()中定义然后被自动释放
- 构造器:
构造器 | 说明 |
public DatagramSocket() | 创建发送端的Socket对象,系统会随机分配一个端口号 |
public DatagramSocket(int port) | 创建接收端的Socket对象并绑定本地主机上的指定端口号 你硬要用这个来创建发送端对象也不是不行 |
- 常用API:
方法 | 说明 |
public void send(DatagramPacket dp) | 发送数据包 |
public void receive(DatagramPacket p) | 接收数据包,将接受的数据直接装载进p的字节数组中 |
2. UDP通信之一发一收
2.1 客户端(发送端)实现步骤
- 先创建发送端对象DatagramSocket(有了人才能扔菜,先创建人再创建盘子)
- 创建数据包对象DatagramPacket(菜盘子)封装需要发送的数据以及对方的IP地址与端口号
- 使用DatagramSocket对象的send方法并传入DatagramPacket对象作为参数 (我开始抛菜啦~~)
- 释放资源
- 注意:先启动发送端会报错,必须先启动接收端
发送端代码示例:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
/**
* TCP中的客户端,UDP中的发送端
*/
public class SenderDemo {
public static void main(String[] args) {
System.out.println("发送端启动~~~~呜呜呜呜呜呜呜");
try (
// 先创建发送端对象(扔菜的人)
DatagramSocket socket = new DatagramSocket();
){
// 先创建字节数组封装数据(菜)
byte[] buffer = "我是别扔来扔去的韭菜,好惨一菜".getBytes(StandardCharsets.UTF_8);
// 创建数据包对象(菜盘子),并指定接收端IP地址和端口号
DatagramPacket packet = new DatagramPacket(buffer, buffer.length,
InetAddress.getLocalHost(), 8888);
// 扔菜!!!!
socket.send(packet);
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.2 接收端实现步骤
- 创建接收端对象DatagramSocket并指定端口(人)
- 创建数据包对象DatagramPacket接收数据(盘子),用于接收数据的数组大小建议为:
1024*64
,因为UDP一个数据包最大为64kb - 使用DatagramSocket对象的receive方法传入DatagramPacket对象作为参数(我接你的菜啦~~),此时线程会阻塞,直到收到数据为止
- 注意:先启动服务端(接收端)再启动客户端(发送端)
- 取出数据,注意:接收到多少读取多少(使用盘子的getLength()方法)
- 释放资源
接收端代码示例:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ReceiverDemo {
public static void main(String[] args) {
System.out.println("服务器端启动~~~~嘟嘟嘟嘟嘟嘟");
try (
// 创建接收端对象(人),端口号需要和发送端数据包对象的端口号一致
DatagramSocket socket = new DatagramSocket(8888);
){
// 创建数据包对象接收数据(盘子)
byte[] bytes = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
// 等待接收数据
// 服务器端被启动后会一直等待接收数据
socket.receive(packet);
// 读取数据
String str = new String(bytes, 0, packet.getLength());
System.out.println("收到的数据:" + str);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3. UDP通信之模拟多发多收
多发多收:
- 发送端可以一直发送消息
- 接收端可以不断的接收多个发送端的消息展示
- 发送端输入了exit或其他结束信息则结束发送端程序
3.1 发送端实现步骤
- 创建发送端对象DatagramSocket(扔菜的人)
- 使用while死循环不断的接收数据输入,如果输入的exit则退出程序(可以使用IO流)
- 如果输入不是exit, 把数据封装成DatagramPacket (盘子),指定接收端的IP地址与端口号
- 使用DatagramSocket对象的send方法将数据包对象进行发送 (抛菜抛菜~~)
- 释放资源
示例:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
* TCP中的客户端,UDP中的发送端
*/
public class SenderDemo {
public static void main(String[] args) {
System.out.println("发送端启动~~~~呜呜呜呜呜呜呜");
try (
// 先创建发送端对象(扔菜的人)
DatagramSocket socket = new DatagramSocket();
){
Scanner in = new Scanner(System.in);
// 创建死循环不断读取数据直到读取exit
while (true) {
System.out.println("请输入发送内容:");
String str = in.nextLine();
// 判断是否为exit
if ("exit".equals(str)){
System.out.println("离线成功,bye~~");
break;
}
// 先创建字节数组封装数据(菜)
byte[] buffer = str.getBytes(StandardCharsets.UTF_8);
// 创建数据包对象(菜盘子),并指定接收端IP地址和端口号
DatagramPacket packet = new DatagramPacket(buffer, buffer.length,
InetAddress.getLocalHost(), 8888);
// 扔菜!!!!
socket.send(packet);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.2 接收端实现步骤
- 创建接收端对象DatagramSocket并指定端口(扔菜的人)
- 创建数据包对象DatagramPacket接收数据(菜盘子),建议数组大小:
1024*64
- 使用while死循环不断的进行第4步
- 使用DatagramSocket对象的receive方法传入DatagramPacket对象(接菜)
注意:
- 此时的接收端只负责接收数据包,因为UDP不用建立连接,无所谓是哪个发送端的数据包
- 因此UDP的接收端可以接收很多发送端的消息
- 而TCP要接收很多很多发送端信息必须使用多线程,因为TCP是需要建立连接的
示例:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ReceiverDemo {
public static void main(String[] args) {
System.out.println("服务器端启动~~~~嘟嘟嘟嘟嘟嘟");
try (
// 创建接收端对象(人),端口号需要和发送端数据包对象的端口号一致
DatagramSocket socket = new DatagramSocket(8888);
){
// 创建数据包对象接收数据(盘子)
byte[] bytes = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
// 写循环不断接收数据
while (true) {
// 等待接收数据
// 服务器端被启动后会一直等待接收数据
socket.receive(packet);
// 读取数据
String str = new String(bytes, 0, packet.getLength());
System.out.println("收到了来自" + packet.getAddress() + "的数据,对方端口为:"
+ packet.getPort() + ",数据为:" + str);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
4. UDP的三种通信方式
- 单播:单台主机与单台主机之间的通信,上面讲的都是单播
- 广播:当前主机与所在网络中的所有主机通信
- 组播:当前主机与选定的一组主机的通信,可以理解为一组主机订阅关注了某个组播IP,这个组播IP更新会给关注者推送推文
4.1 广播实现步骤
- 使用广播地址:255.255.255.255,再指定一个端口,就会发生给和主机在同一个网段的其他主机对应端口(包括本机的这个对应端口)
- 具体步骤:
发送端发送的数据包的目的地写的是广播地址、且指定端口。 (255.255.255.255 , 8888)
本机所在网段的其他主机的程序只要注册对应端口就可以收到消息了。(8888)
网段:例如IP地址前三位相同是在同一个网段 - 所以只需要修改发送端源代码即可,代码示例:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
* TCP中的客户端,UDP中的发送端
*/
public class SenderDemo {
public static void main(String[] args) {
System.out.println("发送端启动~~~~呜呜呜呜呜呜呜");
try (
// 先创建发送端对象(扔菜的人)
DatagramSocket socket = new DatagramSocket();
){
Scanner in = new Scanner(System.in);
// 创建死循环不断读取数据直到读取exit
while (true) {
System.out.println("请输入发送内容:");
String str = in.nextLine();
// 判断是否为exit
if ("exit".equals(str)){
System.out.println("离线成功,bye~~");
break;
}
// 先创建字节数组封装数据(菜)
byte[] buffer = str.getBytes(StandardCharsets.UTF_8);
// 创建数据包对象(菜盘子),并指定接收端IP地址和端口号
//!!!!注意:使用广播修改这里的IP地址为255.255.255.255即可!!!!!!!
DatagramPacket packet = new DatagramPacket(buffer, buffer.length,
InetAddress.getByName("255.255.255.255"), 8888);
// 扔菜!!!!
socket.send(packet);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.2 组播实现步骤
- 使用组播地址:224.0.0.0 ~ 239.255.255.255,同时地址224.0.0.0是保留的,不宜使用
- 发送端在组播地址中选择一个组播IP(例如:224.0.1.1),并指定端口号
- 接收端必须绑定该组播IP(224.0.1.1),并且接收端有注册发送端指定的端口号,才能接受到组播信息
- 接收端绑定组播IP:
DatagramSocket的子类MulticastSocket可以在接收端绑定组播IP
因此在创建接收端对象(人)的时候使用他的儿子MulticastSocket
他儿子练就一身神功,接菜本领更加了得
调用==MulticastSocket的joinGroup(InetAddress.getByName(“组播IP”))==方法绑定组播IP
但是这个方法在JDK14开始过时了…
示例:
MulticastSocket socket = new MulticastSocket(8888);
socket.joinGroup(InetAddress.getByName("224.1.1.1"));
- 不过时的方法:
joinGroup(SocketAddress mcastaddr, NetworkInterface netIf);
// 参数一是SocketAddress,可以通过以下方式获得:
new InetSocketAddress(InetAddress.getByName("224.1.1.1"), 8888);
// 参数二是声明接收端所在的网段,这样子就可以接受同一网段里的主机发送的信息
// 我们一般使用当前主机所在的默认网段即可(一般是局域网):
NetworkInterface.getByInetAddress(InetAddress.getLocalHost();
因此实现组播不仅要改发送端源代码,更重要的是修改接收端源代码
发送端源代码更改组播IP即可,与更改为广播方式的发送端步骤一致,不做展示
接收端代码示例:
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
public class ReceiverDemo {
public static void main(String[] args) {
System.out.println("服务器端启动~~~~嘟嘟嘟嘟嘟嘟");
try (
// 创建接收端对象(人),端口号需要和发送端数据包对象的端口号一致
MulticastSocket socket = new MulticastSocket(8888);
){
// 将接收端对象绑定到组播IP中
socket.joinGroup(InetAddress.getByName("224.1.1.1"));
// 创建数据包对象接收数据(盘子)
byte[] bytes = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
// 写循环不断接收数据
while (true) {
// 等待接收数据
// 服务器端被启动后会一直等待接收数据
socket.receive(packet);
// 读取数据
String str = new String(bytes, 0, packet.getLength());
System.out.println("收到了来自" + packet.getAddress() + "的数据,对方端口为:"
+ packet.getPort() + ",数据为:" + str);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
5. 实时通信
实现步骤:
- 创建一个发送端类,使用多线程以及IO流读写数据,封装了端口号与IP地址等成员变量
- 创建一个接收端类,使用多线程以及IO流读写数据,封装了用户名,端口号等成员变量
- 定义俩个类,都实例化发送端与接收端,实现实时通信