如何快速實(shí)現(xiàn)一個(gè)聊天室?
如何快速實(shí)現(xiàn)一個(gè)聊天室?

前些天做了一個(gè)網(wǎng)站:https://modubox.cn 其中有個(gè)群聊插件,許多人問(wèn)如何實(shí)現(xiàn)的。這里簡(jiǎn)單說(shuō)下,為了快速完成群聊功能,我選擇從最簡(jiǎn)單的 WebSocket 開始。
什么是WebSocket ?
既然要使用它,就需要了解一下它吧。WebSocket其實(shí)也是一種基于TCP的網(wǎng)絡(luò)協(xié)議,它與HTTP協(xié)議最大的不同是:是一種雙向通信協(xié)議,在建立連接后,WebSocket服務(wù)器端和客戶端都能主動(dòng)向?qū)Ψ桨l(fā)送或接收數(shù)據(jù),而HTTP協(xié)議只能客戶端主動(dòng)發(fā)起通信。
所以WebSocket能夠用于聊天,當(dāng)然其他地方也能應(yīng)用,如果做客服系統(tǒng)或推送消息都可以從這里開始。
如何實(shí)現(xiàn)單聊/群聊?
群聊:所有客戶端的消息發(fā)送到服務(wù)器,服務(wù)端將消息發(fā)送給所有客戶端。
單聊:WebSocket客戶端之間是無(wú)法直接通信的,想要通信,必須由服務(wù)端轉(zhuǎn)發(fā)。

