java项目应用webSocket的两种主要方式

42 阅读3分钟

编辑

点/击/蓝/字  关/注/我/们

一、背景

        笔者之前的项目中需要用webSocket协议进行长连接上游供应商,笔者为此做了一些小小的技术预研,特将此记录下来。

tips:因笔者主要是连接上游供应商的服务端,因此本文主要介绍webSocket客户端的知识,暂不编写服务端知识,请见谅。

        具体详细代码地址:

https://github.com/ikuncoder/webSocketConnector

二、javax

        在java的扩展包javax.websocket中已经定义了一套WebSocket的接口规范,应用起来比较简单快捷。

public class MainApplication {
    public static void main(String[] args) {
        connect();
    }
​
    private static void connect() {
        String url = "";//your websocket url,for example,wss://xxxxx.com/websocket/**// 实例化WebSocketService
        WebSocketService webSocketService = new WebSocketService();
​
        // 打开WebSocket连接
        try {
            webSocketService.openWebSocketConnection(url);
            //do something after connect
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

        首先创建一个WebSocketService对象,传入需要连接的url,需要是wss或者是ws开头的。

@ClientEndpoint
@Slf4j
public class WebSocketService {
​
    private Session userSession = null;
    private Long startTime = System.currentTimeMillis();
​
​
    public void openWebSocketConnection(String url) {
        try {
            URI uri = URI.create(url);
            WebSocketContainer container = ContainerProvider.getWebSocketContainer();
            container.connectToServer(this, uri);
        } catch (Exception e) {
            throw new RuntimeException("Could not open WebSocket connection", e);
        }
    }
​
    @OnOpen
    public void onOpen(Session userSession) {
        System.out.println(Thread.currentThread().getName() + " Connection opened.");
        this.userSession = userSession;
    }
​
    @OnClose
    public void onClose(Session userSession, CloseReason reason) {
        System.out.println(Thread.currentThread().getName() + " Connection closed because: " + reason);
        this.userSession = null;
    }
​
    @OnMessage
    public void onMessage(String message) {
        System.out.println(Thread.currentThread().getName() + " 相隔" + (System.currentTimeMillis() - startTime) + "ms,Received: " + message);
        //do something with the message
    }
}

        在openWebSocketConnection方法中创建一个WebSocketContainer 进行连接,这些方法都是javax.webSocket已经封装好了的,可以直接使用。

        连接成功会回调onOpen方法,断开会回调onClose方法,接收的信息回调用onMessage方法。

        javax这种方式比较简单,笔者主要是做一下技术调研,因为笔者更偏向用netty,实际项目中也是用netty做客户端进行长连接。

三、netty

netty的介绍就不多说了,相信应该都听过大名鼎鼎的netty,下面直接show code

public class WebSocketNettyClient {
​
    public static void main(String[] args) throws Exception {
        connect();
    }
​
​
    private static void connect() throws URISyntaxException {
        EventLoopGroup group = new NioEventLoopGroup(1);
        final ClientHandler handler = new ClientHandler();
        String url = "";//your websocket url,for example,wss://xxxxx.com/websocket/**
        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();
        try {
            SslContext sslContext = SslContextBuilder.forClient()// 配置支持的协议版本
                    .build();
            URI websocketURI = uri;
            WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker(websocketURI, WebSocketVersion.V13, (String) null, true, new DefaultHttpHeaders());
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group).channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(sslContext.newHandler(ch.alloc(), host, 443));
                            // 添加一个http的编解码器
                            pipeline.addLast(new HttpClientCodec());
                            // 添加一个用于支持大数据流的支持
                            pipeline.addLast(new ChunkedWriteHandler());
                            // 添加一个聚合器,这个聚合器主要是将HttpMessage聚合成FullHttpRequest/Response
                            pipeline.addLast(new HttpObjectAggregator(1024 * 64));
                            pipeline.addLast(handler);
                        }
                    });
​
            HttpHeaders httpHeaders = new DefaultHttpHeaders();
            //进行握手
            final Channel channel = bootstrap.connect(websocketURI.getHost(), 443).sync().channel();
            handler.setHandshaker(handshaker);
            handshaker.handshake(channel);
            //阻塞等待是否握手成功
            handler.handshakeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
        }
    }
​
    private static class ClientHandler extends SimpleChannelInboundHandler<Object> {
​
        private WebSocketClientHandshaker handshaker;
        ChannelPromise handshakeFuture;
​
        private Long startTime;
​
        /**
         * 当客户端主动链接服务端的链接后,调用此方法
         *
         * @param channelHandlerContext ChannelHandlerContext
         */
        @Override
        public void channelActive(ChannelHandlerContext channelHandlerContext) {
            System.out.println("客户端Active .....");
            handlerAdded(channelHandlerContext);
        }
​
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            System.out.println("\n\t⌜⎓⎓⎓⎓⎓⎓exception⎓⎓⎓⎓⎓⎓⎓⎓⎓\n" +
                    cause.getMessage());
            ctx.close();
        }
​
        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("channelInactive");
            super.channelInactive(ctx);
        }
​
        public void setHandshaker(WebSocketClientHandshaker handshaker) {
            this.handshaker = handshaker;
        }
​
        public void handlerAdded(ChannelHandlerContext ctx) {
            this.handshakeFuture = ctx.newPromise();
        }
​
        public ChannelFuture handshakeFuture() {
            return this.handshakeFuture;
        }
​
        protected void channelRead0(ChannelHandlerContext ctx, Object o) throws Exception {
​
            // 握手协议返回,设置结束握手
            if (!this.handshaker.isHandshakeComplete()) {
                FullHttpResponse response = (FullHttpResponse) o;
                this.handshaker.finishHandshake(ctx.channel(), response);
                this.handshakeFuture.setSuccess();
                System.out.println("---------握手成功------------");
                //do something after handshake success;
                return;
            } else if (o instanceof TextWebSocketFrame) {
                //do something after receive text message;
            } else if (o instanceof CloseWebSocketFrame) {
                System.out.println("连接关闭");
            }
        }
    }
}

        用netty进行webSocket长连接,如果是wss开头的,需要声明SslContext进行ssl校验,需要进行握手连接验证,这一步是比较关键我认为也是难点,需要自己稍微处理一下握手相关的东西,如果是url是ws开头的,就不需要进行握手验证。还需要注意一下连接的端口,如果是wss,默认端口是443,如果是ws,默认端口是80。

        好啦,本次分享就到这里了,希望大家有所收获~

重点!重点!听说笔者开通的微信公众号,感兴趣的话可以关注一下:有理唔理

        有兴趣的话,感兴趣的话可以下载源码:

https://github.com/ikuncoder/webSocketConnector

编辑

点/击/蓝/字  关/注/我/们