其實我一直想不明白HTML5包裝個應用層辦議作為Socket通過基礎目的是為了什么,其實直接支持Socket tcp相對來說更加簡單靈活.既然標准已經制定而瀏覽器也支持那對於我們開發者來說只能用的分.最新版本的WebSocket協議於2011-12其標准規范已經明確下來,所以現在可以根據這標准進行相應的開發.詳細參考http://datatracker.ietf.org/doc/rfc6455/?include_text=1
WebSocket協議主要分為兩部分,第一部分是連接許可驗證和驗證后的數據交互.連接許可驗證比較簡單,由Client發送一個類似於HTTP的請求,服務端獲取請求后根據請求的KEY生成對應的值並返回.
連接請求內容:
1
2
3
4
5
6
7
8
|
GET / HTTP/1.1
Connection:Upgrade
Host:127.0.0.1:8088
Origin:
null
Sec-WebSocket-Extensions:x-webkit-deflate-frame
Sec-WebSocket-Key:puVOuWb7rel6z2AVZBKnfw==
Sec-WebSocket-Version:13
Upgrade:websocket
|
服務端接收請求后主要是成針對Sec-WebSocket-Key生成對就Sec-WebSocket-Accept 的key,生成Sec-WebSocket-Accept 值比較簡單就是Sha1(Sec-WebSocket-Key+258EAFA5-E914-47DA-95CA-C5AB0DC85B11)即可,C#代碼如下:
1
2
3
4
5
|
SHA1 sha1 =
new
SHA1CryptoServiceProvider();
byte
[] bytes_sha1_in = Encoding.UTF8.GetBytes(request.SecWebSocketKey+
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
);
byte
[] bytes_sha1_out = sha1.ComputeHash(bytes_sha1_in);
string
str_sha1_out = Convert.ToBase64String(bytes_sha1_out);
response.SecWebSocketAccept = str_sha1_out;
|
服務端返回內容:
1
2
3
4
5
6
7
8
|
HTTP/1.1 101 Switching Protocols
Connection:Upgrade
Server:beetle websocket server
Upgrade:WebSocket
Date:Mon, 26 Nov 2012 23:42:44 GMT
Access-Control-Allow-Credentials:
true
Access-Control-Allow-Headers:content-type
Sec-WebSocket-Accept:FCKgUr8c7OsDsLFeJTWrJw6WO8Q=
|
經過服務器的返回處理后連接握手成功,后面就可以進行TCP通訊.WebSocket在握手后發送數據並象下層TCP協議那樣由用戶自定義,還是需要遵循對應的應用協議規范...這也是在文章之說沒有直接基於Socket tcp方便的原因.
數據交互協議:
這圖有點難看懂...里面包括幾種情況有掩碼,數據長度小於126,小於UINT16和小於UINT64等幾種情況.后面會慢慢詳細說明.整個協議頭大概分三部分組成,第一部分是描述消息結束情況和類型,第二部分是描述是否存在掩碼長度,第三部分是擴展長度描述和掩碼值.
從圖中可以看到WebSocket協議數據主要通過頭兩個字節來描述數據包的情況
第一個字節
最高位用於描述消息是否結束,如果為1則該消息為消息尾部,如果為零則還有后續數據包;后面3位是用於擴展定義的,如果沒有擴展約定的情況則必須為0.可以通過以下c#代碼方式得到相應值
1
|
mDataPackage.IsEof = (data[start] >> 7) > 0;
|
最低4位用於描述消息類型,消息類型暫定有15種,其中有幾種是預留設置.c#代碼可以這樣得到消息類型:
1
2
|
int
type = data[start] & 0xF;
mDataPackage.Type = (PackageType)type;
|
第二個字節
消息的第二個字節主要用一描述掩碼和消息長度,最高位用0或1來描述是否有掩碼處理,可以通過以下c#代碼方式得到相應值
1
|
bool
hasMask = (data[start] >>7) > 0;
|
剩下的后面7位用來描述消息長度,由於7位最多只能描述127所以這個值會代表三種情況,一種是消息內容少於126存儲消息長度,如果消息長度少於UINT16的情況此值為126,當消息長度大於UINT16的情況下此值為127;這兩種情況的消息長度存儲到緊隨后面的byte[],分別是UINT16(2位byte)和UINT64(4位byte).可以通過以下c#代碼方式得到相應值
1
2
3
4
5
6
7
8
9
10
11
12
|
mPackageLength = (
uint
)(data[start] & 0x7F);
start++;
if
(mPackageLength == 126)
{
mPackageLength = BitConverter.ToUInt16(data, start);
start = start + 2;
}
else
if
(mPackageLength == 127)
{
mPackageLength = BitConverter.ToUInt64(data, start);
start = start + 8;
}
|
如果存在掩碼的情況下獲取4位掩碼值:
1
2
3
4
5
6
7
8
|
if
(hasMask)
{
mDataPackage.Masking_key =
new
byte
[4];
Buffer.BlockCopy(data, start, mDataPackage.Masking_key, 0, 4);
start = start + 4;
count = count - 4;
}
|
獲取消息體
當得到消息體長度后就可以獲取對應長度的byte[],有些消息類型是沒有長度的如%x8 denotes a connection close.對於Text類型的消息對應的byte[]是相應字符的UTF8編碼.獲取消息體還有一個需要注意的地方就是掩碼,如果存在掩碼的情況下接收的byte[]要做如下轉換處理:
1
2
3
4
5
6
|
if
(mDataPackage.Masking_key !=
null
)
{
int
length = mDataPackage.Data.Count;
for
(
var
i = 0; i < length; i++)
mDataPackage.Data.Array[i] = (
byte
)(mDataPackage.Data.Array[i] ^ mDataPackage.Masking_key[i % 4]);
}
|