您好,登錄后才能下訂單哦!
SpringBoot跟WebSocket的開發(fā)過程是怎樣的,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。
1. 服務(wù)端的實(shí)現(xiàn),我嘗試了兩種方式:
第一種是用“@ServerEndPoint”注解來實(shí)現(xiàn),實(shí)現(xiàn)簡單;
第二種稍顯麻煩,但是可以添加攔截器在WebSocket連接建立和斷開前進(jìn)行一些額外操作。
不管用哪種實(shí)現(xiàn)方式,都需要先導(dǎo)入jar包(如下),其中version根據(jù)實(shí)際springboot版本選擇,避免沖突
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> <!-- <version>1.3.5.RELEASE</version> --> </dependency>
1.1 第一種實(shí)現(xiàn)方法
(1)WebSocket 業(yè)務(wù)邏輯實(shí)現(xiàn)。參數(shù)傳遞采用路徑參數(shù)的方法,通過以下方式獲取參數(shù):
@ServerEndpoint("/testWebSocket/{id}/{name}")
public void onOpen(Session session, @PathParam("id") long id, @PathParam("name") String name)
import java.util.concurrent.CopyOnWriteArraySet; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.RestController; @ServerEndpoint("/testWebSocket/{id}/{name}") @RestController public class TestWebSocket { // 用來記錄當(dāng)前連接數(shù)的變量 private static volatile int onlineCount = 0; // concurrent包的線程安全Set,用來存放每個客戶端對應(yīng)的MyWebSocket對象 private static CopyOnWriteArraySet<TestWebSocket> webSocketSet = new CopyOnWriteArraySet<TestWebSocket>(); // 與某個客戶端的連接會話,需要通過它來與客戶端進(jìn)行數(shù)據(jù)收發(fā) private Session session; private static final Logger LOGGER = LoggerFactory.getLogger(TestWebSocket.class); @OnOpen public void onOpen(Session session, @PathParam("id") long id, @PathParam("name") String name) throws Exception { this.session = session; System.out.println(this.session.getId()); webSocketSet.add(this); LOGGER.info("Open a websocket. id={}, name={}", id, name); } @OnClose public void onClose() { webSocketSet.remove(this); LOGGER.info("Close a websocket. "); } @OnMessage public void onMessage(String message, Session session) { LOGGER.info("Receive a message from client: " + message); } @OnError public void onError(Session session, Throwable error) { LOGGER.error("Error while websocket. ", error); } public void sendMessage(String message) throws Exception { if (this.session.isOpen()) { this.session.getBasicRemote().sendText("Send a message from server. "); } } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { TestWebSocket.onlineCount++; } public static synchronized void subOnlineCount() { TestWebSocket.onlineCount--; } }
(2)配置ServerEndpointExporter,配置后會自動注冊所有“@ServerEndpoint”注解聲明的Websocket Endpoint
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
1.2 第二種實(shí)現(xiàn)方法
(1)WebSocket 業(yè)務(wù)邏輯實(shí)現(xiàn)。參數(shù)傳遞采用類似GET請求的方式傳遞,服務(wù)端的參數(shù)在攔截器中獲取之后通過attributes傳遞給WebSocketHandler。
import java.util.ArrayList; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketMessage; import org.springframework.web.socket.WebSocketSession; @RestController public class TestWebSocketController implements WebSocketHandler { private static AtomicInteger onlineCount = new AtomicInteger(0); private static final ArrayList<WebSocketSession> sessions = new ArrayList<>(); private final Logger LOGGER = LoggerFactory.getLogger(TestWebSocketController.class); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { sessions.add(session); int onlineNum = addOnlineCount(); LOGGER.info("Oprn a WebSocket. Current connection number: " + onlineNum); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { sessions.remove(session); int onlineNum = subOnlineCount(); LOGGER.info("Close a webSocket. Current connection number: " + onlineNum); } @Override public void handleMessage(WebSocketSession wsSession, WebSocketMessage<?> message) throws Exception { LOGGER.info("Receive a message from client: " + message.toString()); } @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { LOGGER.error("Exception occurs on webSocket connection. disconnecting...."); if (session.isOpen()) { session.close(); } sessions.remove(session); subOnlineCount(); } /* * 是否支持消息拆分發(fā)送:如果接收的數(shù)據(jù)量比較大,最好打開(true), 否則可能會導(dǎo)致接收失敗。 * 如果出現(xiàn)WebSocket連接接收一次數(shù)據(jù)后就自動斷開,應(yīng)檢查是否是這里的問題。 */ @Override public boolean supportsPartialMessages() { return true; } public static int getOnlineCount() { return onlineCount.get(); } public static int addOnlineCount() { return onlineCount.incrementAndGet(); } public static int subOnlineCount() { return onlineCount.decrementAndGet(); } }
(2)HandShake 攔截器實(shí)現(xiàn)
import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor; public class TestHandShakeInterceptor extends HttpSessionHandshakeInterceptor { private final Logger LOGGER = LoggerFactory.getLogger(TestHandShakeInterceptor.class); /* * 在WebSocket連接建立之前的操作,以鑒權(quán)為例 */ @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { LOGGER.info("Handle before webSocket connected. "); // 獲取url傳遞的參數(shù),通過attributes在Interceptor處理結(jié)束后傳遞給WebSocketHandler // WebSocketHandler可以通過WebSocketSession的getAttributes()方法獲取參數(shù) ServletServerHttpRequest serverRequest = (ServletServerHttpRequest) request; String id = serverRequest.getServletRequest().getParameter("id"); String name = serverRequest.getServletRequest().getParameter("name"); if (tokenValidation.validateSign()) { LOGGER.info("Validation passed. WebSocket connecting.... "); attributes.put("id", id); attributes.put("name", name); return super.beforeHandshake(request, response, wsHandler, attributes); } else { LOGGER.error("Validation failed. WebSocket will not connect. "); return false; } } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) { // 省略 } }
(3)WebSocket 配置類實(shí)現(xiàn)(注冊WebSocket實(shí)現(xiàn)類,綁定接口,同時將實(shí)現(xiàn)類和攔截器綁定)
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; import TestWebSocketController; import TestHandShakeInterceptor; @Configuration @EnableWebMvc @EnableWebSocket public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer { @Autowired private TestWebSocketController testWebSocketController; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(TestWebSocketController, "/testWebSocket") .addInterceptors(new TestHandShakeInterceptor()).setAllowedOrigins("*"); } }
1.3 補(bǔ)充說明
(1)在WebSocket實(shí)現(xiàn)過程中,尤其是通過“@ServerEndpoint”實(shí)現(xiàn)的時候,可能會出現(xiàn)注入失敗的問題,即注入的Bean為null的問題??梢酝ㄟ^手動注入的方式來解決,需要改造實(shí)現(xiàn)類和SpringBoot啟動類,如下:
@ServerEndpoint("testWebsocket") @RestController public class WebSocketController { private TestService testService; private static ApplicationContext applicationContext; @OnOpen public void onOpen(Session session) { testService = applicationContext.getBean(TestService.class); } @OnClose public void onClose() {} @OnMessage public void onMessage(String message, Session session) {} @OnError public void onError(Session session, Throwable error) {} public static void setApplicationContext(ApplicationContext applicationContext) { WebSocketController.applicationContext = applicationContext; } }
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import WebSocketController; @SpringBootApplication public class Application { public static void main(String[] args) { // SpringApplication.run(Application.class, args); SpringApplication springApplication = new SpringApplication(Application.class); ConfigurableApplicationContext configurableApplicationContext = springApplication.run(args); WebSocketController.setApplicationContext(configurableApplicationContext); // 解決WebSocket不能注入的問題 } }
2. 客戶端的實(shí)現(xiàn),我嘗試了html和java WebSocketClient兩種方式
2.1 html實(shí)現(xiàn)
<!DOCTYPE html> <html> <head> <title>WebSocket示例</title> <meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport' /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> </head> <body> <input id="text" type="text"/> <button onclick="send()">發(fā)送消息</button> <hr/> <button onclick="closeWebSocket()">關(guān)閉WebSocket連接</button> <hr/> <div id="message"></div> </body> <script type="text/javascript"> var websocket = null; //判斷當(dāng)前瀏覽器是否支持WebSocket if ('WebSocket' in window) { // 不帶參數(shù)的寫法 websocket = new WebSocket("ws://127.0.0.1:18080/testWebsocket"); // 通過路徑傳遞參數(shù)的方法(服務(wù)端采用第一種方法"@ServerEndpoint"實(shí)現(xiàn)) websocket = new WebSocket("ws://127.0.0.1:18080/testWebsocket/23/Lebron"); // 通過類似GET請求方式傳遞參數(shù)的方法(服務(wù)端采用第二種方法"WebSocketHandler"實(shí)現(xiàn)) websocket = new WebSocket("ws://127.0.0.1:18080/testWebsocket?id=23&name=Lebron"); } else { alert('當(dāng)前瀏覽器 Not support websocket') } //連接發(fā)生錯誤的回調(diào)方法 websocket.onerror = function () { setMessageInnerHTML("WebSocket連接發(fā)生錯誤"); }; //連接成功建立的回調(diào)方法 websocket.onopen = function () { setMessageInnerHTML("WebSocket連接成功"); } //接收到消息的回調(diào)方法 websocket.onmessage = function (event) { setMessageInnerHTML(event.data); } //連接關(guān)閉的回調(diào)方法 websocket.onclose = function () { setMessageInnerHTML("WebSocket連接關(guān)閉"); } //監(jiān)聽窗口關(guān)閉事件,當(dāng)窗口關(guān)閉時,主動去關(guān)閉websocket連接,防止連接還沒斷開就關(guān)閉窗口,server端會拋異常。 window.onbeforeunload = function () { closeWebSocket(); } //將消息顯示在網(wǎng)頁上 function setMessageInnerHTML(innerHTML) { document.getElementById('message').innerHTML += innerHTML + '<br/>'; } //關(guān)閉WebSocket連接 function closeWebSocket() { websocket.close(); } //發(fā)送消息 function send() { var message = document.getElementById('text').value; websocket.send(message); } </script> </html>
2.2 Java WebSocketClient實(shí)現(xiàn)
(1)WebSocketClient 實(shí)現(xiàn)類
import java.net.URI; import org.java_websocket.client.WebSocketClient; import org.java_websocket.drafts.Draft; import org.java_websocket.handshake.ServerHandshake; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TestWebSocketClient extends WebSocketClient { private final Logger LOGGER = LoggerFactory.getLogger(TestWebSocketClient.class); public TestWebSocketClient(URI serverUri) { super(serverUri); } public TestWebSocketClient(URI serverUri, Draft protocolDraft) { super(serverUri, protocolDraft); } @Override public void onOpen(ServerHandshake serverHandshake) { LOGGER.info("Open a WebSocket connection on client. "); } @Override public void onClose(int arg0, String arg1, boolean arg2) { LOGGER.info("Close a WebSocket connection on client. "); } @Override public void onMessage(String msg) { LOGGER.info("WebSocketClient receives a message: " + msg); } @Override public void onError(Exception exception) { LOGGER.error("WebSocketClient exception. ", exception); } }
(2)WebSocketClient 發(fā)送數(shù)據(jù)
String serverUrl = "ws://127.0.0.1:18080/testWebsocket" URI recognizeUri = new URI(serverUrl); client = new TestWebSocketClient(recognizeUri, new Draft_6455()); client.connect(); client.send("This is a message from client. ");
看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進(jìn)一步的了解或閱讀更多相關(guān)文章,請關(guān)注億速云行業(yè)資訊頻道,感謝您對億速云的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。