MST

星途 面试题库

面试题:Python socket模块下如何实现可靠的UDP通信

在Python中,使用socket模块实现一个类似TCP可靠性的UDP通信机制。这需要你考虑如何处理数据包的丢失、乱序到达等问题。请描述设计思路,并给出关键代码片段。同时解释如何使用校验和等方式确保数据的完整性。
42.7万 热度难度
编程语言Python

知识考点

AI 面试

面试题答案

一键面试

设计思路

  1. 序列号:为每个UDP数据包添加序列号,发送方按顺序递增序列号,接收方通过序列号判断数据包是否乱序。
  2. 确认机制:接收方接收到数据包后,向发送方发送确认消息(ACK),发送方根据ACK判断数据包是否成功到达。若未收到ACK,在一定时间后重传数据包。
  3. 超时重传:发送方为每个发送的数据包设置一个定时器,超时后若未收到ACK则重传。
  4. 窗口机制:限制发送方在未收到ACK时可发送的数据包数量,避免网络拥塞。
  5. 校验和:在数据包中添加校验和字段,发送方计算整个数据包(包括数据和头部)的校验和并填充,接收方重新计算校验和并与接收到的校验和比较,若不一致则丢弃数据包。

关键代码片段

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

校验和确保数据完整性解释

  1. 发送方计算:在发送数据包前,发送方将整个数据包(包括序列号等头部信息和数据部分)作为输入,按16位字进行累加,若数据长度为奇数则补零。累加结果进行溢出回卷,最后取反得到校验和,将校验和添加到数据包头部。
  2. 接收方验证:接收方接收到数据包后,提取校验和字段,重新计算数据包(不包括校验和字段本身)的校验和。将重新计算的校验和与接收到的校验和比较,若相等则数据完整,否则认为数据在传输过程中发生错误,丢弃该数据包。