Home Lab 篇六:软路由小包数据慢到拉垮?什么是小包?到底是怎么回事?

2023-05-28 16:03:12 14点赞 93收藏 23评论

在前几的文章中大家在争论软路由小包拉垮的问题。

今天咱们就来聊聊什么是小包,以及为什么小包会拉垮的问题。在文章最后,iN给大家一个测试程序的源代码,大家也可以实际的去测试一下自己家里的路由器到底有多“拉垮”。

实际上“小包拉垮”是一个网路术语(Small Packet Performance Degradation,小包性能拉垮)Degradation直接翻译过来就是“拉垮”的意思。这是一个放之四海皆准的说法。

但为什么会出现“小包拉垮”的问题呢?

这是因为数据在以太网上传输的过程中往往需要先转换为以太网帧,一个标准的以太网帧(不包括物理层的前导码和帧尾部)的最小长度是64个字节,最大长度为1518个字节(由MTU决定)。这个长度包括了目标MAC地址、源MAC地址、长度/类型字段、有效载荷(数据)和校验和字段。数据就承载在以太网帧的有效载荷内。如果要传输的数据大于MTU(最大传输单元)就会在传输的过程中进行切片,重新封装成数个以太网帧。

这里面就需要网卡、路由器、交换机做出各种解封、封装、组合的动作了。通常的情况下在交换机的电路中可以直接处理以太网帧。不过以太网帧是有最小的长度的,也就是64个字节,除去帧的头部14个字节和尾部4个字节之后其有效载荷是46个字节。小于46个字节的纯数据传输都会直接消耗掉一个以太网帧。这也是最小的包长度了。

为什么叫做以太网帧呢?

软路由小包数据慢到拉垮?什么是小包?到底是怎么回事?

因为以太网帧是以太网上有效传输的最小切片信号,你的网络接口带宽在这个层面上有多大并无所谓,关键是你的设备能在单位时间内处理多少以太网帧。所以说其实小包拉垮的问题在任何软硬件网络设备上都是存在的,并不是软路由独有的。

但是到了软路由上,由于来源和目标地址需要软路由再做解析的。软路由并没有专门对应网络数据处理的电路,就需要解码之后再做二次处理和封装。这里就出现了额外的不必要的开销,如果数据包足够大,能以最大传输单元的形式进行传输。软路由反复解码所带来的系统开销就会少一些。但如果是持续的传输小包,要达到一定的传输速率的状况下,软路由的处理开销就相当明显了。尤其是一些软路由是纯靠CPU来计算的,甚至连专门的交换机芯片也不配备,这时候问题就更加显著。

写了个测试程序,在家跑了几轮测试,你会发现当数据包的大小被设置为64个字节的时候

软路由小包数据慢到拉垮?什么是小包?到底是怎么回事?

实际上传输速度也就只有200兆上下,这是因为整个网络系统在处理小包数据,而计算开销的大小要远大实际传输有效数据的大小。

但一旦把网络数据包的大小增加,例如增加到6400个字节:

软路由小包数据慢到拉垮?什么是小包?到底是怎么回事?

网络的速度就一下子跑到了接近于满速率。

那么小包的极限在哪里呢?当我们把数据包的长度设置在400字节的时候,网络还是几乎跑在了满速状态。

软路由小包数据慢到拉垮?什么是小包?到底是怎么回事?

到100个字节的时候,速度一下子就降低下来了:

软路由小包数据慢到拉垮?什么是小包?到底是怎么回事?

这其实就是网络数据包的处理开销远远大于了实际承载有效数据的量。

到设定为16个字节的时候,你会发现实际网速已经仅仅剩下两位数字了

软路由小包数据慢到拉垮?什么是小包?到底是怎么回事?

这些测试,都是在一台专用路由器上做的测试。

软路由小包数据慢到拉垮?什么是小包?到底是怎么回事?

如果换成软路由会是什么样子呢?

恰好,手里还有几个之前玩剩下的软路由,直接换接入到测试的两台电脑上:

软路由小包数据慢到拉垮?什么是小包?到底是怎么回事?

软路由小包数据慢到拉垮?什么是小包?到底是怎么回事?

服务器和测试机,以及线路都不变的情况下,跑16字节的数据小包,软路由跑小包的速度大约是路由器交换机的1/3吧。

这其实就是软路由本身性能问题给网络带来的影响了。而且,在测试软路由的时候,我们还可以发现:

软路由小包数据慢到拉垮?什么是小包?到底是怎么回事?

在测试进行了大约80秒之后,软路由的缓冲区耗尽,包转发速率出现大幅度下降的问题。

