springboot + netty + websocket client ws wss客户端

直接上代码

1.WebSocketClientHandler.class

/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
//The MIT License
//
//Copyright (c) 2009 Carl Bystršm
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.

package com.example.springbootnetty.hanlder;

import io.netty.channel.*;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class WebSocketClientHandler extends SimpleChannelInboundHandler<Object> {
    
    private WebSocketClientHandshaker handshaker;

    private ChannelPromise handshakeFuture;

    public WebSocketClientHandler(WebSocketClientHandshaker handshaker) {
        this.handshaker = handshaker;
    }

    public ChannelFuture handshakeFuture() {
        return handshakeFuture;
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        handshakeFuture = ctx.newPromise();
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        handshaker.handshake(ctx.channel());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        log.info("WebSocket Client disconnected!");
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        Channel ch = ctx.channel();
        if (!handshaker.isHandshakeComplete()) {
            try {
                handshaker.finishHandshake(ch, (FullHttpResponse) msg);
                log.info("websocket client 连接成功");
                handshakeFuture.setSuccess();
            } catch (WebSocketHandshakeException e) {
                log.info("websocket client 连接失败");
                handshakeFuture.setFailure(e);
            }
            return;
        }

        if (msg instanceof FullHttpResponse) {
            FullHttpResponse response = (FullHttpResponse) msg;
            throw new IllegalStateException(
                    "Unexpected FullHttpResponse (getStatus=" + response.status() +
                            ", content=" + response.content().toString(CharsetUtil.UTF_8) + ')');
        }

        WebSocketFrame frame = (WebSocketFrame) msg;
        if (frame instanceof TextWebSocketFrame) {
            TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
            log.info("websocket client 接收到的消息:{}",textFrame.text());
        } else if (frame instanceof PongWebSocketFrame) {
            log.info("WebSocket Client received pong");
        } else if (frame instanceof CloseWebSocketFrame) {
            log.info("websocket client关闭");
            ch.close();
//            log.info("websocket client 5秒后开始重连");
//            Thread.sleep(5000);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        if (!handshakeFuture.isDone()) {
            handshakeFuture.setFailure(cause);
        }
        ctx.close();
        log.error("业务处理错误,websocket client关闭");
    }
}

2.WebsocketClient.class

/*
 * Copyright 2014 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package com.example.springbootnetty.client;

import com.example.springbootnetty.hanlder.WebSocketClientHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.net.URI;
import java.util.concurrent.TimeUnit;

/**
 * This is an example of a WebSocket client.
 * <p>
 * In order to run this example you need a compatible WebSocket server.
 * Therefore you can either start the WebSocket server from the examples
 * by running { io.netty.example.http.websocketx.server.WebSocketServer}
 * or connect to an existing WebSocket server such as
 * <a href="https://www.websocket.org/echo.html">ws://echo.websocket.org</a>.
 * <p>
 * The client will attempt to connect to the URI passed to it as the first argument.
 * You don't have to specify any arguments if you want to connect to the example WebSocket server,
 * as this is the default.
 */
@Slf4j
@Service
public class WebsocketClient {

    private String url = "wss:/localhost/webSocketServer";

    private Channel channel;

    private final static WebsocketClient WEBSOCKET_CLIENT = new WebsocketClient();

    private WebsocketClient() {
    }

    public static WebsocketClient getInstance() {
        return WEBSOCKET_CLIENT;
    }

    private String URL = System.getProperty("url", url);

