springboot整合netty 使用WebSocket进行不同房间多人聊天

1.登录账号

springboot整合netty 使用WebSocket进行不同房间多人聊天_第1张图片

2.登陆后出现预先存在的房间


3.点击后进入房间

springboot整合netty 使用WebSocket进行不同房间多人聊天_第2张图片

4.两个不同房间内只能收本房间有的用户信息

springboot整合netty 使用WebSocket进行不同房间多人聊天_第3张图片

springboot整合netty 使用WebSocket进行不同房间多人聊天_第4张图片

下面上代码

pom.xml 添加netty的依赖


		
			io.netty
			netty-all
			5.0.0.Alpha1
		
@SpringBootApplication
public class App implements CommandLineRunner {
	
	public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }

    //实现CommandLineRunner 重写run方法 这里放了netty的启动
    @Override
    public void run(String... args) throws Exception {
        new NettyService();
    }
}
/**
 *
 * @author Dream
 *  Netty的服务
 *
 */
public class NettyService {

    public NettyService() {
        /*new Thread(() -> {*/
            System.out.println("启动Netty!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workerGroup = new NioEventLoopGroup();
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.group(bossGroup, workerGroup)
                        .channel(NioServerSocketChannel.class)
                        .childHandler(new MyChannelInitializer());
                Channel channel = serverBootstrap.bind(9099).sync().channel();
                channel.closeFuture().sync();
            } catch (Exception e) {
                System.err.println(e);
            } finally {
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        /*}).start();*/
    }

}

public class MyChannelInitializer extends ChannelInitializer {

