# WebSocket協議
WebSocket協議是一種網絡通信協議,是HTML5之后提出的一種在單個TCP上進行`全雙工`通訊的協議。可以使服務端主動的向客戶端發送消息,常用于`推送服務。`其特點如下:
* HTTP協議(1.0)是單向的沒有連接狀態的協議,客戶端瀏覽器為了獲取服務器的動態變化信息,需要配合JavaScript和AJAX進行不斷的異步`輪詢請求`,需要不斷的建立連接,非常耗費資源。
* WebSocket能夠使任一方主動的建立起連接,并且連接只要建立一次就`一直保持連接`狀態。
WebSocket并不是全新的協議,而是利用HTTP協議來建立連接的,其創建過程如下:
1. 瀏覽器發送HTTP請求
~~~
?GET ws://localhost:3000/hello HTTP/1.1
?Host: localhost
?Upgrade: websocket
?Connection: Upgrade
?Origin: http://localhost:3000
?Sec-WebSocket-Key: client-random-string
?Sec-WebSocket-Version: 13
~~~
說明:
* GET請求的地址不是類似`/path/`,而是以`ws://`開頭的地址;
* 請求頭`Upgrade: websocket`和`Connection: Upgrade`表示這個連接將要被轉換為WebSocket連接;
* `Sec-WebSocket-Key`是用于標識這個連接,并非用于加密數據;
* `Sec-WebSocket-Version`指定了WebSocket的協議版本。
2. 服務器接受該請求,就會返回下面的響應:
~~~
?HTTP/1.1 101 Switching Protocols
?Upgrade: websocket
?Connection: Upgrade
?Sec-WebSocket-Accept: server-random-string
~~~
該響應代碼`101`表示本次連接的HTTP協議即將被更改,更改后的協議就是`Upgrade: websocket`指定的WebSocket協議。
3. WebSocket連接建立,服務器端可以主動的發送消息給瀏覽器,可以是二進制數據和文本數據。
下面是如何在前端分離的項目中使用WebSocket協議,前端Vue,后端Spring Boot。
## WebSocket客戶端
適用于支持html5的瀏覽器,其開放的API(不需要引入)為:
1. 創建Web Socket連接
~~~
?var Socket = new WebSocket(url, [protocol] );
~~~
url表示指定連接的URL,protocol是子協議,可選。
對象屬性:
~~~
?Socket.readyState: 連接狀態,0表示連接未建立;1表示連接建立,可以進行通信;2表示連接正在關閉;3表示連接已經關閉,并且不能打開。
?Socket.bufferAmount:等待傳輸的隊列。
~~~
對象事件:
| 事件 | 事件處理程序 | 描述 |
| --- | --- | --- |
| open | Socket.onopen | 連接建立時觸發 |
| message | Socket.onmessage | 瀏覽器客戶端接收服務器的信息時觸發 |
| error | Socket.onerror | 通信發生錯誤時觸發 |
| close | Socket.onclose | 連接關閉時觸發 |
對象方法:
* Socket.send():使用連接發送數據。
* Socket.close():關閉連接。
~~~
?// 初始化一個 WebSocket 對象
?var socket = new WebSocket('ws://localhost:9998/echo');
??
?// 建立 web socket 連接成功觸發事件
?socket.onopen = function() {
? ?// 使用 send() 方法發送數據
? ?socket.send('發送數據');
? ?alert('數據發送中...');
?};
??
?// 接收服務端數據時觸發事件
?socket.onmessage = function(evt) {
? ?var received_msg = evt.data;
? ?alert('數據已接收...');
?};
??
?// 斷開 web socket 連接成功觸發事件
?socket.onclose = function() {
? ?alert('連接已關閉...');
?};
~~~
### Vue使用web socket
~~~
?// 建立web socket連接
?webSocket() {
? ? ?if ('WebSocket' in window) {
? ? ?this.websocket = new WebSocket("ws://localhost:8081/websocket/hello");
? ? } else {
? ? ?console.log("你的瀏覽器還不支持web socket");
?}
?console.log(this.websocket);
?this.websocket.onopen = this.webSocketOnOpen;
?this.websocket.onclose = this.webSocketOnClose;
?this.websocket.onmessage = this.webSocketOnMessage;
?this.websocket.onerror = this.webSocketOnError;
?},
??
?// 連接建立之后
?webSocketOnOpen() {
? ? ?console.log("與后端建立連接");
? ? ?var data = "這里是端口8001建立連接";
? ? ?this.websocket.send(data);
?},
??
?// 連接關閉之后
?webSocketOnClose() {
? ? ?console.log("與后端關閉連接");
?},
??
?// 發送消息
?webSocketOnMessage(res) {
? ? ?console.log("接收到后端發送回來的消息");
? ? ?console.log(res.data)
?},
??
?// 發生錯誤
?webSocketOnError() {
? ? ?console.log("發生錯誤~");
?},
~~~
使用原生的web socket就行了,也有封裝了web socket的工具可以使用。
## WebSocket服務端
如果是使用Tomcat服務器的話,要版本7以上才支持,使用Spring Boot整合WebSocket的過程如下:
1. 引入依賴
~~~
?<dependency>
? ? ?<groupId>org.springframework.boot</groupId>
? ? ?<artifactId>spring-boot-starter-websocket</artifactId>
?</dependency>
~~~
2. 創建核心配置類
~~~
?import org.springframework.context.annotation.Bean;
?import org.springframework.context.annotation.Configuration;
?import org.springframework.messaging.simp.config.MessageBrokerRegistry;
?import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
?import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
?import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
?import org.springframework.web.socket.server.standard.ServerEndpointExporter;
??
?/**
? * spring boot web socket 核心配置類
? */
?@Configuration
?public class WebSocketConfig {
? /**
? ? ? * 配置服務端點導出器
? ? ? * @return
? ? ? */
? ? ?@Bean
? ? ?public ServerEndpointExporter serverEndpointExporter() {
? ? ? ? ?return new ServerEndpointExporter();
? ? }
??
?}
~~~
3. 創建服務端點
~~~
?import lombok.extern.slf4j.Slf4j;
?import org.springframework.stereotype.Component;
?import org.springframework.web.bind.annotation.RequestParam;
??
?import javax.websocket.*;
?import javax.websocket.server.PathParam;
?import javax.websocket.server.ServerEndpoint;
?import java.io.IOException;
?import java.util.Map;
?import java.util.concurrent.CopyOnWriteArraySet;
??
?/**
? * 后端實現web socket
? */
?@Component
?@ServerEndpoint("/hello")
?@Slf4j
?public class WebSocketServer {
? ? ?/**
? ? ? * 靜態變量,用來記錄當前在線連接數
? ? ? * 注意線程安全問題
? ? ? */
? ? ?private static int onlineCount = 0;
? ? ?
? ? ?/**
? ? ? * concurrent包的線程安全集合,存放每個客戶端對應的web socket對象
? ? ? */
? ? ?private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
??
? ? ?/**
? ? ? * 與某個客戶端的連接會話,需要聽過它來給客戶端發送數據
? ? ? * 每個前端的WebSocket對象建立起來的有一個session對象
? ? ? */
? ? ?private Session session;
??
? ? ?/**
? ? ? * 連接建立成功調用的方法
? ? ? */
? ? ?@OnOpen
? ? ?public void onOpen(Session session) {
? ? ? ? ?this.session = session;
? ? ? ? ?System.out.println("session = " + session);
? ? ? ? ?// 添加當前web socket對象
? ? ? ? ?webSocketSet.add(this);
? ? ? ? ?// 累加在線人數
? ? ? ? ?addOnlineCount();
? ? ? ? ?log.info("有新連接加入!當前在線人數為" + getOnlineCount());
? ? ? ? ?try {
? ? ? ? ? ? ?sendMessage("有新連接加入!當前在線人數為" + getOnlineCount());
? ? ? ? } catch (IOException e) {
? ? ? ? ? ? ?log.error("websocket IO異常");
? ? ? ? }
? ? }
??
? ? ?/**
? ? ? * 連接關閉調用的方法
? ? ? */
? ? ?@OnClose
? ? ?public void onClose() {
? ? ? ? ?// 將連接的web socket對象從set中移除
? ? ? ? ?webSocketSet.remove(this);
? ? ? ? ?// 在線人數-1
? ? ? ? ?subOnlineCount();
? ? ? ? ?log.info("有一連接關閉!當前在線人數為" + getOnlineCount());
? ? }
??
? ? ?/**
? ? ? * 收到客戶端消息后調用的方法
? ? ? * @param message 客戶端發送過來的消息
? ? ? */
? ? ?@OnMessage
? ? ?public void onMessage(String message, Session session) {
? ? ? ? ?log.info("來自客戶端的消息:" + message);
??
? ? ? ? ?//群發消息
? ? ? ? ?for (WebSocketServer item : webSocketSet) {
? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ?item.sendMessage(message);
? ? ? ? ? ? } catch (IOException e) {
? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? }
? ? }
??
? ? ?/**
? ? ? * 發生錯誤
? ? ? * @param session
? ? ? * @param error
? ? ? */
? ? ?@OnError
? ? ?public void onError(Session session, Throwable error) {
? ? ? ? ?log.error("發生錯誤");
? ? ? ? ?error.printStackTrace();
? ? }
??
? ? ?/**
? ? ? * 給客戶端瀏覽器發送消息
? ? ? * @param message
? ? ? * @throws IOException
? ? ? */
? ? ?public void sendMessage(String message) throws IOException {
? ? ? ? ?this.session.getBasicRemote().sendText(message);
? ? }
? ? ?/**
? ? ? * 群發自定義消息
? ? ? * */
? ? ?public static void sendInfo(String message) throws IOException {
? ? ? ? ?log.info(message);
? ? ? ? ?for (WebSocketServer item : webSocketSet) {
? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ?item.sendMessage(message);
? ? ? ? ? ? } catch (IOException e) {
? ? ? ? ? ? ? ? ?continue;
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? ?public static synchronized int getOnlineCount() {
? ? ? ? ?return onlineCount;
? ? }
? ? ?public static synchronized void addOnlineCount() {
? ? ? ? ?WebSocketServer.onlineCount++;
? ? }
? ? ?public static synchronized void subOnlineCount() {
? ? ? ? ?WebSocketServer.onlineCount--;
? ? }
?}
~~~
如果要獲取前端發送的消息可用:
~~~
?@Component
?@ServerEndPoint("/hello/{name}/{age}")
?public class WebSocketServer {
? ? ?@OnOpen
? ? ?public void onOpen(@PathParam("name")String name, @PathParam("age")int age) {
? ? ? ? ?
? ? }
?}
~~~
【參考】廖雪峰的官方網站[https://www.liaoxuefeng.com/wiki/1022910821149312/1103303693824096](https://www.liaoxuefeng.com/wiki/1022910821149312/1103303693824096)
- 第一章 Java基礎
- ThreadLocal
- Java異常體系
- Java集合框架
- List接口及其實現類
- Queue接口及其實現類
- Set接口及其實現類
- Map接口及其實現類
- JDK1.8新特性
- Lambda表達式
- 常用函數式接口
- stream流
- 面試
- 第二章 Java虛擬機
- 第一節、運行時數據區
- 第二節、垃圾回收
- 第三節、類加載機制
- 第四節、類文件與字節碼指令
- 第五節、語法糖
- 第六節、運行期優化
- 面試常見問題
- 第三章 并發編程
- 第一節、Java中的線程
- 第二節、Java中的鎖
- 第三節、線程池
- 第四節、并發工具類
- AQS
- 第四章 網絡編程
- WebSocket協議
- Netty
- Netty入門
- Netty-自定義協議
- 面試題
- IO
- 網絡IO模型
- 第五章 操作系統
- IO
- 文件系統的相關概念
- Java幾種文件讀寫方式性能對比
- Socket
- 內存管理
- 進程、線程、協程
- IO模型的演化過程
- 第六章 計算機網絡
- 第七章 消息隊列
- RabbitMQ
- 第八章 開發框架
- Spring
- Spring事務
- Spring MVC
- Spring Boot
- Mybatis
- Mybatis-Plus
- Shiro
- 第九章 數據庫
- Mysql
- Mysql中的索引
- Mysql中的鎖
- 面試常見問題
- Mysql中的日志
- InnoDB存儲引擎
- 事務
- Redis
- redis的數據類型
- redis數據結構
- Redis主從復制
- 哨兵模式
- 面試題
- Spring Boot整合Lettuce+Redisson實現布隆過濾器
- 集群
- Redis網絡IO模型
- 第十章 設計模式
- 設計模式-七大原則
- 設計模式-單例模式
- 設計模式-備忘錄模式
- 設計模式-原型模式
- 設計模式-責任鏈模式
- 設計模式-過濾模式
- 設計模式-觀察者模式
- 設計模式-工廠方法模式
- 設計模式-抽象工廠模式
- 設計模式-代理模式
- 第十一章 后端開發常用工具、庫
- Docker
- Docker安裝Mysql
- 第十二章 中間件
- ZooKeeper