WebSocket为浏览器和服务端提供了双工异步通信的功能,即浏览器可以向服务端发送消息,服务端也可以向浏览器发送消息。WebSocket葙浏览器的支持,如IE 10+、Chrome 13+,Firefox6+,这对我们现在的浏览器来说都不是问题。
WebSocket是通过一个socket来实现双工异步通信能力的。但是直接使用WebSocket (或 者SockJS,WebSocket协议的模拟,增加了当浏览器不支持WebSocket的时候的兼容支持)协议开发程序显得特别烦琐,我们会使用它的子协议STOMP,它是一个更高级别的协议,STOMP协议使用一个基于帧(frame)的格式来定义消息,与HTTP的request和response类似(具有 类似于@RequestMapping 的@MessageMapping)。
在pom.xml 文件中添加websocket依赖的包。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>配置WebSocket,需要在配置类上使用@EnableWebSocketMessageBroker开启WebSocket,并继承AbstractWebSocketMessageBrokerConfigurer类。通过@EnableWebSocketMessageBroker注解开启使用@STOMP协议来传输基于代理的消息,这是控制器支持使用@MessageMapping。"/endpointWisely"注册STOMP协议的节点,并指定映射的URL和指定使用SockJS协议。
import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{ @Override public void registerStompEndpoints(StompEndpointRegistry arg0) { arg0.addEndpoint("/endpointWisely").withSockJS(); } }Controller层 当浏览器向服务器发送请求时,通过@MessageMapping映射/welcome这个地址,相当于@RequestMapping。SendTo当服务器有消息时,会对这个路径的浏览器发送消息。
import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class WsController { @MessageMapping("welcome") @SendTo("/demo_2/topic/getResponse") public String say(String message) throws Exception { return message; } }websocketBroadcast.html,这里需要在static文件中添加sockjs.min.js和stomp.min.js和jquery-2.1.1.min.js,这个可以去网上自行下载。
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8" /> <title>Spring Boot WebSocket+广播式</title> </head> <body onload="disconnect()"> <noscript> <h2 style="color:#ff0000">貌似你的浏览器不支持websocket</h2> </noscript> <div> <div> <button id="connect" onclick="connect()">连接</button> <button id="disconnect" onclick="disconnect();">断开连接</button> </div> <div id="conversationDiv"> <label>输入你的名字</label> <input type="text" id="name" /> <button id="sendName" onclick="sendName();">发送</button> <p id="response"></p> </div> </div> <script th:src="@{websocket/sockjs.min.js}"></script> <script th:src="@{websocket/stomp.min.js}"></script> <script th:src="@{jquery-2.1.1.min.js}"></script> <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'; $('#response').html(); } function connect() { var socket = new SockJS('/demo_2/endpointWisely'); stompClient = Stomp.over(socket); stompClient.connect({}, function(frame) { setConnected(true); console.log('Connected:' + frame); stompClient.subscribe('/demo_2/topic/getResponse', function(response) { showResponse(response.body); }); }); } function disconnect() { if (stompClient != null) { stompClient.disconnect(); } setConnected(false); console.log("Disconnected"); } function sendName() { var name = $('#name').val(); stompClient.send("/welcome", {}, name); } function showResponse(message) { var response = $('#response'); response.html(message); } </script> </body> </html>访问websocketBroadcast.html文件
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter{ @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/ws").setViewName("/WebSocket/websocketBroadcast"); } }因为是广播,可以在浏览器的三个标签页中访问下面的地址,同时把这是哪个连接打开,然后在一个输入框中输入内容,点击发送,就可以看到刚刚发出来的信息。相关信息可以查看console中的信息。
http://localhost:8080/demo_2/ws
当然访问访问websocketBroadcast.html文件也可以通过一个controller层去跳转.
@RequestMapping("/wstest") public String wstest(){ return "/WebSocket/websocketBroadcast"; }在pom.xml文件中添加下面的内容
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>这里面的代码和之前的差不多。 由于是点对点通信,所以需要用户登录,这里设置里两个用户名,wyf和wisely,然后还设置了登录文件和登陆之后默认的跳转文件,关于这部分内容,会在接下面的博客中做进一步学习。
import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/","/login").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .defaultSuccessUrl("/chat") // .failureUrl("/error")错误是跳转的页面 .permitAll() .and() // .rememberMe()是否被kookie记住 // .tokenValiditySeconds(3600)记住的时间长度3600s // .key("key")cookie中的私钥 // .and() .logout() .permitAll(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("wyf") .password("wyf") .roles("USER") .and() .withUser("wisely") .password("wisely") .roles("USER"); } @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/resources/static/**"); } }controller层,和之前的内容相同,就不做过多的介绍了。
import java.security.Principal; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Controller; @Controller public class WebSocketP2PController { @Autowired private SimpMessagingTemplate messagingTemplate; @MessageMapping("/chat") public void handlerChat(Principal principal, String msg) { if (principal.getName().equals("wyf")) { messagingTemplate.convertAndSendToUser("wisely", "/queue/notifications", principal.getName() + "<br/> " + msg); } else { messagingTemplate.convertAndSendToUser("wyf", "/queue/notifications", principal.getName() + "<br/> " + msg); } } }然后就是两个界面:chat.html和login.html login.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/l999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <meta charset="UTF-8" /> <head> <title>登录页面</title> </head> <body> <div th:if="${param.error}">无效的账号和密码</div> <div th:if="${param.logout}">你已注销</div> <form th:action="@{/login}" method="post"> <div> <label>账号: <input type="text" name="username" /></label> </div> <div> <label>密码: <input type="password" name="password" /></label> </div> <div> <input type="submit" value="登陆" /> </div> </form> </body> </html>chat.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <meta charset="UTF-8" /> <head> <title>Home</title> </head> <body> <p>聊天室</p> <form id="wiselyForm"> <textarea rows="4" cols="60" name="text"></textarea> <input type="submit" /> </form> <div id="output"></div> </body> <script th:src="@{websocket/sockjs.min.js}"></script> <script th:src="@{websocket/stomp.min.js}"></script> <script th:src="@{jquery-2.1.1.min.js}"></script> <script th:inline="javascript"> $('#wiselyForm').submit(function(e) { e.preventDefault(); var text = $('#wiselyForm').find('textarea[name="text"]').val(); stomp.send("/chat", {}, text); $('#output').append("<b> " + text + "</b><br/>"); }); var sock = new SockJS('/demo_2/endpointChat'); var stomp = Stomp.over(sock); stomp.connect('user', 'user', function() { stomp.subscribe("/user/queue/notifications", function(response) { $('#output').append("<b>" + response.body + "</b><br/>"); }); }); </script> </html>然后同样添加一个映射文件,一个项目中这个配置文件只能有一个,所以和之前的整合在一起。
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter{ @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/ws").setViewName("/WebSocket/websocketBroadcast"); registry.addViewController("/login").setViewName("/WebSocket/login"); registry.addViewController("/chat").setViewName("/WebSocket/chat"); } }由于一个浏览器只能保存一个session,所以这种点对点通信只能开两个浏览器来完成。然后分别登陆,登陆的用户名密码就是刚刚在config文件中设置的,这两个用户名是存在内存中的,当然也可以从文件或者数据库中去加载。
然后在输入信息发送就可以互相看到了,这样就相当于构建了一个聊天室
关于这部分内容为了可以更好的了解,有必要去好好看看websocket这部分内容。
这是我的源码,有兴趣的可以下载看看http://download.csdn.net/download/q15150676766/9924308