Android VPN TLV协议场景使用

TLV协议格式是一种可变格式,

TLV的意思就是:Type类型, Lenght长度,Value值;
Type和Length的长度固定,一般那是2、4个字节; Value的长度有Length指定;


解析方法:
1.读取type 转换为ntohl、ntohs转换为主机字节序得到类型;指针偏移+2或4
2.读取lenght,转换为ntohl、ntohs转换为主机字节序得到长度;指针偏移+2或4
3.根据得到的长度读取value,指针偏移+Length;

TLV协议是一种通讯协议,一般将数据封装成TLV的形式,即Tag,Length,Value。

协议就是指通信双方对数据传输控制的一种规定,规定了数据格式,同步方式,传送速度,传送步骤的问题作出统一的规定。

可以理解为两个节点之间为了协同工作,协商一定的规则和约定。例如我们会规定字节序,各个字段类型等。

TLV 是一种可变的格式,其中:

  • T 可以理解为 Tag 或 Type ,用于标识标签或者编码格式信息;
  • L 定义数值的长度;
  • V 表示实际的数值。

T 和 L 的长度固定,一般是2或4个字节,V 的长度由 Length 指定。

图例帧格式如下所示:

tlv 协议基本算法如下

TLV 编码

public class BerTlvBuilder {

    private static final Charset ASCII = Charset.forName("US-ASCII");
    private static final BigDecimal HUNDRED = new BigDecimal(100);
    private static final int DEFAULT_SIZE = 5 * 1024;

    public BerTlvBuilder() {
        this((BerTag)null);
    }

    public BerTlvBuilder(BerTag aTemplate) {
        this(aTemplate, new byte[DEFAULT_SIZE], 0, DEFAULT_SIZE);
    }


    public BerTlvBuilder(BerTlvs tlvs) {
        this((BerTag)null);
        for (BerTlv tlv : tlvs.getList()) {
            addBerTlv(tlv);
        }
    }

    public BerTlvBuilder(BerTag aTemplate, byte[] aBuffer, int aOffset, int aLength) {
        theTemplate  = aTemplate;
        theBuffer = aBuffer;
        thePos = aOffset;
        theBufferOffset = aOffset;
    }

    public static BerTlvBuilder from(BerTlv aTlv) {
        return from(aTlv, DEFAULT_SIZE);
    }

    public static BerTlvBuilder from(BerTlv aTlv, int bufferSize) {
        if(aTlv.isConstructed()) {
            BerTlvBuilder builder = template(aTlv.getTag(), bufferSize);
            for (BerTlv tlv : aTlv.theList) {
                builder.addBerTlv(tlv);
            }
            return builder;
        } else {
            return new BerTlvBuilder(null, new byte[bufferSize], 0, bufferSize).addBerTlv(aTlv);
        }
    }

    public static BerTlvBuilder template(BerTag aTemplate) {
        return template(aTemplate, DEFAULT_SIZE);
    }

    public static BerTlvBuilder template(BerTag aTemplate, int bufferSize) {
        return new BerTlvBuilder(aTemplate, new byte[bufferSize], 0, bufferSize);
    }

    public BerTlvBuilder addEmpty(BerTag aObject) {
        return addBytes(aObject, new byte[]{}, 0, 0);
    }

    public BerTlvBuilder addByte(BerTag aObject, byte aByte) {
        // type
        int len = aObject.bytes.length;
        System.arraycopy(aObject.bytes, 0, theBuffer, thePos, len);
        thePos+=len;

        // len
        theBuffer[thePos++] = 1;

        // value
        theBuffer[thePos++] = aByte;
        return this;
    }

    public BerTlvBuilder addAmount(BerTag aObject, BigDecimal aAmount) {
        BigDecimal numeric = aAmount.multiply(HUNDRED);
        StringBuilder sb = new StringBuilder(12);
        sb.append(numeric.longValue());
        while(sb.length() < 12) {
            sb.insert(0, '0');
        }
        return addHex(aObject, sb.toString());
    }

    public BerTlvBuilder addDate(BerTag aObject, Date aDate) {
        SimpleDateFormat format = new SimpleDateFormat("yyMMdd");
        return addHex(aObject, format.format(aDate));
    }

