WebScoket 規範
4.1 握手協議
websocket 是 獨立的基於TCP的協議, 其跟http協議的關係僅僅是 WebSocket 的握手被http 服務器當做 Upgrade request http包處理。 websocket 有自己的握手處理。 TCP連接建立後,client 發送websocket 握手請求. 請求包需求如下:
- 必須是有效的http request 格式
- HTTP request method 必須是GET,協議應不小於1.1 如: Get /chat HTTP/1.1
- 必須包括Upgrade 頭域,並且其值爲“websocket”
- 必須包括"Connection" 頭域,並且其值爲 "Upgrade"
- 必須包括"Sec-WebSocket-Key"頭域,其值採用base64編碼的隨機16字節長的字符序列, 服務器端根據該域來判斷client 確實是websocket請求而不是冒充的,如http。響應方式是,首先要獲取到請求頭中的Sec-WebSocket-Key的值,再把這一段GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"加到獲取到的Sec-WebSocket-Key的值的後面,然後拿這個字符串做SHA-1 hash計算,然後再把得到的結果通過base64加密,就得到了返回給客戶端的Sec-WebSocket-Accept的http響應頭的值。
- 如果請求來自瀏覽器客戶端,還必須包括Origin頭域 。 該頭域用於防止未授權的跨域腳本攻擊,服務器可以從Origin決定是否接受該WebSocket連接。
- 必須包括"Sec-webSocket-Version" 頭域,當前值必須是13.
- 可能包括"Sec-WebSocket-Protocol",表示client(應用程序)支持的協議列表,server選擇一個或者沒有可接受的協議響應之。
- 可能包括"Sec-WebSocket-Extensions", 協議擴展, 某類協議可能支持多個擴展,通過它可以實現協議增強
- 可能包括任意其他域,如cookie
示例如下:
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13
Server 接手到握手請求後應處理該請求包括:
- 處理請求包括處理GET 方法
- 驗證Upgrader頭域
- 驗證Connection 頭域
- 處理Sec-WebSocket-Key頭域,方法見上;
- 處理Sec-WebSocket-Version
- 處理Origin頭域,可選, 瀏覽器必須發送該頭域
- 處理Sec-WebSocket-Protocol頭域,可選
- 處理Sec-WebSocket-Extensions 頭域,可選
- 處理其他頭域,可選
- Server 發送握手響應,這裏只介紹服務器接受該連接情況下,包括:
- http Status-Line
- Upgrade 頭域 ,值必須是"websocket"
- Conntion頭域,值必須是:“Upgrade”
- Sec-WebSocket-Accept” 頭域,該頭域的值即處理Sec-WebSocket-Key" 域後的結果。
- 可選的"Sec-WebSocket-Protocol"頭域
- 可選的"Sec-WebSocket-Extensions"頭域
響應可能如下:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec-WebSocket-Protocol: chat
4.2 數據傳輸
該節主要參考了 http://blog.csdn.net/fenglibing/article/details/6852497。 在WebSocket 協議中,使用序列frames方式來傳輸數據。一個frame的標準格式如下:
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+
FIN:1位,是否是消息的結束幀(分片)
RSV1, RSV2, RSV3: 分別都是1位, 預留,用於約定自定義協議。 如果雙方之間沒有約定自定義協議,那麼這幾位的值都必須爲0,否則必須斷掉WebSocket連接;
Opcode:4位操作碼,定義有效負載數據,如果收到了一個未知的操作碼,連接也必須斷掉,以下是定義的操作碼:
%x0 表示連續消息分片
%x1 表示文本消息分片%x2 表未二進制消息分片
%x3-7 爲將來的非控制消息片斷保留的操作碼
%x8 表示連接關閉 %x9 表示心跳檢查的ping
%xA 表示心跳檢查的pong
%xB-F 爲將來的控制消息片斷的保留操作碼
Mask: 定義傳輸的數據是否有加掩碼,如果設置爲1,掩碼鍵必須放在masking-key區域,客戶端發送給服務端的所有消息,此位的值都是1;
Payload length: 傳輸數據的長度,以字節的形式表示:7位、7+16位、或者7+64位。如果這個值以字節表示是0-125這個範圍,那這個值就表示傳輸數據的長度;如果這個值是126,則隨後的兩個字節表示的是一個16進制無符號數,用來表示傳輸數據的長度;如果這個值是127,則隨後的是8個字節表示的一個64位無符合數,這個數用來表示傳輸數據的長度。多字節長度的數量是以網絡字節的順序表示。負載數據的長度爲擴展數據及應用數據之和,擴展數據的長度可能爲0,因而此時負載數據的長度就爲應用數據的長度。注意Payload length不包括Masking-key在內。
Masking-key: 0或4個字節,客戶端發送給服務端的數據,都是通過內嵌的一個32位值作爲掩碼的;掩碼鍵只有在掩碼位設置爲1的時候存在。 數據Mask方法是,第 i byte 數據 = orig-data ^ (i % 4) .
Payload data: (x+y)位,負載數據爲擴展數據及應用數據長度之和。
Extension data:x位,如果客戶端與服務端之間沒有特殊約定,那麼擴展數據的長度始終爲0,任何的擴展都必須指定擴展數據的長度,或者長度的計算方式,以及在握手時如何確定正確的握手方式。如果存在擴展數據,則擴展數據就會包括在負載數據的長度之內。
Application data:y位,任意的應用數據,放在擴展數據之後,應用數據的長度=負載數據的長度-擴展數據的長度。
把消息分片處理主要是處於以下兩個原因:
- 消息接收方事先並不知道消息大小, 而且也沒必要預留一個足夠大的buffer來處理;
- multiplexing
消息分片一些規則如下(不全):
- 爲分片消息(single-frame) 其FIN置爲1,並且opcode code 不是 0;
- 分片消息序列如下, 第一幀FIN置爲0,opcode code不是0; 接着是FIN置爲0,opcode code也是0; 最後幀 FIN爲1,opcode code爲0.
- 在分片消息發送期間可能插入了控制幀
- 控制幀不能分片
控制幀的opcode符號位爲1, 目前控制幀包括 0×8(Close), 0×9(Ping) 0xA (Pong). 0xB-0xF 被預留。
詳細解析如下,來自http://blog.csdn.net/fenglibing/article/details/6852497:
ws-frame = frame-fin
frame-rsv1
frame-rsv2
frame-rsv3
frame-opcode
frame-masked
frame-payload-length
[ frame-masking-key ]
frame-payload-data
frame-fin = %x0 ; 表示這不是當前消息的最後一幀,後面還有消息
/ %x1 ; 表示這是當前消息的最後一幀
frame-rsv1 = %x0
; 1 bit, 如果沒有擴展約定,該值必須爲0
frame-rsv2 = %x0
; 1 bit, 如果沒有擴展約定,該值必須爲0
frame-rsv3 = %x0
; 1 bit, 如果沒有擴展約定,該值必須爲0
frame-opcode = %x0 ; 表示這是一個連續幀消息
/ %x1 ; 表示文本消息
/ %x2 ; 表示二進制消息
/ %x3-7 ; 保留
/ %x8 ; 表示客戶端發起的關閉
/ %x9 ; ping(用於心跳)
/ %xA ; pong(用於心跳)
/ %xB-F ; 保留
frame-masked = %x0 ; 數據幀沒有加掩碼,後面沒有掩碼key
/ %x1 ; 數據幀加了掩碼,後面有掩碼key
frame-payload-length = %x00-7D
/ %x7E frame-payload-length-16
/ %x7F frame-payload-length-63
; 表示數據幀的長度
frame-payload-length-16 = %x0000-FFFF
; 表示數據幀的長度
frame-payload-length-63 = %x0000000000000000-7FFFFFFFFFFFFFFF
; 表示數據幀的長度
frame-masking-key = 4( %0x00-FF ) ; 掩碼key,只有當掩碼位爲1時出現
frame-payload-data = (frame-masked-extension-data
frame-masked-application-data)
; 當掩碼位爲1時,這裏的數據爲帶掩碼的數據,擴展數據及應用數據都帶掩碼
/ (frame-unmasked-extension-data
frame-unmasked-application-data) ;
當掩碼位爲0時,這裏的數據爲不帶掩碼的數據,擴展數據及應用數據都不帶掩碼
frame-masked-extension-data = *( %x00-FF ) ; 目前保留,以後定義
frame-masked-application-data = *( %x00-FF )
frame-unmasked-extension-data = *( %x00-FF ) ; 目前保留,以後定義
frame-unmasked-application-data = *( %x00-FF )
Close 處理
Close 幀的opcode是0×8. 接收到 Close 幀後,如果之前沒發送過Close幀,則其必須發送Close 幀響應,但其可以延遲發送Close響應幀,例如在其發送完數據之後發送;但是,協議不保證對方在發送Close 幀後仍會處理其後續的數據。Close幀可能Client發起也可能是Server發起。
Ping-Pong 幀
接收到Ping幀後將響應Pong幀, 主要用於檢測網絡連接情況。
Extensions
WebSocket 支持協議擴展。 例如增加一個認證處理或者速率控制等,這通過client-server 協商完成。在WebSocket 握手處理時,通過頭域Sec-WebSocket-Extensions來完成協商。 例如:
Sec-WebSocket-Extensions: mux; max-channels=4; flow-control, deflate-stream
服務器接收一個或多個extensiions 通過再起響應的Sec-WebSocket-Extensions頭域增加一個或多個extension完成。
說明:
服務器建立成功之後,如果有客戶端請求連接本服務器,需要用socket_accept等方法建立一個新的socket連接,並接收客戶端的請求信息,處理之後,返回響應信息,然後握手成功。
接下來是字符串通信,客戶端send過來一段字符串信息,服務器端接收到並返回給客戶端這個字符串。 首先我們處理接收到的信息,根據上篇文章介紹的數據傳輸格式,並firefox的FIN一直爲1,RSV1,2,3爲0,如果是文本消息,那麼opcode爲1,所以數據包的第一個數據是0x81,然後是一位mask值,firefox發來的數據是加了掩碼的,所以mask值爲1,後面跟7位是數據信息長度,我們以客戶端發送hi爲例,那麼長度就是2個字節,則第二個數據就是0x82,這裏沒有約定擴展數據,所以不存在擴展數據長度字節,接下來是4個數據的掩碼(因爲我們這裏是發送hi,2個字節的信息,小於125個字節,所以掩碼是第3-第6個數據,根據數據長度的不同,掩碼的位置也不同,如果取到那7位表示的值是126,則掩碼爲第5-第8個數據,如果取到那7位表示的值是127,則掩碼爲第11-第14個數據),後面跟客戶端發送的內容數據,處理接收到的數據我們需要用取到的掩碼依次輪流跟內容數據做異或(^)運算,第一個內容數據與第一個掩碼異或,第二個內容數據與第二個掩碼異或……第五個內容數據與第一個掩碼異或……以此類推,一直到結束,然後對內容進行編碼。
根據數據長度的不同,掩碼的位置也不同:
從第9個字節開始是 1111101=125,掩碼是第3-第6個數據
從第9個字節開始是 1111110=126,掩碼是第5-第8個數據
從第9個字節開始是 1111111=126,掩碼是第11-第14個數據
舉例一:
1 hi 2 1000000110000010 1101011011101001 3 111110 111000 10111110 10000000 4 111110 111001 11010110 11101001 5 1101000 1101001 6 7 [0] 129 byte 8 [1] 130 byte 9 [2] 214 byte 10 [3] 233 byte 11 [4] 62 byte 12 [5] 56 byte 13 [6] 190 byte 14 [7] 128 byte 15 16 17 1234567890 18 [0] 129 byte 19 [1] 138 byte 20 21 [2] 108 byte 22 [3] 255 byte 23 [4] 86 byte 24 [5] 166 byte 25 26 [6] 93 byte 27 [7] 205 byte 28 [8] 101 byte 29 [9] 146 byte 30 [10] 89 byte 31 [11] 201 byte 32 [12] 97 byte 33 [13] 158 byte 34 [14] 85 byte 35 [15] 207 byte
舉例二:
1 01234567890123456789012345678901234567890123456789 2 01234567890123456789012345678901234567890123456789 3 01234567890123456789012345678901234567890123456789 4 01234567890123456789012345678901234567890123456789 5 6 01234567890123456789012345678901 7 10000001111111100110010011001101 8 9 [0] 129 byte 10 [1] 254 byte 11 [2] 0 byte 12 [3] 201 byte 13 14 [4] 77 byte 15 [5] 175 byte 16 [6] 124 byte 17 [7] 107 byte 18 19 [8] 125 byte 20 [9] 158 byte 21 [10] 78 byte 22 [11] 88 byte 23 [12] 121 byte 24 [13] 154 byte 25 [14] 74 byte 26 [15] 92 byte 27 [16] 117 byte 28 [17] 150 byte 29 [18] 76 byte 30 [19] 90 byte 31 [20] 127 byte 32 [21] 156 byte 33 [22] 72 byte 34 [23] 94 byte 35 [24] 123 byte 36 [25] 152 byte 37 [26] 68 byte 38 [27] 82 byte 39 [28] 125 byte 40 [29] 158 byte 41 [30] 78 byte 42 [31] 88 byte 43 [32] 121 byte 44 [33] 154 byte 45 [34] 74 byte 46 [35] 92 byte 47 [36] 117 byte 48 [37] 150 byte 49 [38] 76 byte 50 [39] 90 byte 51 [40] 127 byte 52 [41] 156 byte 53 [42] 72 byte 54 [43] 94 byte 55 [44] 123 byte 56 [45] 152 byte 57 [46] 68 byte 58 [47] 82 byte 59 [48] 125 byte 60 [49] 158 byte 61 [50] 78 byte 62 [51] 88 byte 63 [52] 121 byte 64 [53] 154 byte 65 [54] 74 byte 66 [55] 92 byte 67 [56] 117 byte 68 [57] 150 byte 69 [58] 76 byte 70 [59] 90 byte 71 [60] 127 byte 72 [61] 156 byte 73 [62] 72 byte 74 [63] 94 byte 75 [64] 123 byte 76 [65] 152 byte 77 [66] 68 byte 78 [67] 82 byte 79 [68] 125 byte 80 [69] 158 byte 81 [70] 78 byte 82 [71] 88 byte 83 [72] 121 byte 84 [73] 154 byte 85 [74] 74 byte 86 [75] 92 byte 87 [76] 117 byte 88 [77] 150 byte 89 [78] 76 byte 90 [79] 90 byte 91 [80] 127 byte 92 [81] 156 byte 93 [82] 72 byte 94 [83] 94 byte 95 [84] 123 byte 96 [85] 152 byte 97 [86] 68 byte 98 [87] 82 byte 99 [88] 125 byte 100 [89] 158 byte 101 [90] 78 byte 102 [91] 88 byte 103 [92] 121 byte 104 [93] 154 byte 105 [94] 74 byte 106 [95] 92 byte 107 [96] 117 byte 108 [97] 150 byte 109 [98] 76 byte 110 [99] 90 byte 111 [100] 127 byte 112 [101] 156 byte 113 [102] 72 byte 114 [103] 94 byte 115 [104] 123 byte 116 [105] 152 byte 117 [106] 68 byte 118 [107] 82 byte 119 [108] 125 byte 120 [109] 158 byte 121 [110] 78 byte 122 [111] 88 byte 123 [112] 121 byte 124 [113] 154 byte 125 [114] 74 byte 126 [115] 92 byte 127 [116] 117 byte 128 [117] 150 byte 129 [118] 76 byte 130 [119] 90 byte 131 [120] 127 byte 132 [121] 156 byte 133 [122] 72 byte 134 [123] 94 byte 135 [124] 123 byte 136 [125] 152 byte 137 [126] 68 byte 138 [127] 82 byte 139 [128] 125 byte 140 [129] 158 byte 141 [130] 78 byte 142 [131] 88 byte 143 [132] 121 byte 144 [133] 154 byte 145 [134] 74 byte 146 [135] 92 byte 147 [136] 117 byte 148 [137] 150 byte 149 [138] 76 byte 150 [139] 90 byte 151 [140] 127 byte 152 [141] 156 byte 153 [142] 72 byte 154 [143] 94 byte 155 [144] 123 byte 156 [145] 152 byte 157 [146] 68 byte 158 [147] 82 byte 159 [148] 125 byte 160 [149] 158 byte 161 [150] 78 byte 162 [151] 88 byte 163 [152] 121 byte 164 [153] 154 byte 165 [154] 74 byte 166 [155] 92 byte 167 [156] 117 byte 168 [157] 150 byte 169 [158] 76 byte 170 [159] 90 byte 171 [160] 127 byte 172 [161] 156 byte 173 [162] 72 byte 174 [163] 94 byte 175 [164] 123 byte 176 [165] 152 byte 177 [166] 68 byte 178 [167] 82 byte 179 [168] 125 byte 180 [169] 158 byte 181 [170] 78 byte 182 [171] 88 byte 183 [172] 121 byte 184 [173] 154 byte 185 [174] 74 byte 186 [175] 92 byte 187 [176] 117 byte 188 [177] 150 byte 189 [178] 76 byte 190 [179] 90 byte 191 [180] 127 byte 192 [181] 156 byte 193 [182] 72 byte 194 [183] 94 byte 195 [184] 123 byte 196 [185] 152 byte 197 [186] 68 byte 198 [187] 82 byte 199 [188] 125 byte 200 [189] 158 byte 201 [190] 78 byte 202 [191] 88 byte 203 [192] 121 byte 204 [193] 154 byte 205 [194] 74 byte 206 [195] 92 byte 207 [196] 117 byte 208 [197] 150 byte 209 [198] 76 byte 210 [199] 90 byte 211 [200] 127 byte 212 [201] 156 byte 213 [202] 72 byte 214 [203] 94 byte 215 [204] 123 byte 216 [205] 152 byte 217 [206] 68 byte 218 [207] 82 byte 219 [208] 71 byte
代碼分析掩碼:
1 /// <summary> 2 ///判斷傳入數據是否存在掩碼 3 /// 傳入數據:hi 4 /// socket接收到的二進制數據: 5 /// 1000000110000010 1101011011101001 6 /// 111110 111000 10111110 10000000 7 /// 掩碼異或的操作: 8 /// 111110 111000 10111110 10000000 9 /// 進行異或^ 111110 111001 11010110 11101001 10 /// 結果: 1101000 1101001 11 /// 數據樣例: 12 /// [0] 129 byte 13 /// [1] 130 byte 14 /// [2] 214 byte 15 /// [3] 233 byte 16 /// [4] 62 byte 17 /// [5] 56 byte 18 /// [6] 190 byte 19 /// [7] 128 byte 20 /// </summary> 21 /// <returns></returns> 22 private string UnWrap() 23 { 24 string result = string.Empty; 25 26 // 計算非空位置 27 int lastStation = GetLastZero(); 28 29 // 利用掩碼對org-data進行異或 30 int frame_masking_key = 1; 31 for (int i = 6; i <= lastStation; i++) 32 { 33 frame_masking_key = i % 4; 34 frame_masking_key = frame_masking_key == 0 ? 4 : frame_masking_key; 35 frame_masking_key = frame_masking_key == 1 ? 5 : frame_masking_key; 36 receivedDataBuffer[i] = Convert.ToByte(receivedDataBuffer[i] ^ receivedDataBuffer[frame_masking_key]); 37 } 38 39 System.Text.UTF8Encoding decoder = new System.Text.UTF8Encoding(); 40 result = decoder.GetString(receivedDataBuffer, 6, lastStation - 6 + 1); 41 42 return result; 43 44 }
1 /// <summary> 2 /// 對傳入數據進行無掩碼轉換 3 /// </summary> 4 /// <returns></returns> 5 public static byte[] Wrap(string msg, int maxBufferSize) 6 { 7 // 掩碼開始位置 8 int masking_key_startIndex = 2; 9 10 byte[] msgByte = Encoding.UTF8.GetBytes(msg); 11 12 // 計算掩碼開始位置 13 if (msgByte.Length <= 125) 14 { 15 masking_key_startIndex = 2; 16 } 17 else if (msgByte.Length > 65536) 18 { 19 masking_key_startIndex = 10; 20 } 21 else if (msgByte.Length > 125) 22 { 23 masking_key_startIndex = 4; 24 } 25 26 // 創建返回數據 27 byte[] result = new byte[msgByte.Length + masking_key_startIndex]; 28 29 // 開始計算ws-frame 30 // frame-fin + frame-rsv1 + frame-rsv2 + frame-rsv3 + frame-opcode 31 result[0] = 0x81; // 129 32 33 // frame-masked+frame-payload-length 34 // 從第9個字節開始是 1111101=125,掩碼是第3-第6個數據 35 // 從第9個字節開始是 1111110>=126,掩碼是第5-第8個數據 36 if (msgByte.Length <= 125) 37 { 38 result[1] = Convert.ToByte(msgByte.Length); 39 } 40 else if (msgByte.Length > 65536) 41 { 42 result[1] = 0x7F; // 127 43 } 44 else if (msgByte.Length > 125) 45 { 46 result[1] = 0x7E; // 126 47 result[2] = Convert.ToByte(msgByte.Length >> 8); 48 result[3] = Convert.ToByte(msgByte.Length % 256); 49 } 50 51 // 將數據編碼放到最後 52 Array.Copy(msgByte, 0, result, masking_key_startIndex, msgByte.Length); 53 54 return result; 55 }
WebSocket 協議:
public enum WebSocketProtocol { /* * * Request GET /WebIM5?uaid=200513807p8912-8de8c7e2-c963-4f67-8aca-8028797efbc1&re=0 HTTP/1.1 Upgrade: WebSocket Connection: Upgrade Host: 10.10.150.60:5002 Origin: https://localhost:444 Sec-WebSocket-Key1: 3+3 1 8kgV"m 0 8 64u43 Sec-WebSocket-Key2: 3_7891 6 4 `50 `8 * * Response HTTP/1.1 101 WebSocket Protocol Handshake Upgrade: WebSocket Connection: Upgrade Sec-WebSocket-Origin: https://localhost:444 Sec-WebSocket-Location: ws://192.168.110..... Sec-WebSocket-Protocol: WebIM5 * * asdfalskdfa * */ draft_00 = 0, /* * * Request GET /WebIM5?uaid=200513807p8912-2e695e5b-9b46-4511-b59e-28981b4ab327&re=0 HTTP/1.1 Upgrade: websocket Connection: Upgrade Host: 10.10.150.60:5002 Origin: https://localhost:444 Sec-WebSocket-Key: 1o4Jk9XPGvTX66OxmNMaww== Sec-WebSocket-Version: 13 * * Response HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec-WebSocket-Protocol: WebIM5 * */ draft_17 = 17 }
支持safari+chrome+firefox:
1 // 開始連接到服務器 2 var pollingInterval; 3 _ws = new WebSocket("ws://192.168.0.103:5002/WebIM5?uaid=200513807p8912-5a78ae8a-cabb-46ee-8d8a-85874bbc942c&re=0"); 4 //_ws = new window.MozWebSocket("ws://192.168.0.103:5002/WebIM5?uaid=200513807p8912-5a78ae8a-cabb-46ee-8d8a-85874bbc942c&re=0"); 5 _ws.onopen = function () { 6 alert("onopen"); 7 8 _socketCreated = true; 9 var args 10 _ws.send("1234567890"); 11 _ws.send("33322233"); 12 }; 13 _ws.onmessage = function (event) { 14 console.log("event.data=" + event.data); 15 16 }; 17 _ws.onclose = function () { 18 alert("onclose"); 19 console.log("onclose"); 20 }; 21 _ws.onerror = function () { 22 console.log("onerror"); 23 }; 24 function send() { 25 _ws.send(document.getElementById("msg").value); 26 }
其中,safari和chrome都是 :_ws = new WebSocket("ws://ip:port"); 但是firefox是:_ws = new window.MozWebSocket("ws://ip:port");