天天看點

RTMP協定學習資料整理筆記

一、NetConnection.call輪詢過程

取自Red5的echo_test例子(RTMP AMF0 Object)

注意,下面的用戶端發包稱為Cn包(n為整數),

伺服器發包稱為Sn包(n為整數)。

粘包的意思是,在嗅探器攔截内容裡,

資料是合并在一起發送的,

一般是由Flash播放器(用戶端)發出。

左面的用戶端是Flash Player調試版(非浏覽器的獨立exe)。

右面的伺服器是RED5伺服器。

測試的作業系統是Windows XP。

------------------------------------------------------------------

Client->Server                          |          Server->Client

------------------------------------------------------------------

1. C0握手包,長度0x601             |          S0握手包,長度0xC01

首位元組0x03 首位元組0x03               |

------------------------------------------------------------------

2. C1握手包,長度0x600             |

首位元組随機                                  |

------------------------------------------------------------------

3. connect包(粘包)                  |

含AMF0資料                                |

首位元組0x03                                 |

                                                  |

子包(粘包)                               |

首位元組0xC3                                |       首位元組0x02

-------------------------------------------------------------------

4. 首位元組0x02                             |       首位元組0x42

                                                  |

_result包                                    |

首位元組0x03                                 |

                                                  |

首位元組0x42                                 |

------------------------------------------------------------------

5. call包                                      |

含AMF0資料                                 |       含AMF0資料

首位元組0x03                                  |      首位元組0x43

------------------------------------------------------------------

6. call包                                      |

含AMF0資料                                 |       含AMF0資料

首位元組0x43                                  |       首位元組0x43

------------------------------------------------------------------

C0, S0和C1的内容可以是随機(甚至全零),

猜測是用于FMS的特殊用途(規範書沒有明确說明)

應該是用來身份認證或加密用(很多網絡遊戲都有類似的機制)

可以參考

http://tlb.org/rtmpout.html

或參考Red5的源代碼關于握手包的代碼和注釋。

二、模型描述:

(1) 握手過程中,C0包長度0x601,首位元組0x03,

S0包可以是随機内容的,

但需要保證首位元組為0x03,長度為0xC01

(2) connect包和C1包内容粘起來

(3) AMF0資料中可能混有0xC?内容的位元組(用于分割過長的資料),

出現在connect包的偏移位置0x8C處(内容為0xC3),

應該是為了保證AMF包包體長度總小于等于0x80。

(4) 用戶端RTMP包首位元組和包體長度

首位元組決定標頭長度。上面提到的用戶端包的首位元組有以下情況:

* C0握手包的0x03:無標頭,

包體長度0x600

* 非C0握手包的0x03:標頭大小12,

包體長度小于0x80,可能有0xC3的分割。

* 随機内容:C1握手包。沒有標頭,

包體長度0x601

* 0xC3:標頭大小1,用于表示connect包的子包,

包體長度小于0x80,由前面的AMF包標頭中的長度資訊計算得到。

* 0x02:標頭大小12,

包體長度小于0x80,可能有0xC3的分割。

* 0x43:標頭大小8

包體長度小于0x80,可能有0xC3的分割。

(5)RTMP包首位元組結構,長度1 Byte

* 標頭長度(2bit) | 頻道ID(6bit)

其中標頭長度的計算包括首位元組在内

(6)RTMP標頭結構(包含首位元組)(下面的B表示Byte位元組數)

* 標頭長度1 Byte:

首位元組(1B)

* 標頭長度8 Bytes:

首位元組(1B) | 時間戳(3B) | 包體總長(3B) | 包類型(1B)

* 標頭長度12 Bytes:

首位元組(1B) | 時間戳(3B) | 包體總長(3B) | 包類型(1B) | 流ID(4B)

(7)伺服器RTMP包首位元組和包體長度

* S0包長度0xC01,首位元組0x03

* call包的響應總傳回0x43首位元組

三、RTMP協定過程的參考資料:

1. rtmp握手Java版附源碼

http://bbs.9ria.com/thread-10560-1-1.html

2. RTMP協定封包分析 參考red5

http://www.cnweblog.com/fly2700/archive/2008/04/09/281431.html

3. RTMP英文介紹

http://wiki.gnashdev.org/RTMP

4. AMF英文介紹

http://wiki.gnashdev.org/AMF

5. RTMP協定的顔色高亮對照解析