    public BerTlvBuilder addTime(BerTag aObject, Date aDate) {
        SimpleDateFormat format = new SimpleDateFormat("HHmmss");
        return addHex(aObject, format.format(aDate));
    }

    public int build() {

        if (theTemplate != null) {

            int tagLen = theTemplate.bytes.length;
            int lengthBytesCount = calculateBytesCountForLength(thePos);

            // shifts array
            System.arraycopy(theBuffer, theBufferOffset, theBuffer, tagLen + lengthBytesCount, thePos);

            // copies tag
            System.arraycopy(theTemplate.bytes, 0, theBuffer, theBufferOffset, theTemplate.bytes.length);

            fillLength(theBuffer, tagLen, thePos);

            thePos += tagLen + lengthBytesCount;
        }
        return thePos;
    }

    private void fillLength(byte[] aBuffer, int aOffset, int aLength) {

        if(aLength < 0x80) {
            aBuffer[aOffset] = (byte) aLength;

        } else if (aLength <0x100) {
            aBuffer[aOffset] = (byte) 0x81;
            aBuffer[aOffset+1] = (byte) aLength;

        } else if( aLength < 0x10000) {

            aBuffer[aOffset]   = (byte) 0x82;
            aBuffer[aOffset+1] = (byte) (aLength / 0x100);
            aBuffer[aOffset+2] = (byte) (aLength % 0x100);

        } else if( aLength < 0x1000000 ) {
            aBuffer[aOffset]   = (byte) 0x83;
            aBuffer[aOffset+1] = (byte) (aLength / 0x10000);
            aBuffer[aOffset+2] = (byte) (aLength / 0x100);
            aBuffer[aOffset+3] = (byte) (aLength % 0x100);
        } else {
            throw new IllegalStateException("length ["+aLength+"] out of range (0x1000000)");
        }
    }

    private int calculateBytesCountForLength(int aLength) {
        int ret;
        if(aLength < 0x80) {
            ret = 1;
        } else if (aLength <0x100) {
            ret = 2;
        } else if( aLength < 0x10000) {
            ret = 3;
        } else if( aLength < 0x1000000 ) {
            ret = 4;
        } else {
            throw new IllegalStateException("length ["+aLength+"] out of range (0x1000000)");
        }
        return ret;
    }

    public BerTlvBuilder addHex(BerTag aObject, String aHex) {
        byte[] buffer = HexUtil.parseHex(aHex);
        return addBytes(aObject, buffer, 0, buffer.length);
    }

    public BerTlvBuilder addBytes(BerTag aObject, byte[] aBytes) {
        return addBytes(aObject, aBytes, 0, aBytes.length);
    }

    public BerTlvBuilder addBytes(BerTag aTag, byte[] aBytes, int aFrom, int aLength) {
        int tagLength        = aTag.bytes.length;
        int lengthBytesCount = calculateBytesCountForLength(aLength);

        // TAG
        System.arraycopy(aTag.bytes, 0, theBuffer, thePos, tagLength);
        thePos+=tagLength;

        // LENGTH
        fillLength(theBuffer, thePos, aLength);
        thePos += lengthBytesCount;

        // VALUE
        System.arraycopy(aBytes, aFrom, theBuffer, thePos, aLength);
        thePos+=aLength;

        return this;
    }

    public BerTlvBuilder add(BerTlvBuilder aBuilder) {
        byte[] array = aBuilder.buildArray();
        System.arraycopy(array, 0, theBuffer, thePos, array.length);
        thePos+=array.length;
        return this;
    }


    public BerTlvBuilder addBerTlv(BerTlv aTlv) {
        if(aTlv.isConstructed()) {
            return add(from(aTlv, theBuffer.length));
        } else {
            return addBytes(aTlv.getTag(), aTlv.getBytesValue());
        }
    }

    /**
     * Add ASCII text
     *
     * @param aTag   tag
     * @param aText  text
     * @return builder
     */
    public BerTlvBuilder addText(BerTag aTag, String aText) {
        return addText(aTag, aText, ASCII);
    }

    /**
     * Add ASCII text
     *
     * @param aTag   tag
     * @param aText  text
     * @return builder
     */
    public BerTlvBuilder addText(BerTag aTag, String aText, Charset aCharset) {
        byte[] buffer = aText.getBytes(aCharset);
        return addBytes(aTag, buffer, 0, buffer.length);
    }

