netty对websocket的支持

netty对websocket的支持

什么是WebSocket?

WebSocket是一种在2011年被互联网工程任务组(IETF)标准化的协议。WebSocket解决了一个长期存在的问题:既然底层的协议(HTTP)是一个请求/响应模式的交互序列(半双工),那么如何实时地发布信息呢?AJAX提供了一定程度上的改善,但是数据流仍然是由客户端所发送的请求驱动的,还有其他的一些或多或少的取巧方式(Comet)。

WebSocket规范以及它的实现代表了对一种更加有效的解决方案的尝试。简单地说,WebSocket提供了在一个单个的TCP连接上提供双向的通信,它为网页和远程服务器之间的双向通信提供了一种替代HTTP轮询的方案。也就是说,WebSocket在客户端和服务器之间提供了真正的双向数据交换,WebSocket连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket只需要建立一次连接,就可以一直保持连接状态,这相比于轮询方式的不停建立连接显然效率要大大提高。

特点:

  • HTML5中的协议,实现与客户端与服务器双向通信,基于消息的文本或二进制数据通信。
  • 适合于对数据的实时性要求比较强的场景,如通信、直播、共享桌面,特别适合于客户。
  • 与服务频繁交互的情况下,如实时共享、多人协作等平台。

缺点:

  • 采用新的协议,前台和后端都需要实现websocket协议。
  • 客户端并不是所有浏览器都支持。

WebSocket通信握手

Websocket借用了HTTP的协议来完成一部分握手。

要使用WebSocket,客户端的请求头中必须携带如下信息:

Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: eKx07sBdizAQzCa6Hq4Bvw==
Sec-WebSocket-Version: 13

说明:

  • Connection: Upgrade:表示客户端希望连接升级。
  • Upgrade: websocket:Upgrade字段必须设置Websocket,表示希望升级到Websocket协议。
  • Sec-WebSocket-Key:是一个随机的字符串。
  • Sec-WebSocket-Version:表示支持的Websocket版本。RFC6455要求使用的版本是13,之前草案的版本均应当弃用。

服务器端返回的响应头:

connection: upgrade
sec-websocket-accept: tUrLqjHcbpMSjRS6Lqcns5SN8Ac=
upgrade: websocket
  • Upgrade: websocket:与客户端一致。
  • Connection: Upgrade:与客户端一致。
  • sec-websocket-accept:服务器端把客户端端请求头中的Sec-WebSocket-Key加上一个特殊字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,然后计算SHA-1摘要,之后进行BASE-64编码,将结果做为 “Sec-WebSocket-Accept” 头的值,返回给客户端。如此操作,可以尽量避免普通HTTP请求被误认为Websocket协议。

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

netty代码实现

服务器端代码如下:

Server.java

package com.morris.netty.protocol.websocket;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketFrameAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
import io.netty.handler.stream.ChunkedWriteHandler;

public class Server {

    private static final int port = 8899;

    public static void main(String[] args) throws Exception {
        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(new HttpServerCodec()); // 请求消息编解码器
                            pipeline.addLast(new HttpObjectAggregator(65536));// 目的是将多个消息转换为单一的request或者response对象
                            pipeline.addLast(new WebSocketServerProtocolHandler("/ws")); // websocket协议支持,url为/ws
                            pipeline.addLast(new ServerHandler());
                        }
                    });
            ChannelFuture future = b.bind("127.0.0.1", port).sync();
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

ServerHandler.java

package com.morris.netty.protocol.websocket;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalDateTime;

@Slf4j
public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        log.info("receive from client: {}", msg.text());
        ctx.writeAndFlush(new TextWebSocketFrame(LocalDateTime.now().toString()));
    }
}

客户端采用html,代码如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Socket</title>
    <script type="text/javascript">
        var websocket;

        //如果浏览器支持WebSocket
        if(window.WebSocket){
            websocket = new WebSocket("ws://localhost:8899/ws");  //获得WebSocket对象

            //当有消息过来的时候触发
            websocket.onmessage = function(event){
                var respMessage = document.getElementById("respMessage");
                respMessage.value += "\n" + event.data;
            }

            //连接关闭的时候触发
            websocket.onclose = function(event){
                var respMessage = document.getElementById("respMessage");
                respMessage.value += respMessage.value + "\n断开连接";
            }

            //连接打开的时候触发
            websocket.onopen = function(event){
                var respMessage = document.getElementById("respMessage");
                respMessage.value = "建立连接";
            }
        }else{
            alert("浏览器不支持WebSocket");
        }

        function sendMsg(msg) { //发送消息
            if(window.WebSocket){
                if(websocket.readyState == WebSocket.OPEN) { //如果WebSocket是打开状态
                    websocket.send(msg); //send()发送消息
                }
            }else{
                return;
            }
        }
    </script>
</head>
<body>
<form onsubmit="return false">
    <textarea style="width: 300px; height: 200px;" name="message"></textarea>
    <input type="button" onclick="sendMsg(this.form.message.value)" value="发送"><br>
    <h3>信息</h3>
    <textarea style="width: 300px; height: 400px;" id="respMessage"></textarea>
    <input type="button" value="清空" onclick="javascript:document.getElementById('respMessage').value = ''">
</form>
</body>
</html>

测试结果如下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
可见Websocket需要借助HTTP的协议来完成握手,在数据的传输过程中不再需要http头部了,只有数据部分,真正的实现了实时和高效。

注意要使用websocket的压缩功能,要将WebSocketServerProtocolHandler的扩展参数改为true。

pipeline.addLast(new WebSocketServerCompressionHandler()); // 支持ws数据的压缩传输
pipeline.addLast(new WebSocketServerProtocolHandler("/ws", null ,true)); // websocket协议支持,url为/ws
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

morris131

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值