【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 {

    /**
     * 发送消息.
* * @param message 发送文本 */ public void send(String message) throws MyException { Channel channel = getChannel(); if (channel != null) { channel.writeAndFlush(new TextWebSocketFrame(message)); return; } throw new MyException("连接已经关闭"); } /** * 连接并发送消息.
*/ public void connect() throws MyException { try { doOpen(); doConnect(); } catch (Exception e) { throw new MyException("连接没有成功打开,原因是:{}" + e.getMessage(), e); } } /** * 初始化连接.
*/ protected abstract void doOpen(); /** * 建立连接.
*/ protected abstract void doConnect() throws MyException; /** * 获取本次连接channel.
* * @return {@link Channel} */ protected abstract Channel getChannel(); /** * 关闭连接.
*/ @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 {

    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 {

    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连接请求.
* * @param msg: */ private void handleHttpRequest(Object msg) { webSocketClientHandshaker.finishHandshake(channel, (FullHttpResponse) msg); handshakeFuture.setSuccess(); } /** * 处理文本帧请求.
* * @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);

    }

    /**
     * 用指定的详细信息和原因构造一个新的异常.
* * @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);
        }

    }
}

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

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

你可能感兴趣的:(java,websocket,netty,websocket,java)