    public BerTlvBuilder addIntAsHex(BerTag aObject, int aCode, int aLength) {
        StringBuilder sb = new StringBuilder(aLength*2);
        sb.append(aCode);
        while(sb.length()<aLength*2) {
            sb.insert(0, '0');
        }
        return addHex(aObject, sb.toString());
    }

    public byte[] buildArray() {
        int count = build();
        byte[] buf = new byte[count];
        System.arraycopy(theBuffer, 0, buf, 0, count);
        return buf;
    }

    public BerTlv buildTlv() {
        int count = build();
        return new BerTlvParser().parseConstructed(theBuffer, theBufferOffset, count);
    }

    public BerTlvs buildTlvs() {
        int count = build();
        return new BerTlvParser().parse(theBuffer, theBufferOffset, count);
    }

    private final int theBufferOffset;
    private int theLengthPosition;
    private int thePos;
    private final byte[] theBuffer;
    private final BerTag theTemplate;
}

TLV解码

public class BerTlvParserV2 {

    private static final BerTagFactory DEFAULT_TAG_FACTORY = new DefaultBerTagFactory();

    private final BerTagFactory tagFactory;
    private final IBerTlvLogger log;

    public BerTlvParserV2() {
        this(DEFAULT_TAG_FACTORY, EMPTY_LOGGER);
    }

    public BerTlvParserV2(IBerTlvLogger aLogger) {
        this(DEFAULT_TAG_FACTORY, aLogger);
    }

    public BerTlvParserV2(BerTagFactory aTagFactory) {
        this(aTagFactory, EMPTY_LOGGER);
    }

    public BerTlvParserV2(BerTagFactory aTagFactory, IBerTlvLogger aLogger) {
        tagFactory = aTagFactory;
        log = aLogger;
    }

    public BerTlv parseConstructed(byte[] aBuf) {
        return parseConstructed(aBuf, 0, aBuf.length);
    }

    public BerTlv parseConstructed(byte[] aBuf, int aOffset, int aLen) {
        ParseResult result = parseWithResult(0, aBuf, aOffset, aLen);
        return result.tlv;
    }

    public BerTlvs parse(byte[] aBuf) {
        return parse(aBuf, 0, aBuf.length);
    }

    public BerTlvs parse(byte[] aBuf, final int aOffset, int aLen) {
        List<BerTlv> tlvs = new ArrayList<BerTlv>();
        if (aLen == 0) return new BerTlvs(tlvs);

        int offset = aOffset;
        while (offset < aLen - 2) {
            ParseResult result = parseWithResult(0, aBuf, offset, aLen - offset);
            tlvs.add(result.tlv);

            if (result.offset >= aOffset + aLen) {
                break;
            }
            offset = result.offset;

        }

        return new BerTlvs(tlvs);
    }

    private ParseResult parseWithResult(int aLevel, byte[] aBuf, int aOffset, int aLen) {
        String levelPadding = createLevelPadding(aLevel);
        if (aOffset + aLen > aBuf.length) {
            throw new IllegalStateException("Length is out of the range [offset=" + aOffset + ",  len=" + aLen + ", array.length=" + aBuf.length + ", level=" + aLevel + "]");
        }
        if (log.isDebugEnabled()) {
            log.debug("{}parseWithResult(level={}, offset={}, len={}, buf={})", levelPadding, aLevel, aOffset, aLen, HexUtil.toFormattedHexString(aBuf, aOffset, aLen));
        }

        // tag
        int tagBytesCount = getTagBytesCount(aBuf, aOffset);
        BerTag tag = createTag(levelPadding, aBuf, aOffset, tagBytesCount);
        if (log.isDebugEnabled()) {
            log.debug("{}tag = {}, tagBytesCount={}, tagBuf={}", levelPadding, tag, tagBytesCount, HexUtil.toFormattedHexString(aBuf, aOffset, tagBytesCount));
        }

        // length
        int lengthBytesCount = getLengthBytesCount(aBuf, aOffset + tagBytesCount);
        int valueLength = getDataLength(aBuf, aOffset + tagBytesCount);

        if (log.isDebugEnabled()) {
            log.debug("{}lenBytesCount = {}, len = {}, lenBuf = {}"
                    , levelPadding, lengthBytesCount, valueLength, HexUtil.toFormattedHexString(aBuf, aOffset + tagBytesCount, lengthBytesCount));
        }

        // value
        if (tag.isConstructed()) {

            ArrayList<BerTlv> list = new ArrayList<BerTlv>();
            addChildren(aLevel, aBuf, aOffset + tagBytesCount + lengthBytesCount, levelPadding, lengthBytesCount, valueLength, list);

            int resultOffset = aOffset + tagBytesCount + lengthBytesCount + valueLength;
            if (log.isDebugEnabled()) {
                log.debug("{}returning constructed offset = {}", levelPadding, resultOffset);
            }
            return new ParseResult(new BerTlv(tag, list), resultOffset);
        } else {
            // value
            byte[] value = new byte[valueLength];
            System.arraycopy(aBuf, aOffset + tagBytesCount + lengthBytesCount, value, 0, valueLength);
            int resultOffset = aOffset + tagBytesCount + lengthBytesCount + valueLength;
            if (log.isDebugEnabled()) {
                log.debug("{}value = {}", levelPadding, HexUtil.toFormattedHexString(value));
                log.debug("{}returning primitive offset = {}", levelPadding, resultOffset);
            }
            return new ParseResult(new BerTlv(tag, value), resultOffset);
        }

    }

