设计思路
- 序列号:为每个UDP数据包添加序列号,发送方按顺序递增序列号,接收方通过序列号判断数据包是否乱序。
- 确认机制:接收方接收到数据包后,向发送方发送确认消息(ACK),发送方根据ACK判断数据包是否成功到达。若未收到ACK,在一定时间后重传数据包。
- 超时重传:发送方为每个发送的数据包设置一个定时器,超时后若未收到ACK则重传。
- 窗口机制:限制发送方在未收到ACK时可发送的数据包数量,避免网络拥塞。
- 校验和:在数据包中添加校验和字段,发送方计算整个数据包(包括数据和头部)的校验和并填充,接收方重新计算校验和并与接收到的校验和比较,若不一致则丢弃数据包。
关键代码片段
import socket
import time
import struct
# 模拟UDP套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(1) # 设置超时时间
# 发送方
def send_data(data, addr):
seq_num = 0
window_size = 5
base = 0
send_buffer = []
while True:
# 将数据分块并添加序列号、校验和
for i in range(window_size):
if base + i < len(data):
packet = struct.pack('!I', seq_num + i) + data[base + i:base + i + 1024]
checksum = calculate_checksum(packet)
packet = struct.pack('!I', checksum) + packet
send_buffer.append(packet)
sock.sendto(packet, addr)
# 等待ACK
try:
while True:
ack, _ = sock.recvfrom(1024)
ack_seq_num = struct.unpack('!I', ack)[0]
if ack_seq_num >= base:
base = ack_seq_num + 1
if base >= len(data):
return
except socket.timeout:
# 超时重传
for i in range(window_size):
if base + i < len(data):
sock.sendto(send_buffer[i], addr)
# 接收方
def receive_data():
received_data = b''
expected_seq_num = 0
while True:
try:
packet, addr = sock.recvfrom(1024)
received_checksum = struct.unpack('!I', packet[:4])[0]
packet = packet[4:]
calculated_checksum = calculate_checksum(packet)
if received_checksum == calculated_checksum:
seq_num = struct.unpack('!I', packet[:4])[0]
if seq_num == expected_seq_num:
received_data += packet[4:]
expected_seq_num += 1
sock.sendto(struct.pack('!I', seq_num), addr)
except socket.timeout:
continue
return received_data
# 计算校验和
def calculate_checksum(data):
if len(data) % 2 != 0:
data += b'\x00'
checksum = 0
for i in range(0, len(data), 2):
word = (data[i] << 8) + data[i + 1]
checksum += word
while checksum >> 16:
checksum = (checksum & 0xFFFF) + (checksum >> 16)
return ~checksum & 0xFFFF
校验和确保数据完整性解释
- 发送方计算:在发送数据包前,发送方将整个数据包(包括序列号等头部信息和数据部分)作为输入,按16位字进行累加,若数据长度为奇数则补零。累加结果进行溢出回卷,最后取反得到校验和,将校验和添加到数据包头部。
- 接收方验证:接收方接收到数据包后,提取校验和字段,重新计算数据包(不包括校验和字段本身)的校验和。将重新计算的校验和与接收到的校验和比较,若相等则数据完整,否则认为数据在传输过程中发生错误,丢弃该数据包。