【netty客户端】通过netty实现封装websocket客户端

现在网上有很多netty实现的websocket服务端,但是客户端实现的不多,或者说是写的比较散,现写下。

另外,源码可以参考github:weboscket客户端以及服务端实现

首先,构建一个抽象类,定义一下对外的接口等:

import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;

import java.io.Closeable;

/**
 * 客户端抽象
 *
 * @author lukou
 * @date 2023/05/18
 */
public abstract class AbstractWebsocketClient implements Closeable {

    /**
     * 发送消息.<br>
     *
     * @param message 发送文本
     */
    public void send(String message) throws MyException {
        Channel channel = getChannel();
        if (channel != null) {
            channel.writeAndFlush(new TextWebSocketFrame(message));
            return;
        }
        throw new MyException("连接已经关闭");
    }

    /**
     * 连接并发送消息.<br>
     */
    public void connect() throws MyException {
        try {
            doOpen();
            doConnect();
        } catch (Exception e) {
            throw new MyException("连接没有成功打开,原因是:{}" + e.getMessage(), e);
        }
    }

    /**
     * 初始化连接.<br>
     */
    protected abstract void doOpen();

    /**
     * 建立连接.<br>
     */
    protected abstract void doConnect() throws MyException;

    /**
     * 获取本次连接channel.<br>
     *
     * @return {@link Channel}
     */
    protected abstract Channel getChannel();

    /**
     * 关闭连接.<br>
     */
    @Override
    public abstract void close();

}

AbstractWebsocketClient 这个抽象类主要定义了一些必要的接口,属性connectionTimeout主要用于连接时间的设置,属性WebsocketContext相当于上下文。

下面是编写子类继承AbstractWebsocketClient:

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.net.URISyntaxException;

/**
 * websocket客户端
 *
 * @author lukou
 * @date 2023/05/18
 */
public class WebsocketClient extends AbstractWebsocketClient {

    private static final Logger log = LoggerFactory.getLogger(WebsocketClient.class);

    private static final NioEventLoopGroup NIO_GROUP = new NioEventLoopGroup();

    private final URI uri;

    private final int port;

    private Bootstrap bootstrap;

    private WebsocketClientHandler handler;

    private Channel channel;

    public WebsocketClient(String url) throws URISyntaxException, MyException {
        super();
        this.uri = new URI(url);
        this.port = getPort();
    }

    /**
     * Extract the specified port
     *
     * @return the specified port or the default port for the specific scheme
     */
    private int getPort() throws MyException {
        int port = uri.getPort();
        if (port == -1) {
            String scheme = uri.getScheme();
            if ("wss".equals(scheme)) {
                return 443;
            } else if ("ws".equals(scheme)) {
                return 80;
            } else {
                throw new MyException("unknown scheme: " + scheme);
            }
        }
        return port;
    }

    @Override
    protected void doOpen() {
        // websocket客户端握手实现的基类
        WebSocketClientHandshaker webSocketClientHandshaker = WebSocketClientHandshakerFactory.newHandshaker(uri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders());
        // 业务处理类
        handler = new WebsocketClientHandler(webSocketClientHandshaker);
        // client端,引导client channel启动
        bootstrap = new Bootstrap();
        // 添加管道 绑定端口 添加作用域等
        bootstrap.group(NIO_GROUP).channel(NioSocketChannel.class).handler(new WebsocketChannelInitializer(handler));
    }

    @Override
    protected void doConnect() {
        try {
            // 启动连接
            channel = bootstrap.connect(uri.getHost(), port).sync().channel();
            // 等待握手响应
            handler.handshakeFuture().sync();
        } catch (InterruptedException e) {
            log.error("websocket连接发生异常", e);
            Thread.currentThread().interrupt();
        }
    }

    @Override
    protected Channel getChannel() {
        return channel;
    }

    @Override
    public void close() {
        if (channel != null) {
            channel.close();
        }
    }

    public boolean isOpen() {
        return channel.isOpen();
    }
}
WebsocketClient主要是实现了父类方法。