    /**
     * @param aLevel          level for debug
     * @param aBuf            buffer
     * @param aOffset         offset (first byte)
     * @param levelPadding    level padding (for debug)
     * @param aDataBytesCount data bytes count
     * @param valueLength     length
     * @param list            list to add
     */
    private void addChildren(int aLevel, byte[] aBuf, int aOffset, String levelPadding, int aDataBytesCount, int valueLength, ArrayList<BerTlv> list) {
        int startPosition = aOffset;
        int len = valueLength;
        while (startPosition < aOffset + valueLength) {
            ParseResult result = parseWithResult(aLevel + 1, aBuf, startPosition, len);
            list.add(result.tlv);

            startPosition = result.offset;
            len = (aOffset + valueLength) - startPosition;

            if (log.isDebugEnabled()) {
                log.debug("{}level {}: adding {} with offset {}, startPosition={}, aDataBytesCount={}, valueLength={}"
                        , levelPadding, aLevel, result.tlv.getTag(), result.offset, startPosition, aDataBytesCount, valueLength);
            }
        }
    }

    private String createLevelPadding(int aLevel) {
        if (!log.isDebugEnabled()) {
            return "";
        }

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < aLevel * 4; i++) {
            sb.append(' ');
        }
        return sb.toString();
    }

    private static class ParseResult {
        public ParseResult(BerTlv aTlv, int aOffset) {
            tlv = aTlv;
            offset = aOffset;
        }

        @Override
        public String toString() {
            return "ParseResult{" +
                    "tlv=" + tlv +
                    ", offset=" + offset +
                    '}';
        }

        private final BerTlv tlv;
        private final int offset;
    }


    private BerTag createTag(String aLevelPadding, byte[] aBuf, int aOffset, int aLength) {
        if (log.isDebugEnabled()) {
            log.debug("{}Creating tag {}...", aLevelPadding, HexUtil.toFormattedHexString(aBuf, aOffset, aLength));
        }
        return tagFactory.createTag(aBuf, aOffset, aLength);
    }

    private int getTagBytesCount(byte[] aBuf, int aOffset) {
        if ((aBuf[aOffset] & 0x1F) == 0x1F) { // see subsequent bytes
            int len = 2;
            for (int i = aOffset + 1; i < aOffset + 10; i++) {
                if ((aBuf[i] & 0x80) != 0x80) {
                    break;
                }
                len++;
            }
            return len;
        } else {
            return 1;
        }
    }


    private int getDataLength(byte[] aBuf, int aOffset) {

        int length = aBuf[aOffset] & 0xff;

        if ((length & 0x80) == 0x80) {
            int numberOfBytes = length & 0x7f;
            if (numberOfBytes > 3) {
                throw new IllegalStateException(String.format("At position %d the len is more then 3 [%d]", aOffset, numberOfBytes));
            }

            length = 0;
            for (int i = aOffset + 1; i < aOffset + 1 + numberOfBytes; i++) {
                length = length * 0x100 + (aBuf[i] & 0xff);
            }

        }
        return length;
    }

    private static int getLengthBytesCount(byte aBuf[], int aOffset) {

        int len = aBuf[aOffset] & 0xff;
        if ((len & 0x80) == 0x80) {
            return 1 + (len & 0x7f);
        } else {
            return 1;
        }
    }


    private static final IBerTlvLogger EMPTY_LOGGER = new IBerTlvLogger() {
        public boolean isDebugEnabled() {
            return false;
        }

        public void debug(String aFormat, Object... args) {
        }
    };


}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++ list详解及模拟实现

