【简介】
近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通信,扩展了浏览器与服务端的通信功能,使服务端也能主动向客户端发送数据。
首先,博主谈一下,Http协议缺陷:通信只能由客户端发起。这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就很麻烦。我们只能通过轮询的方式,每隔一段时间,就发出一个询问,获取服务器有无最新的信息。最典型的就是聊天室。轮询的效率低,且非常浪费资源。
现在来谈谈应时而生的WebSocket。WebSocket最大的特点就是:服务器可以主动的向客户端推送消息,客户端也可以主动向服务器发送信息。
WebSocket的特点:
- 建立在TCP协议上,服务器端的实现比较容易。
- 与HTTP协议有着良好的兼容性,默认端口也是80和443,并且握手阶段采用HTTP协议。
- 数据格式比较轻量,性能开销小,通信高效。
- 可以发送文本以及二进制数据。
- 没有同源限制,客户端可以与任意服务器通信。
- 协议标识符位ws(如果加密则为wss),服务器网址就是URL。
【前后端连接的URL】
ws://localhost:8080/ctm02vmas/webSocket
【前端代码】
<%@ page language="java" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>Java后端WebSocket的Tomcat实现</title>
</head>
<body>
Welcome<br/><input id="text" type="text"/>
<button onclick="send()">发送消息</button>
<hr/>
<button onclick="closeWebSocket()">关闭WebSocket连接</button>
<hr/>
<div id="message"></div>
</body>
<script type="text/javascript">
var websocket = null;
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/websocket");
}
else {
alert('当前浏览器 Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function () {
setMessageInnerHTML("WebSocket连接发生错误");
};
//连接成功建立的回调方法
websocket.onopen = function () {
setMessageInnerHTML("WebSocket连接成功");
}
//接收到消息的回调方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function () {
setMessageInnerHTML("WebSocket连接关闭");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
closeWebSocket();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//关闭WebSocket连接
function closeWebSocket() {
websocket.close();
}
//发送消息
function send() {
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</html>
【后端代码】
import com.hikvision.starfish.core.json.JsonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
@Component
@ServerEndpoint(value = "/webSocket")
public class WebSocketServer {
Logger logger=LoggerFactory.getLogger(WebSocketServer.class);
//记录当前在线连接数
private static int onlineCount=0;
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet=new CopyOnWriteArraySet<>();
private static ConcurrentHashMap<WebSocketServer,String> sessionWithIdMap=new ConcurrentHashMap<>();
private Session session;
@OnOpen
public void onOpen(Session session){
this.session=session;
webSocketSet.add(this);
addOnlineCount();
logger.info("====加入新连接session:{}!当前在线人数为:{}", this.session.getId(), getOnlineCount());
try {
sendMessage(JsonUtil.toJson("连接成功"));
} catch (IOException e) {
logger.error("websocket IO异常");
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this); //从set中删除
//移除sessionWtihBuildingMap中对应的对象
sessionWithIdMap.remove(this);
subOnlineCount(); //在线数减1
logger.info("====关闭连接session:{}!当前在线人数为:{}", this.session.getId(), getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) throws Exception {
logger.debug("====来自客户端的消息session:{}---message:{}", session.getId(), message);
this.session = session;
//保存session和对应的id
sessionWithIdMap.put(this, message);
sendMessage(JsonUtil.toJson("pong=>" + message));
}
/**
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
logger.error("发生错误");
error.printStackTrace();
}
public void sendMessage(String message) throws IOException {
logger.debug("发送至客户端的消息:{}"+message);
this.session.getBasicRemote().sendText(message);
}
/**
* 获取所有websocket
*/
public CopyOnWriteArraySet<WebSocketServer> getWebSockets() {
return webSocketSet;
}
/**
* 群发自定义消息
*/
public void sendInfo(String message) throws IOException {
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
logger.error("sendInfo error:{}",e);
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
【调用方法】
@Autowired
WebSocketService service;
service.sendMessage(message);
存在的问题:直接调用sendMessage()会获取不到session,所以我会调用sendInfo。