websocket協議介紹
屁話不多說,簡單點說,就是一個保持全雙工,能夠保持長連接的協議,我們用它來進行發送彈幕和發視頻,圖片進行即時交流的工具。下面進行彈幕的技術實現。
@OnOpen
public void onOpen(Session session)
{session.setMaxTextMessageBufferSize((int) MAX_BIG_LONG);
addOnlineCount(); // 在線數加1--必須先加1---錯誤的時候會減1
this.session = session;
if (!parseQueryString(session)) // 如果未能取得用戶id和type,退出return;// 驗證賬號,防止偽造
this.chatUser = loginChatServer(chatUserId);
if (this.chatUser == null)
{
closeSession(session);return;
}
addChatUserToHashMap(roomId, chatUserId);
try
{// 發一個應答標記,表示已經成功登陸,沒有構造
sendMessage("SUCCESS");
}
catch (IOException e) {
// TODO Auto-generated catch blocke.printStackTrace();
}Constant.ONLINECOUNT = onlineCount.toString();
}
onopen標識客戶端與服務器進行通訊連接,在此處可進行業務邏輯的處理,將所需賬號提出進行保存。
@Component
@ServerEndpoint("/chatServer")
public class ChatServer {
private static Log logger = LogFactory.getLog(ChatServer.class);
/** AtomicInteger:線程安全的整數對象 */
private static AtomicInteger onlineCount = new AtomicInteger(0);// 線程安全整數對象
private static long MAX_BIG_LONG = 1024 * 4 * 1024;
/** roomId與一個集合的哈希。集合中存儲當前房間的所有用戶 */
private static ConcurrentHashMap<String, CopyOnWriteArraySet<String>> roomToChatUserHashMap = new ConcurrentHashMap<String, CopyOnWriteArraySet<String>>();
/** 用戶與chatServer實例的哈希。 */
private static ConcurrentHashMap<String, ChatServer> chatUserToChatServer = new ConcurrentHashMap<String, ChatServer>();
/** token驗證 **/
private String token;
/** 房間號 **/
private String roomId;
/** chatUser 主鍵Id **/
private String chatUserId;
/** chatUserd對象 **/
private ChatUser chatUser;
/**
* concurrent包的線程安全Set,用來存放每個客戶端對應的MyWebSocket對象。若要實現服務端與單一客戶端通信的話,
* 可以使用Map來存放,其中Key可以為用戶標識·1
*/
聲明兩個類級別的ConcurrentHashMap存儲房間號和chatUser之間的對應關係,一個房間對應多個chatUser賬號,一個chatUser對應一個chatServer實例,發送消息時只需進行遍歷這倆個map找到相應的實例對象,調用它的發消息即可成功的發送
/**
* 一個房間對應的一個chatuser列表 發消息時候進行遍歷操作
*
* @param chatUserId
* @param chatUserId
* @return
*/
private boolean addChatUserToHashMap(String roomId, String chatUserId) {
try {
CopyOnWriteArraySet<String> chatUserIdSet = null;
if (roomToChatUserHashMap.containsKey(roomId)) {
chatUserIdSet = roomToChatUserHashMap.get(roomId);
} else {
chatUserIdSet = new CopyOnWriteArraySet<String>();
}
chatUserIdSet.add(chatUserId);
roomToChatUserHashMap.put(roomId, chatUserIdSet);
chatUserToChatServer.put(chatUserId, this);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
這是進入房間時,將自身賬號加入到房間聊天群組,用來發廣播消息時遍歷的。
/**
* 從哈希中移除已經斷開的連接
*
* @param chatUserId
* @param terminalUuid
* @return
*/
private boolean removeChatUserFromRoomHashMap(String roomId, String chatUserId) {
try {
CopyOnWriteArraySet<String> chatUserIdSet = null;
if (roomToChatUserHashMap.containsKey(roomId)) {// 如果存在
chatUserIdSet = roomToChatUserHashMap.get(chatUserId);// 取得chatUserId的集合
} else {
return true;
}
chatUserIdSet.remove(chatUserId);// 從集合中移除
if (chatUserIdSet.size() == 0) {// 如果已經沒有連接終端
roomToChatUserHashMap.remove(roomId);// 則清除
} else {
roomToChatUserHashMap.put(roomId, chatUserIdSet);// 更新哈希
}
ChatServer chatServer = chatUserToChatServer.get(chatUserId);
// 釋放資源,清空chatServer
chatServer = null;
chatUserToChatServer.remove(chatUserId);// 將chatServer的實例從哈希中移除。
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
當斷開聊天室時,應該講其從聊天組移除掉。
/**
* 關閉websocket連接。
*
* @param session
* 要關閉的會話
*/
private void closeSession(Session session) {
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
return;
}
/**
* 連接關閉調用的方法
*/
@OnClose
public void onClose(Session session) {
try {
removeChatUserFromRoomHashMap(this.roomId, this.chatUserId);
subOnlineCount(); // 在線數減1
} catch (Exception e) {
}
}
關閉調用的方法
/**
* 收到客戶端消息後調用的方法。 客戶端發送消息的方法是,發送到服務器,消息中指明要傳送給那個用戶,消息的類型。<br/>
* 此處只管發送,至於發送的模式(緊急通知,普通通知,訂單消息,聊天信息),這裡並進行解析處理,由客戶端自行處理。<br/>
*
*
* @param message
* 客戶端發送過來的消息
* @param session
* 可選的參數
*/
@OnMessage
public void onMessage(String message, Session session) {
if (StringUtils.isBlank(message)) // 收到的是空串
return;
if (StringUtils.equals(Constant.SUCCESS_RESPONSE, message)) {
return;
}
Gson gson = null;
try {// 解析json串
gson = new Gson();
ChatMessage chatMessage = gson.fromJson(message, ChatMessage.class);
// 解析json出錯
if (chatMessage == null)
return;
/**
* 目的為了以後對每個模塊進行拓展,所以分開寫
*
*/
// 如果是圖片類型的消息體
if (StringUtils.equals(chatMessage.getMessageType(), EnumMessageType.IMAGE.name())) {
try {
boolean dealImageResult = dealBinary(chatMessage);// 處理圖片結果
if (!dealImageResult) {
return;// 如果沒生成,則返回
}
} catch (Exception e) {
logger.error("處理圖片異常" + e.getMessage());
}
}
// 處理小視頻
else if ((StringUtils.equals(chatMessage.getMessageType(), EnumMessageType.VIDEO.name()))) {
try {
boolean dealVideoResult = dealBinary(chatMessage);
if (!dealVideoResult) {
return;
}
} catch (Exception e) {
logger.error("處理小視頻異常" + e.getMessage());
}
}
// 處理二進制文件
else if ((StringUtils.equals(chatMessage.getMessageType(), EnumMessageType.BINARY.name()))) {
try {
boolean dealBinaryResult = dealBinary(chatMessage);
if (!dealBinaryResult) {
return;
}
} catch (Exception e) {
// TODO: handle exception
logger.error("處理文件異常" + e.getMessage());
}
}
chatMessage.setImageBase64("");// 清空串
// 不用做NPE判斷,因為chatUser如果為空 則推出closeSession 所以不可能為空
chatMessage.setChatName(this.chatUser.getUsername());
sendMessageToEveryoneInRoom(chatMessage);
} catch (Exception e) {// 發生錯誤即退出
e.printStackTrace();
}
}
進行通訊的模塊。具體代碼私信我,