UDP协议

1 简述

用户数据报协议(User Datagram Protocol) 简称UDP协议,相对于TCP协议,它的协议简单很多,因为它即不需要建立连接,也不需保证可靠性,和TCP协议的连接性、可靠性,形成鲜明对比,也正因如此,反而突显了它的独特性,在传输层跟TCP并驾齐驱的地位。

HTTP/3 在2022-06-06正式发布,它的数据传输基于UDP协议,可以看出该协议旺盛的生命力。

本文对UDP协议关键概念进行整理,分析它如何进行网络间数据传递


2 特点

2.1 简单性

个人理解,简单是它最主要的特点, 它没有所谓的握手、断开、重置等逻辑,也不提供流量控制,当然更没有数据重传机制,一会儿看了它的"协议格式",就知道它是如何精炼。

2.2 不可靠

数据报文在传输过程中,如果有分段丢失, 不能重构完整报文,则直接丢弃该报文,也不能保证报文间的时序性。

2.3 速度快

因为它的数据传输方式非连接、不可靠,也不控制流量,因此传输速度快,这是其主要优点,适用于需要大量、及时但对数据完整性要求不是很高的业务场景。

3 协议格式

通过Wireshark工具,对网络数据抓包,会发现它的包(frame)数据被一层一层包裹,而我们(进程)真实传递的"数据"被包裹在最里面的那一层。最常见的数据包是3层: 最外面是Ethernet(以太帧)的协议头,中间是IP协议头, 最里面是UDP协议头和包裹的真实数据,其中Ethernet帧、IP协议在上一篇"TCP协议"部分已叙述,下面只描述UDP协议格式。

3.1 UDP

UDP协议位于IP协议上层,被IP协议包裹,也就是IP分组数据区内容,下面是它的格式:

来源端口、目的端口:它和IP地址组合后,构成了完整的UDP发送、接收地址。
消息长度: 占2个字节,值为UDP分组的总长度(包含报头及数据区)。

该协议是不是很精简!

3.2 完整包

在网络中自始至终传递的是IP分组,在传递过程中,经过不同的网络,根据物理层真实的链路层协议,对IP分组进行打包传递,以下是局域网以太帧的完整格式:

 
4. 数据传输

从上图可以看出,客户端、服务端不需要建立、断开连接处理,它的数据传递,除receive数据,需要等待阻塞外,基本上没有阻塞环节,这也是UDP数据传递快的原因。

它以报文为单位进行数据传递,在报文超过MSS字节限制后,IP协议会对其进行分段,到达目的地后会重新构建报文,这个过程,在应用层无感知,接收端可能会丢失报文,但收到的报文,可以保证它的完整性、正确性。(前提:确保双方报文缓存大小一致)

再就是,以太网每帧数据传递有最大字节限制,约1.4k字节,有些物理层可能会更少,需视情况而定,IP协议标准规定一般不会低于576字节,如果网络较差, 建议报文尽量别太大,否则超过限值,IP协议就会进行分段处理, 只要其中一个IP分段没有收到,就会丢掉整个报文,影响数据包送达率


5. 实践-java

下面提供阻塞方式处理例子,通过抓包可以检测,UDP的数据传输,不再需要握手建立连接,也不关心接受端是否能正确收到报文,除了网络原因导致的丢包外,如果接受端不能及时读取报文,就会出现"接受缓存区"溢出,最后导致后续的报文,被抛弃处理。

客户端:

package mtr.demo.tcp.blog;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UdpClient {

	private static final int RECEIVE_BUFFER_SIZE = 10 * 1024 * 1024;
	private static final int SEND_BUFFER_SIZE = 10 * 1024 * 1024;
	private static final int READ_TIMEOUT_MILLISECONDS = 30000;

	public static void main(String[] args) throws Exception {
		
		// 创建socket
		DatagramSocket socket = createDatagramSocket();
		
		try {

			// 发送报文
			for(int i = 0; i < 3; i++) {
				String msg = buildData(i * 10, 10);
				
				DatagramPacket packet = new DatagramPacket(msg.getBytes(), msg.getBytes().length, InetAddress.getByName("192.168.0.106"), 8888);
				socket.send(packet);
				System.out.println(String.format("序列%d, 已发送:%s", i, msg));
			}
			
			// 接收报文
			do {
				DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);
				socket.receive(packet);
				String msg = new String(packet.getData(), 0, packet.getLength(), "utf-8");
				System.out.println("收到报文:" + msg);				
			} while(true);
		} finally {
			socket.close();
			System.out.println("close udp socket");
		}
	}
	
	/*
	 * 创建DatagramSocket
	 */
	static DatagramSocket createDatagramSocket() throws IOException {
		// 创建DatagramSocket, 分配临时接口
		DatagramSocket socket = new DatagramSocket();
		socket.setReceiveBufferSize(RECEIVE_BUFFER_SIZE);
		socket.setSendBufferSize(SEND_BUFFER_SIZE);
		socket.setSoTimeout(READ_TIMEOUT_MILLISECONDS);
		
		System.out.println("creata udp socket, local-port: " + socket.getLocalPort());
	
		return socket;
	}
	
	static String buildData(int start, int len) {
		StringBuilder builder = new StringBuilder();
		
		for(int i = 0; i < len; i++ ) {
			builder.append(String.format("%09d,", start + i));
		}
		return builder.toString();
	}	
}

服务端:

package mtr.demo.tcp.blog;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UdpServer {
	
	private static final int RECEIVE_BUFFER_SIZE = 10 *1024 * 1024;	

	public static void main(String[] args) throws Exception {
		DatagramSocket socket = new DatagramSocket(8888); 
		socket.setReceiveBufferSize(RECEIVE_BUFFER_SIZE);
		
		try {
			System.out.println("开始接受报文...");
			DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);
			int count = 0;
			while(true) {
				count++;
				
				// 接受
				socket.receive(packet);
				String msg = new String(packet.getData(), 0, packet.getLength());
				System.out.println(String.format("收到报文, order:%d, size:%d, data:%s", count, packet.getLength(), msg));
				
				// 回复
				byte[] reply = String.format("receive data order:%d, size: %d", count, packet.getLength()).getBytes();
				socket.send(new DatagramPacket(reply, reply.length, packet.getSocketAddress()));
			}
			
		} finally {
			socket.close();
		}
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值