    @Override
    protected void initChannel(Channel channel) throws Exception {
        ChannelPipeline pipeline = channel.pipeline();
        pipeline.addLast("http-codec", new HttpServerCodec()); // Http消息编码解码
        pipeline.addLast("aggregator", new HttpObjectAggregator(65536)); // Http消息组装
        pipeline.addLast("http-chunked", new ChunkedWriteHandler()); // WebSocket通信支持
        pipeline.addLast("handler", new MyHandler());//指定房间
/**
 *
 * @author Dream
 *
 *  自定义的Handler
 */
public class MyHandler extends SimpleChannelInboundHandler {

    private WebSocketServerHandshaker handshaker;
    private ChannelHandlerContext ctx;
    private String sessionId;
    private String table;
    private String name;

    @Override
    protected void messageReceived(ChannelHandlerContext ctx, Object o) throws Exception {
        if (o instanceof FullHttpRequest) { // 传统的HTTP接入
            handleHttpRequest(ctx, (FullHttpRequest) o);
        } else if (o instanceof WebSocketFrame) { // WebSocket接入
            handleWebSocketFrame(ctx, (WebSocketFrame) o);
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }

    @Override
    public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
        super.close(ctx, promise);
        System.out.println("delete : id = " + this.sessionId + " table = " + this.table);
        //关闭连接将移除该用户消息
        InformationOperateMap.delete(this.sessionId, this.table);
        Mage mage = new Mage();
        mage.setName(this.name);
        mage.setMessage("20002");
        //将用户下线信息发送给为下线用户
        InformationOperateMap.map.get(this.table).forEach((id, iom) -> {
            try {
                iom.sead(mage);
            } catch (Exception e) {
                System.err.println(e);
            }
        });
    }

    /**
     * 处理Http请求,完成WebSocket握手
* 注意:WebSocket连接第一次请求使用的是Http * @param ctx * @param request * @throws Exception */ private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { // 如果HTTP解码失败,返回HTTP异常 if (!request.getDecoderResult().isSuccess() || (!"websocket".equals(request.headers().get("Upgrade")))) { sendHttpResponse(ctx, request, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST)); return; } // 正常WebSocket的Http连接请求,构造握手响应返回 WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://" + request.headers().get(HttpHeaders.Names.HOST), null, false); handshaker = wsFactory.newHandshaker(request); if (handshaker == null) { // 无法处理的websocket版本 WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel()); } else { // 向客户端发送websocket握手,完成握手 handshaker.handshake(ctx.channel(), request); // 记录管道处理上下文,便于服务器推送数据到客户端 this.ctx = ctx; } } /** * Http返回 * @param ctx * @param request * @param response */ private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest request, FullHttpResponse response) { System.out.println("sendHttpResponse:7"); // 返回应答给客户端 if (response.getStatus().code() != 200) { ByteBuf buf = Unpooled.copiedBuffer(response.getStatus().toString(), CharsetUtil.UTF_8); response.content().writeBytes(buf); buf.release(); HttpHeaders.setContentLength(response, response.content().readableBytes()); } // 如果是非Keep-Alive,关闭连接 ChannelFuture f = ctx.channel().writeAndFlush(response); if (!HttpHeaders.isKeepAlive(request) || response.getStatus().code() != 200) { f.addListener(ChannelFutureListener.CLOSE); } } /** * 处理Socket请求 * @param ctx * @param frame * @throws Exception */ private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception { // 判断是否是关闭链路的指令 if (frame instanceof CloseWebSocketFrame) { handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain()); return; } // 判断是否是Ping消息 if (frame instanceof PingWebSocketFrame) { ctx.channel().write(new PongWebSocketFrame(frame.content().retain())); return; } // 当前只支持文本消息,不支持二进制消息 if ((frame instanceof TextWebSocketFrame)) { //throw new UnsupportedOperationException("当前只支持文本消息,不支持二进制消息"); //获取发来的消息 String text =((TextWebSocketFrame)frame).text(); System.out.println("mage : " + text); //消息转成Mage Mage mage = Mage.strJson2Mage(text); //判断是以存在用户信息 if (InformationOperateMap.isNo(mage)) { //判断是否有这个聊天室 if (InformationOperateMap.map.containsKey(mage.getTable())) { //判断是否有其他用户 if (InformationOperateMap.map.get(mage.getTable()).size() > 0) { InformationOperateMap.map.get(mage.getTable()).forEach((id, iom) -> { try { Mage mag = iom.getMage(); mag.setMessage("30003"); //发送其他用户信息给要注册用户 this.sendWebSocket(mag.toJson()); } catch (Exception e) { System.err.println(e); } }); } } //添加用户 InformationOperateMap.add(ctx, mage); System.out.println("add : " + mage.toJson()); } //将用户发送的消息发给所有在同一聊天室内的用户 InformationOperateMap.map.get(mage.getTable()).forEach((id, iom) -> { try { iom.sead(mage); } catch (Exception e) { System.err.println(e); } }); //记录id和table 当页面刷新或浏览器关闭时,注销掉此链路 this.sessionId = mage.getId(); this.table = mage.getTable(); this.name = mage.getName(); } else { System.err.println("------------------error--------------------------"); } } /** * WebSocket返回 */ public void sendWebSocket(String msg) throws Exception { if (this.handshaker == null || this.ctx == null || this.ctx.isRemoved()) { throw new Exception("尚未握手成功,无法向客户端发送WebSocket消息"); } //发送消息 this.ctx.channel().write(new TextWebSocketFrame(msg)); this.ctx.flush(); } }
}}
/**
 * 存储信息
 */
public class InformationOperateMap {

    public static ConcurrentMap> map = new ConcurrentHashMap<>();

    private ChannelHandlerContext ctx;
    private Mage mage;

    private InformationOperateMap(ChannelHandlerContext ctx, Mage mage) {
        this.ctx = ctx;
        this.mage = mage;
    }

    /**
     * 添加用户信息
     * @param ctx
     * @param mage
     */
    public static void add(ChannelHandlerContext ctx, Mage mage) {
        InformationOperateMap iom = new InformationOperateMap(ctx, mage);
        ConcurrentMap cmap = new ConcurrentHashMap<>();
        if (map.containsKey(mage.getTable())) {
            map.get(mage.getTable()).put(mage.getId(), iom);
        } else {
            cmap.put(mage.getId(), iom);
            map.put(mage.getTable(), cmap);
        }
    }

    /**
     * 删除用户信息
     * @param id
     * @param table
     */
    public static void delete(String id, String table) {
        ConcurrentMap cmap = map.get(table);
        if (cmap.size() <= 1) {
            map.remove(table);
        } else {
            cmap.remove(id);
        }
    }


    /**
     * 判断是否存在该用户
     * @param mage
     * @return 存在false 不存在true
     */
    public static boolean isNo(Mage mage) {
        return map.containsKey(mage.getTable()) ? map.get(mage.getTable()).containsKey(mage.getId()) ? false : true : true;
    }

    /**
     * 给用户发送消息
     * @param mage
     * @throws Exception
     */
    public void sead(Mage mage) throws Exception{
        //this.ctx.channel().write(new TextWebSocketFrame(mage.toJson()));
        //this.ctx.flush();
        ctx.writeAndFlush(new TextWebSocketFrame(mage.toJson()));
    }

    public Mage getMage() {
        return mage;
    }
}

/**
 * 解析消息
 * 将前台发过来的消息解析成Mage
 * 后台发送消息到前台转成json字符串
 */
@Data
public class Mage {

    private static ObjectMapper gson = new ObjectMapper();
    /*private static Gson gson = new Gson();*/

    /**
     * 那个聊天室
     */
    private String table;
    /**
     * 用户id
     */
    private String id;
    /**
     * 用户名
     */
    private String name;
    /**
     * 所发送的消息
     */
    private String message;

    /**
     * 将json字符串转成Mage
     * @param message
     * @return
     * @throws Exception
     */
    public static Mage strJson2Mage(String message) throws Exception{
        return Strings.isNullOrEmpty(message) ? null : gson.readValue(message, Mage.class);
    }

    /**
     * 将Mage转成json字符串
     * @return
     * @throws Exception
     */
    public String toJson() throws Exception{
        return gson.writeValueAsString(this);
    }

    public Mage setTableId(String table) {
        this.setTable(table);
        return this;
    }
}

前端页面:




    
    netty简单的聊天室
    


    

源码:https://github.com/dream-broken/springbootEasyFrame


参考:https://blog.csdn.net/a906998248/article/details/52839425

        https://blog.csdn.net/weixin_39168678/article/details/79453585


你可能感兴趣的:(springboot整合netty 使用WebSocket进行不同房间多人聊天)