netty编程之自定义编解码器

news2024/9/29 11:22:42

写在前面

源码 。
本文看下netty如何自定义编解码器。为此netty专门定义抽象类io.netty.handler.codec.MessageToByteEncoderio.netty.handler.codec.ByteToMessageDecoder,后续我们实现自定义的编解码器就继承这两个类来做。

1:正戏

server 启动类:

package com.dahuyou.netty.customercodec.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class NettyServer {

    public static void main(String[] args) {
        new NettyServer().bing(7397);
    }

    private void bing(int port) {
        //配置服务端NIO线程组
        EventLoopGroup parentGroup = new NioEventLoopGroup(); //NioEventLoopGroup extends MultithreadEventLoopGroup Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
        EventLoopGroup childGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(parentGroup, childGroup)
                    .channel(NioServerSocketChannel.class)    //非阻塞模式
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childHandler(new MyChannelInitializer());
            ChannelFuture f = b.bind(port).sync();
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            childGroup.shutdownGracefully();
            parentGroup.shutdownGracefully();
        }

    }

}

MyChannelInitializer类:

package com.dahuyou.netty.customercodec.server;

import com.dahuyou.netty.customercodec.codec.MyDecoder;
import com.dahuyou.netty.customercodec.codec.MyEncoder;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;

public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel channel) {
/*
        */
/*System.out.println("远端连接初始化,IP:" + channel.remoteAddress().getHostString() + ", port:" + channel.remoteAddress().getPort());*//*


        */
/* 解码器 *//*

        // 基于换行符号,基于换行符来分割字符串???
//        channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
        // 基于指定字符串【换行符,这样功能等同于LineBasedFrameDecoder】
        // e.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, false, Delimiters.lineDelimiter()));
        // 基于最大长度
        // e.pipeline().addLast(new FixedLengthFrameDecoder(4));
        // 解码转String,注意调整自己的编码格式GBK、UTF-8 byte->string
        channel.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));

        // 增加内置编码器,这样向对端发送消息就不要转换为二进制数据了
        channel.pipeline().addLast(new StringEncoder(Charset.forName("GBK")));
        //在管道中添加我们自己的接收数据实现方法
        channel.pipeline().addLast(new MyServerHandler());
\*/

        // 这里我们就不使用string的decoder和encoder了,而是使用自定义的编解码器
        // 解码器对应的抽象类是:ByteToMessageDecoder
        // 编码器对应的抽象类是:MessageToByteEncoder
        //自定义解码器
        channel.pipeline().addLast(new MyDecoder());
        //自定义编码器
        channel.pipeline().addLast(new MyEncoder());
        //在管道中添加我们自己的接收数据实现方法
        channel.pipeline().addLast(new MyServerHandler());


    }

}

其中的MyDecoder,MyEncoder就是需要我们自己的定义了编解码器了,如下:

package com.dahuyou.netty.customercodec.codec;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.nio.charset.Charset;
import java.util.List;

/**
 * 自定义的解码器
 */
//public class MyDecoder extends ByteToMessageDecoder<String> {
public class MyDecoder extends ByteToMessageDecoder {
    int BASE_LENGTH = 4;
    /*
    一个完整包的开始标记
     */
    private final byte START_LABEL = 0x02;
    /*
    一个完整包的结束标记
     */
    private final byte END_LABEL = 0x03;

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        //基础长度不足,我们设定基础长度为4
        if (in.readableBytes() < BASE_LENGTH) {
            return;
        }

        int beginIdx; //记录包头位置

        while (true) {
            // 获取包头开始的index
            beginIdx = in.readerIndex();
            // 标记包头开始的index
            in.markReaderIndex();
            // 读到了协议的开始标志,结束while循环
//            if (in.readByte() == 0x02) {
            if (in.readByte() == START_LABEL) {
                break;
            }
            // 未读到包头,略过一个字节
            // 每次略过,一个字节,去读取,包头信息的开始标记
            in.resetReaderIndex();
            in.readByte();
            // 当略过,一个字节之后,
            // 数据包的长度,又变得不满足
            // 此时,应该结束。等待后面的数据到达
            if (in.readableBytes() < BASE_LENGTH) {
                return;
            }

        }

        //剩余长度不足可读取数量[没有内容长度位]
        int readableCount = in.readableBytes();
        if (readableCount <= 1) {
            in.readerIndex(beginIdx);
            return;
        }

        //长度域占4字节,读取int
        ByteBuf byteBuf = in.readBytes(1);
        String msgLengthStr = byteBuf.toString(Charset.forName("GBK"));
        int msgLength = Integer.parseInt(msgLengthStr);