目录 本节目标 1. list的介绍及使用 1.2 list的使用 2.list的模拟实现 1.对list进行初步的实现 2.头插和任意位置的插入 3.pos节点的删除&#xff0c;头删&#xff0c;尾删 4.销毁list和析构函数 5.const迭代器 6.拷贝构造和赋值操作 3.完整代码 本节目标 1. list的…

Zookeeper的ZAB协议原理详解

Zookeeper的ZAB协议原理详解 如何保证数据一致性。 Paxos&#xff0c; 吸收了主从。 zk 数据模型Watch机制 zab zookeeper原子广播协议。 ZAB概念 ZooKeeper是通过Zab协议来保证分布式事务的最终一致性。 Zab(ZooKeeper Atomic Broadcast,.ZooKeeper原子广播协议)支持…

8 款AI 绘画生成器:从文本创建 AI 艺术图像

人工智能正在影响各行各业&#xff0c;近年来它对创意产业的影响越来越大。由于AI绘画生成器的可操作性&#xff0c;许多人有机会用自己的想法进行艺术创作——即使他们没有接受过系统的专业艺术教育。 最先进的人工智能绘画生成器可能会改变我们未来创作艺术的方式。使用 AI …

pandas中DataFrame用法(python)

简介 DataFrame 一个表格型的数据结构&#xff0c;既有行标签&#xff08;index&#xff09;&#xff0c;又有列标签&#xff08;columns&#xff09;&#xff0c;它也被称异构数据表&#xff0c;所谓异构&#xff0c;指的是表格中每列的数据类型可以不同&#xff0c;比如可以…

【火猫TV】LPL春季赛前瞻:Tabe迎战LNG OMG关键一战!

北京时间3月20日&#xff0c;LPL春季赛今天继续进行&#xff0c;今天将会迎来春季赛常规赛第八周第三个比赛日&#xff0c;今天的两场比赛是LNG战队对阵AL战队以及OMG战队对阵BLG战队&#xff0c;今天的两场比赛对于LNG、AL以及OMG战队都是比较重要的&#xff0c;目前三支战队都…

「Nginx」Nginx配置详解

「Nginx」Nginx配置详解 参考文章1、正向代理和方向代理2、指定域名允许跨域 参考文章 1、Nginx反向代理 2、nginx配置详解 3、Nginx服务器之负载均衡策略&#xff08;6种&#xff09; 1、正向代理和方向代理 2、指定域名允许跨域 map $http_origin $allow_cors {default 1;…

vmare17 安装不可启动的iso镜像系统

由于要测试一个软件&#xff0c;要安装一个Windows11_InsiderPreview_Client_x64_zh-cn_26058.iso 于是在虚拟机里捣鼓一下。但是这个iso好像不能直接启动 这样就无法直接安装了&#xff0c;怎么办呢&#xff0c;可以先用个pe系统引导进去&#xff0c;再在PE系统里安装这个iso…

河北库卡机器人KR500电源模块故障,该如何处理?

库卡机器人KR500电源模块常见故障类型及维修方法 1&#xff09;电源模块故障指示灯亮 故障现象&#xff1a;库卡机器人KR500电源模块上的故障指示灯亮起&#xff0c;机器人不能正常工作。 维修方法&#xff1a;根据故障指示灯的闪烁频率或颜色判断具体的故障类型。然后&#xf…

计算机是如何工作的?CPU、内存、操作系统...

文章目录 前言一、冯诺依曼体系&#xff08;Von Neumann Architecture&#xff09;二、内存和硬盘区别三、CPU四、操作系统4.1计算机系统的分层视图4.2进程和线程4.3进程控制块&#xff08;PCB&#xff09;4.4进程管理 五、经典面试题 前言 计算的需求在⼈类的历史中是⼴泛存在…

