Netty基础—7.Netty实现消息推送服务二

大纲

1.Netty实现HTTP服务器

2.Netty实现WebSocket

3.Netty实现的消息推送系统

(1)基于WebSocket的消息推送系统说明

(2)消息推送系统的PushServer

(3)消息推送系统的连接管理封装

(4)消息推送系统的ping-pong探测

(5)消息推送系统的全连接推送

(6)消息推送系统的HTTP响应和握手

(7)消息推送系统的运营客户端

(8)运营客户端连接PushServer

(9)运营客户端的Handler处理器

(10)运营客户端发送推送消息

(11)浏览器客户端接收推送消息

3.Netty实现的消息推送系统

(1)基于WebSocket的消息推送系统说明

(2)消息推送系统的PushServer

(3)消息推送系统的连接管理封装

(4)消息推送系统的ping-pong探测

(5)消息推送系统的全连接推送

(6)消息推送系统的HTTP响应和握手

(7)消息推送系统的运营客户端

(8)运营客户端连接PushServer

(9)运营客户端的Handler处理器

(10)运营客户端发送推送消息

(11)浏览器客户端接收推送消息

(1)基于WebSocket的消息推送系统说明

首先需要一个运营系统能够基于NettyClient和PushServer建立WebSocket长连接,然后浏览器客户端也要和PushServer建立好WebSocket长连接,接着运营系统会让NettyClient发送Push推送消息给PushServer,最后PushServer再把推送消息发送给浏览器客户端。

首先启动PushServer,然后打开多个网页客户端查看console,接着启动运营客系统在控制台输入消息,这样就可以完成一个完整的消息推送的交互了。

(2)消息推送系统的PushServer

复制代码
public class NettyPushServer {
    private static final Logger logger = LogManager.getLogger(NettyPushServer.class);
    private static final int DEFAULT_PORT = 8998;
    private int port;
    
    public NettyPushServer(int port) {
        this.port = port;
    }
    
    public void start() throws Exception {
        logger.info("Netty Push Server is starting.");
        EventLoopGroup bossEventLoopGroup = new NioEventLoopGroup();
        EventLoopGroup workerEventLoopGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossEventLoopGroup, workerEventLoopGroup)
            .channel(NioServerSocketChannel.class)
            .childHandler(new ChannelInitializer<SocketChannel>() {
                protected void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline()
                    .addLast("logging", new LoggingHandler("DEBUG"))
                    .addLast("http-codec", new HttpServerCodec())
                    .addLast("aggregator", new HttpObjectAggregator(65536))
                    .addLast("http-chunked", new ChunkedWriteHandler())
                    .addLast("netty-push-server-handler", new NettyPushServerHandler());
                }
            });
            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
            logger.info("Netty Push Server is started, listened[" + port + "].");
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossEventLoopGroup.shutdownGracefully();
            workerEventLoopGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        NettyPushServer nettyHttpServer = new NettyPushServer(DEFAULT_PORT);
        nettyHttpServer.start();
    }
}

public class NettyPushServerHandler extends SimpleChannelInboundHandler<Object> {
    private static final Logger logger = LogManager.getLogger(NettyPushServerHandler.class);
    
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        logger.info("Client Connection Established: " + ctx.channel());
    }
    
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        logger.info("Client Disconnected: " + ctx.channel());
    }
    
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof FullHttpRequest) {
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        } else if(msg instanceof WebSocketFrame) {
            handleWebSocketFrame(ctx, (WebSocketFrame) msg);
        }
    }
    
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }
    
    private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame webSocketFrame) {


    }
    
    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) {


    }
}

(3)消息推送系统的连接管理封装

复制代码
//用来管理连接
public class ChannelManager {
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    private static ConcurrentHashMap<String, ChannelId> channelIds = new ConcurrentHashMap<String, ChannelId>();
    
    public static void add(Channel channel) {
        channelGroup.add(channel);
        channelIds.put(channel.id().asShortText(), channel.id());
    }
    
