欢迎在文章下方评论
WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP请求完成。
在浏览器中通过http仅能实现单向的通信,comet可以一定程度上模拟双向通信,但效率较低,并需要服务器有较好的支持; flash中的socket和xmlsocket可以实现真正的双向通信,通过 flex ajax bridge,可以在javascript中使用这两项功能. 可以预见,如果websocket一旦在浏览器中得到实现,将会替代上面两项技术,得到广泛的使用.面对这种状况,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽并达到实时通讯。
根据以上两个缺陷,spring给出自己的解决方式
在javaweb开发中,主要是用来做推送用,其他的话不是很了解,若想深入交接websocket,请看一本叫《HTML5 WebSocket权威指南》
由于在网上spring4 结合websocket的demo有点乱七八槽,很多都是错的,既然这样,那我就自己结合网上,做了一个demo。下面主要是spring结合websocket通过sock.js连接及订阅发布的讲解。
建议这个时候同时打开我在github的demo,websocket_demo
@Configuration
@EnableWebMvc
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer{}
这注解三个大致意思是使这个类支持以@Bean的方式加载bean,并且支持springmvc和websocket,不是很准确大致这样,试了一下@EnableWebMvc不加也没什么影响,@Configuration本来就支持springmvc的自动扫描。 然后是继承WebMvcConfigurerAdapter,和实现WebSocketConfigurer。
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(systemWebSocketHandler(), "/websck").addInterceptors(new HandshakeInterceptor());
System.out.println("registed!");
registry.addHandler(systemWebSocketHandler(), "/sockjs/websck/info").addInterceptors(new HandshakeInterceptor())
.withSockJS();
在重写的registerWebSocketHandlers方法中,我们可以看到两种注册方式。一个是有.withSockJS()的连接,一个是没有的。同时指定了在连接之后的的信息处理类是systemWebSocketHandler()
SystemWebSocketHandler implements WebSocketHandler{}//继承WebSocketHandler
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {...//处理连接之后
@Override
public void handleMessage(WebSocketSession wss, WebSocketMessage<?> wsm) throws Exception {...//处理客户端传来的信息
@Override
public void handleTransportError(WebSocketSession wss, Throwable thrwbl) throws Exception {...//处理当异常产生
@Override
public void afterConnectionClosed(WebSocketSession wss, CloseStatus cs) throws Exception {...//处理连接关闭后
@Override
public boolean supportsPartialMessages() {...
@Override
public boolean supportsPartialMessages() {...//是否把消息分割成几个handleMessage来处理
在这个类中大家还会看到其他两个自定义方法,其实主要是为了业务的逻辑处理也产生的。
HandshakeInterceptor extends HttpSessionHandshakeInterceptor{...}//在这里的Interceptor继承了HttpSessionHandshakeInterceptor
@Override
public boolean beforeHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {...//也就是在握手之前处理
@Override
public void afterHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler wsHandler,
Exception ex) {...//也就是在握手之后处理
我这里的拦截器只是简单的输出了下日志,久不加详细讲解了
在connect()方法中,我们可以看到 首先是引入sock.js的支持
ws= new WebSocket("ws://localhost:8080/SpringWebSocketPush/websck");
ws = new SockJS("http://localhost:8080/SpringWebSocketPush/sockjs/websck");
这里创建两个js对象,分别是WebSocket,和SockJS,其中和SockJS是用到了sock.js的支持的。
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/hello").withSockJS();
}
}
@EnableWebSocketMessageBroker 注解的作用: 能够在 WebSocket 上启用 STOMP
registry.addEndpoint("/hello").withSockJS();
这个路径与之前发送和接收消息的目的路径有所不同, 这是一个端点,客户端在订阅或发布消息 到目的地址前,要连接该端点,即 用户发送请求 url=’/server/hello’ 与 STOMP server 进行连接,之后再转发到 订阅url;(server== name of your springmvc project )(干货——端点的作用——客户端在订阅或发布消息 到目的地址前,要连接该端点) @Controller
public class GreetingController {
@MessageMapping("/hello")
@SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
System.out.println("receiving " + message.getName());
System.out.println("connecting successfully.");
return new Greeting("Hello, " + message.getName() + "!");
}
@SubscribeMapping("/macro")
public Greeting handleSubscription() {
System.out.println("this is the @SubscribeMapping('/marco')");
Greeting greeting = new Greeting("i am a msg from SubscribeMapping('/macro').");
return greeting;
}
}
@SendTo("/topic/greetings")
表示的是发布者发送的消息的最终目的,也就是订阅者接受消息是要连接的地址。<script type="text/javascript">
var stompClient = null;
function setConnected(connected) {
document.getElementById('connect').disabled = connected;
document.getElementById('disconnect').disabled = !connected;
document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
document.getElementById('response').innerHTML = '';
}
function connect() {
var socket = new SockJS("<c:url value='/hello'/>");
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/greetings', function(greeting){
showGreeting(JSON.parse(greeting.body).content);
});
});
}
function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function sendName() {
var name = document.getElementById('name').value;
stompClient.send("/app/hello", {}, JSON.stringify({ 'name': name }));
}
function showGreeting(message) {
var response = document.getElementById('response');
var p = document.createElement('p');
p.style.wordWrap = 'break-word';
p.appendChild(document.createTextNode(message));
response.appendChild(p);
}
</script>
对以上代码的 分析(Analysis):
关于stomp,在这篇博文中有详细介绍,可以去看看
其实实现消息的推送和消息还有很多方式。下次会结合消息中间件activeQM来继续讲解。