    public void startClient() throws Exception {
        log.info("websocket client 连接中....");
        URI uri = new URI(URL);
        String scheme = uri.getScheme() == null ? "ws" : uri.getScheme();
        final String host = uri.getHost() == null ? "127.0.0.1" : uri.getHost();
        final int port;
        if (uri.getPort() == -1) {
            if ("ws".equalsIgnoreCase(scheme)) {
                port = 80;
            } else if ("wss".equalsIgnoreCase(scheme)) {
                port = 443;
            } else {
                port = -1;
            }
        } else {
            port = uri.getPort();
        }
        if (!"ws".equalsIgnoreCase(scheme) && !"wss".equalsIgnoreCase(scheme)) {
            System.err.println("Only WS(S) is supported.");
            return;
        }
        final boolean ssl = "wss".equalsIgnoreCase(scheme);
        final SslContext sslCtx;
        if (ssl) {
            sslCtx = SslContextBuilder.forClient()
                    .trustManager(InsecureTrustManagerFactory.INSTANCE).build();
        } else {
            sslCtx = null;
        }

        EventLoopGroup group = new NioEventLoopGroup();
        try {
            // Connect with V13 (RFC 6455 aka HyBi-17). You can change it to V08 or V00.
            // If you change it to V00, ping is not supported and remember to change
            // HttpResponseDecoder to WebSocketHttpResponseDecoder in the pipeline.
            final WebSocketClientHandler handler = new WebSocketClientHandler(WebSocketClientHandshakerFactory.newHandshaker(uri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders()));
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ChannelPipeline p = ch.pipeline();
                            if (sslCtx != null) {
                                p.addLast(sslCtx.newHandler(ch.alloc(), host, port));
                            }
                            p.addLast(
                                    new HttpClientCodec(),
                                    new HttpObjectAggregator(8192),
                                    WebSocketClientCompressionHandler.INSTANCE,
                                    handler);
                        }
                    });

            channel = b.connect(uri.getHost(), port).sync().channel();
            handler.handshakeFuture().sync();
            // 进行注册
            channel.writeAndFlush("发送的第一条消息");
            // 在这里可以添加一个默认心跳 15s发一次
            channel.eventLoop().scheduleAtFixedRate(() -> {
                channel.writeAndFlush(new TextWebSocketFrame("pong"));
            }, 0, 15, TimeUnit.SECONDS);

            //如果下面代码,则main方法所在的线程,即主线程会在执行完bind().sync()方法后,会进入finally 代码块,之前的启动的nettyserver也会随之关闭掉,整个程序都结束了
            channel.closeFuture().sync();
        } catch (Exception e) {
            channel.close();
            log.error("websocket client 启动失败,失败原因:{}", e.getMessage());
        } finally {
            group.shutdownGracefully();
        }
    }
}

3.RunClient.class

package com.example.springbootnetty.client;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @author zhooke
 * @since 2022/4/2 17:36
 **/
@Component
@Slf4j
public class RunClient {

    @PostConstruct
    public void run() {
        try {
            WebsocketClient.getInstance().startClient();
        } catch (Exception e) {
            log.error("websocket启动失败,失败原因:{}", e.getMessage());
        }

    }
}

项目地址

