Spring boot + Socket 實(shí)現(xiàn)即時(shí)通訊

一罢浇、前言

此項(xiàng)目作為練手陆赋,這篇文章作為總結(jié)沐祷。
包名,類名起的比較隨意請(qǐng)見(jiàn)諒奏甫。

后端95%的代碼來(lái)自DavidDingXu
前端樣式

前端代碼寫的不太好戈轿,我也不想改了,也就這樣了阵子。

在看這篇文章之前思杯,請(qǐng)大致看一下java-socket長(zhǎng)連接demo體驗(yàn)這篇文章
項(xiàng)目具體的實(shí)現(xiàn)就不介紹了,全在上面這篇文章里挠进。
記錄一下學(xué)習(xí)的過(guò)程色乾。

效果展示

二、注意點(diǎn)及實(shí)現(xiàn)方案

1 用戶聊天記錄保存和獲取

1.1 問(wèn)題說(shuō)明

項(xiàng)目用沒(méi)有用到數(shù)據(jù)庫(kù)领突,所以用戶消息緩存在內(nèi)存中暖璧。假設(shè):
A 發(fā)送給 B 一條消息 (A -> B)
B 接收到 A 一條消息 (B <- A)
B.Socket無(wú)法推送給前端HTML消息
Socket Client和Server Socket如何保存記錄

1.2 解決思路

1.2.1 消息的保存

Server:消息類型如果是3(FunctionCodeEnum.MESSAGE),添加兩條記錄君旦。比如:

