WebSocket,Socket,Http之间的区别及使用Netty实现WebSocket服务端

文章目录

  • WebSocket(转自CSDN博客王卫东)
    • WebSocket介绍与原理
    • 目的:即时通信,替代轮询
    • 原理
    • WebSocket与HTTP的关系
      • 相同点
      • 不同点
      • 联系
    • WebSocket与Socket的关系
    • 区别
    • HTML5与WebSocket的关系
    • WebSocket 机制
    • WebSocket 实现
    • WebSocket 服务端API
    • WebSocket 客户端API
  • WebSocket(Netty版)即时通讯实现
    • Netty 核心架构
    • 项目结构
    • 代码

WebSocket(转自CSDN博客王卫东)

WebSocket介绍与原理

WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP请求完成。
——百度百科

目的:即时通信,替代轮询

  网站上的即时通讯是很常见的,比如网页的QQ,聊天系统等。按照以往的技术能力通常是采用轮询、Comet技术解决。

  HTTP协议是非持久化的,单向的网络协议,在建立连接后只允许浏览器向服务器发出请求后,服务器才能返回相应的数据。当需要即时通讯时,通过轮询在特定的时间间隔(如1秒),由浏览器向服务器发送Request请求,然后将最新的数据返回给浏览器。这样的方法最明显的缺点就是需要不断的发送请求,而且通常HTTP request的Header是非常长的,为了传输一个很小的数据 需要付出巨大的代价,是很不合算的,占用了很多的宽带。

  缺点:会导致过多不必要的请求,浪费流量和服务器资源,每一次请求、应答,都浪费了一定流量在相同的头部信息上

   然而WebSocket的出现可以弥补这一缺点。在WebSocket中,只需要服务器和浏览器通过HTTP协议进行一个握手的动作,然后单独建立一条TCP的通信通道进行数据的传送。

原理

WebSocket同HTTP一样也是应用层的协议,但是它是一种双向通信协议,是建立在TCP之上的。

连接过程 —— 握手过程

  1. 浏览器、服务器建立TCP连接,三次握手。这是通信的基础,传输控制层,若失败后续都不执行。
  2. TCP连接成功后,浏览器通过HTTP协议向服务器传送WebSocket支持的版本号等信息。(开始前的HTTP握手)
  3. 服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据。
  4. 当收到了连接成功的消息后,通过TCP通道进行传输通信。

WebSocket与HTTP的关系

相同点

  1. 都是一样基于TCP的,都是可靠性传输协议。
  2. 都是应用层协议。

不同点

  1. WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息。HTTP是单向的。
  2. WebSocket是需要握手进行建立连接的。

联系

  WebSocket在建立握手时,数据是通过HTTP传输的。但是建立之后,在真正传输时候是不需要HTTP协议的。

WebSocket与Socket的关系

  Socket其实并不是一个协议,而是为了方便使用TCP或UDP而抽象出来的一层,是位于应用层和传输控制层之间的一组接口。

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

  当两台主机通信时,必须通过Socket连接,Socket则利用TCP/IP协议建立TCP连接。TCP连接则更依靠于底层的IP协议,IP协议的连接则依赖于链路层等更低层次。
  WebSocket则是一个典型的应用层协议。

区别

  Socket是传输控制层协议,WebSocket是应用层协议。

HTML5与WebSocket的关系

  WebSocket API 是 HTML5 标准的一部分, 但这并不代表 WebSocket 一定要用在 HTML 中,或者只能在基于浏览器的应用程序中使用。
  实际上,许多语言、框架和服务器都提供了 WebSocket 支持,例如:

  • 基于 C 的 libwebsocket.org
  • 基于 Node.js 的 Socket.io
  • 基于 Python 的 ws4py
  • 基于 C++ 的 WebSocket++
  • Apache 对 WebSocket 的支持: Apache Module mod_proxy_wstunnel
  • Nginx 对 WebSockets 的支持: NGINX as a WebSockets Proxy 、 NGINX Announces Support for WebSocket Protocol 、WebSocket proxying
  • lighttpd 对 WebSocket 的支持:mod_websocket