http://tlb.org/rtmpout.html

四、connect包的分析,以及AMF0的手工解包

注意,開頭注釋部分不屬于AMF0,不能用AMF0的協定分析(隻能用RTMP)

而後面的AMF0資料不能直接用AMF0解碼器解碼,

因為裡面夾雜了一個0xC3的位元組,它是用來表示前面的部分已經超過128位元組

(從0x02開始算起?),需要插入分割标志。

如果要用

flex.messaging.io.amf.Amf0Input

這個類解碼,需要想辦法去掉這個标志位元組。

/*
	    0x03,
	    //12位元組長頭部(包括這個位元組),ChannelID為3(即Invoke通道)
            //
            //00	12 bytes  0?
	    //01	8 bytes   4?
	    //10	4 bytes   8?
	    //11	1 byte    C?
            //
            //ChannelID	Use
            //02	Ping 和ByteRead通道
            //03	Invoke通道 我們的connect() publish()和自字寫的NetConnection.Call() 資料都是在這個通道的
            //04	Audio和Vidio通道
            //05 06 07	伺服器保留,經觀察FMS2用這些Channel也用來發送音頻或視訊資料

	    0x00, 0x00, 0x00,
	    //時間戳
	    
	    0x00, 0x00, (byte) 0xE4, 
	    //總長度(如果超過0x80或128就分割,頭部加上0xC?位元組(不計入總長度)
	    
	    0x14, 
	    //AMF類型(即Invoke) 0x01-0x06的介紹見Page 31
            //0x01	Chunk Size	changes the chunk size for packets
            //0x02	Unknown	
            //0x03	Bytes Read	send every x bytes read by both sides
            //0x04	Ping	ping is a stream control message, has subtypes
            //0x05	Server BW	the servers downstream bw
            //0x06	Client BW	the clients upstream bw 
            //0x07	Unknown	
            //0x08	Audio Data	packet containing audio
            //0x09	Video Data	packet containing video data
            //0x0A-0x0E	Unknown	
            //0x0F	FLEX_STREAM_SEND	TYPE_FLEX_STREAM_SEND
            //0x10	FLEX_SHARED_OBJECT	TYPE_FLEX_SHARED_OBJECT
            //0x11	FLEX_MESSAGE	TYPE_FLEX_MESSAGE
            //0x12	Notify	an invoke which does not expect a reply
            //0x13	Shared Object	has subtypes
            //0x14	Invoke	like remoting call, used for stream actions too.
            //0x16	StreamData	這是FMS3出來後新增的資料類型,這種類型資料中包含AudioData和VideoData
	    //
            //0x3 This specifies the content type of the RTMP packet is the number of bytes read. This is used to start the RTMP connection.
            //0x4 This specifies the content type of the RTMP message is a ping packet.
            //0x5 This specifies the content type of the RTMP message is server response of some type.
            //0x6 This specifies the content type of the RTMP packet is client request of some type.
            //0x8 This specifies the content type of the RTMP packet is an audio message.
            //0x9 This specifies the content type of the RTMP message is a video packet.
            //0x12 This specifies the content type of the RTMP message is notify.
            //0x13 This specifies the content type of the RTMP message is shared object.
            //0x14 This specifies the content type of the RTMP message is remote procedure call. This invokes the method of a Flash class remotely.


	    0x00, 0x00, 0x00, 0x00, 
	    //StreamID
	*/
	
	    0x02, 
	    0x00, 0x07, 
	    0x63, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74, 
	    //connect
	    //
	    //see Page 45
	    //
	    //Transaction ID 
	    //	String 
	    //	Always set to 1.                      | 
	    //Command Object 
	    //	Object 
	    //	Command information object which has the name-value pairs.                 | 
	    //Optional User Arguements
	    // 	Object  
	    //	Any optional information
	    
	    0x00, 
	    0x3F, (byte) 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	    //(Number) 1.0
	    
	    0x03,
	    //object-marker
	    
	    0x00, 0x03, 
	    0x61, 0x70, 0x70,  
	    //app
	    0x02, 
	    0x00, 0x08, 
	    0x53, 0x4F, 0x53, 0x61, 0x6D, 0x70, 0x6C, 0x65, 
	    //SOSample
	    
	    
	    0x00, 0x08, 
	    0x66, 0x6C, 0x61, 0x73, 0x68, 0x56, 0x65, 0x72, 
	    //flashVer
	    0x02, 
	    0x00, 0x0E, 
	    0x57, 0x49, 0x4E, 0x20, 0x31, 0x30, 0x2C, 0x32, 0x2C, 0x31, 0x35, 0x39, 0x2C, 0x31, 
	    //WIN 10,2,159,1
	    
	    0x00, 0x06, 
	    0x73, 0x77, 0x66, 0x55, 0x72, 0x6C,  
	    //swfURL
	    0x06, 
	    //undefined
	    
	    0x00, 0x05, 
	    0x74, 0x63, 0x55, 0x72, 0x6C, 
	    //tcUrl
	    0x02, 
	    0x00, 0x19, 
	    0x72, 0x74, 0x6D, 0x70, 0x3A, 0x2F, 0x2F, 0x6C, 0x6F, 0x63, 0x61, 0x6C, 0x68, 0x6F, 0x73, 0x74, 
	    0x2F, 0x53, 0x4F, 0x53, 0x61, 0x6D, 0x70, 0x6C, 0x65, 
	    //rtmp://localhost/SOSample
	    
	    0x00, 0x04, 
	    0x66, 0x70, 0x61, 0x64, 
	    //fpad
	    0x01, 
	    0x00, 
	    //false
	    
	    0x00, 0x0C, 
	    0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6C, 0x69, 0x74, 0x69, 0x65, 
	    //FIXME:
	    //(byte) 0xC3, //超過了128位元組的分割包
	    0x73, 
	    //capabilities
	    0x00, 
	    0x40, 0x6D, (byte) 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00,
	    //(Number) 239.0
	    
	    0x00, 0x0B, 
	    0x61, 0x75, 0x64, 0x69, 0x6F, 0x43, 0x6F, 0x64, 0x65, 0x63, 0x73, 
	    //audioCodec
	    0x00, 
	    0x40, (byte) 0xA8, (byte) 0xEE, 0x00, 0x00, 0x00, 0x00, 0x00, 
	    //(Number) 3191.0
	    
	    0x00, 0x0B,
	    0x76, 0x69, 0x64, 0x65, 0x6F, 0x43, 0x6F, 0x64, 0x65, 0x63, 0x73, 
	    //videoCodecs
	    0x00, 
	    0x40, 0x6F, (byte) 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 
	    //(Number) 252.0
	    
	    0x00, 0x0D, 
	    0x76, 0x69, 0x64, 0x65, 0x6F, 0x46, 0x75, 0x6E, 0x63, 0x74, 0x69, 0x6F, 0x6E, 
	    //videoFunction
	    0x00, 
	    0x3F, (byte) 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	    //(Number) 1.0
	    
	    0x00, 0x07, 
	    0x70, 0x61, 0x67, 0x65, 0x55, 0x72, 0x6C,
	    //pageUrl
	    0x06, 
	    //undefined
	    
	    0x00, 0x00,
	    //空的UTF-8字元串
	    0x09, 
	    //object-end-marker
	    
	    0x00, 
	    0x40, (byte) 0xDC, 0x4D, (byte) 0xC0, 0x00, 0x00, 0x00, 0x00,
	    //(Number) 28983.0
	    
           

