8.批量消息发送与批量消息消费

news2024/11/16 1:40:56

highlight: arduino-light

4.4 批量消息

4.4.1 发送限制

生产者进行消息发送时可以一次发送多条消息,批量发送消息能显著提高传递小消息的性能。

不过需要注意以下几点:

  • 批量发送的消息必须具有相同的Topic
  • 批量发送的消息必须具有相同的刷盘策略
  • 批量发送的消息不能是延时消息与事务消息
  • 此外,这一批消息的总大小不应超过4MB。 image.png

批量消息发送是将同一个主题的多条消息一起打包发送到消息服务端,减少网络调用次数,提高网络传输效率。当然,并不是在同一批次中发送的消息数量越多越好,其判断依据是单条消息的长度,如果单条消息内容比较长,则打包多条消息发送会影响其他线程发送消息的响应时间,并且单批次消息总长度不能超过DefaultMQProducer#maxMessageSize即4M。

4.4.2 批量发送大小

默认情况下,一批发送的消息总大小不能超过4MB字节。如果想超出该值,有两种解决方案:

方案一:将批量消息进行拆分,拆分为若干不大于4M的消息集合分多次批量发送

方案二:在Producer端与Broker端修改属性 Producer端需要在发送之前设置Producer的maxMessageSize属性 Broker端需要修改其加载的配置文件中的maxMessageSize属性

4.4.3消息的组成

生产者通过send()方法发送的Message,并不是直接将Message序列化后发送到网络上的,而是通过这个Message生成了一个字符串发送出去的。这个字符串由四部分构成:Topic、消息Body、消息日志(占20字节),及用于描述消息的一堆属性key-value。这些属性中包含例如生产者地址、生产时间、要发送的QueueId等。最终写入到Broker中消息单元中的数据都是来自于这些属性。

批量消息发送要解决的问题是如何将这些消息编码以便服务端能够正确解码出每条消息的消息内容。

4.4.4 批量消费消息:修改批量消费属性

消息发送可以通过批量消息提高发送的速率,我们通过修改批量消费属性提高消费者消费的速率。

```java /** * @author: claude-彭方亮 * @package: com.yl.tmp.whitecloud.consumer.core.consumer.DataSyncConsumer * @date: 2022/11/9 7:43 * @description: * @version: 1.0 */

@Slf4j @Component("dataSyncConsumer") @RocketMQMessageListener(topic = RocketConstant.SYNCALLTOPIC, consumerGroup = RocketConstant.SYNCALLTOPIC + "-consumer-group") public class DataSyncConsumer implements RocketMQPushConsumerLifecycleListener, RocketMQListener {

@Autowired
private RocketMqConsumerConfig rocketMqConsumerConfig;
@Autowired
DataSyncService dataSyncService;
@Autowired
RocketMQTemplate template;
@Autowired
@Qualifier("logJdbcTemplate")
JdbcTemplate logJdbcTemplate;
@Autowired
@Qualifier("scanJdbcTemplate")
JdbcTemplate scanJdbcTemplate;
@Autowired
@Qualifier("logTransactionTemplate")
TransactionTemplate logTransactionTemplate;
@Autowired
@Qualifier("scanTransactionTemplate")
TransactionTemplate scanTransactionTemplate;
@Autowired
@Qualifier("compareJdbcTemplate")
JdbcTemplate compareJdbcTemplate;

public DataSyncConsumer(RocketMqConsumerConfig rocketMqConsumerConfig) {
    this.rocketMqConsumerConfig = rocketMqConsumerConfig;
}

@Override
public void onMessage(MessageExt messageExt) {
    String message = new String(messageExt.getBody(), Charset.forName("utf-8"));
    DataSyncDTO dataSyncDto = this.convertMsg(message);
    String table = dataSyncDto.getTable();
    //根据不同的表选择不同的template
    if (DataSyncConstant.TABLE_SCAN_NAME.equals(table) || DataSyncConstant.TABLE_SCAN_UPLOAD_NAME.equals(table)) {
        dataSyncService.write(dataSyncDto.getList(), dataSyncDto.getDataSourceKey(), dataSyncDto.getTableName(), scanJdbcTemplate, scanTransactionTemplate, dataSyncDto.getLimitStart(), dataSyncDto.getPageSize());
    } else {
        dataSyncService.write(dataSyncDto.getList(), dataSyncDto.getDataSourceKey(), dataSyncDto.getTableName(), logJdbcTemplate, logTransactionTemplate, dataSyncDto.getLimitStart(), dataSyncDto.getPageSize());
    }
}


/***
 * 消息转换为DataSyncDto
 * @param msg
 * @return
 */
private DataSyncDTO convertMsg(String msg) {
    DataSyncDTO dataSyncDto = JSON.parseObject(msg, DataSyncDTO.class);
    return dataSyncDto;
}

/***
 * 设置批量消费属性
 * @param defaultMQPushConsumer
 */
@Override
public void prepareStart(DefaultMQPushConsumer defaultMQPushConsumer) {
    Map<String, ConsumerProperties> consumers = this.rocketMqConsumerConfig.getConsumers();
    ConsumerProperties consumerProperties = (ConsumerProperties) consumers.get(defaultMQPushConsumer.getConsumerGroup());
    if (null != consumerProperties) {
        if (Boolean.TRUE.equals(consumerProperties.getDefaultFlag())) {
            consumerProperties = this.rocketMqConsumerConfig.getDefaultGroup();
        }
        defaultMQPushConsumer.setPullBatchSize(consumerProperties.getPullBatchSize());
        defaultMQPushConsumer.setConsumeThreadMin(consumerProperties.getConsumeThread());
        defaultMQPushConsumer.setConsumeThreadMax(consumerProperties.getConsumeThread());
        defaultMQPushConsumer.setConsumeMessageBatchMaxSize(consumerProperties.getConsumeMessageBatchMaxSize());
    }

}

} ```

