udp分片报文发送和接收

读文件通过udp分片发送的目的端:(包含错误的分片包)

#!/usr/bin/python
# -*- coding: utf-8 -*-

#python send_100frag_file.py -p 55432 -f file_to_send


import argparse
import logging

from scapy.all import *

 # Define the maximum size of a packet
MAX_SIZE = 65500
 # Define the IP addresses and ports
src_ip = '11.0.0.1'
dst_ip = '11.0.0.2'
src_port = 12345
dst_port = 54321
 # Define the payload data
payload_data = 'A' * (MAX_SIZE - 20 - 8)
payload_data = 'A' * MAX_SIZE
# Create the IP header


# 创建参数解析器对象
parser = argparse.ArgumentParser(description="UDP数据接收脚本,可指定端口号和发送数据的文件名: python send_100frag_file.py -p 55432 -f snatdownload")

# 添加端口号参数,指定参数名为port,类型为整数,帮助信息为指定接收UDP数据的端口号,默认值设为9999
parser.add_argument('-p', '--port', type=int, help="指定接收UDP数据的端口号", default=9999)

# 添加文件名参数,指定参数名为filename,类型为字符串,帮助信息为指定保存接收到数据的文件名,默认值设为'received_data.data'

parser.add_argument('-f', '--filename', type=str, help="指定发送数据的文件名", default='input_data.data')
parser.add_argument('-i', '--ipid', type=int, help="指定ip id", default=11111)


# 解析命令行参数
args = parser.parse_args()

# 获取解析后的端口号和文件名
port = args.port
filename = args.filename
ipid = args.ipid

ip_header = IP(src=src_ip, dst=dst_ip, id = ipid)
 # Create the UDP header
#udp_header = UDP(sport=src_port, dport=dst_port+int(sys.argv[1]))
udp_header = UDP(sport=src_port, dport=port)
 # Combine the headers and payload

# 获取文件大小,用于后续判断是否发送完成
file_size = os.path.getsize(filename)
sent_size = 0

# 以二进制模式打开文件,方便处理各种类型的文件
with open(filename, 'rb') as file:
    while True:
        packet = None
        # 每次读取1024字节的数据块,可根据实际网络情况和文件大小调整这个值
        payload_data = file.read(MAX_SIZE)
        if not payload_data:
            break
        # 发送消息,需将消息编码为字节类型,因为网络传输的数据是字节流形式
        
        ip_header = IP(src=src_ip, dst=dst_ip, id = ipid)
        packet = ip_header / udp_header / payload_data
        fragments = fragment(packet, fragsize=656)
        for i, frag in enumerate(fragments):
            print('Fragment ' + str(i+1) + ': ' + str(len(frag)) + ' bytes')
            #print(fragment.show())
            if i < len(fragments) - 0:
                send(frag, verbose=False)
            if i >= 50:
                frag[IP].flags=0
                send(frag, verbose=False)                
        
        ipid += 1
        sent_size += len(payload_data)
        print("发送 {} 字节, 已发送 {} 字节,共 {} 字节".format(len(payload_data), sent_size, file_size))
        

end_marker = "END".encode('utf-8')
packet = ip_header / udp_header / end_marker
send(packet, verbose=False)
print("发送结束标志,发送结束")

接收指定端口收到的udp报文保存到本地文件,通过比较发送文件和接收文件的内容测试中间转发设备是否正确完整的处理了分片和重组:

#!/usr/bin/python
# -*- coding: utf-8 -*-

#python recv_dgram.py  -p 55432 -f my_data.txt

import socket
import argparse
import logging

# 创建参数解析器对象
parser = argparse.ArgumentParser(description="UDP数据接收脚本,可指定端口号和保存数据的文件名: python recv_dgram.py  -p 55432 -f my_data.txt")

# 添加端口号参数,指定参数名为port,类型为整数,帮助信息为指定接收UDP数据的端口号,默认值设为9999
parser.add_argument('-p', '--port', type=int, help="指定接收UDP数据的端口号", default=9999)

# 添加文件名参数,指定参数名为filename,类型为字符串,帮助信息为指定保存接收到数据的文件名,默认值设为'received_data.data'
parser.add_argument('-f', '--filename', type=str, help="指定保存接收到数据的文件名", default='received_data.data')