        //剩余长度不足可读取数量[没有结尾标识]
        readableCount = in.readableBytes();
        if (readableCount < msgLength + 1) {
            in.readerIndex(beginIdx);
            return;
        }

        ByteBuf msgContent = in.readBytes(msgLength);

        //如果没有结尾标识,还原指针位置[其他标识结尾]
        byte end = in.readByte();
//        if (end != 0x03) {
        if (end != END_LABEL) {
            in.readerIndex(beginIdx);
            return;
        }

        out.add(msgContent.toString(Charset.forName("GBK")));

    }
/*
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {

    }*/
}

解码器定义了这样的规则,ASCII码02作为开始字符,ASCII码03作为结束字符,这两个字符都是不可见的控制字符,分别是STX(start of text),ETX(end of text),如下是对stx比较专业的描述:

STX 是 ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)中定义的一个控制字符,其代表“Start of Text”,即正文开始。在数据传输或文本通信中,STX 字符用于标记一个结构化数据块(如纯文本消息)的起始点。它的十进制 ASCII 值是 2,十六进制值是 0x02。

二者对应的ASCII码为:
在这里插入图片描述
然后第三个字符作为长度,之后就是根据解析到的长度来读取数据了,并且最后验证读取数据后的下一个字符是03。最终将数据转换为人类可读的字符串,如下图就是一个可被该解码器解析的例子:
在这里插入图片描述
开始字符 数据长度 数据 结束字符

package com.dahuyou.netty.customercodec.codec;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

//public class MyEncoder extends MessageToByteEncoder<String> {
public class MyEncoder extends MessageToByteEncoder {
    @Override
    protected void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) throws Exception {
        String msg = in.toString();
        byte[] bytes = msg.getBytes();

        byte[] send = new byte[bytes.length + 2];
        System.arraycopy(bytes, 0, send, 1, bytes.length);
        send[0] = 0x02;
        send[send.length - 1] = 0x03;

        out.writeInt(send.length);
        out.writeBytes(send);

    }
}

这里解码器可以暂时不用太关注,只是在数据的开头和结尾增加了STX和ETX,如下:
在这里插入图片描述
最后MyServerHandler的代码就比较简单了,如下:

package com.dahuyou.netty.customercodec.server;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.socket.SocketChannel;

import java.text.SimpleDateFormat;
import java.util.Date;

public class MyServerHandler extends ChannelInboundHandlerAdapter {
    /**
     * 通道激活
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        SocketChannel channel = (SocketChannel) ctx.channel();
        System.out.println("远端连接初始化,IP:" + channel.remoteAddress().getHostString() + ", port:" + channel.remoteAddress().getPort());

        // 通知客户端链接建立成功
        String str = "notify channel build success: " + " " + new Date() + " " + channel.localAddress().getHostString() + "\r\n";
        ctx.writeAndFlush(str);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {


        // 因为在初始化是已经设置了字符串的解码器,所以就不需要上述的手动读取二进制字节码的操作了,这就是框架的好处咯!
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 接收到消息:" + msg);


        // 通知客户端链消息发送成功
        String str = "server received your msg: " + new Date() + " " + msg + "\r\n";
        ctx.writeAndFlush(str);

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
        System.out.println("异常信息:\r\n" + cause.getMessage());
    }

    /**
     * 当客户端主动断开服务端的链接后,这个通道就是不活跃的。也就是说客户端与服务端的关闭了通信通道并且不可以传输数据
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端断开链接" + ctx.channel().localAddress().toString());
    }
}

启动server,简单起见我们直接使用netassit测试:
在这里插入图片描述
以上代码相当于发送了byte数组[stx,4,‘a’.‘1’,‘b’,‘2’,etx]。
当然除了使用工具测试外,我们也可以程序来测试,主要是如下代码(详细看源码):

package com.dahuyou.netty.customercodec.client;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.socket.SocketChannel;

import java.text.SimpleDateFormat;
import java.util.Date;

public class MyClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("通道建立成功");
        // 通知客户端链接建立成功
/*
        String str = "hello server , i'm client very happy connect to you." + " " + new Date() + " " + ((SocketChannel) ctx.channel()).localAddress().getHostString() + "\r\n";
*/
        String xxx = "a1b2";
        System.out.println(xxx.getBytes());
        byte[] bytes = xxx.getBytes();

        // 增加stx length etx
        byte[] send = new byte[bytes.length + 3];
        System.arraycopy(bytes, 0, send, 2, bytes.length);
        send[0] = 0x02; // stx
        send[1] = 0X34; // length 4的ASCII码是52 十六进制就是34
        send[send.length - 1] = 0x03; // etx

