web即时聊天系统的实现
第【2】页
即时通讯方案:webSocket长连接
什么是webSocket?我会结合一个小demo来帮助理解。
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
图片来自https://www.runoob.com/html/html5-websocket.html
可以看到相对于ajax轮询来说,由于websocket进行一次连接http请求后直接可以创建持久性的连接,并进行双向数据传输。只有到使用webSocketAPI 的close()断开连接才结束,避免了每一次http请求发送包含的头部数据等非有效数据造成消耗带宽资源的情况,减少带宽的消耗,降低消息的延迟。最重要的是给服务器减压,因为连接一旦建立,客户端和服务器端就处于平等的地位,可以相互发送数据,不存在请求和响应的区别。其他人获取message更新也不用向服务器请求更新数据,服务器自动发送数据到建立了webSocket连接的用户客户端上。(当然这是需要根据需求设计后端程序进行处理的)
方案思路:用户A与用户B建立了webSocket连接,互发消息和推送都是服务器主动进行转发,资源消耗明显减少。
以下是demo
【前端】HTML与JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
WebSocket长连接
<br />
<input id="text" type="text" />
<button οnclick="webSocketUnit.send()">发送消息</button>
<hr />
<input type="hidden" name="" class="userinfo" value="<?=$username?>">
<button οnclick="window.webSocketUnit.init()" id="open">开启WebSocket连接</button>
<button οnclick="webSocketUnit.closeWebSocket()" id="close">关闭WebSocket连接</button>
<button οnclick=""><a href="../index/quit.php" style="text-decoration:none;display: block;">退出登录</a></button>
<hr />
<div id="message"></div>
</body>
<script>
! function() {
var user = document.getElementsByClassName('userinfo')[0].value;
// document.getElementById("close").disabled = true;
var webSocketUnit = {
websocket: null,
heartCheck: {
timeout: 12000, //12秒发一次心跳
timeoutObj: null,
serverTimeoutObj: null,
reset: function() {
clearInterval(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
return this;
},
start: function() {
var self = this;
this.timeoutObj = setInterval(function() {
//这里发送一个心跳,后端收到后,返回一个心跳消息,
//onmessage拿到返回的心跳就说明连接正常
webSocketUnit.websocket.send('{"username":"client","msg":"ping","type":0}');
}, this.timeout)
}
},
openWebSocket: function() {
if ('WebSocket' in window) {
webSocketUnit.websocket = new WebSocket("ws://127.0.0.1:7001/webSocketTest/websocket");
} else {
alert('当前浏览器 Not support websocket')
}
return webSocketUnit.websocket;
},
closeWebSocket: function() {
webSocketUnit.websocket.send('{"username":"client","msg":"退出群聊","type":2}');
webSocketUnit.websocket.close();
document.getElementById("open").disabled = false;
document.getElementById("close").disabled = true;
},
printMessage: function(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
},
send() {
var message = document.getElementById('text').value;
webSocketUnit.websocket.send('{"username":"' + user + '","msg":"' + message + '","type":2}');
// console.log("{username:'"+user+"',msg:"+message+"}")
},
init: function() {
var websocket = this.openWebSocket() //|| new WebSocket("ws://127.0.0.1:7001/webSocketTest/websocket");
webSocketUnit.heartCheck.start();
//连接发生错误的回调方法
websocket.onerror = function() {
webSocketUnit.printMessage("WebSocket连接发生错误");
};
//连接成功建立的回调方法
websocket.onopen = function() {
webSocketUnit.printMessage("WebSocket连接成功");
websocket.send('{"username":"' + user + '","msg":"加入群聊","type":1}');
}
//接收到消息的回调方法
websocket.onmessage = function(event) {
webSocketUnit.printMessage(event.data);
}
//连接关闭的回调方法
websocket.onclose = function() {
webSocketUnit.heartCheck.reset();
webSocketUnit.printMessage("连接关闭");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function() {
webSocketUnit.closeWebSocket();
}
webSocketUnit.websocket = websocket;
window.websocket = websocket;
document.getElementById("open").disabled = true;
document.getElementById("close").disabled = false;
}
}
var websocket = webSocketUnit.websocket;
window.webSocketUnit = webSocketUnit;
}()
</script>
</html>
【后端】
package com.coden.services;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import net.sf.json.JSONObject;
/**
* @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
* 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
*/
@ServerEndpoint("/websocket")
public class webSocket {
// 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
// concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
private static CopyOnWriteArraySet<webSocket> webSocketSet = new CopyOnWriteArraySet<webSocket>();
// 与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
/**
* 连接建立成功调用的方法
*
* @param session
* 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
@OnOpen
public void onOpen(Session session) {
this.session = session;
webSocketSet.add(this); // 加入set中
addOnlineCount(); // 在线数加1
System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this); // 从set中删除
subOnlineCount(); // 在线数减1
System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*
* @param message
* 客户端发送过来的消息
* @param session
* 可选的参数
*/
@OnMessage
public void onMessage(String message, Session session) {
// 群发消息
for (webSocket item : webSocketSet) {
try {
if(JSONObject.fromObject(message).getInt("type") == 0){
//返回响应心跳消息
// System.out.println("心跳包:" + message);
}else {
item.sendMessage(JSONObject.fromObject(message).getString("username")+" : "+JSONObject.fromObject(message).getString("msg"));
}
//
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
/**
* 发生错误时调用
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("发生错误");
error.printStackTrace();
}
/**
* 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。
*
* @param message
* @throws IOException
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
// this.session.getAsyncRemote().sendText(message);
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
webSocket.onlineCount++;
}
public static synchronized void subOnlineCount() {
webSocket.onlineCount--;
}
};
上面这个例子就展示了webSocket的简单应用,使用起来像是游戏界面内的公屏系统,任何用户都可加进来这个公屏上发表消息
当然如果需要建立起聊天室的功能还需要对项目进行设计,具体的会在实现篇里说明。
使用上效果很好,结合javaee的webSocket包,可以很好的设计webSocket后台程序。
即时通讯方案的SSE可以查阅资料,我就不在这里记录了。
[2019-10-26]