netty实现websocket发送文本和二进制数据

2023-11-07

    最近在学习netty相关的知识,看到netty可以实现 websoket,因此记录一下在netty中实现websocket的步骤,主要实现传递文本消息传递二进制消息(此处只考虑是图片),如果是别的消息可以考虑使用自定义协议。

 

需求:

    1、使用 netty 实现 websocket 服务器

    2、实现 文本信息 的传递

    3、实现 二进制 信息的传递,默认只考虑传输图片,如果需要传递别的,考虑使用自定义协议。

    4、只需要考虑 websocket 协议,不用处理http请求

实现细节:

    1、netty中对websocket增强的处理器

          WebSocketServerProtocolHandler 

              >> 此处理器可以处理了 webSocket 协议的握手请求处理,以及 ClosePingPong控制帧的处理。对于文本和二进制的数据帧需要我们自己处理

              >> 如果我们需要拦截 webSocket 协议握手完成后的处理,可以实现ChannelInboundHandler#userEventTriggered方法,并判断是否是 HandshakeComplete 事件。

              >> 参数:websocketPath 表示 webSocket 的路径

              >> 参数:maxFrameSize 表示最大的帧,如果上传大文件时需要将此值调大

    2、文本消息的处理

               客户端: 直接发送一个字符串即可

               服务端: 服务端给客户端响应文本数据,需要返回  TextWebSocketFrame 对象,否则客户端接收不到。

    3、二进制消息的处理

               客户端:向后台传递一个 blob 对象即可,如果我们需要传递额外的信息,那么可以在 blob 对象中进行添加

               服务端:处理 BinaryWebSocketFrame 帧,然后返回 BinaryWebSocketFrame对象给前台。

    4、针对二进制消息的自定义协议如下:(代码中已删除)

          前四个字节表示文件类型,后面的字节表示具体的数据。

          在java中一个int是4个字节,在js中使用Int32表示

          此协议主要是判断前端是否传递的是 图片,如果是图片就直接传递到后台,然后后台在返回二进制数据到前台直接显示这个图片。非图片不用处理。

     5、js中处理二进制数据

           见 webSocket.html 文件中的处理。

实现步骤:

1、主要的依赖

<dependency>
     <groupId>io.netty</groupId>
     <artifactId>netty-all</artifactId>
     <version>4.1.38.Final</version>
</dependency>

2、webSocket服务端编写

package com.huan.netty.websocket;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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.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.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * netty 整合 websocket
 *
 * @author huan.fu
 * @date 2018/11/7 - 17:25
 */
public class WebSocketServer {

    private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workGroup)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .handler(new LoggingHandler(LogLevel.TRACE))
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline()
                                    .addLast(new LoggingHandler(LogLevel.TRACE))
                                    // HttpRequestDecoder和HttpResponseEncoder的一个组合,针对http协议进行编解码
                                    .addLast(new HttpServerCodec())
                                    // 分块向客户端写数据,防止发送大文件时导致内存溢出, channel.write(new ChunkedFile(new File("bigFile.mkv")))
                                    .addLast(new ChunkedWriteHandler())
                                    // 将HttpMessage和HttpContents聚合到一个完成的 FullHttpRequest或FullHttpResponse中,具体是FullHttpRequest对象还是FullHttpResponse对象取决于是请求还是响应
                                    // 需要放到HttpServerCodec这个处理器后面
                                    .addLast(new HttpObjectAggregator(10240))
                                    // webSocket 数据压缩扩展,当添加这个的时候WebSocketServerProtocolHandler的第三个参数需要设置成true
                                    .addLast(new WebSocketServerCompressionHandler())
                                    // 聚合 websocket 的数据帧,因为客户端可能分段向服务器端发送数据
                                    // https://github.com/netty/netty/issues/1112 https://github.com/netty/netty/pull/1207
                                    .addLast(new WebSocketFrameAggregator(10 * 1024 * 1024))
                                    // 服务器端向外暴露的 web socket 端点,当客户端传递比较大的对象时,maxFrameSize参数的值需要调大
                                    .addLast(new WebSocketServerProtocolHandler("/chat", null, true, 10485760))
                                    // 自定义处理器 - 处理 web socket 文本消息
                                    .addLast(new TextWebSocketHandler())
                                    // 自定义处理器 - 处理 web socket 二进制消息
                                    .addLast(new BinaryWebSocketFrameHandler());
                        }
                    });
            ChannelFuture channelFuture = bootstrap.bind(9898).sync();
            log.info("webSocket server listen on port : [{}]", 9898);
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}

   注意:

          1、看一下上方依次引入了哪些处理器

          2、对于 webSocket 的握手、Close、Ping、Pong等的处理,由 WebSocketServerProtocolHandler 已经处理了,我们自己只需要处理 Text和Binary等数据帧的处理。

          3、对于传递比较大的文件,需要修改 maxFrameSize 参数。

3、自定义处理器握手后和文本消息

