关于websocket的一点认识

WebSocket的概念伴随着html5的出现,其实已经出来很多年了,但是浏览器的支持实现,服务端的支持实现各不相同,Java范畴内也是最近才统一了API实现标准,我匆匆看了一下Oracle提供的实现api,还是比较简洁的,这个jsr356 规范一定要看看。

说到WebSocket协议,必须先说说http协议,我们都知道,http协议最显著的特征就是其通信方式为经典的请求/应答模式,这种模式存在这么几个问题:

  • 浪费资源

    每一次请求都要重新建立一个HTTP连接(底层是TCP),非常浪费资源,如果碰上https等认证过程,每一次请求的验证过程更是缓慢。

  • 服务端很被动

    其实不是,是只能。 服务端只能被动响应客户端的请求,不能主动推送信息给客户端。在需要通知的场景,如聊天室,游戏,客户端应用需要不断地轮询服务器,虽然后面发展出了所谓的comet技术,使用所谓的伪长连接,相对于轮询提升了一些性能,但是还是没有从本质上解决问题。

WebSocket的出现就是为了解决这些问题,它和http协议的关系很暧昧,本质上来说,WebSocket是不限于HTTP协议的,但是由于现存大量的HTTP基础设施,代理,过滤,身份认证等等,WebSocket借用HTTP和HTTPS的端口。由于使用HTTP的端口,因此TCP连接建立后的握手消息是基于HTTP的,由服务器判断这是一个HTTP协议,还是WebSocket协议。WebSocket连接除了建立和关闭时的握手,数据传输和HTTP没丁点关系了。

在阅读完上文提到的jsr356规范以后,我这里做了一个web聊天室的demo,看下代码吧,非常简洁。

首先是服务端,涉及到了这么几个逻辑:

  • websocket服务的定义
  • 各种事件的监听
  • 消息编码/消息解码

websocket服务的定义

1
2
3
4
5
// 定义了服务的访问,消息编解码逻辑类
@ServerEndpoint(value = "/chat/{username}", encoders = MsgEncoder.class, decoders = MsgDecoder.class)
public class ChatServer {
......
}

服务端各种事件的监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 //  建立连接
@OnOpen
public void open(Session session, @PathParam("username") String username) {
SESSIONS.add(session);
DBObject data = new BasicDBObject("username", username);
// 有人上线,向所有的人广播通知
Msg msg = new Msg(Msg.type_0_online, data);
sendMsg(msg);
}


// 关闭连接
@OnClose
public void close(Session session, @PathParam("username") String username) {
SESSIONS.remove(session);
DBObject data = new BasicDBObject("username", username);
// 有人下线,向所有的人广播通知
Msg msg = new Msg(Msg.type_1_offline, data);
sendMsg(msg);
}

//发送消息
@OnMessage
public void message(Session session, Msg msg, @PathParam("username") String username) {
// 将消息发送给聊天室的在线用户
sendMsg(msg);
}


// 广播消息的逻辑
private void sendMsg(Msg msg) {
for (Session session : SESSIONS) {
try {
// 此处直接发送Bean消息对象,消息编码器会将其编码为JSON格式,再传递给客户端
session.getBasicRemote().sendObject(msg);
} catch (IOException e) {
e.printStackTrace();
} catch (EncodeException e) {
e.printStackTrace();
}
}
}

再来看看客户端的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function startWebSocket(url, onopen, onmessage, onclose) {
var ws = null;
if ('WebSocket' in window) {
ws = new WebSocket(url);
} else if ('MozWebSocket' in window) {
ws = new MozWebSocket(url);
} else {
alert('Websocket is not supportted ')
}
ws.onopen = onopen;
ws.onclose = onclose;
ws.onmessage = onmessage;
window.ws = ws;
}

最核心的逻辑都在这个方法里面了,是不是非常简单,下面看看效果图吧。

最后,附上源码地址