一开始用TCP,很大程度时因为简单,可以快速实现一个初级的版本。因为受限于各种要求,TPLine从一开始就不准备通过中间转发推流服务对用户端实施推流。所以在使用中,单台IPad作为主播端,同时开启推流程序,同时为多个接入端推送数据流。
TCP通讯方案实现之后存在以下几个问题:
于是在TCP通讯方案实现后,决定用UDP+组播的方式尝试解决上面的问题。
在做TPLine的时候,没铺天盖地用第三方库,而是所有细节都自己写。是因为看到很多人用了很多第三方库已经用到连基础知道都不想去了解了,以为做技术也就这样了,这是何等的悲哀。其实不用太在意一开始代码写得不好,算法写得完不完美,过程很重要,成长需要不断积累。
格式很大程度上受传输方式的影响。
早期用TCP可靠传输方式进行流媒体数据传输时,对于最上层的应用层调用来说,只要关心数据类型及其对应的传输数据即可(音频数据与视频数据共用同一个通道,各自处理发送与接收及播放),发送端在发送数据时,在数据包中分配一个字节用于记录数据包的类型,类型包括:
因为没有采用其它第三方的实现方式,所有实现都是自己实现的。所以在运用TCP进行数据传输时,在发送及接收端的逻辑处理上,肯定要考虑的问题就是“数据粘包”问题。所有定义的数据格式代码如下:
#pragma mark - 数据封包逻辑
+ (NSData*)toDataPackage:(NSData*)bodyData {
if(bodyData != nil){
NSMutableData *postData = [[NSMutableData alloc] init];
char charnum[4];
uint64_t bodyLen = bodyData.length;// 有四位是协议号
charnum[0] = (unsigned char) ((bodyLen & 0xff000000) >> 24);
charnum[1] = (unsigned char) ((bodyLen & 0x00ff0000) >> 16);
charnum[2] = (unsigned char) ((bodyLen & 0x0000ff00) >> 8);
charnum[3] = (unsigned char) ((bodyLen & 0x000000ff));
char code[1];
code[1] = (unsigned char) ((DATATRANS & 0x00ff));
[postData appendData:[@"SRET" dataUsingEncoding:NSUTF8StringEncoding]];// 4位 协义头
[postData appendBytes:charnum length:4];// 4位 包长度 (code + playload)
[postData appendBytes:code length:1];// 1位code
[postData appendData:bodyData];
return postData;
}
return nil;
}
数据拆包逻辑包码如下:
#pragma mark - 数据拆包逻辑
+ (void)onReceiveData:(NSMutableData*)data callBack:(CallBackBlock)block{
static int hSize = 9;//头的长度
if (data == nil) {
return;
}
NSInteger index = 0;
NSUInteger packageSize = [data length];
while (true) {
@autoreleasepool {
if (index >= packageSize || index + hSize > packageSize) {
break;//位置大于接收数据包长度, 少于一个头信息长度,不处理
}
NSData *SubHeaderData = [data subdataWithRange:NSMakeRange(index + 0, 4)];
NSString *subHeader = [[NSString alloc] initWithData:SubHeaderData encoding:NSUTF8StringEncoding];
if (subHeader == nil || [@"SRET" isEqualToString:subHeader] == NO) {
NSLog(@" === 非法数据包 : %@ === ", subHeader);
index++;
continue;
}
NSData *SizeData = [data subdataWithRange:NSMakeRange(index + 4, 4)];
char *headernum = (char*)[SizeData bytes];
UInt64 headerNum0 = (headernum[0] << 24 );
UInt32 headerNum1 = (headernum[1] << 16);
UInt16 headerNum2 = (headernum[2] << 8 );
UInt8 headerNum3 = headernum[3];
UInt64 bodySize = headerNum0 + headerNum1 + headerNum2 + headerNum3;
if (bodySize == 0 || (packageSize - index - hSize) < bodySize) {
NSLog(@"数据包未接收完整, 完整数据包大小为: %llu,还差 %llu", bodySize, bodySize - packageSize);
break;
}
NSData *codeData = [data subdataWithRange:NSMakeRange(index + 8, 1)];
char *codeChar = (char*)[codeData bytes];
UInt8 uiCode = codeChar[1];
unsigned short code = uiCode;
NSData *bodyData = [data subdataWithRange:NSMakeRange(index + hSize, bodySize)];
index += (bodySize + hSize);
NSError *error;
NSLog(@"======== 已经分离的数据包 ======== \n 包体: %lld",bodySize);
if (!error) {
if (block != nil) {
//在非主线程中运行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
block(code, bodyData);
});
}
}
}
}
if (index > 0) {
//删除已经处理的包,有没有处理与有没有设置delegate有关
[data replaceBytesInRange:NSMakeRange(0, index) withBytes:NULL length:0];
NSLog(@"删除已经处理数据包");
}
}
拆包过程经过代码有删减,可选择性参考。也欢迎提出更好的实现逻辑共同进步。
相对于UDP而言,TCP在传输格式的定义上主要为数据拆包而服务,经过对数据进行合法性判断后,取得包的长度。在后续进行数据接收时,可以清晰知道包的结束位置,进而进行数据的拆分。
而采用UDP进行数据传输时,其实也少不了数据分包拆包的操作。只是在此基础上,我们对于
“二进制数据”部分的内容要进一步进行细化处理。
无论是TCP还是UDP,始终是以报文的方式进行数据传输的。
就应用层使用上说,TCP不用调用者做过多的数据分片处理,而TCP的内部从分片到传输到反馈到流量控制都有一个完整的流程进行应对。
而对于UDP来说,尽管它也有自己的分片逻辑,但无法像TCP内部处理那样,实现全方位的容错处理(当然相比UDP来说发送效率效低)。比如sendto函数允许发送的最大数据长度为 65507 ,而TCP使用上没有这个方面的限制。致于原因,看完下面的简单说明,大概就懂了。
MTU: 最大传输单员,即单次报文传输的最大字节数。
UDP与TCP一样处于“传输层”。而TCP和UDP都是基于下层“网络层”中的“IP协议”进行数据发送。而“网络层”而依赖更下层“数据链路层”进行数据发送的,如常用的以太网协议。
从“数据链路层”分析,不同的链路层协议,基于各种设计上的考虑,其支持的MTU值是不一样的。以“以太网”为例,在局域网内的最大值为1500 ,鉴于Internet上的标准MTU值为576字节,经测试在Internet上大多数为1500, 少部分为 576, 所以基于Internet上的应用进行UDP手动分包时,最好以MTU = 576 - 20 - 8 以下进行分包。
受限于“以太网”的MTU值。IP层1500字节要包括IP的包头与数据。所以对于“传输层”传过来的数据包过大的时候要进行分片处理。当IP的包头在内少于46个字节时,IP层会对其进行补码处理。
其中IP协议的协议头占用20个字节,基中包括用于记录分片长度的16位,16位数据包标识,13位分片序列号,用于分片重组,还有其他属性等。
在UDP层,也占用了8个字节。其中源端口号和目标端口号各占用了两个字节,包长度占用了两个字节,还有一个用于校验的CRC值。
所以,基于局域网的应用,可用字节数 = 1500 - 20(IP包头)- 8(UDP包头) Byte
sendto(self.socketFD, send_Message, data.length, 0, (struct sockaddr*)&m_serveraddr, sizeof(m_serveraddr));
/**
* UDP 数据分片
**/
@interface UDPFragment : NSObject
@property(nonatomic, assign)UInt16 packageID; //包唯一标识
@property(nonatomic, assign)UInt32 packageSize; //包大小
@property(nonatomic, assign)UInt8 fragmentCount; //分片总数
@property(nonatomic, assign)UInt8 fragmentIndex; //分片序号
@property(nonatomic, assign)UInt16 fragmentSize; //分片大小
@property(nonatomic, assign)UInt8 groupID; //分组唯一标识
@property(nonatomic, assign)UInt8 groupLength; //fec组长
@property(nonatomic, assign)UInt8 fragmentType; //分片数据类型 1: 数据, 2: fec
@property(nonatomic, strong)NSData *data; //分片数据
@end
#define PACKAGE_MTU 1456
#pragma mark- 转换成网络包发送格式
- (NSMutableData*)toData{
NSMutableData *postData = [[NSMutableData alloc] init];
char identity[2];
identity[0] = (unsigned char) ((self.packageID & 0x0000ff00) >> 8);
identity[1] = (unsigned char) ((self.packageID & 0x000000ff));
char totalCount[1];
totalCount[0] = (unsigned char) ((self.fragmentCount & 0x000000ff));
char index[1];
index[0] = (unsigned char) ((self.fragmentIndex & 0x000000ff));
char pageSize[2];
pageSize[0] = (unsigned char) ((self.fragmentSize & 0x0000ff00) >> 8);
pageSize[1] = (unsigned char) ((self.fragmentSize & 0x000000ff));
char totalSize[4];
totalSize[0] = (unsigned char) ((self.packageSize & 0xff000000) >> 24);
totalSize[1] = (unsigned char) ((self.packageSize & 0x00ff0000) >> 16);
totalSize[2] = (unsigned char) ((self.packageSize & 0x0000ff00) >> 8);
totalSize[3] = (unsigned char) ((self.packageSize & 0x000000ff));
// 分片组相关属性
char groupID[1];
groupID[0] = (unsigned char) ((self.groupID & 0x000000ff));
char groupLength[1];
groupLength[0] = (unsigned char) ((self.groupLength & 0x000000ff));
char type[1];
type[0] = (unsigned char) ((self.fragmentType & 0x000000ff));
[postData appendData:[@"SRET" dataUsingEncoding:NSUTF8StringEncoding]];// 4位 协义头
[postData appendBytes:identity length:2]; //packageID
[postData appendBytes:totalCount length:1]; //packageSize
[postData appendBytes:index length:1]; //fragmentIndex
[postData appendBytes:pageSize length:2]; //fragmentSize
[postData appendBytes:totalSize length:4]; //packageSize
[postData appendBytes:groupID length:1]; //groupID
[postData appendBytes:groupLength length:1]; //groupLength
[postData appendBytes:type length:1]; //type
[postData appendData:self.data];
return postData;
}
#pragma mark - 数据拆包逻辑
+ (void)onReceiveData:(NSMutableData*)data callBack:(Block)block{
static int hSize = 14;//头的长度
if (data == nil) {
return;
}
NSInteger index = 0;
NSUInteger packageSize = [data length];
while (true) {
@autoreleasepool {
if (index >= packageSize || index + hSize > packageSize) {
break;//位置大于接收数据包长度, 少于一个头信息长度,不处理
}
// 1. header ("FUCK")
NSData *SubHeaderData = [data subdataWithRange:NSMakeRange(index + 0, 4)];
NSString *subHeader = [[NSString alloc] initWithData:SubHeaderData encoding:NSUTF8StringEncoding];
if (subHeader == nil || [@"SRET" isEqualToString:subHeader] == NO) {
NSLog(@" === 非法数据包 : %@ === ", subHeader);
index++;
continue;
}
// 2. identity
NSData *identityData = [data subdataWithRange:NSMakeRange(index + 4, 2)];
char *idenChar = (char*)[identityData bytes];
UInt16 idenH = (idenChar[0] << 8 );
UInt8 ideL = idenChar[1];
UInt16 identity = ideL + idenH;
// 3. count
NSData *countData = [data subdataWithRange:NSMakeRange(index + 6, 1)];
char *countChar = (char*)[countData bytes];
UInt8 pageCount = countChar[0];
// 4. index
NSData *indexData = [data subdataWithRange:NSMakeRange(index + 7, 1)];
char *indexChar = (char*)[indexData bytes];
UInt8 pageIndex = indexChar[0];
// 5. page size
NSData *pageSizeData = [data subdataWithRange:NSMakeRange(index + 8, 2)];
char *pageSizeChar = (char*)[pageSizeData bytes];
UInt16 pageSizeH = (pageSizeChar[0] << 8 );
UInt8 pageSizeL = pageSizeChar[1];
UInt16 pageSize = pageSizeH + pageSizeL;
// 6. size
NSData *sizeData = [data subdataWithRange:NSMakeRange(index + 10, 4)];
char *sizeChar = (char*)[sizeData bytes];
UInt32 sizeH3 = (sizeChar[0] << 24 );
UInt32 sizeH2 = (sizeChar[1] << 16 );
UInt16 sizeH1 = (sizeChar[2] << 8 );
UInt8 sizeL = sizeChar[3];
UInt16 bodySize = sizeH1 + sizeH2 + sizeH3 + sizeL;
if (pageSize == 0 || (packageSize - index - hSize) < pageSize) {
break;
}
// 7. 分片数据
NSData *bodyData = [data subdataWithRange:NSMakeRange(index + hSize, pageSize)];
index += (pageSize + hSize);
// 8. 回调
if (block != nil) {
UDPackage *package = [[UDPackage alloc] init];
package.identity = identity;
package.totalCount = pageCount;
package.index = pageIndex;
package.pageSize = pageSize;
package.totalSize = bodySize;
package.data = bodyData;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //在非主线程中运行
block(package);
});
}
}
}
if (index > 0) {
//删除已经处理的包,有没有处理与有没有设置delegate有关
[data replaceBytesInRange:NSMakeRange(0, index) withBytes:NULL length:0];
}
}