这种问题,基本上也都是软路由通用CPU处理网络数据有局限性的实时要背锅的了。

程序给大家附加在文末了,每次测试后,会生成一张图表,大家也可以测测自己的网络到底是怎么样的:

import socket

import threading

import time

import argparse

import matplotlib.pyplot as plt

from datetime import datetime

import numpy as np

def send_small_packet_data(host, port, data, packet_size, test_duration, packet_rates):

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

start_time = time.time()

packet_count = 0

second_count = 0

packet_rate = 0

while time.time() - start_time < test_duration:

try:

sock.sendto(data.encode(), (host, port))

packet_count += 1

elapsed_time = time.time() - start_time

if int(elapsed_time) > second_count:

packet_rate = packet_count

second_count += 1

packet_rates.append(packet_rate)

human_readable_rate = get_human_readable_packet_rate(packet_rate)

transmission_rate = packet_rate * packet_size * 8 # 以比特/秒为单位

human_readable_transmission_rate = get_human_readable_transmission_rate(transmission_rate)

print(f"Sent {packet_rate} packets in last second, current rate: {human_readable_rate}, packet size: {len(data)} bytes, transmission rate: {human_readable_transmission_rate}")

packet_count = 0

except socket.error as e:

print(f"Error sending data: {e}")

break

print(f"Finished sending packets for {second_count} seconds.")

sock.close()

def get_human_readable_packet_rate(packet_rate):

if packet_rate >= 10**9:

return f"{packet_rate / 10**9:.2f} gpps"

elif packet_rate >= 10**6:

return f"{packet_rate / 10**6:.2f} mpps"

elif packet_rate >= 10**3:

return f"{packet_rate / 10**3:.2f} kpps"

else:

return f"{packet_rate:.2f} pps"

def get_human_readable_transmission_rate(transmission_rate):

units = ["bps", "Kbps", "Mbps", "Gbps"]

unit_index = 0

while transmission_rate >= 1000 and unit_index < 3:

transmission_rate /= 1000

unit_index += 1

return f"{transmission_rate:.2f} {units[unit_index]}"

def receive_small_packet_data(host, port, buffer_size, packet_rates):

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

sock.bind((host, port))

start_time = time.time()

packet_count = 0

second_count = 0

packet_rate = 0

try:

while True:

data, _ = sock.recvfrom(buffer_size)

packet_count += 1

elapsed_time = time.time() - start_time

if int(elapsed_time) > second_count:

packet_rate = packet_count

second_count += 1

packet_rates.append(packet_rate)

human_readable_rate = get_human_readable_packet_rate(packet_rate)

print(f"Received {packet_count} packets in last second, current rate: {human_readable_rate}, packet size: {len(data)} bytes")

packet_count = 0

except KeyboardInterrupt:

print(f"Stopped receiving packets. Total received: {packet_count} packets")

sock.close()

def generate_packet_rate_chart(packet_rates, host, port, packet_size, test_duration):

plt.plot(packet_rates)

plt.xlabel('Time (seconds)')

plt.ylabel('Packet Rate (packets/second)')

