Netty之WebSocket协议开发

开始

在本章的开头把代码奉上,大家下载下来对照的学习,这些代码都是运行通过的。
上节我们讲解了HTTP协议开发,但是,Http协议的开销问题,导致它们不适用于低延迟的应用。为了解决这些问题,我们引入了webSocket。

HTTP协议的弊端

我们来总结一下HTTP协议的弊端:

1.HTTP协议是半双工的协议。大家知道对讲机吗?它就是半双工的设备。当对方在说话时,你就不能说话了,也就是说一个时间点上,只能一个人在说话。现在已经被全双工的电话给替代掉了,现在对讲机这东西用的不多了。
2.HTTP协议冗长而繁琐。HTTP消息包含消息头,消息体,换行符等,采用文本形式传播,相比二进制通信协议,非常冗长且繁琐。
3.针对服务器推送的黑客攻击。例如长时间轮询。

为了解决以上HTTP协议效率底下的问题,WebSocket协议应运而生,它能更好的节约带宽和服务器资源并且实时通信。下面就让我们看看websocket协议的神奇之处。

WebSocket协议的特点

1.单一的TCP连接,采用全双工通信
2.对代理,防火墙,路由器透明。
3.无头部信息,cookie和身份验证
4.无安全开销
5.通过ping帧保持链路激活
6.服务器可以主动的对客户端进行发送消息,而不需要轮询。

总的来说我们webSocket被设计出来的目的就是为了取代轮询这种高消耗的方式。

websocket连接过程

建立连接过程如下:

1.客户端浏览器首先要向服务器发送一个HTTP请求,这个请求和通常的HTTP请求不同,包含了一些附加信息,其中附加信息“UpgradeWebSocket”表明这是一个申请协议升级的http请求。
2.服务器解析这些附加头信息,然后生成应答信息返回给客户端,这样客户端和服务器端的webSocket连接就建立起来了
3.这个连接会一直持续到客户端或者服务器的某一方主动关闭连接。

我们来看一下客户端向服务器请求的消息:

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

我们来看一下服务器返回给客户端的应答消息:

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

请求消息中的“Sec-WebSocket-Key”是随机的,服务器端会用这些数据来构造出一个SHA-1的信息摘要,把“Sec-WebSocket-Key”加上一个魔幻字符串“25EAFA5-E914-47DA-95CA-C54B0DC85B11”.使用SHA-1加密,然后进行BASE-64编码,将结果作为Sec-WebSocket-accept头的值,返回给客户端。

连接示意图如下:

websocket生命周期

握手成功后,客户端和服务器就可以通过”message”方式进行通信了,一个消息由一个或者多个帧组成。

websocket连接关闭。

为关闭websocket连接,客户端和服务器需要通过一个安全的方法关闭底层TCP连接及TLS会话。如果合适,丢弃任何可能接收的字节:必要时可以通过任何可用的手段关闭连接。

编码开始

需求:我们要编写一个webSocket服务器,支持WebSocket的浏览器通过webSocket协议发送请求给我们编写的webSocket服务器,服务器对请求消息进行判断,如果是合法的webSocket请求,则获取请求消息体,并在后面追加字符串:“欢迎使用Netty WebSocket 服务,现在时刻:系统时间”。
当然客户端也是我们编写的,其中内嵌js脚本去创建webSocket连接,如果握手成功打印:“打开websocket 服务正常,浏览器支持Websocket!”

首先对webSocket服务器的功能进行简单讲解。websocket服务端接收到请求消息后,先对消息的类型进行判断,如图所示:

我们就是判断upgrade取它的值判断是否是websocket,如果不是webSocket类型的请求消息,则返回HTTP 400 BAD REQUEST 响应给客户端。如果是服务器返回消息,双方连接正式建立。

由于篇幅问题,只对核心代码讲解,全部代码大家可以下载下来看看。

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 {
                //HttpServerCodec的作用是将请求和应答消息编码或者解码为HTTP消息;
                ch.pipeline().addLast("http-codec", new HttpServerCodec());
                //使用HttpObjectAggregator会把多个消息转换为一个单一的FullHttpRequest或是FullHttpResponse
                ch.pipeline().addLast("http-aggregator", new HttpObjectAggregator(65536));
                //支持处理异步发送大数据文件,但不占用过多的内存,防止发生内存泄漏,这里是向客户端发送html5文件
                ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
                //这个是我们自定义的,处理文件服务器逻辑。主要功能还是在这个文件中
                ch.pipeline().addLast("http-fileServerHandler", new WebSocketServerHandler());
            }
        });
        Channel ch  = b.bind(port).sync().channel();//这里写你本机的IP地址
        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 (Exception e) {

    }finally{
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }

讲解:

HttpServerCodec讲解:HTTP协议的请求解码器和响应编码器即HttpServerCodec,它会将HTTP客户端请求转成HttpRequest对象,将HttpResponse对象编码成HTTP响应发送给客户端。
HttpObjectAggregator讲解:使用HttpObjectAggregator会把多个消息转换为一个单一的FullHttpRequest或是FullHttpResponse

下面我们来看一下核心类WebSocketServerHandler,我都做的详细的注释,希望大家能看懂。

/**
 * @author 作者 YYD
 * @version 创建时间:2016年8月17日 上午6:32:23
 * @function 未添加
 */