@Slf4j
public class TextWebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

	@Override
	protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) {
		log.info("接收到客户端的消息:[{}]", msg.text());
		// 如果是向客户端发送文本消息,则需要发送 TextWebSocketFrame 消息
		InetSocketAddress inetSocketAddress = (InetSocketAddress) ctx.channel().remoteAddress();
		String ip = inetSocketAddress.getHostName();
		String txtMsg = "[" + ip + "][" + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) + "] ==> " + msg.text();
		ctx.channel().writeAndFlush(new TextWebSocketFrame(txtMsg));
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		ctx.close();
		log.error("服务器发生了异常:", cause);
	}

	@Override
	public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
		if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
			log.info("web socket 握手成功。");
			WebSocketServerProtocolHandler.HandshakeComplete handshakeComplete = (WebSocketServerProtocolHandler.HandshakeComplete) evt;
			String requestUri = handshakeComplete.requestUri();
			log.info("requestUri:[{}]", requestUri);
			String subproTocol = handshakeComplete.selectedSubprotocol();
			log.info("subproTocol:[{}]", subproTocol);
			handshakeComplete.requestHeaders().forEach(entry -> log.info("header key:[{}] value:[{}]", entry.getKey(), entry.getValue()));
		} else {
			super.userEventTriggered(ctx, evt);
		}
	}
}

    注意:

           1、此处只处理文本消息,因此 SimpleChannelInboundHandler 中的范型写 TextWebSocketFrame

           2、发送文本消息给客户端,需要发送 TextWebSocketFrame 对象,否则客户端接收不到。

           3、处理 握手后 的处理,判断是否是 HandshakeComplete 事件。

4、处理二进制消息

package com.huan.netty.websocket;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 处理二进制消息
 *
 * @author huan.fu
 * @date 2018/11/8 - 14:37
 */
public class BinaryWebSocketFrameHandler extends SimpleChannelInboundHandler<BinaryWebSocketFrame> {
    private static final Logger log = LoggerFactory.getLogger(BinaryWebSocketFrameHandler.class);

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, BinaryWebSocketFrame msg) throws InterruptedException {
        log.info("服务器接收到二进制消息,消息长度:[{}]", msg.content().capacity());
        ByteBuf byteBuf = Unpooled.directBuffer(msg.content().capacity());
        byteBuf.writeBytes(msg.content());
        ctx.writeAndFlush(new BinaryWebSocketFrame(byteBuf));
    }
}

   注意:

        1、此处只处理二进制消息,因此泛型中写 BinaryWebSocketFrame

5、客户端的写法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>web socket 测试</title>
</head>
<body>

<div style="width: 600px;height: 400px;">
    <p>服务器输出:</p>
    <div style="border: 1px solid #CCC;height: 300px;overflow: scroll" id="server-msg-container">

    </div>
    <p>
        <textarea id="inp-msg" style="height: 50px;width: 500px"></textarea><input type="button" value="发送"
                                                                                   id="send"><br/>
        选择图片: <input type="file" id="send-pic">
    </p>
</div>

<script type="application/javascript">
    var ws = new WebSocket("ws://127.0.0.1:9898/chat");
    ws.onopen = function (ev) {

    };
    ws.onmessage = function (ev) {
        console.info("onmessage", ev);
        var inpMsg = document.getElementById("server-msg-container");
        if (typeof ev.data === "string") {
            inpMsg.innerHTML += ev.data + "<br/>";
        } else {
            var result = ev.data;
            var flagReader = new FileReader();
            flagReader.readAsArrayBuffer(result);
            flagReader.onload = function () {
                var imageReader = new FileReader();
                imageReader.readAsDataURL(result);
                console.info("服务器返回的数据大小:", result.size);
                imageReader.onload = function (img) {
                    var imgHtml = "<img src='" + img.target.result + "' style='width: 100px;height: 100px;'>";
                    inpMsg.innerHTML += imgHtml.replace("data:application/octet-stream;", "data:image/png;") + "<br />";
                    inpMsg.scroll(inpMsg.scrollWidth,inpMsg.scrollHeight);
                };
            }
        }
    };
    ws.onerror = function () {
        var inpMsg = document.getElementById("server-msg-container");
        inpMsg.innerHTML += "发生异常" + "<br/>";
    };
    ws.onclose = function () {
        var inpMsg = document.getElementById("server-msg-container");
        inpMsg.innerHTML += "webSocket 关闭" + "<br/>";
    };

    // 发送文字消息
    document.getElementById("send").addEventListener("click", function () {
        ws.send(document.getElementById("inp-msg").value);
    }, false);

    // 发送图片
    document.querySelector('#send-pic').addEventListener('change', function () {
        var files = this.files;
        if (files && files.length) {
            var file = files[0];
            var fileReader = new FileReader();
            fileReader.readAsArrayBuffer(file);
            fileReader.onload = function (e) {
                // 获取到文件对象
                var result = e.target.result;
                // 发送数据到服务器端
                ws.send(result)
            }
        }
    }, false);
</script>
</body>
</html>

   注意:

          1、如何处理后端返回的二进制数据。

6、实现效果


 

