項目地址:github.com/longxiaonan…javascript
兩個包: com.javasea.web.websocket.springb.websocket 使用實現WebSocketConfigurer
接口的方式實現php
com.javasea.web.websocket.springb.websocket2 經過註解@ServerEndpoint
方式實現html
場景:頁面須要實時顯示被分配的任務,頁面須要實時顯示在線人數。前端
思考:像這樣的消息功能怎麼實現? 若是網頁不刷新,服務端有新消息如何推送到瀏覽器?
解決方案,採用輪詢的方式。即:經過js不斷的請求服務器,查看是否有新數據,若是有,就獲取到新數據。 這種解決方法是否存在問題呢?
固然是有的,若是服務端一直沒有新的數據,那麼js也是須要一直的輪詢查詢數據,這就是一種資源的浪費。 那麼,有沒有更好的解決方案? 有!那就是採用WebSocket技術來解決。java
WebSocket 是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通訊(full-duplex)。一開始的握手須要藉助HTTP請求完成。 WebSocket是真正實現了全雙工通訊的服務器向客戶端推的互聯網技術。 它是一種在單個TCP鏈接上進行全雙工通信協議。Websocket通訊協議與2011年倍IETF定爲標準RFC 6455,Websocket API被W3C定爲標準。jquery
什麼叫作全雙工和半雙工?git
好比對講機,說話的時候就聽不到對方說話,那麼就是半雙工。github
咱們打電話的時候說話的同時也能聽到對方說話,就是全雙工。web
http協議是短鏈接,由於請求以後,都會關閉鏈接,下次從新請求數據,須要再次打開連接。spring
WebSocket協議是一種長連接,只須要經過一次請求來初始化連接,而後全部的請求和響應都是經過這個TCP連接進行通信。
服務器支持狀況:Tomcat 7.0.47+以上才支持。
集成javaee
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
複製代碼
配置tomcat插件
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8082</port>
<path>/</path>
</configuration>
</plugin>
複製代碼
以後啓動服務
只須要在maven中直接運行便可。
pom的詳細配置以下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.iee</groupId>
<artifactId>javasea-web-websoecket-quickstart</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<!-- https://mvnrepository.com/artifact/javax/javaee-api -->
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
<!--<scope>provided</scope>-->
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!--maven編譯插件, 指定jdk爲1.8 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<!-- 使用jdk進行編譯 -->
<fork>true</fork>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- 配置Tomcat插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8082</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
複製代碼
@ServerEndpoint("/websocket/{uid}")
申明這是一個websocket服務 須要指定訪問該服務的地址,在地址中能夠指定參數,須要經過{}進行佔位
@OnOpen
用法:public void onOpen(Session session, @PathParam("uid") String uid) throws IOException{} 該方法將在創建鏈接後執行,會傳入session對象,就是客戶端與服務端創建的長鏈接通道 經過@PathParam獲取url申明中的參數
@OnClose
用法:public void onClose() {} 該方法是在鏈接關閉後執行
@OnMessage
用法:public void onMessage(String message, Session session) throws IOException {}
客戶端消息到來時調用,包含會話Session,根據消息的形式,若是是文本消息,傳入String類型參數或者Reader,若是是二進制消息,傳入byte[]類型參數或者InputStream。
message:發來的消息數據 session:會話對象(也是通道) 發送消息到客戶端 用法:session.getBasicRemote().sendText("你好"); 經過session進行發送。
@ServerEndpoint("/websocket/{uid}")
public class MyWebSocket {
@OnOpen
public void onOpen(Session session, @PathParam("uid") String uid) throws IOException {
// 鏈接成功
session.getBasicRemote().sendText(uid + ",你好,歡迎鏈接WebSocket!");
}
@OnClose
public void onClose() {
System.out.println(this + "關閉鏈接");
}
@OnMessage
public void onMessage(String message, Session session) throws IOException {
System.out.println("接收到消息:" + message);
session.getBasicRemote().sendText("消息已收到.");
}
@OnError
public void onError(Session session, Throwable error) {
System.out.println("發生錯誤");
error.printStackTrace();
}
}
複製代碼
maven中啓動tomcat:
mv tomcat7:run
複製代碼
也能夠用上文中在IDE中直接啓動。
一共有三種測試方式,直接js腳本方式、chrome插件方式或者經過在線工具進行測試:
直接js腳本方式,直接用以下代碼進行測試:
var socket;
if(typeof(WebSocket) == "undefined") {
console.log("您的瀏覽器不支持WebSocket");
}else{
console.log("您的瀏覽器支持WebSocket");
//實現化WebSocket對象,指定要鏈接的服務器地址與端口 創建鏈接
socket = new WebSocket("ws://localhost:8080/websocket2/22");
//打開事件
socket.onopen = function() {
console.log("Socket 已打開");
//socket.send("這是來自客戶端的消息" + location.href + new Date());
};
//得到消息事件
socket.onmessage = function(msg) {
console.log(msg.data);
//發現消息進入 開始處理前端觸發邏輯
};
//關閉事件
socket.onclose = function() {
console.log("Socket已關閉");
};
//發生了錯誤事件
socket.onerror = function() {
alert("Socket發生了錯誤");
//此時能夠嘗試刷新頁面
}
//離開頁面時,關閉socket
//jquery1.8中已經被廢棄,3.0中已經移除
// $(window).unload(function(){
// socket.close();
//});
}
複製代碼
瀏覽器隨便打開一個網頁,而後粘貼到console下,回車便可
chrome插件方式,須要安裝chrome插件,Simple WebSocket Client: chrome.google.com/webstore/de…
在線工具進行測試(推薦):www.websocket-test.com/
我一直測試失敗,還沒找到緣由。下文整合springboot的測試成功。
在webapp下編寫兩個html文件
websocket.html
內容以下<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script> const socket = new WebSocket("ws://localhost:8082/websocket/1"); // 鏈接創建時觸發 socket.onopen = (ws) => { console.log("創建鏈接!", ws); }; // 客戶端接收服務端數據時觸發 socket.onmessage = (ws) => { console.log("接收到消息 >> ", ws.data); }; // 鏈接關閉時觸發 socket.onclose = (ws) => { console.log("鏈接已斷開!", ws); }; // 通訊發生錯誤時觸發 socket.onerror = (ws) => { console.log("發送錯誤!", ws); }; // 2秒後向服務端發送消息 setTimeout(() => { // 使用鏈接發送數據 socket.send("發送一條消息試試"); }, 2000); // 5秒後斷開鏈接 setTimeout(() => { // 關閉鏈接 socket.close(); }, 5000); </script>
</body>
</html>
複製代碼
websocket2.html
內容以下<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>菜鳥教程(runoob.com)</title>
<script type="text/javascript"> function WebSocketTest() { if ("WebSocket" in window) { alert("您的瀏覽器支持 WebSocket!"); // 打開一個 web socket var ws = new WebSocket("ws://localhost:8082/websocket/1"); ws.onopen = function() { // Web Socket 已鏈接上,使用 send() 方法發送數據 ws.send("發送數據"); alert("數據發送中..."); }; ws.onmessage = function (evt) { var received_msg = evt.data; alert("數據已接收..."); }; ws.onclose = function() { // 關閉 websocket alert("鏈接已關閉..."); }; } else { // 瀏覽器不支持 WebSocket alert("您的瀏覽器不支持 WebSocket!"); } } </script>
</head>
<body>
<div id="sse">
<a href="javascript:WebSocketTest()">運行 WebSocket</a>
</div>
</body>
</html>
複製代碼
在瀏覽器請求http://localhost:8082/websocket2.html
emmm,失敗的,還沒找到緣由,下文整合springboot,測試是成功的。
使用springboot內置tomcat時,就不須要引入javaee-api了,spring-boot已經包含了。
springboot的高級組件會自動引用基礎的組件,像spring-boot-starter-websocket就引入了spring-boot-starter-web和spring-boot-starter,因此不要重複引入
springboot已經作了深度的集成和優化,注意是否添加了不須要的依賴、配置或聲明。因爲不少講解組件使用的文章是和spring集成的,會有一些配置。在使用springboot時,因爲springboot已經有了本身的配置,再這些配置有可能致使各類各樣的異常。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>javasea-web-websocket</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.1.5.RELEASE</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>javasea-web-websocket-springb</artifactId>
<dependencies>
<!-- https://mvnrepository.com/artifact/javax/javaee-api -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins> <!-- java編譯插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<!-- 使用jdk進行編譯 -->
<fork>true</fork>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
複製代碼
springboot有兩種方式實現websocket
@ServerEndpoint
方式實現webSocket核心是@ServerEndpoint
這個註解。這個註解是Javaee標準裏的註解,tomcat7以上已經對其進行了實現,若是是用傳統方法使用tomcat發佈的項目,只要在pom文件中引入javaee標準便可使用。
快速入門中的例子就是經過@ServerEndpoint來實現的WebSocket服務,在整合springboot的時候須要額外配置Config類,建立一個ServerEndpointExporter();
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
複製代碼
使用實現WebSocketConfigurer
接口的方式實現
下文就是這種方式的實現
在Spring中,處理消息的具體業務邏輯須要實現WebSocketHandler接口。
package com.javasea.web.websocket.springb.websocket;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
/** * @Description 在Spring中,處理消息的具體業務邏輯須要實現WebSocketHandler接口。 * @Author longxiaonan@163.com * @Date 16:50 2019/10/27 0027 **/
public class MyHandler extends TextWebSocketHandler {
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
System.out.println("獲取到消息 >> " + message.getPayload());
session.sendMessage(new TextMessage("消息已收到"));
if (message.getPayload().equals("10")) {
for (int i = 0; i < 10; i++) {
//回寫消息到client
session.sendMessage(new TextMessage("消息 -> " + i));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
session.sendMessage(new TextMessage("歡迎鏈接到ws服務"));
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
System.out.println("斷開鏈接!");
}
}
複製代碼
package com.javasea.web.websocket.springb.websocket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/ws").setAllowedOrigins("*");
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
複製代碼
package com.javasea.web.websocket.springb;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class APPlication {
public static void main(String[] args) {
SpringApplication.run(APPlication.class, args);
}
}
複製代碼
在線進行測試,url:ws://localhost:8080/ws
用上文的html頁面也能夠測試的,修改地址爲
ws://localhost:8080/ws
而後在文件夾下直接用瀏覽器打開便可。
package com.javasea.web.websocket.springb.websocket;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.Map;
@Component
public class MyHandshakeInterceptor implements HandshakeInterceptor {
/*** 握手以前,若返回false,則不創建連接 */
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { //將用戶id放入socket處理器的會話(WebSocketSession)中
attributes.put("uid", 1001);
System.out.println("開始握手。。。。。。。");
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
System.out.println("握手成功啦。。。。。。");
}
}
複製代碼
將攔截器添加到websocket服務中:
就是在上文的config中添加
addInterceptors(this.myHandshakeInterceptor);
。
package com.javasea.web.websocket.springb.websocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private MyHandshakeInterceptor myHandshakeInterceptor;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/ws").setAllowedOrigins("*").addInterceptors(this.myHandshakeInterceptor);
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
複製代碼
在MyHandler
類afterConnectionEstablished
方法下輸出獲取到的uid
。
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("uid =>" + session.getAttributes().get("uid"));
session.sendMessage(new TextMessage("歡迎鏈接到ws服務"));
}
複製代碼
鏈接websocket服務 ws://localhost:8080/ws
,console輸出:
握手成功啦。。。。。。
uid =>1001
複製代碼
說明測試成功。
github地址:github.com/longxiaonan…