直接上代码
首先加入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
然后将此类注入到spring容器中:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
*
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
建立连接,关闭连接,发送消息 主要代码如下:
/**
* Copyright [2016-2017] [yadong.zhang]
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.yudi.websocket;
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.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
/**
*/
@ServerEndpoint(value = "/websocket/{userId}")
@Component
public class MyWebSocket {
/**
* concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
*/
// private static CopyOnWriteArraySet<MyWebSocket> webSocketSet = new CopyOnWriteArraySet<MyWebSocket>();
/**3
* 为了区分不同的客户端改用ConcurrentHashMap,每次客户端建立一个连接,就会存入一条记录,key为客户端的userId,value为socket连接实例
* 这样可以通过 userId来调用其指定的连接进行操作。
*/
private static ConcurrentHashMap<String,MyWebSocket> webSocketMap = new ConcurrentHashMap<String,MyWebSocket>();
/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;
/**
* 群发自定义消息
*/
public static void sendInfo(String message) {
webSocketMap.forEach((k,v)->{
try {
v.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
});
}
/**
* 向指定userId发送消息
*/
public static void sendInfo(String userId,String message) {
webSocketMap.forEach((k,v)->{
try {
if (k.equals(userId)){
v.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
/**
* 连接建立成功调用的方法
*
*/
@OnOpen
public void onOpen(@PathParam("userId")String userId, Session session) {
System.out.println(userId);
this.session = session;
webSocketMap.put(userId,this);
List list = new ArrayList();
webSocketMap.forEach((k,v)->list.add(k));
// addOnlineCount();
System.out.println("有新连接加入!当前在线人数为" + webSocketMap.size());
sendInfo("当前共有" + webSocketMap.size() + " 位用户在线,userId是:" +list.toString());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(@PathParam("userId")String userId) {
webSocketMap.remove(userId);
sendInfo("当前共有" + webSocketMap.size() + " 位用户在线");
System.out.println("有一连接关闭!当前在线人数为" + webSocketMap.size());
}
/**
* 收到客户端消息后调用的方法
*
* @param message
* 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("来自客户端的消息:" + message);
//群发消息
sendInfo(message);
}
/**
* 发生错误时调用
*/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("发生错误");
error.printStackTrace();
}
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
}
配置文件添加freemarker相关配置:
freemarker:
allow-request-override: false
allow-session-override: false
cache: false
charset: UTF-8
check-template-location: true
content-type : text/html
enabled : true
expose-request-attributes : false
expose-session-attributes: false
expose-spring-macro-helpers: true
prefer-file-system-access: true
suffix: .ftl
template-loader-path: classpath:/templates/
order: 1
settings:
template_update_delay: 0
default_encoding: UTF-8
classic_compatible: true
客户端代码:
<!DOCTYPE HTML>
<html>
<head>
<title>WebSocket测试 | 聊天小程序</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/4.0.0-beta/css/bootstrap.min.css">
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/bootstrap/4.0.0-beta/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col">
<form>
<div class="form-group">
<label for="comment">聊天框:</label> <textarea class="form-control" rows="5" id="text">你好啊</textarea>
</div>
<div class="form-group">
<button onclick="send()" type="button" class="btn btn-primary">发送</button>
<button onclick="closeWebSocket()" type="button" class="btn btn-danger">关闭</button>
</div>
<div>
<ul class="list-group" id="message"></ul>
</div>
</form>
</div>
</div>
</div>
</body>
<script type="text/javascript">
var websocket = null;
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8100/websocket/${userId}");
//连接发生错误的回调方法
websocket.onerror = function () {
setMessageInnerHTML("与服务器连接失败...");
};
//连接成功建立的回调方法
websocket.onopen = function (event) {
setMessageInnerHTML("与服务器连接成功...");
}
//接收到消息的回调方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function () {
setMessageInnerHTML("已关闭当前链接");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
websocket.close();
}
} else {
alert('Not support websocket');
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
$("#message").append("<li class=\"list-group-item\">" + innerHTML + "</li>");
}
//关闭连接
function closeWebSocket() {
websocket.close();
}
//发送消息
function send() {
websocket.send($("#text").val());
}
</script>
</html>
测试用controller:
package com.yudi.websocket.controller;
import com.yudi.websocket.MyWebSocket;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Controller
@RequestMapping("/websocket")
public class WebSocketController {
/**
* 给所有人发消息/指定userId发送消息
* @param message
* @return
*/
@ResponseBody
@RequestMapping(value = "/sendMessage", method = RequestMethod.GET)
public ResponseEntity<?> sendMessage(String userId,String message) {
if (StringUtils.isEmpty(userId)){
//向全部人发送消息
MyWebSocket.sendInfo(message);
}else {
//向指定userId发送消息
MyWebSocket.sendInfo(userId,message);
}
return new ResponseEntity(null,HttpStatus.OK);
}
/**
* 登入聊天室
* @param userId 用户id,每个人唯一,每一个userId会建立一个websocket连接
* @param request
* @param response
* @return
*/
@RequestMapping("/index")
public String index(String userId, HttpServletRequest request, HttpServletResponse response) {
request.setAttribute("userId",userId);
return "index";
}
}
效果图:
向指定userId发送消息:http://localhost:8100/websocket/sendMessage?message=xxxx&userId=2