WebSocket 机制

以下简要介绍一下 WebSocket 的原理及运行机制。

WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和 HTTP 最大不同是:

  • WebSocket 是一种双向通信协议,在建立连接后,WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据,就像 Socket 一样;
  • WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接,连接成功后才能相互通信。

非 WebSocket 模式传统 HTTP 客户端与服务器的交互如下图所示:
WebSocket,Socket,Http之间的区别及使用Netty实现WebSocket服务端_第1张图片
图1. 传统HTTP请求响应客户端服务器交互图

使用 WebSocket 模式客户端与服务器的交互如下图:

WebSocket,Socket,Http之间的区别及使用Netty实现WebSocket服务端_第2张图片
图2. WebSocket请求响应客户端服务器交互图

  上图对比可以看出,相对于传统 HTTP 每次请求-应答都需要客户端与服务端建立连接的模式,WebSocket 是类似 Socket 的 TCP 长连接的通讯模式,一旦 WebSocket 连接建立后,后续数据都以帧序列的形式传输。在客户端断开 WebSocket 连接或 Server 端断掉连接前,不需要客户端和服务端重新发起连接请求。在海量并发及客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实时性优势明显。

  我们再通过客户端和服务端交互的报文看一下 WebSocket 通讯与传统 HTTP 的不同:

  在客户端,new WebSocket 实例化一个新的 WebSocket 客户端对象,连接类似 ws://yourdomain:port/path 的服务端 WebSocket URL,WebSocket 客户端对象会自动解析并识别为 WebSocket 请求,从而连接服务端端口,执行双方握手过程,客户端发送数据格式类似:

清单 1.WebSocket客户端连接报文

GET /webfin/websocket/ HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==
Origin: 
http://localhost
:8080
Sec-WebSocket-Version: 13
http://localhost
:8080
Sec-WebSocket-Version: 13

  可以看到,客户端发起的 WebSocket 连接报文类似传统 HTTP 报文,”Upgrade:websocket”参数值表明这是 WebSocket 类型请求,“Sec-WebSocket-Key”是 WebSocket 客户端发送的一个 base64 编码的密文,要求服务端必须返回一个对应加密的“Sec-WebSocket-Accept”应答,否则客户端会抛出“Error during WebSocket handshake”错误,并关闭连接。

服务端收到报文后返回的数据格式类似:

清单 2.WebSocket 服务端响应报文

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=

  “Sec-WebSocket-Accept”的值是服务端采用与客户端一致的密钥计算出来后返回客户端的,“HTTP/1.1 101 Switching Protocols”表示服务端接受 WebSocket 协议的客户端连接,经过这样的请求-响应处理后,客户端服务端的 WebSocket 连接握手成功, 后续就可以进行 TCP 通讯了。

  在开发方面,WebSocket API 也十分简单,我们只需要实例化 WebSocket,创建连接,然后服务端和客户端就可以相互发送和响应消息,在下文 WebSocket 实现及案例分析部分,可以看到详细的 WebSocket API 及代码实现。

WebSocket 实现

  如上文所述,WebSocket 的实现分为客户端和服务端两部分,客户端(通常为浏览器)发出 WebSocket 连接请求,服务端响应,实现类似 TCP 握手的动作,从而在浏览器客户端和 WebSocket 服务端之间形成一条 HTTP 长连接快速通道。两者之间后续进行直接的数据互相传送,不再需要发起连接和相应。

以下简要描述 WebSocket 服务端 API 及客户端 API。

WebSocket 服务端API

WebSocket 服务端在各个主流应用服务器厂商中已基本获得符合 JEE JSR356 标准规范 API 的支持,以下列举了部分常见的商用及开源应用服务器对 WebSocket Server 端的支持情况:

