UDP是什么
UDP是一个传输层的数据包装协议,特点有:
- 无连接(不需要两端连通就可以发送消息给接收端)
- 不可靠传输(无法知道数据是否送达)
- 面向数据报(以数据报为数据传输单位)
- 全双工(两端都可发送可接收)
- 发送数据的大小受限(64KB的缓冲区)
先简单了解一下UDP协议的格式:
协议报头组成大概就是:
源端口号 + 目的端口号 + UDP的长度 + UDP校验和
每一项都是用16bits(2字节)来存储,总共报头大小为64bits也就是8字节。
因为有了16bits的UDP长度限制,所以就是限制了数据的内容不能超过16bits,即64KB的大小,假如有更大的数据就只能拆成多个数据报来传输,这个过程可能会出现数据丢失等问题。所以发送数据的大小受限是UDP协议的一个缺点。
如何使用UDP协议进行传输
使用UDP传输的基本操作有:
- 传输通过DatagramSocket在两个进程之间通讯,数据通过DatagramPacket打包。
- DatagramSocket的参数通常为Port
- DatagramPacket的参数通常为byte[]和byte[]的长度(将字节数组打包到packet中)
- 主机地址由IP决定,进程由端口号(PORT)决定
- 接收端只需要绑定固定的端口号,发送端需要知道接收端的端口号和IP(主机的保留域名为localhost,或者127.0.0.1)
- 创建Socket地址通过InetSocketAddress API,
如何编写最简单的UDP代码
有了上述基本操作我们可以写出对最基本的UDP客户端服务器:
对服务器有:
public class UdpEchoServer {
private static final int PORT = 8888;
public static void main(String[] args) throws IOException {
//创建一个DatagramSocket数据报套接字,用于在进程间通讯
DatagramSocket socket = new DatagramSocket(PORT);
while (true) {
//创建bytes[]来获取数据socket中的内容
//创建DatagramPacket来打包数据(对服务器而言是获取DatagramSocket中的数据)
byte[] bytes = new byte[516];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
//阻塞等待数据报接收
System.out.println("等待接收UDP数据报...");
socket.receive(packet);
//这里执行完后其实已经完成了接收工作
//可以调用一些方法来查看获取的数据、数据来源等
System.out.println("客户端IP:" + packet.getSocketAddress());
System.out.println("客户端Port:" + packet.getPort());
System.out.println("客户端发送的原数据:" + packet.getData());
}
}
}
对客户端有:
public class UdpClient {
private static final SocketAddress address =
new InetSocketAddress("127.0.0.1", 8888);
public static void main(String[] args) throws IOException {
//创建DatagramPacket用于打包数据
DatagramSocket socket = new DatagramSocket();
byte[] bytes = "miss everyday".getBytes();
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, address);
socket.send(packet);
System.out.println("客户端数据已发送...");
}
}
注:对服务器而言,传入PORT就是告诉Socket从这个端口接收数据。对客户端而言,不需要告诉Socket从哪里接收数据,但是需要告诉Socket把数据送去哪个端口,所以把SocketAddress给写入了packet中,由packet去告诉Socket往哪个端口送数据。
知道了如何写出基本的UDP协议下的数据传输后,我们可以稍微升级一下,写一个翻译服务器,即在客户端发送翻译请求,然后服务器返回发送的结果。
对服务器有:
public class UdpDictClient {
private static final SocketAddress address =
new InetSocketAddress("localhost", 4090);
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket();
Scanner scanner = new Scanner(System.in);
while (true) {
byte[] resp = new byte[128];
DatagramPacket packet = new DatagramPacket(resp, resp.length);
System.out.print("请输入要翻译的英文(以.结尾):");
byte[] req = scanner.next().getBytes();
DatagramPacket request = new DatagramPacket(req, req.length, address);
socket.send(request);
System.out.println("翻译请求已发送...");
socket.receive(packet);
String answer = new String(packet.getData());
System.out.println("结果:" + answer);
}
}
}
对客户端有:
public class UdpDictServer {
private static HashMap<String, String> dict;
private static final int PORT = 4090;
public static void main(String[] args) throws IOException {
iniDict();
System.out.println("服务器启动...");
DatagramSocket socket = new DatagramSocket(PORT);
while (true) {
System.out.println("正在等待接收客户端请求...");
byte[] req = new byte[128];
DatagramPacket request = new DatagramPacket(req, req.length);
socket.receive(request);
System.out.println("已收到客户端" +
request.getSocketAddress() + "请求,开始处理...");
String getBytes = new String(request.getData());
char[] chars = getBytes.toCharArray();
StringBuilder stringBuilder = new StringBuilder();
for (char aChar : chars) {
if (aChar == '.') {
break;
} else {
stringBuilder.append(aChar);
}
}
getBytes = stringBuilder.toString();
String resp = dict.getOrDefault(getBytes, "没有找到翻译");
byte[] bytes = resp.getBytes();
DatagramPacket response =
new DatagramPacket(bytes, bytes.length, request.getSocketAddress());
socket.send(response);
System.out.println("已向客户端返回响应...");
System.out.println("------------------");
}
}
private static void iniDict () {
dict = new HashMap<>();
dict.put("Matty", "Lyh");
dict.put("Justin", "比伯");
dict.put("Stephen", "库里");
}
}
这样下来应该就对UDP有一定认识了