zoukankan      html  css  js  c++  java
  • WebSocket专题(阿里)

    我们的项目中使用了websocket 用java-websocket 开源项目做的,阿里的人问我用啥实现的websocket一时没有答上来

    回来做了总结:

    1、前言

    最近有同学问我有没有做过在线咨询功能。同时,公司也刚好让我接手一个 IM 项目。所以今天抽时间记录一下最近学习的内容。本文主要剖析了 WebSocket 的原理,以及附上一个完整的聊天室实战 Demo (包含前端和后端,代码下载链接在文末)。

    2、WebSocket 与 HTTP

    WebSocket 协议在2008年诞生,2011年成为国际标准。现在所有浏览器都已经支持了。WebSocket 的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话。

    HTTP 有 1.1 和 1.0 之说,也就是所谓的 keep-alive ,把多个 HTTP 请求合并为一个,但是 Websocket 其实是一个新协议,跟 HTTP 协议基本没有关系,只是为了兼容现有浏览器,所以在握手阶段使用了 HTTP 。

    下面一张图说明了 HTTP 与 WebSocket 的主要区别:

    WebSocket 的其他特点:

    • 建立在 TCP 协议之上,服务器端的实现比较容易。
    • 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
    • 数据格式比较轻量,性能开销小,通信高效。
    • 可以发送文本,也可以发送二进制数据。
    • 没有同源限制,客户端可以与任意服务器通信。
    • 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

    3、WebSocket 是什么样的协议,具体有什么优点

    首先,WebSocket 是一个持久化的协议,相对于 HTTP 这种非持久的协议来说。简单的举个例子吧,用目前应用比较广泛的 PHP 生命周期来解释。

    HTTP 的生命周期通过 Request 来界定,也就是一个 Request 一个 Response ,那么在 HTTP1.0 中,这次 HTTP 请求就结束了。

    在 HTTP1.1 中进行了改进,使得有一个 keep-alive,也就是说,在一个 HTTP 连接中,可以发送多个 Request,接收多个 Response。但是请记住 Request = Response, 在 HTTP 中永远是这样,也就是说一个 Request 只能有一个 Response。而且这个 Response 也是被动的,不能主动发起。

    你 BB 了这么多,跟 WebSocket 有什么关系呢? 好吧,我正准备说 WebSocket 呢。

    首先 WebSocket 是基于 HTTP 协议的,或者说借用了 HTTP 协议来完成一部分握手。

    首先我们来看个典型的 WebSocket 握手

    GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
    Sec-WebSocket-Protocol: chat, superchat
    Sec-WebSocket-Version: 13
    Origin: http://example.com

    熟悉 HTTP 的童鞋可能发现了,这段类似 HTTP 协议的握手请求中,多了这么几个东西。

    Upgrade: websocket
    Connection: Upgrade

    这个就是 WebSocket 的核心了,告诉 Apache 、 Nginx 等服务器:注意啦,我发起的请求要用 WebSocket 协议,快点帮我找到对应的助理处理~而不是那个老土的 HTTP。

    Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
    Sec-WebSocket-Protocol: chat, superchat
    Sec-WebSocket-Version: 13

    首先, Sec-WebSocket-Key 是一个 Base64 encode 的值,这个是浏览器随机生成的,告诉服务器:泥煤,不要忽悠我,我要验证你是不是真的是 WebSocket 助理。

    然后, Sec_WebSocket-Protocol 是一个用户定义的字符串,用来区分同 URL 下,不同的服务所需要的协议。简单理解:今晚我要服务A,别搞错啦~

    最后, Sec-WebSocket-Version 是告诉服务器所使用的 WebSocket Draft (协议版本),在最初的时候,WebSocket 协议还在 Draft 阶段,各种奇奇怪怪的协议都有,而且还有很多期奇奇怪怪不同的东西,什么 Firefox 和 Chrome 用的不是一个版本之类的,当初 WebSocket 协议太多可是一个大难题。。不过现在还好,已经定下来啦~大家都使用同一个版本: 服务员,我要的是13岁的噢→_→

    然后服务器会返回下列东西,表示已经接受到请求, 成功建立 WebSocket 啦!

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
    Sec-WebSocket-Protocol: chat

    这里开始就是 HTTP 最后负责的区域了,告诉客户,我已经成功切换协议啦~

    Upgrade: websocket
    Connection: Upgrade

    依然是固定的,告诉客户端即将升级的是 WebSocket 协议,而不是 mozillasocket,lurnarsocket 或者 shitsocket。

    然后, Sec-WebSocket-Accept 这个则是经过服务器确认,并且加密过后的 Sec-WebSocket-Key 。 服务器:好啦好啦,知道啦,给你看我的 ID CARD 来证明行了吧。

    后面的, Sec-WebSocket-Protocol 则是表示最终使用的协议。

    至此,HTTP 已经完成它所有工作了,接下来就是完全按照 WebSocket 协议进行了。

    4、WebSocket 的作用

    在讲 WebSocket之前,我就顺带着讲下 ajax轮询 和 long poll 的原理。

    4-1、ajax轮询

    ajax轮询的原理非常简单,让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息。

    场景再现:

    客户端:啦啦啦,有没有新信息(Request)
    
    服务端:没有(Response)
    
    客户端:啦啦啦,有没有新信息(Request)
    
    服务端:没有。。(Response)
    
    客户端:啦啦啦,有没有新信息(Request)
    
    服务端:你好烦啊,没有啊。。(Response)
    
    客户端:啦啦啦,有没有新消息(Request)
    
    服务端:好啦好啦,有啦给你。(Response)
    
    客户端:啦啦啦,有没有新消息(Request)
    
    服务端:。。。。。没。。。。没。。。没有(Response) —- loop

    4-2、long poll

    long poll 其实原理跟 ajax轮询 差不多,都是采用轮询的方式,不过采取的是阻塞模型(一直打电话,没收到就不挂电话),也就是说,客户端发起请求后,如果没消息,就一直不返回 Response 给客户端。直到有消息才返回,返回完之后,客户端再次建立连接,周而复始。

    场景再现:

    客户端:啦啦啦,有没有新信息,没有的话就等有了才返回给我吧(Request)
    
    服务端:额。。 等待到有消息的时候。。来 给你(Response)
    
    客户端:啦啦啦,有没有新信息,没有的话就等有了才返回给我吧(Request) -loop

    从上面可以看出其实这两种方式,都是在不断地建立HTTP连接,然后等待服务端处理,可以体现HTTP协议的另外一个特点,被动性。

    何为被动性呢,其实就是,服务端不能主动联系客户端,只能有客户端发起。

    从上面很容易看出来,不管怎么样,上面这两种都是非常消耗资源的。

    ajax轮询 需要服务器有很快的处理速度和资源。long poll 需要有很高的并发,也就是说同时接待客户的能力。

    所以 ajax轮询 和 long poll 都有可能发生这种情况。

    
    客户端:啦啦啦啦,有新信息么?
    
    服务端:正忙,请稍后再试(503 Server Unavailable)
    
    客户端:。。。。好吧,啦啦啦,有新信息么?
    
    服务端:正忙,请稍后再试(503 Server Unavailable)
    

    4-3、WebSocket

    通过上面这两个例子,我们可以看出,这两种方式都不是最好的方式,需要很多资源。

    一种需要更快的速度,一种需要更多的’电话’。这两种都会导致’电话’的需求越来越高。

    哦对了,忘记说了 HTTP 还是一个无状态协议。通俗的说就是,服务器因为每天要接待太多客户了,是个健忘鬼,你一挂电话,他就把你的东西全忘光了,把你的东西全丢掉了。你第二次还得再告诉服务器一遍。

    所以在这种情况下出现了 WebSocket 。他解决了 HTTP 的这几个难题。首先,被动性,当服务器完成协议升级后(HTTP->Websocket),服务端就可以主动推送信息给客户端啦。所以上面的情景可以做如下修改。

    
    客户端:啦啦啦,我要建立Websocket协议,需要的服务:chat,Websocket协议版本:17(HTTP Request)
    
    服务端:ok,确认,已升级为Websocket协议(HTTP Protocols Switched)
    
    客户端:麻烦你有信息的时候推送给我噢。。
    
    服务端:ok,有的时候会告诉你的。
    
    服务端:balabalabalabala
    
    服务端:balabalabalabala
    
    服务端:哈哈哈哈哈啊哈哈哈哈
    
    服务端:笑死我了哈哈哈哈哈哈哈
    

    这样,只需要经过一次 HTTP 请求,就可以做到源源不断的信息传送了。

    5、实战代码

    本文的更新源 托管于GitHub

    参考文档:
    php socket 文档
    js 的 WebSocket 文档

    前端代码:https://github.com/nnngu/WebSocketDemo-js
    后端代码:https://github.com/nnngu/WebSocketDemo-php

    运行步骤:

    1. 在终端打开 WebSocketDemo-php 目录,执行 php -q server.php
    2. 用浏览器访问 WebSocketDemo-js 目录里面的 index.html

    运行截图:

    最近项目里需要在Java服务端与c++进行websocket通信,java_websocket.client.WebSocketClient插件很好的解决了这个需求。

       首先需要在pom.xml文件中引入此依赖:

    <dependency>
    <groupId>org.java-websocket</groupId>
    <artifactId>Java-WebSocket</artifactId>
    <version>1.3.5</version>
    </dependency>

    Java代码如下:

    import org.apache.log4j.Logger;
    import org.java_websocket.WebSocket;
    import org.java_websocket.client.WebSocketClient;
    import org.java_websocket.drafts.Draft_6455;
    import org.java_websocket.handshake.ServerHandshake;
    
    import com.cn.service.impl.SearchPersonServiceImpl;
    
    import java.io.UnsupportedEncodingException;
    import java.net.URI;
    import java.net.URISyntaxException;
    
    
    public class WebsocketClient {
    
    private static Logger logger = Logger.getLogger(WebsocketClient.class);
    public static WebSocketClient client;
    public static void main(String[] args) {
    try {
    client = new WebSocketClient(new URI("ws://192.168.4.100:8080/project/websocket"),new Draft_6455()) {
    @Override
    public void onOpen(ServerHandshake serverHandshake) {
    logger.info("握手成功");
    }
    
    @Override
    public void onMessage(String msg) {
    logger.info("收到消息=========="+msg);
    if(msg.equals("over")){
    client.close();
    }
    
    }
    
    @Override
    public void onClose(int i, String s, boolean b) {
    logger.info("链接已关闭");
    }
    
    @Override
    public void onError(Exception e){
    e.printStackTrace();
    logger.info("发生错误已关闭");
    }
    };
    } catch (URISyntaxException e) {
    e.printStackTrace();
    }
    
    client.connect();
    logger.info(client.getDraft());
    while(!client.getReadyState().equals(WebSocket.READYSTATE.OPEN)){
    logger.info("正在连接...");
    }
    //连接成功,发送信息
    client.send("哈喽,连接一下啊");
    
    }
    
    }



      

    前言

    今天在慕课网上看到了Java的新教程(Netty入门之WebSocket初体验):https://www.imooc.com/learn/941

    WebSocket我是听得很多,没有真正使用过的技术。我之前也去了解过了WebSocket究竟是什么东西,不过一直没有去实践过。

    我在写监听器博文的时候,在线人数功能用监听器的是来做,在评论有说使用WebSocket的方式会更加好。

    那么,我们就来探究一下WebSocket究竟是什么东西,顺便了解一下Netty!

    WebSocket介绍

    什么是WebSocket

    WebSocket是一个协议,归属于IETF。

    • HTTP是运行在TCP协议传输层上的应用协议,而WebSocket是通过HTTP协议协商如何连接,然后独立运行在TCP协议传输层上的应用协议。
    • Websocket是一个持久化的协议,相对于HTTP这种非持久的协议来说。
    • websocket约定了一个通信的规范,通过一个握手的机制,客户端和服务器之间能建立一个类似tcp的连接,从而方便它们之间的通信

    为什么需要WebSocket

    添加WebSocket特性,是为了更好、更灵活,轻量的与服务器通讯。因为WebSocket提供了简单的消息规范,可以更快的适应长连接的环境,其实现在HTTP协议自身就可以做,但是不太轻便。

    WebSocket最大的特点就是实现全双工通信:客户端能够实时推送消息给服务端,服务端也能够实时推送消息给客户端。

    WebSocket可以做聊天室,股票实时价格显示等应用

    纠正WebSocket误区

    WebSocket是一种应用协议,而我们常常看到了HTML5 WebSocket是API,不要将其进行混淆。

    广义上的 HTML5 里面包含的是 WebSocket API,并不是 WebSocket。简单的说,可以把 WebSocket 当成 HTTP,WebSocket API 当成 Ajax。

    Netty介绍

    什么是Netty

    知乎的@郭无心总结得很好,我下面就摘抄一下了(链接在下方):

    Netty是什么?

    • 1)本质:JBoss做的一个Jar包
    • 2)目的:快速开发高性能、高可靠性的网络服务器和客户端程序
    • 3)优点:提供异步的、事件驱动的网络应用程序框架和工具

    通俗的说:一个好使的处理Socket的东东

    如果没有Netty?

    远古:java.net + java.io

    近代:java.nio

    其他:Mina,Grizzly

    简单来说:

    • 你想写个tomcat一样的Server,可以用netty。
    • 你想写一个即时通讯的应用,可以用netty。
    • 你想实现一个高性能Rpc框架,可以用netty。

    Netty优势

    Netty优势:API简单,性能高,入门门槛低,成熟稳健,修复了很多原生NIO的bug

    回到课程中来

    课程是以Netty实现WebSocket来进行讲解的,也就上边所说的:用Netty来实现即时通信的应用

    源码下载地址:https://img.mukewang.com/down/5a6e804c0001970d00000000.zip

    首先创建了一个全局配置类,WebSocket是全双工通信的,它是通过通道来进行通信,因此配置了系统通道组,管理所有的通道

    
    /**
     * 存储整个工程的全局配置
     * @author liuyazhuang
     *
     */
    public class NettyConfig {
    
        /**
         * 存储每一个客户端接入进来时的channel对象
         */
        public static ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    }
    
    

    配置一些通道的信息(可以理解成Servlet时配置request对象的charset、response对象的缓存)

    
    /**
     * 初始化连接时候的各个组件
     * @author liuyazhuang
     *
     */
    public class MyWebSocketChannelHandler extends ChannelInitializer<SocketChannel> {
    
        //配置通道的一些编码格式、数据大小、处理器(交由谁处理)
        @Override
        protected void initChannel(SocketChannel e) throws Exception {
            e.pipeline().addLast("http-codec", new HttpServerCodec());
            e.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
            e.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
            e.pipeline().addLast("handler", new MyWebSocketHandler());
        }
    }
    

    Netty接收请求,分别处理HTTP请求和WebSocket请求,此部分在视频中单单只是代码编写,并没有做过多的介绍。下面我就整理一下:

    • 该类是用于处理请求的核心业务类
    • 最重要的方法是:messageReceived()方法,主要判断是HTTP请求还是WebSocket请求
      • 是HTTP请求时,就handHttpRequest()来进行处理,该方法判断是否有握手的倾向,
        • 如果不是WebSocket握手请求消息,那么直接返回HTTP 400 BAD REQUEST 响应给客户端,应答消息,并关闭链接。
        • 如果是握手请求,那么就进行握手,将WebSocket相关的编码和解码类动态添加到ChannelPipeline中
      • 是websocket则群发,服务端向每个连接上来的客户端群发消息
    
    package com.imooc.netty;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelFutureListener;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    import io.netty.handler.codec.http.DefaultFullHttpResponse;
    import io.netty.handler.codec.http.FullHttpRequest;
    import io.netty.handler.codec.http.HttpResponseStatus;
    import io.netty.handler.codec.http.HttpVersion;
    import io.netty.handler.codec.http.websocketx.*;
    import io.netty.util.CharsetUtil;
    
    import java.util.Date;
    
    /**
     * 接收/处理/响应客户端websocket请求的核心业务处理类
     *
     * @author liuyazhuang
     */
    public class MyWebSocketHandler extends SimpleChannelInboundHandler<Object> {
    
        private WebSocketServerHandshaker handshaker;
        private static final String WEB_SOCKET_URL = "ws://localhost:8888/websocket";
    
        //客户端与服务端创建连接的时候调用
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            NettyConfig.group.add(ctx.channel());
            System.out.println("客户端与服务端连接开启...");
        }
    
        //客户端与服务端断开连接的时候调用
        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            NettyConfig.group.remove(ctx.channel());
            System.out.println("客户端与服务端连接关闭...");
        }
    
        //服务端接收客户端发送过来的数据结束之后调用
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            ctx.flush();
        }
    
        //工程出现异常的时候调用
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    
        //服务端处理客户端websocket请求的核心方法
        @Override
        protected void messageReceived(ChannelHandlerContext context, Object msg) throws Exception {
    
            // 传统的HTTP接入
            //第一次握手请求消息由HTTP协议承载,所以它是一个HTTP消息,执行handleHttpRequest方法来处理WebSocket握手请求。
            if (msg instanceof FullHttpRequest) {
                handHttpRequest(context, (FullHttpRequest) msg);
            }
    
            // WebSocket接入
            // 客户端通过文本框提交请求消息给服务端,WebSocketServerHandler接收到的是已经解码后的WebSocketFrame消息。
            else if (msg instanceof WebSocketFrame) {
    
                handWebsocketFrame(context, (WebSocketFrame) msg);
            }
        }
    
        /**
         * 处理客户端向服务端发起http握手请求的业务
         *
         * @param ctx
         * @param req
         */
        private void handHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
    
            //如果不是WebSocket握手请求消息,那么就返回 HTTP 400 BAD REQUEST 响应给客户端。
            if (!req.getDecoderResult().isSuccess()
                    || !("websocket".equals(req.headers().get("Upgrade")))) {
                sendHttpResponse(ctx, req,
                        new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
                return;
            }
    
            //如果是握手请求,那么就进行握手
            WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
                    WEB_SOCKET_URL, null, false);
            handshaker = wsFactory.newHandshaker(req);
            if (handshaker == null) {
                WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel());
            } else {
    
                // 通过它构造握手响应消息返回给客户端,
                // 同时将WebSocket相关的编码和解码类动态添加到ChannelPipeline中,用于WebSocket消息的编解码,
                // 添加WebSocketEncoder和WebSocketDecoder之后,服务端就可以自动对WebSocket消息进行编解码了
                handshaker.handshake(ctx.channel(), req);
            }
        }
    
        /**
         * 处理客户端与服务端之前的websocket业务
         *
         * @param ctx
         * @param frame
         */
        private void handWebsocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
            //判断是否是关闭websocket的指令
            if (frame instanceof CloseWebSocketFrame) {
                handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
            }
            //判断是否是ping消息
            if (frame instanceof PingWebSocketFrame) {
                ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
                return;
            }
    
            //判断是否是二进制消息,如果是二进制消息,抛出异常
            if (!(frame instanceof TextWebSocketFrame)) {
                System.out.println("目前我们不支持二进制消息");
                throw new RuntimeException("【" + this.getClass().getName() + "】不支持消息");
            }
    
            //返回应答消息
            //获取客户端向服务端发送的消息
            String request = ((TextWebSocketFrame) frame).text();
            System.out.println("服务端收到客户端的消息====>>>" + request);
            TextWebSocketFrame tws = new TextWebSocketFrame(new Date().toString()
                    + ctx.channel().id()
                    + " ===>>> "
                    + request);
            //群发,服务端向每个连接上来的客户端群发消息
            NettyConfig.group.writeAndFlush(tws);
        }
    
        /**
         * 服务端向客户端响应消息
         *
         * @param ctx
         * @param req
         * @param res
         */
        private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req,
                                      DefaultFullHttpResponse res) {
    
            // 返回应答给客户端
            if (res.getStatus().code() != 200) {
                ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8);
                res.content().writeBytes(buf);
                buf.release();
            }
    
            // 如果是非Keep-Alive,关闭连接
            ChannelFuture f = ctx.channel().writeAndFlush(res);
            if (res.getStatus().code() != 200) {
                f.addListener(ChannelFutureListener.CLOSE);
            }
        }
    }
    
    

    最后,编写入口程序:启动WebSocket服务

    
    package com.imooc.netty;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.Channel;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    
    /**
     * 程序的入口,负责启动应用
     * @author liuyazhuang
     *
     */
    public class Main {
        public static void main(String[] args) {
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workGroup = new NioEventLoopGroup();
            try {
                ServerBootstrap b = new ServerBootstrap();
                b.group(bossGroup, workGroup);
                b.channel(NioServerSocketChannel.class);
                b.childHandler(new MyWebSocketChannelHandler());
                System.out.println("服务端开启等待客户端连接....");
                Channel ch = b.bind(8888).sync().channel();
                ch.closeFuture().sync();
            } catch (Exception e) {
                e.printStackTrace();
            }finally{
                //优雅的退出程序
                bossGroup.shutdownGracefully();
                workGroup.shutdownGracefully();
            }
        }
    }
    

    客户端代码:

    
    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset = utf-8"/>
            <title>WebSocket客户端</title>
        <script type="text/javascript">
            var socket;
            if(!window.WebSocket){
                window.WebSocket = window.MozWebSocket;
            }
    
            if(window.WebSocket){
                socket = new WebSocket("ws://localhost:8888/websocket");
                socket.onmessage = function(event){
                    var ta = document.getElementById('responseContent');
                    ta.value += event.data + "
    ";
                };
    
                socket.onopen = function(event){
                    var ta = document.getElementById('responseContent');
                    ta.value = "你当前的浏览器支持WebSocket,请进行后续操作
    ";
                };
    
                socket.onclose = function(event){
                    var ta = document.getElementById('responseContent');
                    ta.value = "";
                    ta.value = "WebSocket连接已经关闭
    ";
                };
            }else{
                alert("您的浏览器不支持WebSocket");
            }
    
            function send(message){
                if(!window.WebSocket){
                    return;
                }
                if(socket.readyState == WebSocket.OPEN){
                    socket.send(message);
                }else{
                    alert("WebSocket连接没有建立成功!!");
                }
            }
        </script>
        </head>
        <body>
            <form onSubmit="return false;">
                <input type = "text" name = "message" value = ""/>
                <br/><br/>
                <input type = "button" value = "发送WebSocket请求消息" onClick = "send(this.form.message.value)"/>
                <hr color="red"/>
                <h2>客户端接收到服务端返回的应答消息</h2>
                <textarea id = "responseContent" style = "1024px; height:300px"></textarea>
            </form>
        </body>
    </html>
    

    !](//upload-images.jianshu.io/upload_images/5291509-66760e9dc063bbd4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

    实际使用WebSocket

    上面的例子讲解了Netty实现WebSocket,一般我们使用WebSocket不会自己来实现,都是用现成的工具包来进行实现。

    我查到的常用的方式有两种:

    • Tomcat实现WebSocket
    • 整合Spring实现WebSocket

    这一部分我就不再赘述了,等我用到的时候再补教程吧,先mark下相关的博客:

    我们的系统中用到了java-websocket 和apache的GenericObjectPool技术:

      private static final class WSClientFactory implements PooledObjectFactory<WSClient> {
            private String url;
            MessageListener listener;
    
            WSClientFactory(String url, MessageListener listener) {
                this.url = url;
                this.listener = listener;
            }
    
            @Override
            public PooledObject<WSClient> makeObject() throws Exception {
                WSClient wsClient = new WSClient(new URI(url), listener);
                Exception ex = null;
                try {
                    if (wsClient.connectBlocking(60, TimeUnit.SECONDS)) {
                        LOGGER.info("{}的WebSocket连接已打开", url);
                        return new DefaultPooledObject(wsClient);
                    }
    
                    wsClient.close();
                } catch (Exception e) {
                    ex = e;
                    wsClient.close();
                }
    
                throw new RetryableException(String.format("%s的WebSocket连接打开失败", url), ex);
            }
    
            @Override
            public void destroyObject(PooledObject<WSClient> pooledObject) {
                try {
                    pooledObject.getObject().close();
                } catch (Exception e) {
                    LOGGER.error("AbstractCityWebSocketClient.destroyObject error.", e);
                }
            }
    
            @Override
            public boolean validateObject(PooledObject<WSClient> pooledObject) {
                if (pooledObject.getObject().isOpen()) {
                    return true;
                }
    
                LOGGER.warn("{}的WebSocket连接验证失败", url);
                return false;
            }
    
            @Override
            public void activateObject(PooledObject<WSClient> pooledObject) {
    
            }
    
            @Override
            public void passivateObject(PooledObject<WSClient> pooledObject) {
    
            }
        }
    
        public static final class WSClient extends WebSocketClient {
            MessageListener listener;
    
            WSClient(URI serverUri, MessageListener listener) {
                super(serverUri);
                this.listener = listener;
                this.setConnectionLostTimeout(600);
            }
    
            @Override
            public void onOpen(ServerHandshake serverHandshake) {
                LOGGER.info("WSClient.onOpen");
            }
    
            @Override
            public void onMessage(String msg) {
                LOGGER.info("WSClient.onMessage:{}", msg);
                if (listener != null){
                    listener.onMessage(this, new AbstractMap.SimpleEntry<>(this.getURI(), msg));
                }
            }
    
            @Override
            public void onClose(int i, String s, boolean b) {
                LOGGER.warn("WSClient.onClose:i:{},s:{},b:{}", i, s, b);
            }
    
            @Override
            public void onError(Exception e) {
                LOGGER.error("WSClient.onError", e);
            }
        }
        /**
         * wsclient 是基于uri构建的
         * key:uri的串
         */
        private LoadingCache<String, GenericObjectPool<WSClient>> wsClientPoolCache;
    
    
        /**
         * 构造
         *
         * @param destroyPoolAfterAccessSec 在多少秒内,URI关联的池资源若没有被访问,则销毁
         * @param poolConfig                池的配置
         */
        public AbstractCityWebSocketClient(int destroyPoolAfterAccessSec, GenericObjectPoolConfig<WSClient> poolConfig) {
            final MessageListener listener = this;
            wsClientPoolCache = CacheBuilder
                    .newBuilder()
                    .expireAfterAccess(destroyPoolAfterAccessSec, TimeUnit.SECONDS)
                    .maximumSize(500L)
                    .removalListener((RemovalListener<String, GenericObjectPool<WSClient>>) removalNotification -> {
                        /* 连接池销毁 */
                        if (removalNotification.getValue() != null) {
                            removalNotification.getValue().close();
                            LOGGER.warn("url:{} 的WebSocket连接池已销毁,池数目:{}", removalNotification.getKey(), wsClientPoolCache.size());
                        }
                    })
                    .build(new CacheLoader<String, GenericObjectPool<WSClient>>() {
                        @Override
                        public GenericObjectPool<WSClient> load(String url) {
                            LOGGER.warn("构造 url:{} 的WebSocket连接池,池数目:{}", url, wsClientPoolCache.size() + 1);
                            return new GenericObjectPool<>(new WSClientFactory(url, listener), poolConfig);
                        }
                    });
        }
    
        @Override
        @RhinoBreaker
        @ApiResult
        protected CityResponse execBatchRequest(List<CityRequest> cityRequestList) {
            GenericObjectPool<WSClient> wsClientPool = null;
            WSClient wsClient = null;
            Transaction transaction = Cat.newTransaction(CatConstant.TRANSACTION_WEBSOCKET_BATCH_REQUEST.getKey(), CatConstant.TRANSACTION_WEBSOCKET_BATCH_REQUEST.getValue());
            URI uri;
    
            try {
                uri = getURI(cityRequestList.get(0));
                wsClientPool = wsClientPoolCache.get(uri.toString());
                wsClient = wsClientPool.borrowObject();
                CityResponse cityResponse = send(wsClient, cityRequestList);
                if (cityResponse.isOk()) {
                    transaction.setSuccessStatus();
                } else {
                    transaction.setStatus("上报失败");
                }
                return cityResponse;
            } catch (Exception e) {
                transaction.setStatus(e);
                return new FailureResponse(e);
            } finally {
                if (wsClientPool != null && wsClient != null) {
                    ((WebSocketImpl) wsClient.getConnection()).updateLastPong();
                    wsClientPool.returnObject(wsClient);
                }
                transaction.complete();
            }
        }
    
        @Override
        @RhinoBreaker
        @ApiResult
        public CityResponse execRequest(CityRequest cityRequest) {
            GenericObjectPool<WSClient> wsClientPool = null;
            WSClient wsClient = null;
            URI uri;
            Transaction transaction = Cat.newTransaction(CatConstant.TRANSACTION_WEBSOCKET_REQUEST.getKey(), CatConstant.TRANSACTION_WEBSOCKET_REQUEST.getValue());
    
            try {
                uri = getURI(cityRequest);
                wsClientPool = wsClientPoolCache.get(uri.toString());
                wsClient = wsClientPool.borrowObject();
                CityResponse cityResponse = send(wsClient, Collections.singletonList(cityRequest));
                if (cityResponse.isOk()) {
                    transaction.setSuccessStatus();
                } else {
                    transaction.setStatus("上报失败");
                }
                return cityResponse;
            } catch (Exception e) {
                transaction.setStatus(e);
                /*网络IO 异常 & 可重试异常 & 连接池获取异常*/
                return new FailureResponse(e);
            } finally {
                if (wsClientPool != null && wsClient != null) {
                    ((WebSocketImpl) wsClient.getConnection()).updateLastPong();
                    wsClientPool.returnObject(wsClient);
                }
                transaction.complete();
            }
        }

    总结

    WebSocket最大的特点就是长连接,能够实时推送数据。

    参考链接:

     参考:看完让你彻底理解 WebSocket 原理,附完整的实战代码(包含前端和后端)

    参考:WebSocket就是这么简单

    参考:Java服务端模拟websocket客户端建立长链接之---WebSocketClient

     
  • 相关阅读:
    苹果一体机发射Wi-Fi
    iphone 屏蔽系统自动更新,消除设置上的小红点
    data parameter is nil 异常处理
    copy与mutableCopy的区别总结
    java axis2 webservice
    mysql 远程 ip访问
    mysql 存储过程小问题
    mysql游标错误
    is not writable or has an invalid setter method错误的解决
    Struts2中关于"There is no Action mapped for namespace / and action name"的总结
  • 原文地址:https://www.cnblogs.com/aspirant/p/11482920.html
Copyright © 2011-2022 走看看