    public static void remove(Channel channel) {
        channelGroup.remove(channel);
        channelIds.remove(channel.id().asShortText());
    }
    
    public static Channel get(String id) {
        return channelGroup.find(channelIds.get(id));
    }
    
    public static void pushToAllChannels(TextWebSocketFrame webSocketFrame) {
        channelGroup.writeAndFlush(webSocketFrame);
    }
}

public class NettyPushServerHandler extends SimpleChannelInboundHandler<Object> {
    ...
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        logger.info("Client Connection Established: " + ctx.channel());
        ChannelManager.add(ctx.channel());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        logger.info("Client Disconnected: " + ctx.channel());
        ChannelManager.remove(ctx.channel());
    }
    ...
}

(4)消息推送系统的ping-pong探测

复制代码
public class NettyPushServerHandler extends SimpleChannelInboundHandler<Object> {
    ...
    private WebSocketServerHandshaker webSocketServerHandshaker;
    ...
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof FullHttpRequest) {
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        } else if(msg instanceof WebSocketFrame) {
            handleWebSocketFrame(ctx, (WebSocketFrame) msg);
        }
    }
    
    private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame webSocketFrame) {
        //WebSocket网页客户端发送的是ping消息,它会不停的ping服务端,看看长连接是否存活和有效
        if (webSocketFrame instanceof PingWebSocketFrame) {
            logger.info("Receive ping frame from client: " + ctx.channel());
            WebSocketFrame pongWebSocketFrame = new PongWebSocketFrame(webSocketFrame.content().retain());
            ctx.channel().write(pongWebSocketFrame);
            return;
        }
        //WebSocket网页客户端发送一个请求过来,请求关闭这个WebSocket连接
        if (webSocketFrame instanceof CloseWebSocketFrame) {
            logger.info("Receive close WebSocket request from client: " + ctx.channel());
            webSocketServerHandshaker.close(ctx.channel(), ((CloseWebSocketFrame) webSocketFrame).retain());
            return;
        }
        ...
    }
    ...
}

(5)消息推送系统的全连接推送

复制代码
public class NettyPushServerHandler extends SimpleChannelInboundHandler<Object> {
    ...
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof FullHttpRequest) {
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        } else if(msg instanceof WebSocketFrame) {
            handleWebSocketFrame(ctx, (WebSocketFrame) msg);
        }
    }
    
    private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame webSocketFrame) {
        //WebSocket网页客户端发送的是ping消息,它会不停的ping服务端,看看长连接是否存活和有效
        if (webSocketFrame instanceof PingWebSocketFrame) {
            logger.info("Receive ping frame from client: " + ctx.channel());
            WebSocketFrame pongWebSocketFrame = new PongWebSocketFrame(webSocketFrame.content().retain());
            ctx.channel().write(pongWebSocketFrame);
            return;
        }
        
        //WebSocket网页客户端发送一个请求过来,请求关闭这个WebSocket连接
        if (webSocketFrame instanceof CloseWebSocketFrame) {
            logger.info("Receive close WebSocket request from client: " + ctx.channel());
            webSocketServerHandshaker.close(ctx.channel(), ((CloseWebSocketFrame) webSocketFrame).retain());
            return;
        }
        
        //WebSocket网页客户端发送请求,但它不是text文本请求
        if (!(webSocketFrame instanceof TextWebSocketFrame)) {
            logger.error("Netty Push Server only support text frame, does not support other type frame.");
            String errorMsg = String.format("%s type frame is not supported.", webSocketFrame.getClass().getName());
            throw new UnsupportedOperationException(errorMsg);
        }
        
        //WebSocket网页客户端发送一个文本请求过来,是TextFrame类型的
        String request = ((TextWebSocketFrame)webSocketFrame).text();
        logger.info("Receive text frame[" + request + "] from client: " + ctx.channel());
      
        //构建响应
        TextWebSocketFrame response = new TextWebSocketFrame(request);
        //发送给所有连接,全连接推送
        ChannelManager.pushToAllChannels(response);
    }
    ...
}

(6)消息推送系统的HTTP响应和握手