# 解析命令行参数
args = parser.parse_args()

# 获取解析后的端口号和文件名
port = args.port
file_name = args.filename

# 创建UDP套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 绑定本地地址和端口,这里假设绑定到本地所有可用地址(空字符串)的9999端口,你可以按需修改
local_address = ('', port)
udp_socket.bind(local_address)
udp_socket.settimeout(5)  # 设置超时时间为5秒

# 以二进制写入模式打开文件,方便处理各种类型的数据(文本、二进制等)
tot_len = 0
with open(file_name, 'wb') as file:
    while True:
        try:
            # 接收数据报,设置缓冲区大小为1024字节,可根据实际情况调整大小
            udp_socket.settimeout(5)  # 设置超时时间为5秒
            data, sender_address = udp_socket.recvfrom(65530)
            if not data:
                break
            if len(data) == 3 and data.decode('utf-8') == "END":
                logging.info("完成接收")
                print("完成接收")
                break
            tot_len += len(data)
             # 将接收到的数据写入文件
            file.write(data)
            print("从 {}接收到{}字节, total length: {}".format(sender_address, len(data), tot_len))

        except socket.timeout:
            logging.info("接收超时,可能暂时没有消息到达")
            continue
        except socket.error as e:
            print("接收UDP数据报时出错: {}".format(e))
            break

# 关闭套接字
udp_socket.close()
print("已成功将接收到的UDP数据报保存到 {} 文件中。".format(file_name))

### TCP 报文分片UDP 报文分片的区别 #### 分片处理方式 对于 **TCP** 协议而言,由于其面向连接并提供可靠传输服务,因此在遇到网络路径最大传输单元 (MTU) 较小时,TCP 可以通过调整最大报文段长度(MSS, Maximum Segment Size)来避免 IP 层的分片操作。然而,当 MSS 设置不当或中间路由器 MTU 过低时,仍可能发生 IP 层的分片现象[^1]。 ```python # Python伪代码展示如何设置TCP socket选项中的MSS import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) mss_value = 1460 # 假设的最大报文段大小 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG, mss_value) ``` 相比之下,**UDP** 并不直接参与分片决策;相反,它依赖于底层 IP 层来进行必要的分片工作。这意味着一旦应用程序发送的数据量超过了当前链路层所能承载的最大尺寸,则这些数据会被分割成多个较小的部分并通过独立的数据包形式传送至目的地。值得注意的是,UDP 不具备内置机制用于检测或管理因IP级分片而产生的任何问题,这可能导致部分片段丢失而不被察觉的情况发生。 #### 数据重组过程 在一个成功的通信过程中: - 对于 **TCP** 来说,即使发生了 IP 级别的分片,最终目的主机上的协议栈也会负责重新组装原始消息,并按照正确的顺序交付给上层应用。这是因为 TCP 维护着一个滑动窗口机制,能够追踪已发出但尚未确认的消息序列号,从而确保所有接收到的数据都能按序排列。 - 而对于 **UDP** ,情况则有所不同。由于缺乏类似 TCP 的流量控制拥塞控制特性,再加上没有专门针对分片管理重组的功能设计,使得整个过程更加复杂且容易失败。特别是当某个分片未能成功抵达目标节点时,其余已经到达的目的地将无法完成完整的数据恢复,除非采取额外措施如自定义的应用程序级别重试逻辑。 #### 性能影响 从性能角度来看: - 使用 **TCP** 发送大量数据可能会触发更频繁的 ACK 窗口更新消息交换,增加了往返时间延迟(RTT),但这有助于维持稳定性可靠性。另外,尽管存在潜在的 IP 分片风险,但由于现代互联网基础设施普遍支持较大的 MTUs,实际遭遇这种情况的概率相对较低。 - 就 **UDP** 而言,虽然理论上它可以实现更低的开销更高的吞吐率,但在面对较大规模的数据传输需求时,过度依赖 IP 分片反而可能带来负面效果——不仅增加了丢包的可能性,还加重了末端系统的负担,尤其是在需要执行复杂的分片重组任务的情况下。此外,如果某些关键分片网络中遗失,则整个事务都不得不放弃或重启,严重影响用户体验服务质量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值