1 UDP通信机制
Internet 协议集支持一个无连接的传输协议,该协议称为用户数据报协议(UDP,User Datagram Protocol)。与TCP协议不同,UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。在发送数据前,需要进行封包操作(使用 DatagramPacket 类),才能发送和接收数据(使用 DatagramSocket 类)。
使用UDP传输数据时,可能存在的问题有:
丢失包;
乱序;
错误包;
数据包的重复发送。
通常需要使用UDP的场景主要是对数据流要求不高时,如视频流、音频流等。
2 UDP 通信编程的一般步骤
-
创建客户端的 DatagramSocket 并定义客户端用来接收报文的端口;
-
创建服务器端的 DatagramSocket 并定义服务端用来接收报文的端口;
-
在服务器端创建 DatagramPacket 对象,封装待发送的数据包;
-
客户端发送报文;
-
服务器端接收报文。
3 服务端
服务端接收数据使用 DatagramSocket
对象,创建对象时,需要指定一个用于接收请求的端口。
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UdpServer {
public static void main(String[] args) {
try {
//创建服务端接收数据的 DatagramSocket 对象
DatagramSocket datagramSocket = new DatagramSocket(5555);
//创建数据缓冲区
byte[] buff = new byte[1024];
//创建数据报的包对象
DatagramPacket packet = new DatagramPacket(buff, buff.length);
//等待接收客户端发送的数据
datagramSocket.receive(packet);
//获取数据
String input = new String(packet.getData(), 0, packet.getLength());
System.out.println("接收到客户端的请求: " + input);
} catch (Exception e) {
e.printStackTrace();
}
}
}
在接收客户端发来的请求时,使用了字节缓冲区,可以一次性接收 1024 个字节的数据,可以减少网络IO的操作。
在服务端和客户端之间的数据流使用 DatagramPacket
封装,因此在接收数据时,应使用该对象进行接收。
datagramSocket.receive(packet);
方法是一个阻塞式方法,当没有接收到数据时,程序将在这里等待,接收到数据后,才会继续执行下边的代码。
在接收到字节数据后,如果不想直接使用二进制数据,需要对数据进行转换。
在获取接收到的数据时,使用的是 new String(packet.getData(), 0, packet.getLength())
,并没有读取缓冲区的所有内容,因为当从网卡中读取的实际数据,若长度小于缓冲区长度时,其数据对于服务端来说是没有意义的。我们仅需要拿走实际接收到的数据报即可。
4 客户端
创建客户端时,也需要创建一个 DatagramSocket 对象,同时指定发送数据的端口,如果和服务端在同一台机器上时,该端口应与服务端不同。
在客户端发送数据时,需要将数据封装到 DatagramPacket 对象中,同时需要指定接收数据的服务端IP及端口。
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.util.Scanner;
public class UdpClient {
public static void main(String[] args) {
try {
//创建数据发送对象,并指定要发送数据的端口
DatagramSocket socket = new DatagramSocket(5556);
//将要发送的数据转换为字节数组
Scanner scanner = new Scanner(System.in);
String output = scanner.next();
byte[] bytes = output.getBytes();
//创建数据报的包对象
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, new InetSocketAddress("127.0.0.1", 5555));
//发送消息
socket.send(packet);
} catch (Exception e) {
e.printStackTrace();
}
}
}
5 通信
5.1 不启动服务端
当没有启动服务端,只启动客户端发送数据时,客户端将数据正常发出而没有报错。
可见客户端在发送数据时,并没有对服务端进行检查。
5.2 启动服务端
这是一次通信,如果想要建立多次通信,可以在服务端加上循环,方法同 TCP 通信类似。