之前實現(xiàn)websocket基于stomp的,覺得springboot封裝的太高,不怎么靈活,現(xiàn)在實現(xiàn)一個純h5的,也大概了解websocket在內(nèi)部是怎么傳輸?shù)摹?/p>
1.環(huán)境搭建
因為在上一篇基于stomp協(xié)議實現(xiàn)的websocket里已經(jīng)有大概介紹過web的基本情況了,所以在這篇就不多說了,我們直接進(jìn)入正題吧,在springboot中,我們還是需要導(dǎo)入websocket的包。
在pox.xml加上對springboot對websocket的支持:
1
2
3
4
5
|
<!-- websocket --> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-websocket</artifactid> </dependency> |
這里大概說一下自己的一點小見解:客戶端與服務(wù)器建立websocket連接,實際上是創(chuàng)建了一個socket,這個socket是共享與客戶端和服務(wù)器的。兩者只要往對應(yīng)的socket里操作,就可以實現(xiàn)雙方實時通訊了
2.編碼實現(xiàn)
一、在springboot中,添加websocket的配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
package com.cloud.sbjm.configure; import org.springframework.context.annotation.configuration; 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 com.cloud.sbjm.security.websocketinterceptor; import com.cloud.sbjm.service.imp.myhandler; //實現(xiàn)接口來配置websocket請求的路徑和攔截器。 @configuration @enablewebsocket public class websocketh5config implements websocketconfigurer{ @override public void registerwebsockethandlers(websockethandlerregistry registry) { //handler是websocket的核心,配置入口 registry.addhandler( new myhandler(), "/myhandler/{id}" ).setallowedorigins( "*" ).addinterceptors( new websocketinterceptor()); } } |
1.@configuration:注解標(biāo)識該類為spring的配置類
2.@enablewebsocket:開啟注解接收和發(fā)送消息
3.實現(xiàn)websocketconfigurer接口,重寫registerwebsockethandlers方法,這是一個核心實現(xiàn)方法,配置websocket入口,允許訪問的域、注冊handler、定義攔截器。客戶端通過“/myhandler/{id}”直接訪問handler核心類,進(jìn)行socket的連接、接收、發(fā)送等操作,這里由于還加了個攔截器,所以建立新的socket訪問時,都先進(jìn)來攔截器再進(jìn)去handler類,“new websocketinterceptor()”是我實現(xiàn)的攔截器,“new myhandler()”是我實現(xiàn)的一個handler類。
二、websocketinterceptor攔截器的實現(xiàn):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
package com.cloud.sbjm.security; import java.util.map; import javax.servlet.http.httpsession; 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.handshakeinterceptor; public class websocketinterceptor implements handshakeinterceptor { //進(jìn)入hander之前的攔截 @override public boolean beforehandshake(serverhttprequest request, serverhttpresponse serverhttpresponse, websockethandler websockethandler, map<string, object> map) throws exception { if (request instanceof servletserverhttprequest) { string id = request.geturi().tostring().split( "id=" )[ 1 ]; system.out.println( "當(dāng)前session的id=" +id); servletserverhttprequest serverhttprequest = (servletserverhttprequest) request; httpsession session = serverhttprequest.getservletrequest().getsession(); map.put( "websocket_userid" ,id); } return true ; } @override public void afterhandshake(serverhttprequest serverhttprequest, serverhttpresponse serverhttpresponse, websockethandler websockethandler, exception e) { system.out.println( "進(jìn)來websocket的afterhandshake攔截器!" ); } } |
1.實現(xiàn)了handshakeinterceptor 接口,并實現(xiàn)了beforehandshake該方法,該方法是在進(jìn)入handler核心類之前進(jìn)行攔截。
這里主要實現(xiàn)的邏輯是:
截取客戶端建立websocket連接時發(fā)送的url地址字符串,并通過對該字符串進(jìn)行特殊標(biāo)識截取操作,獲取客戶端發(fā)送的唯一標(biāo)識(由自己定義的,一般是系統(tǒng)用戶id唯一標(biāo)識,用以標(biāo)識該用戶),并把它以鍵值對的形式放到session里,這樣后期可以通過該session獲取它對應(yīng)的用戶id了。【一個session對應(yīng)著一個websocketsession】
三、myhandler核心類的實現(xiàn)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
package com.cloud.sbjm.service.imp; import java.io.ioexception; import java.util.hashmap; import java.util.map; import java.util.set; import net.sf.json.jsonobject; import org.springframework.stereotype.service; import org.springframework.web.socket.closestatus; import org.springframework.web.socket.textmessage; import org.springframework.web.socket.websockethandler; import org.springframework.web.socket.websocketmessage; import org.springframework.web.socket.websocketsession; @service public class myhandler implements websockethandler { //在線用戶列表 private static final map<string, websocketsession> users; static { users = new hashmap<>(); } //新增socket @override public void afterconnectionestablished(websocketsession session) throws exception { system.out.println( "成功建立連接" ); string id = session.geturi().tostring().split( "id=" )[ 1 ]; system.out.println(id); if (id != null ) { users.put(id, session); session.sendmessage( new textmessage( "成功建立socket連接" )); system.out.println(id); system.out.println(session); } system.out.println( "當(dāng)前在線人數(shù):" +users.size()); } //接收socket信息 @override public void handlemessage(websocketsession websocketsession, websocketmessage<?> websocketmessage) throws exception { try { jsonobject jsonobject = jsonobject.fromobject(websocketmessage.getpayload()); system.out.println(jsonobject.get( "id" )); system.out.println(jsonobject.get( "message" )+ ":來自" +(string)websocketsession.getattributes().get( "websocket_userid" )+ "的消息" ); sendmessagetouser(jsonobject.get( "id" )+ "" , new textmessage( "服務(wù)器收到了,hello!" )); } catch (exception e){ e.printstacktrace(); } } /** * 發(fā)送信息給指定用戶 * @param clientid * @param message * @return */ public boolean sendmessagetouser(string clientid, textmessage message) { if (users.get(clientid) == null ) return false ; websocketsession session = users.get(clientid); system.out.println( "sendmessage:" + session); if (!session.isopen()) return false ; try { session.sendmessage(message); } catch (ioexception e) { e.printstacktrace(); return false ; } return true ; } /** * 廣播信息 * @param message * @return */ public boolean sendmessagetoallusers(textmessage message) { boolean allsendsuccess = true ; set<string> clientids = users.keyset(); websocketsession session = null ; for (string clientid : clientids) { try { session = users.get(clientid); if (session.isopen()) { session.sendmessage(message); } } catch (ioexception e) { e.printstacktrace(); allsendsuccess = false ; } } return allsendsuccess; } @override public void handletransporterror(websocketsession session, throwable exception) throws exception { if (session.isopen()) { session.close(); } system.out.println( "連接出錯" ); users.remove(getclientid(session)); } @override public void afterconnectionclosed(websocketsession session, closestatus status) throws exception { system.out.println( "連接已關(guān)閉:" + status); users.remove(getclientid(session)); } @override public boolean supportspartialmessages() { return false ; } /** * 獲取用戶標(biāo)識 * @param session * @return */ private integer getclientid(websocketsession session) { try { integer clientid = (integer) session.getattributes().get( "websocket_userid" ); return clientid; } catch (exception e) { return null ; } } } |
1.實現(xiàn)了websockethandler接口,并實現(xiàn)了關(guān)鍵的幾個方法。
① afterconnectionestablished(接口提供的):建立新的socket連接后回調(diào)的方法。主要邏輯是:將成功建立連接的websocketsssion放到定義好的常量[private static final map<string, websocketsession> users;]中去。這里也截取客戶端訪問的url的字符串,拿到標(biāo)識,以鍵值對的形式講每一個websocketsession存到users里,以記錄每個socket。
② handlemessage(接口提供的):接收客戶端發(fā)送的socket。主要邏輯是:獲取客戶端發(fā)送的信息。這里之所以可以獲取本次socket的id,是因為客戶端在第一次進(jìn)行連接時,攔截器進(jìn)行攔截后,設(shè)置好id,這樣也說明,雙方在相互通訊的時候,只是對第一次建立好的socket持續(xù)進(jìn)行操作。
③ sendmessagetouser(自己定義的):發(fā)送給指定用戶信息。主要邏輯是:根據(jù)用戶id從常量users(記錄每一個socket)中,獲取socket,往該socket里發(fā)送消息,只要客戶端還在線,就能收到該消息。
④sendmessagetoallusers (自己定義的):這個廣播消息,發(fā)送信息給所有socket。主要邏輯是:跟③類型,只不過是遍歷整個users獲取每一個socket,給每一個socket發(fā)送消息即可完廣播發(fā)送
⑤handletransporterror(接口提供的):連接出錯時,回調(diào)的方法。主要邏輯是:一旦有連接出錯的socket,就從users里進(jìn)行移除,有提供該socket的參數(shù),可直接獲取id,進(jìn)行移除。這個在客戶端沒有正常關(guān)閉連接時,會進(jìn)來,所以在開發(fā)客戶端時,記得關(guān)閉連接
⑥afterconnectionclosed(接口提供的):連接關(guān)閉時,回調(diào)的方法。主要邏輯:一旦客戶端/服務(wù)器主動關(guān)閉連接時,將個socket從users里移除,有提供該socket的參數(shù),可直接獲取id,進(jìn)行移除。
后臺的開發(fā)就開發(fā)完了,大家有沒有發(fā)現(xiàn)比基于stomp協(xié)議實現(xiàn)要靈活得多?
四、客戶端頁面的實現(xiàn)【基于h5】
不需要加入任何的js包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
<!doctype html> <html> <head> <title>socket.html</title> <meta name= "keywords" content= "keyword1,keyword2,keyword3" > <meta name= "description" content= "this is my page" > <meta name= "content-type" content= "text/html" charset= "utf-8" > <!--<link rel= "stylesheet" type= "text/css" href= "./styles.css" rel= "external nofollow" >--> </head> <body> welcome<br/> <input id= "text" type= "text" /><button onclick= "send()" >send</button> <button onclick= "closewebsocket()" >close</button> <div id= "message" > </div> <!-- 公共js --> <script type= "text/javascript" src= "../websocket/jquery.min.js" ></script> <script type= "text/javascript" > var userid= "888" ; var websocket= null ; $(function() { //創(chuàng)建websocket connectwebsocket(); }) //強(qiáng)制關(guān)閉瀏覽器 調(diào)用websocket.close(),進(jìn)行正常關(guān)閉 window.onunload = function() { //關(guān)閉連接 closewebsocket(); } //建立websocket連接 function connectwebsocket(){ console.log( "開始..." ); //建立websocket連接 websocket = new websocket( "ws://127.0.0.1:9091/cloud-sbjm/myhandler/id=" +userid); //打開websokcet連接時,回調(diào)該函數(shù) websocket.onopen = function () { console.log( "onpen" ); } //關(guān)閉websocket連接時,回調(diào)該函數(shù) websocket.onclose = function () { //關(guān)閉連接 console.log( "onclose" ); } //接收信息 websocket.onmessage = function (msg) { console.log(msg.data); } } //發(fā)送消息 function send(){ var postvalue={}; postvalue.id=userid; postvalue.message=$( "#text" ).val(); websocket.send(json.stringify(postvalue)); } //關(guān)閉連接 function closewebsocket(){ if (websocket != null ) { websocket.close(); } } </script> </body> </html> |
頁面比較簡單,簡單解釋一下:
1.new websocket("ws://127.0.0.1:9091/cloud-sbjm/myhandler/id="+userid),與服務(wù)器建立websocket連接,后面的id="+userid,是動態(tài)參數(shù),跟服務(wù)器配置handler的訪問地址時對應(yīng)"/myhandler/{id}"。
2.h5也提供多個回調(diào)函數(shù)
onopen:打開websokcet連接時,回調(diào)該函數(shù)
onclose:關(guān)閉websocket連接時,回調(diào)該函數(shù)
onmessage:服務(wù)器給該socket發(fā)送消息時,回調(diào)該函數(shù),獲取消息
websocket.send(json.stringify(postvalue));:給socket發(fā)送消息,服務(wù)器獲取
websocket.close();客戶端主要關(guān)閉連接,會觸發(fā)客戶端的onclose方法和服務(wù)器的afterconnectionclosed方法
到此服務(wù)端的開發(fā)也完成了,下面執(zhí)行一下程序效果圖:
一、建立連接
客戶端:
服務(wù)器:
二、發(fā)送消息
客戶端:
服務(wù)器:
三、服務(wù)器主動推送消息
服務(wù)器代碼:
到此已經(jīng)完成了,各位可以根據(jù)自己需求進(jìn)行修改,這會靈活多了!
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。
原文鏈接:https://blog.csdn.net/Ouyzc/article/details/79994401