利用netty实现websocket ;redis的订阅发布websocket相结合

复制代码
由于Http协议是无状态的,每一次请求只能响应一次,下次请求需要重新连接。
如果客户端请求一个服务端资源,需要实时监服务端执行状态(比如导出大数据量时需要前端监控导出状态),这个时候不断请求连接浪费资源。可以通过WebSocket建立一个长连接,实现客户端与服务端双向交流。

websocket服务器

java 复制代码
public class NioWebSocketServer {
    private final Logger logger=Logger.getLogger(this.getClass());
    private void init(){
        logger.info("正在启动websocket服务器");
        NioEventLoopGroup boss=new NioEventLoopGroup();
        NioEventLoopGroup work=new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap=new ServerBootstrap();
            bootstrap.group(boss,work);
            bootstrap.channel(NioServerSocketChannel.class);
            bootstrap.childHandler(new NioWebSocketChannelInitializer());
            Channel channel = bootstrap.bind(8083).sync().channel();
            logger.info("webSocket服务器启动成功:"+channel);
            channel.closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
            logger.info("运行出错:"+e);
        }finally {
            boss.shutdownGracefully();
            work.shutdownGracefully();
            logger.info("websocket服务器已关闭");
        }
    }

    public static void main(String[] args) {
        new NioWebSocketServer().init();
    }
}

ChannelInitializer

java 复制代码
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;

public class NioWebSocketChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) {
        ch.pipeline().addLast("logging",new LoggingHandler("DEBUG"));//设置log监听器,并且日志级别为debug,方便观察运行流程
        ch.pipeline().addLast("http-codec",new HttpServerCodec());//设置解码器
        ch.pipeline().addLast("aggregator",new HttpObjectAggregator(65536));//聚合器,使用websocket会用到
        ch.pipeline().addLast("http-chunked",new ChunkedWriteHandler());//用于大数据的分区传输
        ch.pipeline().addLast("handler",new NioWebSocketHandler());//自定义的业务handler
    }
}

Hander

java 复制代码
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;
import org.apache.log4j.Logger;
import org.wisdom.netty.global.ChannelSupervise;

import java.util.Date;

import static io.netty.handler.codec.http.HttpUtil.isKeepAlive;

public class NioWebSocketHandler extends SimpleChannelInboundHandler<Object> {

    private final Logger logger=Logger.getLogger(this.getClass());

    private WebSocketServerHandshaker handshaker;

