消息推送
实现实时信息传递传统的方式有两种,一种是ajax轮询,一种是http long poll。
首先是ajax轮询,ajax轮询的原理非常简单,让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息。这个机制的缺点一是实时性不够,二是频繁的请求会给服务器带来极大的压力。
http long poll其实原理跟ajax轮询差不多,都是采用轮询的方式,不过采取的是阻塞模型(一直打电话,没收到就不挂电话),也就是说,客户端发起连接后,如果没消息,就一直不返回响应给客户端。直到有消息才返回,返回完之后,客户端再次建立连接,周而复始。这个机制暂时地解决了实时性问题,但是它带来了新的问题:以多线程模式运行的服务器会让大部分线程大部分时间都处于挂起状态,极大地浪费服务器资源。
传统的HTTP协议不能做到WebSocket实现的功能,是因为HTTP协议是一个请求-响应协议,请求必须先由浏览器发给服务器,服务器才能响应这个请求,再把数据发送给浏览器。换句话说,浏览器不主动请求,服务器是没法主动发数据给浏览器的。
所以为了在浏览器和服务器之间建立一个不受限的双向通信的通道,比如说,服务器可以在任意时刻发送消息给浏览器,HTML5推出了WebSocket标准,让浏览器和服务器之间可以建立无限制的全双工通信,任何一方都可以主动发消息给对方。
它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
此外,还包括以下特点:
(1)建立在 TCP 协议之上,服务器端的实现比较容易。
(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
(3)数据格式比较轻量,性能开销小,通信高效。
(4)可以发送文本,也可以发送二进制数据。
(5)没有同源限制,客户端可以与任意服务器通信。
(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
java WebSocket实现
websocket简单实现分为以下几个步骤:添加websocket库、编写后台代码、编写前端代码
添加websocket库
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
编写后台代码
后台实现websocket有两种方式:使用继承类、使用注解;注解方式比较方便,以下代码中使用注解方式来进行演示。
声明websocket地址类似Spring MVC中的@controller注解类似,websocket使用@ServerEndpoint来进行声明接口:@ServerEndpoint(value="/websocket/{paraName}") ; 其中 “ { } ”用来表示带参数的连接,如果需要获取{}中的参数在参数列表中增加:@PathParam(“paraName”) Integer userId 。则连接地址形如:ws://localhost:8080/project-name/websocket/8,其中每个连接可以设置不同的paraName的值。
注解、成员数据介绍:
1.@OnOpen
有连接时的触发函数。 我们可以在用户连接时记录用户的连接带的参数,只需在参数列表中增加参数:@PathParam(“paraName”) String paraName。
2.@OnClose
连接关闭时的调用方法。
3.@OnMessage
收到消息时调用的函数,其中Session是每个websocket特有的数据成员,详情见4.
4.Session
每个Session代表了两个websocket断点的会话;当websocket握手成功后,websocket就会提供一个打开的Session,可以通过这个Session来对另一个端点发送数据;如果Session关闭后发送数据将会报错。
5.Session.getBasicRemote().sendText(“message”)
向该Session连接的用户发送字符串数据。
6.@OnError
发生意外错误时调用的函数。
@ServerEndpoint(value="/websocketTest/{userId}")
public class Test {
private Logger logger = LoggerFactory.getLogger(Test.class);
private static String userId;
//连接时执行
@OnOpen
public void onOpen(@PathParam("userId") String userId,Session session) throws IOException{
this.userId = userId;
logger.debug("新连接:{}",userId);
}
//关闭时执行
@OnClose
public void onClose(){
logger.debug("连接:{} 关闭",this.userId);
}
//收到消息时执行
@OnMessage
public void onMessage(String message, Session session) throws IOException {
logger.debug("收到用户{}的消息{}",this.userId,message);
session.getBasicRemote().sendText("收到 "+this.userId+" 的消息 "); //回复用户
}
//连接错误时执行
@OnError
public void onError(Session session, Throwable error){
logger.debug("用户id为:{}的连接发送错误",this.userId);
error.printStackTrace();
}
}
编写前端代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
websocket Demo---- user000 <br />
<input id="text" type="text" />
<button onclick="send()"> Send </button>
<button onclick="closeWebSocket()"> Close </button>
<div id="message"> </div>
<script type="text/javascript">
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
websocket = new WebSocket("ws://localhost:8080/Demo/websocketTest/user000");
console.log("link success")
}else{
alert('Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//连接成功建立的回调方法
websocket.onopen = function(event){
setMessageInnerHTML("open");
}
console.log("-----")
//接收到消息的回调方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function(){
setMessageInnerHTML("close");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
websocket.close();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//关闭连接
function closeWebSocket(){
websocket.close();
}
//发送消息
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</body>
</html>
总结
websocket特别适合于需要实时数据传送的场景,比轮询方式效率高很多。