我們在使用websocket的時候其實主要面對的問題就是session共享的問題:
不管是基於Spring實現的Websocket的WebsocketSession
還是基於JDK實現的Session
亦或者基於netty實現的ChannelHandlerContext
用圖來描述下場景吧:
OK,大家看到這個圖了,差不多應該明白了Session共享應該怎麼處理了。其實原理很簡單:
1、我們知道nginx有IP保持的功能,其實這個功能就能解決大部分場景的Session共享問題。 但是某些極限情況下還是會有問題,比如在瀏覽器沒有關閉的情況下同一個用戶更換了網絡的情況導致IP變了,或者對於某些網絡的IP是變動的情況下,就會出現Session找不到的情況。
2、基於上述nginx的原理我們可以進行優化,還是單例存儲。那麼要操作的時候,我告訴所有的服務端,你們去找這個用戶的Session,並把消息帶過去。那麼相應的節點根據用戶拿到Session了就可以進行處理了。
上面2點大概簡單的描述了下Session共享的原理,那麼有這麼個場景,文字可能不太好表達,我們還是用圖來說明:
一般出現多端情況也應該就上面2種情況,要麼允許,要麼不允許。我這裏簡單的說下不允許的處理流程。
建立連接的時候,先獲取老的Session
Session oldSession = SOCK_MAP.get(baseStudentInfo.getId());
存在,則推送關閉消息,不存在告知其他節點去清楚。當然本節點的的Server要排除在外,這裏就通過IP判斷即可。
if(oldSession!=null) {
oldSession.getBasicRemote().sendObject(close);
}else{
//關閉其他節點的的session
authService.pushCloseMessage(close);
}
//替換
SOCK_MAP.put(baseStudentInfo.getId(),session);
消息監聽
String serverIp = IPUtils.getLocalhostIp();
logger.info("當前IP:"+serverIp);
logger.info("content的IP:"+wsMessage.getBody().getContent());
//IP不相等,說明不是當前連接的服務端,關閉其他端口
if(!serverIp.equals(wsMessage.getBody().getContent())){
//關閉session,並返回給前端
customerHandler.closeSession(wsMessage.getBody().getReceiver(), wsMessage);
}
關閉的方法:
/**
* 關閉Session
* @param studentId
* @param closeMessage
*/
public void closeSession(Long studentId,WsMessage closeMessage){
Session session = SOCK_MAP.get(studentId);
if(session!=null) {
try {
session.getBasicRemote().sendObject(closeMessage);
SOCK_MAP.remove(studentId);
//清除redis
logger.info("連接已關閉:" + studentId);
} catch (Exception e) {
e.printStackTrace();
logger.error("關閉連接異常");
}
}
}
這樣基本就避免多端登錄的問題,如果允許多端登錄的時候只需要更改存儲,更改發送消息變成羣發即可。