WebSocket protocol 是HTML5一種新的協(xié)議。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工通信(full-duplex)。一開始的握手需要借助HTTP請求完成。
——百度百科
什么是WebSocket
WebSocket 是 HTML5 開始提供的一種在單個(gè) TCP 連接上進(jìn)行全雙工通訊的協(xié)議。
原理
WebSocket 使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單,允許服務(wù)端主動向客戶端推送數(shù)據(jù)。在 WebSocket API 中,瀏覽器和服務(wù)器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接,并進(jìn)行雙向數(shù)據(jù)傳輸。
在 WebSocket API 中,瀏覽器和服務(wù)器只需要做一個(gè)握手的動作,然后,瀏覽器和服務(wù)器之間就形成了一條快速通道。兩者之間就直接可以數(shù)據(jù)互相傳送。
連接過程
- 瀏覽器、服務(wù)器建立TCP連接,三次握手。這是通信的基礎(chǔ),傳輸控制層,若失敗后續(xù)都不執(zhí)行。
- TCP連接成功后,瀏覽器通過HTTP協(xié)議向服務(wù)器傳送WebSocket支持的版本號等信息。(開始前的HTTP握手)
- 服務(wù)器收到客戶端的握手請求后,同樣采用HTTP協(xié)議回饋數(shù)據(jù)。
- 當(dāng)收到了連接成功的消息后,通過TCP通道進(jìn)行傳輸通信。
目的
現(xiàn)在,很多網(wǎng)站為了實(shí)現(xiàn)推送技術(shù),所用的技術(shù)都是 Ajax 輪詢。輪詢是在特定的的時(shí)間間隔(如每1秒),由瀏覽器對服務(wù)器發(fā)出HTTP請求,然后由服務(wù)器返回最新的數(shù)據(jù)給客戶端的瀏覽器。這種傳統(tǒng)的模式帶來很明顯的缺點(diǎn),即瀏覽器需要不斷的向服務(wù)器發(fā)出請求,然而HTTP請求可能包含較長的頭部,其中真正有效的數(shù)據(jù)可能只是很小的一部分,顯然這樣會浪費(fèi)很多的帶寬等資源。
HTML5 定義的 WebSocket 協(xié)議,能更好的節(jié)省服務(wù)器資源和帶寬,并且能夠更實(shí)時(shí)地進(jìn)行通訊。

