摘要:開始聊天發(fā)送聊天信息時(shí)消息,這樣后端就知道是誰要發(fā)給誰,根據(jù)用戶名去找到具體的線程去多帶帶推送消息,實(shí)現(xiàn)單聊。前端待完善左側(cè)聊天列表沒有實(shí)現(xiàn),每搜索一個(gè)在線用戶,應(yīng)該動(dòng)態(tài)顯示在左側(cè),點(diǎn)擊該用戶,動(dòng)態(tài)顯示右側(cè)聊天窗口進(jìn)行消息發(fā)送。
上節(jié)課講了群聊,這次來說說單聊,單聊要比群聊復(fù)雜點(diǎn),但是代碼也不是很多,主要是前端顯示比較麻煩點(diǎn)。
效果:
首先一個(gè)新的用戶,需要先登陸,輸入自己的昵稱,然后點(diǎn)擊登陸。后端服務(wù)會(huì)把你的用戶名和當(dāng)前的線程進(jìn)行邦定,這樣就可以通過你的用戶名找到你的線程。登陸成功,后端返回定義好的消息 success,前端判斷記錄CHAT.me,這樣給別人發(fā)消息時(shí)就可以攜帶自己的信息。
在輸入框輸入用戶名,就可以返回對應(yīng)的用戶的線程,這樣你就可以把消息發(fā)送給你要聊天的對象。如果不存在,后端回返回消息給前端,該用戶不存在。如果存在,就記錄此用戶名到CHAT.to中,這樣你發(fā)送消息的時(shí)候就可以發(fā)送給對應(yīng)用戶了。
發(fā)送聊天信息時(shí)me:to:消息,這樣后端就知道是誰要發(fā)給誰,根據(jù)用戶名去找到具體的線程去多帶帶推送消息,實(shí)現(xiàn)單聊。
前端待完善左側(cè)聊天列表沒有實(shí)現(xiàn),每搜索一個(gè)在線用戶,應(yīng)該動(dòng)態(tài)顯示在左側(cè),點(diǎn)擊該用戶,動(dòng)態(tài)顯示右側(cè)聊天窗口進(jìn)行消息發(fā)送?,F(xiàn)在是你和所有人的單聊消息都會(huì)顯示在右側(cè),沒有完成拆分,因?yàn)檫@是一個(gè)頁面,處理起來比較麻煩,我一個(gè)后端就不花時(shí)間搞了,感興趣的可以自己去實(shí)現(xiàn)。
前端代碼因?yàn)樽⒁暠容^詳細(xì),就直接復(fù)制整個(gè)代碼到這里,大家自己看。
后端改造單人聊天
加入一個(gè)UserMap,邦定user和Channel
package netty; import java.util.HashMap; import java.util.Map; import io.netty.channel.Channel; /** * The class UserMap */ public class UserMap { private HashMapusers = new HashMap(); private static UserMap instance; public static UserMap getInstance () { if (instance == null) { instance = new UserMap(); } return instance; } private UserMap () { } public void addUser(String userId, Channel ch) { this.users.put(userId, ch); } public Channel getUser (String userId) { return this.users.get(userId); } public void deleteUser (Channel ch) { for (Map.Entry map: users.entrySet()) { if (map.getValue() == ch) { users.remove(map.getKey()); break; } } } }
ChatHandler改造
package netty; import java.time.LocalDateTime; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketFrame; import io.netty.util.concurrent.GlobalEventExecutor; /** * */ public class ChatHandler extends SimpleChannelInboundHandler{ public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); public static UserMap usermap = UserMap.getInstance(); /** * 每當(dāng)從服務(wù)端收到新的客戶端連接時(shí),客戶端的 Channel 存入ChannelGroup列表中,并通知列表中的其他客戶端 Channel */ @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { Channel incoming = ctx.channel(); for (Channel channel : channels) { channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 加入 "); } channels.add(ctx.channel()); } /** * 每當(dāng)從服務(wù)端收到客戶端斷開時(shí),客戶端的 Channel 移除 ChannelGroup 列表中,并通知列表中的其他客戶端 Channel */ @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { Channel incoming = ctx.channel(); for (Channel channel : channels) { channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 離開 "); } channels.remove(ctx.channel()); } /** * 會(huì)話建立時(shí) */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { // (5) Channel incoming = ctx.channel(); System.out.println("ChatClient:"+incoming.remoteAddress()+"在線"); } /** * 會(huì)話結(jié)束時(shí) */ @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { // (6) Channel incoming = ctx.channel(); System.out.println("ChatClient:"+incoming.remoteAddress()+"掉線"); //清除離線用戶 this.usermap.deleteUser(incoming); } /** * 出現(xiàn)異常 */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (7) Channel incoming = ctx.channel(); System.out.println("ChatClient:"+incoming.remoteAddress()+"異常"); // 當(dāng)出現(xiàn)異常就關(guān)閉連接 cause.printStackTrace(); ctx.close(); } /** * 讀取客戶端發(fā)送的消息,并將信息轉(zhuǎn)發(fā)給其他客戶端的 Channel。 */ @Override protected void channelRead0(ChannelHandlerContext ctx, Object request) throws Exception { if (request instanceof FullHttpRequest) { //是http請求 FullHttpResponse response = new DefaultFullHttpResponse( HttpVersion.HTTP_1_1,HttpResponseStatus.OK , Unpooled.wrappedBuffer("Hello netty" .getBytes())); response.headers().set("Content-Type", "text/plain"); response.headers().set("Content-Length", response.content().readableBytes()); response.headers().set("connection", HttpHeaderValues.KEEP_ALIVE); ctx.channel().writeAndFlush(response); } else if (request instanceof TextWebSocketFrame) { // websocket請求 //此處id為neety自動(dòng)分配給每個(gè)對話線程的id,有兩種,一個(gè)長id一個(gè)短id,長id唯一,短id可能會(huì)重復(fù) String userId = ctx.channel().id().asLongText(); //客戶端發(fā)送過來的消息 String msg = ((TextWebSocketFrame)request).text(); System.out.println("收到客戶端"+userId+":"+msg); //發(fā)送消息給所有客戶端 群聊 //channels.writeAndFlush(new TextWebSocketFrame(msg)); // 邦定user和channel // 定義每個(gè)上線用戶主動(dòng)發(fā)送初始化信息過來,攜帶自己的name,然后完成綁定 模型 init:[usrname] // 實(shí)際場景中應(yīng)該使用user唯一id if (msg.indexOf("init") != -1) { String userNames[] = msg.split(":"); if ("init".equals(userNames[0])) { // 記錄新的用戶 this.usermap.addUser(userNames[1].trim(), ctx.channel()); ctx.channel().writeAndFlush(new TextWebSocketFrame("success")); } } //搜索在線用戶 消息模型 search:[username] if (msg.indexOf("search") != -1) { Channel ch = this.usermap.getUser(msg.split(":")[1].trim()); if (ch != null) { //此用戶存在 ctx.channel().writeAndFlush(new TextWebSocketFrame("search:"+msg.split(":")[1].trim()+":已找到")); } else { // 此用戶不存在 ctx.channel().writeAndFlush(new TextWebSocketFrame("search:"+msg.split(":")[1].trim()+":未找到")); } } //發(fā)送消息給指定的用戶 消息模型 me:to:[msg] if (msg.split(":").length == 3) { //判斷是單聊消息 this.usermap.getUser(msg.split(":")[1].trim()).writeAndFlush(new TextWebSocketFrame(msg)); } //ctx.channel().writeAndFlush(new TextWebSocketFrame(((TextWebSocketFrame)request).text())); } } }
注釋很詳細(xì),自己看
總結(jié)消息模型應(yīng)該定義一個(gè)多帶帶的類來管理,我目前是用的String字符串來判斷,提前規(guī)定了一些模型,通過判斷來響應(yīng)前端的請求,比較簡單。還有就是沒有使用數(shù)據(jù)庫,前端不能顯示聊天記錄,不能實(shí)現(xiàn)消息的已讀未讀。實(shí)際場景中應(yīng)該對消息進(jìn)行加密存儲(chǔ),且不能窺探用戶隱私。
前端可以使用localstorage來存儲(chǔ)聊天記錄,自己可以擴(kuò)展。
前端的顯示可能有點(diǎn)問題,自己可以調(diào)。其實(shí)主要是學(xué)習(xí)netty后端的搭建
別忘了關(guān)注我 mike啥都想搞
求關(guān)注啊。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/101348.html
摘要:提供異步的事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用程序框架和工具,用以快速開發(fā)高性能高可靠性的網(wǎng)絡(luò)服務(wù)器和客戶端程序??偨Y(jié)我們完成了服務(wù)端的簡單搭建,模擬了聊天會(huì)話場景。 之前一直在搞前端的東西,都快忘了自己是個(gè)java開發(fā)。其實(shí)還有好多java方面的東西沒搞過,突然了解到netty,覺得有必要學(xué)一學(xué)。 介紹 Netty是由JBOSS提供的一個(gè)java開源框架。Netty提供異步的、事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用程序框...
摘要:上節(jié)課完成了的后端搭建,搞定了簡單的請求響應(yīng),今天來結(jié)合前端來完成群聊功能。其實(shí)后端群聊很簡單,就是把一個(gè)用戶的輸入消息,返回給所有在線客戶端,前端去負(fù)責(zé)篩選顯示。 上節(jié)課完成了netty的后端搭建,搞定了簡單的http請求響應(yīng),今天來結(jié)合前端websocket來完成群聊功能。話不多說先上圖:showImg(https://segmentfault.com/img/bVbnCa8?w=...
摘要:今天來做一個(gè)簡單的聊天室支持換房間支持私信的寫的代碼有點(diǎn)渣里面有很多不是很好的地方畢竟我只是一個(gè)野生程序猿環(huán)境地址樣子差不多是這個(gè)樣子的我不想把代碼發(fā)到我的服務(wù)器上因?yàn)檫@個(gè)項(xiàng)目太小了很垃圾而且怕被攻擊這里有錄的一個(gè)演示視頻沒有廣告的你們可以 今天來做一個(gè)簡單的聊天室,支持換房間,支持私信的. 寫的代碼有點(diǎn)渣,里面有很多不是很好的地方.畢竟我只是一個(gè)野生程序猿. 環(huán)境: php7.0...
閱讀 1229·2023-04-26 02:20
閱讀 3351·2021-11-22 14:45
閱讀 4169·2021-11-17 09:33
閱讀 1025·2021-09-06 15:00
閱讀 1495·2021-09-03 10:30
閱讀 3904·2021-07-26 22:01
閱讀 1007·2019-08-30 15:54
閱讀 549·2019-08-30 15:43