package com.tinet.websocket.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @author dsx
*/
@Configuration
public class WebsocketConfig {
/**
* ServerEndpointExporter 作用
*
* 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
*
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
用于给前端建立连接的server
package com.tinet.websocket.config;
import com.alibaba.fastjson.JSON;
import com.tinet.websocket.pojo.MessageBody;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author DENGSHAOXIANG
*/
@ServerEndpoint("/websocket/test/{userId}")
@Component
public class WebsocketServer {
public WebsocketServer() {
//每当有一个连接,都会执行一次构造方法
System.out.println("新的连接。。。");
}
/**
* 存放当前连接数
*/
private static final AtomicInteger COUNT = new AtomicInteger();
/**
* 存放所有的连接
*/
private static final ConcurrentHashMap<String, Session> SESSIONS = new ConcurrentHashMap<>();
//发送消息
public void sendMessage(Session toSession, String message) {
if (toSession != null) {
try {
toSession.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
} else {
System.out.println("对方不在线");
}
}
public void sendMessageToUser(String user, String message) {
Session toSession = SESSIONS.get(user);
sendMessage(toSession, message);
}
@OnMessage
public void onMessage(String message) {
System.out.println("服务器收到消息:" + message);
MessageBody messageBody = JSON.parseObject(message, MessageBody.class);
String userId = messageBody.getUserId();
sendMessageToUser(userId, messageBody.getMessage());
}
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
if (SESSIONS.get(userId) != null) {
return;
}
SESSIONS.put(userId, session);
COUNT.incrementAndGet();
System.out.println(userId + "上线了,当前在线人数:" + COUNT);
}
@OnClose
public void onClose(@PathParam("userId") String userId) {
SESSIONS.remove(userId);
COUNT.decrementAndGet();
System.out.println(userId + "下线了,当前在线人数:" + COUNT);
}
@OnError
public void onError(Session session, Throwable throwable) {
System.out.println("发生错误");
throwable.printStackTrace();
}
}
后端代码就完成了
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSockettitle>
head>
<script src="sockjs.js">script>
<script src="stomp.js">script>
<body>
<h3>hello socketh3>
<p>【userId】:
<div><input id="userId" name="userId" type="text" value="10">div>
<p>【toUserId】:
<div><input id="toUserId" name="toUserId" type="text" value="20">div>
<p>【消息内容】:
<div><input id="contentText" name="contentText" type="text" value="hello websocket">div>
<p>操作:
<div style="color: green"><a onclick="openSocketByStomp()">开启socketa>div>
<p>操作:
<div style="color: green"><a onclick="closeSocket()">断开socketa>div>
<p>【操作】:
<div style="color: green"><a onclick="sendMessage()">发送消息a>div>
body>
<script>
var socket;
let stompClient;
function openSocket() {
if (typeof (WebSocket) == "undefined") {
alert("您的浏览器不支持WebSocket");
} else {
if (socket != null) {
return;
}
//实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接
var userId = document.getElementById('userId').value;
// var socketUrl="ws://127.0.0.1:22599/webSocket/"+userId;
var socketUrl = "ws://localhost/websocket/test/111";
console.log(socketUrl);
socket = new WebSocket(socketUrl);
//打开事件
socket.onopen = function () {
console.log("websocket已打开");
//socket.send("这是来自客户端的消息" + location.href + new Date());
};
//获得消息事件
socket.onmessage = function (msg) {
var serverMsg = "收到服务端信息:" + msg.data;
console.log(serverMsg);
//发现消息进入 开始处理前端触发逻辑
};
//关闭事件
socket.onclose = function () {
console.log("websocket已关闭");
};
//发生了错误事件
socket.onerror = function () {
console.log("websocket发生了错误");
}
}
}
function sendMessage() {
if (socket === undefined || socket === null) {
alert("请先连接");
return;
}
if (typeof (WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
} else {
// console.log("您的浏览器支持WebSocket");
var toUserId = document.getElementById('toUserId').value;
var contentText = document.getElementById('contentText').value;
var msg = '{"userId":"' + toUserId + '","message":"' + contentText + '"}';
console.log(msg);
socket.send(msg);
}
}
function closeSocket() {
if (socket === undefined || socket === null) {
alert("请先连接");
return;
}
socket.close();
socket = null;
}
script>
html>
实现WebSocketMessageBrokerConfigurer并实现相关方法
package com.tinet.websocket.config;
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.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
/**
* Created by dengshaoxiang on 2019/11/21 11:36
* description: broker
*/
@Configuration
@EnableWebSocketMessageBroker
public class WebsocketBrokerConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// endpoint用来建立ws连接的
registry.addEndpoint("/gs-guide-websocket", "/test2")
.setHandshakeHandler(userHandleShake())
.setAllowedOriginPatterns("*")
.withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// to enable a simple memory-based message broker to carry
// the greeting messages back to the client on destinations prefixed with "/topic".
// broker广播器,定义一个广播器前缀,前端(客户端)可以向指定的channel发起订阅,当后端通过@sendTo 或者convertAndSend 可以向指定
//通道发消息,订阅了该通道的客户端可以收到消息
registry.enableSimpleBroker("/topic", "/chat");
// designates the "/app" prefix for messages that are
// bound for @MessageMapping-annotated methods.
// This prefix will be used to define all the message mappings;
// ws接口定义前缀,后端定义了接口,使用@MessageMapping 前端可以请求该接口并传数据
// 总结 broker可以用来向客户端发送数据,destination可以用来向服务器发送数据 ,区别在于,客户端得先发起订阅
registry.setApplicationDestinationPrefixes("/app");
}
@Bean
public UserHandleShake userHandleShake() {
return new UserHandleShake();
}
}
重写DefaultHandshakeHandler的determineUser方法,给每个连接session注入principal用户信息。
package com.tinet.websocket.config;
import com.tinet.websocket.pojo.UserInfo;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
import java.util.Map;
/**
* @author dengsx
* @create 2021/07/22
**/
public class UserHandleShake extends DefaultHandshakeHandler {
@Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
System.out.println("shake");
UserInfo info = new UserInfo();
info.setUserName(servletRequest.getParameter("userName"));
return info;
}
}
package com.tinet.websocket.pojo;
import java.security.Principal;
/**
* @author dengsx
* @create 2021/07/22
**/
public class UserInfo implements Principal {
private String userName;
@Override
public String getName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
写一个简单的用来聊天的controller
package com.tinet.websocket.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import java.util.Map;
/**
* @author dengsx
* @create 2021/07/19
**/
@MessageMapping("/chat")
@Controller
public class WsController {
@Autowired
private SimpMessagingTemplate template;
@MessageMapping("/message")
public void testWs(Map<String, Object> params) {
template.convertAndSendToUser(String.valueOf(params.get("userName")), "/chat", params.getOrDefault("content", "内容提取失败,我是默认消息"));
}
}
后端代码就已经完成了
<template>
<div>
<el-button :disabled="disable" @click="openSocket('user1')" style="width: 200px">创建用户1连接el-button>
<el-button :disabled="disable" @click="openSocket('user2')" style="width: 200px">创建用户2连接el-button>
<el-input v-model="content" style="width: 200px">el-input>
<el-button @click="sendMessage1()" style="width: 200px">发送消息给用户1el-button>
<el-input v-model="content3" style="width: 200px">el-input>
<el-button @click="sendMessage2()" style="width: 200px">发送消息给用户2el-button>
<el-input v-model="content1" style="width: 200px">xxxel-input>
<el-button @click="sendNoAppMsg()" style="width: 200px">发送无app消息el-button>
div>
template>
<script>
import SockJS from 'sockjs-client'
import Stomp from 'stompjs'
export default {
name: "TestWebsocket",
data(){
return {
stompClient:'',
content:"",
content1:"",
content3:"",
disable: false,
};
},
created(){
},
methods:{
openSocket(name){
let socket = new SockJS('http://localhost/test2?userName='+name);
// 获取STOMP子协议的客户端对象
this.stompClient = Stomp.over(socket);
this.stompClient.connect({
},()=>{
this.disable = true;
this.$message({
showClose: true, message: "连接成功", type: 'success'});
// 订阅主题
this.stompClient.subscribe("/user/chat",(msg)=>{
console.log(msg);
this.$message({
showClose: true, message: msg.body, type: 'success'});
})
// 订阅无app主题,
this.stompClient.subscribe("/topic1/noapp",(msg)=>{
console.log(msg);
this.$message({
showClose: true, message: JSON.parse(msg.body).message, type: 'success'});
})
})
},
sendMessage1(){
let obj = {
userName:'user1',
content:this.content
}
this.stompClient.send("/app/chat/message",{
},JSON.stringify(obj))
this.content = "";
},
sendMessage2(){
let obj = {
userName:'user2',
content:this.content3
}
this.stompClient.send("/app/chat/message",{
},JSON.stringify(obj))
this.content = "";
},
sendNoAppMsg(){
let obj = {
userId:'dsx',
message:this.content1
}
this.stompClient.send("/topic1/noapp",{
},JSON.stringify(obj))
this.content1 = "";
}
}
}
script>
<style scoped>
style>
前端订阅主题/user/chat,用于接受消息
前端向/app/chat/message 发送消息,传入指定userName参数,后端接受到消息后会将消息转发给指定的userName。