Consumer的MessageListenerConcurrently监听接口的consumeMessage()方法的第一个参数为消息列表,但默认情况下每次只能消费一条消息。若要使其一次可以消费多条消息,则可以通过修改Consumer的consumeMessageBatchMaxSize属性来指定。不过,该值不能超过32。因为默认情况下消费者每次可以拉取的消息最多是32条。若要修改一次拉取的最大值,则可通过修改Consumer的pullBatchSize属性来指定。

ConsumeThreadMin和ConsumeThreadMax主要是负责消费的线程池大小,ConsumeMessageBatchMaxSize是每个消费线程每次消费的最大消息的数量。

4.4.5 批量消费存在的问题

Consumer的pullBatchSize属性与consumeMessageBatchMaxSize属性是否设置的越大越好?当然不是。

pullBatchSize值设置的越大,Consumer每拉取一次需要的时间就会越长,且在网络上传输出现问题的可能性就越高。若在拉取过程中若出现了问题,那么本批次所有消息都需要全部重新拉取。

consumeMessageBatchMaxSize值设置的越大,Consumer的消息并发消费能力越低,且这批被消费的消息具有相同的消费结果。因为consumeMessageBatchMaxSize指定的一批消息只会使用一个线程进行处理,且在处理过程中只要有一个消息处理异常,则这批消息需要全部重新再次消费处理。所以如果出现频繁的消费异常,会导致大面积的消费重试!性能也会急剧下降。

4.4.6批量发送消息源码

源代码:DefaultMQProducer#send

send方法调用了batch方法。batch方法对消息做了压缩。

