<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-websocketartifactId>
dependency>
@Configuration
public class WebSocketConfig {
// 如果你使用的不是Spring Boot依赖的服务器,才需要自己创建
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
@ServerEndpoint 定义端点服务类
定义WebSocket的打开,关闭,错误,发送消息
@ServerEndpoint("/ws") //创建服务端点 地址为/ws
@Service
public class WebSocketServiceImpl {
//每一个客户端打开,都会创建WebSocketServiceImpl对象, 下面是计数 将这个对象保存到 CopyOnWriteArraySet 中
//关闭是 清楚这个对象 ,并且 计数 减一
//消息发送, 通过轮询所有的客户端,都发送消息
//只发送特定的用户,则需要得到用户信息,然后在发送
// 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
// concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServiceImpl对象。
private static CopyOnWriteArraySet<WebSocketServiceImpl>
webSocketSet = new CopyOnWriteArraySet<>();
// 与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
/**
* 连接建立成功调用的方法。标注客户端打开websocket服务端点调用方法*/
@OnOpen
public void onOpen(Session session) {
this.session = session;
webSocketSet.add(this); // 加入set中
addOnlineCount(); // 在线数加1
System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
try {
sendMessage("有新的连接加入了!!");
} catch (IOException e) {
System.out.println("IO异常");
}
}
/**
* 连接关闭调用的方法。标注客户端关闭websocket服务端点调用方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this); // 从set中删除
subOnlineCount(); // 在线数减1
System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("来自客户端的消息:" + message);
// 群发消息
for (WebSocketServiceImpl item : webSocketSet) {
try {
/*
// 获取当前用户名称
String userName = item.getSession()
.getUserPrincipal().getName();
System.out.println(userName);
*/
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 发生错误时调用 客户端 请求服务端 发生异常调用
*/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("发生错误");
error.printStackTrace();
}
/**
* 发送消息
* @param message 客户端消息
* @throws IOException
*/
private void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
// 返回在线数
private static synchronized int getOnlineCount() {
return onlineCount;
}
// 当连接人数增加时
private static synchronized void addOnlineCount() {
WebSocketServiceImpl.onlineCount++;
}
// 当连接人数减少时
private static synchronized void subOnlineCount() {
WebSocketServiceImpl.onlineCount--;
}
}
this.session.getBasicRemote().sendText(message); 发送消息
@ServerEndpoint("/ws") //创建服务端点 地址为/ws
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
My WebSocket
测试一下WebSocket站点吧
var websocket = null;
// 判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
// 创建WebSocket对象,连接服务器端点
websocket = new WebSocket("ws://localhost:8080/ws");
} else {
alert('Not support websocket')
}
// 连接发生错误的回调方法
websocket.onerror = function() {
appendMessage("error");
};
// 连接成功建立的回调方法
websocket.onopen = function(event) {
appendMessage("open");
}
// 接收到消息的回调方法
websocket.onmessage = function(event) {
appendMessage(event.data);
}
// 连接关闭的回调方法
websocket.onclose = function() {
appendMessage("close");
}
// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,
// 防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function() {
websocket.close();
}
// 将消息显示在网页上
function appendMessage(message) {
var context = $("#context").html() +"
" + message;
$("#context").html(context);
}
// 关闭连接
function closeWebSocket() {
websocket.close();
}
// 发送消息
function sendMessage() {
var message = $("#message").val();
websocket.send(message);
}
@Controller
@RequestMapping("/websocket")
public class WebSocketController {
// 跳转websocket页面
@GetMapping("/index")
public String websocket() {
return "websocket";
}
}
@Configuration
@EnableWebSocketMessageBroker //启用STOMP协议
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
// 如果你使用的不是Spring Boot依赖的服务器,才需要自己创建
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
// 注册服务器端点
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 增加一个聊天服务端点
registry.addEndpoint("/socket").withSockJS();//也可以支持sockJS
// 增加一个用户服务端点
registry.addEndpoint("/wsuser").withSockJS();
}
// 定义服务器端点请求和订阅前缀
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 客户端订阅路径前缀
registry.enableSimpleBroker("/sub", "/queue");
// 服务端点请求前缀
registry.setApplicationDestinationPrefixes("/request");
}
}
@Controller
@RequestMapping("/websocket")
public class WebSocketController {
@Autowired // 注入Spring Boot自动配置消息模板对象
private SimpMessagingTemplate simpMessagingTemplate;
// 发送页面
@GetMapping("/send")
public String send() {
return "send";
}
// 接收页面
@GetMapping("/receive")
public String receive() {
return "receive";
}
// 对特定用户发送页面
@GetMapping("/sendUser")
public String sendUser() {
return "send-user";
}
// 接收用户消息页面
@GetMapping("/receiveUser")
public String receiveUser() {
return "receive-user";
}
// 定义消息请求路径
@MessageMapping("/send")
// 定义结果发送到特定路径
@SendTo("/sub/chat")
public String sendMsg(String value) {
return value;
}
// 将消息发送给特定用户
@MessageMapping("/sendUser")
public void sendToUser(Principal principal, String body) {
String srcUser = principal.getName();
// 解析用户和消息
String []args = body.split(",");
String desUser = args[0];
String message = "【" + srcUser + "】给你发来消息:" + args[1];
// 发送到用户和监听地址
simpMessagingTemplate.convertAndSendToUser(desUser,
"/queue/customer", message);
}
}
@MessageMapping("/send") 定义消息请求路径
@SendTo("/sub/chat") 在执行完 这个方法后,将返回结果发送到订阅的这个目的址中
principal 获得当前用户的消息
simpMessagingTemplate.convertAndSendToUser(desUser, “/queue/customer”, message);
@SpringBootApplication(scanBasePackages = "com.springboot.chapter13")
@EnableScheduling
public class Chapter13Application extends WebSecurityConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(Chapter13Application.class, args);
}
// 定义3个可以登录的内存用户
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 密码加密器
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
// 加入三个内存用户,密码分别为加密后的"p1","p2"和"p3"
// 可以通过 passwordEncoder.encode("p1")这样获得加密后的密码
auth.inMemoryAuthentication().passwordEncoder(passwordEncoder)
.withUser("user1")
.password("$2a$10$7njFQKL2WV862XP6Hlyly.F0lkSHtOOQyQ/rlY7Ok26h.gGZD4IqG").roles("USER").and()
.withUser("user2").password("$2a$10$Q2PwvWNpog5sZX583LuQfet.y1rfPMsqtrb7IjmvRn7Ew/wNUjVwS")
.roles("ADMIN").and().withUser("user3")
.password("$2a$10$GskYZT.34BdhmEdOlAS8Re7D73RprpGN0NjaiqS2Ud8XdcBcJck4u").roles("USER");
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
My WebSocket
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
My WebSocket
等待接收消息
// 客户端订阅路径前缀
registry.enableSimpleBroker("/sub");
// 服务端点请求前缀
registry.setApplicationDestinationPrefixes("/request");
// 增加一个聊天服务端点
registry.addEndpoint("/socket").withSockJS();
// 定义消息请求路径
@MessageMapping("/send")
// 定义结果发送到特定路径
@SendTo("/sub/chat")
发送消息:
首先是创建了:new SockJS('/socket'); 路径
发送消息的路径:stompClient.send("/request/send", {}, value);
接收消息时:
var s = new SockJS('/socket');
stompClient.subscribe('/sub/chat', function(data) {
$('#receive').html(data.body);
});