一、引言
小編最近一直在使用springboot框架開發(fā)項(xiàng)目,畢竟現(xiàn)在很多公司都在采用此框架,之后小編也會(huì)陸續(xù)寫關(guān)于springboot開發(fā)常用功能的文章。
什么場(chǎng)景下會(huì)要使用到websocket的呢?
websocket主要功能就是實(shí)現(xiàn)網(wǎng)絡(luò)通訊,比如說最經(jīng)典的客服聊天窗口、您有新的消息通知,或者是項(xiàng)目與項(xiàng)目之間的通訊,都可以采用websocket來實(shí)現(xiàn)。
二、websocket介紹
百度百科介紹:WebSokcet
在公司實(shí)際使用websocket開發(fā),一般來都是這樣的架構(gòu),首先websocket服務(wù)端是一個(gè)單獨(dú)的項(xiàng)目,其他需要通訊的項(xiàng)目都是以客戶端來連接,由服務(wù)端控制消息的發(fā)送方式(群發(fā)、指定發(fā)送)。但是也會(huì)有服務(wù)端、客戶端在同一個(gè)項(xiàng)目當(dāng)中,具體看項(xiàng)目怎么使用。
本文呢,采用的是服務(wù)端與客戶端分離來實(shí)現(xiàn),包括使用springboot搭建websokcet服務(wù)端、html5客戶端、springboot后臺(tái)客戶端, 具體看下面代碼。
三、服務(wù)端實(shí)現(xiàn)
*步驟一*:springboot底層幫我們自動(dòng)配置了websokcet,引入maven依賴
<dependency>
????<groupId>org.springframework.boot</groupId>
????<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
*步驟二*:如果是你采用springboot內(nèi)置容器啟動(dòng)項(xiàng)目的,則需要配置一個(gè)Bean。如果是采用外部的容器,則可以不需要配置。
/**
?*?@Auther:?liaoshiyao
?*?@Date:?2019/1/11?11:49
?*?@Description:?配置類
?*/
@Component
public?class?WebSocketConfig?{
????/**
?????*?ServerEndpointExporter?作用
?????*
?????*?這個(gè)Bean會(huì)自動(dòng)注冊(cè)使用@ServerEndpoint注解聲明的websocket?endpoint
?????*
?????*?@return
?????*/
????@Bean
????public?ServerEndpointExporter?serverEndpointExporter()?{
????????return?new?ServerEndpointExporter();
????}
}
*步驟三*:最后一步當(dāng)然是編寫服務(wù)端核心代碼了,其實(shí)小編不是特別想貼代碼出來,貼很多代碼影響文章可讀性。
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;
/**
?*?@Auther:?liaoshiyao
?*?@Date:?2019/1/11?11:48
?*?@Description:?websocket?服務(wù)類
?*/
/**
?*
?*?@ServerEndpoint?這個(gè)注解有什么作用?
?*
?*?這個(gè)注解用于標(biāo)識(shí)作用在類上,它的主要功能是把當(dāng)前類標(biāo)識(shí)成一個(gè)WebSocket的服務(wù)端
?*?注解的值用戶客戶端連接訪問的URL地址
?*
?*/
@Slf4j
@Component
@ServerEndpoint("/websocket/{name}")
public?class?WebSocket?{
????/**
?????*??與某個(gè)客戶端的連接對(duì)話,需要通過它來給客戶端發(fā)送消息
?????*/
????private?Session?session;
?????/**
?????*?標(biāo)識(shí)當(dāng)前連接客戶端的用戶名
?????*/
????private?String?name;
????/**
?????*??用于存所有的連接服務(wù)的客戶端,這個(gè)對(duì)象存儲(chǔ)是安全的
?????*/
????private?static?ConcurrentHashMap<String,WebSocket>?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]?連接成功,當(dāng)前連接人數(shù)為:={}",webSocketSet.size());
????}
????@OnClose
????public?void?OnClose(){
????????webSocketSet.remove(this.name);
????????log.info("[WebSocket]?退出成功,當(dāng)前連接人數(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();
????????}
????}
}
四、客戶端實(shí)現(xiàn)
*HTML5實(shí)現(xiàn)*:以下就是核心代碼了,其實(shí)其他博客有很多,小編就不多說了。
?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("連接出錯(cuò)");
????}
????window.onbeforeunload?=?function?()?{
????????websocket.close(num);
????}
*SpringBoot后臺(tái)實(shí)現(xiàn)*:小編發(fā)現(xiàn)多數(shù)博客都是采用js來實(shí)現(xiàn)客戶端,很少有用后臺(tái)來實(shí)現(xiàn),所以小編也就寫了寫,大神請(qǐng)勿噴?。很多時(shí)候,項(xiàng)目與項(xiàng)目之間通訊也需要后臺(tái)作為客戶端來連接。
*步驟一*:首先我們要導(dǎo)入后臺(tái)連接websocket的客戶端依賴
<!--websocket作為客戶端-->
<dependency>
????<groupId>org.java-websocket</groupId>
????<artifactId>Java-WebSocket</artifactId>
????<version>1.3.5</version>
</dependency>
*步驟二*:把客戶端需要配置到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;
/**
?*?@Auther:?liaoshiyao
?*?@Date:?2019/1/11?17:38
?*?@Description:?配置websocket后臺(tái)客戶端
?*/
@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]?連接錯(cuò)誤={}",ex.getMessage());
????????????????}
????????????};
????????????webSocketClient.connect();
????????????return?webSocketClient;
????????}?catch?(Exception?e)?{
????????????e.printStackTrace();
????????}
????????return?null;
????}
}
*步驟三*:使用后臺(tái)客戶端發(fā)送消息
1、首先小編寫了一個(gè)接口,里面有指定發(fā)送和群發(fā)消息兩個(gè)方法。
2、實(shí)現(xiàn)發(fā)送的接口,區(qū)分指定發(fā)送和群發(fā)由服務(wù)端來決定(小編在服務(wù)端寫了,如果帶有TOUSER標(biāo)識(shí)的,則代表需要指定發(fā)送給某個(gè)websocket客戶端)
3、最后采用get方式用瀏覽器請(qǐng)求,也能正常發(fā)送消息
package?com.example.socket.code;
/**
?*?@Auther:?liaoshiyao
?*?@Date:?2019/1/12?10:57
?*?@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.code;
import?org.java_websocket.client.WebSocketClient;
import?org.springframework.beans.factory.annotation.Autowired;
import?org.springframework.stereotype.Component;
/**
?*?@Auther:?liaoshiyao
?*?@Date:?2019/1/12?10:56
?*?@Description:?websocket接口實(shí)現(xiàn)類
?*/
@Component
public?class?ScoketClient?implements?WebSocketService{
????@Autowired
????private?WebSocketClient?webSocketClient;
????@Override
????public?void?groupSending(String?message)?{
????????//?這里我加了6666--?是因?yàn)槲以趇ndex.html頁面中,要拆分用戶編號(hào)和消息的標(biāo)識(shí),只是一個(gè)例子而已
????????//?在index.html會(huì)隨機(jī)生成用戶編號(hào),這里相當(dāng)于模擬頁面發(fā)送消息
????????//?實(shí)際這樣寫就行了?webSocketClient.send(message)
????????webSocketClient.send(message+"---6666");
????}
????@Override
????public?void?appointSending(String?name,?String?message)?{
????????//?這里指定發(fā)送的規(guī)則由服務(wù)端決定參數(shù)格式
????????webSocketClient.send("TOUSER"+name+";"+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;
/**
?*?@Auther:?liaoshiyao
?*?@Date:?2019/1/11?16:47
?*?@Description:?測(cè)試后臺(tái)websocket客戶端
?*/
@RestController
@RequestMapping("/websocket")
public?class?IndexController?{
????@Autowired
????private?ScoketClient?webScoketClient;
????@GetMapping("/sendMessage")
????public?String?sendMessage(String?message){
????????webScoketClient.groupSending(message);
????????return?message;
????}
}
五、最后
其實(shí),貼了這么多大一片的代碼,感覺上影響了博客的美觀,也不便于瀏覽,如果沒看懂小伙伴,可以下載源碼看下。
里面一共兩個(gè)項(xiàng)目,服務(wù)端、客戶端(html5客戶端、后臺(tái)客戶端),是一個(gè)網(wǎng)頁群聊的小案例。
https://download.csdn.net/download/weixin_38111957/10912384
*祝大家學(xué)習(xí)愉快~~~*
六、針對(duì)評(píng)論區(qū)的小伙伴提出的疑點(diǎn)進(jìn)行解答
看了小伙伴提出的疑問,小編也是非常認(rèn)可的,如果是單例的情況下,這個(gè)對(duì)象的值都會(huì)被修改。
小編就抽了時(shí)間Debug了一下,經(jīng)過下圖也可以反映出,能夠看出,webSokcetSet中存在三個(gè)成員,并且vlaue值都是不同的,所以在這里沒有出現(xiàn)對(duì)象改變而把之前對(duì)象改變的現(xiàn)象。
服務(wù)端這樣寫是沒問題的。
文章來源地址http://www.zghlxwxcb.cn/news/detail-639728.html
緊接著,小編寫了一個(gè)測(cè)試類,代碼如下,經(jīng)過測(cè)試輸出的結(jié)果和小伙伴提出的疑點(diǎn)是一致的。
最后總結(jié):這位小伙伴提出的觀點(diǎn)確實(shí)是正確的,但是在實(shí)際WebSocket服務(wù)端案例中為什么沒有出現(xiàn)這種情況,當(dāng)WebSokcet這個(gè)類標(biāo)識(shí)為服務(wù)端的時(shí)候,每當(dāng)有新的連接請(qǐng)求,這個(gè)類都是不同的對(duì)象,并非單例。
這里也感謝“煙花蘇柳”所提出的問題。
import?com.alibaba.fastjson.JSON;
import?java.util.concurrent.ConcurrentHashMap;
/**
?*?@Auther:?IT賤男
?*?@Date:?2018/11/1?16:15
?*?@Description:
?*/
public?class?TestMain?{
????/**
?????*?用于存所有的連接服務(wù)的客戶端,這個(gè)對(duì)象存儲(chǔ)是安全的
?????*/
????private?static?ConcurrentHashMap<String,?Student>?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));
????}
}
/**
?*?提供一個(gè)單例類
?*/
class?Student?{
????public?String?name;
????private?Student()?{
????}
????private?static?final?Student?student?=?new?Student();
????public?static?Student?getStudent()?{
????????return?student;
????}
}
{"1":{"name":"李四"},"2":{"name":"李四"}}
文章來源:http://www.zghlxwxcb.cn/news/detail-639728.html
到了這里,關(guān)于SpringBoot+WebSocket實(shí)現(xiàn)服務(wù)端、客戶端的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!