表1. WebSocket 服务段支持

厂商 应用服务器 备注
IBM WebSphere WebSphere 8.0 以上版本支持,7.X 之前版本结合 MQTT 支持类似的 HTTP 长连接
甲骨文 WebLogic WebLogic 12c 支持,11g 及 10g 版本通过 HTTP Publish 支持类似的 HTTP 长连接
微软 IIS IIS 7.0+支持
Apache Tomcat Tomcat 7.0.5+支持,7.0.2X 及 7.0.3X 通过自定义 API 支持
Jetty Jetty 7.0+支持

以下我们使用 Tomcat7.0.5 版本的服务端示例代码说明 WebSocket 服务端的实现:

JSR356 的 WebSocket 规范使用 javax.websocket.*的 API,可以将一个普通 Java 对象(POJO)使用 @ServerEndpoint 注释作为 WebSocket 服务器的端点,代码示例如下:

清单 3.WebSocket 服务段 API 示例

 @ServerEndpoint("/echo")
 public class EchoEndpoint {
 
 @OnOpen
 public void onOpen(Session session) throws IOException {
 //以下代码省略...
 }
 
 @OnMessage
 public String onMessage(String message) {
 //以下代码省略...
 }
 
 @Message(maxMessageSize=6)
 public void receiveMessage(String s) {
 //以下代码省略...
 } 
 
 @OnError
 public void onError(Throwable t) {
 //以下代码省略...
 }
 
 @OnClose
 public void onClose(Session session, CloseReason reason) {
 //以下代码省略...
 } 
 }

代码解释:

上文的简洁代码即建立了一个 WebSocket 的服务端,@ServerEndpoint("/echo") 的 annotation 注释端点表示将 WebSocket 服务端运行在 ws://[Server 端 IP 或域名]:[Server 端口]/websockets/echo 的访问端点,客户端浏览器已经可以对 WebSocket 客户端 API 发起 HTTP 长连接了。

使用 ServerEndpoint 注释的类必须有一个公共的无参数构造函数,@onMessage 注解的 Java 方法用于接收传入的 WebSocket 信息,这个信息可以是文本格式,也可以是二进制格式。

OnOpen 在这个端点一个新的连接建立时被调用。参数提供了连接的另一端的更多细节。Session 表明两个 WebSocket 端点对话连接的另一端,可以理解为类似 HTTPSession 的概念。

OnClose 在连接被终止时调用。参数 closeReason 可封装更多细节,如为什么一个 WebSocket 连接关闭。

更高级的定制如 @Message 注释,MaxMessageSize 属性可以被用来定义消息字节最大限制,在示例程序中,如果超过 6 个字节的信息被接收,就报告错误和连接关闭。

注意:早期不同应用服务器支持的 WebSocket 方式不尽相同,即使同一厂商,不同版本也有细微差别,如 Tomcat 服务器 7.0.5 以上的版本都是标准 JSR356 规范实现,而 7.0.2x/7.0.3X 的版本使用自定义 API (WebSocketServlet 和 StreamInbound, 前者是一个容器,用来初始化 WebSocket 环境;后者是用来具体处理 WebSocket 请求和响应,详见案例分析部分),且 Tomcat7.0.3x 与 7.0.2x 的 createWebSocketInbound 方法的定义不同,增加了一个 HttpServletRequest 参数,使得可以从 request 参数中获取更多 WebSocket 客户端的信息,如下代码所示:

清单 4.Tomcat7.0.3X版本WebSocket API


public class EchoServlet extends WebSocketServlet {
@Override
protected StreamInbound createWebSocketInbound(String subProtocol,
HttpServletRequest request) {
 //以下代码省略....
return new MessageInbound() {
 //以下代码省略....
}
protected void onBinaryMessage(ByteBuffer buffer)
throws IOException {
 //以下代码省略...
}
protected void onTextMessage(CharBuffer buffer) throws IOException {
 getWsOutbound().writeTextMessage(buffer);
 //以下代码省略...
}
};
}
}