java public SendResult send(Collection<Message> msgs)    throws MQClientException, RemotingException, MQBrokerException, InterruptedException {    //压缩消息集合成一条消息,然后发送出去    return this.defaultMQProducerImpl.send(batch(msgs)); }

代码:DefaultMQProducer#batch

java private MessageBatch batch(Collection<Message> msgs) throws MQClientException {    MessageBatch msgBatch;    try {        //将集合消息封装到MessageBatch        msgBatch = MessageBatch.generateFromList(msgs);        //遍历消息集合,检查消息合法性,设置消息ID,设置Topic        for (Message message : msgBatch) {            Validators.checkMessage(message, this);            MessageClientIDSetter.setUniqID(message);            message.setTopic(withNamespace(message.getTopic()));       }        //压缩消息,设置消息body        msgBatch.setBody(msgBatch.encode());   } catch (Exception e) {        throw new MQClientException("Failed to initiate the MessageBatch", e);   }    //设置msgBatch的topic    msgBatch.setTopic(withNamespace(msgBatch.getTopic()));    return msgBatch; }

4.4.7批量发送消息代码示例

调用的是DefaultMQProducer#send。

```java package com.itheima.mq.rocketmq.batch; ​ import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.message.Message; ​ import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; ​ public class Producer { ​    public static void main(String[] args) throws Exception {        //1.创建消息生产者producer,并制定生产者组名        DefaultMQProducer producer = new DefaultMQProducer("group1");        //2.指定Nameserver地址        producer.setNamesrvAddr("127.0.0.1:9876");        //3.启动producer        producer.start();

       List msgs = new ArrayList ();

       //4.创建消息对象,指定主题Topic、Tag和消息体        /**         * 参数一:消息主题Topic         * 参数二:消息Tag         * 参数三:消息内容         */        Message msg1 = new Message("test", "batch", ("Hello World" + 1).getBytes());        Message msg2 = new Message("test", "batch", ("Hello World" + 2).getBytes());        Message msg3 = new Message("test", "batch", ("Hello World" + 3).getBytes()); ​        msgs.add(msg1);        msgs.add(msg2);        msgs.add(msg3); ​        //5.发送消息        SendResult result = producer.send(msgs);        //发送状态        SendStatus status = result.getSendStatus(); ​        System.out.println("发送结果:" + result); ​        //线程睡1秒        TimeUnit.SECONDS.sleep(1); ​        //6.关闭生产者producer        producer.shutdown();   } ​ } ```

消费者消费消息

java package com.itheima.mq.rocketmq.batch; ​ import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.message.MessageExt; ​ import java.util.List; ​ public class Consumer {    public static void main(String[] args) throws Exception {        //1.创建消费者Consumer,制定消费者组名        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");        //2.指定Nameserver地址        consumer.setNamesrvAddr("127.0.0.1:9876");        //3.订阅主题Topic和Tag        consumer.subscribe("test", "batch"); ​        //4.设置回调函数,处理消息        consumer.registerMessageListener(new MessageListenerConcurrently() { ​            //接受消息内容            @Override            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {                for (MessageExt msg : msgs) {                    System.out.println("consumeThread=" + Thread.currentThread().getName() + "," + new String(msg.getBody()));               }                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;           }       });        //5.启动消费者consumer        consumer.start(); ​        System.out.println("消费者启动");   } } ​

4.4.8 消息列表切割

如果消息的总长度可能大于4MB时,这时候最好把消息进行分割

```java public class ListSplitter implements Iterator > { private final int SIZE LIMIT = 1024 * 1024 * 4; private final List messages; private int currIndex; public ListSplitter(List messages) { this.messages = messages; } @Override public boolean hasNext() { return currIndex < messages.size(); } @Override public List next() { int nextIndex = currIndex; int totalSize = 0; for (; nextIndex < messages.size(); nextIndex++) { Message message = messages.get(nextIndex); int tmpSize = message.getTopic().length() + message.getBody().length; Map properties = message.getProperties(); for (Map.Entry entry : properties.entrySet()) { tmpSize += entry.getKey().length() + entry.getValue().length(); } tmpSize = tmpSize + 20; // 增加日志的开销20字节 if (tmpSize > SIZE LIMIT) { //单个消息超过了最大的限制 //忽略,否则会阻塞分裂的进程 if (nextIndex - currIndex == 0) { //假如下一个子列表没有元素,则添加这个子列表然后退出循环,否则只是退出循环 nextIndex++; } break; } if (tmpSize + totalSize > SIZE_LIMIT) { break; } else { totalSize += tmpSize; }

}
   List<Message> subList = messages.subList(currIndex, nextIndex);
   currIndex = nextIndex;
   return subList;

} } //把大的消息分裂成若干个小的消息 ListSplitter splitter = new ListSplitter(messages); while (splitter.hasNext()) { try { List listItem = splitter.next(); producer.send(listItem); } catch (Exception e) { e.printStackTrace(); //处理error } } ```

4.4.9 数据同步批量消息做数据切割

java /*** * 发送消息 * @param dataSourceKey * @param table * @param limitStart * @param tableIndex * @param tableName * @param list */ private void sendMessage(String dataSourceKey, String table, Integer limitStart, int tableIndex, String tableName, List<LinkedHashMap<String, Object>> list) { //要被发送消息的dto DataSyncDTO dataSyncDto = new DataSyncDTO(); dataSyncDto.setDataSourceKey(dataSourceKey); dataSyncDto.setTable(table); dataSyncDto.setLimitStart(limitStart); dataSyncDto.setList(list); dataSyncDto.setTableName(tableName); dataSyncDto.setPageSize(DataSyncConstant.MAX_PAGE_SIZE); //将发送消息的dto转换为json String json = JSON.toJSONStringWithDateFormat(dataSyncDto, DataSyncConstant.dateFormat, SerializerFeature.WriteMapNullValue); //获取要发送的json的消息的大小(字节) byte[] bytes = json.getBytes(); //转换为单位mb int sizeOfJson = bytes.length / 1024 / 1024; //计算批次,默认每批最少向mq发送20条数据 MIN_SEND_PAGE_SIZE = 20 int batchNum = calculateBatchNum(list.size(), DataSyncConstant.MIN_SEND_PAGE_SIZE); //判断大于1mb 这里并没有设为4mb if (sizeOfJson >= DataSyncConstant.MQ_MESSAGE_SIZE) { //遍历批次 依次发送消息 for (int i = 0; i < batchNum; i++) { //切割list List subList = startPage(list, i + 1, DataSyncConstant.MIN_SEND_PAGE_SIZE); //发送消息 sendMessage(dataSourceKey, table, limitStart, tableIndex, tableName, subList); } return; } //如果小于2mb 直接发送消息 DefaultMQProducer producer = mqTemplate.getProducer(); Message message = new Message(); message.setBody(json.getBytes()); message.setTopic(RocketConstant.SYNC_ALL_TOPIC); message.setTags(table); Long sendStart = System.currentTimeMillis(); SendResult send = null; try { send = producer.send(message, DataSyncConstant.sendMessageMaxTimeOut); } catch (Exception e) { try { if (DataSyncConstant.TABLE_SCAN_NAME.equals(table) || DataSyncConstant.TABLE_SCAN_UPLOAD_NAME.equals(table)) { addSyncAllRecord(dataSourceKey, tableName, list, e.getMessage(), scanJdbcTemplate, scanTransactionTemplate); } else { addSyncAllRecord(dataSourceKey, tableName, list, e.getMessage(), logJdbcTemplate, logTransactionTemplate); } } catch (Exception ex) { log.info("数据同步-发送到mq-插入同步记录异常$$$$ :{} $$$!dataSourceKey:{},tableName:{},size:{},json:{}", ex.getMessage(), dataSourceKey, tableName, list.size(), JSON.toJSONStringWithDateFormat(list, DataSyncConstant.dateFormat, SerializerFeature.WriteMapNullValue)); } } if (EmptyUtil.isNotEmpty(send) && !SendStatus.SEND_OK.equals(send.getSendStatus())) { try { if (DataSyncConstant.TABLE_SCAN_NAME.equals(table) || DataSyncConstant.TABLE_SCAN_UPLOAD_NAME.equals(table)) { addSyncAllRecord(dataSourceKey, tableName, list, "发送mq失败", scanJdbcTemplate, scanTransactionTemplate); } else { addSyncAllRecord(dataSourceKey, tableName, list, "发送mq失败", logJdbcTemplate, logTransactionTemplate); } } catch (Exception ex) { log.info("数据同步-发送到mq-插入同步记录异常$$$$ :{} $$$!dataSourceKey:{},tableName:{},size:{},json:{}", ex.getMessage(), dataSourceKey, tableName, list.size(), JSON.toJSONStringWithDateFormat(list, DataSyncConstant.dateFormat, SerializerFeature.WriteMapNullValue)); } } }

批量发送消息最大限制是4mb,但是并不是说消息一次发送的越大越好,当消息的大小趋近于4mb的时候,性能会急剧下降。官方文档里给的建议是不要大于1mb。

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

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

相关文章

统计数组中非零元素的个数统计数组中每列中非零元素的个数统计数组中每行中非零元素的个数numpy.count_nonzero()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 统计数组中非零元素的个数 统计数组中每列中非零元素的个数 统计数组中每行中非零元素的个数 numpy.count_nonzero() 选择题 以下说法错误的是? import numpy as np anp.array([[0,1,2,3],[3,…

文件加密后怎么打开?打开加密文件的方法

当我们把重要文件加密后&#xff0c;可以有效地保护文件数据安全&#xff0c;那么文件加密后该怎么打开使用呢&#xff1f;下面我们就一起来了解一下吧。 EFS加密 作为Windows系统提供的文件加密方法&#xff0c;EFS加密并不需要密码。在我们使用加密时登录的系统账号时&…

使用HTML制作一个赛龙舟小游戏

在这个信息爆炸的时代&#xff0c;开发者们肩负着前所未有的责任与挑战&#xff0c;以屈原名言 路漫漫其修远兮&#xff0c;吾将上下而求索 为指引&#xff0c;使用HTML制作一个赛龙舟小游戏&#xff0c;以此激励广大开发者在技术征途上不断求索&#xff0c;追求极致。 一、前期…

忘记了谷歌Gmail账号名怎么办?用这种方法轻松找回谷歌邮箱地址

有些朋友以前注册过谷歌邮箱&#xff0c;但很久很久没有再去使用。 现在注册ChatGPT需要谷歌邮箱&#xff0c;于是打算把尘封已久的谷歌邮箱找出来&#xff0c;可是这时候你才发现&#xff0c;谷歌邮箱的账号名早已忘掉了。 今天重点来说说如何找回谷歌账号&#xff0c;希望能够…

ANTLR实战

ANTLR&#xff08;Another Tool for Language Recognition&#xff09;是目前非常活跃的语法生成工具&#xff0c;用Java语言编写&#xff0c;基于LL&#xff08;∗&#xff09;解析方式&#xff0c;使用自上而下的递归下降分析方法。ANTLR可以用来产生词法分析器、语法分析器和…

【运维知识进阶篇】zabbix5.0稳定版详解5(SNMP网络管理协议监控)

简单网络管理协议&#xff08;SNMP&#xff09;是专门设计用于在IP网络管理网络节点&#xff08;服务器、工作站、路由器、交换机及HUBS网络枢纽等&#xff09;的一种标准协议&#xff0c;它是一种应用层协议。 目录 SNMP三种版本 监控SNMP SNMP&#xff1a;OID和MIB介绍可以…

深度学习基于Resnet18的图像多分类--训练自己的数据集(超详细 含源码)

1.ResNet18原理 2.文件存储 一个样本存放的文件夹为dataset 下两个文件夹 train和test文件(训练和预测) 3.训练和测试的文件要相同。下面都分别放了 crane (鹤)、elephant(大象)、leopard(豹子) 4.编写预测的Python文件&#xff1a;code.py 跟dataset是同级路径。 5.code.p…

ROS从入门到精通2-7:Gazebo仿真之动态生成障碍物

目录 0 专栏介绍1 动态生成障碍应用场景2 基于Gazebo动态生成障碍2.1 spawn_model服务2.2 动态构造障碍物URDF2.3 请求服务与动态生成 3 实测演示 0 专栏介绍 本专栏旨在通过对ROS的系统学习&#xff0c;掌握ROS底层基本分布式原理&#xff0c;并具有机器人建模和应用ROS进行实…

CSS | 解决html中img标签图片底部存在空白缝隙的问题

目录 问题描述 原因分析 解决方案 写在最后 问题描述 在学习CSS的过程中&#xff0c;我们经常会遇到图片底侧存在空白缝隙的问题。 代码示例&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" />&l…

SpringCloudAlibaba之Sentinel源码分析--protoc-3.17.3-win64

Sentinel源码分析 文章目录 Sentinel源码分析1.Sentinel的基本概念1.1.ProcessorSlotChain1.2.Node1.3.Entry1.3.1.自定义资源1.3.2.基于注解标记资源 1.4.Context1.4.1.什么是Context1.4.2.Context的初始化1.4.2.1.自动装配1.4.2.2.AbstractSentinelInterceptor1.4.2.3.Contex…

【C++初阶】string类常见题目详解(一)—— 仅仅反转字母、字符串中的第一个唯一字母、字符串最后一个单词的长度、验证回文串、字符串相加

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C初阶 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【C初阶】…

【Python 基础篇】Python 集合及集合常用函数

文章目录 导言一、集合的创建和访问二、集合的常用函数len()add()remove()union()intersection()difference()issubset()issuperset()clear() 总结 导言 在Python中&#xff0c;集合&#xff08;Set&#xff09;是一种无序、不重复的数据类型&#xff0c;用于存储多个唯一的元…

HCIP网络笔记分享——广域网协议及BGP协议

第二部分 HCIA回顾一、广域网技术1、HDLC2、PPP3、PAP4、CHAP5、GRE6、运行路由协议 二、动态路由协议1、OSPF2、重发布3、路由策略3.1 抓流量3.2 具体过程 4、BGP 三、BGP边界网关协议1、BGP的数据包2、BGP的状态机3、BGP的工作过程4、BGP的路由黑洞问题5、BGP的防环问题6、BG…

Studio One6.1.1免费中文版电子音乐、摇滚乐制作软件

Studio One6是一款专业的音乐制作软件&#xff0c;该软件提供了全面的音频编辑和混音功能&#xff0c;包括录制、编曲、合成、采样等多种工具&#xff0c;可用于制作各种类型的音乐&#xff0c;如流行音乐、电子音乐、摇滚乐等。 Studio One6.1的主要特点包括&#xff1a; 1. …

深入理解什么是端口(port)

每当看到有人的简历上写着熟悉 tcp/ip, http 等协议时, 我就忍不住问问他们: 你给我说说, 端口是啥吧! 可惜, 很少有人能说得让人满意... 所以这次就来谈谈端口(port), 这个熟悉的陌生人. 在此过程中, 还会谈谈间接层, naming service 等概念, IoC, 依赖倒置等原则以及 TCP 协议…

JavaEE的学习(Spring +Spring MVC + MyBatis)

一、Spring入门 Spring是一个轻量级的控制反转 (IoC-Inversion of Control)和面向切面 (AOP-Aspect Oriented Programming)的容器&#xff08;框架&#xff09;。它采用分层架构&#xff0c;由大约20个模块组成&#xff0c;这些模块分为Core Container、Data Access/Integrati…

什么是计算机蠕虫?

计算机蠕虫诞生的背景 计算机蠕虫的诞生与计算机网络的发展密切相关。20世纪60年代末和70年代初&#xff0c;互联网还处于早期阶段&#xff0c;存在着相对较少的计算机和网络连接。然而&#xff0c;随着计算机技术的进步和互联网的普及&#xff0c;计算机网络得以迅速扩张&…

TC8:SOMEIPSRV_FORMAT_09-10

SOMEIPSRV_FORMAT_09: Undefined bits in the Flag field 目的 Flag字段中的未定义位应静态设置为0 测试步骤 DUT CONFIGURE:启动具有下列信息的服务Service ID:SERVICE-ID-1Instance数量:1Tester:客户端-1监听在网卡上DUT:发送SOME/IP Notification消息Tester:验证接收…

Flutter应用开发,系统样式改不了?SystemChrome 状态栏、导航栏、屏幕方向……想改就改

文章目录 开发场景SystemChrome 介绍SystemChrome的使用导入 SystemChrome 包隐藏状态栏说明 改变状态栏的样式注意事项其他样式说明 锁定屏幕方向锁定屏幕方向实例注意事项 开发场景 开发APP时&#xff0c;我们经常要客制化状态栏、导航栏栏等的样式和风格&#xff0c;Flutte…

网络之网络基础入门

文章目录 前言一、局域网和广域网1.局域网LAN2.广域网WAN3.城域网和校园网4.如何区分广域网和局域网 二、协议1.概念2.理解3.协议分层4.数据传输的条件 三、OSI七层模型&#xff08;了解即可&#xff09;1.概念2.OSI七层模型 四、TCP/IP五层&#xff08;四层&#xff09;模型1.…