首先,需要在pom.xml文件中添加NettySpring Boot的依赖: ```xml <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.43.Final</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` 然后,创建一个WebSocket客户端类,继承自 `ChannelInboundHandlerAdapter`,实现WebSocket协议相关的方法: ```java @Component public class WebSocketClient extends ChannelInboundHandlerAdapter { private WebSocketClientHandshaker handshaker; private ChannelPromise handshakeFuture; @Autowired private WebSocketClientHandler handler; public void connect(String url) throws Exception { URI uri = new URI(url); String scheme = uri.getScheme() == null ? "ws" : uri.getScheme(); String host = uri.getHost() == null ? "127.0.0.1" : uri.getHost(); int port = uri.getPort(); if (port == -1) { if ("ws".equalsIgnoreCase(scheme)) { port = 80; } else if ("wss".equalsIgnoreCase(scheme)) { port = 443; } } if (!"ws".equalsIgnoreCase(scheme) && !"wss".equalsIgnoreCase(scheme)) { throw new IllegalArgumentException("Unsupported scheme: " + scheme); } final WebSocketClientHandler handler = this.handler; EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .option(ChannelOption.SO_KEEPALIVE, true) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); if ("wss".equalsIgnoreCase(scheme)) { SslContext sslContext = SslContextBuilder.forClient().build(); pipeline.addLast(sslContext.newHandler(ch.alloc(), host, port)); } pipeline.addLast(new HttpClientCodec(), new HttpObjectAggregator(8192), WebSocketClientCompressionHandler.INSTANCE, handler); } }); Channel channel = bootstrap.connect(uri.getHost(), port).sync().channel(); handshaker = WebSocketClientHandshakerFactory.newHandshaker( uri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders()); handshakeFuture = channel.newPromise(); handler.setHandshakeFuture(handshakeFuture); channel.writeAndFlush(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri.getRawPath())) .addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) { if (future.isSuccess()) { handshaker.handshake(future.channel()); } else { handshakeFuture.setFailure(future.cause()); } } }); handshakeFuture.sync(); channel.closeFuture().sync(); } finally { group.shutdownGracefully(); } } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { handler.setCtx(ctx); handshaker.handshake(ctx.channel()); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println("WebSocket Client disconnected!"); handler.setCtx(null); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { Channel ch = ctx.channel(); if (!handshaker.isHandshakeComplete()) { try { handshaker.finishHandshake(ch, (FullHttpResponse) msg); System.out.println("WebSocket Client connected!"); handshakeFuture.setSuccess(); } catch (WebSocketHandshakeException e) { System.out.println("WebSocket Client failed to connect"); handshakeFuture.setFailure(e); } return; } if (msg instanceof FullHttpResponse) { FullHttpResponse response = (FullHttpResponse) msg; throw new IllegalStateException( "Unexpected FullHttpResponse (getStatus=" + response.status() + ", content=" + response.content().toString(CharsetUtil.UTF_8) + ')'); } WebSocketFrame frame = (WebSocketFrame) msg; if (frame instanceof TextWebSocketFrame) { TextWebSocketFrame textFrame = (TextWebSocketFrame) frame; System.out.println("WebSocket Client received message: " + textFrame.text()); } else if (frame instanceof PongWebSocketFrame) { System.out.println("WebSocket Client received pong"); } else if (frame instanceof CloseWebSocketFrame) { System.out.println("WebSocket Client received closing"); ch.close(); } } } ``` 在这个类中,我们注入了一个 `WebSocketClientHandler` 的实例,它也是一个 `ChannelInboundHandlerAdapter`,用于处理WebSocket消息。在 `connect()` 方法中,我们使用Netty创建了一个Bootstrap实例,并设置了一些参数,然后连接到WebSocket服务器。在连接成功后,进行了握手操作,以确保连接正常建立。 下面是 `WebSocketClientHandler` 的实现: ```java @Component public class WebSocketClientHandler extends ChannelInboundHandlerAdapter { private ChannelHandlerContext ctx; private ChannelPromise handshakeFuture; public void setCtx(ChannelHandlerContext ctx) { this.ctx = ctx; } public void setHandshakeFuture(ChannelPromise handshakeFuture) { this.handshakeFuture = handshakeFuture; } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt == WebSocketClientProtocolHandler.ClientHandshakeStateEvent.HANDSHAKE_COMPLETE) { handshakeFuture.setSuccess(); } else { super.userEventTriggered(ctx, evt); } } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // Handle WebSocket message } // Other methods } ``` 在这个类中,我们重写了 `channelRead()` 方法,用于处理WebSocket消息。我们也保存了当前的 `ChannelHandlerContext` 和握手操作的 `ChannelPromise`,以便在连接成功后进行操作。 最后,在Spring Boot的主类中,可以通过注入 `WebSocketClient` 的方式来启动WebSocket客户端: ```java @SpringBootApplication public class Application { @Autowired private WebSocketClient webSocketClient; public static void main(String[] args) throws Exception { SpringApplication.run(Application.class, args); } @EventListener(ApplicationReadyEvent.class) public void connect() throws Exception { webSocketClient.connect("ws://localhost:8080/websocket"); } } ``` 在 `connect()` 方法中,我们调用了 `WebSocketClient` 的 `connect()` 方法,来连接到WebSocket服务器。注意,这个方法是阻塞的,直到连接关闭或连接失败。 这样,我们就成功地使用NettySpring Boot实现了一个WebSocket客户端,并且可以将其注入到Spring Boot的Bean容器中,以方便管理和使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值