SpringBoot整合Netty之Websocket

版权声明:柠檬乐园:200145783 https://blog.csdn.net/u014431237/article/details/83590263

前后端通过websocket通信进行聊天~ 核心代码整理如下:


netty组件

@Component
public class NettyBooter implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        if(contextRefreshedEvent.getApplicationContext().getParent() == null){
            try {
                //开启WebSocket服务
                WSServer.getInstance().start();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

WSServer.java

/**
 * 考虑反射:
 *   由于在调用 SingletonHolder.instance 的时候,才会对单例进行初始化,而且通过反射,是不能从外部类获取内部类的属性的。
 *   所以这种形式,很好的避免了反射入侵。
 * 考虑多线程:
 *   由于静态内部类的特性,只有在其被第一次引用的时候才会被加载,所以可以保证其线程安全性。
 * 不需要传参的情况下 优先考虑静态内部类
 */
@Component
public class WSServer {

    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;
    private  ServerBootstrap server;
    private ChannelFuture future;

    private static class SingletionWSServer{
        static final WSServer instance = new WSServer();
    }

    public static WSServer getInstance(){
        return SingletionWSServer.instance;
    }

    public WSServer() {
        bossGroup = new NioEventLoopGroup();
       workerGroup =new NioEventLoopGroup();
        server = new ServerBootstrap();

       server.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new WSServerInitialzer());//自定义初始化handler容器
    }

    public void start(){
        //自定义端口
        this.future = server.bind(8088);
    }

}

初始化handler容器类

public class WSServerInitialzer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {

        ChannelPipeline pipeline = socketChannel.pipeline();

        //websocket 基于http协议 所以要有http编解码器
        pipeline.addLast(new HttpServerCodec());

        //对写大数据流的支持
        pipeline.addLast(new ChunkedWriteHandler());

        //对httpMessage进行聚合,聚合成FullHttpRequest或FullHttpResponse
        //几乎在netty中的编程 ,都会使用到此handler
        pipeline.addLast(new HttpObjectAggregator(1024*64));

        //====================以上是使用支持http协议====

        //===================增加心跳===================
        //如果是读写空闲  不处理
        pipeline.addLast(new IdleStateHandler(8,10,12));
       //自定义空闲状态检测
        pipeline.addLast(new HeartBeatHandler());


        /*
        * websocket 服务器处理的协议 ,用于指定给客户端连接访问的路由 :/ws
        * 本handler 会帮你处理一些繁重的复杂的事
        * 会帮你处理握手动作 :handshaking (close,ping,pong)ping+pong=心跳
        * 对于websocket来讲, 都是以frams进行传输的不同的数据类型对应的frames也不同
        * */
        pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));

        //自定义handler
        pipeline.addLast(new ChatHandler());

    }
}

自定义的聊天handelr,其中channelRead0中很多东西没有提供,自己看注释理解吧 ,反正收到前端传来的消息,随你怎么处理,我只是提供一种思路而已