复制代码
public class NettyPushServerHandler extends SimpleChannelInboundHandler<Object> {
    ...
    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) {
        if (!request.decoderResult().isSuccess() || (!"websocket".equals(request.headers().get("Upgrade")))) {
            DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST);
            sendHttpResponse(ctx, request, response);
            return;
        }
        logger.info("Receive handshake request from client: " + ctx.channel());
        
        //握手建立
        WebSocketServerHandshakerFactory factory = new WebSocketServerHandshakerFactory("ws://localhost:8998/push", null, false);
        webSocketServerHandshaker = factory.newHandshaker(request);
        if (webSocketServerHandshaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            webSocketServerHandshaker.handshake(ctx.channel(), request);
            logger.info("Netty push server handshake with client: " + ctx.channel());
        }
    }
    
    //HTTP响应
    private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest request, DefaultFullHttpResponse response) {
        if (response.status().code() != RESPONSE_CODE_OK) {
            ByteBuf byteBuf = Unpooled.copiedBuffer(response.status().toString(), CharsetUtil.UTF_8);
            response.content().writeBytes(byteBuf);
            logger.info("Http Response is not ok: " + byteBuf.toString(CharsetUtil.UTF_8));
            byteBuf.release();
        }
        ChannelFuture channelFuture = ctx.channel().writeAndFlush(response);
        if (response.status().code() != RESPONSE_CODE_OK) {
            channelFuture.addListener(ChannelFutureListener.CLOSE);
        }
    }
    ...
}

(7)消息推送系统的运营客户端

复制代码
public class OperationNettyClient {
    private static final Logger logger = LogManager.getLogger(OperationNettyClient.class);
    private static final String WEB_SOCKET_SCHEME = "ws";
    private static final String WSS_SCHEME = "wss";
    private static final String LOCAL_HOST = "127.0.0.1";
    private static final String PUSH_SERVER_URI = System.getProperty("url", "ws://127.0.0.1:8998/push");
  
    private static URI uri;
    private static String scheme;
    private static String host;
    private static int port;
    private static SslContext sslContext;

    private EventLoopGroup eventLoopGroup;

    public void start() throws Exception {
        //...
    }

    public static void main(String[] args) throws Exception {
        uri = new URI(PUSH_SERVER_URI);
        scheme = getScheme(uri);
        host = getHost(uri);
        port = getPort(uri, scheme);

        checkScheme(scheme);
        initSslContext(scheme);
    }
    
    private static String getScheme(URI pushServerUri) {
        return pushServerUri.getScheme() == null ? WEB_SOCKET_SCHEME : pushServerUri.getScheme();
    }
    
    private static String getHost(URI pushServerUri) {
        return pushServerUri.getHost() == null ? LOCAL_HOST : pushServerUri.getHost();
    }
    
    private static int getPort(URI pushServerUri, String scheme) {
        int port;
        if (pushServerUri.getPort() == -1) {
            if (WEB_SOCKET_SCHEME.equals(scheme)) {
                port = 80;
            } else if(WSS_SCHEME.equals(scheme)) {
                port = 443;
            } else {
                port = -1;
            }
        } else {
            port = pushServerUri.getPort();
        }
        return port;
    }
    
    //检查scheme是否是ws或wss
    private static void checkScheme(String scheme) {
        if (!WEB_SOCKET_SCHEME.equals(scheme) && !WSS_SCHEME.equals(scheme)) {
            logger.error("Only Support ws or wss scheme.");
            throw new RuntimeException("Only Support ws or wss scheme.");
        }
    }
    
    //如果WebSocket使用了SSL,也就是wss,那么初始化对应的sslContext
    private static void initSslContext(String scheme) throws Exception {
        boolean enableSSL = WSS_SCHEME.equals(scheme);
        if (enableSSL) {
            sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
        } else {
            sslContext = null;
        }
    }
}

(8)运营客户端连接PushServer

