软件编程
位置:首页>> 软件编程>> java编程>> Java实战之用springboot+netty实现简单的一对一聊天

Java实战之用springboot+netty实现简单的一对一聊天

作者:caiyang2015  发布时间:2023-12-03 07:28:19 

标签:Java,springboot,netty,聊天,一对一聊天

一、引入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实现简单的一对一聊天

 Java实战之用springboot+netty实现简单的一对一聊天

来源:https://blog.csdn.net/u014190164/article/details/116093343

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com