这里写自定义目录标题
websocket
WebSocket是一种在单个TCP连接上进行全双工通信的协议。简单点说其实就是浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。例如现在很多网站的站内通信,点赞通知等及时通讯交互都可以用这个实现。具体的技术原理大家找度娘。
SpringBoot 整合websocket
引入jar包
SpringBoot 整合 websocket非常简单,只要引入websocket包即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
配置websocket
@Configuration
@ConditionalOnWebApplication
public class WebSocketConfig {
/**
* ServerEndpointExporter 作用
*
* 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
*
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
消息体定义
websocket的作用主要就是为了客户端和服务端通讯使用,既然是通讯,那么肯定有一些基本的参数,例如发送者、接收者,消息正文,消息发送时间等,这里可以定义一个消息体的对象。
public class WebSocketMsgVO implements Serializable {
private static final long serialVersionUID = 6853050643035639834L;
private String fromId;
private String fromName;
private String toId;
private String toName;
private String createDateTime;
private String content;//消息正文
private String sendMsgType;//发送消息的类型 :open/auto/dialog
private Map<String, Object> data = new HashMap<String, Object>();
}
前面6个字段看名字就知道什么意思了,就不多说了,主要是后面两个字段做个说明。
sendMsgType : 发送消息的类型,例如在浏览器连接上websocket时,服务器主动给浏览器推送“连接成功”提醒的消息(我这里定义成open),再例如会发送一些自动消息(auto),如果是2个客户之间正常的即时通话则定义为dialog。
data : 主要是为了扩展使用,例如不同的功能模块可以有一些特殊的信息,那么就放在这里。
websocket
@Component
@ServerEndpoint("/webSocket/{id}")
public class WebSocketServer {
// 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static AtomicInteger onlineNum = new AtomicInteger();
// concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。
private static ConcurrentHashMap<String, Session> sessionPools = new ConcurrentHashMap<>();
// 建立连接成功调用
@OnOpen
public void onOpen(final Session session, @PathParam(value = "id") final String id) {
session.setMaxIdleTimeout(10 * 60 * 1000);
try {
synchronized (WebSocketServer.sessionPools) {
if (WebSocketServer.sessionPools.containsKey(id)) {
WebSocketServer.sessionPools.remove(id);
} else {
WebSocketServer.addOnlineCount();
}
WebSocketServer.sessionPools.put(id, session);
}
System.out.println(id + "加入webSocket!当前人数为" + WebSocketServer.onlineNum);
final WebSocketMsgVO messageVO = new WebSocketMsgVO();
messageVO.setToId(id);
messageVO.setContent("欢迎" + id + "加入连接!");
messageVO.setSendMsgType("open");
sendMessage(session, HnJsonUtils.jsonToString(messageVO));
} catch (final IOException e) {
e.printStackTrace();
}
}
// 关闭连接时调用
@OnClose
public void onClose(@PathParam(value = "id") final String id) {
synchronized (WebSocketServer.sessionPools) {
if (WebSocketServer.sessionPools.containsKey(id)) {
WebSocketServer.sessionPools.remove(id);
WebSocketServer.subOnlineCount();
}
}
System.out.println(id + "断开webSocket连接!当前人数为" + WebSocketServer.onlineNum);
}
// 收到客户端信息
@OnMessage
public void onMessage(final String message) throws IOException {
System.out.println("收到客户端消息:" + message);
try {
if (!WebSocketServer.sessionPools.isEmpty()) {
final WebSocketMsgVO messageVO = HnJsonUtils.jsonToBean(message, WebSocketMsgVO.class);
if (WebSocketServer.sessionPools.containsKey(messageVO.getToId())) {
sendMessage(WebSocketServer.sessionPools.get(messageVO.getToId()), message);
}
}
} catch (final Exception e) {
e.printStackTrace();
}
}
// 错误时调用
@OnError
public void onError(final Session session, final Throwable throwable) {
System.out.println("发生错误");
throwable.printStackTrace();
}
// 发送消息
private void sendMessage(final Session session, final String message) throws IOException {
if (session != null) {
synchronized (session) {
System.out.println("发送数据:" + message);
session.getBasicRemote().sendText(message);
}
}
}
public static void addOnlineCount() {
WebSocketServer.onlineNum.incrementAndGet();
}
public static void subOnlineCount() {
WebSocketServer.onlineNum.decrementAndGet();
}
}
从代码中的各个方法名称基本就能看出来websocket的工作原理和流程。
1.客户端连接服务器端,触发onOpen()
2.当客户端发送消息,服务器端通过onMessage()接收,并通过sendMessage()推送到相应的接收者浏览器,然后由前端的websocket来接收处理消息。
application.yml
server:
port: 9990
servlet:
context-path: /socketweb
前端socket.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket</title>
</head>
<body>
<h3>hello socket</h3>
<p>【fromId】:<div><input id="fromId" name="fromId" type="text" value="google"></div>
<p>【toId】:<div><input id="toId" name="toId" type="text" value="firefox"></div>
<p>【内容】:<div><input id="content" name="content" type="text" value="hello firefox, 这是个测试的websocket"></div>
<p>【操作】:<input type="button" onclick="openSocket()" value="开启socket" />
<p>【操作】:<input type="button" onclick="sendMessage()"value="发送消息" />
<ul id="cont"></ul>
</body>
<script type="text/javascript">
var socket;
function openSocket() {
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else{
console.log("您的浏览器支持WebSocket");
//实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接
var fromId = document.getElementById('fromId').value;
var socketUrl="ws://127.0.0.1:9990/socketweb/webSocket/"+fromId;
console.log(socketUrl);
if(socket!=null){
socket.close();
socket=null;
}
socket = new WebSocket(socketUrl);
//打开事件
socket.onopen = function() {
console.log("websocket已打开");
//socket.send("这是来自客户端的消息" + location.href + new Date());
};
//获得消息事件
socket.onmessage = function(msg) {
var serverMsg = "收到服务端信息:" + msg.data;
console.log(serverMsg);
var message=JSON.parse(msg.data);
var node=document.createElement("LI");
var textnode=document.createTextNode(message.content);
node.appendChild(textnode);
document.getElementById("cont").appendChild(node);
};
//关闭事件
socket.onclose = function() {
console.log("websocket已关闭");
};
//发生了错误事件
socket.onerror = function() {
console.log("websocket发生了错误");
}
}
}
function sendMessage() {
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else {
// console.log("您的浏览器支持WebSocket");
var fromId = document.getElementById('fromId').value;
var toId = document.getElementById('toId').value;
var content = document.getElementById('content').value;
var jsonMsg={fromId:fromId,toId:toId,content:content,sendMsgType:"dialog"};
console.log(jsonMsg);
socket.send(JSON.stringify(jsonMsg));
}
}
</script>
</html>
效果
思考
以上就是最简单的实现websocket即时通讯的方法,但这里只是实现了单个通道下即时通讯的功能,在正式的项目中可能还是有问题的,例如正式环境下是有多个服务器做负载均衡的,这个时候就会导致不同的客户端在与服务器握手时会连接道不同的服务器上,这样也就导致发送到服务器上的消息在推送时找不到对应的客户端,那么就需要换另外的做法了。这块下次有时间在写了 。