本篇文章针对基于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" +);
}