        ByteBuf buf = Unpooled.buffer(send.length/* + 3*/); // + 3 stx 长度 etx
        buf.writeBytes(send);

//       ctx.writeAndFlush(str);
       ctx.writeAndFlush(buf);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //接收msg消息{与上一章节相比,此处已经不需要自己进行解码}
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " receive msg: " + msg);
        //通知客户端链消息发送成功
//        String str = "hello server received your msg: " + new Date() + " " + msg + "\r\n";
//        ctx.writeAndFlush(str);

    }
}

即channelActive中的逻辑,注意这里写入的都是ASCII码值。

写在后面

参考文章列表

Ascii完整码表(256个) 。
工具 › 开发类 › 进制转换 。
win的netassist TCP测试工具和Linux的nc工具使用 。

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

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

相关文章

卖一辆亏6万搞倾销,极越汽车高管掀了小米汽车遮羞布?

"炮轰解决不了极越销量问题" 作者 | 魏 强 编辑 | 卢旭成 8月22日早9点40分&#xff0c;极越汽车公关负责人徐继业发朋友圈炮轰小米汽车创始人雷军&#xff1a;“雷军这样的企业家&#xff0c;有点公德心和羞耻心好不好&#xff1f;每台车亏6万&#xff0c;亏那么…

Springboot中的mapper与entity难以觉察的匹配问题,困扰我几天时间,形成很大的压力!

最近&#xff0c;应好友邀请&#xff0c;替她做了一个心理疗愈项目的小系统&#xff0c;在编制后台API代码时&#xff0c;遇到了一个不易觉察的问题&#xff0c;终于查找出来&#xff0c;并且解决了&#xff0c;现奉献出来&#xff0c;供大家碰到类似问题&#xff0c;进行参考。…

面试题详解

前言&#xff1a;这一期我们专门来巩固所学知识&#xff0c;同时见识一些面试题。对知识做出一个总结。 1 不创建临时变量交换两个整数 . 第一种方法 #include<stdio.h> int main() {int a 0;int b 0;scanf("%d %d", &a, &b);printf("交换前…

中央空调安装8个标准流程指南

1、内机安装施工队进场第一步就是吊装内机&#xff0c;这里只要注意2个点就可以避免后续问题。 ① 内机离房顶距离不得小于1公分&#xff0c;避免机器运行时与墙顶产生共振。② 内机吊装需考虑百分之一的坡度&#xff0c;接冷凝水的一侧要稍微低一些&#xff0c;避免冷凝水排不…

单个像素的威胁:微小的变化如何欺骗深度学习系统

深度学习&#xff08;DL&#xff09;是人工智能&#xff08;AI&#xff09;的基本组成部分。它的目标是使机器能够执行需要决策机制的任务&#xff0c;这些决策机制往往近似于人类推理。深度学习模型是许多先进应用的核心&#xff0c;例如医疗诊断和自动驾驶汽车。不幸的是&…

饿了么后端登录模块

一、回顾 高并发集群 饿了么后端的登录模块 1、数据库 1. 主从复制(高可用) 2. 传统的主从复制 3. gtids事务型的主从复制 4. 注意 1. server_id唯一 2. 8.x版本需要get_ssl_pub_key 3. 5.x不需要 4. change master to 5. stop | start slave 5. 非交互 import pymy…

uniapp 地图map画出地市轮廓

最近做uniapp项目 H5微信小程序&#xff0c;需要在地图中打点并把相对应的区域轮廓给画出来。 首先查看uniapp官方文档&#xff1a;https://uniapp.dcloud.net.cn/component/map.html 想在uniapp中使用map直接写map标签即可 <view class"page-section page-section-…

CSS实现鼠标hover展开菜单

效果图&#xff1a; HTML源码&#xff1a; 背景图地址需要更改 <!-- 软件介绍 --> <div class"software-box"><div class"software-container"><div class"software-title"><h2>&ldquo; 一个软件搞定所有 &am…

创客匠人老蒋在「IP私域发售六脉神剑落地班」现场金句频出

破认知&#xff0c;提新知&#xff0c;老蒋的观点&#xff0c;再一次带你穿越周期 2024&#xff0c;变革不断&#xff0c;知识服务行业到底该怎么做&#xff1f;「IP私域发售六脉神剑落地班」现场&#xff0c;老蒋作为大课导师&#xff0c;再一次为大家带来了新鲜且颠覆的行业知…

