Java實(shí)戰(zhàn)之用springboot+netty實(shí)現(xiàn)簡單的一對(duì)一聊天
一、引入pom
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.chat.info</groupId> <artifactId>chat-server</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> </properties> <dependencies> <!-- web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.33.Final</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.56</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
二、創(chuàng)建netty 服務(wù)端
package com.chat.server; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @Component @Slf4j public class ChatServer { private EventLoopGroup bossGroup; private EventLoopGroup workGroup; private void run() throws Exception { log.info("開始啟動(dòng)聊天服務(wù)器"); bossGroup = new NioEventLoopGroup(1); workGroup = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChatServerInitializer()); //啟動(dòng)服務(wù)器 ChannelFuture channelFuture = serverBootstrap.bind(7000).sync(); log.info("開始啟動(dòng)聊天服務(wù)器結(jié)束"); channelFuture.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); } } /** * 初始化服務(wù)器 */ @PostConstruct() public void init() { new Thread(() -> { try { run(); } catch (Exception e) { e.printStackTrace(); } }).start(); } @PreDestroy public void destroy() throws InterruptedException { if (bossGroup != null) { bossGroup.shutdownGracefully().sync(); } if (workGroup != null) { workGroup.shutdownGracefully().sync(); } } }
package com.chat.server; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; 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.stream.ChunkedWriteHandler; public class ChatServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pipeline = socketChannel.pipeline(); //使用http的編碼器和解碼器 pipeline.addLast(new HttpServerCodec()); //添加塊處理器 pipeline.addLast(new ChunkedWriteHandler()); pipeline.addLast(new HttpObjectAggregator(8192)); pipeline.addLast(new WebSocketServerProtocolHandler("/chat")); //自定義handler,處理業(yè)務(wù)邏輯 pipeline.addLast(new ChatServerHandler()); } }
package com.chat.server; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.chat.config.ChatConfig; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.util.AttributeKey; import lombok.extern.slf4j.Slf4j; import java.time.LocalDateTime; @Slf4j public class ChatServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception { //傳過來的是json字符串 String text = textWebSocketFrame.text(); JSONObject jsonObject = JSON.parseObject(text); //獲取到發(fā)送人的用戶id Object msg = jsonObject.get("msg"); String userId = (String) jsonObject.get("userId"); Channel channel = channelHandlerContext.channel(); if (msg == null) { //說明是第一次登錄上來連接,還沒有開始進(jìn)行聊天,將uid加到map里面 register(userId, channel); } else { //有消息了,開始聊天了 sendMsg(msg, userId); } } /** * 第一次登錄進(jìn)來 * * @param userId * @param channel */ private void register(String userId, Channel channel) { if (!ChatConfig.concurrentHashMap.containsKey(userId)) { //沒有指定的userId ChatConfig.concurrentHashMap.put(userId, channel); // 將用戶ID作為自定義屬性加入到channel中,方便隨時(shí)channel中獲取用戶ID AttributeKey<String> key = AttributeKey.valueOf("userId"); channel.attr(key).setIfAbsent(userId); } } /** * 開發(fā)發(fā)送消息,進(jìn)行聊天 * * @param msg * @param userId */ private void sendMsg(Object msg, String userId) { Channel channel1 = ChatConfig.concurrentHashMap.get(userId); if (channel1 != null) { channel1.writeAndFlush(new TextWebSocketFrame("服務(wù)器時(shí)間" + LocalDateTime.now() + " " + msg)); } } /** * 一旦客戶端連接上來,該方法被執(zhí)行 * * @param ctx * @throws Exception */ @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { log.info("handlerAdded 被調(diào)用" + ctx.channel().id().asLongText()); } /** * 斷開連接,需要移除用戶 * * @param ctx * @throws Exception */ @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { removeUserId(ctx); } /** * 移除用戶 * * @param ctx */ private void removeUserId(ChannelHandlerContext ctx) { Channel channel = ctx.channel(); AttributeKey<String> key = AttributeKey.valueOf("userId"); String userId = channel.attr(key).get(); ChatConfig.concurrentHashMap.remove(userId); log.info("用戶下線,userId:{}", userId); } /** * 處理移除,關(guān)閉通道 * * @param ctx * @param cause * @throws Exception */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); } }
三、存儲(chǔ)用戶channel 的map
package com.chat.config; import io.netty.channel.Channel; import java.util.concurrent.ConcurrentHashMap; public class ChatConfig { public static ConcurrentHashMap<String, Channel> concurrentHashMap = new ConcurrentHashMap(); }
四、客戶端html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> <script> var socket; //判斷當(dāng)前瀏覽器是否支持websocket if (window.WebSocket) { //go on socket = new WebSocket("ws://localhost:7000/chat"); //相當(dāng)于channelReado, ev 收到服務(wù)器端回送的消息 socket.onmessage = function (ev) { var rt = document.getElementById("responseText"); rt.value = rt.value + "\n" + ev.data; } //相當(dāng)于連接開啟(感知到連接開啟) socket.onopen = function (ev) { var rt = document.getElementById("responseText"); rt.value = "連接開啟了.." var userId = document.getElementById("userId").value; var myObj = {userId: userId}; var myJSON = JSON.stringify(myObj); socket.send(myJSON) } //相當(dāng)于連接關(guān)閉(感知到連接關(guān)閉) socket.onclose = function (ev) { var rt = document.getElementById("responseText"); rt.value = rt.value + "\n" + "連接關(guān)閉了.." } } else { alert("當(dāng)前瀏覽器不支持websocket") } //發(fā)送消息到服務(wù)器 function send(message) { if (!window.socket) { //先判斷socket是否創(chuàng)建好 return; } if (socket.readyState == WebSocket.OPEN) { //通過socket 發(fā)送消息 var sendId = document.getElementById("sendId").value; var myObj = {userId: sendId, msg: message}; var messageJson = JSON.stringify(myObj); socket.send(messageJson) } else { alert("連接沒有開啟"); } } </script> </head> <body> <h1 th:text="${userId}"></h1> <input type="hidden" th:value="${userId}" id="userId"> <input type="hidden" th:value="${sendId}" id="sendId"> <form onsubmit="return false"> <textarea name="message" style="height: 300px; width: 300px"></textarea> <input type="button" value="發(fā)送" onclick="send(this.form.message.value)"> <textarea id="responseText" style="height: 300px; width: 300px"></textarea> <input type="button" value="清空內(nèi)容" onclick="document.getElementById('responseText').value=''"> </form> </body> </html>
五、controller 模擬用戶登錄以及要發(fā)送信息給誰
package com.chat.controller; import com.chat.config.ChatConfig; import io.netty.channel.Channel; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; @Controller public class ChatController { @GetMapping("login") public String login(Model model, @RequestParam("userId") String userId, @RequestParam("sendId") String sendId) { model.addAttribute("userId", userId); model.addAttribute("sendId", sendId); return "chat"; } @GetMapping("sendMsg") public String login(@RequestParam("sendId") String sendId) throws InterruptedException { while (true) { Channel channel = ChatConfig.concurrentHashMap.get(sendId); if (channel != null) { channel.writeAndFlush(new TextWebSocketFrame("test")); Thread.sleep(1000); } } } }
六、測試
登錄成功要發(fā)消息給bbb
登錄成功要發(fā)消息給aaa
到此這篇關(guān)于Java實(shí)戰(zhàn)之用springboot+netty實(shí)現(xiàn)簡單的一對(duì)一聊天的文章就介紹到這了,更多相關(guān)springboot+netty實(shí)現(xiàn)一對(duì)一聊天內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)戰(zhàn)之鮮花商城系統(tǒng)的實(shí)現(xiàn)
這篇文章主要介紹了如何利用Java語言實(shí)現(xiàn)鮮花商城系統(tǒng),文中采用的技術(shù)有Spring、SpringMVC、Mybatis、JSP等,感興趣的小伙伴可以了解一下2022-05-05springboot程序啟動(dòng)慢-未配置hostname的解決
這篇文章主要介紹了springboot程序啟動(dòng)慢-未配置hostname的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08JAVA反射機(jī)制中g(shù)etClass和class對(duì)比分析
這篇文章主要介紹了JAVA反射機(jī)制中g(shù)etClass和class對(duì)比分析,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11IDEA?Debug過程中使用Drop?Frame或Reset?Frame實(shí)現(xiàn)操作回退的方法
在IDEA中就提供了一個(gè)幫助你回退代碼的機(jī)會(huì),但這個(gè)方法并不是萬能的,好了,下面就來具體說說IDEA?Debug過程中使用Drop?Frame或Reset?Frame實(shí)現(xiàn)操作回退的方法,感興趣的朋友一起看看吧2022-04-04JAVA編程實(shí)現(xiàn)TCP網(wǎng)絡(luò)通訊的方法示例
這篇文章主要介紹了JAVA編程實(shí)現(xiàn)TCP網(wǎng)絡(luò)通訊的方法,簡單說明了TCP通訊的原理并結(jié)合具體實(shí)例形式分析了java實(shí)現(xiàn)TCP通訊的步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-08-08Java?easyExcel的復(fù)雜表頭多級(jí)表頭導(dǎo)入
最近在項(xiàng)目開發(fā)中遇到的一個(gè)excel復(fù)雜表頭的導(dǎo)入數(shù)據(jù)庫操作,下面這篇文章主要給大家介紹了關(guān)于Java?easyExcel的復(fù)雜表頭多級(jí)表頭導(dǎo)入的相關(guān)資料,需要的朋友可以參考下2022-06-06