接下来,需要添加处理器到客户端:

public class WebsocketChannelInitializer extends ChannelInitializer<SocketChannel> {

    private final WebsocketClientHandler handler;

    public WebsocketChannelInitializer(WebsocketClientHandler handler) {
        this.handler = handler;
    }

    @Override
    protected void initChannel(SocketChannel ch) {
        ChannelPipeline p = ch.pipeline();
        p.addLast(new HttpClientCodec());
        p.addLast(new HttpObjectAggregator(8192));
        p.addLast(WebSocketClientCompressionHandler.INSTANCE);
        p.addLast(handler);
    }
}
WebsocketChannelInitializer主要还是用于管道中添加处理器,当然包括自定义处理器。

接下实现自定义处理器:

import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * websocket客户端处理程序
 *
 * @author lukou
 * @date 2023/05/18
 */
public class WebsocketClientHandler extends SimpleChannelInboundHandler<Object> {

    private static final Logger log = LoggerFactory.getLogger(WebsocketClientHandler.class);

    /**
     * 连接处理器
     */
    private final WebSocketClientHandshaker webSocketClientHandshaker;

    /**
     * netty提供的数据过程中的数据保证
     */
    private ChannelPromise handshakeFuture;

    private Channel channel;

    public WebsocketClientHandler(WebSocketClientHandshaker webSocketClientHandshaker) {
        this.webSocketClientHandshaker = webSocketClientHandshaker;
    }

    public ChannelFuture handshakeFuture() {
        return handshakeFuture;
    }

    /**
     * ChannelHandler添加到实际上下文中准备处理事件,调用此方法
     *
     * @param ctx ChannelHandlerContext
     */

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

    /**
     * 当客户端主动链接服务端的链接后,调用此方法
     *
     * @param ctx ChannelHandlerContext
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        channel = ctx.channel();
        webSocketClientHandshaker.handshake(channel);
        log.info("建立连接");
    }

    /**
     * 链接断开后,调用此方法
     *
     * @param ctx ChannelHandlerContext
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        log.info("连接断开");
    }

    /**
     * 接收消息,调用此方法
     *
     * @param ctx ChannelHandlerContext
     * @param msg Object
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
        if (!webSocketClientHandshaker.isHandshakeComplete()) {
            this.handleHttpRequest(msg);
            log.info("websocket已经建立连接");
            return;
        }
        if (msg instanceof FullHttpResponse) {
            FullHttpResponse response = (FullHttpResponse) msg;
            throw new IllegalStateException("Unexpected FullHttpResponse (getStatus=" + response.status() + ", content=" + response.content().toString(CharsetUtil.UTF_8) + ')');
        }
        this.handleWebSocketFrame(msg);
    }

    /**
     * 处理http连接请求.<br>
     *
     * @param msg:
     */
    private void handleHttpRequest(Object msg) {
        webSocketClientHandshaker.finishHandshake(channel, (FullHttpResponse) msg);
        handshakeFuture.setSuccess();
    }

    /**
     * 处理文本帧请求.<br>
     *
     * @param msg 消息
     */
    private void handleWebSocketFrame(Object msg) {
        WebSocketFrame frame = (WebSocketFrame) msg;
        if (frame instanceof TextWebSocketFrame) {
            TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
            // ...自定义
            log.info("收到消息:{}", textFrame.text());
        } else if (frame instanceof CloseWebSocketFrame) {
            log.info("连接收到关闭帧");
            channel.close();
        }
    }

    /**
     * 运行过程中未捕获的异常,调用此方法
     *
     * @param ctx ChannelHandlerContext
     * @param cause Throwable
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.info("监控触发异常=>{}", cause.getMessage(), cause);
    }
}

剩下的相关的类也贴一下吧

上下文WebsocketContext,可以根据需求修改:

public class WebsocketContext {

    /**
     * 计数器(用于监听是否返回结果)
     */
    private CountDownLatch countDownLatch;

    /**
     * 最终结果
     */
    private String result;