public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    static public ChannelGroup clients =
            new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {

        Channel currentChannel = ctx.channel();
        //获取客户端传输过来的消息
        String content = msg.text();

        System.out.println("接收的数据:" + content);
        //1.获取客户端发送来的消息
        DataContent dataContent = JsonUtils.jsonToPojo(content, DataContent.class);
        Integer action = dataContent.getAction();
        //2判断消息的类型,更具不同的类型来处理不同的业务
        if (action == MsgActionEnum.CONNECT.getType()) {
            //2.1当websocket 第一次open的时候 初始化channel 并把userid和channel进行绑定
            String senderId = dataContent.getMixinMsg().getSenderId();
            UserChannelRel.put(senderId, currentChannel);
        } else if (action == MsgActionEnum.CHAT.getType()) {
            //2.2聊天类型的消息
            MixinMsg mixinMsg = dataContent.getMixinMsg();
            String msgText = mixinMsg.getMsg();
            String recevierId = mixinMsg.getReceiverId();
            String senderId = mixinMsg.getSenderId();

            //保存消息到数据库,并且标记为未签收
            IChatMsgService chatMsgService = (IChatMsgService) SpringUtil.getBean("chatMsgServiceImpl");

            String msgId = chatMsgService.saveMsg(mixinMsg);
            mixinMsg.setMsgId(msgId);

            //构造发送的消息
            DataContent dataContentMsg = new DataContent();
            dataContentMsg.setMixinMsg(mixinMsg);
            //发送消息
            Channel recvchannel = UserChannelRel.get(recevierId);
            //从全局用户channel关系中获取接收方的channel
            if (recvchannel == null) {
                //TODD channel为空代表用户离线 推送消息
            } else {
                //当channel 不为空的时候 从ChannelGroup去查找channnel是否存在\
                Channel findChannel = clients.find(recvchannel.id());
                if (findChannel == null) {
                    //TODD channel为空代表用户离线 推送消息
                } else {
                    //用户在线
                    recvchannel.writeAndFlush(new TextWebSocketFrame(
                            JsonUtils.objectToJson(dataContentMsg)
                    ));
                }
            }
        } else if (action == MsgActionEnum.SIGNED.getType()) {
             //批量签收消息
                ...

        } else if (action == MsgActionEnum.KEEPALIVE.getType()) {
            //2.2心跳类型的消息
            System.out.println("收到【" + ctx.channel() + "】的心跳包!");
        }


        /*
        //群发
        TextWebSocketFrame tws = new TextWebSocketFrame(new Date().toString() + "--"
                + ctx.channel().id() + "===》" + content);


        for (Channel channel : clients) {
            channel.writeAndFlush(tws);
        }*/

//      下面这个方法 和上面的for循环 一致
//       clients.writeAndFlush(tws);

    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        clients.add(ctx.channel());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        //ChannelGroup会自动移除
        clients.remove(ctx.channel());
    }


    //异常处理
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.channel().close();
        clients.remove(ctx.channel());
    }
}

心跳类

//处理心跳
public class HeartBeatHandler extends ChannelInboundHandlerAdapter {
    
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleState state = ((IdleStateEvent) evt).state();
            if (state == IdleState.READER_IDLE) {
                System.out.println("进入读空闲。。。");
            }else if(state == IdleState.WRITER_IDLE){
                System.out.println("进入写空闲。。。");
            }else if(state == IdleState.ALL_IDLE){
                //关闭无用的channel 以防资源浪费
                Channel channel = ctx.channel();
                channel.close();
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }
}

前端 js 实现websocket客户端 :https://blog.csdn.net/wangzhanzheng/article/details/78603532

改造实现如下,仅供参考

// 构建聊天业务CHAT  WEBSocket
			window.CHAT = {
				socket: null,
				init: function() {
					if (window.WebSocket) {

						// 如果当前的状态已经连接,那就不需要重复初始化websocket
						if (CHAT.socket != null &&
							CHAT.socket != undefined &&
							CHAT.socket.readyState == WebSocket.OPEN) {
							return false;
						}

						CHAT.socket = new WebSocket(app.nettyServerUrl);
						CHAT.socket.onopen = CHAT.wsopen,
							CHAT.socket.onclose = CHAT.wsclose,
							CHAT.socket.onerror = CHAT.wserror,
							CHAT.socket.onmessage = CHAT.wsmessage;
					} else {
						alert("不支持ws通信...");
					}
				},
				chat: function(msg) {

					// 如果当前websocket的状态是已打开,则直接发送, 否则重连
					if (CHAT.socket != null &&
						CHAT.socket != undefined &&
						CHAT.socket.readyState == WebSocket.OPEN) {
						CHAT.socket.send(msg);
					} else {
						// 重连websocket
						CHAT.init();
						setTimeout("CHAT.reChat('" + msg + "')", "1000");
					}
					// 渲染快照列表进行展示
				
				},
				reChat: function(msg) {
					console.log("消息重新发送...");
					CHAT.socket.send(msg);
				},
				wsopen: function() {
					console.log("websocket连接已建立...");

				
					// 构建Msg
				
					// 构建DataContent
				
					// 发送websocket
					CHAT.chat(JSON.stringify(dataContent));

					// 每次连接之后,获取用户的未读未签收消息列表
				

					// 定时发送心跳
					setInterval("CHAT.keepalive()", 10000);
				},
				wsmessage: function(e) {
					console.log("接受到消息:" + e.data);
	
				},
				wsclose: function() {
					console.log("连接关闭...");
				},
				wserror: function() {
					console.log("发生错误...");
				},
				signMsgList: function(unSignedMsgIds) {
					// 批量签收 
                                    ...	
				},
				keepalive: function() {				
					// 发送心跳
				        ...
			            
				}
			};

猜你喜欢

转载自blog.csdn.net/u014431237/article/details/83590263