传统http协议
传统http协议是无状态的,每次请求都要由客户端主动发起,服务端进行处理后返回结果,而服务端很难主动主动向客户端发送消息
对于即时通信等功能来说使用http有很大的不便之处。
轮询:是最原始的实现实时web应用的解决方案。轮询技术要求客户端以设定的时间间隔周期性地向服务端发送请求,
频繁地查询是否有新的数据改动。这样方法会导致过多不必要的请求,浪费流量和资源
Coment技术:又可以分为长轮询和流技术。长轮询改进了上述的轮询技术,减小了无用的请求。它为某些数据设置了过期时间,
当数据过期后才会向服务端发送请求。流技术通常指客户端使用一个隐藏的窗口与服务端建立一个HTTP长连接,服务端会不断更新
连接状态以保持HTTP长连接存活
这两种技术都是基于请求-应答模式,都不算是真正意义上的实时技术;他们的每次请求,应答都狼浪费流量在头信息上,复杂
WebSocket(伴随HTML5推出)
websocket是一个持久化的,在单个TCP连接上进行全双工通讯的协议。
web浏览器和服务器都必须实现websocket协议来建立和维护连接,websocket连接长期存在,与典型的http连接不同,对服务器有重要影响
基于多线程或多进程的服务器无法适用于 WebSockets,因为它旨在打开连接,尽可能快地处理请求,然后关闭连接。任何实际的 WebSockets
服务器端实现都需要一个异步服务器
简单websocket实现
@ServerEndpoint(注册websocket服务注解)
要实现的四个注解(四个方法)
@OnOpen(连接建立成功调用的方法)
@OnClose(连接关闭调用的方法)
@OnMessage(收到客户端消息调用的方法)
@OnError(连接错误调用的方法)
后台代码如下:
@ServerEndpoint("/websocket/{userId}")
@Component
public class WebSocketServer {
static Logger log = Logger.getLogger(WebSocketServer.class);
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
//private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
//新:使用map对象,便于根据userId来获取对应的WebSocket
private static ConcurrentHashMap<String, WebSocketServer> websocketList = new ConcurrentHashMap<>();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
//接收userId
private String userId = "";
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
this.session = session;
websocketList.put(userId, this);
log.info("websocketList->" + JSON.toJSONString(websocketList));
addOnlineCount(); //在线数加1
log.info("有新窗口开始监听:" + userId + ",当前在线人数为" + getOnlineCount());
this.userId = userId;
try {
Enumeration<String> keys = websocketList.keys();
if(keys.hasMoreElements()){
System.out.println(keys.nextElement());
}
MessageFormat messageFormat = new MessageFormat();
messageFormat.setFromUserId("系统消息");
messageFormat.setReceiveMessage("用户-" + userId + "-连接成功");
messageFormat.setMessageType("1");
sendMessage(JSON.toJSONString(messageFormat));
} catch (IOException e) {
log.error("websocket IO异常");
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
if (websocketList.get(this.userId) != null) {
websocketList.remove(this.userId);
subOnlineCount(); //在线数减1
log.info("用户:" + userId + "断开连接!当前在线人数为" + getOnlineCount());
}
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到来自窗口" + userId + "的信息:" + message);
if (StringUtils.isNotBlank(message)) {
MessageFormat format = JSONObject.parseObject(message, MessageFormat.class);
String text = format.getContentText();
try {
String toUserId = format.getToUserId();
String contentText = format.getContentText();
//传送给对应用户的websocket
if (StringUtils.isNotBlank(toUserId) && StringUtils.isNotBlank(contentText)) {
WebSocketServer socketx = websocketList.get(toUserId);
//需要进行转换,userId
MessageFormat messageFormat = new MessageFormat();
if (socketx != null) {
messageFormat.setFromUserId(toUserId);
messageFormat.setReceiveMessage(text);
socketx.sendMessage(JSON.toJSONString(messageFormat));
}else {
messageFormat.setFromUserId("系统消息");
messageFormat.setReceiveMessage("用户-" + userId + "-不在线");
messageFormat.setMessageType("1");
socketx.sendMessage(JSON.toJSONString(messageFormat));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 连接错误调用的方法
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误");
error.printStackTrace();
}
/**
* 实现服务器主动推送
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 群发自定义消息
*/
public static void sendInfo(String id, String message) throws IOException {
log.info("推送消息到窗口:" + id + ",推送内容:" + message);
for (String str : websocketList.keySet()) {
try {
MessageFormat messageFormat = new MessageFormat();
messageFormat.setReceiveMessage(message);
messageFormat.setMessageType("2");
if (id == null) {
messageFormat.setFromUserId("推送消息");
websocketList.get(str).sendMessage(JSON.toJSONString(messageFormat));
} else if (str.equals(id)) {
messageFormat.setFromUserId("用户-" + id + "-推送消息" );
websocketList.get(str).sendMessage(JSON.toJSONString(messageFormat));
}
} catch (IOException e) {
continue;
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
前端部分也要实现websocket服务:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script th:src="@{/js/jquery-2.1.1.min.js}"></script>
</head>
<p>
<script>
var socket;
var start = false;
function openSocket() {
if (typeof (WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
} else {
console.log("您的浏览器支持WebSocket");
//实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接
//等同于socket = new WebSocket("ws://服务器IP/项目名/项目路径/注册ID");
if (start == true) {
alert("您已连接成功,请不要重复连接!")
} else {
socket = new WebSocket("ws://服务器IP/项目名/项目路径/" + $("#userId").val());
//打开事件
socket.onopen = function () {
console.log("Socket 已打开");
start = true;
//socket.send("这是来自客户端的消息" + location.href + new Date());
};
//获得消息事件
socket.onmessage = function (msg) {
var message = JSON.parse(msg.data);
console.log(msg);
//发现消息进入 开始处理前端触发逻辑
if(message.messageType == 0){
$("#test").append(message.fromUserId + " ---> " +message.receiveMessage + "<br>") ;
}else {
alert(message.fromUserId + ":" + message.receiveMessage);
}
};
//关闭事件
socket.onclose = function () {
console.log("Socket已关闭");
};
//发生了错误事件
socket.onerror = function () {
alert("Socket发生了错误");
//此时可以尝试刷新页面
}
}
}
}
function sendMessage() {
if (typeof (WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
} else {
console.log("您的浏览器支持WebSocket");
console.log('{"toUserId":\"' + $("#toUserId").val() + '\","contentText":\"' + $("#contentText").val() + '\"}');
socket.send('{"toUserId":\"' + $("#toUserId").val() + '\","contentText":\"' + $("#contentText").val() + '\"}');
$("#test").append($("#contentText").val() + " <--- " + $("#userId").val() + "<br>");
}
}
function pushMessage() {
if (typeof (WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
} else {
console.log("您的浏览器支持WebSocket");
$.post("http://服务器IP/推送服务路径",
{'id':'','message':$("#contentText").val()},function (result) {
if(result){
alert("推送成功");
}else{
alert("推送失败")
}
})
}
}
function test() {
if (typeof (WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
} else {
console.log("您的浏览器支持WebSocket");
socket.send('{"toUserId":\"' + $("#toUserId").val() + '\","contentText":"用户下线"}');
socket.close();
start = false;
}
}
</script>
【当前用户ID】:<input id="userId" name="userId" type="text"><br>
【接收用户ID】:<input id="toUserId" name="toUserId" type="text"><br>
【操作】:<input type="button" onclick="openSocket()" value="加入连接">
<input id="bt" type="button" onclick="test()" value="断开连接"><br>
【接收消息内容】:
<div id="test"></div>
<br>
<input id="contentText" name="contentText" type="text">
<input type="button" onclick="sendMessage()" value="发送消息">
<input type="button" onclick="pushMessage()" value="推送消息">
</body>
</html>