用 ElementPlus的日历组件如何改为中文

文章目录 问题分析 问题 直接引入日历组件后&#xff0c;都是英文&#xff0c;应该如何把头部英文改为中文 <template><el-calendar><template #date-cell"{ data }"><p :class"data.isSelected ? is-selected : ">{{ data.da…

Web and HTTP

Web and HTTP First, a review… ▪ web page consists of objects ▪ object can be HTML file, JPEG image, Java applet, audio file,… ▪ web page consists of base HTML-file which includes several referenced objects ▪ each object is addressable by a URL, e.g.,…

java面向对象进阶---学习第一课

1.static学习&#xff1a; static&#xff0c;是java修饰符&#xff0c;可以修饰成员方法&#xff0c;成员变量。 注意&#xff1a;&#xff01;&#xff01;&#xff01;共享的情况&#xff0c;就是用static来修饰 类名&#xff1a;1.见名知意。2.私有化构造方法 3.方法定义…

发布 AUR 软件包 (ArchLinux)

首发日期 2024-03-09, 以下为原文内容: 理论上来说, 我们应该平等的对待每一个 GNU/Linux 发行版本. 但是, 因为窝日常使用 ArchLinux, 所以对 ArchLinux 有一些特别的优待, 比如自己做的软件优先为 ArchLinux 打包发布. 本文以软件包 librush-bin 为例, 介绍发布 AUR 软件包的…

LF-YOLO

LF-YOLO算法解读&#xff0c;针对x射线图像 1、EMF&#xff1a;网络结构的改变&#xff0c;enhanced multiscale feature(增强的多尺度特性)&#xff0c;多尺度融合模块。利用基于参数的方法和无参数的方法&#xff0c;可以同时结合X射线图像的局部和全局上下文&#xff0c;利用…

kaggle竞赛宝典 | 时间序列和时空数据大模型综述!(建议收藏!)

本文来源公众号“kaggle竞赛宝典”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;时间序列和时空数据大模型综述&#xff01; 作者&#xff1a;算法进阶 时间序列和时空数据大模型综述&#xff01; 1 前言 大型语言模型&…

short、byte 运算不能赋值给原类型问题分析

一、题目分析 该题目来源于牛客网中的一道选择题 给出如上代码&#xff0c;问你输入结果&#xff0c;但是考试时并不能看出错误原因导致踩坑 &#xff1b; 鼠标指向报错位置&#xff0c;直接给出提示了&#xff0c;两种类型四则运算都会强制转换为int之后进行运算 二、具体原…

演讲嘉宾公布 | 智能家居与会议系统专题论坛将于3月28日举办

一、智能家居与会议系统专题论坛 智能家居通过集成先进的技术和设备&#xff0c;为人们提供了更安全、舒适、高效、便捷且多彩的生活体验。智能会议系统它通过先进的技术手段&#xff0c;提高了会议效率&#xff0c;降低了沟通成本&#xff0c;提升了参会者的会议体验。对于现代…

Linux系统部署SQL Server结合内网穿透实现公网访问本地数据库

文章目录 前言1. 安装sql server2. 局域网测试连接3. 安装cpolar内网穿透4. 将sqlserver映射到公网5. 公网远程连接6.固定连接公网地址7.使用固定公网地址连接 前言 简单几步实现在Linux centos环境下安装部署sql server数据库&#xff0c;并结合cpolar内网穿透工具&#xff0…

使用OpenHarmony如何定制开发一套分布式亲子早教系统

概述 本篇Codelab是基于TS扩展的声明式开发范式编程语言编写的一个分布式益智拼图游戏&#xff0c;可以两台设备同时开启一局拼图游戏&#xff0c;每次点击九宫格内的图片&#xff0c;都会同步更新两台设备的图片位置。效果图如下&#xff1a; 说明&#xff1a; 本示例涉及使用…

【Gradle】取消使用idea+Gradle创建项目时自动生成.main结尾的子module

显示效果如下图所示&#xff0c;看起来比较不爽&#xff0c;但是不影响使用 解决方案&#xff1a; 一、打开.idea/gradle.xml文件 先在gradle.xml中添加内容 <option name"resolveModulePerSourceSet" value"false" />&#xff0c;然后刷新Gradle工…