plt.xticks(np.arange(0, test_duration + 1, step=max(1, test_duration // 10)))

timestamp = datetime.now().strftime("%Y%m%d%H%M%S")

plt.title(f"Packet Rate Test - {timestamp}", fontsize=10)

test_info = f"Test Parameters:n Host: {host} Port: {port} Packet Size: {packet_size} bytes Test Duration: {test_duration} seconds"

plt.figtext(0.5, -0.05, test_info, wrap=True, horizontalalignment='center', fontsize=10)

plt.grid(True)

plt.tight_layout()

plt.savefig(f"packet_rate_{timestamp}.jpeg")

plt.show()

def send_tcp_data(host, port, data, packet_size, test_duration, packet_rates):

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.connect((host, port))

start_time = time.time()

packet_count = 0

second_count = 0

packet_rate = 0

while time.time() - start_time < test_duration:

try:

sock.send(data.encode())

packet_count += 1

elapsed_time = time.time() - start_time

if int(elapsed_time) > second_count:

packet_rate = packet_count

second_count += 1

packet_rates.append(packet_rate)

human_readable_rate = get_human_readable_packet_rate(packet_rate)

transmission_rate = packet_rate * packet_size * 8 # 以比特/秒为单位

human_readable_transmission_rate = get_human_readable_transmission_rate(transmission_rate)

print(f"Sent {packet_rate} packets in last second, current rate: {human_readable_rate}, packet size: {len(data)} bytes, transmission rate: {human_readable_transmission_rate}")

packet_count = 0

except socket.error as e:

print(f"Error sending data: {e}")

break

print(f"Finished sending packets for {second_count} seconds.")

sock.close()

def receive_tcp_data(host, port, buffer_size, packet_rates):

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.bind((host, port))

sock.listen(1)

conn, addr = sock.accept()

start_time = time.time()

packet_count = 0

second_count = 0

packet_rate = 0

try:

while True:

data = conn.recv(buffer_size)

if not data:

break

packet_count += 1

elapsed_time = time.time() - start_time

if int(elapsed_time) > second_count:

packet_rate = packet_count

second_count += 1

packet_rates.append(packet_rate)

human_readable_rate = get_human_readable_packet_rate(packet_rate)

print(f"Received {packet_count} packets in last second, current rate: {human_readable_rate}, packet size: {len(data)} bytes")

packet_count = 0

except KeyboardInterrupt:

print(f"Stopped receiving packets. Total received: {packet_count} packets")

sock.close()

def run_test(server_mode, tcp_mode, host, port, data, packet_size, test_duration):

packet_rates = []

if tcp_mode:

send_func = send_tcp_data

receive_func = receive_tcp_data

else:

send_func = send_small_packet_data

receive_func = receive_small_packet_data

if server_mode:

print("Running in server mode...")

receive_func(host, port, packet_size, packet_rates)

else:

print("Running in client mode...")

start_time = time.time()

thread = threading.Thread(target=send_func,

args=(host, port, data, packet_size, test_duration, packet_rates))

thread.start()

thread.join()

end_time = time.time()

elapsed_time = end_time - start_time

print(f"Sent packets for {elapsed_time:.4f} seconds.")

generate_packet_rate_chart(packet_rates, host, port, packet_size, test_duration)

if __name__ == "__main__":

parser = argparse.ArgumentParser(description="Small Packet Data Test")

parser.add_argument("--server", action="store_true", help="Run in server mode")

parser.add_argument("--client", action="store_true", help="Run in client mode")

parser.add_argument("--tcp", action="store_true", help="Run in TCP mode")

parser.add_argument("host", type=str, help="Server IP address")

parser.add_argument("-z", "--packet-size", type=int, default=64, help="Packet size in bytes (default: 64)")

parser.add_argument("-d", "--test-duration", type=float, default=10.0, help="Test duration in seconds (client mode only, default: 10.0)")

args = parser.parse_args()

host = args.host

port = 12345

data = 'X' * args.packet_size

packet_size = args.packet_size

test_duration = args.test_duration

if args.server:

run_test(True, args.tcp, host, port, data, packet_size, None)

elif args.client:

run_test(False, args.tcp, host, port, data, packet_size, test_duration)

else:

print("Please specify either server mode or client mode.")

展开 收起

ihuman 洪恩 识字子集拼音思维ABC会员永久包3-6岁儿童早教启蒙礼物玩具 识字会员终身包

ihuman 洪恩 识字子集拼音思维ABC会员永久包3-6岁儿童早教启蒙礼物玩具 识字会员终身包

268元起

Microsoft 微软 OFFICE 365 家庭版 会员

Microsoft 微软 OFFICE 365 家庭版 会员

249元起

任天堂 Nintendo Switch《舞力全开 Just Dance》 游戏兑换卡

任天堂 Nintendo Switch《舞力全开 Just Dance》 游戏兑换卡

159元起

WPS 金山软件 WPS 超级会员 3年卡

WPS 金山软件 WPS 超级会员 3年卡

308元起

Microsoft 微软 Office 365 个人版

Microsoft 微软 Office 365 个人版

177元起

Microsoft 微软 到手18.2元/月 微软office365家庭版microsoft365增强版15个月

Microsoft 微软 到手18.2元/月 微软office365家庭版microsoft365增强版15个月

279元起

Microsoft 微软 OFFICE 365 个人版 办公软件

Microsoft 微软 OFFICE 365 个人版 办公软件

185元起

WPS超级会员Pro套餐4年卡1488天官方正版pdf转word排版

WPS超级会员Pro套餐4年卡1488天官方正版pdf转word排版

676.4元起

Microsoft 微软 office专业版永久激活码office2019增强版终身版outlook密钥

Microsoft 微软 office专业版永久激活码office2019增强版终身版outlook密钥

249元起

WPS超级会员4年套餐pdf转word排版PPT润色模板素材店铺

WPS超级会员4年套餐pdf转word排版PPT润色模板素材店铺

暂无报价

国行版 Switch体感游戏套装 《健身环大冒险》

国行版 Switch体感游戏套装 《健身环大冒险》

439元起

WPS 金山软件 会员季卡

WPS 金山软件 会员季卡

59.85元起

Microsoft 微软 办公软件 优惠商品

Microsoft 微软 办公软件 优惠商品

239元起

Microsoft 微软 365 家庭版 电子秘钥 正版高级Office应用 1T云存储

Microsoft 微软 365 家庭版 电子秘钥 正版高级Office应用 1T云存储

299元起

Microsoft 微软 活动6天 office365家庭版microsoft365订阅密钥

Microsoft 微软 活动6天 office365家庭版microsoft365订阅密钥

239元起

Microsoft 微软 office365家庭版15个月 203元

Microsoft 微软 office365家庭版15个月 203元

198元起
23评论

  • 精彩
  • 最新
  • 价格呢,测试用的专用路由器和软路由目前市场价分别是多少呀 [皱眉]

    校验提示文案

    提交
    一个3k多一个1k

    校验提示文案

    提交
    垃圾佬听了直摇头 [皱眉]

    校验提示文案

    提交
    收起所有回复
  • 不要看他这么长不完整的小包说明。实际场景中配置好小包优先就行,90%的小包都是网游。mtu1400,1460,1500测试那个没问题就用那个。没💰ros,有💰panabit配置小包策略优先啥问题都解决了。追求单纯的小包转发性能没有任何意义。家用再不瞎折腾,那点数据量压根用不到任何包级调整。小包优先唯一的实用场景是网吧。

    校验提示文案

    提交
  • 我正好前两天看到油桶有个up谈到这个小包传送问题,他用我们最常见4125畅网演示了一下爱快,ros,openwrt原版和开了大神加速插件的open。具体大家都能在网上找到,我就说说结果,ros和开加速插件的op都可以跑1480和1460数值,单位是啥抱歉我不认识。爱快和原版op的确特别拉垮,只有600多的数值。总结说人话就是用改版OP开加速就没这个问题了。有本事的就用ros。除非是专业人士软路由只能说有影响,但不用太在意,只要选对系统。

    校验提示文案

    提交
    ros只能给一台设备开nat1, 这个就没法用了. openwrt 以前qos 不好用, 现在不知道了. 有空了再去试试.

    校验提示文案

    提交
    收起所有回复
  • 头一次见贴代码贴那么长的。。。还电路这种翻译 [献黄瓜]

    校验提示文案

    提交
  • 所以说硬路由还是有专业优势?或者软路由有什么办法可以去优化呢?主路由用的就是爱快系统

    校验提示文案

    提交
    爱快没有可优化的空间 软路由除了软件的sfe或者flowoffload加速就只剩下dpdk 前者使用场景非常受限 qosvpn等需要cpu处理就无法被加速 后者只有尖端商用才有支持 比如飞塔防火墙和tnsr

    校验提示文案

    提交
    那没必要软路由了吗?

    校验提示文案

    提交
    还有2条回复
    收起所有回复
  • 其实一句话就可以说清楚。就是pps是一定的。如果单个包大,带宽就大。一条路上只能跑10辆车,你跑载重10吨的车和载重100吨的车,运力就不一样

    校验提示文案

    提交
  • 还有另外一个问题,家用的话,什么应用场景下才会遇到这种小包拉胯的情况呢?

    校验提示文案

    提交
  • 说人话就是专业人干专业事。没有调试过的 软路由肯定没有专业硬件好用。 进过调试的可以比肩甚至超越硬路由,重点是你会用。

    校验提示文案

    提交
  • 家里主路由是飞塔fortinet 60e poe 标准的防火墙,大包 中包 小包 3G bps ,我觉得企业级的桌面防火墙下放碾压家用

    校验提示文案

    提交
    俺家折腾专用副宽带 主路由是50E 64也能到2.5G 路由器也省了 闲鱼才300 好用 好用 之前的折腾路由是RB4011 因为是放书桌上 不好看 飞塔的红标系列颜值够高 就它了 让RB4011暂时当交换机了 [皱眉]

    校验提示文案

    提交
    收起所有回复
  • 哈哈哈哈

    校验提示文案

    提交
  • 贴代码的见的不多

    校验提示文案

    提交
  • Mark

    校验提示文案

    提交
  • 收藏起来待用

    校验提示文案

    提交
  • 小白入门怎么配置,主要是打游戏

    校验提示文案

    提交
  • 拿j4125和思科什么的比是这情况,和ros的mikrotik比呢,毕竟这两个才是替代关系

    校验提示文案

    提交
提示信息

取消
确认
评论举报

相关好价推荐
查看更多好价

相关文章推荐

更多精彩文章
更多精彩文章
最新文章 热门文章
93
扫一下,分享更方便,购买更轻松