添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-log</artifactId>
<version>4.1.17</version>
</dependency>
配置 websocket
创建一个 WebSocketConfig 以启用 WebSocket 的支持
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
ws 服务器配置
以下, 为 websocket 添加一个入口地址, 并实现 @OnOpen
@onClose
@onMessage
已完成对 ws 的监听
@ServerEndpoint("/websocket/{sid}")
@Component
public class WebSocketServer {
static Log log = LogFactory.get(WebSocketServer.class);
// 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
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;
// 群发消息
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage("有新用户加入: " + sid);
} catch (IOException e) {
e.printStackTrace();
}
}
try {
sendMessage("连接成功");
} catch (IOException e) {
log.error("websocket IO异常");
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this); // 从set中删除
subOnlineCount(); // 在线数减1
log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
// 群发消息
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(sid + "离开群聊");
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
* */
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到来自窗口"+sid+"的信息:"+message);
// 群发消息
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(sid + "说: " + 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 send(String message, @PathParam("from") String from, @PathParam("to") String to) throws IOException {
log.info(from + "发送给" + to + ", 内容: " + message);
for (WebSocketServer item : webSocketSet) {
try {
//这里可以设定只推送给这个sid的,为null则全部推送
if(to==null) {
item.sendMessage(message);
}else if(item.sid.equals(to)){
item.sendMessage(from + "对你说: " + 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
@RequestMapping("/checkcenter")
public class CheckCenterController {
// 页面请求
@GetMapping("/socket/{cid}")
public String index(ModelMap map, @PathVariable String cid) {
map.addAttribute("cid", cid);
return "socket";
}
// 单发
@ResponseBody
@RequestMapping("/socket/push/{from}/{to}")
public Result pushToOne(@PathVariable String from, @PathVariable String to, String message) {
System.out.println("one");
try {
WebSocketServer.send(message, from, to);
} catch (IOException e) {
e.printStackTrace();
return Result.error(from + " : " + to + "#" + e.getMessage());
}
return Result.ok("发送成功");
}
// 群发
@ResponseBody
@RequestMapping("/socket/push/{from}")
public Result pushToAll(@PathVariable String from, String message) {
System.out.println("all");
try {
WebSocketServer.send(message, from, null);
} catch (IOException e) {
e.printStackTrace();
return Result.error(from + " : " + null + "#" + e.getMessage());
}
return Result.ok("发送成功");
}
}
视图的实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title th:text="${cid}"></title>
<script src="//cdn.bootcss.com/vue/2.5.21/vue.min.js"></script>
<script src="//cdn.bootcss.com/axios/0.19.0-beta.1/axios.min.js"></script>
</head>
<body>
<div id="app">
<div>
<h1 v-if="cid">欢迎: {{cid}}</h1>
<h1 v-else>您的浏览器不支持WebSocket</h1>
</div>
<ul>
<li v-for="msg in messages">{{msg}}</li>
</ul>
<div>
<p>消息内容</p>
<textarea v-model="info" id="" cols="30" rows="10"></textarea>
<p>
<button @click="sendAll">群发消息</button>
</p>
<p>
<span>给</span>
<input type="text" v-model="toCid"/>
<button @click="sendOne">发送消息</button>
</p>
</div>
</div>
<script>
const app = new Vue({
el: '#app',
data () {
return {
cid: [[${cid}]], // 获取控制器传来的值, 当前用户的cid
messages: [], // 要发送的消息
info: '',
toCid: '', // 接收用户的cid
}
},
created () {
if(typeof(WebSocket) === "undefined") {
console.log("您的浏览器不支持WebSocket");
} else {
console.log("您的浏览器支持WebSocket");
let socket = new WebSocket(`ws://localhost:8080/websocket/${this.cid}`)
socket.onopen = function() {
console.log("Socket 已打开")
};
socket.onmessage = msg => {
console.log(msg.data)
this.messages.push(msg.data)
};
socket.onclose = () => {
console.log("Socket已关闭")
};
socket.onerror = () => {
alert("Socket发生了错误")
}
}
},
methods: {
sendAll () {
axios
.get(`/checkcenter/socket/push/${this.cid}?message=${this.info}`)
.then((response) => {
this.server = response.data
this.info = ""
})
},
sendOne () {
axios
.get(`/checkcenter/socket/push/${this.cid}/${this.toCid}?message=${this.info}`)
.then((response) => {
this.server = response.data
this.info = ""
})
}
}
})
</script>
</body>
</html>
浏览器分别访问:
http://localhost:8080/checkcenter/socket/1
http://localhost:8080/checkcenter/socket/2
http://localhost:8080/checkcenter/socket/3
实现效果: