【十】Netty WebSocket协议栈开发

news2024/10/6 20:40:13

Netty WebSocket协议栈开发

  • 背景介绍
  • HTTP 协议的弊端
  • WebSocket 介绍
    • WebSocket 特点
    • WebSocket 连接建立
    • Socket生命周期
    • WebSocket关闭
  • 开发
    • WebSocket 服务端功能介绍
      • 流程图
    • 代码实现
      • jar 依赖
      • WebSocket 服务端启动类 WebSocketServer
      • 服务端业务处理类 WebSocketServerHandler
      • WebSocketServer.html
        • 说明
  • 测试
    • 打开html页面
      • 用Goole浏览器打开 WebSocketServer.html .
      • 启动服务端。
        • 服务端打印:
        • 刷新下html. 打印信息如下;
        • 点击 发送 WebSocket 请求消息,
      • 关闭服务器
        • 浏览器截图
  • 总结

背景介绍

一直以来,网络在很大程度都是围绕着HTTP的请求/响应模式而构建的。客户端加载一个网页,然后直到用户点击,否则什么都不会发送。后来 AJAX 开始让网络变得更加动态,但 HTTP通信仍然是由客户端控制,需要用户进行互动或定期轮询。各种技术让服务器得知有新数据时,立即将数据发送到客户端。比如 "推送"或 Comet长轮询。利用长轮询,客户端可以打开指向服务器的HTTP连接,而服务器会一直保持连接打开,直到发送响应。
但是,由于HTTP协议的开销,导致它们不适合低延迟应用。比如 IM 系统
为了解决这些问题,WebSocket 将网络套接字引入到了客户端和服务端,浏览器和服务器之间可以通过套接字建立持久的连接,双方随时都可以互发数据给对方,而不是之前由客户端控制的一请求一应答模式。

HTTP 协议的弊端

(1)HTTP协议为半双工协议。半双工协议指数据可以在客户端和服务端两个方向传输,但是不能同时传输。意味着在同一个时刻,只有一个方向的数据传输。
(2)HTTP消息冗长而繁琐。HTTP 消息包含消息头,消息体,换行符。通常情况下采用文本方式传输,相比于其他的二进制通信协议,冗长繁琐。
(3)容易受到攻击。长时间轮询。
为了解决HTTP协议效率低下的问题。HTML5 定义了WebSocket协议,能更好的节省服务器资源和宽带并达到实时通信。

WebSocket 介绍

WebSocket 是 HTML5 开始提供的一种浏览器与服务器间进行全双工通信的网络技术。WebSocket通信协议于2011年被IETF定为标准 RFC6455,WebSocket API 被W3C定位标准。
在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后 浏览器和服务器之间就形成了一条快速通道,两者就可以直接互相传送数据了。WebSocket基于TCP双向全双工进行消息传递,在同一时空,即可以发送消息,也可以接收消息。相比于HTTP的半双工协议,性能得到大大提升。

WebSocket 特点

(1)单一的TCP连接,采用全双工模式通信
(2)对代理,防火墙和路由器透明
(3)五头部信息,Cookie和身份验证
(4)五安全开销
(5)通过 “ping/pong” 帧保持链路激活
(6)服务器可以主动传递消息给客户端,不再需要客户端轮询
WebSocket 设计出来就是要取代轮询和Comet技术。使客户端浏览器具备像C/S架构下桌面系统一样的实时通信能力。

WebSocket 连接建立

客户端和服务端建立连接。
请求消息中的Sec-WebSocket-Key 是随机的,服务端会用这些数据构造出一个SHA-1 的信息摘要。服务端会用这些数据来构造一个SHA-1的信息摘要。使用SHA-1 加密,然后进行BASE-64 编码,将结果作为"Sec-WebSocket-Accept"头的值,返回给客户端。
如图:
在这里插入图片描述

Socket生命周期

握手成功之后,服务端和客户端就可以通过"message"的方式进行通信了,一个消息由一个或多个帧组成,WebSocket 的消息不一定对应一个特定网络层的帧,它可以被分割成多个帧或者被合并。
帧都有自己对应的类型,属于同一个消息的多个帧具有相同类型的数据。从广义上将,数据类型可以是文本数据,二进制数据和控制帧。
Socket 生命周期图:
在这里插入图片描述

WebSocket关闭

