SpringBoot+WebSocket實現(xiàn)服務端、客戶端
點擊上方藍色字體,選擇“標星公眾號”
優(yōu)質(zhì)文章,第一時間送達
? 作者?|??47號Gamer丶?
來源 |? urlify.cn/jUR3Af
一、引言
本人最近一直在使用springboot框架開發(fā)項目,畢竟現(xiàn)在很多公司都在采用此框架,之后本人也會陸續(xù)寫關于springboot開發(fā)常用功能的文章。?
什么場景下會要使用到websocket的呢?
websocket主要功能就是實現(xiàn)網(wǎng)絡通訊,比如說最經(jīng)典的客服聊天窗口、您有新的消息通知,或者是項目與項目之間的通訊,都可以采用websocket來實現(xiàn)。
二、websocket介紹
在公司實際使用websocket開發(fā),一般來都是這樣的架構,首先websocket服務端是一個單獨的項目,其他需要通訊的項目都是以客戶端來連接,由服務端控制消息的發(fā)送方式(群發(fā)、指定發(fā)送)。但是也會有服務端、客戶端在同一個項目當中,具體看項目怎么使用。
本文呢,采用的是服務端與客戶端分離來實現(xiàn),包括使用springboot搭建websokcet服務端、html5客戶端、springboot后臺客戶端,?具體看下面代碼。
三、服務端實現(xiàn)
步驟一:springboot底層幫我們自動配置了websokcet,引入maven依賴
?
????org.springframework.boot
????spring-boot-starter-websocket
步驟二:如果是你采用springboot內(nèi)置容器啟動項目的,則需要配置一個Bean。如果是采用外部的容器,則可以不需要配置。
/**
?*?@Description:?配置類
?*/
@Component
public?class?WebSocketConfig?{
??
????/**
?????*?ServerEndpointExporter?作用
?????*
?????*?這個Bean會自動注冊使用@ServerEndpoint注解聲明的websocket?endpoint
?????*
?????*?@return
?????*/
????@Bean
????public?ServerEndpointExporter?serverEndpointExporter()?{
????????return?new?ServerEndpointExporter();
????}
}
步驟三:最后一步當然是編寫服務端核心代碼了,其實本人不是特別想貼代碼出來,貼很多代碼影響文章可讀性。
package?com.example.socket.code;
??
import?lombok.extern.slf4j.Slf4j;
import?org.springframework.stereotype.Component;
??
import?javax.websocket.OnClose;
import?javax.websocket.OnMessage;
import?javax.websocket.OnOpen;
import?javax.websocket.Session;
import?javax.websocket.server.PathParam;
import?javax.websocket.server.ServerEndpoint;
import?java.util.concurrent.ConcurrentHashMap;
??
/**
?*?@Description:?websocket?服務類
?*/
??
/**
?*
?*?@ServerEndpoint 這個注解有什么作用?
?*
?*?這個注解用于標識作用在類上,它的主要功能是把當前類標識成一個WebSocket的服務端
?*?注解的值用戶客戶端連接訪問的URL地址
?*
?*/
??
@Slf4j
@Component
@ServerEndpoint("/websocket/{name}")
public?class?WebSocket?{
??
????/**
?????*??與某個客戶端的連接對話,需要通過它來給客戶端發(fā)送消息
?????*/
????private?Session?session;
??
?????/**
?????*?標識當前連接客戶端的用戶名
?????*/
????private?String?name;
??
????/**
?????*??用于存所有的連接服務的客戶端,這個對象存儲是安全的
?????*/
????private?static?ConcurrentHashMap?webSocketSet?=?new?ConcurrentHashMap<>();
??
??
????@OnOpen
????public?void?OnOpen(Session?session,?@PathParam(value?=?"name")?String?name){
????????this.session?=?session;
????????this.name?=?name;
????????//?name是用來表示唯一客戶端,如果需要指定發(fā)送,需要指定發(fā)送通過name來區(qū)分
????????webSocketSet.put(name,this);
????????log.info("[WebSocket]?連接成功,當前連接人數(shù)為:={}",webSocketSet.size());
????}
??
??
????@OnClose
????public?void?OnClose(){
????????webSocketSet.remove(this.name);
????????log.info("[WebSocket]?退出成功,當前連接人數(shù)為:={}",webSocketSet.size());
????}
??
????@OnMessage
????public?void?OnMessage(String?message){
????????log.info("[WebSocket]?收到消息:{}",message);
????????//判斷是否需要指定發(fā)送,具體規(guī)則自定義
????????if(message.indexOf("TOUSER")?==?0){
????????????String?name?=?message.substring(message.indexOf("TOUSER")+6,message.indexOf(";"));
????????????AppointSending(name,message.substring(message.indexOf(";")+1,message.length()));
????????}else{
????????????GroupSending(message);
????????}
??
????}
??
????/**
?????*?群發(fā)
?????*?@param?message
?????*/
????public?void?GroupSending(String?message){
????????for?(String?name?:?webSocketSet.keySet()){
????????????try?{
????????????????webSocketSet.get(name).session.getBasicRemote().sendText(message);
????????????}catch?(Exception?e){
????????????????e.printStackTrace();
????????????}
????????}
????}
??
????/**
?????*?指定發(fā)送
?????*?@param?name
?????*?@param?message
?????*/
????public?void?AppointSending(String?name,String?message){
????????try?{
????????????webSocketSet.get(name).session.getBasicRemote().sendText(message);
????????}catch?(Exception?e){
????????????e.printStackTrace();
????????}
????}
}
四、客戶端實現(xiàn)
HTML5實現(xiàn):以下就是核心代碼了,其實其他博客有很多,本人就不多說了。
var?websocket?=?null;
???if('WebSocket'?in?window){
???????websocket?=?new?WebSocket("ws://192.168.2.107:8085/websocket/testname");
???}
?
???websocket.onopen?=?function(){
???????console.log("連接成功");
???}
?
???websocket.onclose?=?function(){
???????console.log("退出連接");
???}
?
???websocket.onmessage?=?function?(event){
???????console.log("收到消息"+event.data);
???}
?
???websocket.onerror?=?function(){
???????console.log("連接出錯");
???}
?
???window.onbeforeunload?=?function?()?{
???????websocket.close(num);
???}
SpringBoot后臺實現(xiàn):本人發(fā)現(xiàn)多數(shù)博客都是采用js來實現(xiàn)客戶端,很少有用后臺來實現(xiàn),所以本人也就寫了寫,大神請勿噴?。很多時候,項目與項目之間通訊也需要后臺作為客戶端來連接。
步驟一:首先我們要導入后臺連接websocket的客戶端依賴
????org.java-websocket
????Java-WebSocket
????1.3.5
步驟二:把客戶端需要配置到springboot容器里面去,以便程序調(diào)用。
package?com.example.socket.config;
??
import?lombok.extern.slf4j.Slf4j;
import?org.java_websocket.client.WebSocketClient;
import?org.java_websocket.drafts.Draft_6455;
import?org.java_websocket.handshake.ServerHandshake;
import?org.springframework.context.annotation.Bean;
import?org.springframework.stereotype.Component;
??
import?java.net.URI;
??
/**
?*?@Description:?配置websocket后臺客戶端
?*/
@Slf4j
@Component
public?class?WebSocketConfig?{
??
????@Bean
????public?WebSocketClient?webSocketClient()?{
????????try?{
????????????WebSocketClient?webSocketClient?=?new?WebSocketClient(new?URI("ws://localhost:8085/websocket/test"),new?Draft_6455())?{
????????????????@Override
????????????????public?void?onOpen(ServerHandshake?handshakedata)?{
????????????????????log.info("[websocket]?連接成功");
????????????????}
??
????????????????@Override
????????????????public?void?onMessage(String?message)?{
????????????????????log.info("[websocket]?收到消息={}",message);
??
????????????????}
??
????????????????@Override
????????????????public?void?onClose(int?code,?String?reason,?boolean?remote)?{
????????????????????log.info("[websocket]?退出連接");
????????????????}
??
????????????????@Override
????????????????public?void?onError(Exception?ex)?{
????????????????????log.info("[websocket]?連接錯誤={}",ex.getMessage());
????????????????}
????????????};
????????????webSocketClient.connect();
????????????return?webSocketClient;
????????}?catch?(Exception?e)?{
????????????e.printStackTrace();
????????}
????????return?null;
????}
??
}
步驟三:使用后臺客戶端發(fā)送消息
1、首先本人寫了一個接口,里面有指定發(fā)送和群發(fā)消息兩個方法。
2、實現(xiàn)發(fā)送的接口,區(qū)分指定發(fā)送和群發(fā)由服務端來決定(本人在服務端寫了,如果帶有TOUSER標識的,則代表需要指定發(fā)送給某個websocket客戶端)。
3、最后采用get方式用瀏覽器請求,也能正常發(fā)送消息。
package?com.example.socket.code;
??
/**
?*?@Description:?websocket?接口
?*/
public?interface?WebSocketService?{
??
????/**
?????*?群發(fā)
?????*?@param?message
?????*/
?????void?groupSending(String?message);
??
????/**
?????*?指定發(fā)送
?????*?@param?name
?????*?@param?message
?????*/
?????void?appointSending(String?name,String?message);
}
package?com.example.socket.chat;
??
import?com.example.socket.code.ScoketClient;
import?org.springframework.beans.factory.annotation.Autowired;
import?org.springframework.web.bind.annotation.GetMapping;
import?org.springframework.web.bind.annotation.RequestMapping;
import?org.springframework.web.bind.annotation.RestController;
??
/**
?*?@Description:?測試后臺websocket客戶端
?*/
@RestController
@RequestMapping("/websocket")
public?class?IndexController?{
??
????@Autowired
????private?ScoketClient?webScoketClient;
??
????@GetMapping("/sendMessage")
????public?String?sendMessage(String?message){
????????webScoketClient.groupSending(message);
????????return?message;
????}
}
五、最后
注意:
如果是單例的情況下,這個對象的值都會被修改。
本人就抽了時間Debug了一下,經(jīng)過下圖也可以反映出,能夠看出,webSokcetSet中存在三個成員,并且vlaue值都是不同的,所以在這里沒有出現(xiàn)對象改變而把之前對象改變的現(xiàn)象。
服務端這樣寫是沒問題的。