因此选择 WebSocket 的 Server 端重点需要选择其版本,通常情况下,更新的版本对 WebSocket 的支持是标准 JSR 规范 API,但也要考虑开发易用性及老版本程序移植性等方面的问题,如下文所述的客户案例,就是因为客户要求统一应用服务器版本所以使用的 Tomcat 7.0.3X 版本的 WebSocketServlet 实现,而不是 JSR356 的 @ServerEndpoint 注释端点。

WebSocket 客户端API

对于 WebSocket 客户端,主流的浏览器(包括 PC 和移动终端)现已都支持标准的 HTML5 的 WebSocket API,这意味着客户端的 WebSocket JavaScirpt 脚本具备良好的一致性和跨平台特性,以下列举了常见的浏览器厂商对 WebSocket 的支持情况:

表 2.WebSocket 客户端支持

浏览器 支持情况
Chrome Chrome version 4+支持
Firefox Firefox version 5+支持
IE IE version 10+支持
Safari IOS 5+支持
Android Brower Android 4.5+支持

客户端 WebSocket API 基本上已经在各个主流浏览器厂商中实现了统一,因此使用标准 HTML5 定义的 WebSocket 客户端的 JavaScript API 即可,当然也可以使用业界满足 WebSocket 标准规范的开源框架,如 Socket.io。

清单5. WebSocket 客户端API

var ws = new WebSocket(“ws://echo.websocket.org”); 
 ws.onopen = function(){ws.send(“Test!); }; 
 ws.onmessage = function(evt){console.log(evt.data);ws.close();}; 
 ws.onclose = function(evt){console.log(“WebSocketClosed!);}; 
 ws.onerror = function(evt){console.log(“WebSocketError!);};

第一行代码是在申请一个 WebSocket 对象,参数是需要连接的服务器端的地址,同 HTTP 协议开头一样,WebSocket 协议的 URL 使用 ws://开头,另外安全的 WebSocket 协议使用 wss://开头。

第二行到第五行为 WebSocket 对象注册消息的处理函数,WebSocket 对象一共支持四个消息 onopen, onmessage, onclose 和 onerror,有了这 4 个事件,我们就可以很容易很轻松的驾驭 WebSocket。

当 Browser 和 WebSocketServer 连接成功后,会触发 onopen 消息;如果连接失败,发送、接收数据失败或者处理数据出现错误,browser 会触发 onerror 消息;当 Browser 接收到 WebSocketServer 发送过来的数据时,就会触发 onmessage 消息,参数 evt 中包含 Server 传输过来的数据;当 Browser 接收到 WebSocketServer 端发送的关闭连接请求时,就会触发 onclose 消息。我们可以看出所有的操作都是采用异步回调的方式触发,这样不会阻塞 UI,可以获得更快的响应时间,更好的用户体验。

WebSocket(Netty版)即时通讯实现

Netty 核心架构

WebSocket,Socket,Http之间的区别及使用Netty实现WebSocket服务端_第3张图片
官网给出的底层示意图:
WebSocket,Socket,Http之间的区别及使用Netty实现WebSocket服务端_第4张图片

项目结构

WebSocket,Socket,Http之间的区别及使用Netty实现WebSocket服务端_第5张图片

代码

public class WebSocketServer {
    public void run(int port) {
        //工作组
        //处理客户端连接
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        //读写处理
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            pipeline.addLast("http-codec",
                                    new HttpServerCodec());
                            pipeline.addLast("aggregator",
                                    new HttpObjectAggregator(65536));
                            pipeline.addLast("http-chunked",
                                    new ChunkedWriteHandler());
                            pipeline.addLast("handler", new NettyChatServerHandler(port));

                        }
                    });

            //同步绑定端口, 开启服务
            Channel channel = bootstrap.bind(port).sync().channel();
            System.out.println("Web socket server started at port" + port + ".");
            System.out.println("Open your browser and navigate to http://localhost:" + port + "/");


            //同步阻塞 等待服务关闭
            channel.closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }

    public static void main(String[] args) {
        int port = 8081;
        new WebSocketServer().run(port);
    }
}