为了关闭WebSocket 连接,客户端和服务端需要通过一个安全的方法关闭底层TCP连接以及TL会话。如果合适,丢弃任何可能已经接收的字节;必要时(比如受到攻击),可以通过任何可用的手段关闭连接。底层的TCP连接,在正常情况下,应该首先由服务器关闭。在异常情况下(例如在一个合理的时间周期后没有收到服务器的TCP Close),客户端可用发起 TCP Close。因此,当服务器被指示关闭WebSocket 连接时,他应该立即发起一个TCP Close 操作。客户端应该等待服务器的TCP Close
WebSocket 的握手关闭消息带有一个状态码和一个可选的关闭原因,它必须按照协议要求发送一个Close控制帧,当对端接收到关闭控制帧指令时,需要主动关闭WebSocket连接。

开发

Netty 基于HTTP协议栈开发了WebSocket 协议栈,利用Netty的WebSocket 协议栈可以非常方便的开发出WebSocket客户端和服务端。

WebSocket 服务端功能介绍

支持WebSocket 的浏览器通过 WebSocket 协议发送请求消息给服务端,服务端对请求消息进行判断,如果时合法的WebSocket请求,则获取请求消息体,并在后面追加字符串 "欢迎使用Netty WebSocket 服务,现在是 "+系统时间。

客户端端 HTML通过内嵌的JS脚本创建WebSocket连接,如果握手成功,在文本框中打印 “打开WebSocket 服务正常,浏览器支持WebSocket!” 。
如果浏览器不支持WebSocket ,回提示 sorry,您的浏览器不支持WebSocket 协议。

流程图

在这里插入图片描述

代码实现

jar 依赖

       <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId> <!-- Use 'netty5-all' for 5.0-->
            <version>5.0.0.Alpha1</version>
            <scope>compile</scope>
        </dependency>

WebSocket 服务端启动类 WebSocketServer

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 {
                            //将请求和应答消息编码或者解码为HTTP消息
                            socketChannel.pipeline()
                                    .addLast("http-codec",new HttpServerCodec());
                            //将多个部分组合成一条完整的HTTP消息
                            socketChannel.pipeline()
                                    .addLast("aggregator",new HttpObjectAggregator(65536));
                            //向客户端发送HTML5文件,主要用于支持浏览器和服务器进行WebSocket
                            socketChannel.pipeline()
                                    .addLast("http-chunked",new ChunkedWriteHandler());
                            socketChannel.pipeline()
                                    .addLast(new WebSocketServerHandler());
                        }
                    });
            //服务端绑定端口
            ChannelFuture future=bootstrap.bind(port).sync();
            System.out.println("server is started.");
            System.out.println("open your browser and navigate to http://127.0.0.1:"+port+"/");
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //优雅关闭
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        new WebSocketServer().run(8080);
    }
}

服务端业务处理类 WebSocketServerHandler

注释里写了详细的说明。