最后總結(jié):在實際WebSocket服務端案例中為什么沒有出現(xiàn)這種情況,當WebSokcet這個類標識為服務端的時候,每當有新的連接請求,這個類都是不同的對象,并非單例。
import?com.alibaba.fastjson.JSON;
??
import?java.util.concurrent.ConcurrentHashMap;
??
/**
?*?@Description:
?*/
public?class?TestMain?{
??
????/**
?????*?用于存所有的連接服務的客戶端,這個對象存儲是安全的
?????*/
????private?static?ConcurrentHashMap?webSocketSet?=?new?ConcurrentHashMap<>();
??
????public?static?void?main(String[]?args)?{
????????Student?student?=?Student.getStudent();
????????student.name?=?"張三";
????????webSocketSet.put("1",?student);
??
????????Student?students?=?Student.getStudent();
????????students.name?=?"李四";
????????webSocketSet.put("2",?students);
??
????????System.out.println(JSON.toJSON(webSocketSet));
????}
}
??
/**
?*?提供一個單例類
?*/
class?Student?{
??
????public?String?name;
??
????private?Student()?{
????}
??
????private?static?final?Student?student?=?new?Student();
??
????public?static?Student?getStudent()?{
????????return?student;
??
????}
}
打印結(jié)果:
{"1":{"name":"李四"},"2":{"name":"李四"}}



??? ?
感謝點贊支持下哈?