public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object>{
    private static final Logger logger = Logger.getLogger(WebSocketServerHandler.class.getName());
    private WebSocketServerHandshaker handshaker;
    /**
     * 接收客户端发过来的消息并处理
     * FullHttpRequest :
     *  官网解释:Combine the {@link HttpRequest} and {@link FullHttpMessage}, so the request is a <i>complete</i> HTTP
     * request.
     * 这个请求是 代表http请求完成的标记。
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        if(msg instanceof FullHttpRequest){//接收到客户端的握手请求,开始处理握手
            handleHttpRequest(ctx,(FullHttpRequest)msg);
        }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{
         /**
          * 如果不成功或者消息头不包含"Upgrade",说明不是websocket连接,报400异常。
          */
        if(!req.getDecoderResult().isSuccess()||(!"websocket".equals(req.headers().get("Upgrade")))){
            sendHttpResponse(ctx,req,new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.BAD_REQUEST));
            return;
        }
        /**
         * WebSocket是一种全新的协议,不属于http无状态协议,协议名为"ws",这意味着一个websocket连接地址会是这样的写法: 
           ws://127.0.0.1:8080/websocket。ws不是http,所以传统的web服务器不一定支持,需要服务器与浏览器同时支持, WebSocket才能正常运行,目前大部分浏览器都支持Websocket。
           WebSocketServerHandshaker 官网的解释是:服务器端Web套接字打开和关闭握手基类 
           WebSocketServerHandshakerFactory 官网的解释是:自动检测正在使用的网络套接字协议的版本,并创建一个新的合适的 WebSocketServerHandshaker。
         */
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://localhost:8080/websocket",null,false);
        handshaker = wsFactory.newHandshaker(req);//创建一个握手协议
        if(handshaker == null){
            /**
             * Return that we need cannot not support the web socket version
             * 返回不支持websocket 版本
             */
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        }else {
            handshaker.handshake(ctx.channel(), req);//开始握手
        }
    }
    /**
     * 我们判断数据类型,只支持文本类型
     * @param ctx
     * @param frame
     */
    private void handleWebSocketFrame(ChannelHandlerContext ctx,WebSocketFrame frame) {
        if(frame instanceof CloseWebSocketFrame){//如是接收到的是关闭websocket,就关闭连接
            handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
            return;
        }
        if(frame instanceof PingWebSocketFrame){//如果信息是2进制数据,就反给它,
            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 receive %s",ctx.channel(),request));
        }
        ctx.channel().write(new TextWebSocketFrame(request+",欢迎使用netty websocket 服务,现在时刻"+new Date().toString()));
    }
    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());

        }
        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();
    }
}

上面的代码,我都做的详细的注释,大家可以对照着看看。
在handleHttpRequest方法中我们判断消息头中是否包含“Upgrade”字段,如果不包含就返回Http 400 响应。握手请求经过简单校验后,我们通过构造握手工厂,创建握手出理类webSocketServerHandshaker,通过它构造握手响应消息返回给客户端,同时将webSocket消息的编码和解码类动态添加到ChannelPipeline中,用于websocket消息的编解码。

下面来看看客户端浏览器代码

<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){
    var  socket = new WebSocket("ws://localhost:9090/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 = '';
        ta.value = "打开websocket服务正常,浏览器支持websocket!";   
    };
    socket.onclose = function(event){
        var ta = document.getElementById('responseText');
        ta.value = '';
        ta.value = "websocket关闭!";
    };
}else{
    alert("抱歉,您的浏览器不支持WebSocket 协议!");
}
function send(message){
    if(!window.WebSocket){
        return;
    }
    if(socket.readyState == window.WebSocket.OPEN){
        socket.send(message);
    }else{
        alert("WebSocket 还没有建立连接!")
    }
}
</script>
<form onsubmit="return false;">
<input type="text" name="message" vaule="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> 

我们这里不加讲解了,大家看看就明白了。

看看客户端的运行效果

初始状态效果图

发送消息效果图

结尾

在本章的结尾把代码奉上,大家下载下来对照的学习,这些代码都是运行通过的。
好了就讲到这里吧,由于HttP协议本身存在的弊端,产生了websocket协议。希望对大家有所帮助。有不懂的可以联系我549544653@qq.com

同时鼓励自己,坚持就会有奇迹,加油!

WebSocket协议是一种全双工通信协议,它建立在HTTP协议之上,使用标准的HTTP端口(80和443),并支持跨域通信。它允许客户端和服务器之间进行实时、双向的数据传输,而不需要轮询或长轮询技术。 WebSocket协议开发包括以下步骤: 1. 了解WebSocket协议的基本概念和原理,包括握手过程、消息格式、数据传输等。 2. 选择一个合适的WebSocket库或框架,例如Socket.io、Netty、Tornado等,这些库或框架可以帮助我们快速开发WebSocket应用程序。 3. 编写客户端和服务器端的代码,客户端代码可以使用JavaScript或其他支持WebSocket协议的语言编写,服务器端代码可以使用Java、Python、Node.js等语言编写。 4. 进行测试和调试,确保WebSocket应用程序能够正常运行,并且能够处理各种异常情况。 5. 部署和发布WebSocket应用程序,将其部署到云服务器或本地服务器上,并通过域名或IP地址访问WebSocket服务。 在开发WebSocket应用程序时,需要注意以下几点: 1. 安全性:WebSocket应用程序需要对数据进行加密和解密,防止数据被窃取或篡改。 2. 性能:WebSocket应用程序需要处理大量的实时数据流,需要优化代码和网络传输性能,以确保应用程序的响应速度和稳定性。 3. 兼容性:WebSocket协议在不同的浏览器和操作系统中可能存在差异,需要进行兼容性测试和优化。 4. 可扩展性:WebSocket应用程序需要具备良好的可扩展性,能够支持大量的并发连接和数据传输。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序编织梦想

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

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

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

打赏作者

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

抵扣说明:

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

余额充值