Java提供了对 TCP Socket 与 UDP Socket 的支持。TCP Socket 可以查看之前的一篇文章(TCP Socket编程)。
相比TCP,UDP是无连接的,只提供数据的不可靠传递,它一旦把应用程序发给网络层的数据发送出去,就不保留数据备份,因此UDP应用一般允许一定量的丢包、出错。但有些应用,如TFTP,如果需要则必须在应用层增加可靠机制。
绝大多数UDP应用都不需要可靠机制,甚至可能因为引入可靠机制而降低性能。流媒体、即时多媒体游戏和IP电话就是典型的UDP应用。如果某个应用需要很高的可靠性,可以用TCP来代替UDP。
Java通过 DatagramSocket 类提供对 UDP Socket 的支持,用 DatagramPacket 类包装发送或者接收的数据。
客户端UDP通信建立步骤:
- 创建 DatagramSocket 实例,如果设置了端口号,则客户端会在该端口号上监听从服务器端发送的数据,否则会随机选择一个端口
- 使用 DatagramSocket 的 send() 和 receive() 方法发送和接收 DatagramPacket 实例
- 通信完成后,调用 DatagramSocket 实例的 close() 方法关闭套接字
UDP Socket Demo
Server端:
public class UDPServer {
private static byte[] temp = new byte[1024];
private static String strSend = "hello,Client!";
public static void server() throws IOException {
//监听4445端口
DatagramSocket socket = new DatagramSocket(4445);
System.out.println("Server is ready to receive data!");
DatagramPacket servRecv = new DatagramPacket(temp, 1024);
boolean flag = true;
while (flag) {
//接收从客户端发送的消息
socket.receive(servRecv);
String strRecv = new String(servRecv.getData(), 0, servRecv.getLength());
System.out.printf("%9s%s%n", "Receive: ", strRecv);
//向客户端发送消息
DatagramPacket servSend = new DatagramPacket(strSend.getBytes(), strSend.length(),
InetAddress.getLocalHost(), 3000);
socket.send(servSend);
System.out.printf("%9s%s%n", "Send: ", strSend);
//重新设置缓冲数组大小
servRecv.setLength(1024);
}
socket.close();
}
public static void main(String[] args) throws IOException {
server();
}
}
Client端:
public class UDPClient {
private static final int TIMEOUT = 5000;
public static String client(String str) throws IOException {
// 监听3000端口
DatagramSocket client = new DatagramSocket();
byte[] temp = new byte[1024];
// 设置超时时间
client.setSoTimeout(TIMEOUT);
// 向服务器的4445端口发送消息
DatagramPacket ctSend = new DatagramPacket(str.getBytes(), str.length(), InetAddress.getLocalHost(), 4445);
// 接收服务器端发送的消息
DatagramPacket ctRecv = new DatagramPacket(temp, 1024);
String strRecvd = null;
boolean flag = true;
while (flag) {
System.out.println("Client is ready to send data!");
client.send(ctSend);
System.out.printf("%9s%s%n", "Send: ", str);
try {
//receive方法是阻塞的
client.receive(ctRecv);
String strRecv = new String(ctRecv.getData(), 0, ctRecv.getLength());
System.out.printf("%9s%s%n", "Receive: ", strRecv);
flag = false;
strRecvd = new String(ctRecv.getData(), 0, ctRecv.getLength());
} catch (SocketTimeoutException e) {
System.out.println("服务器无响应!");
}
}
client.close();
return strRecvd;
}
public static void main(String[] args) throws IOException {
client("hello,Server!");
}
}
输出结果:
Server is ready to receive data!
Receive: hello,Server!
Send: hello,Client!
Client is ready to send data!
Send: hello,Server!
Receive: hello,Client!
注意:需要先启动Server,再启动Client
UDP消息边界
TCP采用面向流的传输,没有消息保护边界,如果发送端连续发送数据,接收端有可能在一次接收动作中接收两个或者更多的数据包。但是,对于数据传输频繁的程序来讲,使用TCP可能会容易粘包。
UDP采用面向消息的传输,存在消息保护边界,接收端一次只能接收发送端发出的一个数据包,因此UDP没有粘包问题。但是当发送数据量较小时,会增加发送和接收开销。因此,最好设置一个大小比较合适的数据包进行UDP数据的发送。
UDP数据包长度
从UDP数据报的报头可以看出,UDP的最大数据报长度是 2 16 − 1 2^{16}-1 216−1个字节。由于UDP报头占8个字节,所以数据部分(数据包)的最大理论长度是 2 16 − 1 − 8 = 65527 2^{16}-1-8 = 65527 216−1−8=65527。
但是在传输过程中,UDP数据报是作为下层协议的数据字段进行传输的,它的长度受到下层IP层和数据链路层协议的制约。
IP数据报的最大长度与UDP相同,IP报头占20个字节,因此在IP层,UDP数据包的最大理论长度为65527-20 = 65507。当一个IP数据报封装成链路层的帧时,此数据报的总长度不能超过链路层规定的MTU(最大传送单元)值,最常用的以太网的MTU值是1500字节。若所传送的数据报长度超过此MTU值,会进行分片处理。
鉴于Internet上的标准MTU值为576字节,所以在进行Internet的UDP编程时,最好将UDP的数据长度控制在504字节(去除最长的IP首部60字节、4字节的富余量、UDP首部8字节)以内。
参考资料:
- UDP Socket 编程
- 浅谈UDP(数据包长度,收包能力,丢包及进程结构选择)
- UDP包的边界
- 维基百科—数据报协议
- 计算机网络(第七版)- 谢希仁