这里只实现服务器端WebScket到消息中间件RabbitMQ部分,前端代码不会。前端跟中间件交互部分的功能(向中间件发送消息、从中间件读取消息)用接口代替
实现思路
前端发起请求与服务器建立连接 ->WebSocket发送消息到RabbitMQ队列中->WebSocket监听Rabbit消息队列中的消息
项目结构
配置RabbitMQ
#配置rabbitmq的基本信息 : ip 端口 账号和密码
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
写一个RabbitMQ的配置类
package com.example.demo.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author lyd
* @Description: 配置RabbitMQ,创建一个交换机、一个队列
* @date 14:46
*/
@Configuration
public class AppConfig {
public static final String ROUTING_KEY = "rabbit.msg";
public static final String DIRECT_EXCHANGE = "directexchange";
public static final String DIRECT_QUEUE = "directqueue";
@Bean
public Queue directQueue() {
return new Queue(DIRECT_QUEUE);
}
@Bean
public DirectExchange directExchange() {
return new DirectExchange(DIRECT_EXCHANGE);
}
@Bean
public Binding binding() {
return BindingBuilder.bind(directQueue()).to(directExchange()).with(ROUTING_KEY);
}
}
再写一个WebSocket启动类,开启WebSocket支持
package com.example.demo.config;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @author lyd
* @Description: 开启WebSocket支持
* @date 15:43
*/
@Configuration
@Component
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
实现WebSocket的核心类,通过@OnOpen
、@OnClose
、@OnMessag
、@OnError
四个注解实现四个核心的方法
package com.example.demo.websocket;
import com.example.demo.direct.DirectSender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
@ServerEndpoint(value = "/webSocket")
@Component
public class WebSocketServer {
/**
* 存放每个客户端对应的WebSocket对象
*/
public static CopyOnWriteArraySet<WebSocketServer> webSockets = new CopyOnWriteArraySet<WebSocketServer>();
@Autowired
static DirectSender directSender;
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen() throws InterruptedException, IOException {
webSockets.add(this);
System.out.println("有新用户加入");
}
@OnClose
public void onClose() throws IOException {
webSockets.remove(this);
System.out.println("有用户离开");
}
/**
* 收到客户端消息后调用的方法
*/
@OnMessage
public void onMessage(String msg) throws InterruptedException {
System.out.println("从客户端接受的消息: " + msg);
}
@OnError
public void onError(Throwable error) {
error.printStackTrace();
}
}
定义一个消息发送类,实现一个通过WebSocket向队列发送消息的方法
package com.example.demo.direct;
import com.example.demo.config.AppConfig;
import com.example.demo.websocket.WebSocketServer;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author lyd
* @Description:
* @date 14:50
*/
@Component
public class DirectSender {
@Autowired
RabbitTemplate rabbitTemplate;
/**
* 向RabbitMQ队列中发送消息,方便后面客户端可以从队列中读取该消息
*
* 也可以用来代替客户端向队列中发送消息,我不会写前端连接rabbitmq的代码,就用这个接口代替了。或者在RabbitMQ的管理面板中手动输入数据
* @param msg
*/
public void sendDirect(String msg) {
for (WebSocketServer webSocketServer : WebSocketServer.webSockets) {
rabbitTemplate.convertAndSend(AppConfig.DIRECT_EXCHANGE, AppConfig.ROUTING_KEY, msg +" ("+webSocketServer.toString()+")");
}
}
}
定义一个消息接收类,实现一个通过WebSocket从队列中读取消息的方法
package com.example.demo.direct;
import com.example.demo.config.AppConfig;
import com.example.demo.websocket.WebSocketServer;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author lyd
* @Description: 监听RabbitMQ中队列消息
* @date 15:16
*/
@Component
public class DirectReceive {
/**
* 监听客户端发送到RabbitMQ队列中的消息,并把消息发送给WebSocketServer
* @param msg
* @throws InterruptedException
* @throws IOException
*/
@RabbitListener(queues = AppConfig.DIRECT_QUEUE)
@RabbitHandler
public void processToPre(String msg) throws InterruptedException, IOException {
Thread.sleep(500);
for (WebSocketServer webSocketServer : WebSocketServer.webSockets) {
System.out.println("WebSocket从队列中取出客户端("+webSocketServer.toString()+")发送过来的消息:");
webSocketServer.onMessage(msg);
}
}
}
再写一个发送消息的接口,方便测试用
package com.example.demo.controller;
import com.example.demo.direct.DirectSender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/**
* @author lyd
* @Description:
* @date 14:49
*/
@RestController
public class MessageController {
@Autowired
DirectSender directSender;
/**
* 接口:
* 调用向队列中发送消息的方法
*/
@RequestMapping("senderMsg")
@ResponseBody
public void senderMsg() {
directSender.sendDirect("我是向队列中存储的消息");
}
}
最后写一个html页面,用于前端跟客户端建立连接
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
</body>
<script type="text/javascript">
var websocket = null;
if ('WebSocket' in window) {
websocket = new WebSocket('ws://localhost:8080/webSocket');
} else {
alert('该浏览器不支持websocket');
}
websocket.onopen = function (e) {
console.log('websocket建立连接');
websocket.send('websocket建立连接');
}
websocket.onclose = function (e) {
console.log('websocket关闭连接');
}
websocket.onmessage = function (e) {
console.log(e, 'websocket收到消息');
document.getElementById('msgs').innerHTML = document.getElementById('msgs').innerHTML + '<br/>' + e.data;
}
websocket.onerror = function (event) {
console.log('websocket通信发生错误');
}
window.onbeforeunload = function (event) {
websocket.close();
}
</script>
</html>
测试
1)启动项目后访问http://localhost:8080/chat.html
,客户端跟服务端建立连接
2) 调用http://localhost:8080/senderMsg
,WebSocket向RabbitMQ队列中发送消息,这一步也同时实现了服务端监听队列消息并把消息发给WebSocket