## 1. 从Wireshark到Python:为什么我们需要自动化分析
做嵌入式开发或者网络调试的朋友,肯定对Wireshark不陌生。这个图形化工具确实强大,点点鼠标就能看到网络里流动的所有数据包。但不知道你有没有遇到过这种情况:同事甩给你一个抓包文件,说“帮忙看看UDP是不是丢包了”,你兴冲冲地用Wireshark打开,结果电脑直接卡死——因为那个pcapng文件足足有3.6GB,里面塞了500多万个数据包!
我最近就遇到了这么个事儿。我们在Ubuntu上开发一个程序,每10毫秒就要给MCU发送一帧51字节的UDP数据。MCU那边设定了个超时机制:如果100毫秒内收不到下一帧,就直接报“UDP Lost”错误。理论上这应该很稳定,但实际运行中就是会偶尔丢帧。同事录了40分钟的通信数据,用Wireshark打开直接卡成幻灯片。
这时候你就需要换个思路了。Wireshark的图形界面适合交互式分析,但面对海量数据时,它的性能瓶颈就很明显。更重要的是,我们需要的不是手动翻看500万行数据,而是**自动化地找出所有时间间隔超过100毫秒的异常点**。这就是Python登场的时候了。
用Python分析pcapng文件有几个明显优势。首先是**处理大文件不卡顿**,Python可以流式读取,不需要把整个文件加载到内存。其次是**灵活性高**,你可以写任何自定义的分析逻辑,比如我这里就要计算相邻UDP包的时间差。最后是**可重复性**,一旦写好脚本,下次遇到类似问题直接运行就行,不用再手动操作。
可能你会问,为什么不直接用Wireshark的过滤功能?Wireshark的显示过滤器确实强大,但它主要是用来筛选和查看,对于“找出所有时间间隔大于X的数据包”这种需要跨数据包计算的任务,还是得靠编程。而且,当你需要生成报告、统计丢包率、或者把分析结果集成到其他系统时,Python脚本的优势就更明显了。
## 2. 环境搭建:五分钟搞定Python pcapng解析
工欲善其事,必先利其器。在开始写代码之前,我们得先把环境准备好。这里我假设你用的是Windows系统,但Linux和macOS的步骤也大同小异。
首先确保你安装了Python 3.7或更高版本。打开命令行,输入`python --version`检查一下。如果没有安装,去Python官网下载安装包,记得勾选“Add Python to PATH”那个选项。
接下来就是安装我们今天的主角:**python-pcapng**库。这个库是纯Python实现的,专门用来解析pcapng格式的文件。为什么不用更知名的scapy或者dpkt呢?主要是因为这个库对pcapng格式的支持最完整,而且API设计得很直观,特别适合我们这种“读取文件-分析数据”的场景。
安装命令很简单:
```bash
pip install python-pcapng
```
如果你在国内,可能会觉得下载速度慢,可以加上清华的镜像源:
```bash
pip install python-pcapng -i https://pypi.tuna.tsinghua.edu.cn/simple
```
安装完成后,我们来验证一下。创建一个简单的测试脚本:
```python
import pcapng
print("pcapng库版本:", pcapng.__version__)
```
运行没问题的话,环境就准备好了。这里有个小细节:python-pcapng库的文档其实不算特别丰富,但好在它的源码结构清晰。如果你遇到问题,我建议直接去GitHub上看它的源码和示例。官方仓库地址是`rshk/python-pcapng`,里面有几个简单的例子可以参考。
另外,虽然我们主要用python-pcapng,但我也提一下其他可选方案。**scapy**确实功能强大,能构造和解析各种协议的数据包,但它对大文件的支持不如python-pcapng流畅。**dpkt**更底层一些,需要你对网络协议有更深的理解。对于我们今天的任务——快速解析大文件并分析时间戳——python-pcapng是最合适的选择。
## 3. 深入pcapng文件结构:不只是数据包那么简单
在开始写分析代码之前,我们得先搞清楚pcapng文件里到底有什么。很多人以为抓包文件就是一堆数据包堆在一起,其实不然。pcapng作为pcap的下一代格式,它的结构要复杂得多,也包含了更多元数据。
当你用python-pcapng打开一个文件时,它实际上是在读取一系列的“块”(block)。每个块都有特定的类型和结构。让我用一个实际的例子来说明。假设我们有一个简单的抓包文件,用代码读取它的前几个块:
```python
import pcapng
from pprint import pprint
with open('test.pcapng', 'rb') as fp:
scanner = pcapng.FileScanner(fp)
block_count = 0
for block in scanner:
block_count += 1
print(f"\n=== 第{block_count}个块 ===")
print("块类型:", type(block).__name__)
if block_count == 1:
print("这是Section Header块")
print("硬件架构:", block.hardware)
print("操作系统:", block.os)
print("Wireshark版本:", block.appname)
elif block_count == 2:
print("这是Interface Description块")
print("链路层类型:", block.link_type)
print("快照长度:", block.snaplen)
print("接口名称:", block.options.get('if_name', 'N/A'))
elif hasattr(block, 'packet_payload_info'):
print("这是EnhancedPacket块(数据包)")
print("时间戳:", block.timestamp)
print("捕获长度:", block.packet_payload_info[0])
print("原始长度:", block.packet_payload_info[1])
if block_count >= 5: # 只看前5个块
break
```
运行这段代码,你会看到类似这样的输出:
```
=== 第1个块 ===
块类型: SectionHeader
这是Section Header块
硬件架构: x86_64
操作系统: Linux 5.4.0-91-generic
Wireshark版本: Wireshark 3.4.8
=== 第2个块 ===
块类型: InterfaceDescription
这是Interface Description块
链路层类型: 1 (以太网)
快照长度: 65535
接口名称: eth0
=== 第3个块 ===
块类型: EnhancedPacket
这是EnhancedPacket块(数据包)
时间戳: 1623064879.8129659
捕获长度: 93
原始长度: 93
```
看到了吗?**前两个块根本不是数据包,而是文件的元数据**。第一个Section Header块告诉你这个文件是在什么环境下抓的包,第二个Interface Description块描述了抓包的网卡信息。从第三个块开始才是真正的数据包。
这个认知很重要,因为很多人在写解析代码时会犯一个错误:直接把第一个块当成数据包来处理。结果就是程序崩溃或者得到错误的时间戳。我刚开始用的时候也踩过这个坑,所以特别提醒大家注意。
数据包块(EnhancedPacket)是我们分析的重点。每个EnhancedPacket块都包含这些关键信息:
- `interface_id`: 来自哪个网卡(对应Interface Description块)
- `timestamp`: 捕获时间戳,单位是秒,带小数部分
- `packet_payload_info`: 一个元组,包含(捕获长度, 原始长度, 原始数据)
- `options`: 可选的附加信息
时间戳的存储方式有点特别。它实际上是由两个32位整数组成的:`timestamp_high`和`timestamp_low`。但python-pcapng很贴心地提供了`block.timestamp`属性,直接返回浮点数形式的秒数。这个设计让我们省去了手动转换的麻烦。
## 4. 编写核心分析脚本:定位UDP丢包时间点
理解了文件结构,我们现在可以动手写分析脚本了。我们的目标很明确:找出所有相邻UDP数据包之间时间间隔超过100毫秒(0.1秒)的位置。
先看完整的脚本,然后我一行行解释:
```python
import pcapng
from datetime import datetime
def analyze_udp_latency(pcapng_file, threshold=0.1):
"""
分析pcapng文件中的UDP延迟问题
参数:
pcapng_file: pcapng文件路径
threshold: 时间阈值(秒),默认0.1秒(100毫秒)
"""
cnt = -2 # 计数器,从-2开始是为了跳过前两个非数据包块
old_timestamp = 0
anomalies = [] # 存储所有异常点
print(f"开始分析文件: {pcapng_file}")
print(f"时间阈值: {threshold*1000}毫秒")
print("-" * 50)
with open(pcapng_file, 'rb') as fp:
scanner = pcapng.FileScanner(fp)
for block in scanner:
cnt += 1
# 跳过前两个块(SectionHeader和InterfaceDescription)
if cnt < 0:
continue
# 第一个数据包块,初始化时间戳
if cnt == 0:
old_timestamp = block.timestamp
continue
# 检查是否是UDP包(根据我们的应用,UDP载荷是51字节)
# 注意:packet_payload_info[0]是捕获长度,包含各层协议头
# 以太网头(14) + IP头(20) + UDP头(8) + 数据(51) = 93字节
if block.packet_payload_info[0] == 93:
dt = block.timestamp - old_timestamp
if dt > threshold:
# 将时间戳转换为可读格式
human_time = datetime.utcfromtimestamp(block.timestamp).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
anomaly_info = {
'line_number': cnt + 1, # 调整行号(从1开始计数)
'timestamp': block.timestamp,
'human_time': human_time,
'delay_ms': dt * 1000,
'packet_size': block.packet_payload_info[0]
}
anomalies.append(anomaly_info)
print(f"异常点 #{len(anomalies)}")
print(f" 行号: {anomaly_info['line_number']}")
print(f" 绝对时间: {anomaly_info['human_time']}")
print(f" 延迟: {anomaly_info['delay_ms']:.2f}毫秒")
print(f" 包大小: {anomaly_info['packet_size']}字节")
print("-" * 30)
# 更新旧时间戳
old_timestamp = block.timestamp
print(f"\n分析完成!共发现 {len(anomalies)} 个异常点")
return anomalies
# 使用示例
if __name__ == "__main__":
# 替换为你的pcapng文件路径
result = analyze_udp_latency("2021672005_666.pcapng", threshold=0.1)
# 如果需要保存结果到文件
if result:
with open("analysis_result.txt", "w") as f:
f.write("UDP延迟分析报告\n")
f.write("="*50 + "\n")
for idx, anomaly in enumerate(result, 1):
f.write(f"异常点 #{idx}\n")
f.write(f" 行号: {anomaly['line_number']}\n")
f.write(f" 时间: {anomaly['human_time']}\n")
f.write(f" 延迟: {anomaly['delay_ms']:.2f}毫秒\n\n")
```
这个脚本的核心逻辑其实不复杂,但有几个关键点需要解释:
**计数器的巧妙设计**:`cnt = -2`这个初始值可能看起来有点奇怪。这是因为我们要跳过前两个非数据包块。当`cnt`为-2和-1时,对应的是SectionHeader和InterfaceDescription块,我们直接跳过。当`cnt`变成0时,才是第一个数据包块。
**时间戳的处理**:我们只关心UDP数据包之间的间隔,所以只在遇到UDP包时才计算时间差。`old_timestamp`保存上一个UDP包的时间戳,每次计算完差值后更新它。
**UDP包的识别**:这里我用了一个简单但有效的方法:通过包的总长度来判断。在我们的应用场景中,每个UDP数据包的结构是固定的:以太网头14字节 + IP头20字节 + UDP头8字节 + 数据51字节 = 93字节。所以只要捕获长度是93字节,我就认为它是我们的应用数据包。如果你的应用不同,需要调整这个判断条件。
**性能考虑**:这个脚本是流式读取的,内存占用很小,即使处理几个GB的文件也不会卡。但如果你需要更复杂的分析(比如同时跟踪多个UDP流),可能需要考虑优化。
运行这个脚本,你会看到类似这样的输出:
```
开始分析文件: 2021672005_666.pcapng
时间阈值: 100.0毫秒
--------------------------------------------------
异常点 #1
行号: 231442
绝对时间: 2021-06-07 11:35:22.123
延迟: 208.00毫秒
包大小: 93字节
------------------------------
异常点 #2
行号: 236810
绝对时间: 2021-06-07 11:35:45.678
延迟: 191.80毫秒
------------------------------
...
```
## 5. 高级技巧:处理多流、大文件和性能优化
基础脚本能解决大部分问题,但在实际项目中,我们可能会遇到更复杂的情况。比如,同一个抓包文件里可能有多个UDP流(多个端口对),或者文件特别大需要优化性能,或者我们想分析更复杂的模式。
### 5.1 处理多个UDP流
在实际网络中,可能有多个设备同时在通信。我们的脚本需要能区分不同的UDP流。修改后的版本如下:
```python
import pcapng
import dpkt
from collections import defaultdict
def analyze_multiple_udp_streams(pcapng_file, threshold=0.1):
"""
分析多个UDP流的延迟问题
"""
streams = defaultdict(list) # 每个流保存自己的时间戳序列
stream_last_timestamp = {} # 每个流的上一个时间戳
with open(pcapng_file, 'rb') as fp:
scanner = pcapng.FileScanner(fp)
for block_idx, block in enumerate(scanner):
# 跳过非EnhancedPacket块
if not hasattr(block, 'packet_payload_info'):
continue
# 解析以太网帧
try:
eth = dpkt.ethernet.Ethernet(block.packet_data)
if isinstance(eth.data, dpkt.ip.IP):
ip = eth.data
if isinstance(ip.data, dpkt.udp.UDP):
udp = ip.data
# 构造流标识符:源IP:端口 -> 目的IP:端口
stream_id = f"{dpkt.utils.inet_to_str(ip.src)}:{udp.sport} -> {dpkt.utils.inet_to_str(ip.dst)}:{udp.dport}"
# 获取时间戳
current_ts = block.timestamp
# 检查这个流是否有上一个时间戳
if stream_id in stream_last_timestamp:
last_ts = stream_last_timestamp[stream_id]
dt = current_ts - last_ts
if dt > threshold:
streams[stream_id].append({
'block_idx': block_idx,
'timestamp': current_ts,
'delay_ms': dt * 1000,
'payload_len': len(udp.data)
})
# 更新这个流的上一个时间戳
stream_last_timestamp[stream_id] = current_ts
except Exception as e:
# 有些包可能解析失败,跳过
continue
# 输出结果
for stream_id, anomalies in streams.items():
if anomalies:
print(f"\n流 {stream_id} 发现 {len(anomalies)} 个异常:")
for anomaly in anomalies[:5]: # 只显示前5个
print(f" 包索引: {anomaly['block_idx']}, 延迟: {anomaly['delay_ms']:.2f}ms")
if len(anomalies) > 5:
print(f" ... 还有 {len(anomalies)-5} 个异常")
return streams
```
这个版本使用了`dpkt`库来解析网络协议,能准确识别UDP包的源和目的端口,从而区分不同的流。`defaultdict`用来为每个流自动创建列表,代码更简洁。
### 5.2 大文件性能优化
当处理几十GB的抓包文件时,即使是Python流式读取也可能遇到性能问题。这时候可以考虑以下几个优化策略:
**使用更高效的解析库**:python-pcapng本身已经比较高效,但如果你只需要时间戳和包长度,可以尝试直接读取文件二进制数据,手动解析pcapng格式。不过这样代码会复杂很多,除非性能真的是瓶颈,否则不建议。
**并行处理**:如果文件特别大,可以考虑用多进程并行处理。思路是把大文件拆分成多个小文件,或者让每个进程处理文件的不同部分。但要注意,pcapng文件不能随意拆分,因为数据包之间可能有依赖关系。
**采样分析**:如果不是必须分析每一个包,可以每隔N个包分析一次,或者只分析特定时间段的包。
**使用tshark预处理**:Wireshark的命令行工具tshark非常高效,可以先用tshark提取出需要的信息,再用Python分析。例如:
```bash
# 提取所有UDP包的时间戳和长度
tshark -r huge_file.pcapng -T fields -e frame.time_epoch -e udp.length -Y "udp" > udp_data.txt
```
然后用Python读取这个文本文件进行分析,速度会快很多。
### 5.3 内存映射文件
对于超大文件,可以使用内存映射(mmap)来避免频繁的磁盘IO:
```python
import mmap
import pcapng
def analyze_with_mmap(pcapng_file, threshold=0.1):
with open(pcapng_file, 'rb') as f:
# 创建内存映射
with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
scanner = pcapng.FileScanner(mm)
# 后续分析与之前相同
# ...
```
内存映射让文件看起来像在内存中一样,操作系统会负责按需加载数据,对于随机访问大文件特别有效。
## 6. 结果分析与可视化:从数据到洞察
找到异常点只是第一步,更重要的是理解这些异常背后的原因。我们需要对分析结果进行进一步的统计和可视化。
### 6.1 统计摘要
首先,让我们生成一个统计摘要:
```python
def generate_statistics(anomalies):
"""
生成异常点的统计信息
"""
if not anomalies:
print("没有发现异常点")
return
delays = [a['delay_ms'] for a in anomalies]
stats = {
'total_anomalies': len(anomalies),
'max_delay': max(delays),
'min_delay': min(delays),
'avg_delay': sum(delays) / len(delays),
'median_delay': sorted(delays)[len(delays)//2],
}
# 延迟分布
delay_buckets = {'<200ms': 0, '200-500ms': 0, '500-1000ms': 0, '>1000ms': 0}
for delay in delays:
if delay < 200:
delay_buckets['<200ms'] += 1
elif delay < 500:
delay_buckets['200-500ms'] += 1
elif delay < 1000:
delay_buckets['500-1000ms'] += 1
else:
delay_buckets['>1000ms'] += 1
print("="*50)
print("UDP延迟异常统计报告")
print("="*50)
print(f"异常点总数: {stats['total_anomalies']}")
print(f"最大延迟: {stats['max_delay']:.2f}ms")
print(f"最小延迟: {stats['min_delay']:.2f}ms")
print(f"平均延迟: {stats['avg_delay']:.2f}ms")
print(f"中位数延迟: {stats['median_delay']:.2f}ms")
print("\n延迟分布:")
for bucket, count in delay_buckets.items():
percentage = (count / stats['total_anomalies']) * 100
print(f" {bucket}: {count}次 ({percentage:.1f}%)")
return stats
```
### 6.2 时间序列可视化
用matplotlib绘制延迟的时间分布图:
```python
import matplotlib.pyplot as plt
from datetime import datetime
def plot_delay_timeline(anomalies, output_file="delay_timeline.png"):
"""
绘制延迟异常的时间线图
"""
if not anomalies:
print("没有数据可绘制")
return
# 准备数据
timestamps = [a['timestamp'] for a in anomalies]
delays = [a['delay_ms'] for a in anomalies]
# 将时间戳转换为datetime对象
times = [datetime.fromtimestamp(ts) for ts in timestamps]
# 创建图表
plt.figure(figsize=(12, 6))
# 散点图:每个异常点
plt.scatter(times, delays, alpha=0.6, s=50, label='异常点')
# 添加趋势线(移动平均)
if len(delays) > 10:
window_size = min(20, len(delays)//10)
moving_avg = []
for i in range(len(delays)):
start = max(0, i - window_size//2)
end = min(len(delays), i + window_size//2 + 1)
moving_avg.append(sum(delays[start:end]) / (end - start))
plt.plot(times, moving_avg, 'r-', linewidth=2, label=f'{window_size}点移动平均')
plt.xlabel('时间')
plt.ylabel('延迟 (毫秒)')
plt.title('UDP延迟异常时间分布')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
# 保存图表
plt.savefig(output_file, dpi=150)
print(f"图表已保存到: {output_file}")
plt.show()
```
### 6.3 根本原因分析
在我的实际案例中,通过分析发现了一个有趣的现象:大多数严重延迟(超过1秒)都发生在SSH图形界面操作期间。用Wireshark打开原始文件,跳转到异常点对应的行号(比如299379行),确实能看到SSH流量占据了大量带宽。
这就解释了为什么UDP会丢包:SSH的图形界面传输(比如X11转发)会产生突发的大流量,挤占了网络带宽,导致UDP包被缓冲甚至丢弃。等SSH流量过去后,被积压的UDP包才一股脑发出来,但这时候MCU已经超时报错了。
基于这个发现,我们采取了几个改进措施:
1. **避免在关键通信期间使用SSH图形界面**,改用命令行SSH
2. **在路由器上配置QoS**,给UDP小包更高的优先级
3. **调整MCU的超时机制**,增加一些容错能力
4. **考虑使用更可靠的传输方式**,比如TCP,或者增加应用层的重传机制
## 7. 扩展应用:不仅仅是UDP丢包分析
我们上面讨论的方法其实可以应用到很多其他场景。一旦掌握了用Python分析pcapng文件的基本技能,你就能解决各种网络问题。
### 7.1 TCP重传分析
TCP重传是网络问题的常见指标。我们可以修改脚本来自动识别TCP重传:
```python
def analyze_tcp_retransmissions(pcapng_file):
"""
分析TCP重传
"""
tcp_streams = defaultdict(lambda: {'last_seq': {}, 'retransmissions': []})
with open(pcapng_file, 'rb') as fp:
scanner = pcapng.FileScanner(fp)
for block_idx, block in enumerate(scanner):
if not hasattr(block, 'packet_payload_info'):
continue
try:
eth = dpkt.ethernet.Ethernet(block.packet_data)
if isinstance(eth.data, dpkt.ip.IP):
ip = eth.data
if isinstance(ip.data, dpkt.tcp.TCP):
tcp = ip.data
stream_id = f"{dpkt.utils.inet_to_str(ip.src)}:{tcp.sport}-{dpkt.utils.inet_to_str(ip.dst)}:{tcp.dport}"
direction = 'out' if tcp.sport < tcp.dport else 'in'
# 检查是否是重传
if direction in tcp_streams[stream_id]['last_seq']:
last_seq = tcp_streams[stream_id]['last_seq'][direction]
if tcp.seq <= last_seq:
# 发现重传
tcp_streams[stream_id]['retransmissions'].append({
'block_idx': block_idx,
'timestamp': block.timestamp,
'seq': tcp.seq,
'direction': direction
})
# 更新序列号
tcp_streams[stream_id]['last_seq'][direction] = tcp.seq + len(tcp.data)
except Exception as e:
continue
# 输出结果
for stream_id, data in tcp_streams.items():
if data['retransmissions']:
print(f"流 {stream_id}: {len(data['retransmissions'])} 次重传")
return tcp_streams
```
### 7.2 DNS响应时间分析
DNS解析慢会影响整个应用的体验。我们可以分析DNS查询和响应之间的时间差:
```python
def analyze_dns_response_time(pcapng_file, threshold=0.5):
"""
分析DNS响应时间
"""
dns_queries = {} # 存储查询包,key是事务ID
slow_responses = []
with open(pcapng_file, 'rb') as fp:
scanner = pcapng.FileScanner(fp)
for block_idx, block in enumerate(scanner):
if not hasattr(block, 'packet_payload_info'):
continue
try:
eth = dpkt.ethernet.Ethernet(block.packet_data)
if isinstance(eth.data, dpkt.ip.IP):
ip = eth.data
if isinstance(ip.data, dpkt.udp.UDP):
udp = ip.data
if udp.sport == 53 or udp.dport == 53:
# 尝试解析DNS
dns = dpkt.dns.DNS(udp.data)
if dns.qr == 0: # 查询
dns_queries[dns.id] = {
'timestamp': block.timestamp,
'query': dns.qd[0].name if dns.qd else 'unknown',
'block_idx': block_idx
}
elif dns.qr == 1: # 响应
if dns.id in dns_queries:
query_time = dns_queries[dns.id]['timestamp']
response_time = block.timestamp
rtt = response_time - query_time
if rtt > threshold:
slow_responses.append({
'query': dns_queries[dns.id]['query'],
'rtt': rtt * 1000, # 转毫秒
'query_time': query_time,
'response_time': response_time,
'block_idx': block_idx
})
# 清理已处理的查询
del dns_queries[dns.id]
except Exception as e:
continue
print(f"发现 {len(slow_responses)} 个慢DNS响应(>{threshold*1000}ms)")
for resp in slow_responses[:10]: # 只显示前10个
print(f"查询: {resp['query']}, 响应时间: {resp['rtt']:.2f}ms")
return slow_responses
```
### 7.3 流量模式分析
我们还可以分析流量的时间分布模式,比如识别周期性流量、突发流量等:
```python
def analyze_traffic_pattern(pcapng_file, time_window=1.0):
"""
分析流量模式,统计每个时间窗口内的包数量
"""
from collections import Counter
time_buckets = Counter()
with open(pcapng_file, 'rb') as fp:
scanner = pcapng.FileScanner(fp)
for block in scanner:
if not hasattr(block, 'packet_payload_info'):
continue
# 将时间戳按窗口大小分桶
bucket = int(block.timestamp / time_window)
time_buckets[bucket] += 1
# 转换为时间序列
buckets = sorted(time_buckets.items())
times = [b[0] * time_window for b in buckets]
counts = [b[1] for b in buckets]
# 计算统计信息
avg_pps = sum(counts) / len(counts) if counts else 0
max_pps = max(counts) if counts else 0
min_pps = min(counts) if counts else 0
print(f"流量模式分析(窗口大小: {time_window}秒)")
print(f"平均包率: {avg_pps/time_window:.2f} 包/秒")
print(f"最大包率: {max_pps/time_window:.2f} 包/秒")
print(f"最小包率: {min_pps/time_window:.2f} 包/秒")
# 识别突发流量(超过平均值的2倍)
burst_threshold = avg_pps * 2
bursts = [(times[i], counts[i]) for i in range(len(counts)) if counts[i] > burst_threshold]
if bursts:
print(f"\n发现 {len(bursts)} 个流量突发:")
for time, count in bursts[:5]:
print(f" 时间: {time:.1f}s, 包数: {count}")
return times, counts
```
这些扩展应用展示了Python网络分析脚本的灵活性。一旦你熟悉了基本的pcapng解析,就能根据具体需求定制各种分析工具。我在实际项目中用类似的方法解决过HTTP慢请求、数据库连接问题、甚至安全审计中的异常流量检测。关键是要清楚你的分析目标,然后设计合适的算法来从海量数据中提取有价值的信息。