本篇文章針對基於spring @EnableWebSocket 實現socket通信業務處理的處理
在socket通信默認情況下是線程不安全的,當多個線程訪問同一個socket實體是將會發生錯誤,具體看源碼當socket發送信息是改變自身狀態,當另一個線程發送時會檢查狀態,當狀態不爲初始值是將拋出異常,
本人解決思路是將每個socket客戶端的信息根據放到單獨隊列去處理,以實現單線程操作
不涉及socket存儲處理以及發送的邏輯代碼
首先定義消息實體類封裝收到的消息
public class MessageCheckBean {
/*
1 open 2 message 3 close 4 error
*/
private Integer msgType;
private WebSocketSession session;
private String msg;
private CloseReason closeReason;
private Throwable throwable;
public Integer getMsgType() {
return msgType;
}
public void setMsgType(Integer msgType) {
this.msgType = msgType;
}
public WebSocketSession getSession() {
return session;
}
public void setSession(WebSocketSession session) {
this.session = session;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public CloseReason getCloseReason() {
return closeReason;
}
public void setCloseReason(CloseReason closeReason) {
this.closeReason = closeReason;
}
public Throwable getThrowable() {
return throwable;
}
public void setThrowable(Throwable throwable) {
this.throwable = throwable;
}
然後首先消息隊列以及線程池管理邏輯,講接收消息和發送消息分開
/**
* 消息隊列以及線程池管理
*/
public class MsgCheckSessionQueueManager {
private static boolean openAll = false;
private static boolean closeAllThread = false;
/*
原始數據隊列
*/
private static LinkedBlockingQueue<MessageCheckBean> rowBlockingQueue = new LinkedBlockingQueue();
private static ArrayList<LinkedBlockingQueue<UserMsg>> needSendMsgArray = new ArrayList<>();
static {
needSendMsgArray.add(new LinkedBlockingQueue<>());
needSendMsgArray.add(new LinkedBlockingQueue<>());
needSendMsgArray.add(new LinkedBlockingQueue<>());
}
//實際生產請使用其他線程池
private static ExecutorService sendMsgCacheThreadPool = new ThreadPoolExecutor(needSendMsgArray.size(), needSendMsgArray.size(),
20L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
private static ExecutorService rawMsgDealCacheThreadPool = new ThreadPoolExecutor(needSendMsgArray.size(), needSendMsgArray.size(),
20L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
/**
* 處理client發送來的消息
* @param msg
*/
public static void addRawMsg(MessageCheckBean msg){
try {
rowBlockingQueue.put(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 處理髮送給client的消息
* @param userMsg
*/
public static void addUserMsg(UserMsg userMsg){
try {
needSendMsgArray.get(根據業務處理消息到不同隊列).put(userMsg);
} catch (Exception e) {
e.printStackTrace();
}
}
/*
開啓線程調度
*/
public static void beginDealMsg(){
if(!openAll){
dealMessage();
openAll = true;
}
}
public static void main(String[] args) {
beginDealMsg();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
shutDownTheadPool();
}
private static void dealMessage(){
Thread threadRowDeal = new Thread(new Runnable() {
@Override
public void run() {
rawMsgDealCacheThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
while (!closeAllThread){
MessageCheckBean msgBewan = rowBlockingQueue.take();
//處理來自socket客戶端端的原始數據
dealRawMsgToSendQueue(msgBewan);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
});
threadRowDeal.setDaemon(true);
threadRowDeal.start();
//開啓線程池處理消息隊列
Thread openRowDealThread = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i< needSendMsgArray.size(); i++){
final int j = i;
sendMsgCacheThreadPool.execute(new Runnable() {
@Override
public void run() {
LinkedBlockingQueue<UserMsg> linkedBlockingQueue = needSendMsgArray.get(j);
while (!closeAllThread){
try {
UserMsg userMsg = linkedBlockingQueue.take();
//發送消息
MsgClientDealCheckSeesionManager.sendMsgToClient(userMsg);
} catch (InterruptedException e) {
e.printStackTrace();
}
//處理來自socket客戶端端的原始數據
}
}
});
}
}
});
openRowDealThread.setDaemon(true);
openRowDealThread.start();
}
/**
* 更具業務處理來自socket客戶端的原始數據 並將消息插入到needSendMsgArray
* 每個用戶插入到單獨隊列
*/
private static void dealRawMsgToSendQueue(MessageCheckBean msgBean) throws InterruptedException {
UserMsg userMsg = MsgClientDealCheckSeesionManager.dealRawMsgToSendQueue(msgBean);
if(userMsg != null){
addUserMsg(userMsg);
}
}
/*
關閉線程池 添加消息防止堵塞隊列拋出異常
*/
public static void shutDownTheadPool(){
closeAllThread = true;
openAll = false;
MessageCheckBean messageBean = new MessageCheckBean();
messageBean.setMsgType(5);
MsgCheckSessionQueueManager.addRawMsg(messageBean);
for (int i = 0; i < needSendMsgArray.size(); i++) {
try {
UserMsg userMsg = new UserMsg();
userMsg.setDeviceId(String.valueOf(i));
needSendMsgArray.get(i).put(userMsg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
rawMsgDealCacheThreadPool.shutdown();
sendMsgCacheThreadPool.shutdown();
}
}
封裝消息接收消息處理類實現收到信息的業務處理
/**
* 消息處理類
* 1 open 2 message 3 close 4 error
*/
public class MsgCheckSeesionHandler {
public static void openSeesion(WebSocketSession session){
MessageCheckBean messageBean = new MessageCheckBean();
messageBean.setMsgType(1);
messageBean.setSession(session);
MsgCheckSessionQueueManager.addRawMsg(messageBean);
}
public static void onMessage(WebSocketSession session, String msg){
MessageCheckBean messageBean = new MessageCheckBean();
messageBean.setMsgType(2);
messageBean.setSession(session);
messageBean.setMsg(msg);
MsgCheckSessionQueueManager.addRawMsg(messageBean);
}
public static void onClose(WebSocketSession session) {
MessageCheckBean messageBean = new MessageCheckBean();
messageBean.setMsgType(3);
messageBean.setSession(session);
MsgCheckSessionQueueManager.addRawMsg(messageBean);
}
public static void onError(WebSocketSession session, Throwable throwable) {
MessageCheckBean messageBean = new MessageCheckBean();
messageBean.setMsgType(4);
messageBean.setSession(session);
messageBean.setThrowable(throwable);
MsgCheckSessionQueueManager.addRawMsg(messageBean);
}
}
處理封裝的消息的具體實現類更具業務處理消息
/**
* 原始數據處理類
*/
public class MsgClientDealCheckSeesionManager {
/*
根據userMsg直接發送消息
*/
public static void sendMsgToClient(UserMsg userMsg) {
System.out.println("send to client" + userMsg.getDeviceId());
}
/*
處理客戶端發送來的消息
*/
public static UserMsg dealRawMsgToSendQueue(MessageCheckBean msgBean) {
UserMsg uerMsg = new UserMsg();
if(msgBean.getMsgType() == 1){
//openSession
uerMsg = SeesionOpenDealManager.openSeesion(msgBean.getSession());
} else if(msgBean.getMsgType() == 2){
//onMessage
System.out.println("message");
uerMsg.setDeviceId(msgBean.getSession().getAttributes().get("deviceId").toString());
uerMsg = SessionMessageDealManager.onMessag(msgBean.getSession(),msgBean.getMsg());
} else if(msgBean.getMsgType() == 3){
//onClose
uerMsg.setDeviceId(msgBean.getSession().getAttributes().get("deviceId").toString());
System.out.println("close");
} else if(msgBean.getMsgType() == 4){
//onError
uerMsg.setDeviceId(msgBean.getSession().getAttributes().get("deviceId").toString());
System.out.println("error");
} else {
}
return uerMsg;
}
}
重新上篇文章長得消息接口實現類替換消息接收處理,將消息直接存儲到消息隊列中
@RestController
public class WebSocketControllerBack extends TextWebSocketHandler {
private final Logger logger = LoggerFactory.getLogger(WebSocketController.class);
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
logger.info("WebSocket服務端連接: "+session.getAttributes().get("token")+"===>"+session.getAttributes().get("deviceId"));
MsgCheckSeesionHandler.openSeesion(session);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
logger.info("WebSocket服務端關閉: 關閉連接狀態: "+status);
MsgCheckSeesionHandler.onClose(session);
}
@Override
public void handleMessage(WebSocketSession wsSession, WebSocketMessage<?> message) throws Exception {
logger.info("WebSocket服務端接受:接受來自客戶端發送的信息: "+message.getPayload().toString());
MsgCheckSeesionHandler.onMessage(wsSession, message.getPayload().toString());
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
logger.info("WebSocket服務端異常:連接異常信息: " + exception.getMessage());
MsgCheckSeesionHandler.onError(session, exception);
}
/*
* 是否支持消息拆分發送:如果接收的數據量比較大,最好打開(true), 否則可能會導致接收失敗。
* 如果出現WebSocket連接接收一次數據後就自動斷開,應檢查是否是這裏的問題。
*/
@Override
public boolean supportsPartialMessages() {
return true;
}
}
到此整體思路實現完成,此版本針對對數據安全不高的通信,若不能丟數據或者其他業務需求,可嘗試將消息存儲到其他消息框架中
更具業務優化發送消息
public static void sendMsgToClient(UserMsg userMsg) {
System.out.println("send to client" +);
}