這些資料是通過SocketSniffer依附在Flash播放器上嗅探得到的

(上面内容是其中的一段發送包)

http://www.nirsoft.net/utils/socket_sniffer.html

裡面有部分内容被注釋掉(開頭的標頭和中間的0xC3)

是為了友善下面用Amf0Input類抽取其中的資料。

五、使用Adobe的開源項目BlazeDS提供的AMF0解碼類進行解碼的Java代碼

http://opensource.adobe.com/wiki/display/blazeds/BlazeDS

這裡是直接抽取出Object對象,然後列印出字元串。

public static void test2() throws ClassNotFoundException, IOException {
	SerializationContext context = new SerializationContext();
	Amf0Input amf0in = new Amf0Input(context);
	amf0in.setInputStream(new ByteArrayInputStream(head2));
	while(amf0in.available() > 0) {
	    //System.out.println("available:" + amf0in.available());
	    Object message = amf0in.readObject();
	    System.out.println(message);
	}
    }

           

(注:隻用于測試,請注意BlazeDS的代碼版權)

六、AMF0手工解碼參考

注意,這裡是上面AMF0資料的解碼結果。

AMF0每個标簽占一個位元組:

number-marker = 0x00

boolean-marker = 0x01

string-marker = 0x02

object-marker = 0x03