瀏覽器通過 JavaScript 向服務(wù)器發(fā)出建立 WebSocket 連接的請求,連接建立以后,客戶端和服務(wù)器端就可以通過 TCP 連接直接交換數(shù)據(jù)。
當(dāng)你獲取 Web Socket 連接后,你可以通過 send() 方法來向服務(wù)器發(fā)送數(shù)據(jù),并通過 onmessage 事件來接收服務(wù)器返回的數(shù)據(jù)。
以下 API 用于創(chuàng)建 WebSocket 對象。
var Socket = new WebSocket(url, [protocol] );
以上代碼中的第一個(gè)參數(shù) url, 指定連接的 URL。第二個(gè)參數(shù) protocol 是可選的,指定了可接受的子協(xié)議。
WebSocket與HTTP的關(guān)系
相同點(diǎn)
- 1. 都是一樣基于TCP的,都是可靠性傳輸協(xié)議。
- 2. 都是應(yīng)用層協(xié)議。
不同點(diǎn)
- 1. WebSocket是雙向通信協(xié)議,模擬Socket協(xié)議,可以雙向發(fā)送或接受信息。HTTP是單向的。
- 2. WebSocket是需要握手進(jìn)行建立連接的。
聯(lián)系
WebSocket在建立握手時(shí),數(shù)據(jù)是通過HTTP傳輸?shù)?。但是建立之后,在真正傳輸時(shí)候是不需要HTTP協(xié)議的。
WebSocket 屬性
以下是 WebSocket 對象的屬性。假定我們使用了以上代碼創(chuàng)建了 Socket 對象:
| 屬性 | 描述 |
|---|---|
| Socket.readyState | 只讀屬性 readyState 表示連接狀態(tài),可以是以下值: 0 - 表示連接尚未建立。1. 表示連接已建立,可以進(jìn)行通信。2. 表示連接正在進(jìn)行關(guān)閉。 3. 表示連接已經(jīng)關(guān)閉或者連接不能打開。 |
| Socket.bufferedAmount | 只讀屬性 bufferedAmount 已被 send() 放入正在隊(duì)列中等待傳輸,但是還沒有發(fā)出的 UTF-8 文本字節(jié)數(shù)。 |
WebSocket 事件
以下是 WebSocket 對象的相關(guān)事件。假定我們使用了以上代碼創(chuàng)建了 Socket 對象:
| 事件 | 事件處理程序 | 描述 |
|---|---|---|
| open | Socket.onopen | 連接建立觸發(fā) |
| message | Socket.onmessage | 客戶端接收服務(wù)端數(shù)據(jù)時(shí)觸發(fā) |
| error | Socket.onerror | 通信發(fā)生錯(cuò)誤時(shí)觸發(fā) |
| close | Socket.onclose | 連接關(guān)閉時(shí)觸發(fā) |
WebSocket 方法
以下是 WebSocket 對象的相關(guān)方法。假定我們使用了以上代碼創(chuàng)建了 Socket 對象:
| 方法 | 描述 |
|---|---|
| Socket.send() | 使用連接發(fā)送數(shù)據(jù) |
| Socket.close() | 關(guān)閉連接 |
WebSocket實(shí)例
WebSocket 協(xié)議本質(zhì)上是一個(gè)基于 TCP 的協(xié)議。
為了建立一個(gè) WebSocket 連接,客戶端瀏覽器首先要向服務(wù)器發(fā)起一個(gè) HTTP 請求,這個(gè)請求和通常的 HTTP 請求不同,包含了一些附加頭信息,其中附加頭信息"Upgrade: WebSocket"表明這是一個(gè)申請協(xié)議升級的 HTTP 請求,服務(wù)器端解析這些附加的頭信息然后產(chǎn)生應(yīng)答信息返回給客戶端,客戶端和服務(wù)器端的 WebSocket 連接就建立起來了,雙方就可以通過這個(gè)連接通道自由的傳遞信息,并且這個(gè)連接會持續(xù)存在直到客戶端或者服務(wù)器端的某一方主動的關(guān)閉連接。
客戶端的 HTML 和 JavaScript
注意:websocket地址:ws://{ip}:{端口}/{工程名}/{ServerEndpoint路徑}}
地址使用localhost會出現(xiàn)如下錯(cuò)誤:
WebSocket connection to 'ws://localhost:8080/aidTibet/websocket' failed: Error during WebSocket handshake: Unexpected response code: 302
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>測試WebSocket</title>
<script type="text/javascript">
function WebSocketTest()
{
if ("WebSocket" in window)
{
alert("您的瀏覽器支持WebScoket!")
// 打開一個(gè)web socket
var ws = new WebSocket("ws://172.20.241.171:8080/websocket")
ws.onopen = function(){
// web socket已連接上,使用 send() 發(fā)送數(shù)據(jù)
ws.send("發(fā)送數(shù)據(jù)")
alert("數(shù)據(jù)發(fā)送中...")
}
ws.onmessage = function(evt){
var received_msg = evt.data
alert("數(shù)據(jù)已接收..." + received_msg)
}
ws.onclose = function() {
// 關(guān)閉WebSocket
alert("連接已關(guān)閉...")
}
} else {
// 瀏覽器不支持 WebSocket
alert("您的瀏覽器不支持 WebSocket!")
}
}
</script>
</head>
<body>
<div>
<a href="javascript:WebSocketTest()">運(yùn)行 WebSocket</a>
</div>
</body>
</html>
服務(wù)端
服務(wù)端使用SpringBoot結(jié)合WebSocket的方式實(shí)現(xiàn)。
- 導(dǎo)入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
- 新建WebSocket配置類
@Configuration
public class WebsocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
- 新建WebSocket服務(wù)端
@ServerEndpoint("/websocket")
@Component
@Slf4j
public class MyWebsocketServer {
/**
* 存放所有在線的客戶端
*/
private static Map<String, Session> clients = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session) {
log.info("有新的客戶端連接了: {}", session.getId());
//將新用戶存入在線的組
clients.put(session.getId(), session);
}
/**
* 客戶端關(guān)閉
* @param session session
*/
@OnClose
public void onClose(Session session) {
log.info("有用戶斷開了, id為:{}", session.getId());
//將掉線的用戶移除在線的組里
clients.remove(session.getId());
}
/**
* 發(fā)生錯(cuò)誤
* @param throwable e
*/
@OnError
public void onError(Throwable throwable) {
throwable.printStackTrace();
}
/**
* 收到客戶端發(fā)來消息
* @param message 消息對象
*/
@OnMessage
public void onMessage(String message) {
log.info("服務(wù)端收到客戶端發(fā)來的消息: {}", message);
this.sendAll(message);
}
/**
* 群發(fā)消息
* @param message 消息內(nèi)容
*/
private void sendAll(String message) {
for (Map.Entry<String, Session> sessionEntry : clients.entrySet()) {
sessionEntry.getValue().getAsyncRemote().sendText(message);
}
}
}
啟動后,使用客戶端發(fā)送數(shù)據(jù):

參考:http://www.itdecent.cn/p/8c4983a3ca2e(包含創(chuàng)建連接時(shí)傳遞userId已區(qū)分不同客戶端)