复制代码
public class OperationNettyClient {
    private static final Logger logger = LogManager.getLogger(OperationNettyClient.class);
    private static final String WEB_SOCKET_SCHEME = "ws";
    private static final String WSS_SCHEME = "wss";
    private static final String LOCAL_HOST = "127.0.0.1";
    private static final String PUSH_SERVER_URI = System.getProperty("url", "ws://127.0.0.1:8998/push");
    private static final String INPUT_MESSAGE_QUIT = "quit";
    private static final String INPUT_MESSAGE_CLOSE = "close";
    private static final String INPUT_MESSAGE_PING = "ping";

    private static URI uri;
    private static String scheme;
    private static String host;
    private static int port;
    private static SslContext sslContext;

    private EventLoopGroup eventLoopGroup;

    public Channel start() throws Exception {
        logger.info("Operation Netty Client is connecting.");
        eventLoopGroup = new NioEventLoopGroup();

        WebSocketClientHandshaker webSocketClientHandshaker = WebSocketClientHandshakerFactory.newHandshaker(uri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders());
        final OperationNettyClientHandler operationNettyClientHandler = new OperationNettyClientHandler(webSocketClientHandshaker);

        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(eventLoopGroup)
        .channel(NioSocketChannel.class)
        .handler(new ChannelInitializer<SocketChannel>() {
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline channelPipeline = ch.pipeline();
                if (sslContext != null) {
                    channelPipeline.addLast(sslContext.newHandler(ch.alloc(), host, port));
                }
                channelPipeline.addLast(new HttpClientCodec())
                .addLast(new HttpObjectAggregator(65536))
                .addLast(WebSocketClientCompressionHandler.INSTANCE)
                .addLast(operationNettyClientHandler);
            }
        });
        Channel channel = bootstrap.connect(uri.getHost(), port).sync().channel();
        logger.info("Operation Netty Client connected to push server.");
        operationNettyClientHandler.channelFuture().sync();

        return channel;
    }
    
    public void shutdownGracefully() {
        eventLoopGroup.shutdownGracefully();
    }
    
    public static void main(String[] args) throws Exception {
        uri = new URI(PUSH_SERVER_URI);
        scheme = getScheme(uri);
        host = getHost(uri);
        port = getPort(uri, scheme);

        checkScheme(scheme);
        initSslContext(scheme);

        OperationNettyClient operationNettyClient = new OperationNettyClient();
        try {
            Channel channel = operationNettyClient.start();
        } finally {
            operationNettyClient.shutdownGracefully();
        }
    }
    ...
}

(9)运营客户端的Handler处理器

复制代码
public class OperationNettyClientHandler extends SimpleChannelInboundHandler<Object> {
    private static final Logger logger = LogManager.getLogger(OperationNettyClientHandler.class);
    private WebSocketClientHandshaker webSocketClientHandshaker;
    private ChannelFuture channelFuture;

    public OperationNettyClientHandler(WebSocketClientHandshaker webSocketClientHandshaker) {
        this.webSocketClientHandshaker = webSocketClientHandshaker;
    }
    
    public ChannelFuture channelFuture() {
        return channelFuture;
    }
    
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        channelFuture = ctx.newPromise();
    }
    
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        webSocketClientHandshaker.handshake(ctx.channel());
        logger.info("Operation Netty Client send WebSocket handshake request.");
    }
    
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        logger.info("netty client disconnected.");
    }
    
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        Channel channel = ctx.channel();
        if (!webSocketClientHandshaker.isHandshakeComplete()) {
            try {
                webSocketClientHandshaker.finishHandshake(channel, (FullHttpResponse) msg);
                logger.info("Netty Client connected.");
                ((ChannelPromise)channelFuture).setSuccess();
            } catch(WebSocketHandshakeException e) {
                logger.error("WebSocket handshake failed.", e);
                ((ChannelPromise)channelFuture).setFailure(e);
            }
            return;
        }

        if (msg instanceof FullHttpResponse) {  
            FullHttpResponse response = (FullHttpResponse) msg;
            throw new IllegalStateException("Not Supported HTTP Response.");
        }

        WebSocketFrame webSocketFrame = (WebSocketFrame) msg;
        if (webSocketFrame instanceof TextWebSocketFrame) {
            TextWebSocketFrame textWebSocketFrame = (TextWebSocketFrame) webSocketFrame;
            logger.info("Receives text frame: " + textWebSocketFrame.text());
        } else if(webSocketFrame instanceof PongWebSocketFrame) {
            logger.info("Receives pong frame: " + webSocketFrame);
        } else if(webSocketFrame instanceof CloseWebSocketFrame) {
            logger.info("Receives close WebSocket frame, Netty Client is closing.");
            channel.close();
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        logger.error("Operation Netty client handler exception caught.", cause);
        if (!channelFuture.isDone()) {
            ((ChannelPromise)channelFuture).setFailure(cause);
        }
        ctx.close();
    }
}

