<!-- WebSocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
如果是Java项目,引入以下jar包
红框为必须:
javax.websocket-api-1.0.jar
spring-context-4.2.5.RELEASE.jar
spring-websocket-4.2.5.RELEASE.jar
注意引入jar包和项目所用的spring版本保持一致,项目中用4.2.5我这demo也用这个
2、编写配置文件
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
/**
* 注入ServerEndpointExporter,
* 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
2、编写主要的实现类
import com.alibaba.fastjson.JSON;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.entity.SocketMessage;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@ServerEndpoint("/webSocket/{userId}")
@Component
public class WebSocketServer {
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static AtomicInteger onlineNum = new AtomicInteger();
//concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。
private static ConcurrentHashMap<String, Session> sessionPools = new ConcurrentHashMap<>();
//发送消息
public void sendMessage(Session session, String message) throws IOException {
if(session != null){
synchronized (session) {
session.getBasicRemote().sendText(message);
}
}
}
//给指定用户发送信息
public Integer sendInfo(String userId, String message){
Session session = sessionPools.get(userId);
try {
sendMessage(session, message);
return 1;
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
// 群发消息
public void broadcast(String message){
for (Session session: sessionPools.values()) {
try {
sendMessage(session, message);
} catch(Exception e){
e.printStackTrace();
continue;
}
}
}
//建立连接成功调用
@OnOpen
public void onOpen(Session session, @PathParam(value = "userId") String userId){
sessionPools.put(userId, session);
addOnlineCount();
// 广播上线消息
SocketMessage msg = new SocketMessage();
msg.setDate(new Date());
msg.setTo("0");
msg.setText(userId);
broadcast(JSON.toJSONString(msg,true));
}
//关闭连接时调用
@OnClose
public void onClose(@PathParam(value = "userId") String userId){
sessionPools.remove(userId);
subOnlineCount();
// 广播下线消息
SocketMessage msg = new SocketMessage();
msg.setDate(new Date());
msg.setTo("-2");
msg.setText(userId);
broadcast(JSON.toJSONString(msg,true));
}
//收到客户端信息后,根据接收人的userId把消息推下去或者群发
// to=-1群发消息
@OnMessage
public void onMessage(String message) throws IOException{
SocketMessage msg= JSON.parseObject(message, SocketMessage.class);
msg.setDate(new Date());
if (msg.getTo().equals("-1")) {
broadcast(JSON.toJSONString(msg,true));
} else {
sendInfo(msg.getTo(), JSON.toJSONString(msg,true));
}
}
//错误时调用
@OnError
public void onError(Session session, Throwable throwable){
throwable.printStackTrace();
}
public static void addOnlineCount(){
onlineNum.incrementAndGet();
}
public static void subOnlineCount() {
onlineNum.decrementAndGet();
}
public static AtomicInteger getOnlineNumber() {
return onlineNum;
}
public static ConcurrentHashMap<String, Session> getSessionPools() {
return sessionPools;
}
}
引入了额外的jar包也是为了这里使用的,其中
这个实体类是为了方便发送消息添加的,也可删掉这块相关的代码,不影响,我这里也贴出,方便大家使用
import com.alibaba.fastjson.annotation.JSONField;
import java.util.Date;
public class SocketMessage {
//发送者name
public String from;
//接收者name
public String to;
//发送的文本
public String text;
//发送时间
@JSONField(format="yyyy-MM-dd HH:mm:ss")
public Date date;
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
@Override
public String toString() {
return "SocketMessage{" +
"from='" + from + '\'' +
", to='" + to + '\'' +
", text='" + text + '\'' +
", date=" + date +
'}';
}
}
3、编写测试接口
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.service.WebSocketServer;
@Controller
@RequestMapping("/web")
public class WebSocketController {
@Autowired
WebSocketServer webSocketServer;
/**
* 发送单人消息
*
* @param userId 接收人用户ID
* @param mes 消息
* @return 1 发送成功 0 发送失败
*/
@RequestMapping("/sendInfo")
@ResponseBody
public Integer sendInfo(@RequestParam("userId") String userId, @RequestParam("mes") String mes) {
Integer num = webSocketServer.sendInfo(userId, mes);
return num;
}
/**
* 群发消息
*
* @param mes 消息
* @return 1 发送成功 0 发送失败
*/
@RequestMapping("/broadcast")
@ResponseBody
public void broadcast(@RequestParam String mes) {
webSocketServer.broadcast(mes);
}
/**
* 测试发送多条
*/
@RequestMapping("/sendInfoFor")
@ResponseBody
public Integer sendInfo() {
for (int i = 0; i < 100; i++) {
webSocketServer.sendInfo(1 + "", i + "");
}
return 1;
}
}
4、简单的html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<title>websocket测试页面</title>
</head>
<body>
<div class="panel panel-default">
<div class="panel-body">
<div class="row">
<div class="col-md-6">
<div class="input-group">
<span class="input-group-addon">ws地址</span>
<input type="text" id="address" class="form-control" placeholder="ws地址"
aria-describedby="basic-addon1" value="ws://localhost:8010/webSocket/admin">
<div class="input-group-btn">
<button class="btn btn-default" type="submit" id="connect">连接</button>
</div>
</div>
</div>
</div>
<div class="row" style="margin-top: 10px;display: none;" id="msg-panel">
<div class="col-md-6">
<div class="input-group">
<span class="input-group-addon">消息</span>
<input type="text" id="msg" class="form-control" placeholder="消息内容" aria-describedby="basic-addon1">
<div class="input-group-btn">
<button class="btn btn-default" type="submit" id="send">发送</button>
</div>
</div>
</div>
</div>
<div class="row" style="margin-top: 10px; padding: 10px;">
<div class="panel panel-default">
<div class="panel-body" id="log" style="height: 450px;overflow-y: auto;">
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous"></script>
<script type="text/javascript">
$(function () {
var _socket;
$("#connect").click(function () {
_socket = new _websocket($("#address").val());
_socket.init();
});
$("#send").click(function () {
var _msg = $("#msg").val();
output("发送消息:" + _msg);
_socket.client.send(JSON.stringify(_msg));
});
});
function output(e) {
var _text = $("#log").html();
$("#log").html(_text + "<br>" + e);
}
function _websocket(address) {
this.address = address;
this.client;
this.init = function () {
if (!window.WebSocket) {
this.websocket = null;
return;
}
var _this = this;
var _client = new window.WebSocket(_this.address);
_client.onopen = function () {
output("websocket打开");
$("#msg-panel").show();
};
_client.onclose = function () {
_this.client = null;
output("websocket关闭");
$("#msg-panel").hide();
};
_client.onmessage = function (evt) {
output(evt.data);
};
_this.client = _client;
};
return this;
}
</script>
</body>
</html>
可以把这里修改成自己项目的访问地址,方便每次访问
5、进行测试
启动项目,启动html页面,点击连接
连接成功会如图所示
打开postman发送请求
这里的1是发送成功之后的返回值,也可根据实际情况来修改
html页面已接受到消息
最后附上项目的结构图,demo项目有点粗糙