完成代码:

 代码如下:netty-study: netty的一个简单的学习过程 - Gitee.com

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

netty实现websocket发送文本和二进制数据 的相关文章

随机推荐

  • 申请带@msn.com后缀的邮箱

    很多朋友总是抱怨申请msn邮箱时总是申请到 hotmail com的 为什么申请不到 msn com的呢 我从网上Google了一下 这个地址是申请简体中文MSN邮箱的 https accountservices passport net
  • 华为交换机中配置管理IP

    1 配置IP和掩码
  • 什么是Embedding?

    说起 Embedding 我想你肯定不会陌生 至少经常听说 事实上 Embedding 技术不仅名气大 而且用 Embedding 方法进行相似物品推荐 几乎成了业界最流行的做法 无论是国外的 Facebook Airbnb 还是在国内的阿
  • 数据结构和算法(1):开始

    算法概述 所谓算法 即特定计算模型下 旨在解决特定问题的指令序列 输入 待处理的信息 问题 输出 经处理的信息 答案 正确性 的确可以解决指定的问题 确定性 任一算法都可以描述为一个由基本操作组成的序列 可行性 每一基本操作都可实现 且在常
  • Socket权威分析(官方分析,少道听途说)

    一 最近有个技术的朋友 每次搞技术讨论都在谈什么WebSocket 然而并没有对它有全面清晰的人数 凡是口口相传的东西都是会有问题的 下面就给大家权威分析一下 二 对一种技术的了解 最好的方式 首选官网 其次百科 官网地址 Socket I
  • Java 对象深拷贝工具类

    目录 1 使用场景 1 1 场景一 1 2 场景二 2 Spring 中的对象拷贝 3 本工具类中的对象拷贝 3 1 拷贝对象本身 单个 3 2 拷贝对象本身 批量 3 3 拷贝对象属性至其他类 单个 3 4 拷贝对象属性至其他类 批量 4
  • 一篇介绍SSD的特别好的博客

    https www cnblogs com xuanyuyt p 7222867 html label4 转载于 https www cnblogs com cumtchw p 11586776 html
  • 任天堂xci文件提取romfs

    最近需要宝可梦剑盾中的各种宝可梦 NPC 场景模型和UI资源 音乐资源等等 准备工作 1 宝可梦剑 xci 从任天堂switch破解机导出或者网络上下载 2 准备解压和提取工具Unpack zip 下载地址 How to easy extr
  • ElasticSearch高可用安装部署(Linux)

    ElasticSearch高可用安装部署 一 小型的ElasticSearch集群的节点角色规划 对于Ingest节点 如果我们没有格式转换 类型转换等需求 直接设置为false 3 5个节点属于轻量级集群 要保证主节点个数满足 节点数 2
  • log4j2 安装interactsh

    漏洞测试log4j2 sudo apt install gccgo go sudo apt install golang go go env w GOPROXY https goproxy cn direct 1 root kali hom
  • jwt安全问题

    文章目录 jwt安全问题 jwt简介 jwt组成 header payload signature 潜在漏洞 空加密算法 web346 密钥爆破 web348 敏感信息泄露 web349 修改算法RS256为HS256 web350 jwt
  • pytorch cycleGAN代码学习1

    一 新的东西 p s 很多架构都和之前一样 就举些不同的 1 ReplayBuffer Buffers of previously generated samples fake A buffer ReplayBuffer fake B bu
  • JSONException异常

    下面是net sf json JSONException java lang reflect InvocationTargetException异常 net sf json JSONException java lang reflect I
  • 筛选图片,写JSON文件和复制

    筛选图片 写JSON文件和复制 筛选图片 写JSON文件 筛选图片复制 筛选图片 写JSON文件 coding utf 8 from PIL import Image ImageDraw ImageFont import os import
  • NVIDIA Video Codec SDK学习

    这是sdk官网 https developer nvidia com nvidia video codec sdk https docs nvidia com video technologies video codec sdk 这是cud
  • python用于计算和数据处理的包有哪些_数据处理常用Python包

    原博文 2020 05 16 21 07 数据计算 numpy https github com AI 10 Data processing blob master E6 95 B0 E6 8D AE E5 88 86 E6 9E 90 E
  • Django---------创建、运行

    目录 1 安装django 2 pycharm 专业版 创建项目 3 默认项目的文件介绍 4 App的创建和说明 5 启动运行django 1 确保app已注册 settings py 2 编写URL和视图函数对应关系 url py 3 编
  • File类的常用方法

    import java io File import java io IOException public class Demo public static void main String args File file new File
  • xfce4汉化

    xfce4 设置中文 安装locales并配置 sudo apt install locales sudo dpkg reconfigure locales 选择语言编码en US UTF8 zh CN GB2312 zh CN GBK G
  • netty实现websocket发送文本和二进制数据

    最近在学习netty相关的知识 看到netty可以实现 websoket 因此记录一下在netty中实现websocket的步骤 主要实现传递文本消息和传递二进制消息 此处只考虑是图片 如果是别的消息可以考虑使用自定义协议 需求 1 使用