public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {
    private static final Logger logger = Logger.getLogger(WebSocketServerHandler.class.getName());

    private WebSocketServerHandshaker handshaker;

    @Override
    protected void messageReceived(ChannelHandlerContext channelHandlerContext, Object msg) throws Exception {
        //如果是传统的HTTP接入
        if (msg instanceof FullHttpRequest) {
            handleHttpRequest(channelHandlerContext, (FullHttpRequest) msg);
        } else if (msg instanceof WebSocketFrame) {
            handleWebSocketFrame(channelHandlerContext, (WebSocketFrame) msg);
        }
    }

    public void channelReadComplete(ChannelHandlerContext context) {
        context.flush();
    }

    /**
     * 第一次握手请求消息 由HTTP协议承载,
     * @param context
     * @param request
     */
    private void handleHttpRequest(ChannelHandlerContext context, FullHttpRequest request) {
        //如果Http解码失败,返回HTTP异常
        if (!request.getDecoderResult().isSuccess()
                || (!"websocket".equals(request.headers().get("Upgrade")))) {
            sendHttpResponse(context, request,
                    new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }

        //校验通过,开始构造握手工厂,然后构造握手处理类WebSocketServerHandshaker。
        //构造握手响应返回,本机测试
        WebSocketServerHandshakerFactory factory =
                new WebSocketServerHandshakerFactory("ws://127.0.0.1:8080/websocket",
                        null, false);
        handshaker=factory.newHandshaker(request);
        if (handshaker==null){
            WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(context.channel());
        }else {
            //将WebSocket 相关的编码和解码类动态添加到ChannelPipeline中,用于WebSocket消息的编解码
            // 有了WebSocket Encoder 和 WebSocket Decoder之后。服务端就可以自动对WebSocket消息进行编解码了。
            // 源码可以查看 WebSocketServerHandshaker.handshake
            handshaker.handshake(context.channel(),request);
        }

    }

    private void handleWebSocketFrame(ChannelHandlerContext context, WebSocketFrame socketFrame) {
        //判断是否关闭链路的指令
        if (socketFrame instanceof CloseWebSocketFrame){
            handshaker.close(context.channel(),(CloseWebSocketFrame) socketFrame.retain());
            return;
        }
        //判断是否是ping消息
        if (socketFrame instanceof PingWebSocketFrame){
            context.channel().write(new PongWebSocketFrame(socketFrame.content().retain()));
            return;
        }

        //本demo 只支持文本消息,不支持二进制消息
        //其他消息直接抛出异常
        if (!(socketFrame instanceof TextWebSocketFrame)){
            throw new UnsupportedOperationException(
                    String.format("%s frame types not supported",socketFrame.getClass().getName()));
        }
        //返回应答消息
        //从 TextWebSocketFrame 中获取请求消息字符串
        String requestStr=((TextWebSocketFrame) socketFrame).text();
        if (logger.isInfoEnabled()){
            logger.info(String.format("%s received %s",context.channel(),requestStr));
        }
        //通过处理后,通过构造新的TextWebSocketFrame 消息返回给客户端。
        // 之前添加了TextWebSocketFrame的编码类。
        // 所以可以直接发送TextWebSocketFrame对象
        String timeStr=new SimpleDateFormat("YYYY-MM-DD dd:mm:ss").format(new Date());
        context.channel().write(new TextWebSocketFrame(
                requestStr+" ,欢迎使用Netty WebSocket 服务,现在是 : "+timeStr));





    }


    private static void sendHttpResponse(ChannelHandlerContext context, FullHttpRequest request,
                                         FullHttpResponse response) {
        //返回应答给客户端
        if (response.getStatus().code() != 200) {
            ByteBuf buf = Unpooled.copiedBuffer(response.toString().toString(), CharsetUtil.UTF_8);
            response.content().writeBytes(buf);
            buf.release();
            setContentLength(response, response.content().readableBytes());
        }
        //如果是非Keep-Alive ,关闭连接
        ChannelFuture future = context.channel().writeAndFlush(response);
        if (!isKeepAlive(request) || response.getStatus().code() != 200) {
            future.addListener(ChannelFutureListener.CLOSE);
        }

    }

    public void exceptionCaught(ChannelHandlerContext context, Throwable cause) {
        cause.printStackTrace();
        context.close();
    }
}

WebSocketServer.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Netty WebSocket</title>
</head>
<body>
<br>
<script type="text/javascript">
    var socket;
    if(!window.WebSocket){
        window.WebSocket=window.MozWebSocket;

    }
    //判断浏览器是否支持WebSocket
    if(window.WebSocket){
         // 进行握手链接
        socket=new WebSocket("ws://127.0.0.1:8080/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!";
        };
        //如果服务器关闭,显示 WebSocket 关闭
        socket.onclose=function(event){
            var ta=document.getElementById('responseText');
            ta.value="WebSocket 关闭!!";
        };
    }else{
        alert("sorry,您的浏览器 不支持 WebSocket 协议! ");
    }
    function send(message){
        if(!window.WebSocket){
           return ;
        }
        if(socket.readyState==WebSocket.OPEN){
            socket.send(message);
        }else{
            alert("WebSocket 连接没有建立成功!");
        }

    }


</script>
<form onsubmit="return false;">
    <input type="text" name="message" value="Netty learning WebSocket"/>
    <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>

说明

总体代码比较简单。特别是客户端的html代码,但是 很方便我们进行测试。各种打印信息,都能帮助我们更好的理解 WebSocket协议。

测试

打开html页面

用Goole浏览器打开 WebSocketServer.html .

此时浏览器的显示界面。能正确显示 服务器是关闭的。
在这里插入图片描述

启动服务端。

服务端打印:

在这里插入图片描述

刷新下html. 打印信息如下;

在这里插入图片描述

点击 发送 WebSocket 请求消息,

在这里插入图片描述
此时可以鼠标无限制点击,发现时间在变化,说明服务端在不停的返回数据。此时 http请求还是101。
和我们常见的HTTP请求显然不同
在这里插入图片描述

关闭服务器

此时服务端会给客户端发送 TCP Close。浏览器客户端接收到了消息后,会进行对应打印信息展示。

浏览器截图

在这里插入图片描述

总结

本章首先介绍了HTTP协议的弊端和产生WebSocket 的一些技术背景,随后介绍了下WebSocket的基本信息。然后写了一个小demo。但是 WebSocket 是很复杂的,这个demo 只能算一个入门的小例子。如果想深入学习 WebSocket。还需要不断的深入学习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/160597.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

nacos的部署以及nacos启动报错“Unable to start embedded Tomcat”(部分解决)

这几天有一个基于yshop改编的SpringBoot的项目开发需求&#xff0c;本地需要下载使用nacos-server进行部署和开发&#xff0c;于是下载了nacos-server-2.0.3,并将其解压在一个没有中文路径的地方。 接下来根据nacos需求&#xff0c;设置了JAVA_HOME的环境变量&#xff1a; 修…

防火墙NAT综合实验

实验要求 1.内网网段配置动态pat将内网192.168.1.0网段映射到防火墙外网接口上 2.dmz区域服务器做静态pat将两台服务器对应到一个外网接口的不同端口 3.开启nat 控制 4.对内网网段192.168.2.0或豁免能够发访问外网 5.并通过远程进行验证 实验命令 ciscoasa# conf t cisco…

如何快速做好SEO优化?怎样综合查询seo?

本篇接着讲新手优化网站的技巧及应该注意什么方面&#xff0c;一起来看看吧&#xff01; 6.优化你的图片 从可读性的角度来看&#xff0c;图像非常重要。他们帮助可视化你的内容&#xff0c;帮助读者更容易理解。但它们对SEO也很重要&#xff0c;因为它们可以帮助你的网站被抓…

MySQL——SQL逻辑语句相同但是性能相差巨大?

在 MySQL 中&#xff0c;有很多看上去逻辑相同&#xff0c;但性能却差异巨大的 SQL 语句。对这些语句使用不当的话&#xff0c;就会不经意间导致整个数据库的压力变大。 下面通过三个案例对SQL语句进行分析&#xff1a; 案例一&#xff1a;条件字段函数操作 假设你现在维护了…

51单片机特性概览

51单片机指的是使用英特尔8051指令集的微控制器。 首先要了解什么是微控制器。 一、什么是微控制器&#xff1f; 微控制器包括&#xff1a; CPU其他组件(可能包括RAM,ROM,I/O端口&#xff0c;定时器、计数器、通信端口)&#xff0c; 而微处理器只包含CPU。 一开始只有微处…

适应性学习率

目录 适应性学习率 Adaptive learning rate 为什么不是临界点仍会导致训练停止 示例一示例二 RMSRMSPropAdam学习率还和时间有关 Learin Rate DecayWarm up 2021 - 类神经网络训练不起来怎么办(三) 自动调整学习率 (Learning Rate)适应性学习率 Adaptive learning rate 一般…

团队协作软件如何整合内容营销

每个内容营销团队都以不同的方式管理他们的流程和工作流程——无论是为成长中的团队扩展编辑流程&#xff0c;还是视频和社交媒体活动规划。优化内容营销项目管理就是降低复杂性和更有效地管理工作。 但是&#xff0c;为什么正确地做到这一点如此重要呢&#xff1f;与对外营…

第一个uni-app程序小结

工院喵开发小结 这是一篇关于uniapp新手写微信小程序的收获和踩坑总结。 目录工院喵开发小结一、架构二、收获1. 一些扩展组件的使用a. uni-uib. uni-listc. 栅格系统d. uni.scss辅助样式e. uni-easyinput 增强输入框f. uni-fab悬浮按钮g. swiper2. api管理3. 页面跳转传参4. …

iOS 语言基础初探 Xcode 工具

前言&#xff1a; 作为 iOS 开发的主要应用工具之一&#xff0c;Xcode 已经越来越被业内认可&#xff0c;本章节将针对此官方开发工具&#xff0c;为同学解读 Xcode 的基本情况&#xff0c;认识 Xcode 的工程体系&#xff0c;带领大家进入 iOS 开发第一步。 &#x1f3b6;文章目…

SAP入门技术分享四:模块化程序

模块化程序1.子程序概要2.子程序定义3.子程序参数&#xff08;1&#xff09;传递参数的方法&#xff08;2&#xff09;定义参数类型&#xff08;3&#xff09;参数与结构体&#xff08;4&#xff09;参数与内表4.调用子程序&#xff08;1&#xff09;调用程序内部子程序&#x…

vue npm link关联本地组件库

什么是 npm link 就是把你在本地开发好的文件做一个映射和链接&#xff0c;当你在 本地开发一个a项目&#xff0c;你的本地b项目想使用a项目下的组件 这时候就是需要进行npm link链接起来 a项目的运行效果 b项目的运行效果&#xff1a; 想要实现的效果&#xff1a;(在b项目上…

Oracle SQL Developer使用dbms_output.put_line显示输出

dbms输出 点击DBMS输出左侧的号&#xff0c;选择需要输出的数据库&#xff0c;点击确定 与步骤2选择相同数据库&#xff0c;右击数据库&#xff0c;选择打开SQL工作表(T) 在工作表中执行语句 declare --定义&#xff0c;相当于声明属性。t_a varchar2(20);--声明自定义属…

怎么写一篇计算机SCI论文初稿? - 易智编译EaseEditing

一、SCI论文的要求 SCI论文的核心是创新性。对于这个方面来说主要就是针对于论文的观点正确&#xff0c;文字通畅&#xff0c;逻辑严密&#xff0c;结构合理&#xff0c;结论有创新等等。 二、SCI论文格式规范 每一个SCI期刊都有自己特定的宗旨、栏目和专业定位&#xff0c;投…

TCP通信机制:三次握手、四次挥手、滑动窗口

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 TCP通信机制1. TCP三次握手2. TCP四次挥手3. TCP连接与数据传输过程4. TCP滑动窗口机制5. server服务端…

这套设备管理方案助你效率10倍提升

车间工厂等货物人员密集场所&#xff0c;对消防安全的要求很高。消防设备管理自然是生产制造型企业的核心之一。消防设施的有效管理&#xff0c;既要保证日常巡检工作的有效性&#xff0c;又要在设备出现故障后及时响应。在此基础上还要对整体管理情况进行数据分析&#xff0c;…

振弦采集模块的各种参数操作

振弦采集模块的各种参数操作 固件版本读取 点击指令区【 读取版本】 按钮&#xff0c;读取当前连接模块的固件版本信息&#xff0c;读取到的版本信息显示于按钮右侧。 VMTool 会根据读取到的版本不同对功能和界面做出调整&#xff0c;故此&#xff0c; 在使用 VMTool 时&#…

Workfine新手入门:给图片加水印

哈喽&#xff0c;我是办公助手小W&#xff0c;又到了跟大家分享办公小技巧的时候啦&#xff01; 最近Workfine5.0最新版本上线后&#xff0c;一直有人问到底有啥新功能啊&#xff1f;与往期版本有何不同呢&#xff1f;小W亲自去体验了一番&#xff0c;最大的一个亮点就是新增了…

90、【树与二叉树】leetcode ——104. 二叉树的最大深度:层次遍历+DFS+子问题分解(C++版本)

解题思路 原题链接&#xff1a;104. 二叉树的最大深度 解题思路 1、迭代法&#xff1a;层次遍历BFS /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), rig…

Blender 物理属性 (二)布料和碰撞

文章目录添加与去除布料查看布料效果布料的预设属性(模拟不同的布料效果)布料与其他物体碰撞布料的自交叉问题布料钉固制作一个窗帘添加与去除布料 1 添加&#xff1a;物体模式选中一个物体&#xff0c;属性栏/物理属性&#xff08;蓝色圆圈&#xff09;&#xff0c;选择布料 …

程序员转项目经理,需要拒绝的3大习惯。

程序员转项目经理并不容易&#xff0c;需要我们转变思路&#xff0c;并拒绝我们日常3大习惯。 1、拒绝单打独干&#xff0c;转而关注与干系人的沟通协调。 做为技术人员&#xff0c;在确定自我任务后&#xff0c;独自敲代码就好&#xff0c;具有较强的单干性质。而作为项目经理…