Java Udp通信
1. UDP通信原理
UDP协议的特点
1. UDP是一种无连接、不可靠传输协议
2. 将数据源IP、目的地IP和端口以及数据封装成数据包,大小限制在64KB之内,直接发送出去即可
3. 和tcp同属于传输层的一种传输协议,常用于消息接发等需要安全性较低的场景
4. 使用udp通信需具备客户端和服务端,服务端要先于客户端启动
UDP协议只实现了两个功能
1. 在IP协议的基础上添加了端口
2. 对传输过程中可能产生的数据错误进行了检测,并抛弃已经损坏的数据
2. 关键API
java.net包里提供了实现网络应用程序的类
在Java中,我们可以使用DatagramSocket来创建UDP服务器,使用DatagramPacket来装载接受的数据
DatagramSocket这个类是用来发送和接收数据包的套接字类
发送数据包用的方法是: DatagramSocket.send(DatagramPacket p)
接收数据包用的方法是: DatagramSocket.receive(DatagramPacket p)
DatagramPacket这个类是用来存储数据的数据包,一般的用法如下:
//其中data是数据字节数组,address是目标地址,port是目标端口号
DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
3. UDP报文格式
首先要知道UDP是一种无连接的传输层协议。与TCP不同的是,UDP不保证数据完整无误的发送给目标,而是通过IP层的最大努力机制来传递数据的。
每个UDP报文分别由UDP报文头和UDP数据区两部分组成。
其中UDP报文头有8个字节:
源端口(2字节)+目的端口(2字节)+报文长度(2字节)+校验值(2字节)
UDP数据区长度可变,根据报文长度不同而不同。
4. UDP的通信建立的步骤
UDP客户端首先向被动等待联系的服务器发送一个数据报文。一个典型的UDP客户端要经过下面三步操作:
1. 创建一个DatagramSocket实例,可以有选择地对本地地址和端口号进行设置,如果设置了端口号,则客户端会在该端口号上监听从服务器端发送来的数据
2. 使用DatagramSocket实例的send()和receive()方法来发送和接收DatagramPacket实例,进行通信
3. 通信完成后,调用DatagramSocket实例的close()方法来关闭该套接字
由于UDP是无连接的,因此UDP服务端不需要等待客户端的请求以建立连接。另外,UDP服务器为所有通信使用同一套接字,这点与TCP服务器不同,TCP服务器则为每个成功返回的accept()方法创建一个新的套接字。一个典
型的UDP服务端要经过下面三步操作:
1. 创建一个DatagramSocket实例,指定本地端口号,并可以有选择地指定本地地址,此时,服务器已经准备好从任何客户端接收数据报文
2. 使用DatagramSocket实例的receive()方法接收一个DatagramPacket实例,当receive()方法返回时,数据报文就包含了客户端的地址,这样就知道了回复信息应该发送到什么地方
3. 使用DatagramSocket实例的send()方法向服务器端返回DatagramPacket实例
5. UDP通信案例
5.1 服务器端代码
package Udp通信;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class UdpServer {
public static void main(String[] args) throws Exception {
// 1.创建服务器端:注册端口
DatagramSocket socket = new DatagramSocket(3333);
// 2.创建一个数据包对象接收客户端发送的数据
// 创建字节数组,指定接收的数据包的大小
byte[] buf = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
// 3.服务器启动成功,输出提示信息
System.out.println("****服务器端启动成功****");
// 4.通过循环不停的向客户接收数据
new Thread(() -> {
while (true) {
try {
// 4.1 等待接收数据,此方法在接收到数据报之前会一直阻塞
socket.receive(packet);
} catch (IOException e) {
e.printStackTrace();
}
// 4.2 读取数据
int len = packet.getLength();
String rs = new String(buf, 0, len);
System.out.println("收到了ip为:" + packet.getAddress() + " 端口号为:" + packet.getPort() + "的消息:" + rs);
}
}).start();
final InetAddress[] address = {null};
final int[] port = {0};
final boolean[] flag = {false};
// 5.向客户端发送信息
new Thread(() -> {
while (true) {
if (!flag[0]) {
// 5.1 定义客户端的地址、端口号、数据
address[0] = packet.getAddress();//获取发送端的地址
port[0] = packet.getPort();//获取 发送端进程所绑定的端口
flag[0] = true;
}
System.out.println("请输入:");
// 5.2 从键盘接受数据
Scanner scanner = new Scanner(System.in);
//nextLine方式接受字符串
String msg = scanner.nextLine();
// 5.3 创建一个数据包对象封装数据
byte[] buffer = msg.getBytes();
// 5.4 创建数据报,包含响应的数据信息
DatagramPacket packets = new DatagramPacket(buffer, buffer.length, address[0], port[0]);
try {
// 5.5 发送数据给客户端
socket.send(packets);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
5.2 客户端代码
package Udp通信;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class UdpClient {
public static void main(String[] args) throws Exception {
// 1.定义服务器的地址、端口号、数据
InetAddress address = InetAddress.getByName("localhost");
// 2.定义服务器端口
int port = 3333;
// 3.创建发送端对象:发送端自带默认端口号
DatagramSocket socket = new DatagramSocket(2222);
// 4.客户端启动成功,输出提示信息
System.out.println("****客户端启动成功****");
// 5.向服务端发送信息
new Thread(() -> {
try {
while (true) {
System.out.println("请输入:");
// 5.1 从键盘接受数据
Scanner sc = new Scanner(System.in);
// 5.2 nextLine方式接受字符串
String msg = sc.nextLine();
// 5.3 创建一个数据包对象封装数据
byte[] buffer = msg.getBytes();
DatagramPacket packets = new DatagramPacket(buffer, buffer.length, address, port);
// 5.4 发送数据出去
socket.send(packets);
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
// 6.通过循环不停的向服务器接收数据
new Thread(() -> {
try {
while (true) {
// 6.1 创建一个数据包对象接收数据
byte[] buf = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
// 6.2 接收服务器响应的数据
socket.receive(packet);
// 6.3 取出数据
int len = packet.getLength();
String rs = new String(buf, 0, len);
System.out.println("收到了ip为:" + packet.getAddress() + " 端口号为:" + packet.getPort() + "的消息:" + rs);
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}