一罢浇、前言
此項(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)題:
- 前端廣播群發(fā)(沒(méi)啥意思,不想寫)
- 前端接收消息必須點(diǎn)一下接谨。比如:
A -> B B頁(yè)面必須在當(dāng)前在線的其他用戶點(diǎn)擊A用戶摆碉,才可以接收消息。并且沒(méi)有任何提現(xiàn)脓豪。(沒(méi)想到什么好的辦法) - A用戶登陸巷帝,并沒(méi)有點(diǎn)退出,直接關(guān)閉頁(yè)面扫夜。(導(dǎo)致Socket一直存在)楞泼。
- 心跳機(jī)制(我的項(xiàng)目里并沒(méi)有加)
- 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