众所周知,Web 应用的交互过程通常是客户端通过浏览器发出一个请求,服务器端接收请求后进行处理并返回结果给客户端,客端浏览器将信息呈现。但是对于实时性要求较高、海量并发的应用,比如金融证券的实时信息,web导航应用中地理位置获取,社交网络的实时消息推送等。
方案一:轮询,客户端用js代码每隔一定时间向服务器发送请求,这样会造成资源浪费(浪费带宽),在高并发的情况下还可能造成服务器奔溃。
方案二:基于Flash、AdobeFlash,通过socket实现数据信息交互,再利用Flash暴露的接口供js调用,但是Flash在移动互联网上的支持不好,IOS和Android都不支持Flash了。
方案三:WebSocket,2014年开始,各大应用服务器和浏览器厂商逐步统一,J2EE7也实现了WebSocket协议,无论客户端还是服务器都提供了对其的支持。
WebSocket介绍与原理
WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和HTTP 最大不同是:
WebSocket 是一种双向通信协议,在基于http建立连接后,WebSocket 服务器和 browser都能主动向对方发送或接收数据,就像 Socket 一样;WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接,连接成功后才能相互通信,实现长连接。
WebSocket 客户端连接报文
GET /webfin/websocket/ HTTP/1.1Host: localhost
Upgrade: websocketConnection: Upgrade
Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==Origin: http://localhost:8080
Sec-WebSocket-Version: 13
WebSocket 服务端响应报文
HTTP/1.1 101 Switching ProtocolsUpgrade: websocket
Connection: UpgradeSec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=
下载javax.websocket.jar,使用注解方式实现了一个简单的多房间聊天demo,demo只有一个服务端类和一个前端chat.html页面,打开多个chat.html页面,输入相同的房间名,进入房间后可以相互通信,不同房间不能互相通信,不同用户我用websocket的session自己分配的id来区分,因为一个用户连接到webSocket服务器就对应一个session,实际开发可以用http的session中登录的用户名来区分,连接到服务器的url中,roomName是一个路径参数,即在chat.html中获取到房间名。多房间的原理其实就是把多个用(session)放在roomName对应的set集合中,每次广播信息只在房间名对应的set集合中广播,实现房间聊天信息的隔离。
代码如下:
-
package webSocketTest;
-
-
import javax.websocket.OnClose;
-
import javax.websocket.OnMessage;
-
import javax.websocket.OnOpen;
-
import javax.websocket.Session;
-
import javax.websocket.server.PathParam;
-
import javax.websocket.server.ServerEndpoint;
-
import java.util.HashSet;
-
import java.util.Set;
-
import java.util.Map;
-
import java.util.concurrent.ConcurrentHashMap;
-
import java.util.concurrent.CopyOnWriteArraySet;
-
-
/**
-
* writer: holien
-
* Time: 2017-08-01 13:00
-
* Intent: webSocket服务器
-
*/
-
@ServerEndpoint(
"/webSocket/chat/{roomName}")
-
public
class WsServer {
-
-
// 使用map来收集session,key为roomName,value为同一个房间的用户集合
-
// concurrentMap的key不存在时报错,不是返回null
-
private
static
final Map<String, Set<Session>> rooms =
new ConcurrentHashMap();
-
-
@OnOpen
-
public void connect(@PathParam("roomName") String roomName, Session session) throws Exception {
-
// 将session按照房间名来存储,将各个房间的用户隔离
-
if (!rooms.containsKey(roomName)) {
-
// 创建房间不存在时,创建房间
-
Set<Session> room =
new HashSet<>();
-
// 添加用户
-
room.add(session);
-
rooms.put(roomName, room);
-
}
else {
-
// 房间已存在,直接添加用户到相应的房间
-
rooms.get(roomName).add(session);
-
}
-
System.out.println(
"a client has connected!");
-
}
-
-
@OnClose
-
public void disConnect(@PathParam("roomName") String roomName, Session session) {
-
rooms.get(roomName).remove(session);
-
System.out.println(
"a client has disconnected!");
-
}
-
-
@OnMessage
-
public void receiveMsg(@PathParam("roomName") String roomName,
-
String msg, Session session) throws Exception {
-
// 此处应该有html过滤
-
msg = session.getId() +
":" + msg;
-
System.out.println(msg);
-
// 接收到信息后进行广播
-
broadcast(roomName, msg);
-
}
-
-
// 按照房间名进行广播
-
public static void broadcast(String roomName, String msg) throws Exception {
-
for (Session session : rooms.get(roomName)) {
-
session.getBasicRemote().sendText(msg);
-
}
-
}
-
-
}
-
<!DOCTYPE html><html lang="en">
-
<head>
-
<meta charset="UTF-8">
-
<title>网络聊天室
</title>
-
</head>
-
<style type="text/css">
-
.msg_board {
-
width: 322px;
-
height: 100px;
-
border: solid 1px darkcyan;
-
padding: 5px;
-
overflow-y: scroll;
-
// 文字长度大于div宽度时换行显示
-
word-break: break-all;
-
}
-
/*set srcoll start*/
-
::-webkit-scrollbar
-
{
-
width: 10px;
-
height: 10px;
-
background-color: #D6F2FD;
-
}
-
::-webkit-scrollbar-track
-
{
-
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
-
/*border-radius: 5px;*/
-
background-color: #D6F2FD;
-
}
-
::-webkit-scrollbar-thumb
-
{
-
height: 20px;
-
/*border-radius: 10px;*/
-
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
-
background-color: #89D7F7;
-
}
-
/*set srcoll end*/
-
</style>
-
<body>
-
<label>房间名
</label>
-
<input id="input_roomName" size="10" maxlength="10">
-
<input type="button" value="进入聊天室" onclick="initWebSocket()" />
-
<input type="button" value="退出聊天室" onclick="closeWs()" />
<br>
-
<div class="msg_board">
</div>
-
<input id="input_msg" size="43" maxlength="40">
-
<input type="button" value="发送" onclick="send_msg()" />
-
</body>
-
<script type="text/javascript">
-
var webSocket;
-
-
function send_msg() {
-
if (webSocket !=
null) {
-
var input_msg =
document.getElementById(
"input_msg").value.trim();
-
if (input_msg ==
"") {
-
return;
-
}
-
webSocket.send(input_msg);
-
// 清除input框里的信息
-
document.getElementById(
"input_msg").value =
"";
-
}
else {
-
alert(
"您已掉线,请重新进入聊天室...");
-
}
-
};
-
-
function closeWs() {
-
webSocket.close();
-
};
-
-
function initWebSocket() {
-
var roomName =
document.getElementById(
"input_roomName").value;
-
// 房间名不能为空
-
if (roomName ==
null || roomName ==
"") {
-
alert(
"请输入房间名");
-
return;
-
}
-
if (
"WebSocket"
in
window) {
-
// alert("您的浏览器支持 WebSocket!");
-
if (webSocket ==
null) {
-
var url =
"ws://localhost:8080/webSocket/chat/" + roomName;
-
// 打开一个 web socket
-
webSocket =
new WebSocket(url);
-
}
else {
-
alert(
"您已进入聊天室...");
-
}
-
-
webSocket.onopen =
function () {
-
alert(
"已进入聊天室,畅聊吧...");
-
};
-
-
webSocket.onmessage =
function (evt) {
-
var msg_board =
document.getElementsByClassName(
"msg_board")[
0];
-
var received_msg = evt.data;
-
var old_msg = msg_board.innerHTML;
-
msg_board.innerHTML = old_msg + received_msg +
"<br>";
-
// 让滚动块往下移动
-
msg_board.scrollTop = msg_board.scrollTop +
40;
-
};
-
-
webSocket.onclose =
function () {
-
// 关闭 websocket,清空信息板
-
alert(
"连接已关闭...");
-
webSocket =
null;
-
document.getElementsByClassName(
"msg_board")[
0].innerHTML =
"";
-
};
-
}
-
else {
-
// 浏览器不支持 WebSocket
-
alert(
"您的浏览器不支持 WebSocket!");
-
}
-
}
-
</script>
-
</html>
同时打开3个chat.html页面,2个页面进入名为“技术分享会”的房间,剩下一个进入名为“spring源码分析”的房间,只有相同房间的信息可以互通,截图如下:
发觉要更好的学习一种技术,得把该技术运用在具体功能实现上,这样会迫使我们去找资料和API,使得对该技术的认识更深刻...