一、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代碼實作這個用戶端(如果不涉及媒體播放的話)