浏览器客户端和服务器建立起websocket的链接,最初也是http请求握手,通过httpServletRequest发送http请求到服务器,其中头部就包含需要请求websocket链接的一系列信息,大概过程如下
1.客户端请求一个链接,头部中包含如下信息
GET /demo HTTP/1.1
Host: example.com
Connection: Upgrade
Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
Upgrade: WebSocket
Sec-WebSocket-Key1: 4@1 46546xW%0l 1 5
Origin: http://example.com
[8-byte security key]
2.服务器根据头部中的Sec-WebSocket-Key2,Sec-WebSocket-Key1,Upgrade,[8-byte security key] 知道客户端需要一个websocket协议链接,于是返回一个消息,包含如下头部
HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
WebSocket-Origin: http://example.com
WebSocket-Location: ws://example.com/demo
[16-byte hash response]
3.客户端收到消息之后,建立起websocket链接,这时就可以进行实时通信了
我们可以定义一个处理器来实现WebSocketHandler处理请求
public class MyWebSocketHandler implements WebSocketHandler{
/**
* webscoket建立好链接之后的处理函数
* @param session 当前websocket的会话id,打开一个websocket通过都会生成唯一的一个会话,可以通过该id进行发送消息到浏览器客户端
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// TODO Auto-generated method stub
}
/**
* 客户端发送服务器的消息时,的处理函数,在这里收到消息之后可以分发消息
*/
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
// TODO Auto-generated method stub
}
/**
* 消息传输过程中出现的异常处理函数
*/
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
// TODO Auto-generated method stub
}
/**
* websocket链接关闭的回调
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
// TODO Auto-generated method stub
}
/**
* 是否支持处理拆分消息,返回true返回拆分消息
*/
@Override
public boolean supportsPartialMessages() {
// TODO Auto-generated method stub
return false;
}
}
websocket的链接建立是基于http握手协议,我们可以添加一个拦截器处理握手之前和握手之后过程
public class MyHandShakeInterceptor implements HandshakeInterceptor{
/**
* 握手之前,若返回false,则不建立链接
*/
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
// TODO Auto-generated method stub
return true;
}
/**
* 握手之后
*/
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception exception) {
// TODO Auto-generated method stub
}
}
接下来,需要我们把处理器和拦截器注册到spring websocket中
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new MyWebSocketHandler(), "/portfolio")//添加一个处理器还有定义处理器的处理路径
.addInterceptors(new MyHandShakeInterceptor())
.withSockJS();
}
}
在这里我们用到.withSockJS(),SockJS是spring用来处理浏览器对websocket的兼容性,目前浏览器支持websocket还不是很好,特别是IE11以下.SockJS能关键浏览器能否支持websocket来提供三种方式用于websocket请求,三种方式分别是 WebSocket, HTTP Streaming以及 HTTP Long Polling
SockJS提供了浏览器客户端的js库,在浏览器我们请求websocket就这么用
var socket = new SockJS('/whats/portfolio');//项目名称 + 处理器拦截路径名就会打开的目的websocket链接口
/**
* 建立成功的回调函数
*/
socket.onopen = function() {
console.log('open');
};
/**
* 服务器有消息返回的回调函数
*/
socket.onmessage = function(e) {
console.log('message', e.data);
};
/**
* websocket链接关闭的回调函数
*/
socket.onclose = function() {
console.log('close');
};
然后客户端发送一个消息
document.getElementById("ws").onclick = function() {
socket.send("fff");
}
服务器MyWebSocketHandler中,通过handlemessage接收消息并进行分发
/**
* 客户端发送服务器的消息时,的处理函数,在这里收到消息之后可以分发消息
*/
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
//获取消息
String body = (String) message.getPayload();
//一系列的处理之后...
//发送消息
session.sendMessage(message);
}
session可以用来标注客户端id,相对于我们的httpsession,这样,如果我们想做一个精准推送和全部推送,我们可以这么做
首先在自定义的处理其中,建一个队列来存储连进来的websocketsession
public class MyWebSocketHandler implements WebSocketHandler{
private List<WebSocketSession> users = new ArrayList<WebSocketSession>(); //存放WebSocketSession的队列
/**
* webscoket建立好链接之后的处理函数
* @param session 当前websocket的会话id,打开一个websocket通过都会生成唯一的一个会话,可以通过该id进行发送消息到浏览器客户端
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
session.getAttributes().put("userid", "xxxx");//如果有用户登录,可以把用户的id绑定到session里面,便于后面做精准推送
users.add(session); //每个链接来的客户端都把WebSocketSession保存进来
}
//...
}
然后呢,我们在接收到消息的时候,就可以直接的根据需要精准推送到用户或者全部推送了
/**
* 客户端发送服务器的消息时,的处理函数,在这里收到消息之后可以分发消息
*/
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
// TODO Auto-generated method stub
for (WebSocketSession webSocketSession : users) {
//Long id = (Long) session.getAttributes().get("userid");//可以通过获取userid进行匹配,从而进行精准推送
message = new TextMessage("ggg");
webSocketSession.sendMessage(message); //全部推送
}
}
spring websocket大概的请求过程就是这样子,这是基础的请求过程,细心的同学可能发现,如果我有很多种不同的业务请求,是不是要写很多个处理器??能不能只实现一个处理器,然后由这个处理器来进行分发处理,做到类似springmvc的DispatcherServlet那样子??下次研究看看