    public WebsocketContext(CountDownLatch countDownLatch) {
        this.countDownLatch= countDownLatch;
    }

    public CountDownLatch getCountDownLatch() {
        return countDownLatch;
    }

    public void setCountDownLatch(CountDownLatch countDownLatch) {
        this.countDownLatch= countDownLatch;
    }

    public String getResult() {
        return result;
    }

    public void setResult(String result) {
        this.result = result;
    }
 
}

异常类MyException:

public class MyException extends Exception {


    public MyException () {
        super();
    }

    public MyException (String message) {
        super(message);

    }

    /**
     * 用指定的详细信息和原因构造一个新的异常.<br>
     *
     * @param message
     * @param cause
     * @return:
     */
    public MyException (String message, Throwable cause) {
        super(message, cause);
    }

}

测试:

import lombok.extern.slf4j.Slf4j;

import java.net.URISyntaxException;

@Slf4j
public class Test {

    public static void main(String[] args) {
        try (WebsocketClient websocketClient = new WebsocketClient("ws://localhost:8081/example4/ws")) {
            // 连接
            websocketClient.connect();
            // 发送消息
            websocketClient.send("xxxxxxxxxxxxxxxxx");
            // 阻塞一下,否则这里客户端会调用close方法
            Thread.sleep(10);
        } catch (URISyntaxException | MyException | InterruptedException e) {
            log.error("发生异常,原因:{}", e.getMessage(), e);
        }

    }
}

至此,整个客户端封装的差不多了,当然如果用于生产还是要根据实际情况修改的。

如果有不足的地方也请指出,大家一起探讨,感谢。

  • 6
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
很抱歉,作为一名AI语言模型,我无法生成代码。不过我可以给你提供一些参考资料,希望能对你有所帮助。 首先,你需要在Spring Boot项目中引入NettyWebSocket的依赖,例如: ```xml <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.22.Final</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` 然后,你可以编写一个WebSocket客户端类,例如: ```java @Component public class WebSocketClient { private final WebSocketClientHandshaker handshaker; private WebSocketChannel channel; public WebSocketClient(@Value("${websocket.url}") String url) throws Exception { URI uri = new URI(url); handshaker = WebSocketClientHandshakerFactory.newHandshaker(uri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders()); } public void connect() throws Exception { Bootstrap bootstrap = new Bootstrap(); EventLoopGroup group = new NioEventLoopGroup(); try { bootstrap.group(group) .channel(NioSocketChannel.class) .handler(new WebSocketClientInitializer(handshaker)); ChannelFuture future = bootstrap.connect(handshaker.uri().getHost(), handshaker.uri().getPort()).sync(); channel = ((WebSocketClientHandler) future.channel().pipeline().last()).getChannel(); handshaker.handshake(channel).sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { group.shutdownGracefully(); } } public void send(String message) { WebSocketFrame frame = new TextWebSocketFrame(message); channel.writeAndFlush(frame); } public void close() { channel.close(); } } ``` 其中,WebSocketClientInitializer是用于初始化NettyWebSocket客户端的,例如: ```java public class WebSocketClientInitializer extends ChannelInitializer<SocketChannel> { private final WebSocketClientHandshaker handshaker; public WebSocketClientInitializer(WebSocketClientHandshaker handshaker) { this.handshaker = handshaker; } @Override protected void initChannel(SocketChannel channel) throws Exception { channel.pipeline() .addLast(new HttpClientCodec()) .addLast(new HttpObjectAggregator(8192)) .addLast(new WebSocketClientHandler(handshaker)); } } ``` 最后,你可以在Spring Boot的控制器中使用WebSocketClient来与WebSocket服务器进行通信,例如: ```java @RestController public class WebSocketController { @Autowired private WebSocketClient webSocketClient; @PostMapping("/send") public void send(@RequestBody String message) { webSocketClient.send(message); } } ``` 这样你就可以使用Spring Boot和Netty实现WebSocket客户端了。希望对你有所帮助。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值