Websocket实现主要依赖三个方法:
1.OnOpen: server与client建立连接,进行握手,首先执行的就是onOpen方法;
2.OnMessage:server与client实时通讯,参数就是传递的消息;另外我们可以通过maxMessageSize设置消息的大小;
3.OnClose:断开连接执行onClose方法,server端释放连接。
springboot集成websocket非常简单,只需要简单几步:
1.在pom文件里加入websocket jar包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2.创建websocket的config类:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* 不加这个config会报错
*/
@Configuration
public class WebsocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
websocketConfig类必须要创建,不然启动会抛异常。
3.创建websocket服务:
package com.example.demo.websocket;
import com.alibaba.fastjson.JSON;
import com.example.demo.enums.MsgSendType;
import com.example.demo.model.vo.WebsocketMsgVO;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* <b>Title:</b><i>TODO</i></p>
* <p> Desc: DAO</p>
* <p>source folder:com.vbim.datacenter.entity</p>
* <p>Copyright:Copyright(c)2018</p>
* <p>Company:vanke</p>
* <p>Create Date:2019-8-9 16:30</p>
* <p>Modified By:v-zhoukq01</p>
* <p>Modified Date:2019-8-9 16:30</p>
*
* @author <a href="v-zhoukq01@vanke.com" title="邮箱地址">v-zhoukq01</a>
* @version Version 0.1
*/
@Component
@ServerEndpoint("/websocket/{userAccount}")
public class WebsocketServer {
private final Logger logger = LoggerFactory.getLogger(WebsocketServer.class);
// 心跳检测,发送ping命令
private final static String PING = "PING";
// ConcurrentHashMap线程安全,用来存放客户端的WebsocketServer对象,userAccount做Key
private static Map<String, WebsocketServer> map = new ConcurrentHashMap<>();
// 用来统计连接数量(在线人数)
private static volatile int onlineCount = 0;
// 在线用户Key
private String userAccount;
// 客户端建立连接,创建一个session,通过session可以将消息发到对应的客户端
private Session session;
@OnOpen
public void onOpen(@PathParam("userAccount") String userAccount, Session session) {
this.userAccount = userAccount;
this.session = session;
if (!map.containsKey(userAccount)) {
map.put(userAccount, this);
addOnlineCount();
logger.info(userAccount + "建立连接!");
}
}
@OnMessage(maxMessageSize=1000)
public void onMessage(String message) {
WebsocketMsgVO websocketMsgVO = null;
if (StringUtils.isBlank(message)) {
logger.info("message is null");
return;
}
if (message.equals(PING)) {
sendOne(userAccount, "PONE");
logger.info("心跳检测");
return;
}
try {
websocketMsgVO = JSON.parseObject(message, WebsocketMsgVO.class);
} catch (Exception e) {
logger.error("消息格式错误", e);
sendOne(userAccount, "消息格式错误");
return;
}
String fromUser = websocketMsgVO.getFromUser();
String toUser = websocketMsgVO.getToUser();
String sendMsg = websocketMsgVO.getMessage();
String sendType = websocketMsgVO.getSendType();
if (sendType.equals(MsgSendType.TOALL.getKey())) {
logger.info("服务端收到消息," + fromUser + "群发消息");
sendAll(sendMsg);
} else {
logger.info("服务端收到消息," + fromUser + "发给" + toUser);
sendOne(toUser, sendMsg);
}
}
private void sendAll(String msg) {
try {
for (String client : map.keySet()) {
map.get(client).session.getBasicRemote().sendText(msg);
}
} catch (IOException e) {
logger.error("群发消息异常", e);
}
}
private void sendOne(String toUser, String msg) {
try {
map.get(toUser).session.getBasicRemote().sendText(msg);
} catch (IOException e) {
logger.error("发送消息异常", e);
}
}
@OnError
public void onError(Session session, Throwable throwable) {
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
logger.error(userAccount + "客户端发生异常!", throwable);
}
@OnClose
public void onClose() {
if (StringUtils.isNotBlank(userAccount) && map.containsKey(userAccount)) {
map.remove(userAccount);
subOnlineCount();
}
}
/**
* 方法用途: 有客户端建立连接,在线人数++<br/>
* 操作步骤: TODO<br/>
* ${tags}
*/
public static synchronized void addOnlineCount() {
WebsocketServer.onlineCount++;
}
/**
* 方法用途: 有客户端退出连接,在线人数--<br/>
* 操作步骤: TODO<br/>
* ${tags}
*/
public static synchronized void subOnlineCount() {
WebsocketServer.onlineCount--;
}
}
4.前端页面client代码:
<!DOCTYPE HTML>
<html>
<head>
<title>My WebSocket</title>
</head>
<body>
Welcome<br/>
<input id="text" type="text" /><button onclick="send()">Send</button> <button onclick="closeWebSocket()">Close</button>
<div id="message">
</div>
</body>
<script type="text/javascript">
var websocket = null;
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
websocket = new WebSocket("ws://localhost:80/websocket/app");
}
else{
alert('Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//连接成功建立的回调方法
websocket.onopen = function(event){
setMessageInnerHTML("open");
}
//接收到消息的回调方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function(){
setMessageInnerHTML("close");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
websocket.close();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//关闭连接
function closeWebSocket(){
websocket.close();
}
//发送消息
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</html>
5.打开HTML页面进行测试,得到结果如下:
我这里是一个json字符串(对应websocketMsgVO属性字段),完整信息如下:
{
"fromUser": "test",
"messageId":1,
"title":"test title",
"message":"hello world",
"toUser":"app",
"sendType":"TOALL"
}
偶然间看到一篇关于websocket分布式部署文章,写得非常不错,有兴趣的伙伴可以参考:websocket集群