一. 前言
一开始用TCP,很大程度时因为简单,可以快速实现一个初级的版本。因为受限于各种要求,TPLine从一开始就不准备通过中间转发推流服务对用户端实施推流。所以在使用中,单台IPad作为主播端,同时开启推流程序,同时为多个接入端推送数据流。
TCP通讯方案实现之后存在以下几个问题:
- 接入并发能力有限。
- 接入数量上升的后,画面质量不断下降。
于是在TCP通讯方案实现后,决定用UDP+组播的方式尝试解决上面的问题。
在做TPLine的时候,没铺天盖地用第三方库,而是所有细节都自己写。是因为看到很多人用了很多第三方库已经用到连基础知道都不想去了解了,以为做技术也就这样了,这是何等的悲哀。其实不用太在意一开始代码写得不好,算法写得完不完美,过程很重要,成长需要不断积累。
二. 格式的定义
格式很大程度上受传输方式的影响。
早期用TCP可靠传输方式进行流媒体数据传输时,对于最上层的应用层调用来说,只要关心数据类型及其对应的传输数据即可(音频数据与视频数据共用同一个通道,各自处理发送与接收及播放),发送端在发送数据时,在数据包中分配一个字节用于记录数据包的类型,类型包括:
- 音频数据
数据封装格式为 aac音频数据 - 视频数据
数据封装格式为 h.264视频数据 - 逻辑控制数据
逻辑控制包括用户身份验证等其他辅助逻辑
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;
}
- “SRET”用于数据分隔及判断是否为非法数据包的依据。当接收的数据不是SRET开头时会不断丢弃无效的数据,直到找到合法数据包头为止。
- 其后的4个字节为“数据长度”。在接收端进行数据包分拆合并时,这个“数据长度”就显得由为种要。在处理数据粘连时,可以根据“数据长度”来有效对连续发送来过的数据进行分拆。
- “类型参数”,用一个字节表示,用于区分数据类型的。
- “类型参数”之后就是对应要传输的二进制数据。因为TCP在传输过程内部可以对发送的数据进行分片发送操作,所以简单来说这样的包结构简单而又可以满足需求。
数据拆包逻辑包码如下:
#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] ==