一,UDP基本概念
UDP协议在传输层,有几个重要的特性:
- UDP是无连接的:UDP想要进行通信,不需要征得对方的同意,只要在send方法中指定目标的地址(UDP自身是不会存储对端的信息的)
- UDP是不可靠传输:UDP没有安全机制,它只负责发送,至于接收端有没有收到,没有收到后怎么处理,它都不关心。(UDP的传输效率更高)
- UDP是面向数据报的:这里的数据报是数据传输的一个单位。
- UDP是全双工的:UDP可以实现双向通信
二,UDP协议端格式
这两个图一个意思。为什么没有源IP和目的IP,因为IP协议在网络层不在传输层。
源端口号和目的端口号讲过了,下面我们重点聊一聊什么是UDP长度和UDP校验和
2.1 UDP长度
UDP长度是两个字节,16位表示的数据,表示范围 0 ~ 65535 => 64kb,即UDP数据报最大就是64kb.
2.2 UDP校验和
在网络传输过程中,可能会出现一些外部干扰,就会导致数据传输出错(传输的数据内容出错)。这时就需要有方法来识别出错数据,而UDP校验和就是这样的一种检查方法。
校验和本质是一个字符串,是通过原始数据通过专门的算法生成的,但是体积比原始数据更小,如果原始数据相同,校验和一定相同,反之,校验和相同,原始数据大概率相同(理论上存在不同的可能性,但是概率极低,可忽略不计)
校验的过程:
1) 发送方把发送的数据整理好称为data1,通过专门的算法,计算出校验和sum1
2) 发送方把data1和sum1一起通过网络发送给接收方
3) 接收方把接受到的数据称为data2(由于干扰,可能数据出错),收到sum2(数据也可能出错)
4) 接收方根据data2按照相同的算法,计算出校验和sum3
5) 对比 sum2 和 sum3 是否相同,如果不同,则认为 data1 和 data2 一定不同。如果相同,则认为data1 和 data2 大概率相同(理论上存在不同的可能性,但是概率极低,工程上忽略不计)
UDP协议中使用CRC算法(循环冗余算法)来计算校验和:把当前计算校验和的数据,每个字节都进行累加,把结果保存到UDP校验和中,如果数据溢出也没关系,如果中间某个数据传输错误,第二次计算的校验和与第一次不同。但是可能出现前一个字节恰好少1,后一个字节恰好多1这种类似的情况,虽然概率不大,但是还是会出现这种情况。
介绍一种更加保险更加常见的算法:md5
1)定长:md5的长度是固定的,无论你的数据有多长,计算出的md5都是固定的
2)分散:给定两个原始数据,哪怕绝大部分内容相同,但只要有一个字节不同,得到的md5值的差异也会很大
3)不可逆:给你一个md5值,要你还原出原始数据,由于计算量过于庞大,已经突破现有的算力极限,理论上是不可能的
三,UDP中的Socket api
Socket API 是系统提供的一组用于网络编程的应用程序接口,socket 本质上是一种特殊的文件,它是把网卡这个设备,给抽象成了文件,往socket文件中写数据,就相当于是通过网卡发送数据,从socket文件中读数据,就相当于通过网卡接收数据。
在 JAVA 中使用 DatagramSocket 这个类来表示系统内部 socket 文件。使用 DatagramPacket 这个类来表示UDP数据报。
3.1 DatagramSocket
|--------------------------------|-----------------------------|
| 方法名 | 说明 |
| DatagramSocket() | 创建一个Socket,绑定到本机任意一个没人使用的端口 |
| DatagramSocket(int port) | 创建一个Socket,绑定port端口 |
| void receive(DatagramPacket p) | 接收数据报p |
| void send(DatagramPacket p) | 发送数据报p |
| void close() | 关闭Socket |
3.2 DatagramPacket
|----------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|
| 方法名 | 说明 |
| DatagramPacket(byte[] buf, int length) | 构造一个DatagramPacket以用来接收数据报,接收的数据保存在 字节数组(第一个参数buf)中,接收指定长度(第二个参数 length) |
| DatagramPacket(byte[] buf, int offset, int length,SocketAddress address) | 构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数 length)。address指定目的主机的IP和端口号 |
| getAddress() | 从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址 |
| int getPort() | 从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号 |
| byte[] getData() | 获取数据报中的数据 |
四,使用UDP实现客户端和服务器
4.1 回显服务器
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
//回显服务器UDP
public class Server {
private DatagramSocket socket = null;
public Server(int port) throws SocketException {
socket = new DatagramSocket(port);//端口号
}
public void start() throws IOException {
System.out.println("服务器启动");
while(true){
//1.读请求
DatagramPacket request = new DatagramPacket(new byte[1024],1024);
socket.receive(request);//如果没有请求,就阻塞
String r = new String(request.getData(),0,request.getLength());//得到真实长度
//2.根据请求计算响应
String response = process(r);
//3.返回响应
DatagramPacket res = new DatagramPacket(response.getBytes(),
response.getBytes().length,request.getSocketAddress());
// response.length()得到字符长度,response.getBytes().length得到字节长度
// 网络传输以字节为单位进行操作
socket.send(res);
//4.打印日志
System.out.printf("[%s:%d] req=%s,res=%s\n",
request.getAddress().toString(),request.getPort(),r,response);
//为什么不close()?
//socket是文件描述符表中的一个表象,因为我们不使用socket时,进程就要关闭,资源就全部释放了
}
}
public String process(String request){
return request;
}
public static void main(String[] args) throws IOException {
Server server = new Server(9090);
server.start();
}
}
4.2 回显客户端
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class Client {
private DatagramSocket socket = null;
private String serverIP = "";
private int serverPort = 0;
public Client(String ip, int port) throws SocketException {
socket = new DatagramSocket();
//UDP不会存储对端的信息,需要在应用程序中把对端的信息记录下来
this.serverIP = ip;
this.serverPort = port;
}
public void start() throws IOException {
System.out.println("客户端请启动");
Scanner scanner = new Scanner(System.in);
while(true){
//1.输入请求
System.out.print("-> ");
String req = scanner.next();
//2.发给服务器
DatagramPacket request = new DatagramPacket(req.getBytes(),req.getBytes().length,
InetAddress.getByName(serverIP),serverPort);
socket.send(request);
//3.获取响应
DatagramPacket res = new DatagramPacket(new byte[1024],1024);
socket.receive(res);
//4.显示响应
String response = new String(res.getData(),0,res.getLength());
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
Client client = new Client("10.162.153.3",9090);
client.start();
}
}