movieclip-marker = 0x04 ; reserved, not supported

null-marker = 0x05

undefined-marker = 0x06

reference-marker = 0x07

ecma-array-marker = 0x08

object-end-marker = 0x09

strict-array-marker = 0x0A

date-marker = 0x0B

long-string-marker = 0x0C

unsupported-marker = 0x0D

recordset-marker = 0x0E ; reserved, not supported

xml-document-marker = 0x0F

typed-object-marker = 0x10

AMF0解包結果:(含義見Page 46)

connect

1.0

ASObject(2208288){

app=SOSample,

fpad=false,

flashVer=WIN 10,2,159,1,

tcUrl=rtmp://localhost/SOSample,

audioCodecs=3191.0,

videoFunction=1.0,

pageUrl=null,

capabilities=239.0,

swfUrl=null,

videoCodecs=252.0

}

28983.0

    這裡遺漏objectEncoding(見Page46)用于指定AMF格式

七、AMF0/AMF3和RTMP的官方規範書。

見英文wiki介紹。

http://en.wikipedia.org/wiki/Real_Time_Messaging_Protocol

Adobe官方早已公開了RTMP和AMF的協定内容(雖然有些關鍵内容沒有完全公開)。

其中提到的RTMP規範下載下傳在

http://www.adobe.com/devnet/rtmp.html

AMF介紹

http://en.wikipedia.org/wiki/Action_Message_Format

其中也提到AMF0和AMF3規範的下載下傳(見底部)

http://opensource.adobe.com/wiki/display/blazeds/Java+AMF+Client

八、測試用AS3代碼。

可參考Red5安裝包内的echo_test例子,

http://code.google.com/p/red5/

那個例子可以測試AMF0和AMF3在傳輸不同内容時的情況。

可以用嗅探器攔截發包。

代碼如下。

這裡是通過點選滑鼠,

使用RTMP協定發送AMF0封包的Object對象:

{a: "foo", b: "bar"}

package  
{
	import flash.display.Sprite;
	import flash.events.SecurityErrorEvent;
	import flash.events.NetStatusEvent;
	import flash.net.NetConnection;
	import flash.net.Responder;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.events.MouseEvent;
	import flash.net.ObjectEncoding;
	
	public class TestRTMPEcho extends Sprite
	{
		private var txt:TextField = new TextField;
		private var cn:NetConnection;
		
		public function TestRTMPEcho() 
		{
			txt.autoSize = TextFieldAutoSize.LEFT;
			addChild(txt);
			stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
			log("點選滑鼠開始");
		}
		
		private function onMouseDown(e:MouseEvent):void 
		{
			txt.text = "";
			log("初始化...");
			cn = new NetConnection();
			cn.objectEncoding = ObjectEncoding.AMF0;
			cn.connect("rtmp:/127.0.0.1/echo");
			cn.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
			cn.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
		}
		
		private function netStatusHandler(event:NetStatusEvent):void 
		{
			switch (event.info.code) 
			{
				case "NetConnection.Connect.Success":
					log("連接配接成功!");
					cn.call("echo", new Responder(result, status), {a: "foo", b: "bar"});
					break;
					
				case "NetConnection.Connect.Closed":
					log("關閉連接配接");
					break;
				
				case "NetConnection.Connect.Failed":
					log("連接配接失敗!");
					break;
				
				case "NetStream.Play.StreamNotFound":
					log("無法找到遠端主機");
					break;
			}
		}
		
		private function result(e:Object):void
		{
			log("result");
			for (var key:String in e)
			{
				log("\t" + key + "=>" + e[key]);
			}
			cn.close();
		}
		
		private function status(e:Object):void
		{
			log("status");
			log(e.description);
			cn.close();
		}
		
		private function securityErrorHandler(event:SecurityErrorEvent):void 
		{
			txt.appendText("securityError: " + event);
		}
		
		private function log(text:String):void 
		{
			txt.appendText(text + "\n");
		}
	}
}
      

關于NetConnection的用法請參考官方文檔。

它是FlashPlayer内置的RTMP用戶端,可以透明地(對于應用開發者是不可視)發送AMF包(通過RTMP協定)

是以理論上可以用純AS3代碼實作這個用戶端(如果不涉及媒體播放的話)