特別說明:自學(xué)筆記
使用websocket有兩種方式:
- 使用sockjs,
- 使用h5的標(biāo)準竹伸。
使用Html5標(biāo)準自然更方便簡單辫樱,所以記錄的是配合h5的使用方法幸斥。
1匹摇、pom.xml中添加如下:
核心是@ServerEndpoint這個注解。這個注解是Javaee標(biāo)準里的注解甲葬,tomcat7以上已經(jīng)對其進行了實現(xiàn)廊勃,如果是用傳統(tǒng)方法使用tomcat發(fā)布項目,只要在pom文件中引入javaee標(biāo)準即可使用经窖。
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
但使用springboot的內(nèi)置tomcat時坡垫,就不需要引入javaee-api了,spring-boot已經(jīng)包含了画侣。使用springboot的websocket功能首先引入springboot組件冰悠。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>1.3.5.RELEASE</version>
</dependency>
順便說一句,springboot的高級組件會自動引用基礎(chǔ)的組件配乱,像spring-boot-starter-websocket就引入了spring-boot-starter-web和spring-boot-starter溉卓,所以不要重復(fù)引入。
2搬泥、使用@ServerEndpoint創(chuàng)立websocket endpoint
首先要注入ServerEndpointExporter桑寨,這個bean會自動注冊使用了@ServerEndpoint注解聲明的Websocket endpoint。要注意忿檩,如果使用獨立的servlet容器尉尾,而不是直接使用springboot的內(nèi)置容器,就不要注入ServerEndpointExporter燥透,因為它將由容器自己提供和管理沙咏。
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
接下來就是寫websocket的具體實現(xiàn)類辨图,很簡單,直接上代碼:
package com.reapal.websocket;
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.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author jackcooper
* @create 2017-12-28 13:04
*/
@ServerEndpoint(value = "/websocket")
@Component
public class ApplicationWebSocket {
//靜態(tài)變量芭碍,用來記錄當(dāng)前在線連接數(shù)徒役。設(shè)計成線程安全的。
private static AtomicInteger onlineCount = new AtomicInteger(0);
//concurrent包的線程安全Set窖壕,用來存放每個客戶端對應(yīng)的ApplicationWebSocket對象忧勿。
private static CopyOnWriteArraySet<ApplicationWebSocket> webSocketSet = new CopyOnWriteArraySet<ApplicationWebSocket>();
//與某個客戶端的連接會話,需要通過它來給客戶端發(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("當(dāng)前在線人數(shù)為:"+getOnlineCount());
} 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ā)送過來的消息*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("來自客戶端的消息:" + message);
//群發(fā)消息
for (ApplicationWebSocket item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 發(fā)生錯誤時調(diào)用
@OnError
*/
public void onError(Session session, Throwable error) {
System.out.println("發(fā)生錯誤");
error.printStackTrace();
}
public void sendMessage(String message) throws IOException {
//getBasicRemote是阻塞式的
this.session.getBasicRemote().sendText(message);
//非阻塞式的
// this.session.getAsyncRemote().sendText(message);
}
/**
* 群發(fā)自定義消息
* */
public static void sendInfo(String message) throws IOException {
for (ApplicationWebSocket item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
continue;
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount.get();
}
public static synchronized void addOnlineCount() {
ApplicationWebSocket.onlineCount.getAndIncrement();
}
public static synchronized void subOnlineCount() {
ApplicationWebSocket.onlineCount.getAndDecrement();
}
}
使用springboot的唯一區(qū)別是要@Component聲明下,而使用獨立容器是由容器自己管理websocket的速勇,但在springboot中連容器都是spring管理的晌砾。
雖然@Component默認是單例模式的,但springboot還是會為每個websocket連接初始化一個bean烦磁,所以可以用一個靜態(tài)set保存起來养匈。
3、前端代碼
<!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ā)生錯誤的回調(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)閉時呕乎,主動去關(guān)閉websocket連接,防止連接還沒斷開就關(guān)閉窗口陨晶,server端會拋異常猬仁。
window.onbeforeunload = function(){
websocket.close();
}
//將消息顯示在網(wǎng)頁上
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集成的褐耳,會有一些配置诈闺,在使用springboot時,由于springboot已經(jīng)有了自己的配置铃芦,再這些配置有可能導(dǎo)致各種各樣的異常买雾。