之前實現websocket基于stomp的,覺得springboot封裝的太高,不怎么靈活,現在實現一個純h5的,也大概了解websocket在內部是怎么傳輸的。
1.環境搭建
因為在上一篇基于stomp協議實現的websocket里已經有大概介紹過web的基本情況了,所以在這篇就不多說了,我們直接進入正題吧,在springboot中,我們還是需要導入websocket的包。
在pox.xml加上對springboot對websocket的支持:
1
2
3
4
5
|
<!-- websocket --> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-websocket</artifactid> </dependency> |
這里大概說一下自己的一點小見解:客戶端與服務器建立websocket連接,實際上是創建了一個socket,這個socket是共享與客戶端和服務器的。兩者只要往對應的socket里操作,就可以實現雙方實時通訊了
2.編碼實現
一、在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; //實現接口來配置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:注解標識該類為spring的配置類
2.@enablewebsocket:開啟注解接收和發送消息
3.實現websocketconfigurer接口,重寫registerwebsockethandlers方法,這是一個核心實現方法,配置websocket入口,允許訪問的域、注冊handler、定義攔截器??蛻舳送ㄟ^“/myhandler/{id}”直接訪問handler核心類,進行socket的連接、接收、發送等操作,這里由于還加了個攔截器,所以建立新的socket訪問時,都先進來攔截器再進去handler類,“new websocketinterceptor()”是我實現的攔截器,“new myhandler()”是我實現的一個handler類。
二、websocketinterceptor攔截器的實現:
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 { //進入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( "當前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( "進來websocket的afterhandshake攔截器!" ); } } |
1.實現了handshakeinterceptor 接口,并實現了beforehandshake該方法,該方法是在進入handler核心類之前進行攔截。
這里主要實現的邏輯是:
截取客戶端建立websocket連接時發送的url地址字符串,并通過對該字符串進行特殊標識截取操作,獲取客戶端發送的唯一標識(由自己定義的,一般是系統用戶id唯一標識,用以標識該用戶),并把它以鍵值對的形式放到session里,這樣后期可以通過該session獲取它對應的用戶id了?!疽粋€session對應著一個websocketsession】
三、myhandler核心類的實現
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( "當前在線人數:" +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( "服務器收到了,hello!" )); } catch (exception e){ e.printstacktrace(); } } /** * 發送信息給指定用戶 * @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( "連接已關閉:" + status); users.remove(getclientid(session)); } @override public boolean supportspartialmessages() { return false ; } /** * 獲取用戶標識 * @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.實現了websockethandler接口,并實現了關鍵的幾個方法。
① afterconnectionestablished(接口提供的):建立新的socket連接后回調的方法。主要邏輯是:將成功建立連接的websocketsssion放到定義好的常量[private static final map<string, websocketsession> users;]中去。這里也截取客戶端訪問的url的字符串,拿到標識,以鍵值對的形式講每一個websocketsession存到users里,以記錄每個socket。
② handlemessage(接口提供的):接收客戶端發送的socket。主要邏輯是:獲取客戶端發送的信息。這里之所以可以獲取本次socket的id,是因為客戶端在第一次進行連接時,攔截器進行攔截后,設置好id,這樣也說明,雙方在相互通訊的時候,只是對第一次建立好的socket持續進行操作。
③ sendmessagetouser(自己定義的):發送給指定用戶信息。主要邏輯是:根據用戶id從常量users(記錄每一個socket)中,獲取socket,往該socket里發送消息,只要客戶端還在線,就能收到該消息。
④sendmessagetoallusers (自己定義的):這個廣播消息,發送信息給所有socket。主要邏輯是:跟③類型,只不過是遍歷整個users獲取每一個socket,給每一個socket發送消息即可完廣播發送
⑤handletransporterror(接口提供的):連接出錯時,回調的方法。主要邏輯是:一旦有連接出錯的socket,就從users里進行移除,有提供該socket的參數,可直接獲取id,進行移除。這個在客戶端沒有正常關閉連接時,會進來,所以在開發客戶端時,記得關閉連接
⑥afterconnectionclosed(接口提供的):連接關閉時,回調的方法。主要邏輯:一旦客戶端/服務器主動關閉連接時,將個socket從users里移除,有提供該socket的參數,可直接獲取id,進行移除。
后臺的開發就開發完了,大家有沒有發現比基于stomp協議實現要靈活得多?
四、客戶端頁面的實現【基于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() { //創建websocket connectwebsocket(); }) //強制關閉瀏覽器 調用websocket.close(),進行正常關閉 window.onunload = function() { //關閉連接 closewebsocket(); } //建立websocket連接 function connectwebsocket(){ console.log( "開始..." ); //建立websocket連接 websocket = new websocket( "ws://127.0.0.1:9091/cloud-sbjm/myhandler/id=" +userid); //打開websokcet連接時,回調該函數 websocket.onopen = function () { console.log( "onpen" ); } //關閉websocket連接時,回調該函數 websocket.onclose = function () { //關閉連接 console.log( "onclose" ); } //接收信息 websocket.onmessage = function (msg) { console.log(msg.data); } } //發送消息 function send(){ var postvalue={}; postvalue.id=userid; postvalue.message=$( "#text" ).val(); websocket.send(json.stringify(postvalue)); } //關閉連接 function closewebsocket(){ if (websocket != null ) { websocket.close(); } } </script> </body> </html> |
頁面比較簡單,簡單解釋一下:
1.new websocket("ws://127.0.0.1:9091/cloud-sbjm/myhandler/id="+userid),與服務器建立websocket連接,后面的id="+userid,是動態參數,跟服務器配置handler的訪問地址時對應"/myhandler/{id}"。
2.h5也提供多個回調函數
onopen:打開websokcet連接時,回調該函數
onclose:關閉websocket連接時,回調該函數
onmessage:服務器給該socket發送消息時,回調該函數,獲取消息
websocket.send(json.stringify(postvalue));:給socket發送消息,服務器獲取
websocket.close();客戶端主要關閉連接,會觸發客戶端的onclose方法和服務器的afterconnectionclosed方法
到此服務端的開發也完成了,下面執行一下程序效果圖:
一、建立連接
客戶端:
服務器:
二、發送消息
客戶端:
服務器:
三、服務器主動推送消息
服務器代碼:
到此已經完成了,各位可以根據自己需求進行修改,這會靈活多了!
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/Ouyzc/article/details/79994401