    /**
     重写channelRead0方法,处理接收到的消息
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        logger.debug("收到消息:"+msg);
        if (msg instanceof FullHttpRequest){
            //以http请求形式接入,但是走的是websocket
                handleHttpRequest(ctx, (FullHttpRequest) msg);
        }else if (msg instanceof  WebSocketFrame){
            //处理websocket客户端的消息
            handlerWebSocketFrame(ctx, (WebSocketFrame) msg);
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //添加连接
        logger.debug("客户端加入连接:"+ctx.channel());
        ChannelSupervise.addChannel(ctx.channel());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        //断开连接
        logger.debug("客户端断开连接:"+ctx.channel());
        ChannelSupervise.removeChannel(ctx.channel());
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }
    private void handlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame){
        // 判断是否关闭链路的指令
        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)) {
            logger.debug("本例程仅支持文本消息,不支持二进制消息");
            throw new UnsupportedOperationException(String.format(
                    "%s frame types not supported", frame.getClass().getName()));
        }
        // 返回应答消息
        String request = ((TextWebSocketFrame) frame).text();
        logger.debug("服务端收到:" + request);
        TextWebSocketFrame tws = new TextWebSocketFrame(new Date().toString()
                + ctx.channel().id() + ":" + request);
        // 群发
        ChannelSupervise.send2All(tws);
        // 返回【谁发的发给谁】
        // ctx.channel().writeAndFlush(tws);
    }
    /**
     * 唯一的一次http请求,用于创建websocket
     * */
    private void handleHttpRequest(ChannelHandlerContext ctx,
                                   FullHttpRequest req) {
        //要求Upgrade为websocket,过滤掉get/Post
        if (!req.decoderResult().isSuccess()
                || (!"websocket".equals(req.headers().get("Upgrade")))) {
            //若不是websocket方式,则创建BAD_REQUEST的req,返回给客户端
            sendHttpResponse(ctx, req, new DefaultFullHttpResponse(
                    HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
                "ws://localhost:8083/websocket", null, false);
        handshaker = wsFactory.newHandshaker(req);
        if (handshaker == null) {
            WebSocketServerHandshakerFactory
                    .sendUnsupportedVersionResponse(ctx.channel());
        } else {
            handshaker.handshake(ctx.channel(), req);
        }
    }
    /**
     * 拒绝不合法的请求,并返回错误信息
     * */
    private static void sendHttpResponse(ChannelHandlerContext ctx,
                                         FullHttpRequest req, DefaultFullHttpResponse res) {
        // 返回应答给客户端
        if (res.status().code() != 200) {
            ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(),
                    CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
        }
        ChannelFuture f = ctx.channel().writeAndFlush(res);
        // 如果是非Keep-Alive,关闭连接
        if (!isKeepAlive(req) || res.status().code() != 200) {
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }
}

存储信息

java 复制代码
import io.netty.channel.Channel;
import io.netty.channel.ChannelId;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class ChannelSupervise {
    private   static ChannelGroup GlobalGroup=new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    private  static ConcurrentMap<String, ChannelId> ChannelMap=new ConcurrentHashMap();
    public  static void addChannel(Channel channel){
        /**
          channel(通道是socket的连接信息;) = [id: 0x2a5ba781, L:/127.0.0.1:8083 - R:/127.0.0.1:60663]
          ==ChannelMap=={28176dd1=28176dd1, 2a5ba781=2a5ba781}
         */
        GlobalGroup.add(channel);
        ChannelMap.put(channel.id().asShortText(),channel.id());
        System.out.println("==channel=="+channel);
        System.out.println("==ChannelMap=="+ChannelMap.toString());
    }
    public static void removeChannel(Channel channel){
        GlobalGroup.remove(channel);
        ChannelMap.remove(channel.id().asShortText());
        System.out.println("==removeChannel=="+channel);
        System.out.println("==removeChannelChannelMap=="+ChannelMap.toString());
    }
    public static  Channel findChannel(String id){
        return GlobalGroup.find(ChannelMap.get(id));
    }

    /**
     * 根据channel id 进行 群发通知
     * @param tws
     */
    public static void send2All(TextWebSocketFrame tws){
        GlobalGroup.writeAndFlush(tws);
    }
}

html

java 复制代码
<!-- index.html -->
 
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>WebSocket Test</title>
</head>
<body>
    <h1>WebSocket Test</h1>
    <div>
        <input type="text" id="message" placeholder="Message">
        <button onclick="send()">Send</button>
    </div>
    <div id="output"></div>
    <script>
        var socket = new WebSocket("ws://localhost:8083/");
 
        socket.onopen = function(event) {
            console.log("WebSocket opened: " + event);
        };
 
        socket.onmessage = function(event) {
            console.log("WebSocket message received: " + event.data);
            var output = document.getElementById("output");
            output.innerHTML += "<p>" + event.data + "</p>";
        };
 
        socket.onclose = function(event) {
            console.log("WebSocket closed: " + event);
        };
 
        function send() {
            var message = document.getElementById("message").value;
            socket.send(message);
        }
    </script>
</body>
</html>

redis 订阅

java 复制代码
   public void sendAlarmFaultMessage(String message) {

        String newMessge= null;
        try {
            newMessge = new String(message.getBytes(RedisKeyConstant.UTF8), RedisKeyConstant.UTF8);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        //redisTemplate.convertAndSend(RedisKeyConstant.REDIS_CHANNEL, newMessge);
        RedissonClient redissonClient = SpringUtil.getBean(RedissonClient.class);
        RTopic topic = redissonClient.getTopic(RedisKeyConstant.REDIS_CHANNEL_FAULT);
        topic.publish(newMessge);
        redisTemplate.opsForList().rightPush(RedisKeyConstant.REDIS_MESSAGE_FAULT, newMessge);
    }

redis发布

java 复制代码
@Bean
    public RTopic rFaultTopic(RedissonClient redissonClient) {
        RTopic rTopic = redissonClient.getTopic(RedisKeyConstant.REDIS_CHANNEL_FAULT);
        try{
            if(rTopic != null){
                rTopic.addListener(String.class, (channel, message) -> {
                    if (channel.toString().contains(RedisKeyConstant.REDIS_CHANNEL_FAULT)) {
                        RedisUtil.lpop(RedisKeyConstant.REDIS_MESSAGE_FAULT);
                        log.info("Channel is already fault "+message);
                        AlarmWebsocketService alarmWebsocketService = SpringUtil.getBean(AlarmWebsocketService.class);
                        alarmWebsocketService.sendAllMessage(message);
                    }
                });
            }
        }catch (Exception e){
            log.info("Error sending alarm "+ ExceptionUtils.getStackTrace(e));
        }
        return rTopic;
    }
相关推荐
上海安当技术10 小时前
固件签名与Secure Boot:安当CAS如何构建汽车固件信任链
网络·汽车·信任链·boot·secure·固件签名·安当cas
上海安当技术11 小时前
诊断接入认证:安当CAS如何堵住汽车网络安全的物理后门
网络·web安全·汽车·access·secure·安当cas·诊断接入认证
ze^011 小时前
【软考信息安全】第2章 网络攻击原理与常用方法
网络·web安全·网络安全
这个DBA有点耶12 小时前
数据库一体机架构演进:从硬件堆叠到软硬深度耦合
服务器·网络·数据库·硬件架构·运维开发·database·数据库架构
arbboter12 小时前
【网络工具】NetProxy网络代理用户手册
开发语言·网络·c#·网络代理·网络异常·代理工具
消失的旧时光-194314 小时前
第一篇:Ktor Client 到底是什么?从 Retrofit 迁移理解 Ktor 网络请求架构
网络·架构·retrofit·ktor·dsl
数据知道14 小时前
反序列化漏洞:Java、PHP、Python 三条线各讲透
java·网络·python·安全·网络安全·php
江畔柳前堤14 小时前
Function Calling 与 Tool Calling:从认知到工程的全景深度解析
开发语言·网络·人工智能·深度学习·算法·机器学习·php
LabVIEW开发15 小时前
LabVIEW 通过以太网控制 Yokogawa WT3000 功率分析仪:tmctl.dll 与原始套接字方案
网络·labview·labview知识·labview功能·labview程序
xiaoxiangsiyan15 小时前
企业日常运维高频应用服务全解
运维·网络·云原生·容器·dns