(10)运营客户端发送推送消息

复制代码
public class OperationNettyClient {
    ...
    public void waitInputMessage(Channel channel) throws Exception {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        while(true) {
            logger.info("Wait for input message.");
            String message = bufferedReader.readLine();
            if (INPUT_MESSAGE_QUIT.equals(message)) {
                break;
            } else if(INPUT_MESSAGE_CLOSE.equals(message)) {
                channel.writeAndFlush(new CloseWebSocketFrame());
                channel.closeFuture().sync();
                break;
            } else if(INPUT_MESSAGE_PING.equals(message)) {
                WebSocketFrame webSocketFrame = new PingWebSocketFrame(Unpooled.wrappedBuffer(new byte[] {8, 1, 8, 1}));
                channel.writeAndFlush(webSocketFrame);
            } else {
                WebSocketFrame webSocketFrame = new TextWebSocketFrame(message);
                channel.writeAndFlush(webSocketFrame);
            }
        }
    }
    
    public static void main(String[] args) throws Exception {
        uri = new URI(PUSH_SERVER_URI);
        scheme = getScheme(uri);
        host = getHost(uri);
        port = getPort(uri, scheme);
    
        checkScheme(scheme);
        initSslContext(scheme);
    
        OperationNettyClient operationNettyClient = new OperationNettyClient();
        try {
            Channel channel = operationNettyClient.start();
            //运营客户端发送消息入口
            operationNettyClient.waitInputMessage(channel);
        } finally {
            operationNettyClient.shutdownGracefully();
        }
    }
    ...
}

(11)浏览器客户端接收推送消息

复制代码
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
        <title>websocket网页</title>
    </head>
    <body onload="connectServer();">
        <script type="text/javascript">
        var websocket;
        function connectServer() {
            if ("WebSocket" in window) {
                console.log("your browser supports websocket!");
                websocket = new WebSocket("ws://localhost:8998/push");
                websocket.onopen = function() {
                    console.log("established connection with push server.");
                }
                websocket.onmessage = function(ev) {
                    var response = ev.data;
                    console.log("receives push message from netty server: " + response);
                }
            }
        }
				</script>
    </body>
</html>
相关推荐
东阳马生架构23 分钟前
Netty源码—2.Reactor线程模型二
netty·reactor线程模型
CryptoRzz8 小时前
对接股票金融数据源API
网络·python·websocket·网络协议·金融
柃歌12 小时前
【USTC 计算机网络】第二章:应用层 - 应用层原理
笔记·websocket·网络协议·tcp/ip·计算机网络
Edward-tan17 小时前
【玩转全栈】---- Django 基于 Websocket 实现群聊(解决channel连接不了)
网络·websocket·网络协议·django
金丝猴也是猿18 小时前
多款优秀抓包工具介绍:Wireshark与BurpSuite静静的时光机2025-02-24 05:02
websocket·网络协议·tcp/ip·http·网络安全·https·udp
东阳马生架构1 天前
Netty源码—2.Reactor线程模型一
netty
她的双马尾1 天前
WebSocket:开启实时通信的新篇章
网络·websocket·网络协议
东阳马生架构2 天前
Netty基础—6.Netty实现RPC服务一
netty·rpc服务