🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# WebSocket协议 &nbsp;&nbsp;&nbsp;&nbsp;WebSocket协议是一种网络通信协议,是HTML5之后提出的一种在单个TCP上进行`全双工`通讯的协议。可以使服务端主动的向客户端发送消息,常用于`推送服务。`其特点如下: * HTTP协议(1.0)是单向的没有连接状态的协议,客户端浏览器为了获取服务器的动态变化信息,需要配合JavaScript和AJAX进行不断的异步`轮询请求`,需要不断的建立连接,非常耗费资源。 * WebSocket能够使任一方主动的建立起连接,并且连接只要建立一次就`一直保持连接`状态。 &nbsp;&nbsp;&nbsp;&nbsp;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 ~~~ &nbsp;&nbsp;&nbsp;&nbsp;该响应代码`101`表示本次连接的HTTP协议即将被更改,更改后的协议就是`Upgrade: websocket`指定的WebSocket协议。 3. WebSocket连接建立,服务器端可以主动的发送消息给浏览器,可以是二进制数据和文本数据。 &nbsp;&nbsp;&nbsp;&nbsp; 下面是如何在前端分离的项目中使用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('连接已关闭...');  }; ~~~ &nbsp;&nbsp;&nbsp;&nbsp; ### 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的工具可以使用。 &nbsp;&nbsp;&nbsp;&nbsp; ## 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) {               }  } ~~~ &nbsp;&nbsp;&nbsp;&nbsp; 【参考】廖雪峰的官方网站[https://www.liaoxuefeng.com/wiki/1022910821149312/1103303693824096](https://www.liaoxuefeng.com/wiki/1022910821149312/1103303693824096) &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;