netty搭建的服务器基本上都是差不多的写法:

  • 绑定主线程组和工作线程组,这部分对应架构图中的事件循环组

  • 只有服务器才需要绑定端口,客户端是绑定一个地址

  • 配置channel(数据通道)参数,重点就是ChannelInitializer的配置

  • 以异步的方式启动,最后是结束关闭两个线程组

自定义处理器NettyChatServerhandler

public class NettyChatServerHandler extends SimpleChannelInboundHandler<Object> {

    private int port;

    private static volatile int onlineCount = 0; //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。


    private static List<String> list = new ArrayList<String>();   //在线列表,记录用户名称
    private static Map<String, ChannelHandlerContext> routetab = new HashMap<String, ChannelHandlerContext>();  //用户名和websocket的sChannelHandlerContext绑定的路由表


    private WebSocketServerHandshaker handshaker;

    public NettyChatServerHandler(int port) {
        this.port = port;
    }

    @Override
    public void messageReceived(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        // 传统的HTTP接入
        if (msg instanceof FullHttpRequest) {
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        }
        // WebSocket接入
        else if (msg instanceof WebSocketFrame) {
            handleWebSocketFrame(ctx, (WebSocketFrame) msg);
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    private void handleHttpRequest(ChannelHandlerContext ctx,
                                   FullHttpRequest req) throws Exception {

        // 如果HTTP解码失败,返回HTTP异常
        if (!req.getDecoderResult().isSuccess()
                || (!"websocket".equals(req.headers().get("Upgrade")))) {
            sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1,
                    BAD_REQUEST));
            return;
        }


        // 构造握手响应返回,本机测试
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
                "ws://localhost:" + port + "/websocket", null, false);
        handshaker = wsFactory.newHandshaker(req);
        if (handshaker == null) {
            WebSocketServerHandshakerFactory
                    .sendUnsupportedWebSocketVersionResponse(ctx.channel());
        } else {
            handshaker.handshake(ctx.channel(), req);
        }
    }

    private void handleWebSocketFrame(ChannelHandlerContext ctx,
                                      WebSocketFrame frame) {

        //判断是否式打开链路指令


        // 判断是否是关闭链路的指令
        if (frame instanceof CloseWebSocketFrame) {
            handshaker.close(ctx.channel(),
                    (CloseWebSocketFrame) frame.retain());
            return;
        }
        // 判断是否是Ping消息
        if (frame instanceof PingWebSocketFrame) {
            ctx.channel().write(
                    new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        // 本例程仅支持文本消息,不支持二进制消息
        if (!(frame instanceof TextWebSocketFrame)) {
            throw new UnsupportedOperationException(String.format(
                    "%s frame types not supported", frame.getClass().getName()));
        }

        // 返回应答消息
        String _message = ((TextWebSocketFrame) frame).text();

        JSONObject chat = JSON.parseObject(_message);
        String type = chat.get("type").toString();

        if ("login".equals(type)) {
            handleWebSocketLogin(chat, ctx);
        } else if ("logout".equals(type)) {
            handleWebSocketLogout(chat);
        } else if ("message".equals(type)) {
            handleWebSocketMessage(_message);
        }


    }

    //处理客户端发送的消息
    private void handleWebSocketMessage(String _message) {
        JSONObject chat = JSON.parseObject(_message);
        JSONObject message = JSON.parseObject(chat.get("message").toString());
        if (message.get("to") == null || message.get("to").equals("")) {      //如果to为空,则广播;如果不为空,则对指定的用户发送消息
            broadcast(_message);
        } else {
            String[] userlist = message.get("to").toString().split(",");
            singleSend(_message, routetab.get(message.get("from").toString()));      //发送给自己,这个别忘了
            for (String user : userlist) {
                if (!user.equals(message.get("from"))) {
                    singleSend(_message, routetab.get(user));     //分别发送给每个指定用户
                }
            }
        }
    }

    //处理客户端退出
    private void handleWebSocketLogout(JSONObject chat) {
        JSONObject message = JSON.parseObject(chat.get("message").toString());
        String userid = message.get("from").toString();
        routetab.remove(userid);
        list.remove(userid);
        subOnlineCount();

        broadcast(getMessage("[" + userid + "]退出群聊,当前在线人数为:" + getOnlineCount(), "notice", list));
    }

    //处理客户端登录
    private void handleWebSocketLogin(JSONObject chat, ChannelHandlerContext ctx) {
        JSONObject message = JSON.parseObject(chat.get("message").toString());
        String userid = message.get("from").toString();
        routetab.put(userid, ctx);
        list.add(userid);
        addOnlineCount();
        broadcast(getMessage("[" + userid + "]加入群聊,当前在线人数为:" + getOnlineCount(), "notice", list));
    }


    //处理http 请求连接
    private static void sendHttpResponse(ChannelHandlerContext ctx,
                                         FullHttpRequest req, FullHttpResponse res) {
        // 返回应答给客户端
        if (res.getStatus().code() != 200) {
            ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(),
                    CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
            setContentLength(res, res.content().readableBytes());
        }

        // 如果是非Keep-Al2ive,关闭连接
        ChannelFuture f = ctx.channel().writeAndFlush(res);
        if (!isKeepAlive(req) || res.getStatus().code() != 200) {
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

    public int getOnlineCount() {
        return onlineCount;
    }

    public void addOnlineCount() {
        NettyChatServerHandler.onlineCount++;
    }

    public void subOnlineCount() {
        NettyChatServerHandler.onlineCount--;
    }

    /**
     * 组装返回给前台的消息
     *
     * @param message 交互信息
     * @param type    信息类型
     * @param list    在线列表
     * @return
     */
    public String getMessage(String message, String type, List list) {
        JSONObject member = new JSONObject();
        member.put("message", message);
        member.put("type", type);
        member.put("list", list);
        return member.toString();
    }

    /**
     * 广播消息
     *
     * @param message
     */
    public void broadcast(String message) {
        Set<Map.Entry<String, ChannelHandlerContext>> entries = routetab.entrySet();

        Iterator<Map.Entry<String, ChannelHandlerContext>> iterator = entries.iterator();

        while (iterator.hasNext()) {
            Map.Entry<String, ChannelHandlerContext> entry = iterator.next();
            entry.getValue().writeAndFlush(new TextWebSocketFrame(message));
        }
    }

    /**
     * 对特定用户发送消息
     *
     * @param message
     * @param session
     */
    public void singleSend(String message, ChannelHandlerContext channelHandlerContext) {
        channelHandlerContext.writeAndFlush(new TextWebSocketFrame(message));
    }
}

执行流程是:

  • web发起一次类似是http的请求,并在channelRead0方法中进行处理,并通过instanceof去判断帧对象是FullHttpRequest还是WebSocketFrame,建立连接是时候会是FullHttpRequest
  • 在handleHttpRequest方法中去创建websocket,首先是判断Upgrade是不是websocket协议,若不是则通过sendHttpResponse将错误信息返回给客户端,紧接着通过WebSocketServerHandshakerFactory创建socket对象并通过handshaker握手创建连接
  • 在连接创建好后的所以消息流动都是以WebSocketFrame来体现
  • 在handlerWebSocketFrame去处理消息,也可能是客户端发起的关闭指令,ping指令等等

再向客户端发送消息时,需刷新缓存。应使用ChannelHandlerContext的**writeAndFlush()**发送消息给客户端。

你可能感兴趣的:(WebSocket)