Map<String, List<UserMessage>> map = new HashMap<>(16);
map.put("A",// List UserMessage(SEND,B,data));
map.put("B",// List UserMessage(RECEIVE,A,data));

A Client:A客戶端需要在自己的集合中添加一條發(fā)送的消息澎办。

String userId = A
List<UserMessage> list;
list.add(// UserMessage(SEND,B,data))

B Client:B客戶端(接收方)接收到從Server發(fā)來(lái)的消息,保存集合金砍。

// A Client -> Server -> B Client
String userId = B
List<UserMessage> list;
list.add(// UserMessage(RECEIVE,A,data))

當(dāng)Client斷開(kāi)連接時(shí)候局蚀,保存在客戶端的聊天記錄會(huì)丟失。當(dāng)用戶再次登錄時(shí)恕稠,可從Server中獲取之前聊天記錄琅绅。(此功能目前未實(shí)現(xiàn))

1.2.2 消息的獲取

因?yàn)楹蠖藷o(wú)法主動(dòng)推送給前端,解決辦法有點(diǎn)LOW鹅巍,JS 定時(shí)請(qǐng)求接口獲取用戶聊天記錄千扶。(目前沒(méi)有想到更好的辦法)

1.3 具體代碼實(shí)現(xiàn)

1.3.1 用戶記錄實(shí)體類

public class UserMessage {

    /**
     *  1 接收 2 發(fā)送
     * */
    private Integer state;

    private String userId;

    private String message;

    private Date date;
}

1.3.2 Server端保存記錄

/**
 * 存儲(chǔ) 用戶聊天記錄
 */
ConcurrentHashMap<String,List<UserMessage>> userMessageMap = new ConcurrentHashMap<>();

當(dāng)用戶登錄時(shí),初始化集合骆捧。

 // 初始化用戶記錄
userMessageMap.put(userId, Collections.synchronizedList(new ArrayList<>()));

服務(wù)端接收消息的類型是MESSAGE

// 消息
// 用戶登錄后一定存在 不需要在判斷key存不存在

// A -> B  A - B,message,date,2
userMessageMap.get(messageDto.getUserId()).add(
    new UserMessage(messageDto.getSendUserId(), messageDto.getMessage(), new Date(), MessageCodeEnum.SEND.getValue()));
// B <- A  B - A,message,date,1
userMessageMap.get(messageDto.getSendUserId()).add(
    new UserMessage(messageDto.getUserId(), messageDto.getMessage(), new Date(), MessageCodeEnum.RECEIVE.getValue()));

// A.socket( uId = A, sId = B) -> message -> B.socket( uId = B, sId = A)
// message userId和sendUserId 要倒過(guò)來(lái)
String swap = messageDto.getUserId();
messageDto.setUserId(messageDto.getSendUserId());
messageDto.setSendUserId(swap);
connection.println(JSONObject.toJSONString(messageDto));

1.3.3 Client端保存記錄

封裝Connection澎羞,用戶消息,心跳線程

public class SocketClient {

    private Connection connection;

    /**
     *  保存消息
     * */
    private List<UserMessage> messageList;

    private ScheduledExecutorService clientHeartExecutor;
}

緩存所有Client信息

ConcurrentMap<String, SocketClient> existSocketClientMap = new ConcurrentHashMap<>();

發(fā)送消息時(shí)直接添加到messageList中凑懂。

public void sendMessage(String userId, String sendUserId, String message) {

    existSocketClientMap.get(userId).getMessageList().add(
            new UserMessage(sendUserId, message, new Date(), MessageCodeEnum.SEND.getValue()));

    SocketMessageDto messageDto = new SocketMessageDto();
    messageDto.setFunctionCode(FunctionCodeEnum.MESSAGE.getValue());
    messageDto.setUserId(userId);
    messageDto.setSendUserId(sendUserId);
    messageDto.setMessage(message);
    existSocketClientMap.get(userId).getConnection().println(messageDto);
}

接收消息時(shí)直接添加到messageList中煤痕。

if (functionCode.equals(FunctionCodeEnum.MESSAGE.getValue())) {
    // 消息
    existSocketClientMap.get(socketMessage.getUserId()).getMessageList().add(
            new UserMessage(socketMessage.getSendUserId(), socketMessage.getMessage(), new Date(), MessageCodeEnum.RECEIVE.getValue()));
}
未解決問(wèn)題:
  1. 前端廣播群發(fā)(沒(méi)啥意思,不想寫)
  2. 前端接收消息必須點(diǎn)一下接谨。比如:
    A -> B B頁(yè)面必須在當(dāng)前在線的其他用戶點(diǎn)擊A用戶摆碉,才可以接收消息。并且沒(méi)有任何提現(xiàn)脓豪。(沒(méi)想到什么好的辦法)
  3. A用戶登陸巷帝,并沒(méi)有點(diǎn)退出,直接關(guān)閉頁(yè)面扫夜。(導(dǎo)致Socket一直存在)楞泼。
  4. 心跳機(jī)制(我的項(xiàng)目里并沒(méi)有加)
  5. A登陸后驰徊,無(wú)法繼承上次登陸后記錄。(登陸退出都沒(méi)解決堕阔,還有登陸記錄的問(wèn)題9鞒А!3健)
    牺弹。。时呀。张漂。。谨娜。航攒。
自我總結(jié):

總之,這個(gè)項(xiàng)目確實(shí)學(xué)到了不少東西趴梢,比如handler處理(我的項(xiàng)目里并沒(méi)有用)漠畜。熟悉了Socket,還動(dòng)了腦子坞靶∨杈裕總之這個(gè)練習(xí)還是蠻有意義的?滩愁??
發(fā)現(xiàn)大佬項(xiàng)目有一些自己感覺(jué)不合理的地方辫封,總之還是大佬強(qiáng)硝枉。希望自己以后繼續(xù)努力!>胛ⅰ妻味!也成為一名大佬。

最后撈一下自己jiHongYuan
大佬g(shù)ithubDavidDingXu
大佬知乎DavidDingXu

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末欣福,一起剝皮案震驚了整個(gè)濱河市责球,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拓劝,老刑警劉巖雏逾,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異郑临,居然都是意外死亡栖博,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門厢洞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)仇让,“玉大人典奉,你說(shuō)我怎么就攤上這事∩ミ矗” “怎么了卫玖?”我有些...
    開(kāi)封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)踊淳。 經(jīng)常有香客問(wèn)我假瞬,道長(zhǎng),這世上最難降的妖魔是什么嚣崭? 我笑而不...
    開(kāi)封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任笨触,我火速辦了婚禮,結(jié)果婚禮上雹舀,老公的妹妹穿的比我還像新娘芦劣。我一直安慰自己,他們只是感情好说榆,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布虚吟。 她就那樣靜靜地躺著,像睡著了一般签财。 火紅的嫁衣襯著肌膚如雪串慰。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天唱蒸,我揣著相機(jī)與錄音邦鲫,去河邊找鬼。 笑死神汹,一個(gè)胖子當(dāng)著我的面吹牛庆捺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播屁魏,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼滔以,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了氓拼?” 一聲冷哼從身側(cè)響起你画,我...
    開(kāi)封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎桃漾,沒(méi)想到半個(gè)月后坏匪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡撬统,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年剥槐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宪摧。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡粒竖,死狀恐怖颅崩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蕊苗,我是刑警寧澤沿后,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站朽砰,受9級(jí)特大地震影響尖滚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瞧柔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一漆弄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧造锅,春花似錦撼唾、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至糙箍,卻和暖如春渤愁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背深夯。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工抖格, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咕晋。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓他挎,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親捡需。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,089評(píng)論 1 32
  • 網(wǎng)絡(luò)編程 一.楔子 你現(xiàn)在已經(jīng)學(xué)會(huì)了寫python代碼,假如你寫了兩個(gè)python文件a.py和b.py损姜,分別去運(yùn)...
    go以恒閱讀 1,992評(píng)論 0 6
  • 專業(yè)考題類型管理運(yùn)行工作負(fù)責(zé)人一般作業(yè)考題內(nèi)容選項(xiàng)A選項(xiàng)B選項(xiàng)C選項(xiàng)D選項(xiàng)E選項(xiàng)F正確答案 變電單選GYSZ本規(guī)程...
    小白兔去釣魚閱讀 8,975評(píng)論 0 13
  • 一饰剥、Python簡(jiǎn)介和環(huán)境搭建以及pip的安裝 4課時(shí)實(shí)驗(yàn)課主要內(nèi)容 【Python簡(jiǎn)介】: Python 是一個(gè)...
    _小老虎_閱讀 5,720評(píng)論 0 10
  • 說(shuō)明 本文 翻譯自 realpython 網(wǎng)站上的文章教程 Socket Programming in Pytho...
    keelii閱讀 2,101評(píng)論 0 16