Netty网络编程八:Netty之WebSocket协议栈开发详解

一:概述

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
其使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。其中,浏览器和服务器只需要完成一次握手,
两者之间就直接可以创建持久性的连接,并进行双向数据传输。

二:websocket握手过程

一个典型的WebSocket握手请求如下:
客户端请求:

GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13

服务端响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Location: ws://example.com/

注意:

  1. Connection 必须设置 Upgrade,表示客户端希望连接升级。
  2. Upgrade 字段必须设置 Websocket,表示希望升级到 Websocket 协议。
  3. Sec-WebSocket-Key 是随机的字符串,服务器端会用这些数据来构造出一个 SHA-1 的信息摘要。把 “Sec-WebSocket-Key” 加上一个特殊字符串 “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,然后计算 SHA-1 摘要,之后进行 BASE-64 编码,将结果做为 “Sec-WebSocket-Accept” 头的值,返回给客户端。如此操作,可以尽量避免普通 HTTP 请求被误认为 Websocket 协议。
  4. Sec-WebSocket-Version 表示支持的 Websocket 版本。RFC6455 要求使用的版本是 13,之前草案的版本均应当弃用。
  5. Origin 字段是可选的,通常用来表示在浏览器中发起此 Websocket 连接所在的页面,类似于 Referer。但是,与 Referer 不同的是,Origin 只包含了协议和主机名称。

三:Netty中WebSocket协议栈的开发

WebSocket服务端:

public class NettyWebSocketServer {

    private int port;

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

    public void start(){
        // boss 是处理客户端连接的线程池
        // worker 是处理从客户端连接转发过来的IO数据的读写线程池
        NioEventLoopGroup boss = new NioEventLoopGroup();
        NioEventLoopGroup worker = new NioEventLoopGroup();
        try{
            // ServerBootstrap 对外一个便利创建服务端,Builder建造者设计模式
            ServerBootstrap sb = new ServerBootstrap();
            // 绑定线程池
            sb.group(boss,worker)
                    // 绑定channel 服务端绑定NioServerSocketChannel,此实现jdk的ServerSocketChannel
                    .channel(NioServerSocketChannel.class)
                    // 绑定服务端相关参数,可添加绑定多个参数
                    .option(ChannelOption.SO_BACKLOG, 1024) //指定此套接口排队的最大连接个数
                    // IO事件处理类,主要处理IO事件的读写
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            // 请求和应答消息编码或者解码为HTTP消息
                            pipeline.addLast(new HttpServerCodec());
                            // 将HTTP消息的多个部分组合成一条完整的HTTP消息
                            pipeline.addLast(new HttpObjectAggregator(65535));
                            // 向客户端发送HTML5文件,主要用于支持浏览器和服务端进行WebSocket通信
                            pipeline.addLast(new ChunkedWriteHandler());
                            pipeline.addLast(new NettyWebSocketServerHandler());

                        }
                    });
            // 绑定端口,同步等待成功
            ChannelFuture cf = sb.bind(port).sync();
            System.out.println("服务已启动.................监听端口:" + port);
            // 等待服务器监听端口关闭
            cf.channel().closeFuture().sync();
        }catch (Exception e){
            // 优雅关闭线程资源
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        NettyWebSocketServer nettyServer = new NettyWebSocketServer(8080);
        nettyServer.start();
    }
}

Handler处理:

public class NettyWebSocketServerHandler extends ChannelInboundHandlerAdapter {

    private WebSocketServerHandshaker handshaker;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 客户端第一次接入,升级Upgrade  websocket
        if(msg instanceof FullHttpRequest){
            handleHttpRequest(ctx, (FullHttpRequest)msg);
        }
        // websocket数据交互
        else if(msg instanceof WebSocketFrame){
            handleWebSocketFrame(ctx , (WebSocketFrame)msg);
        }
    }

    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 supportes", frame.getClass().getName()));
        }
        // 返回应答消息
        String request = ((TextWebSocketFrame) frame).text();
        System.out.println("当前收到的消息是: " + request);
        String timeStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        ctx.channel().write(new TextWebSocketFrame(request+ ", netty webSocket 服务端,time now is " + timeStr));
    }

    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
        // 如果http解析失败,返回异常
        if(!req.decoderResult().isSuccess() || (!"websocket".equals(req.headers().get("Upgrade")))){
            sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }
        // 构造握手响应返回
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://localhost:8080/websocket",null, false);
        handshaker = wsFactory.newHandshaker(req);
        if(handshaker == null){
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        }else {
            handshaker.handshake(ctx.channel(),req);
        }
    }

    private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, DefaultFullHttpResponse response) {
        // 返回给客户端
        if(response.status().code() != 200){
            ByteBuf buf = Unpooled.copiedBuffer(req.toString().toString(), CharsetUtil.UTF_8);
            response.content().writeBytes(buf);
            buf.release();
            response.headers().set("Content-Length",response.content().readableBytes());
        }
        // 如果是非Keep-Alive,关闭连接
        ChannelFuture f = ctx.channel().writeAndFlush(response);
        if(!req.headers().get("Connection").equals("keep-alive") || response.status().code()!=200){
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }

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

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

客户端:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    <script type="text/javascript">
        var socket;
        if(window.WebSocket){
            socket = new WebSocket("ws://localhost:8080/websocket");
            socket.onmessage = function (ev) {
                var ta = document.getElementById("responseText");
                ta.value = "";
                ta.value = ev.data;
            };
            socket.onopen = function (ev) {
                var ta = document.getElementById("responseText");
                ta.value = "打开websocket服务端正常,浏览器支持WebSocket!";
            }
            socket.onclose = function (ev) {
                var ta = document.getElementById("responseText");
                ta.value = "正在关闭WebSocket.....";
            }
        }else {
            console.info("log......................")
            alert("当前浏览器不支持WeboSocket协议!")
        }
        function send(message) {
            if(!window.WebSocket){return;}
            if(socket.readyState == WebSocket.OPEN){
                socket.send(message);
            }else {
                alert("webSOcket 连接没有建立成功!")
            }
        }
    </script>

    <form "return false;">
        <input type="text" name="message" value="Netty webSocket学习"/>
        <br/>
        <input type="button" value="发送webSocket请求消息" "send(this.form.message.value)"/>
        <hr color="blue"/>
        <h3>服务端返回应答消息</h3>
        <textarea id="responseText" style="width:500px;height:300px;"></textarea>
    </form>

</body>
</html>

核心api:
WebSocketServerHandshaker : 主要是负责客户端服务端握手协议的基类
WebSocketFrame : 负责websocket通信的基类,其子类包括
在这里插入图片描述
分别对不同类型的数据进行处理。

博客中案例代码:https://download.csdn.net/download/qq_22871607/11072379

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值