實(shí)現(xiàn)
1. 引入WebSocket的支持
我們使用當(dāng)前最流行的Spring Boot框架構(gòu)建項(xiàng)目,然后引入Spring Boot 對(duì) WebSocket 的支持:
<dependency>
?<groupId>org.springframework.boot</groupId>
?<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2. 開啟WebSocket
@Configuration
public?class?WebSocketConfig?{
????@Bean
????public?ServerEndpointExporter?serverEndpointExporter()?{
????????return?new?ServerEndpointExporter();
????}
}
3. 服務(wù)端
這里主要有以下幾點(diǎn):
- 聲明服務(wù)端點(diǎn)路徑
- 存儲(chǔ)所有連接用戶,等待匹配用戶
- 連接 onOpen,消息OnMessage,關(guān)閉onClose,錯(cuò)誤onError 方法
- 發(fā)送消息給特定連接者
@ServerEndpoint(value?=?"/websocket/random/")
@Component
public?class?ChatRandomServer?{
????//所有連接
????public?static?ConcurrentHashMap<String,?ChatRandomServer>?webSocketSet?=?new?ConcurrentHashMap<>();
????//與某個(gè)客戶端的連接會(huì)話,需要通過(guò)它來(lái)給客戶端發(fā)送數(shù)據(jù)
????private?Session?session;
????//所有在配對(duì)的ID
????private?static?List<String>?webSocketLiveList?=?new?CopyOnWriteArrayList();
????//自己的id標(biāo)識(shí)
????private?String?id?=?"";
????//連接對(duì)象的id標(biāo)識(shí)
????private?String?toUser?=?"";
????/**
?????*?連接建立成功調(diào)用的方法
?????*/
????@OnOpen
????public?void?onOpen(Session?session)?{
????????session.setMaxIdleTimeout(3600000);
????????this.session?=?session;
????????//獲取用戶ip
????????String?ip?=?IpUtil.getRemoteAddress(session);
????????this.id?=?ip;
????????ChatRandomServer?put?=?webSocketSet.put(this.id,?this);
????????//如果已經(jīng)在隊(duì)里,就不去找對(duì)象
????????if?(put?==?null)?{
????????????try?{
????????????????if?(pair())?{
????????????????????sendMessage("匹配成功");
????????????????}
????????????}?catch?(IOException?e)?{
????????????????e.printStackTrace();
????????????}
????????}?else?{
????????????try?{
????????????????sendMessage("匹配失敗");
????????????????webSocketSet.remove(this.id);?
????????????????session.close();
????????????}?catch?(IOException?e)?{
????????????????e.printStackTrace();
????????????}
????????}
????????log.info("用戶{}加入!當(dāng)前在線人數(shù)為:?{}",?this.id,?webSocketSet.size());
????}
????/**
?????*?連接關(guān)閉調(diào)用的方法
?????*/
????@OnClose
????public?void?onClose()?{
????????ChatRandomServer?UserId?=?webSocketSet.get(toUser);
????????webSocketLiveList.remove(this.id);
????????if?(UserId?!=?null)?{
????????????try?{
????????????????sendToUser(session,?"對(duì)方已離開",?toUser);
????????????}?catch?(IOException?e)?{
????????????????e.printStackTrace();
????????????}
????????}
????????webSocketSet.remove(this.id);
????????log.info("{}連接關(guān)閉!當(dāng)前在線人數(shù):{}, 當(dāng)前在匹配的人數(shù):{}"?,this.id,webSocketSet.size(),?webSocketLiveList.size());
????}
????/**
?????*?收到客戶端消息后調(diào)用的方法
?????*
?????*?@param?message?客戶端發(fā)送過(guò)來(lái)的消息
?????*/
????@OnMessage
????public?void?onMessage(String?message,?Session?session)?{
????????log.info("來(lái)自?{}?的消息:?{}",?this.id,?message);
????????try?{
????????????ChatRandomServer.sendToUser(session,?message,?toUser,?2);
????????}?catch?(IOException?e)?{
????????????e.printStackTrace();
????????}
????}
????@OnError
????public?void?onError(Session?session,?Throwable?error)?{
????????log.error("發(fā)生錯(cuò)誤");
????????error.printStackTrace();
????????try?{
????????????SendSelf(session,"服務(wù)器出現(xiàn)錯(cuò)誤");
????????}?catch?(IOException?e)?{
????????????e.printStackTrace();
????????}
????}
????/**
?????*?發(fā)送消息給自己
?????*/
????public?void?sendMessage(String?message)?throws?IOException?{
????????SendSelf(this.session,?message);
????}
????private?static?void?SendSelf(Session?session,?String?message)?throws?IOException?{
????????session.getBasicRemote().sendText(message);
????}
????/**
?????*?發(fā)送信息給指定ID用戶
?????*/
????public?static?void?sendToUser(Session?session,?String?message,?String?sendUserId)?throws?IOException?{
????????ChatRandomServer?UserId?=?webSocketSet.get(sendUserId);
????????if?(UserId?!=?null)?{
????????????UserId.sendMessage(message);
????????}?else?{
????????????SendSelf(session,?"發(fā)送失敗");
????????}
????}
????/**
?????*?通知除了自己之外的所有人
?????*/
????private?void?sendOnlineCount(String?message)?{
????????for?(String?key?:?webSocketSet.keySet())?{
????????????try?{
????????????????if?(key.equals(id))?{
????????????????????continue;
????????????????}
????????????????webSocketSet.get(key).sendMessage(message);
????????????}?catch?(IOException?e)?{
????????????????e.printStackTrace();
????????????}
????????}
????}
????/**
?????*?發(fā)送信息給所有人
?????*/
????public?void?sendToAll(String?message)?throws?IOException?{
????????for?(String?key?:?webSocketSet.keySet())?{
????????????try?{
????????????????webSocketSet.get(key).sendMessage(message);
????????????}?catch?(IOException?e)?{
????????????????e.printStackTrace();
????????????}
????????}
????}
????public?synchronized?boolean?pair()?throws?IOException?{
????????//是否存在等待匹配的用戶
????????if?(webSocketLiveList.size()?>?0)?{
????????????//隨機(jī)匹配一個(gè)
????????????Random?ra?=?new?Random();
????????????int?nextInt?=?ra.nextInt(webSocketLiveList.size());
????????????toUser?=?webSocketLiveList.get(nextInt);
????????????try?{
????????????????ChatRandomServer?UserId?=?webSocketSet.get(toUser);
????????????????UserId.setToUser(id);
????????????????sendToUser(session,?"配對(duì)成功",?toUser);
????????????}?catch?(IOException?e)?{
????????????????e.printStackTrace();
????????????}
????????????webSocketLiveList.remove(nextInt);
????????????return?true;
????????}
????????//沒(méi)有匹配的,則將自己加入等待匹配隊(duì)列
????????webSocketLiveList.add(id);
????????return?false;
????}
}
4. 前端支持
?start:?function?()?{
????????if?(typeof?(WebSocket)?===?"undefined")?{
??????????alert("您的瀏覽器不支持socket")
????????}?else?{
??????????//?實(shí)例化socket
??????????this.socket?=?new?WebSocket(`ws://localhost:8082/websocket/room`);
??????????//?監(jiān)聽socket連接
??????????this.socket.onopen?=?this.open
??????????//?監(jiān)聽socket錯(cuò)誤信息
??????????this.socket.onerror?=?this.error
??????????//?監(jiān)聽socket消息
??????????this.socket.onmessage?=?this.getMessage
??????????this.socket.onclose?=?this.close
????????}
??????},
??????open:?function?()?{
??????},
??????error:?function?()?{
??????},
??????getMessage:?function?(obj)?{
????????//接收信息后根據(jù)不同情況不同處理方式
????????let?data?=?JSON.parse(obj.data);
????????if?(data.code?===?1)?{
????????}?else?if?(data.code?===?2)?{
????????}?else?{
????????}
??????},
??????close:?function?(e)?{
??????},
??????doSend:?function?()?{
????????if?(that.sendData?===?'')?{
??????????return;
????????}
????????this.socket.send(that.sendData);
????????that.sendData?=?'';
??????},
以上代碼不完整,如果需要看下完整代碼,聯(lián)系我。

評(píng)論
圖片
表情
