springboot-websocket作為客戶端
本篇部落格主要記錄下使用websocket作為客戶端的功能
一,websocket服務端
1,建立一個spring boot專案
pom.xml的程式碼如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.jack</groupId> <artifactId>springboot-websocket-chat</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>springboot-websocket-chat</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.8.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!--<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <!--json包--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.39</version> </dependency> <!--websocket作為客戶端--> <dependency> <groupId>org.java-websocket</groupId> <artifactId>Java-WebSocket</artifactId> <version>1.3.5</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
application.yml裡面的配置:
server:
port: 9090
spring:
application:
name: websocket-chat
2,spring mvc的配置類
package com.jack.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; /** * Created by jack on 2017/10/25. */ @Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { //super.addViewControllers(registry); registry.addViewController("/ws").setViewName("/ws"); registry.addViewController("/chat").setViewName("/chat"); } }
3,websocket的配置
package com.jack.config; /** * Created by jack on 2017/10/25. */ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** * 使用@ServerEndpoint創立websocket endpoint * 首先要注入 ServerEndpointExporter, * 這個bean會自動註冊使用了 @ServerEndpoint 注 * 解宣告的 Web Socket endpoint。 * 要注意,如果使用獨立的 Servlet 容器, * 而不是直接使用 Spring Boot 的內建容器, * 就不要注入 ServerEndpointExporter, * 因為它將由容器自己提供和管理 */ @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
4,websocket的session獲取的配置
package com.jack.config;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionContext;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import java.util.Enumeration;
/**
* Created by jack on 2017/10/25.
*/
/**
* 解決websocket獲取不到session的問題
* 參考:http://www.cnblogs.com/jarviswhj/p/4227559.html
* http://www.cnblogs.com/zhaoww/p/5119706.html
*/
public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
//super.modifyHandshake(sec, request, response);
HttpSession httpSession = (HttpSession)request.getHttpSession();
//解決httpSession為null的情況
if (httpSession == null){
httpSession = new HttpSession() {
@Override
public long getCreationTime() {
return 0;
}
@Override
public String getId() {
return null;
}
@Override
public long getLastAccessedTime() {
return 0;
}
@Override
public ServletContext getServletContext() {
return null;
}
@Override
public void setMaxInactiveInterval(int i) {
}
@Override
public int getMaxInactiveInterval() {
return 0;
}
@Override
public HttpSessionContext getSessionContext() {
return null;
}
@Override
public Object getAttribute(String s) {
return null;
}
@Override
public Object getValue(String s) {
return null;
}
@Override
public Enumeration<String> getAttributeNames() {
return null;
}
@Override
public String[] getValueNames() {
return new String[0];
}
@Override
public void setAttribute(String s, Object o) {
}
@Override
public void putValue(String s, Object o) {
}
@Override
public void removeAttribute(String s) {
}
@Override
public void removeValue(String s) {
}
@Override
public void invalidate() {
}
@Override
public boolean isNew() {
return false;
}
};
}
sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
}
}
5,websocket服務類
package com.jack.controller;
import com.jack.config.GetHttpSessionConfigurator;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Created by jack on 2017/10/25.
*/
/**
* websocket的具體實現類
*/
@ServerEndpoint(value = "/websocket",configurator = GetHttpSessionConfigurator.class)
@Component
public class WebSocketServer {
//靜態變數,用來記錄當前線上連線數。應該把它設計成執行緒安全的。
private static int onlineCount = 0;
//concurrent包的執行緒安全Set,用來存放每個客戶端對應的MyWebSocket物件。
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
//與某個客戶端的連線會話,需要通過它來給客戶端傳送資料
private Session session;
/**
* 連線建立成功呼叫的方法
*/
@OnOpen
public void onOpen(Session session,EndpointConfig config) {
this.session = session;
HttpSession httpSession= (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
System.out.println("name is : "+ httpSession.getAttribute("name"));
//sessionMap.put(session.getId(), session);
System.out.println("the session is : "+session.getId());
System.out.println("session content is : "+session.getId());
System.out.println("httpSession is : "+httpSession.getId());
webSocketSet.add(this); //加入set中
addOnlineCount(); //線上數加1
System.out.println("有新連線加入!當前線上人數為" + getOnlineCount());
try {
//sendMessage(CommonConstant.CURRENT_WANGING_NUMBER.toString());
sendMessage("22222222");
} catch (IOException e) {
System.out.println("IO異常");
}
}
/**
* 連線關閉呼叫的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this); //從set中刪除
subOnlineCount(); //線上數減1
System.out.println("有一連線關閉!當前線上人數為" + getOnlineCount());
}
/**
* 收到客戶端訊息後呼叫的方法
*
* @param message 客戶端傳送過來的訊息
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("來自客戶端的訊息:" + message);
System.out.println("onMessage sessionId is : "+session.getId());
//群發訊息
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 發生錯誤時呼叫
*/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("發生錯誤");
error.printStackTrace();
}
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message+" : sendMessage id is : "+this.session.getId());
//this.session.getAsyncRemote().sendText(message);
}
/**
* 群發自定義訊息
*/
public static void sendInfo(String message) throws IOException {
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
continue;
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
6,main/resources/templates/ws.html程式碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>My WebSocket</title>
</head>
<body>
Welcome<br/>
<input id="text" type="text" /><button onclick="send()">Send</button> <button onclick="closeWebSocket()">Close</button>
<div id="message">
</div>
</body>
<script type="text/javascript">
var websocket = null;
//判斷當前瀏覽器是否支援WebSocket
if('WebSocket' in window){
websocket = new WebSocket("ws://localhost:9090/websocket");
}
else{
alert('Not support websocket')
}
//連線發生錯誤的回撥方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//連線成功建立的回撥方法
websocket.onopen = function(event){
setMessageInnerHTML("open");
}
//接收到訊息的回撥方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}
//連線關閉的回撥方法
websocket.onclose = function(){
setMessageInnerHTML("close");
}
//監聽視窗關閉事件,當視窗關閉時,主動去關閉websocket連線,防止連線還沒斷開就關閉視窗,server端會拋異常。
window.onbeforeunload = function(){
websocket.close();
}
//將訊息顯示在網頁上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//關閉連線
function closeWebSocket(){
websocket.close();
}
//傳送訊息
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</html>
上面的程式碼實現了一個websocket作為服務端,html頁面作為客戶端的一個websocket連線的例子。下面實現websockert作為客戶端的功能
二,websocket作為客戶端
1,引入jar包:
<!--websocket作為客戶端-->
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.3.5</version>
</dependency>
2,編寫一個測試類,程式碼如下:
package com.jack.test;
import org.java_websocket.WebSocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.handshake.ServerHandshake;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
/**
* Created by jack on 2017/10/25.
*/
public class WebsocketClient {
public static WebSocketClient client;
public static void main(String[] args) {
try {
client = new WebSocketClient(new URI("ws://localhost:9090/websocket"),new Draft_6455()) {
@Override
public void onOpen(ServerHandshake serverHandshake) {
System.out.println("開啟連結");
}
@Override
public void onMessage(String s) {
System.out.println("收到訊息"+s);
}
@Override
public void onClose(int i, String s, boolean b) {
System.out.println("連結已關閉");
}
@Override
public void onError(Exception e) {
e.printStackTrace();
System.out.println("發生錯誤已關閉");
}
};
} catch (URISyntaxException e) {
e.printStackTrace();
}
client.connect();
System.out.println(client.getDraft());
while(!client.getReadyState().equals(WebSocket.READYSTATE.OPEN)){
System.out.println("還沒有開啟");
}
System.out.println("打開了");
try {
send("hello world".getBytes("utf-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
client.send("hello world");
client.close();
}
public static void send(byte[] bytes){
client.send(bytes);
}
}
3,執行測試
1)啟動spring boot專案執行websocket的服務端
2)執行
WebsocketClient
類
3)websocket服務輸出日誌如下:
name is : null
the session is : 1
session content is : 1
httpSession is : null
有新連線加入!當前線上人數為1
來自客戶端的訊息:hello world
onMessage sessionId is : 1
有一連線關閉!當前線上人數為0
4)websocket作為客戶端輸出日誌如下:
還沒有開啟
還沒有開啟
還沒有開啟
還沒有開啟
還沒有開啟
還沒有開啟
還沒有開啟
還沒有開啟
還沒有開啟
還沒有開啟
還沒有開啟
還沒有開啟
還沒有開啟
開啟連結
打開了
收到訊息22222222 : sendMessage id is : 1
收到訊息hello world : sendMessage id is : 1
連結已關閉
Process finished with exit code 0
總結:本篇使用另外一種方式實現了websocket的服務端,並在websocket通訊中獲取到session,要獲取session需要先呼叫:http://localhost:9090/test/testSession這個介面,程式碼如下:
package com.jack.controller;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* Created by jack on 2017/10/25.
*/
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping(value = "/testSession",method = RequestMethod.GET)
public JSONObject testSession(HttpServletRequest request, HttpServletResponse response){
System.out.println("session test");
HttpSession session = request.getSession();
System.out.println("the session is : "+session.getId());
Cookie[] cookies = request.getCookies();
System.out.println("cookies is : "+JSONObject.toJSON(cookies));
JSONObject json = new JSONObject();
json.put("success", "session test .");
return json;
}
}
用來生成一個session,模擬登入功能以後有一個session的作用。然後使用websocket實現了一個客戶端,直接利用客戶端連線websocket的服務端。
還有一種方式實現websocket作為客戶端,參考:http://www.cnblogs.com/akanairen/p/5616351.html
收集的websocket相關的資訊:
http://www.cnblogs.com/xiaojf/p/6613822.html
http://www.cnblogs.com/akanairen/p/5616351.html
http://blog.csdn.net/blueblueskyhua/article/details/70807847
http://blog.csdn.net/zzhao114/article/details/60154017
https://segmentfault.com/a/1190000009038991
http://blog.csdn.net/haoyuyang/article/details/53364372
https://segmentfault.com/a/1190000007397316
一種可以實現自定義的websocket的實現,主要給出部分主要的程式碼,可以獲取request的引數,進行一些登入驗證,比如session,token,使用者名稱等
package com.qwrt.station.websocket.config;
import com.qwrt.station.websocket.websockethandler.ChatHandshakeInterceptor;
import com.qwrt.station.websocket.websockethandler.ChatMessageHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.handler.TextWebSocketHandler;
/**
* Created by jack on 2017/10/26.
*/
@Configuration
@EnableWebSocket
public class SpringWebSocketConfig implements WebSocketConfigurer {
/**
* 這個類是配置類,第一個addHandler是對正常連線的配置,
* 第二個是如果瀏覽器不支援websocket,使用socketjs模擬websocket的連線。
* @param webSocketHandlerRegistry
*/
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
//setAllowedOrigins("*")解決跨越問題
webSocketHandlerRegistry.addHandler(chatMessageHandler(),"/webSocketServer").addInterceptors(new ChatHandshakeInterceptor()).setAllowedOrigins("*");
webSocketHandlerRegistry.addHandler(chatMessageHandler(), "/sockjs/webSocketServer").addInterceptors(new ChatHandshakeInterceptor()).setAllowedOrigins("*").withSockJS();
}
@Bean
public TextWebSocketHandler chatMessageHandler(){
return new ChatMessageHandler();
}
}
package com.qwrt.station.websocket.websockethandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
import javax.servlet.http.HttpSession;
import java.util.Map;
/**
* Created by jack on 2017/10/26.
*/
public class ChatHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
private static final Logger logger = LoggerFactory.getLogger(ChatHandshakeInterceptor.class);
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
logger.debug("beforeHandshake......Before Handshake");
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
String topic = servletRequest.getServletRequest().getParameter("topic");
HttpSession session = servletRequest.getServletRequest().getSession(false);
if (session == null){
logger.error("session is null ,cant not connection websocket,to do login");
return false;
}else {
logger.debug("beforeHandshake ---sessionId = "+session.getId()+" ,topic = "+topic);
}
//logger.debug("beforeHandshake ---sessionId = "+session.getId()+" ,topic = "+topic);
attributes.put("topic", topic);
}
return super.beforeHandshake(request, response, wsHandler, attributes);
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
logger.debug("afterHandshake......進入After Handshake方法");
super.afterHandshake(request, response, wsHandler, ex);
}
}
package com.qwrt.station.websocket.websockethandler;
import com.alibaba.fastjson.JSONObject;
import com.qwrt.station.api.vo.authority.SessionInfo;
import com.qwrt.station.websocket.util.WebsocketHandlerMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.PongMessage;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Created by jack on 2017/10/26.
*/
public class ChatMessageHandler extends TextWebSocketHandler {
//靜態變數,用來記錄當前線上連線數。應該把它設計成執行緒安全的。
private static int onlineCount = 0;
//public static final ArrayList<WebSocketSession> users;// 這個會出現效能問題,最好用Map來儲存,key用userid
//concurrent包的執行緒安全Set,用來存放每個客戶端對應的MyWebSocket物件。
//public static final CopyOnWriteArraySet<WebSocketSession> WEBSOCKET_SESSION = new CopyOnWriteArraySet<WebSocketSession>();
public static final ConcurrentMap<String,WebSocketSession> WEBSOCKET_SESSION = new ConcurrentHashMap<>();
public static final ConcurrentMap<String,ConcurrentMap<String,String>> USER_MSG_SESSION = new ConcurrentHashMap<>();
//public static final ConcurrentMap<String,ConcurrentMap<String,WebSocketSession>> USER_MSG_WEBSOCKET_SESSIONIDS = new ConcurrentHashMap<>();
private static Logger logger = LoggerFactory.getLogger(ChatMessageHandler.class);
//初始化時間
private static Long oldTime = new Date().getTime();
private static int minute = 10;
/**
* 連線成功時候,會觸發UI上onopen方法
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
logger.debug("connect to the websocket success......sessionId="+session.getId());
logger.debug("session.getAttributes = "+JSONObject.toJSON(session.getAttributes()));
WEBSOCKET_SESSION.put(session.getId(),session);
}
/**
* 在UI在用js呼叫websocket.send()時候,會呼叫該方法
*/
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
logger.debug("handleTextMessage........message is : "+message.getPayload());
}
/**
* 處理傳輸資訊錯誤的時候,進行的處理
* @param session
* @param exception
* @throws Exception
*/
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
logger.debug("handleTransportError........"+JSONObject.toJSON(session.getAttributes()));
}
/**
*在websocket連線關閉的時候進行的處理
* @param session
* @param closeStatus
* @throws Exception
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
logger.error("websocket connection closed......");
}
@Override
public boolean supportsPartialMessages() {
return false;
}
public static synchronized int getOnlineCount() {
return WEBSOCKET_SESSION.size();
}
/*public static synchronized void addOnlineCount() {
ChatMessageHandler.onlineCount++;
}
public static synchronized void subOnlineCount() {
ChatMessageHandler.onlineCount--;
}*/
/**
* 移除使用者資訊和對應的session
* @param session
*/
public static void removeUserSessionIdMsg(WebSocketSession session){
}
/**
* 一個使用者名稱可能有多個websocket連線的sessionId,需要進行儲存
* @param userName
* @param sessionId
*/
public void saveUserSessionIdMsg(String userName,String sessionId){
..... session num = "+WEBSOCKET_SESSION .size()+" ,username num = : "+USER_MSG_SESSION.size()+" ,username = "+userName+" ,websocketSessionIdCount = "+sessionMap.size());
}
@Override
protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
super.handlePongMessage(session, message);
logger.debug("handlePongMessage...................");
}
/**
* 更新時間標誌,移除在10分鐘內已經關閉了的websocketSession
*/
private static void updateTimeAndRemoveWebsocketSessionId(){
}
}