WebSocket
WebSocket協議支持(在受控環境中運行不受信任的代碼的)客戶端與(選擇加入該代碼的通信的)遠程主機之間進行全雙工通信。用於此的安全模型是Web瀏覽器常用的基於原始的安全模式。 協議包括一個開放的握手以及隨後的TCP層上的消息幀。 該技術的目標是爲基於瀏覽器的、需要和服務器進行雙向通信的(服務器不能依賴於打開多個HTTP連接(例如,使用XMLHttpRequest或
<
iframe>和長輪詢))應用程序提供一種通信機制。
socket消息推送流程
- 後臺創建socket服務;
- 用戶登錄後與後臺建立socket連接,默認使用websocket,如果瀏覽器不支持則使用scokjs連接;
- 建立連接後,服務端可以向用戶推送信息;
javaweb中,socket的實現方式有多種,這裏使用Spring-webscoket的方式實現。
demo
搭建環境
在SpringMVC的項目基礎上,導入websocket的相關jar包。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>4.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>4.1.9.RELEASE</version>
</dependency>
websocket服務端實現類
@Configuration
@EnableWebMvc
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer{
private static final Logger logger = LoggerFactory.getLogger(WebSocketConfig.class);
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
System.out.println("==========================註冊socket");
//註冊websocket server實現類,"/webSocketServer"訪問websocket的地址
registry.addHandler(msgSocketHandle(),
"/webSocketServer").
addInterceptors(new WebSocketHandshakeInterceptor());
//使用socketjs的註冊方法
registry.addHandler(msgSocketHandle(),
"/sockjs/webSocketServer").
addInterceptors(new WebSocketHandshakeInterceptor())
.withSockJS();
}
/**
*
* @return 消息發送的Bean
*/
@Bean(name = "msgSocketHandle")
public WebSocketHandler msgSocketHandle(){
return new MsgScoketHandle();
}
}
這裏使用的config配置的形式註冊bean和配置,所以需要在SpringMVC的配置文件中添加對類的自動掃描
<mvc:annotation-driven />
<context:component-scan base-package="com.wqh.websocket"/>
攔截器類
主要是獲取到當前連接的用戶,並把用戶保存到WebSocketSession中
public class WebSocketHandshakeInterceptor implements HandshakeInterceptor {
private static final Logger logger = LoggerFactory.getLogger(WebSocketHandshakeInterceptor.class);
/**
* 握手前
* @param request
* @param response
* @param webSocketHandler
* @param attributes
* @return
* @throws Exception
*/
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler, Map<String, Object> attributes) throws Exception {
logger.info("握手操作");
if (request instanceof ServletServerHttpRequest){
ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) request;
HttpSession session = servletServerHttpRequest.getServletRequest().getSession(false);
if(session != null){
//從session中獲取當前用戶
User user = (User) session.getAttribute("user");
attributes.put("user",user);
}
}
return true;
}
/**
* 握手後
* @param serverHttpRequest
* @param serverHttpResponse
* @param webSocketHandler
* @param e
*/
@Override
public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
}
}
socket處理消息類
@Component
public class MsgScoketHandle implements WebSocketHandler {
/**已經連接的用戶*/
private static final ArrayList<WebSocketSession> users;
static {
//保存當前連接用戶
users = Lists.newArrayList();
}
/**
* 建立鏈接
* @param webSocketSession
* @throws Exception
*/
@Override
public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
//將用戶信息添加到list中
users.add(webSocketSession);
System.out.println("=====================建立連接成功==========================");
User user = (User) webSocketSession.getAttributes().get("user");
if(user != null){
System.out.println("當前連接用戶======"+user.getName());
}
System.out.println("webSocket連接數量====="+users.size());
}
/**
* 接收消息
* @param webSocketSession
* @param webSocketMessage
* @throws Exception
*/
@Override
public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
User user = (User) webSocketSession.getAttributes().get("user");
System.out.println("收到用戶:"+user.getName()+"的消息");
System.out.println(webSocketMessage.getPayload().toString());
System.out.println("===========================================");
}
/**
* 異常處理
* @param webSocketSession
* @param throwable
* @throws Exception
*/
@Override
public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable){
if (webSocketSession.isOpen()){
//關閉session
try {
webSocketSession.close();
} catch (IOException e) {
}
}
//移除用戶
users.remove(webSocketSession);
}
/**
* 斷開鏈接
* @param webSocketSession
* @param closeStatus
* @throws Exception
*/
@Override
public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
users.remove(webSocketSession);
User user = (User) webSocketSession.getAttributes().get("user");
System.out.println(user.getName()+"斷開連接");
}
@Override
public boolean supportsPartialMessages() {
return false;
}
/**
* 發送消息給指定的用戶
* @param user
* @param messageInfo
*/
public void sendMessageToUser(User user, TextMessage messageInfo){
for (WebSocketSession session : users) {
User sessionUser = (User) session.getAttributes().get("user");
//根據用戶名去判斷用戶接收消息的用戶
if(user.getName().equals(sessionUser.getName())){
try {
if (session.isOpen()){
session.sendMessage(messageInfo);
System.out.println("發送消息給:"+user.getName()+"內容:"+messageInfo);
}
break;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
controller及頁面
這裏簡單的模擬登錄,前臺傳入登錄參數,直接將參數保存到session中。
@RequestMapping("websocket")
@Controller
public class UserController {
@Autowired
private MsgScoketHandle msgScoketHandle;
@RequestMapping("login")
public String login(User user, HttpServletRequest request){
user.setId(UUID.randomUUID().toString().replace("-",""));
request.getSession().setAttribute("user",user);
return "/index";
}
@ResponseBody
@RequestMapping("sendMsg")
public String sendMag(String content,String toUserName){
User user = new User();
user.setName(toUserName);
TextMessage textMessage = new TextMessage(content);
msgScoketHandle.sendMessageToUser(user,textMessage);
return "200";
}
}
登錄頁面省略,直接socket連接頁面,這裏使用sockjs
來創建連接,所以需要先添加js文件
sockjs.min.js
<script>
$(document).ready(function() {
var ws;
if ('WebSocket' in window) {
ws = new WebSocket("ws://"+window.location.host+"/webSocketServer");
} else if ('MozWebSocket' in window) {
ws = new MozWebSocket("ws://"+window.location.host+"/webSocketServer");
} else {
//如果是低版本的瀏覽器,則用SockJS這個對象,對應了後臺“sockjs/webSocketServer”這個註冊器,
//它就是用來兼容低版本瀏覽器的
ws = new SockJS("http://"+window.location.host+"/sockjs/webSocketServer");
}
ws.onopen = function (evnt) {
};
//接收到消息
ws.onmessage = function (evnt) {
alert(evnt.data);
$("#msg").html(evnt.data);
};
ws.onerror = function (evnt) {
console.log(evnt)
};
ws.onclose = function (evnt) {
}
$("#btn1").click(function () {
ws.send($("#text").val());
});
$("#btn2").bind("click",function () {
var url = "${pageContext.request.contextPath}/websocket/sendMsg";
var content = $("#text").val();
var toUserName = "admin"
$.ajax({
data: "content=" + content + "&toUserName=" + toUserName,
type: "get",
dataType: 'text',
async: false,
contentType: "application/x-www-form-urlencoded;charset=UTF-8",
encoding: "UTF-8",
url: url,
success: function (data) {
alert(data.toString());
},
error: function (msg) {
alert(msg);
},
});
})
});
</script>
<body>
當前登錄用戶:${pageContext.session.getAttribute("user").name}<br>
<input type="text" id="text">
<button id="btn1" value="發送給後臺">發送給後臺</button>
<button id="btn2" value="發送給其他用戶">發送給其他用戶</button>
<div id="msg"></div>
</body>
</html>
啓動項目
在控制可以看到socket註冊成功
訪問頁面,第一個用戶使用admin登錄,第二個使用1234登錄
首先將消息發送給後臺,後臺打印消息
使用1234用戶發送消息給admin
爬坑
在Springmvc項目中都會指定連接訪問的後綴,比如.do、.action,但是這裏會導致按照以上配置會導致前端連接socket服務時404。我的解決辦法是修改web.xml,將DispatcherServlet
的<url-pattern>
改爲/
。。。但是新的問題又出現了,頁面無法加載資源文件,所以還需要在SpringMVC.xml中添加對靜態資源的配置,這裏具體的mapping
和location
看自己的具體項目。
<mvc:resources mapping="/css/**" location="/css/" />
<mvc:resources mapping="/images/**" location="/images/" />
<mvc:resources mapping="/js/**" location="/js/" />