一、引入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>
二、创建netty 服务端
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(\"开始启动聊天服务器\"); bossGroup = new NioEventLoopGroup(1); workGroup = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChatServerInitializer()); //启动服务器 ChannelFuture channelFuture = serverBootstrap.bind(7000).sync(); log.info(\"开始启动聊天服务器结束\"); channelFuture.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); } } /** * 初始化服务器 */ @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,处理业务逻辑 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); //获取到发送人的用户id Object msg = jsonObject.get(\"msg\"); String userId = (String) jsonObject.get(\"userId\"); Channel channel = channelHandlerContext.channel(); if (msg == null) { //说明是第一次登录上来连接,还没有开始进行聊天,将uid加到map里面 register(userId, channel); } else { //有消息了,开始聊天了 sendMsg(msg, userId); } } /** * 第一次登录进来 * * @param userId * @param channel */ private void register(String userId, Channel channel) { if (!ChatConfig.concurrentHashMap.containsKey(userId)) { //没有指定的userId ChatConfig.concurrentHashMap.put(userId, channel); // 将用户ID作为自定义属性加入到channel中,方便随时channel中获取用户ID AttributeKey<String> key = AttributeKey.valueOf(\"userId\"); channel.attr(key).setIfAbsent(userId); } } /** * 开发发送消息,进行聊天 * * @param msg * @param userId */ private void sendMsg(Object msg, String userId) { Channel channel1 = ChatConfig.concurrentHashMap.get(userId); if (channel1 != null) { channel1.writeAndFlush(new TextWebSocketFrame(\"服务器时间\" + LocalDateTime.now() + \" \" + msg)); } } /** * 一旦客户端连接上来,该方法被执行 * * @param ctx * @throws Exception */ @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { log.info(\"handlerAdded 被调用\" + 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); } /** * 处理移除,关闭通道 * * @param ctx * @param cause * @throws Exception */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); } }
三、存储用户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; //判断当前浏览器是否支持websocket if (window.WebSocket) { //go on socket = new WebSocket(\"ws://localhost:7000/chat\"); //相当于channelReado, ev 收到服务器端回送的消息 socket.onmessage = function (ev) { var rt = document.getElementById(\"responseText\"); rt.value = rt.value + \"\\n\" + ev.data; } //相当于连接开启(感知到连接开启) 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) } //相当于连接关闭(感知到连接关闭) socket.onclose = function (ev) { var rt = document.getElementById(\"responseText\"); rt.value = rt.value + \"\\n\" + \"连接关闭了..\" } } else { alert(\"当前浏览器不支持websocket\") } //发送消息到服务器 function send(message) { if (!window.socket) { //先判断socket是否创建好 return; } if (socket.readyState == WebSocket.OPEN) { //通过socket 发送消息 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=\"发送\" onclick=\"send(this.form.message.value)\"> <textarea id=\"responseText\" style=\"height: 300px; width: 300px\"></textarea> <input type=\"button\" value=\"清空内容\" onclick=\"document.getElementById(\'responseText\').value=\'\'\"> </form> </body> </html>
五、controller 模拟用户登录以及要发送信息给谁
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); } } } }
六、测试
登录成功要发消息给bbb
登录成功要发消息给aaa
到此这篇关于Java实战之用springboot+netty实现简单的一对一聊天的文章就介绍到这了,更多相关springboot+netty实现一对一聊天内容请搜索自学编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持自学编程网!