輕鬆搞定WebSocket
實現後臺向前端推送資訊
pom.xml引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
WebSocketConfig
啟用WebSocket的支援也是很簡單,幾句程式碼搞定
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** * 開啟WebSocket支援 * @author zhengkai */ @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
WebSocketServer
因為WebSocket是類似客戶端服務端的形式(採用ws協議),那麼這裡的WebSocketServer其實就相當於一個ws協議的Controller
直接@ServerEndpoint("/websocket")@Component啟用即可,然後在裡面實現@OnOpen,@onClose,@onMessage等方法
/** * @Author: ynz * @Date: 2018/12/22/022 10:35 */ @ServerEndpoint("/websocket/{sid}") @Component @Slf4j public class WebSocketServer { //靜態變數,用來記錄當前線上連線數。應該把它設計成執行緒安全的。 private static int onlineCount = 0; //concurrent包的執行緒安全Set,用來存放每個客戶端對應的MyWebSocket物件。 private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>(); //與某個客戶端的連線會話,需要通過它來給客戶端傳送資料 private Session session; //接收sid private String sid=""; /** * 連線建立成功呼叫的方法*/ @OnOpen public void onOpen(Session session,@PathParam("sid") String sid) { this.session = session; webSocketSet.add(this); //加入set中 addOnlineCount(); //線上數加1 log.info("有新視窗開始監聽:"+sid+",當前線上人數為" + getOnlineCount()); this.sid=sid; try { sendMessage("連線成功"); } catch (IOException e) { log.error("websocket IO異常"); } } /** * 連線關閉呼叫的方法 */ @OnClose public void onClose() { webSocketSet.remove(this); //從set中刪除 subOnlineCount(); //線上數減1 log.info("有一連線關閉!當前線上人數為" + getOnlineCount()); } /** * 收到客戶端訊息後呼叫的方法 * * @param message 客戶端傳送過來的訊息*/ @OnMessage public void onMessage(String message, Session session) { log.info("收到來自視窗"+sid+"的資訊:"+message); //群發訊息 for (WebSocketServer item : webSocketSet) { try { item.sendMessage(message); } catch (IOException e) { e.printStackTrace(); } } } /** * * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("發生錯誤"); error.printStackTrace(); } /** * 實現伺服器主動推送 */ public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } /** * 群發自定義訊息 * */ public static void sendInfo(String message,@PathParam("sid") String sid) throws IOException { log.info("推送訊息到視窗"+sid+",推送內容:"+message); for (WebSocketServer item : webSocketSet) { try { //這裡可以設定只推送給這個sid的,為null則全部推送 if(sid==null) { item.sendMessage(message); }else if(item.sid.equals(sid)){ item.sendMessage(message); } } catch (IOException e) { continue; } } } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { WebSocketServer.onlineCount++; } public static synchronized void subOnlineCount() { WebSocketServer.onlineCount--; } }
訊息推送
至於推送新資訊,可以再自己的Controller寫個方法呼叫WebSocketServer.sendInfo();即可
@Controller public class CheckCenterController { //推送資料介面 @ResponseBody @RequestMapping("/socket/push/{cid}") public String pushToWeb(@PathVariable String cid,@RequestBody String message) { try { WebSocketServer.sendInfo(message,cid); } catch (IOException e) { e.printStackTrace(); } return message; } }
頁面發起socket請求
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script src="js/jquery.min.js"></script>
<script>
var socket = null;
function connect(){
if(typeof(WebSocket) == "undefined") {
console.log("您的瀏覽器不支援WebSocket");
}else{
console.log("您的瀏覽器支援WebSocket");
//實現化WebSocket物件,指定要連線的伺服器地址與埠 建立連線
socket = new WebSocket($("#url").val());
//開啟事件
socket.onopen = function() {
console.log("Socket 已開啟");
$("#status").html("已連線...");
//socket.send("這是來自客戶端的訊息" + location.href + new Date());
};
//獲得訊息事件
socket.onmessage = function(msg) {
console.log(msg.data);
$("#displayMsg").html( $("#displayMsg").html()+"<br>"+msg.data );
//發現訊息進入 開始處理前端觸發邏輯
};
//關閉事件
socket.onclose = function() {
console.log("Socket已關閉");
$("#status").html("未連線...");
socket = null;
};
//發生了錯誤事件
socket.onerror = function() {
alert("Socket發生了錯誤");
//此時可以嘗試重新整理頁面
}
}
}
function send() {
if(socket == null){
alert("未連線");
return false;
}
socket.send($("#sendMsg").val());
}
function closeConnect(){
$("#status").html("已斷開...");
socket.close();
}
</script>
</head>
<body>
連線地址:<input type="text" id="url" style="width:400px;" value="ws://127.0.0.1:8080/websocket/22"></input>
<button type="button" id="connect" onclick="connect()">連線</button>
<button type="button" id="closeConnect" onclick="closeConnect()">斷開</button>
<div id="status" style="display:inline;">未連線...</div>
<br><br>
傳送訊息:<input type="text" id="sendMsg" style="width:400px;"></input>
<button type="button" onclick="send()">傳送</button><br><br>
<div>接收到訊息:</div>
<div id="displayMsg"></div>
</body>
</html>
訪問:http://127.0.0.1:8080/socket.html,可以連線服務,傳送訊息。
或者:http://127.0.0.1:8080/socket/push/{cid}給相應的服務發訊息。
實現SSH WEB客戶端
新增依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.54</version>
</dependency>
WebSocketMessageBrokerConfigurer
配置訊息代理,預設情況下使用內建的訊息代理。 類上的註解@EnableWebSocketMessageBroker:此註解表示使用STOMP協議來傳輸基於訊息代理的訊息,此時可以在@Controller類中使用@MessageMapping
在方法registerStompEndpoints()裡addEndpoint方法:新增STOMP協議的端點。這個HTTP URL是供WebSocket或SockJS客戶端訪問的地址;withSockJS:指定端點使用SockJS協議
在方法configureMessageBroker()裡設定簡單訊息代理,並配置訊息的傳送的地址符合配置的字首的訊息才傳送到這個broker
@Configuration
// 此註解表示使用STOMP協議來傳輸基於訊息代理的訊息,此時可以在@Controller類中使用@MessageMapping
@EnableWebSocketMessageBroker
public class SSHSocketConfig implements WebSocketMessageBrokerConfigurer {
/**
* setAllowedOrigins方法用來設定來自那些域名的請求可訪問,預設為localhost
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocket")
.setAllowedOrigins("*");
//SockJS客戶端訪問
/*registry.addEndpoint("/my-websocket").withSockJS();*/
}
/**
* 配置訊息代理
* 啟動Broker,訊息的傳送的地址符合配置的字首來的訊息才傳送到這個broker
*/
public void configureMessageBroker(MessageBrokerRegistry registry) {
/**
* 配置訊息代理
* 啟動簡單Broker,訊息的傳送的地址符合配置的字首來的訊息才傳送到這個broker
*/
registry.enableSimpleBroker("/topic");
//只接收這字首傳送過來的訊息
registry.setApplicationDestinationPrefixes("/send");//應用請求字首
}
}
@Controller類 ,訊息處理
@Controller
public class SSHController {
@Resource
private SimpMessagingTemplate messagingTemplate ;
static Map<String,SSHData> map = new HashMap<>();
/**
* 接收訊息
*/
@MessageMapping("/receive/{id}")
// @SendTo("/topic/test")
public String receiver(@DestinationVariable("id") String id, String msg)
throws IOException {
SSHData sshData = map.get(id);
if(sshData != null){
OutputStream outputStream = map.get(id).getOutputStream();
outputStream.write((msg).getBytes());
outputStream.flush();
}else{
messagingTemplate.convertAndSend("/topic/"+id,"遠端伺服器未連線。。。\n\r");
}
return msg;
}
/**
* 建立SSH連線
*/
@RequestMapping("/connect")
@ResponseBody
public String connect(String user,String host,Integer port,String password,String id)
throws IOException {
SSHData sshData = map.get(id);
if(sshData != null){
sshData.release();
}
ChannelShell channelShell = SshUtils.getShellChannel( user, host, port , password, id);
if(channelShell == null){
messagingTemplate.convertAndSend("/topic/"+id,
"遠端伺服器連線失敗,請檢查使用者或者密碼是正確\n\r");
return "";
}
map.put(id,new SSHData(channelShell,messagingTemplate,id));
return "";
}
/**
* 斷開連線
*/
@RequestMapping("/disconnect")
@ResponseBody
public String disConnect(String id) throws IOException {
SSHData sshData = map.get(id);
if(sshData != null){
sshData.release();
map.remove(id);
}
messagingTemplate.convertAndSend("/topic/"+id,"已斷開連線。。。\n\r");
return "";
}
}
-
@MessageMapping:指定要接收訊息的地址,類似@RequestMapping
-
@SendTo預設訊息將被髮送到與傳入訊息相同的目的地,但是目的地前面附加字首(預設情況下為“/topic”}
-
@DestinationVariable接收URL的引數,類似@PathVariable
前端stomp、sockjs的配置
Stomp
websocket使用socket實現雙工非同步通訊能力。但是如果直接使用websocket協議開發程式比較繁瑣,我們可以使用它的子協議Stomp
SockJS
sockjs是websocket協議的實現,增加了對瀏覽器不支援websocket的時候的相容支援 SockJS的支援的傳輸的協議有3類: WebSocket, HTTP Streaming, and HTTP Long Polling。預設使用websocket,如果瀏覽器不支援websocket,則使用後兩種的方式。
SockJS使用”Get /info”從服務端獲取基本資訊。然後客戶端會決定使用哪種傳輸方式。如果瀏覽器使用websocket,則使用websocket。如果不能,則使用Http Streaming,如果還不行,則最後使用 HTTP Long Polling。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script src="js/jquery.min.js"></script>
<script src="js/xterm/sockjs.min.js"></script>
<script src="js/xterm/stomp.min.js"></script>
<link href="js/xterm/xterm.css" rel="stylesheet"></link>
<script src="js/xterm/xterm.js"></script>
</head>
<body>
<div id="terminal"></div>
<div id="terminal2" style="height: 10%">
<div id="desc"></div><br>
IP:<input id="ip" type="text" value="47.106.106.**"></input>
Port:<input id="port" type="text" value="22"></input>
使用者名稱:<input id="username" type="text" value="root"></input>
密碼:<input id="password" type="text" value="*"></input>
<button onclick="connect()">登入</button>
<button onclick="disconnect()">斷開</button>
</div>
<script>
var stompClient ;
var term = new Terminal({
cols: 150,
rows: 35,
screenKeys: true,
useStyle: true,
cursorBlink: true
});
term.open(document.getElementById('terminal'));
term.on('data', function($data) {
//term.write($data);
stompClient.send("/send/receive/1",{}, $data );
});
document.onkeydown=function(){
if (event.keyCode == 13){
term.write("\n\r");
}
}
</script>
<script>
$(document).ready(function() {
openSocket();
});
function openSocket() {
if(stompClient==null){
var socketPath ='ws://127.0.0.1:8080/websocket';
var socket = new WebSocket(socketPath);
stompClient = Stomp.over(socket);
var headers={
"Access-Control-Allow-Origin":"*",
"Access-Control-Allow-Credentials":"true",
"token":"kltoen"
};
stompClient.connect(headers, function(frame) {
$("#desc").html("WebSocket已連線");
stompClient.subscribe('/topic/1', function(event) {
term.write(event.body);
},headers);
},function (error) {
$("#desc").html("WebSocket連線失敗");
});
window.setInterval(function(){ //每隔5秒鐘傳送一次心跳,避免websocket連線超時而自動斷開
stompClient.send(" ");
},30000);
}
}
function connect() {
$.ajax({
type: "GET",
url: "http://127.0.0.1:8080/connect?user="+$("#username").val()+
"&host="+$("#ip").val()+ "&port="+$("#port").val()+
"&password="+$("#password").val()+"&id=1"
});
}
function disconnect() {
$.ajax({
type: "GET",
url: "http://127.0.0.1:8080/disconnect?&id=1"
});
}
</script>
</body>
</html>
測試