Netty——WebSocket之事

        在说WebSocket以前,我们再来看看HTTP协议,HTTP1.0,HTTP1.1,HTTP2.0每个版本的更新带来更高效的更丰富的功能:短连接、长连接、缓存处理的丰富、断点续传、错误通知的丰富、多路复用、请求优先级、header压缩……更多的我们来看这篇文章:HTTP1.0、HTTP1.1和HTTP2.0的区别https://www.cnblogs.com/zhangyfr/p/8662673.html)

        HTTP解决了我们很多问题,而且也非常强大,但是它有哪些不足之处呢?我们来看下:1,HTTP协议为半双工协议,可以在 client和server两个方向上传输,但是不能同时传输,意味着同一时刻,只能一个方向上传送;    2,HTTP消息冗长而复杂,包含消息头、消息体、换行符,虽然功能强大,但是在某些方面也成了不足。              例如我们需要server向client端推送数据利用HTTP就很难,例如一些监控系统,随着后台数据的变化,前边也跟着相应的变化,随后后来的轮询机制等可以解决这个问题,但是这样采用了长连接,大量消耗了服务器的宽带和资源。也就是在client和server期望实时通讯的情况下,Http就有些头疼了。亦是HTML5定义了WebSocket协议,更好的节省服务器资源和宽带,并达到实时通讯的目的。好下边我们来看WebSocket的相关内容:

       WebSocket,浏览器和服务器只需要做一个握手动作(非HTTP三次握手),然后之间就会形成一条快速通道,两者就可以相互传送数据了,是基于TCP双向全双工进行消息传递,其特点包括:1,单一的TCP连接,采用全双工模式通信;    2,对代理、防火墙和路由器透明;     3,无头部信息、Cookie和身份验证;     4,无安全开销;     5,通过“ping/pong”帧保持链路激活;     6,服务器可以主动传递消息给客户端,不再需要客户端轮询。

      好了,讲了WebSocket,我们来看下,利用Netty如何方便快速的开发WebSocket的服务端,下边通过一个实时传输文本的例子来看,服务端代码:

public class WebSocketServer {


    public void run(final int port) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline pipeline = ch.pipeline();
                    pipeline.addLast("http-codec",new HttpServerCodec());
                    pipeline.addLast("aggregator",new HttpObjectAggregator(65536));
                    pipeline.addLast("http-chunked",new ChunkedWriteHandler());
                    //业务处理
                    ch.pipeline().addLast("handler", new WebSocketServerHandler());
                }
            });
            Channel ch =b.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 + "/");
            ch.closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }


    public static void main(String[] args) {
        int port = 8091;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {

            }
        }

        new WebSocketServer().run(port);
    }
}




public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {

    private static final Logger logger = Logger.getLogger(WebSocketServerHandler.class.getName());

    private WebSocketServerHandshaker handshaker;

    /**
     * 接受到消息处理
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
        if(msg instanceof FullHttpRequest){
            //传统的http接入
            handlerHttpRequest(ctx,(FullHttpRequest)msg);
        }else if(msg instanceof WebSocketFrame){
            //WebSocket接入
            handlerWebSocketFrame(ctx,(WebSocketFrame)msg);
        }
    }

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

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


    /**
     * 处理正常请求
     * @param ctx
     * @param req
     */
    private void handlerHttpRequest(ChannelHandlerContext ctx,FullHttpRequest req){
        //如果HTTP解码失败,返回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:8091/websocket",null,false);
        handshaker = wsFactory.newHandshaker(req);
        if(handshaker ==null){
            WebSocketServerHandshakerFactory .sendUnsupportedVersionResponse(ctx.channel());
        }else{
            handshaker.handshake(ctx.channel(),req);
        }
    }


    /**
     * 处理websocket请求
     * @param ctx
     * @param frame
     */
    private void handlerWebSocketFrame(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 request = ((TextWebSocketFrame) frame).text();
        if(logger.isLoggable(Level.FINE)){
            logger.fine(String.format("%s received %s",ctx.channel(),request));
        }
        System.out.println("server 接受到的信息:" + request);
        ctx.channel().write(new TextWebSocketFrame(request + " ,欢迎使用netty websocket服务,现在时刻:" + new Date().toString()));
    }

    /**
     * 响应客户端消息
     * @param ctx
     * @param req
     * @param res
     */
    private static void sendHttpResponse(ChannelHandlerContext ctx , FullHttpRequest req, FullHttpResponse res){
        if(res.status().code() !=200){
            ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
            HttpHeaderUtil.setContentLength(res,res.content().readableBytes());
        }

        ChannelFuture f = ctx.channel().writeAndFlush(res);
        if(!HttpHeaderUtil.isKeepAlive(req) || res.status().code()!=200){
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }

}

         前端页面:

<html>
    <head>
        <meta charset="UTF-8">
        Netty WebSocket 时间服务器
    </head>
    <br>
    <body>
        <br>
        <script type="text/javascript">
            var  socket;
            if(!window.WebSocket){
                window.WebSocket = window.MozWebSocket;
            }
            if(window.WebSocket){
                socket = new WebSocket("ws://localhost:8091/websocket");
                socket.onmessage = function (event) {
                    var ta =document.getElementById('responseText');
                    ta.value ="";
                    ta.value = event.data;
                };
                socket.onopen =function (event) {
                    var ta =document.getElementById("responseText");
                    ta.value  = "打开WebSocket服务正常,浏览器支持websocket!"
                };
                socket.onclose = function (event) {
                    var ta =document.getElementById("responseText");
                    ta.value="";
                    ta.value="WebSocket关闭!";
                };
            }else{
                alert("sorry,您的浏览器不支持websocket协议!");
            }

            function send(message) {
                if(!window.WebSocket){
                    return;
                }
                if(socket.readyState = WebSocket.OPEN){
                    socket.send(message);
                    socket.send("ljh study netty")
                }else{
                    alert("WebSocket连接没有建立成功!");
                }
            }
        </script>

        <form onsubmit="return false;">
            <input type="text" name="message" value="Netty最佳实践"/>
            <br><br>
            <input type="button" value="发送WebSocket消息" onclick="send(this.form.message.value)"/>
            <hr color="blue"/>
            <h3>服务端返回的应答消息</h3>
            <textarea id="responseText" style="width: 500px;height :300px;"></textarea>

        </form>
    </body>
</html>

          Netty对WebSocket协议栈的支持还是非常棒的,更多的我们需要了解可以到WebSocket官网http://www.websocket.org/index.html)

猜你喜欢

转载自blog.csdn.net/liujiahan629629/article/details/83551341