目录
端口号
UDP协议
UDP协议特点
UDP 无连接
UDP 面向数据报
UDP 全双工
UDP 的报文格式
什么是校验和?
如何基于校验和来完成数据校验呢?
CRC算法(循环冗余算法)
MD5算法/SHA算法(这里只介绍MD5算法(工作中常用))
UDP的服务端和客户端代码(简单的步骤例子)
UDP的服务端
UDP的客户端
本篇主要介绍传输层(TCP/IP五层(或四层)模型)中的 UDP协议
想要详细了解UDP的内容,我们还需要了解一个概念 —— 端口号
端口号
写一个服务器,必须手动指定一个端口号,通过端口来区分当前这个主机上的不同的应用程序;写一个客户端,当客户端在通信的时候也会有一个端口号(代码中感受不到),系统自动分配。
端口号,固定就是占2个字节的 . 意味着,端口号表示的数据范围就是 0 ~ 65535(一般 0 不使用)
1~1023 称为 "知名端口号"
1024~65535 是我们普通的端口号
"知名端口号" :就是给一些比较 ”知名“ 的服务器,预留一个 ”位置“,但事实上,这些所谓的 ”知名“ 服务器,现在大部分都已经不再使用了
其中几个,到现在仍然知名的端口:
22:ssh 服务器的端口号 (ssh协议是用来登录远程主机的)
80:http 服务器的端口号
443:https 服务器的端口号
......
我们要确保使用的端口号和别人的端口号不要重复
介绍完端口号,我们就可以来介绍 UDP了
UDP协议
UDP协议特点
UDP :无连接,不可靠,面向数据报,全双工
UDP 无连接
UDP 协议本身不会存储对端的信息,要在发送数据的时候,显示指定要传输给谁
不可靠,代码中体现不出来
UDP 面向数据报
UDP 全双工
通过一个socket,既可以 send,又可以 receive
(最底下有完整的代码)
UDP 的报文格式
学习一个协议,其中最主要的工作就是理解协议的报文格式
ps:源ip, 目的ip 在 网络层
什么是校验和?
计算机中非常广泛使用的概念
前提:网络传输中,由于一些外部的干扰,就可能会出现数据传输出错的情况。
因此我们就要想办法,能够识别出出错的程序,校验和,就是这样的一种检查手段。
校验和本质上是 一个字符串,体积比原始的数据更小,又是通过原始的数据生成的。
原始数据相同,得到的校验和就一定相同,反之,校验和相同,原始数据大概率相同(理论上存在不同的情况,但实际的概率非常低,忽略不计)
如何基于校验和来完成数据校验呢?
1.发送方,把要发送的数据整理好 —— data1,然后通过一定的算法,计算出校验和 checksum1
2.发送方 把 data1 和 checksum1 一起通过网络发送出去
3.接收方收到数据,收到的数据称为 data2(此时的这个数据可能就和 data1 不一样了),还收到了checksum1
4.接收方再根据data2重新计算校验和(按照相同的算法),得到checksum2
5.对比 checksum1 和 checksum2 是否相同,如果不同,则认为data2 和 data1 一定不相同。如果相同,则认为 data1 和 data2 大概率是相同的(理论上存在不同的可能性,概率低,忽略)
通过上面方式,就能发现数据传输出错
计算校验和,有很多种算法,此处UDP中使用的 CRC算法(循环冗余算法)
CRC算法(循环冗余算法)
就是把当前要计算 校验和 的数据的每个字节,都进行累加,把结果保存到这两个字节的变量中(累加过程中如果溢出了,也没关系)
如果中间某个数据,出现传输错误,第二次计算的 校验和 就会和第一次不同。
ps: CRC 这个算法其实不是特别靠谱,导致两个不同数据,得到相同的CRC校验和的概率比较大
(比如前一个字节恰好少个1,后一个字节恰好多个1,其实概率还是不大的,但确实有一定的风险)
MD5算法/SHA算法(这里只介绍MD5算法(工作中常用))
MD5算法 里有一系列的公式,来完成MD5的计算(咱们不需要考虑公式,这是给数学问题),但是咱们需要知道MD5 的特点
MD5 的特点
1.定长:无论你原始数据多长,计算得到的MD5,都是固定长度 (这就很适配校验和,校验和就不应该太长,要不然不方便网络传输)
2.分散:给定两个原始 数据,哪怕绝大多数内容都一样,但只要有一个字节不同,得到的MD5值都会差异很大
3.不可逆:给你一个原始数据,计算MD5,非常容易,但是给你MD5,让你还原始数据,计算量非常庞大,以至于超出了现有计算机的算力极限,理论上是不可行的
UDP的服务端和客户端代码(简单的步骤例子)
UDP的服务端
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
// 创建一个 DatagramSocket 对象. 后续操作网卡的基础.
private DatagramSocket socket = null;
public UdpEchoServer(int port) throws SocketException {
// 这么写就是手动指定端口
socket = new DatagramSocket(port);
// 这么写就是让系统自动分配端口
// socket = new DatagramSocket();
}
public void start() throws IOException {
// 通过这个方法来启动服务器.
System.out.println("服务器启动!");
// 一个服务器程序中, 经常能看到 while true 这样的代码.
while (true) {
// 1. 读取请求并解析.
DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(requestPacket);
// 当前完成 receive 之后, 数据是以 二进制 的形式存储到 DatagramPacket 中了.
// 要想能够把这里的数据给显示出来, 还需要把这个二进制数据给转成字符串.
String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
// 2. 根据请求计算响应(一般的服务器都会经历的过程)
// 由于此处是回显服务器, 请求是啥样, 响应就是啥样.
String response = process(request);
// 3. 把响应写回到客户端.
// 搞一个响应对象, DatagramPacket
// 往 DatagramPacket 里构造刚才的数据, 再通过 send 返回.
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
requestPacket.getSocketAddress());
socket.send(responsePacket);
// 4. 打印一个日志, 把这次数据交互的详情打印出来.
System.out.printf("[%s:%d] req=%s, resp=%s\n", requestPacket.getAddress().toString(),
requestPacket.getPort(), request, response);
}
}
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
}
UDP的客户端
package network;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverIp = "";
private int serverPort = 0;
public UdpEchoClient(String ip, int port) throws SocketException {
// 创建这个对象, 不能手动指定端口.
socket = new DatagramSocket();
// 由于 UDP 自身不会持有对端的信息. 就需要在应用程序里, 把对端的情况给记录下来.
// 这里咱们主要记录对端的 ip 和 端口 .
serverIp = ip;
serverPort = port;
}
public void start() throws IOException {
System.out.println("客户端启动!");
Scanner scanner = new Scanner(System.in);
while (true) {
// 1. 从控制台读取数据, 作为请求
System.out.print("-> ");
String request = scanner.next();
// 2. 把请求内容构造成 DatagramPacket 对象, 发给服务器.
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
InetAddress.getByName(serverIp), serverPort);
socket.send(requestPacket);
// 3. 尝试读取服务器返回的响应了.
DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(responsePacket);
// 4. 把响应, 转换成字符串, 并显示出来.
String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
//UdpEchoClient client = new UdpEchoClient("42.192.83.143", 9090);
client.start();
}
}