您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“如何使用Springboot+netty實現Web聊天室”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“如何使用Springboot+netty實現Web聊天室”這篇文章吧。
新建Spring項目:
選擇JDK版本:
選擇Spring Web:
項目名稱和位置的設置:
導入.jar包:
gson: https://search.maven.org/artifact/com.google.code.gson/gson/2.8.9/jar
DemoApplication:
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.Environment; import java.net.InetAddress; import java.net.UnknownHostException; @SpringBootApplication public class DemoApplication { public static void main(String[] args) throws UnknownHostException { ConfigurableApplicationContext application = SpringApplication.run(DemoApplication.class, args); Environment env = application.getEnvironment(); String host = InetAddress.getLocalHost().getHostAddress(); String port = env.getProperty("server.port"); System.out.println("[----------------------------------------------------------]"); System.out.println("聊天室啟動成功!點擊進入:\t http://" + host + ":" + port); System.out.println("[----------------------------------------------------------"); WebSocketServer.inst().run(53134); } }
User:
package com.example.demo; import java.util.Objects; public class User { public String id; public String nickname; public User(String id, String nickname) { super(); this.id = id; this.nickname = nickname; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return id.equals(user.getId()); } @Override public int hashCode() { return Objects.hash(id); } public String getUid() { return id; } }
SessionGroup:
package com.example.demo; import com.google.gson.Gson; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.ChannelGroupFuture; import io.netty.channel.group.DefaultChannelGroup; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.util.concurrent.ImmediateEventExecutor; import org.springframework.util.StringUtils; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public final class SessionGroup { private static SessionGroup singleInstance = new SessionGroup(); // 組的映射 private ConcurrentHashMap<String, ChannelGroup> groupMap = new ConcurrentHashMap<>(); public static SessionGroup inst() { return singleInstance; } public void shutdownGracefully() { Iterator<ChannelGroup> groupIterator = groupMap.values().iterator(); while (groupIterator.hasNext()) { ChannelGroup group = groupIterator.next(); group.close(); } } public void sendToOthers(Map<String, String> result, SocketSession s) { // 獲取組 ChannelGroup group = groupMap.get(s.getGroup()); if (null == group) { return; } Gson gson=new Gson(); String json = gson.toJson(result); // 自己發送的消息不返回給自己 // Channel channel = s.getChannel(); // 從組中移除通道 // group.remove(channel); ChannelGroupFuture future = group.writeAndFlush(new TextWebSocketFrame(json)); future.addListener(f -> { System.out.println("完成發送:"+json); // group.add(channel);//發送消息完畢重新添加。 }); } public void addSession(SocketSession session) { String groupName = session.getGroup(); if (StringUtils.isEmpty(groupName)) { // 組為空,直接返回 return; } ChannelGroup group = groupMap.get(groupName); if (null == group) { group = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE); groupMap.put(groupName, group); } group.add(session.getChannel()); } /** * 關閉連接, 關閉前發送一條通知消息 */ public void closeSession(SocketSession session, String echo) { ChannelFuture sendFuture = session.getChannel().writeAndFlush(new TextWebSocketFrame(echo)); sendFuture.addListener(new ChannelFutureListener() { public void operationComplete(ChannelFuture future) { System.out.println("關閉連接:"+echo); future.channel().close(); } }); } /** * 關閉連接 */ public void closeSession(SocketSession session) { ChannelFuture sendFuture = session.getChannel().close(); sendFuture.addListener(new ChannelFutureListener() { public void operationComplete(ChannelFuture future) { System.out.println("發送所有完成:"+session.getUser().getNickname()); } }); } /** * 發送消息 * @param ctx 上下文 * @param msg 待發送的消息 */ public void sendMsg(ChannelHandlerContext ctx, String msg) { ChannelFuture sendFuture = ctx.writeAndFlush(new TextWebSocketFrame(msg)); sendFuture.addListener(f -> {//發送監聽 System.out.println("對所有發送完成:"+msg); }); } }
SocketSession:
package com.example.demo; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.util.AttributeKey; import java.util.HashMap; import java.util.Map; import java.util.UUID; public class SocketSession { public static final AttributeKey<SocketSession> SESSION_KEY = AttributeKey.valueOf("SESSION_KEY"); /** * 用戶實現服務端會話管理的核心 */ // 通道 private Channel channel; // 用戶 private User user; // session唯一標示 private final String sessionId; private String group; /** * session中存儲的session 變量屬性值 */ private Map<String, Object> map = new HashMap<String, Object>(); public SocketSession(Channel channel) {//注意傳入參數channel。不同客戶端會有不同channel this.channel = channel; this.sessionId = buildNewSessionId(); channel.attr(SocketSession.SESSION_KEY).set(this); } // 反向導航 public static SocketSession getSession(ChannelHandlerContext ctx) {//注意ctx,不同的客戶端會有不同ctx Channel channel = ctx.channel(); return channel.attr(SocketSession.SESSION_KEY).get(); } // 反向導航 public static SocketSession getSession(Channel channel) { return channel.attr(SocketSession.SESSION_KEY).get(); } public String getId() { return sessionId; } private static String buildNewSessionId() { String uuid = UUID.randomUUID().toString(); return uuid.replaceAll("-", ""); } public synchronized void set(String key, Object value) { map.put(key, value); } public synchronized <T> T get(String key) { return (T) map.get(key); } public boolean isValid() { return getUser() != null ? true : false; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public Channel getChannel() { return channel; } }
WebSocketServer:
package com.example.demo; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler; import io.netty.handler.stream.ChunkedWriteHandler; import io.netty.handler.timeout.IdleStateHandler; import java.util.concurrent.TimeUnit; public class WebSocketServer { private static WebSocketServer wbss; private static final int READ_IDLE_TIME_OUT = 60; // 讀超時 private static final int WRITE_IDLE_TIME_OUT = 0;// 寫超時 private static final int ALL_IDLE_TIME_OUT = 0; // 所有超時 public static WebSocketServer inst() { return wbss = new WebSocketServer(); } public void run(int port) { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer <SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); // Netty自己的http解碼器和編碼器,報文級別 HTTP請求的解碼和編碼 pipeline.addLast(new HttpServerCodec()); // ChunkedWriteHandler 是用于大數據的分區傳輸 // 主要用于處理大數據流,比如一個1G大小的文件如果你直接傳輸肯定會撐暴jvm內存的; // 增加之后就不用考慮這個問題了 pipeline.addLast(new ChunkedWriteHandler()); // HttpObjectAggregator 是完全的解析Http消息體請求用的 // 把多個消息轉換為一個單一的完全FullHttpRequest或是FullHttpResponse, // 原因是HTTP解碼器會在每個HTTP消息中生成多個消息對象HttpRequest/HttpResponse,HttpContent,LastHttpContent pipeline.addLast(new HttpObjectAggregator(64 * 1024)); // WebSocket數據壓縮 pipeline.addLast(new WebSocketServerCompressionHandler()); // WebSocketServerProtocolHandler是配置websocket的監聽地址/協議包長度限制 pipeline.addLast(new WebSocketServerProtocolHandler("/ws", null, true, 10 * 1024)); // 當連接在60秒內沒有接收到消息時,就會觸發一個 IdleStateEvent 事件, // 此事件被 HeartbeatHandler 的 userEventTriggered 方法處理到 pipeline.addLast( new IdleStateHandler(READ_IDLE_TIME_OUT, WRITE_IDLE_TIME_OUT, ALL_IDLE_TIME_OUT, TimeUnit.SECONDS)); // WebSocketServerHandler、TextWebSocketFrameHandler 是自定義邏輯處理器, pipeline.addLast(new WebSocketTextHandler()); } }); Channel ch = b.bind(port).syncUninterruptibly().channel(); ch.closeFuture().syncUninterruptibly(); // 返回與當前Java應用程序關聯的運行時對象 Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { SessionGroup.inst().shutdownGracefully(); bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } }); } }
WebSocketTextHandler:
package com.example.demo; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.timeout.IdleState; import io.netty.handler.timeout.IdleStateEvent; import java.util.HashMap; import java.util.Map; public class WebSocketTextHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { //@Override protected void channelRead(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { SocketSession session = SocketSession.getSession(ctx); TypeToken<HashMap<String, String>> typeToken = new TypeToken<HashMap<String, String>>() { }; Gson gson=new Gson(); java.util.Map<String,String> map = gson.fromJson(msg.text(), typeToken.getType()); User user = null; switch (map.get("type")) { case "msg": Map<String, String> result = new HashMap<>(); user = session.getUser(); result.put("type", "msg"); result.put("msg", map.get("msg")); result.put("sendUser", user.getNickname()); SessionGroup.inst().sendToOthers(result, session); break; case "init": String room = map.get("room"); session.setGroup(room); String nick = map.get("nick"); user = new User(session.getId(), nick); session.setUser(user); SessionGroup.inst().addSession(session); break; } } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { // 是否握手成功,升級為 Websocket 協議 if (evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) { // 握手成功,移除 HttpRequestHandler,因此將不會接收到任何消息 // 并把握手成功的 Channel 加入到 ChannelGroup 中 new SocketSession(ctx.channel()); } else if (evt instanceof IdleStateEvent) { IdleStateEvent stateEvent = (IdleStateEvent) evt; if (stateEvent.state() == IdleState.READER_IDLE) { System.out.println("bb22"); } } else { super.userEventTriggered(ctx, evt); } } @Override protected void messageReceived(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception { } }
之后項目外創建一個test.html:
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>群聊天室</title> <style type="text/css"> body { margin-right:50px; margin-left:50px; } .ddois { position: fixed; left: 120px; bottom: 30px; } </style> </head> <body> 群名:<input type="text" id="room" name="group" placeholder="請輸入群"> <br /><br /> 昵稱:<input type="text" id="nick" name="name" placeholder="請輸入昵稱"> <br /><br /> <button type="button" onclick="enter()">進入聊天群</button> <br /><br /> <div id="message"></div> <br /><br /> <div class="ddois"> <textarea name="send" id="text" rows="10" cols="30" placeholder="輸入發送消息"></textarea> <br /><br /> <button type="button" onclick="send()">發送</button> </div> <script type="text/javascript"> var webSocket; if (window.WebSocket) { webSocket = new WebSocket("ws://localhost:53134/ws"); } else { alert("抱歉,您的瀏覽器不支持WebSocket協議!"); } //連通之后的回調事件 webSocket.onopen = function() { console.log("已經連通了websocket"); // setMessageInnerHTML("已經連通了websocket"); }; //連接發生錯誤的回調方法 webSocket.onerror = function(event){ console.log("出錯了"); // setMessageInnerHTML("連接失敗"); }; //連接關閉的回調方法 webSocket.onclose = function(){ console.log("連接已關閉..."); } //接收到消息的回調方法 webSocket.onmessage = function(event){ console.log("bbdds"); var data = JSON.parse(event.data) var msg = data.msg; var nick = data.sendUser; switch(data.type){ case 'init': console.log("mmll"); break; case 'msg': console.log("bblld"); setMessageInnerHTML(nick+": "+msg); break; default: break; } } function enter(){ var map = new Map(); var nick=document.getElementById('nick').value; var room=document.getElementById('room').value; map.set("type","init"); map.set("nick",nick); console.log(room); map.set("room",room); var message = Map2Json(map); webSocket.send(message); } function send() { var msg = document.getElementById('text').value; var nick = document.getElementById('nick').value; console.log("1:"+msg); if (msg != null && msg != ""){ var map = new Map(); map.set("type","msg"); map.set("msg",msg); var map2json=Map2Json(map); if (map2json.length < 8000){ console.log("4:"+map2json); webSocket.send(map2json); }else { console.log("文本太長了,少寫一點吧????"); } } } //將消息顯示在網頁上 function setMessageInnerHTML(innerHTML) { document.getElementById("message").innerHTML += innerHTML + "<br/>"; } function Map2Json(map) { var str = "{"; map.forEach(function (value, key) { str += '"'+key+'"'+':'+ '"'+value+'",'; }) str = str.substring(0,str.length-1) str +="}"; return str; } </script> </body> </html>
先運行項目,然后運行html
以上是“如何使用Springboot+netty實現Web聊天室”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。