基于SpringBoot的在线笔记网站的设计与实现

目录 项目技术和环境 页面展示 登录注册 管理员页面 用户页面 在线网址 源代码 本系统由十大核心模块构成&#xff0c;包括用户登录与注册模块、个人中心模块、笔记分类与笔记管理模块、笔记详情展示模块、分享协作与收藏管理模块、回收站与用户管理模块&#xff0c;以及…

CoppeliaSim(V-Rep)与ROS1、ROS2接口变迁-2024-

Webots&#xff1a;Webots与ROS1、ROS2接口变迁 Gazebo&#xff1a;Gazebo与ROS1、ROS2接口变迁 ROS1 2016&#xff1a;ROS_Kinetic_18 使用V-Rep3.3.1和Matlab2015b&#xff08;vrep_ros_bridge&#xff09;续 vrep_ros_bridge 插件 一、项目背景与目标 vrep_ros_bridge 是…

[数据集][目标检测]光伏发电板红外图像鸟粪检测数据集VOC+YOLO格式173张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;173 标注数量(xml文件个数)&#xff1a;173 标注数量(txt文件个数)&#xff1a;173 标注类别…

Windows—RAW编程

客服端骨架&#xff1a; #include <iostream> #include <WinSock2.h> #pragma comment(lib,"ws2_32.lib")int main() {WORD wVersionRequested MAKEWORD(2, 2);WSADATA lpWSAData;WSAStartup(wVersionRequested, &lpWSAData);SOCKADDR_IN saddr{ …

如何在没有密码的情况下解锁iPhone?

如果您的 iPhone 被锁定&#xff0c;知道如何在没有密码的情况下解锁它会派上用场。有几种方法可以帮助您重新使用无价的小工具&#xff0c;无论您是忘记了密码&#xff0c;还是现在只想使用手机。这篇博客文章将讨论在 iPhone 上设置密码的价值、忘记密码的典型原因以及在没有…

什么是DDOS攻击?DDOS攻击一小时多少钱?DDOS攻击如何防御?

什么是DDOS攻击? 拒绝服务攻击&#xff08;DDOS&#xff09;亦称洪水攻击&#xff0c;是一种网络攻击手法&#xff0c;其目的在于使目标计算机的网络或系统资源耗尽&#xff0c;使服务暂时中断或停止&#xff0c;导致其正常用户无法访问。当黑客使用网络上两个或以上被攻陷的计…

AWK的高级用法、三剑客总结

1、定义引用变量 -v:声明变量 案例一: 将系统的变量a,在awk里赋值为变量b,然后调用变量b -v 选项将其传递给 awk直接调用的话需要先用双引号再用单引号awk直接定义变量并引用调用函数getline,读取一行数据的时候并不是得到当前行而是当前行的下一行打印整个行面下面含有ro…

mp4怎么转换成mp3?看了就会的8种mp4转mp3方法!

mp4怎么转换成mp3&#xff1f;MP4作为广泛应用的视频格式&#xff0c;在日常娱乐与工作中扮演着重要角色&#xff0c;但它在特定情境下也带来了一些不便&#xff0c;你是否曾遇到过这样的困扰&#xff0c;当视频内容中的画面并非焦点&#xff0c;而你只对其中的音频感兴趣时&am…

鸿蒙Harmony编程开发:服务端证书锁定防范中间人攻击示例

1. TLS通讯中间人攻击及防范简介 TLS安全通讯的基础是基于对操作系统或者浏览器根证书的信任&#xff0c;如果CA证书签发机构被入侵&#xff0c;或者设备内置证书被篡改&#xff0c;都会导致TLS握手环节面临中间人攻击的风险。其实&#xff0c;这种风险被善意利用的情况还是很…

EasyCVR视频汇聚技术赋能智慧煤矿:车载设备接入方案助力实时监控与远程监管

在煤矿行业&#xff0c;智慧化转型已成为提升生产效率、保障安全的重要途径。随着物联网、大数据、云计算等技术的快速发展&#xff0c;智慧煤矿建设逐步深入&#xff0c;车载设备作为煤矿生产的重要一环&#xff0c;其接入智慧管理系统显得尤为重要。本文将详细介绍智慧煤矿车…

软件开发整体介绍

软件开发流程 需求分析&#xff1a;需求规格说明书&#xff08;一般以word文档的形式&#xff09;、产品原型&#xff08;静态页面展示出来&#xff09;设计&#xff1a;UI设计、数据库设计、接口设计编码&#xff1a;项目代码、单元测试测试&#xff1a;测试用例、测试报告上…