2025-04-18 10:33:51 +08:00
|
|
|
|
import 'dart:collection';
|
|
|
|
|
|
|
|
|
|
|
|
class PacketLossStatistics {
|
|
|
|
|
|
static final PacketLossStatistics _instance =
|
|
|
|
|
|
PacketLossStatistics._internal();
|
|
|
|
|
|
factory PacketLossStatistics() => _instance;
|
|
|
|
|
|
PacketLossStatistics._internal();
|
|
|
|
|
|
|
|
|
|
|
|
// 记录每个messageId的分包信息
|
|
|
|
|
|
// key: messageId, value: {totalPackets, receivedPackets}
|
|
|
|
|
|
final Map<int, PacketInfo> _packetsMap = HashMap();
|
|
|
|
|
|
|
2025-04-25 10:21:05 +08:00
|
|
|
|
// 配置参数
|
|
|
|
|
|
int _maxCapacity = 300; // 最大容量为300条记录
|
|
|
|
|
|
int _timeoutMs = 30000; // 默认超时时间为30秒
|
|
|
|
|
|
|
2025-04-18 10:33:51 +08:00
|
|
|
|
// 统计信息
|
|
|
|
|
|
int _totalMessages = 0; // 总消息数
|
|
|
|
|
|
int _lostMessages = 0; // 丢包的消息数
|
|
|
|
|
|
int _totalPackets = 0; // 总分包数
|
|
|
|
|
|
int _lostPackets = 0; // 丢失的分包数
|
|
|
|
|
|
|
|
|
|
|
|
// 记录分包数据
|
|
|
|
|
|
void recordPacket(int messageId, int currentIndex, int totalPackets) {
|
2025-04-25 10:21:05 +08:00
|
|
|
|
// 定期清理超时记录
|
|
|
|
|
|
_cleanupExpiredPackets();
|
|
|
|
|
|
|
|
|
|
|
|
// 检查容量限制
|
|
|
|
|
|
_checkCapacityLimit();
|
|
|
|
|
|
|
2025-04-18 10:33:51 +08:00
|
|
|
|
if (!_packetsMap.containsKey(messageId)) {
|
|
|
|
|
|
_packetsMap[messageId] = PacketInfo(totalPackets);
|
|
|
|
|
|
_totalMessages++;
|
|
|
|
|
|
_totalPackets += totalPackets;
|
2025-04-25 10:21:05 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 更新时间戳
|
|
|
|
|
|
_packetsMap[messageId]!.timestamp = DateTime.now().millisecondsSinceEpoch;
|
2025-04-18 10:33:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_packetsMap[messageId]!.receivedPackets.add(currentIndex);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果收到了该messageId的最后一个包,进行统计
|
|
|
|
|
|
if (currentIndex == totalPackets) {
|
|
|
|
|
|
_checkPacketLoss(messageId);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-25 10:21:05 +08:00
|
|
|
|
// 清理超时的记录
|
|
|
|
|
|
void _cleanupExpiredPackets() {
|
|
|
|
|
|
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
|
|
|
|
|
final expiredMessageIds = <int>[];
|
|
|
|
|
|
|
|
|
|
|
|
_packetsMap.forEach((messageId, info) {
|
|
|
|
|
|
// 如果记录超时,添加到待清理列表
|
|
|
|
|
|
if (currentTime - info.timestamp > _timeoutMs) {
|
|
|
|
|
|
expiredMessageIds.add(messageId);
|
|
|
|
|
|
|
|
|
|
|
|
// 统计丢包
|
|
|
|
|
|
_lostMessages++;
|
|
|
|
|
|
_lostPackets += (info.totalPackets - info.receivedPackets.length);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 移除超时记录
|
|
|
|
|
|
for (var messageId in expiredMessageIds) {
|
|
|
|
|
|
_packetsMap.remove(messageId);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查并确保不超过最大容量
|
|
|
|
|
|
void _checkCapacityLimit() {
|
|
|
|
|
|
if (_packetsMap.length <= _maxCapacity) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果超过容量限制,按时间戳排序并删除最旧的记录
|
|
|
|
|
|
var entries = _packetsMap.entries.toList()
|
|
|
|
|
|
..sort((a, b) => a.value.timestamp.compareTo(b.value.timestamp));
|
|
|
|
|
|
|
|
|
|
|
|
// 计算需要移除的数量(移除25%的旧记录,至少保证有一定空间)
|
|
|
|
|
|
int removeCount = (_packetsMap.length * 0.25).ceil();
|
|
|
|
|
|
|
|
|
|
|
|
// 移除并统计丢包
|
|
|
|
|
|
for (int i = 0; i < removeCount && i < entries.length; i++) {
|
|
|
|
|
|
var entry = entries[i];
|
|
|
|
|
|
_lostMessages++;
|
|
|
|
|
|
_lostPackets +=
|
|
|
|
|
|
(entry.value.totalPackets - entry.value.receivedPackets.length);
|
|
|
|
|
|
_packetsMap.remove(entry.key);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-18 10:33:51 +08:00
|
|
|
|
// 检查丢包情况
|
|
|
|
|
|
void _checkPacketLoss(int messageId) {
|
|
|
|
|
|
final info = _packetsMap[messageId]!;
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否有丢失的包
|
|
|
|
|
|
int received = info.receivedPackets.length;
|
|
|
|
|
|
if (received < info.totalPackets) {
|
|
|
|
|
|
_lostMessages++;
|
|
|
|
|
|
_lostPackets += (info.totalPackets - received);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清理该messageId的记录,避免内存泄漏
|
|
|
|
|
|
_packetsMap.remove(messageId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取丢包率统计信息
|
|
|
|
|
|
PacketLossInfo getStatistics() {
|
|
|
|
|
|
if (_totalMessages == 0 || _totalPackets == 0) {
|
|
|
|
|
|
return PacketLossInfo(0.0, 0.0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算消息级别的丢包率
|
|
|
|
|
|
double messageLossRate = (_lostMessages / _totalMessages) * 100;
|
|
|
|
|
|
|
|
|
|
|
|
// 计算分包级别的丢包率
|
|
|
|
|
|
double packetLossRate = (_lostPackets / _totalPackets) * 100;
|
|
|
|
|
|
|
|
|
|
|
|
return PacketLossInfo(messageLossRate, packetLossRate);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-25 10:21:05 +08:00
|
|
|
|
// Getter和Setter,允许外部调整参数
|
|
|
|
|
|
int get maxCapacity => _maxCapacity;
|
|
|
|
|
|
set maxCapacity(int value) {
|
|
|
|
|
|
if (value > 0) {
|
|
|
|
|
|
_maxCapacity = value;
|
|
|
|
|
|
// 设置新容量后立即检查
|
|
|
|
|
|
_checkCapacityLimit();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int get timeoutMs => _timeoutMs;
|
|
|
|
|
|
set timeoutMs(int value) {
|
|
|
|
|
|
if (value > 0) {
|
|
|
|
|
|
_timeoutMs = value;
|
|
|
|
|
|
// 设置新超时后立即清理
|
|
|
|
|
|
_cleanupExpiredPackets();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前未完成记录数
|
|
|
|
|
|
int get pendingRecordsCount => _packetsMap.length;
|
|
|
|
|
|
|
2025-04-18 10:33:51 +08:00
|
|
|
|
// 重置统计数据
|
|
|
|
|
|
void reset() {
|
|
|
|
|
|
_packetsMap.clear();
|
|
|
|
|
|
_totalMessages = 0;
|
|
|
|
|
|
_lostMessages = 0;
|
|
|
|
|
|
_totalPackets = 0;
|
|
|
|
|
|
_lostPackets = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 分包信息类
|
|
|
|
|
|
class PacketInfo {
|
|
|
|
|
|
final int totalPackets;
|
|
|
|
|
|
final Set<int> receivedPackets = HashSet<int>();
|
2025-04-25 10:21:05 +08:00
|
|
|
|
int timestamp; // 添加时间戳字段,记录最后更新时间
|
2025-04-18 10:33:51 +08:00
|
|
|
|
|
2025-04-25 10:21:05 +08:00
|
|
|
|
PacketInfo(this.totalPackets)
|
|
|
|
|
|
: timestamp = DateTime.now().millisecondsSinceEpoch;
|
2025-04-18 10:33:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 丢包统计信息类
|
|
|
|
|
|
class PacketLossInfo {
|
|
|
|
|
|
final double messageLossRate; // 消息丢失率
|
|
|
|
|
|
final double packetLossRate; // 分包丢失率
|
|
|
|
|
|
|
|
|
|
|
|
PacketLossInfo(this.messageLossRate, this.packetLossRate);
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
String toString() {
|
|
|
|
|
|
return 'Message Loss Rate: ${messageLossRate.toStringAsFixed(2)}%, Packet Loss Rate: ${packetLossRate.toStringAsFixed(2)}%';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|