很多初學(xué)者就是看了惡心的握手就再也沒有研究的興趣了,不過,弄懂了就感覺沒什么了.
socket建立連接以后,就需要認(rèn)證了,也就是握手.只有握手完成以后才能進(jìn)入下一步的操作,比如發(fā)送控制命令,交換消息,發(fā)送音視頻包等.客戶端需要發(fā)送C0,C1,C2給服務(wù)器端,服務(wù)器端需要發(fā)送S0,S1,S2給客戶端,至于C0,C1,C2,S0,S1,S2是什么,下面會(huì)介紹,暫時(shí)認(rèn)為是數(shù)據(jù)吧.
重點(diǎn)來(lái)了:
- 握手順序
- 客戶端首先發(fā)送C0,等待服務(wù)器返回S0
- 服務(wù)器端收到C0后,發(fā)送S0給客戶端
- 客戶端收到S0后,發(fā)送C1個(gè)服務(wù)器
- 服務(wù)器收到C1后發(fā)送S1給客戶端
- 客戶端收到S1后發(fā)送C2給服務(wù)器端
- 服務(wù)器端收到C2后,發(fā)送S2給客戶端
- 握手完成
是不是感覺很復(fù)雜的樣子,來(lái)一張圖清晰明了:
客戶端 服務(wù)器端
C0 ------->
<------- S0
C1 ------->
<------- S1
C2 ------->
<------- S2
然而實(shí)際上,客戶端通常都是C0和C1一起發(fā)送,或者C0發(fā)送完馬上發(fā)送C1.而服務(wù)器端一般都做了兼容處理,可能按照順序發(fā)送,也可能在收到C1后就把S0,S1,S2一起發(fā)送給客戶端.
-
C0,C1,C2,S0,S1,S2 的數(shù)據(jù)格式
-
C0 和 S0 的格式
C0 和 S0 包都是一個(gè)字節(jié)(8bit),表示版本號(hào)Paste_Image.png在 C0 中取胎,這一字段指示出客戶端要求的 RTMP 版本號(hào)嘶朱。在 S0 中,這一字段指示出服務(wù)器端選擇的 RTMP 版本號(hào)。默認(rèn)為 3溯壶。0哈蝇、1、2 三個(gè)值是由早期其他產(chǎn)品使用的薪寓,是廢棄值亡资;4 - 31 被保留為 RTMP 協(xié)議的未來(lái)實(shí)現(xiàn)版本使用;32 - 255 不允許使用 (以區(qū)分開 RTMP 和其他常以一個(gè)可打印字符開始的文本協(xié)議)向叉。無(wú)法識(shí)別客戶端所請(qǐng)求版本號(hào)的服務(wù)器應(yīng)該以版本 3 響應(yīng)锥腻,(收到響應(yīng)的) 客戶端可以選擇降低到版本 3,或者放棄握手母谎。
-
C1 和 S1 的格式
C1 和 S1 數(shù)據(jù)包的長(zhǎng)度都是 1536 字節(jié)瘦黑,包含以下字段:Paste_Image.pngTime (前四個(gè)字節(jié)):這個(gè)字段包含一個(gè) timestamp,用于本終端發(fā)送的所有后續(xù)塊的時(shí)間起點(diǎn)奇唤。這個(gè)值可以是 0幸斥,或者一些任意值。要同步多個(gè)塊流咬扇,終端可以發(fā)送其他塊流當(dāng)前的 timestamp 的值甲葬。
Zero (緊跟著的四個(gè)字節(jié)):這個(gè)字段必須都是 0。
Random data (剩下的1528 個(gè)字節(jié)):這個(gè)字段可以包含任意值懈贺。終端需要區(qū)分出響應(yīng)來(lái)自它發(fā)起的握手還是對(duì)端發(fā)起的握手经窖,這個(gè)數(shù)據(jù)應(yīng)該發(fā)送一些足夠隨機(jī)的數(shù)。簡(jiǎn)單點(diǎn),就是隨機(jī)數(shù). -
C2 和 S2 的格式
C2 和 S2 數(shù)據(jù)包長(zhǎng)度都是 1536 字節(jié)梭灿,基本就是 S1 和 C1 的副本 (分別)画侣,包含有以下字段:
Paste_Image.png- Time (前四個(gè)字節(jié)):這個(gè)字段必須包含終端在 S1 (給 C2) 或者 C1 (給 S2) 發(fā)的 timestamp.例如:C1的前四個(gè)字節(jié)為0,那么S2的前四個(gè)字節(jié)也是0.
- Time2 (緊跟著的四個(gè)字節(jié)):這個(gè)字段必須包含終端先前發(fā)出數(shù)據(jù)包 (s1 或者 c1) timestamp,例如,S1的前四個(gè)字節(jié)為 0x00 00 00 01,那么S2的第4~8字節(jié)就是0x 00 00 00 01
- Random echo (剩下1528 個(gè)字節(jié)):這個(gè)字段必須包含終端發(fā)的 S1 (給 C2) 或者 S2 (給 C1) 的隨機(jī)數(shù)。兩端都可以一起使用 time 和 time2 字段再加當(dāng)前 timestamp 以快速估算帶寬和/或者連接延遲堡妒,但這不太可能是有多大用處配乱。
-
如果握手失敗,服務(wù)器會(huì)終止響應(yīng),并斷開socket連接
如果握手成功,則可以進(jìn)入到下一個(gè)環(huán)節(jié),可以開始交換消息了.
實(shí)際測(cè)試(用的SRS測(cè)的)發(fā)現(xiàn),服務(wù)器端根本不鳥你,只要收到C1,后面就可能先發(fā)一部分,再發(fā)一部分(長(zhǎng)度不定,不一定是上文所說(shuō)的1536),也可能一次性全部給你,但是S0+S1+S2的總字節(jié)數(shù)(也就是3073個(gè)字節(jié))是對(duì)的
附上代碼:
先來(lái)個(gè)分類:主要是解析url的各個(gè)部分:
//解析推流地址
@interface NSString (URL)
@property(readonly) NSString *scheme;
@property(readonly) NSString *host;
@property(readonly) NSString *app;
@property(readonly) NSString *playPath;
@property(readonly) UInt32 port;
@end
//------------華麗的分割線 .m文件---------------------
#import "NSString+URL.h"
@implementation NSString (URL)
- (NSString *)scheme{
return [self componentsSeparatedByString:@"://"].firstObject;
}
- (NSString *)host{
NSURL *url = [NSURL URLWithString:self];
return url.host;
}
- (NSString *)app{
NSString *sep = [NSString stringWithFormat:@"%@/",self.host];
NSString *res = [self componentsSeparatedByString:sep].lastObject;
return [res componentsSeparatedByString:@"/"].firstObject;
}
- (NSString *)playPath{
NSString *sep = [NSString stringWithFormat:@"%@/",self.host];
NSString *res = [self componentsSeparatedByString:sep].lastObject;
NSString *reu = [res componentsSeparatedByString:@"/"].lastObject;
return [reu componentsSeparatedByString:@":"].firstObject;
}
- (UInt32)port{
NSString *sep = [NSString stringWithFormat:@"%@/",self.host];
NSString *res = [self componentsSeparatedByString:sep].lastObject;
NSString *reu = [res componentsSeparatedByString:@"/"].lastObject;
NSArray *ret = [reu componentsSeparatedByString:@":"];
if (ret.count < 2) {
return 0;
}
return [ret.lastObject intValue];
}
@end
//核心代碼
//SGRtmpSession.h
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger, SGRtmpSessionStatus) {
SGRtmpSessionStatusNone = 0,
SGRtmpSessionStatusConnected = 1,
SGRtmpSessionStatusHandshake0 = 2,
SGRtmpSessionStatusHandshake1 = 3,
SGRtmpSessionStatusHandshake2 = 4,
SGRtmpSessionStatusHandshakeComplete = 5,
SGRtmpSessionStatusFCPublish = 6,
SGRtmpSessionStatusReady = 7,
SGRtmpSessionStatusSessionStarted = 8,
SGRtmpSessionStatusError = 9,
SGRtmpSessionStatusNotConnected = 10
};
@class SGRtmpSession;
@protocol SGRtmpSessionDeleagte <NSObject>
- (void)rtmpSession:(SGRtmpSession *)rtmpSession didChangeStatus:(SGRtmpSessionStatus)rtmpStatus;
@end
@interface SGRtmpSession : NSObject
@property (nonatomic,weak) id<SGRtmpSessionDeleagte> delegate;
@property (nonatomic,copy) NSString *url;
- (void)connect;
- (void)disConnect;
@end
//------------華麗的分割線 .m文件---------------------
#import "SGRtmpSession.h"
#import "SGStreamSession.h"
#import "NSString+URL.h"
//c1,c2,s1,s2的大小
static const size_t kRTMPSignatureSize = 1536;
@interface SGRtmpSession()<SGStreamSessionDelegate>
@property (nonatomic,strong) SGStreamSession *session;
/**
* 狀態(tài)很重要,貫穿整個(gè)項(xiàng)目
*/
@property (nonatomic,assign) SGRtmpSessionStatus rtmpStatus;
@property (nonatomic,strong) NSMutableData *handshake;
@end
@implementation SGRtmpSession
- (void)dealloc{
NSLog(@"%s",__func__);
self.url = nil;
self.delegate = nil;
self.session = nil;
_rtmpStatus = SGRtmpSessionStatusNone;
}
- (SGStreamSession *)session{
if (_session == nil) {
_session = [[SGStreamSession alloc] init];
_session.delegate = self;
}
return _session;
}
- (void)setUrl:(NSString *)url{
_url = url;
NSLog(@"scheme:%@",url.scheme);
NSLog(@"host:%@",url.host);
NSLog(@"app:%@",url.app);
NSLog(@"playPath:%@",url.playPath);
NSLog(@"port:%zd",url.port);
}
- (void)setRtmpStatus:(SGRtmpSessionStatus)rtmpStatus{
_rtmpStatus = rtmpStatus;
NSLog(@"rtmpStatus-----%zd",rtmpStatus);
if ([self.delegate respondsToSelector:@selector(rtmpSession:didChangeStatus:)]) {
[self.delegate rtmpSession:self didChangeStatus:_rtmpStatus];
}
}
- (instancetype)init{
if (self = [super init]) {
_rtmpStatus = SGRtmpSessionStatusNone;
}
return self;
}
- (void)connect{
[self.session connectToServer:self.url.host port:self.url.port];
}
- (void)disConnect{
[self.session disConnect];
}
#pragma mark -------delegate---------
- (void)streamSession:(SGStreamSession *)session didChangeStatus:(SGStreamStatus)streamStatus{
if (streamStatus & NSStreamEventHasBytesAvailable) {//收到數(shù)據(jù)
[self didReceivedata];
return;//return
}
if (streamStatus & NSStreamEventHasSpaceAvailable){ //可以寫數(shù)據(jù)
if (_rtmpStatus == SGRtmpSessionStatusConnected) {
[self handshake0];
}
return;//return
}
if ((streamStatus & NSStreamEventOpenCompleted) &&
_rtmpStatus < SGRtmpSessionStatusConnected) {
self.rtmpStatus = SGRtmpSessionStatusConnected;
}
if (streamStatus & NSStreamEventErrorOccurred) {
self.rtmpStatus = SGRtmpSessionStatusError;
}
if (streamStatus & NSStreamEventEndEncountered) {
self.rtmpStatus = SGRtmpSessionStatusNotConnected;
}
}
- (void)handshake0{
self.rtmpStatus = SGRtmpSessionStatusHandshake0;
//c0
char c0Byte = 0x03;
NSData *c0 = [NSData dataWithBytes:&c0Byte length:1];
[self writeData:c0];
//c1
uint8_t *c1Bytes = (uint8_t *)malloc(kRTMPSignatureSize);
memset(c1Bytes, 0, 4 + 4);
NSData *c1 = [NSData dataWithBytes:c1Bytes length:kRTMPSignatureSize];
free(c1Bytes);
[self writeData:c1];
}
- (void)handshake1{
self.rtmpStatus = SGRtmpSessionStatusHandshake2;
NSData *s1 = [self.handshake subdataWithRange:NSMakeRange(0, kRTMPSignatureSize)];
//c2
uint8_t *s1Bytes = (uint8_t *)s1.bytes;
memset(s1Bytes + 4, 0, 4);
NSData *c2 = [NSData dataWithBytes:s1Bytes length:s1.length];
[self writeData:c2];
}
//接收到數(shù)據(jù)
- (void)didReceivedata{
NSData *data = [self.session readData];
if (self.rtmpStatus >= SGRtmpSessionStatusConnected &&
self.rtmpStatus < SGRtmpSessionStatusHandshakeComplete) {
//將我收的數(shù)據(jù)保存起來(lái),因?yàn)榭倲?shù)是3073個(gè)字節(jié)
[self.handshake appendData:data];
}
NSLog(@"%zd",data.length);
//handshke 服務(wù)氣端情況
// 1.按照官方文檔c0,c1,c2
// 2.一起發(fā)3073個(gè)字節(jié)
// 3.先發(fā)一部分,再發(fā)一部分,每部分大小不確定,總數(shù)正確
switch (_rtmpStatus) {
case SGRtmpSessionStatusHandshake0:{
uint8_t s0;
[data getBytes:&s0 length:1];
if (s0 == 0x03) {//s0
self.rtmpStatus = SGRtmpSessionStatusHandshake1;
if (data.length > 1) {//后面還有數(shù)據(jù),但不確定長(zhǎng)度
data = [data subdataWithRange:NSMakeRange(1, data.length -1)];
self.handshake = data.mutableCopy;
}else{
break;
}
}else{
NSLog(@"握手失敗");
break;
}
}
case SGRtmpSessionStatusHandshake1:{
if (self.handshake.length >= kRTMPSignatureSize) {//s1
[self handshake1];
if (self.handshake.length > kRTMPSignatureSize) {//>
NSData *subData = [self.handshake subdataWithRange:NSMakeRange(kRTMPSignatureSize, self.handshake.length - kRTMPSignatureSize)];
self.handshake = subData.mutableCopy;
}else{// =
self.handshake = [NSMutableData data];
break;
}
}else{//<
break;
}
}
case SGRtmpSessionStatusHandshake2:{//s2
if (data.length >= kRTMPSignatureSize) {
NSLog(@"握手完成");
self.rtmpStatus = SGRtmpSessionStatusHandshakeComplete;
}
break;
}
default:
break;
}
}
- (void)writeData:(NSData *)data{
if (data.length == 0) {
return;
}
[self.session writeData:data];
}
@end
握手部分相對(duì)來(lái)說(shuō)比較惡心,雖然很簡(jiǎn)單,處理起來(lái)很麻煩,仔細(xì)打上斷點(diǎn)試試.主要是解析服務(wù)器端數(shù)據(jù)比較繞,建議先從3073字節(jié)的那種情況做處理,其他的倆種情況,自己可以根據(jù)思路寫一套邏輯.握手看明白了,后面就簡(jiǎn)單多了.
下面附上測(cè)試代碼:
@interface ViewController ()
@property (nonatomic,strong) SGRtmpSession *session;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (SGRtmpSession *)session{
if (_session == nil) {
_session = [[SGRtmpSession alloc] init];
_session.url = @"rtmp://192.168.1.106/live/2005";
}
return _session;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.session connect];
}
輸出結(jié)果:
2016-08-02 22:23:15.764 SGRtmpPublisher[650:225659] scheme:rtmp
2016-08-02 22:23:15.766 SGRtmpPublisher[650:225659] host:192.168.1.106
2016-08-02 22:23:15.766 SGRtmpPublisher[650:225659] app:live
2016-08-02 22:23:15.766 SGRtmpPublisher[650:225659] playPath:2005
2016-08-02 22:23:15.766 SGRtmpPublisher[650:225659] port:0
2016-08-02 22:23:15.884 SGRtmpPublisher[650:225659] 連接成功
2016-08-02 22:23:15.885 SGRtmpPublisher[650:225659] rtmpStatus-----1
2016-08-02 22:23:15.886 SGRtmpPublisher[650:225659] 可以發(fā)送字節(jié)
2016-08-02 22:23:15.886 SGRtmpPublisher[650:225659] rtmpStatus-----2
2016-08-02 22:23:15.886 SGRtmpPublisher[650:225659] 可以發(fā)送字節(jié)
2016-08-02 22:23:15.898 SGRtmpPublisher[650:225659] 有字節(jié)可讀
2016-08-02 22:23:15.898 SGRtmpPublisher[650:225659] 1537
2016-08-02 22:23:15.899 SGRtmpPublisher[650:225659] rtmpStatus-----3
2016-08-02 22:23:15.899 SGRtmpPublisher[650:225659] rtmpStatus-----4
2016-08-02 22:23:15.899 SGRtmpPublisher[650:225659] 可以發(fā)送字節(jié)
2016-08-02 22:23:15.900 SGRtmpPublisher[650:225659] 有字節(jié)可讀
2016-08-02 22:23:15.900 SGRtmpPublisher[650:225659] 1536
2016-08-02 22:23:15.900 SGRtmpPublisher[650:225659] 握手完成
2016-08-02 22:23:15.900 SGRtmpPublisher[650:225659] rtmpStatus-----5