那么当丢包事情发生的时个,一般的处理办法如下:
NACK机制
当接收端发现数据包丢失时,通过向发送端发送丢包重发请求或者是数据包接收状态,对数据数据丢包与接收情况向发送端发起反馈(具体的实现逻辑有很多种,有超时重发,丢包请求重发等,具体方案可按项目要求进行实现),驱使发送端对某个接收端的丢失数据进行定点发送。
FEC机制
丢包重发机制似乎已经可以解决很多问题了,其实在很多场景上,FEC向前恢复机制的处理方式更为重要和普遍。
得益于UDP在数据发送上最大限度对网络带宽进行行了榨取,虽然存在丢包问题,但与TCP顺序串行发送及对于某些场景下效率低下的调节调整机制相比,UDP简单而粗暴但效率却非常的高。
FEC机制通过冗余发送的方式,通对分片进行分组,生成分组数据的冗余包,与数据包一起发送到接收端。接收端在收到数据包后,对分片进行分组恢复,对每个分片中存在丢失的数据包进行反向生成丢失的分片。但理论上单靠FEC不能实现100%数据的恢复。而FEC恢复算法多种多样,有最简单的XOR,还有RS矩阵算法等。
这里提到的是向前纠错方案中最为简单易懂的XOR异或算法。
以XOR异或算法为例,在TPLine中的FEC数据分片的实现流程是这样的:
冗余分片
。冗余分片
的长度为MTU
的值MTU
值的情况,在生成冗余分片
的时候要进行补码操作,但数据本身不做补码发送。所以最后一个分组可能在最后一个分片长度少于其它分片长度的情况下,也正常进行“异或”操作,生成长度为MTU
值的冗余分片
。
/**
* @param fragmentArray 进行分片后的 单个包的分片集合
* @param length MTU值
**/
+ (NSMutableArray*__nonnull)toSplitFECPackage:(NSMutableArray*__nonnull)fragmentArray length:(int)length{
NSMutableArray *newPackages = [[NSMutableArray alloc] init];
if (fragmentArray != nil && fragmentArray.count > 0){
int count = fragmentArray.count;
//“计算组长”
int gLength = [self countGroupLength:count];
//fec附加包的总个数
int fecCount = count / gLength + (count % gLength > 0? 1 : 0);
int fragmentIndex = 0;
//实际组长, 组长不代表所有组都是这个数量,特别是最后一组有可能少于,可以计算得出
int groupLength = gLength + 1;
Byte *fecByte = (Byte*)malloc(length);
memset(fecByte, 0, sizeof(Byte) * length);
for (int i = 0; i < count; i++) {
// 0. 计算groupID
int groupID = ((i + 1) / gLength) - 1 + ((i + 1) % gLength > 0? 1 : 0);
// 1. 对分片中的 fec分组值进行设置
UDPFragment *fragment = fragmentArray[i];
fragment.groupID = groupID; //Fec Identity
fragment.fragmentIndex = fragmentIndex;
fragment.fragmentType = 1; //分片数据类型 1: 数据, 2: fec
fragment.groupLength = groupLength; //分片组 长度
fragment.fragmentCount += fecCount; //分片总数
[newPackages addObject:fragment];
// 2. 进行“异或”操作
NSData *data = fragment.data;
Byte *targetByte = (Byte*)[data bytes];
for (int j = 0; j < length; j++) {
Byte tb = 0;
if (j < data.length){
tb = targetByte[j];
}
fecByte[j] = fecByte[j] ^ tb;
}
fragmentIndex++;
// 3. 一个fec分组“异或”操作完成后,对数据进行封包处理
if ((i + 1) % gLength == 0 || i + 1 == count){
UDPFragment *fecFragment = [[UDPFragment alloc] init];
fecFragment.packageID = fragment.packageID;
fecFragment.groupID = groupID;
fecFragment.fragmentIndex = fragmentIndex;
fecFragment.groupLength = groupLength; //分片组 长度
fecFragment.fragmentCount = fragment.fragmentCount; //分片总数
fecFragment.fragmentSize = length; //分片大小
fecFragment.packageSize = fragment.packageSize; //数据包总大小
fecFragment.fragmentType = 2; //分片数据类型 1: 数据, 2: fec
fecFragment.data = [[NSData alloc] initWithBytes:fecByte length:length];
[newPackages addObject:fecFragment];
fragmentIndex ++;
memset(fecByte, 0, sizeof(Byte) * length); //重置“异或”值的空间
}
}
}
return newPackages;
}
#pragma mark- 开始运行向前纠错
- (void)startFECOperation:(UDPPackage*_Nonnull)package{
for (NSNumber *number in package.lostArray){
int index = number.intValue;
// 1.计算出所在groupID
int groupID = ((index + 1) / package.groudLength) - 1 + ((index + 1) % package.groudLength > 0? 1 : 0);
// 1.1 计算应该分组中,组员个数
int fragmentCount = package.fragmentCount;
//该分组中有多少个片(包括fec分片) ,组长不代表所有组都是这个数量,特别是最后一组有可能少于,可以计算得出
int groudItemCount = 0;
if (fragmentCount > package.groudLength){
//计算出最后一个groupID
int lastID = fragmentCount / package.groudLength - 1 + (fragmentCount % package.groudLength > 0? 1 : 0);
if (groupID == lastID){
groudItemCount = fragmentCount % package.groudLength; //最后一组
}else{
groudItemCount = package.groudLength; //非最后一组
}
}else{
groudItemCount = fragmentCount;
}
// 2.收集分片组的所有分片
int groupReCount = 0; //分组中收集到的分片个数
NSMutableArray *array = [[NSMutableArray alloc] init];
UDPFragment *fecFragment = nil;
for (int i = 0; i < package.fragmentBuffer.count; i++) {
UDPFragment *item = [package.fragmentBuffer objectAtIndex:i];
if (item.groupID == groupID){
if (item.fragmentType == 1){ //1: 数据, 2: fec
[array addObject:item];
}else if(item.fragmentType == 2){ //1: 数据, 2: fec
fecFragment = item;
}
groupReCount++;
}
}
// 3.FEC处理
if (groupReCount == groudItemCount - 1){ //一个组中,只是少了一个包,则可以还原
if (fecFragment == nil){
//丢的是FEC分片,则数据为完整,可以进行包组合工作
UDPFragment *fecEmptyPackage = [[UDPFragment alloc] init];
fecEmptyPackage.fragmentType = 2; //1: 数据, 2: fec
fecEmptyPackage.packageID = groupID;
fecEmptyPackage.packageID = package.packageID;
fecEmptyPackage.fragmentIndex = index;
[package.fragmentBuffer addObject:fecEmptyPackage]; //手动添加一个,在下一步合并数据包时再回收
}else{ //还原丢失的分片
UDPFragment *losePackage = [XORFec reduceLosePackage:array fec:fecFragment index:index];
if (losePackage != nil){
[package.fragmentBuffer addObject:losePackage]; //还完后的分片入库
}else{
NSLog(@"*** [FEC] 还原数据包 [失败]: PID: %d, index = %d ***", package.packageID, index);
}
}
}else{
NSLog(logString, package.packageID, index, groudItemCount - groupReCount - 1);
}
}
// === 4. 重组检测 ===
if(package.fragmentCount == package.fragmentBuffer.count){
[self onRegroupAction:package]; //包已经收完, 对包进行合并
[self.packageMap removeObjectForKey:[NSNumber numberWithInt:package.packageID]]; //放到合并队列后,回收缓存
self.lastUDPPackage = nil;
}
}