Http只能由客户端发起请求,服务端返回请求,这就导致如果服务端有新的消息需要发送给客户端就比较麻烦,所以websocket就应运而生了。下面是springBoot整合websocket,实现服务端推送消息到客户端的一个小demo,这里使用的是定时任务的方式来模拟这种推送,实际使用中,可以根据情况主动推送。
1.创建maven工程,添加如下依赖(使用的是springBoot 2.2.2.RELEASE):
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.44</version>
</dependency>
<!-- Hutool超级工具类 http://hutool.mydoc.io/ -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.3.2</version>
</dependency>
</dependencies>
2.websocket服务端:
@ServerEndpoint("/ws/{userId}")
@Component
public class WebSocketServer {
/**concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。*/
private static ConcurrentHashMap<String,WebSocketServer> webSocketMap = new ConcurrentHashMap<String,WebSocketServer>();
/**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
private Session session;
/**接收userId*/
private String userId="";
/**
* 连接建立成功调用的方法*/
@OnOpen
public void onOpen(Session session,@PathParam("userId") String userId) {
this.session = session;
this.userId=userId;
if(webSocketMap.containsKey(userId)){
webSocketMap.remove(userId);
webSocketMap.put(userId,this);
}else{
webSocketMap.put(userId,this);
}
System.out.println("当前连接用户:"+userId);
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
if(webSocketMap.containsKey(userId)){
webSocketMap.remove(userId);
}
System.out.println("用户 "+userId+" 退出:");
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("收到用户消息:"+userId+",报文:"+message);
if(StrUtil.isNotBlank(message)){
try {
//解析发送的报文
JSONObject jsonObject = JSON.parseObject(message);
//追加发送人(防止串改)
jsonObject.put("fromUserId",this.userId);
String toUserId=jsonObject.getString("toUserId");
//传送给对应toUserId用户的websocket
if(StrUtil.isNotBlank(toUserId) && webSocketMap.containsKey(toUserId)){
webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString());
}else{
webSocketMap.get(this.userId).sendMessage("请求的userId:"+toUserId+"不在该服务器上");
System.out.println("请求的userId:"+toUserId+"不在该服务器上");
}
}catch (Exception e){
e.printStackTrace();
}
}
}
/**
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("用户错误:"+this.userId+",原因:"+error.getMessage());
error.printStackTrace();
}
/**
* 实现服务器主动推送
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 发送自定义消息
* */
public static void sendInfo(String message,String userId) throws IOException {
System.out.println("发送消息到:"+userId+",报文:"+message);
if(StrUtil.isNotBlank(userId) && webSocketMap.containsKey(userId)){
webSocketMap.get(userId).sendMessage(message);
}else{
System.out.println("用户"+userId+",不在线!");
}
}
public static ConcurrentHashMap<String, WebSocketServer> getWebSocketMap() {
return webSocketMap;
}
public static void setWebSocketMap(ConcurrentHashMap<String, WebSocketServer> webSocketMap) {
WebSocketServer.webSocketMap = webSocketMap;
}
}
3.websocket客户端,在resources目录下,创建static目录,在创建一个文件test.html,其内容如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>websocket通讯</title>
</head>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
var socket;
//开启连接
function openSocket() {
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else{
if (!$("#fromUserId").val()){
alert("请输入用户名");
return;
}
console.log("您的浏览器支持WebSocket");
//实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接
//等同于socket = new WebSocket("ws://localhost:8888/xxxx/im/25");
//var socketUrl="${request.contextPath}/im/"+$("#userId").val();
var socketUrl="http://localhost:8080/ws/"+$("#fromUserId").val();
socketUrl=socketUrl.replace("https","ws").replace("http","ws");
console.log(socketUrl);
if(socket!=null){
socket.close();
socket=null;
}
socket = new WebSocket(socketUrl);
//打开事件
socket.onopen = function() {
console.log("websocket已打开");
$("#allContent")
.append("<div>" + "收到消息:已建立连接,"+new Date() + "</div>");
};
//获得消息事件
socket.onmessage = function(msg) {
$("#allContent")
.append("<div>" + "收到消息" +":"+msg.data + "</div>");
console.log(msg.data);
};
//关闭事件
socket.onclose = function() {
$("#allContent")
.append("<div>" + "收到消息:websocket已关闭,"+new Date() + "</div>");
console.log("websocket已关闭");
};
//发生了错误事件
socket.onerror = function() {
$("#allContent")
.append("<div>" + "收到消息:websocket连接发生了错误,"+new Date() + "</div>");
console.log("websocket发生了错误");
}
}
}
// 关闭连接
function closeSocket() {
if (socket){
socket.disconnect();
}
}
// 发送消息
function sendMessage() {
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else {
// 判断是否已建立连接
if (socket){
socket.send('{"toUserId":"'+$("#toUserId").val()+'","contentText":"'+$("#contentText").val()+'"}');
}else {
alert("请先建立连接");
}
}
}
</script>
<body>
【请输入用户名】:<input id="fromUserId" name="fromUserId" type="text" value="admin"/>
【目标用户名】:<input id="toUserId" name="toUserId" type="text" value="qxf"/>
<button onclick="openSocket()" style="color:red;">开启连接</button>
<button onclick="closeSocket()" style="color:red;">关闭连接</button>
<button onclick="sendMessage()" style="color:blue;">发送消息</button>
【内容】:<input id="contentText" name="contentText" type="text" value="hello websocket">
<div id="allContent">
</div>
</body>
</html>
主要就是那几个监听事件,比如
onmessage,表示收到消息时的回调
4.定时任务模式主动推送:
@Component
public class SendMsgToClient {
//从第10秒开始,每隔5秒发送一次
@Scheduled(cron="10/5 * * * * ?")
public void sendMsg() {
try {
ConcurrentHashMap<String, WebSocketServer> webSocketMap = WebSocketServer.getWebSocketMap();
ConcurrentHashMap.KeySetView<String, WebSocketServer> userIds = webSocketMap.keySet();
if (userIds.size() <= 0){
System.out.println("当前没有用户连接,不发送消息");
return;
}
String toUserId = null;
int count = new Random(System.currentTimeMillis()).nextInt(userIds.size());
//取一个发送消息
for (int i = 0; i < userIds.size();i++){
Iterator<String> iterator = userIds.iterator();
if (iterator.hasNext()){
if (i == count){
toUserId = iterator.next();
}else {
iterator.next();
}
}
}
if (StrUtil.isNotBlank(toUserId)){
WebSocketServer.sendInfo("这是服务端主动推送的消息:"+new Date(),toUserId);
}else {
System.out.println("当前没有用户连接,不发送消息");
}
}catch (IOException e){
System.out.println("定时推送消息失败...");
}
}
}
5.启动类添加如下配置:
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}