本文只作為個(gè)人筆記,大部分代碼是引用其他人的文章的。
在springboot項(xiàng)目中使用websocket做推送,雖然挺簡(jiǎn)單的,但初學(xué)也踩過幾個(gè)坑,特此記錄。
使用websocket有兩種方式:1是使用sockjs,2是使用h5的標(biāo)準(zhǔn)。使用Html5標(biāo)準(zhǔn)自然更方便簡(jiǎn)單,所以記錄的是配合h5的使用方法。
1、pom
核心是@ServerEndpoint這個(gè)注解。這個(gè)注解是Javaee標(biāo)準(zhǔn)里的注解,tomcat7以上已經(jīng)對(duì)其進(jìn)行了實(shí)現(xiàn),如果是用傳統(tǒng)方法使用tomcat發(fā)布項(xiàng)目,只要在pom文件中引入javaee標(biāo)準(zhǔn)即可使用。
1
2
3
4
5
6
|
< dependency > < groupId >javax</ groupId > < artifactId >javaee-api</ artifactId > < version >7.0</ version > < scope >provided</ scope > </ dependency > |
但使用springboot的內(nèi)置tomcat時(shí),就不需要引入javaee-api了,spring-boot已經(jīng)包含了。使用springboot的websocket功能首先引入springboot組件。
1
2
3
4
5
|
< dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-websocket</ artifactId > < version >1.3.5.RELEASE</ version > </ dependency > |
順便說(shuō)一句,springboot的高級(jí)組件會(huì)自動(dòng)引用基礎(chǔ)的組件,像spring-boot-starter-websocket就引入了spring-boot-starter-web和spring-boot-starter,所以不要重復(fù)引入。
2、使用@ServerEndpoint創(chuàng)立websocket endpoint
首先要注入ServerEndpointExporter,這個(gè)bean會(huì)自動(dòng)注冊(cè)使用了@ServerEndpoint注解聲明的Websocket endpoint。要注意,如果使用獨(dú)立的servlet容器,而不是直接使用springboot的內(nèi)置容器,就不要注入ServerEndpointExporter,因?yàn)樗鼘⒂扇萜髯约禾峁┖凸芾怼?/p>
1
2
3
4
5
6
7
8
|
@Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } } |
接下來(lái)就是寫websocket的具體實(shí)現(xiàn)類,很簡(jiǎ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
|
@ServerEndpoint (value = "/websocket" ) @Component public class MyWebSocket { //靜態(tài)變量,用來(lái)記錄當(dāng)前在線連接數(shù)。應(yīng)該把它設(shè)計(jì)成線程安全的。 private static int onlineCount = 0 ; //concurrent包的線程安全Set,用來(lái)存放每個(gè)客戶端對(duì)應(yīng)的MyWebSocket對(duì)象。 private static CopyOnWriteArraySet<MyWebSocket> webSocketSet = new CopyOnWriteArraySet<MyWebSocket>(); //與某個(gè)客戶端的連接會(huì)話,需要通過它來(lái)給客戶端發(fā)送數(shù)據(jù) private Session session; /** * 連接建立成功調(diào)用的方法*/ @OnOpen public void onOpen(Session session) { this .session = session; webSocketSet.add( this ); //加入set中 addOnlineCount(); //在線數(shù)加1 System.out.println( "有新連接加入!當(dāng)前在線人數(shù)為" + getOnlineCount()); try { sendMessage(CommonConstant.CURRENT_WANGING_NUMBER.toString()); } catch (IOException e) { System.out.println( "IO異常" ); } } /** * 連接關(guān)閉調(diào)用的方法 */ @OnClose public void onClose() { webSocketSet.remove( this ); //從set中刪除 subOnlineCount(); //在線數(shù)減1 System.out.println( "有一連接關(guān)閉!當(dāng)前在線人數(shù)為" + getOnlineCount()); } /** * 收到客戶端消息后調(diào)用的方法 * * @param message 客戶端發(fā)送過來(lái)的消息*/ @OnMessage public void onMessage(String message, Session session) { System.out.println( "來(lái)自客戶端的消息:" + message); //群發(fā)消息 for (MyWebSocket item : webSocketSet) { try { item.sendMessage(message); } catch (IOException e) { e.printStackTrace(); } } } /** * 發(fā)生錯(cuò)誤時(shí)調(diào)用 @OnError public void onError(Session session, Throwable error) { System.out.println("發(fā)生錯(cuò)誤"); error.printStackTrace(); } public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); //this.session.getAsyncRemote().sendText(message); } /** * 群發(fā)自定義消息 * */ public static void sendInfo(String message) throws IOException { for (MyWebSocket item : webSocketSet) { try { item.sendMessage(message); } catch (IOException e) { continue ; } } } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { MyWebSocket.onlineCount++; } public static synchronized void subOnlineCount() { MyWebSocket.onlineCount--; } } |
使用springboot的唯一區(qū)別是要@Component聲明下,而使用獨(dú)立容器是由容器自己管理websocket的,但在springboot中連容器都是spring管理的。
雖然@Component默認(rèn)是單例模式的,但springboot還是會(huì)為每個(gè)websocket連接初始化一個(gè)bean,所以可以用一個(gè)靜態(tài)set保存起來(lái)。
3、前端代碼
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
|
<!DOCTYPE HTML> <html> <head> <title>My WebSocket</title> </head> <body> Welcome<br/> <input id= "text" type= "text" /><button onclick= "send()" >Send</button> <button onclick= "closeWebSocket()" >Close</button> <div id= "message" > </div> </body> <script type= "text/javascript" > var websocket = null ; //判斷當(dāng)前瀏覽器是否支持WebSocket if ( 'WebSocket' in window){ websocket = new WebSocket( "ws://localhost:8084/websocket" ); } else { alert( 'Not support websocket' ) } //連接發(fā)生錯(cuò)誤的回調(diào)方法 websocket.onerror = function (){ setMessageInnerHTML( "error" ); }; //連接成功建立的回調(diào)方法 websocket.onopen = function (event){ setMessageInnerHTML( "open" ); } //接收到消息的回調(diào)方法 websocket.onmessage = function (event){ setMessageInnerHTML(event.data); } //連接關(guān)閉的回調(diào)方法 websocket.onclose = function (){ setMessageInnerHTML( "close" ); } //監(jiān)聽窗口關(guān)閉事件,當(dāng)窗口關(guān)閉時(shí),主動(dòng)去關(guān)閉websocket連接,防止連接還沒斷開就關(guān)閉窗口,server端會(huì)拋異常。 window.onbeforeunload = function (){ websocket.close(); } //將消息顯示在網(wǎng)頁(yè)上 function setMessageInnerHTML(innerHTML){ document.getElementById( 'message' ).innerHTML += innerHTML + '<br/>' ; } //關(guān)閉連接 function closeWebSocket(){ websocket.close(); } //發(fā)送消息 function send(){ var message = document.getElementById( 'text' ).value; websocket.send(message); } </script> </html> |
4、總結(jié)
springboot已經(jīng)做了深度的集成和優(yōu)化,要注意是否添加了不需要的依賴、配置或聲明。由于很多講解組件使用的文章是和spring集成的,會(huì)有一些配置,在使用springboot時(shí),由于springboot已經(jīng)有了自己的配置,再這些配置有可能導(dǎo)致各種各樣的異常。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。
原文鏈接:http://www.cnblogs.com/bianzy/p/5822426.html