##
## 一、為什么需要 WebSocket?
初次接觸 WebSocket 的人,都會問同樣的問題:我們已經有了 HTTP 協議,為什么還需要另一個協議?它能帶來什么好處?
答案很簡單,因為 HTTP 協議有一個缺陷:通信只能由客戶端發起。
舉例來說,我們想了解今天的天氣,只能是客戶端向服務器發出請求,服務器返回查詢結果。HTTP 協議做不到服務器主動向客戶端推送信息。
這種單向請求的特點,注定了如果服務器有連續的狀態變化,客戶端要獲知就非常麻煩。我們只能使用["輪詢"](https://www.pubnub.com/blog/2014-12-01-http-long-polling/):每隔一段時候,就發出一個詢問,了解服務器有沒有新的信息。最典型的場景就是聊天室。
輪詢的效率低,非常浪費資源(因為必須不停連接,或者 HTTP 連接始終打開)。因此,工程師們一直在思考,有沒有更好的方法。WebSocket 就是這樣發明的。
## 二、簡介
WebSocket 協議在2008年誕生,2011年成為國際標準。所有瀏覽器都已經支持了。
它的最大特點就是,服務器可以主動向客戶端推送信息,客戶端也可以主動向服務器發送信息,是真正的雙向平等對話,屬于[服務器推送技術](https://en.wikipedia.org/wiki/Push_technology)的一種。

其他特點包括:
(1)建立在 TCP 協議之上,服務器端的實現比較容易。
(2)與 HTTP 協議有著良好的兼容性。默認端口也是80和443,并且握手階段采用 HTTP 協議,因此握手時不容易屏蔽,能通過各種 HTTP 代理服務器。
(3)數據格式比較輕量,性能開銷小,通信高效。
(4)可以發送文本,也可以發送二進制數據。
(5)沒有同源限制,客戶端可以與任意服務器通信。
(6)協議標識符是`ws`(如果加密,則為`wss`),服務器網址就是 URL。
> ~~~markup
>
> ws://example.com:80/some/path
>
> ~~~

**長連接**:一個連接上可以連續發送多個數據包,在連接期間,如果沒有數據包發送,需要雙方發鏈路檢查包。
**TCP/IP**:TCP/IP屬于傳輸層,主要解決數據在網絡中的傳輸問題,只管傳輸數據。但是那樣對傳輸的數據沒有一個規范的封裝、解析等處理,使得傳輸的數據就很難識別,所以才有了應用層協議對數據的封裝、解析等,如HTTP協議。
**HTTP**:HTTP是應用層協議,封裝解析傳輸的數據。 從HTTP1.1開始其實就默認開啟了長連接,也就是請求header中看到的Connection:Keep-alive。但是這個長連接只是說保持了(服務器可以告訴客戶端保持時間Keep-Alive:timeout=200;max=20;)這個TCP通道,直接Request - Response,而不需要再創建一個連接通道,做到了一個性能優化。但是HTTP通訊本身還是Request - Response。
**socket**:與HTTP不一樣,socket不是協議,它是在程序層面上對傳輸層協議(可以主要理解為TCP/IP)的接口封裝。 我們知道傳輸層的協議,是解決數據在網絡中傳輸的,那么socket就是傳輸通道兩端的接口。所以對于前端而言,socket也可以簡單的理解為對TCP/IP的抽象協議。
**WebSocket**: WebSocket是包裝成了一個應用層協議作為socket,從而能夠讓客戶端和遠程服務端通過web建立全雙工通信。websocket提供ws和wss兩種URL方案。[協議英文文檔](https://tools.ietf.org/rfc/rfc6455.txt)和[中文翻譯](http://blog.csdn.net/stoneson/article/details/8063802)
## WebSocket API
* * *
使用WebSocket構造函數創建一個WebSocket連接,返回一個websocket實例。通過這個實例我們可以監聽事件,這些事件可以知道什么時候簡歷連接,什么時候有消息被推過來了,什么時候發生錯誤了,時候連接關閉。我們可以使用node搭建一個WebSocket服務器來看看,[github](https://github.com/daipeng7/websocket)。同樣也可以調用[websocket.org](http://demos.kaazing.com/echo/)網站的demo服務器[demos.kaazing.com/echo/](http://demos.kaazing.com/echo/)。
### 事件
~~~
//創建WebSocket實例,可以使用ws和wss。第二個參數可以選填自定義協議,如果多協議,可以以數組方式
var socket = new WebSocket('ws://demos.kaazing.com/echo');
復制代碼
~~~
* **open**
服務器相應WebSocket連接請求觸發
~~~
socket.onopen = (event) => {
socket.send('Hello Server!');
};
復制代碼
~~~
* **message**
服務器有 響應數據 觸發
~~~
socket.onmessage = (event) => {
debugger;
console.log(event.data);
};
復制代碼
~~~
* **error**
出錯時觸發,并且會關閉連接。這時可以根據錯誤信息進行按需處理
~~~
socket.onerror = (event) => {
console.log('error');
}
復制代碼
~~~
* **close**
~~~
連接關閉時觸發,這在兩端都可以關閉。另外如果連接失敗也是會觸發的。
針對關閉一般我們會做一些異常處理,關于異常參數:
1. socket.readyState
2 正在關閉 3 已經關閉
2. event.wasClean [Boolean]
true 客戶端或者服務器端調用close主動關閉
false 反之
3. event.code [Number] 關閉連接的狀態碼。socket.close(code, reason)
4. event.reason [String]
關閉連接的原因。socket.close(code, reason)
socket.onclose = (event) => {
debugger;
}
復制代碼
~~~
### 方法
* **send**
send(data) 發送方法 data 可以是String/Blob/ArrayBuffer/ByteBuffer等
需要注意,使用send發送數據,必須是連接建立之后。一般會在onopen事件觸發后發送:
~~~
socket.onopen = (event) => {
socket.send('Hello Server!');
};
復制代碼
~~~
如果是需要去響應別的事件再發送消息,也就是將WebSocket實例socket交給別的方法使用,因為在發送時你不一定知道socket是否還連接著,所以可以檢查readyState屬性的值是否等于OPEN常量,也就是查看socket是否還連接著。
~~~
btn.onclick = function startSocket(){
//判斷是否連接是否還存在
if(socket.readyState == WebSocket.OPEN){
var message = document.getElementById("message").value;
if(message != "") socket.send(message);
}
}
復制代碼
~~~
* **close**
使用close(\[code\[,reason\]\])方法可以關閉連接。code和reason均為選填
~~~
// 正常關閉
socket.close(1000, "closing normally");
復制代碼
~~~
### 常量
| 常量名 | 值 | 描述 |
| --- | --- | --- |
| CONNECTING | 0 | 連接還未開啟 |
| OPEN | 1 | 連接開啟可以通信 |
| CLOSING | 2 | 連接正在關閉中 |
| CLOSED | 3 | 連接已經關閉 |