Preface
The introduction of WebSocket in Springboot will not be repeated here. There are more introductions on the Internet. This paper mainly solves the problem of broadcasting messages (for a topic) and sending messages to specific users. No more nonsense, start our realization.
Environmental Science
Only the core technical points are listed here.
jdk :1.8
maven :3.X
Spring boot:1.5.2.RELEASE
WebSocket: spring-boot-starter-websocket
Front-end engine: thymeleaf
Front-end framework: org.webjars: jQuery, Bootstrap, websocket, etc.
Realization
socket core configuration container
Configure top and server services, inject session bean s, and listen.
package com.ricky.websocket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
/**
* socket Core Configuration Container
*/
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");// / users default notification
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ricky-websocket").withSockJS();
}
@Bean
public SocketSessionRegistry SocketSessionRegistry(){
return new SocketSessionRegistry();
}
@Bean
public STOMPConnectEventListener STOMPConnectEventListener(){
return new STOMPConnectEventListener();
}
}
User session record class
User session records for destruction, storage, and access.
package com.ricky.websocket;
import org.springframework.util.Assert;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Created by baiguantao on 2017/8/4.
* User session record class
*/
public class SocketSessionRegistry{
//this map save every session
//This collection stores session
private final ConcurrentMap<String, Set<String>> userSessionIds = new ConcurrentHashMap();
private final Object lock = new Object();
public SocketSessionRegistry() {
}
/**
*
* Get session Id
* @param user
* @return
*/
public Set<String> getSessionIds(String user) {
Set set = (Set)this.userSessionIds.get(user);
return set != null?set: Collections.emptySet();
}
/**
* Get all session s
* @return
*/
public ConcurrentMap<String, Set<String>> getAllSessionIds() {
return this.userSessionIds;
}
/**
* register session
* @param user
* @param sessionId
*/
public void registerSessionId(String user, String sessionId) {
Assert.notNull(user, "User must not be null");
Assert.notNull(sessionId, "Session ID must not be null");
Object var3 = this.lock;
synchronized(this.lock) {
Object set = (Set)this.userSessionIds.get(user);
if(set == null) {
set = new CopyOnWriteArraySet();
this.userSessionIds.put(user, (Set<String>) set);
}
((Set)set).add(sessionId);
}
}
public void unregisterSessionId(String userName, String sessionId) {
Assert.notNull(userName, "User Name must not be null");
Assert.notNull(sessionId, "Session ID must not be null");
Object var3 = this.lock;
synchronized(this.lock) {
Set set = (Set)this.userSessionIds.get(userName);
if(set != null && set.remove(sessionId) && set.isEmpty()) {
this.userSessionIds.remove(userName);
}
}
}
}
STOMP listening class
User session injection.
package com.ricky.websocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.web.socket.messaging.SessionConnectEvent;
/**
* Created by baiguantao on 2017/8/4.
* STOMP Monitoring category
* Used for session registration and key value acquisition
*/
public class STOMPConnectEventListener implements ApplicationListener<SessionConnectEvent> {
@Autowired
SocketSessionRegistry webAgentSessionRegistry;
@Override
public void onApplicationEvent(SessionConnectEvent event) {
StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage());
//login get from browser
String agentId = sha.getNativeHeader("login").get(0);
String sessionId = sha.getSessionId();
webAgentSessionRegistry.registerSessionId(agentId,sessionId);
}
}
Chat Controller
Message Core Controller, Sending Single Message, Common Message Function.
package com.ricky.websocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Chat Controller
*/
@Controller
public class GreetingController {
/**session Operational class*/
@Autowired
SocketSessionRegistry webAgentSessionRegistry;
/**Message Sending Tool*/
@Autowired
private SimpMessagingTemplate template;
@RequestMapping(value = "/index")
public String index(){
return "/index";
}
@RequestMapping(value = "/msg/message")
public String ToMessage(){
return "/message";
}
@RequestMapping(value = "/msg/messaget2")
public String ToMessaget2(){
return "/messaget2";
}
/**
* User Broadcasting
* Send Message Broadcasting for Internal Send Use
* @param request
* @return
*/
@GetMapping(value = "/msg/sendcommuser")
public @ResponseBody
OutMessage SendToCommUserMessage(HttpServletRequest request){
List<String> keys=webAgentSessionRegistry.getAllSessionIds().entrySet()
.stream().map(Map.Entry::getKey)
.collect(Collectors.toList());
Date date=new Date();
keys.forEach(x->{
String sessionId=webAgentSessionRegistry.getSessionIds(x).stream().findFirst().get().toString();
template.convertAndSendToUser(sessionId,"/topic/greetings",new OutMessage("commmsg: allsend, " + "send comm" +date.getTime()+ "!"),createHeaders(sessionId));
});
return new OutMessage("sendcommuser, " + new Date() + "!");
}
/**
* The same sending message is just that the ws version http request is not accessible
* Send messages based on user key
* @param message
* @return
* @throws Exception
*/
@MessageMapping("/msg/hellosingle")
public void greeting2(InMessage message) throws Exception {
Map<String,String> params = new HashMap(1);
params.put("test","test");
//There's no verification here.
String sessionId=webAgentSessionRegistry.getSessionIds(message.getId()).stream().findFirst().get();
template.convertAndSendToUser(sessionId,"/topic/greetings",new OutMessage("single send to: "+message.getId()+", from:" + message.getName() + "!"),createHeaders(sessionId));
}
private MessageHeaders createHeaders(String sessionId) {
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);
return headerAccessor.getMessageHeaders();
}
}
Other
There are other auxiliary tool classes, inmessage for receiving messages and outmessage for outputting messages.
Front end part
Only one is shown here, because the core functions are already included. Including the establishment of socket links, sending messages, subscriptions and other functions.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<link href="/webjars/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<script src="/webjars/jquery/3.1.0/jquery.min.js"></script>
<script src="/webjars/sockjs-client/1.0.2/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/2.3.3/stomp.min.js"></script>
<body>
<blockquote class="layui-elem-quote">/user/topic-message</blockquote>
<div id="main-content" class="container">
<div class="row">
<div class="col-md-6">
<form class="form-inline">
<div class="form-group">
<label for="connect">WebSocket connection:</label>
<button id="connect" class="btn btn-default" type="submit">Connect</button>
<button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
</button>
</div>
</form>
</div>
<div class="col-md-6">
<form class="form-inline">
<div class="form-group">
<label for="name">What is your name?</label>
<input type="text" id="name" class="form-control" placeholder="Your name here...">
</div>
<button id="send" class="btn btn-default" type="submit">Send</button>
</form>
</div>
</div>
<div class="row">
<div class="col-md-12">
<table id="conversation" class="table table-striped">
<thead>
<tr>
<th>Greetings</th>
</tr>
</thead>
<tbody id="greetings">
</tbody>
</table>
</div>
</div>
</form>
</div>
<script>
// /msg/sendcommuser
var stompClient = null;
//Pass the user key value
var login = "ricky";
function setConnected(connected) {
$("#connect").prop("disabled", connected);
$("#disconnect").prop("disabled", !connected);
if (connected) {
$("#conversation").show();
}
else {
$("#conversation").hide();
}
$("#greetings").html("");
}
function connect() {
var socket = new SockJS('/ricky-websocket');
stompClient = Stomp.over(socket);
stompClient.connect({login:login}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/user/topic/greetings', function (greeting) {
showGreeting(JSON.parse(greeting.body).content);
});
});
}
function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function sendName() {
stompClient.send("/app/msg/hellosingle", {}, JSON.stringify({'name': $("#name").val(),'id':'rickyt2'}));
}
function showGreeting(message) {
$("#greetings").append("<tr><td>" + message + "</td></tr>");
}
$(function () {
$("form").on('submit', function (e) {
e.preventDefault();
});
$( "#connect" ).click(function() { connect(); });
$( "#disconnect" ).click(function() { disconnect(); });
$( "#send" ).click(function() { sendName(); });
});
</script>
</body>
</html>
Demonstration
homepage
Visit http://localhost:82 Enter the home page, mainly providing two different users ricky and rickyt2, as well as the function of sending public messages.
ricky users
After entering, click connect and enter sendName to send the message.
rickyt2 user
After entering, click connect and enter sendName to send the message.
Sending public messages
Click the public message button on the home page to send the public message. If you want to send it again, refresh the public message page.
Explain
User keys are delivered from pages, and keys sent to special users are also delivered from pages. If you want to send messages to specific users, you can achieve related functions. Here, only a demonstration is made, so you are dead.
Pits encountered
webjars 404
By default, the relevant plug-ins are introduced as follows:
<link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet">
But found that no, even with the relevant plug-in (locator), not, and then look at the source code, found that there are 3.3.7 directories, need to add version paths.
<link href="/webjars/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
Message User Problem
This pit is about the path, and a little partner may wonder why my subscription has a prefix of / user. This is actually because we use the convertAndSendToUser source code of SimpMessaging Template. The default prefix is user, so we add / user prefix, which is also the core of messages sent to specific users.
Refer to destination in SimpMessaging Template:
public void send(D destination, Message<?> message) {
this.doSend(destination, message);
}
epilogue
Finally, once into the programming deep like the sea, from then on white teenagers.
Attached demo address: SpringBootWebSocket
author
ricky Exchange Group: 2449 30845