消息中间件的功能:

  通过学习ActiveMq,kafka,rabbitMq这些消息中间件,我们大致能为消息中间件的功能做一下以下定义:可以先从基本的需求开始思考

  • 最基本的是要能支持消息的发送和接收,需要涉及到网络通信就一定会涉及到NIO
  • 消息中心的消息存储(持久化/非持久化)
  • 消息的序列化和反序列化
  • 是否跨语言
  • 消息的确认机制,如何避免消息重发

  高级功能:

  • 消息的有序性
  • 是否支持事务消息
  • 消息收发的性能,对高并发大数据量的支持
  • 是否支持集群
  • 消息的可靠性存储
  • 是否支持多协议

MQ消息存储选择:

  从主流的几种MQ消息队列采用的存储方式来看,主要会有三种

  1. 分布式KV存储,比如ActiveMQ中采用的levelDB、Redis, 这种存储方式对于消息读写能力要求不高的情况下可以使用
  2. 文件系统存储,常见的比如kafka、RocketMQ、RabbitMQ都是采用消息刷盘到所部署的机器上的文件系统来做持久化,这种方案适合对于有高吞吐量要求的消息中间件,因为消息刷盘是一种高效率,高可靠、高性能的持久化方式,除非磁盘出现故障,否则一般是不会出现无法持久化的问题
  3. 关系型数据库,比如ActiveMQ可以采用mysql作为消息存储,关系型数据库在单表数据量达到千万级的情况下IO性能会出现瓶颈,所以ActiveMQ并不适合于高吞吐量的消息队列场景。

  总的来说,对于存储效率,文件系统要优于分布式KV存储,分布式KV存储要优于关系型数据库.

RocketMQ的发展历史:

  RocketMq是一个由阿里巴巴开源的消息中间件, 2012年开源,2017年成为apache顶级项目。它的核心设计借鉴了Kafka,所以我们在了解RocketMQ的时候,会发现很多和kafka相同的特性。同时呢,Rocket在某些功能上和kafka又有较大的差异,接下来我们就去了解RocketMQ

  • 支持集群模型、负载均衡、水平扩展能力
  • 亿级别消息堆积能力
  • 采用零拷贝的原理,顺序写盘,随机读
  • 底层通信框架采用Netty NIO
  • NameServer代替Zookeeper,实现服务寻址和服务协调
  • 消息失败重试机制、消息可查询
  • 强调集群无单点,可扩展,任意一点高可用,水平可扩展
  • 经过多次双十一的考验

RocketMQ的架构:

  集群本身没有什么特殊之处,和kafka的整体架构类似,其中zookeeper替换成了NameServer。在rocketmq的早版本(2.x)的时候,是没有namesrv组件的,用的是zookeeper做分布式协调和服务发现,但是后期阿里数据根据实际业务需求进行改进和优化,自主研发了轻量级的namesrv,用于注册Client服务与Broker的请求路由工作,namesrv上不做任何消息的位置存储,频繁操作zookeeper的位置存储数据会影响整体集群性能.

RocketMQ由四部分组成:

  1. Name Server 可集群部署,节点之间无任何信息同步。提供轻量级的服务发现和路由
  2. Broker(消息中转角色,负责存储消息,转发消息) 部署相对复杂,Broker 分为Master 与Slave,一个Master 可以对应多个Slave,但是一个Slave 只能对应一个Master,Master 与Slave 的对应关系通过指定相同的BrokerName,不同的BrokerId来定 义,BrokerId为0 表示Master,非0 表示Slave。Master 也可以部署多个。
  3. Producer,生产者,拥有相同 Producer Group 的 Producer 组成一个集群, 与Name Server 集群中的其中一个节点(随机选择)建立长连接,定期从Name Server 取Topic 路由信息,并向提供Topic服务的Master 建立长连接,且定时向Master 发送心跳。Producer 完全无状态,可集群部署。
  4. Consumer,消费者,接收消息进行消费的实例,拥有相同 Consumer Group 的 Consumer 组成一个集群,与Name Server 集群中的其中一个节点(随机选择)建立长连接,定期从Name Server 取Topic 路由信息,并向提供Topic 服务的Master、Slave 建立长连接,且定时向Master、Slave 发送心跳。Consumer既可以从Master 订阅消息,也可以从Slave 订阅消息,订阅规则由Broker 配置决定。

  要使用rocketmq,至少需要启动两个进程,nameserver、broker,前者是各种topic注册中心,后者是真正的broker。

单机环境RocketMQ的安装(单master):

  下载 rocketmq的安装文件: http://rocketmq.apache.org

  解压 unzip rocketmq-all-4.4.0-bin-release.zip

启动 nameserver:

  进入rocketMQ解压目录下的bin文件夹,启动namesrv服务:nohup sh mqnamesrv &  tail -f ~/logs/rocketmqlogs/namesrv.log 查看启动日志

  停止 nameserver : sh bin/mqshutdown namesrv . 停止服务的时候需要注意,要先停止broker,其次停止nameserver。

  默认情况下,nameserver监听的是 9876 端口。查看日志内容出现如下信息即启动成功:

启动 broker:

  nohup sh bin/mqbroker -n ${namesrvIp}:9876 -c /conf/broker.conf &   其中[-c可以指定broker.conf配置文件]。默认情况下会加载conf/broker.conf

  停止broker :sh bin/mqshutdown broker

  • nohup sh mqbroker -n localhost:9876 &  启动broker,其中-n表示指定当前broker对应的命名服务地址: 默认情况下,broker监听的是10911端口 。
  • 输入 tail -f ~/logs/rocketmqlogs/broker.log 查看日志
  • 如果 tail -f ~/logs/rocketmqlogs/broker.log 提示找不到文件,则打开当前目录下的 nohup.out日志文件查看,出现如下日志表示启动失败,提示内存无法分配

内存不足的问题:

  这是因为bin 目录下启动 nameserv 与 broker 的 runbroker.sh 和 runserver.sh 文件中默认分配的内存太大,rocketmq比较耗内存,所以默认分配的内存比较大,而系统实际内存却太小导致启动失败,通常像虚拟机上安装的 CentOS 服务器内存可能是没有高的,只能调小。实际中应该根据服务器内存情况,配置一个合适的值 ,我这里设置成1g。

JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn256m"
Xms:是指设定程序启动时占用内存大小。一般来讲,大点,程序会启动的快一点,但是也可能会导致机器暂时间变慢。
Xmx:是指设定程序运行期间最大可占用的内存大小。如果程序运行需要占用更多的内存,超出了这个设置值,就会抛出OutOfMemory异常。
xmn:年轻代的heap大小,一般设置为Xmx的3、4分之一

  修改后重新启动,输入 tail -f ~/logs/rocketmqlogs/broker.log 查看日志:

  在这里我们会发现,这个broker所监听的IP地址似乎不是我localhost,这个会导致后续我们会连接不上broker,我们需要修改配置文件 broker.conf 增加一行配置 brokerIP1=192.168.1.101,然后重新启动 nohup sh mqbroker -n localhost:9876 -c ../conf/broker.conf &,再查看日志:

broker.conf 文件 基本配置:

  • namesrvAddr  :nameserver地址
  • brokerClusterName = DefaultCluster:Cluster名称,如果集群机器数比较多,可以分成多个cluster,每个cluster提供给不同的业务场景使用
  • brokerName = broker-a:broker名称,如果配置主从模式,master和slave需要配置相同的名称来表明关系
  • brokerId = 0:在主从模式中,一个master broker可以有多个slave,0表示master,大于0表示不同slave的id
  • deleteWhen = 04:删除文件时间点,默认是凌晨4点
  • fileReservedTime = 48:文件保留时间,默认48小时
  • brokerRole = ASYNC_MASTER: SYNC_MASTER/ASYNC_MASTER/SLAVE ; 同步表示slave和master消息同步完成后再返回信息给客户端
  • flushDiskType = ASYNC_FLUSH:刷盘方式
  • autoCreateTopicEnable = true : topic不存在的情况下自动创建
  • brokerIP1         ip           ip设置外网ip,不需要连接外网的话,可以在参数前面加#注释掉
  • listenPort         port        port可自由设置,一般设置10911
  • brokerPermission      0x4|0x2       broker读写权限
  • defaultTopicQueueNums     8      默认topic读写队列数
  • clusterTopicEnable            true     是否启用集群topic
  • brokerTopicEnable            true     是否启用brokertopic
  • autoCreateSubscriptionGroup     TRUE      是否允许Broker 自动创建订阅组,建议线下开启,线上关闭
  • sendMessageThreadPoolNums     1        发送消息线程池数量
  • storePathConsumeQueue        $HOME/store/consumequeue      消费队列存储路径
  • storePathIndex                         $HOME/store/index         消息索引存储路径
  • storeCheckpoint            $HOME/store/checkpoint          checkpoint 文件存储路径
  • abortFile                  $HOME/store/abort             abort 文件存储路径

消息发送和接收基本应用:

1.添加 pom 依赖:

<dependency>
  <groupId>org.apache.rocketmq</groupId>
  <artifactId>rocketmq-client</artifactId>
  <version>4.5.</version>
</dependency>

2.生产者 producer:

public class RocketMqProducer {
public static void main(String[] args) throws MQClientException, InterruptedException {
/*
     *生产者组,简单来说就是多个发送同一类消息的生产者称之为一个生产者组
*rocketmq支持事务消息,在发送事务消息时,如果事务消息异常(producer挂了),broker端会来回查
*事务的状态,这个时候会根据group名称来查找对应的producer来执行相应的回查逻辑。相当于实现了producer的高可用
     */
DefaultMQProducer producer = new DefaultMQProducer("unique_producer_group_name");
     producer.setDefaultTopicQueueNums(3);//设置默认的queue数量
//指定namesrv服务地址,获取broker相关信息
producer.setNamesrvAddr("192.168.1.101:9876");
producer.start();
for (int i = ; i < ; i++) {
try {
//创建一个消息实例,指定指定topic、tag、消息内容
Message msg = new Message("testTopic", "testTag",
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */);
//发送消息并且获取发送结果
SendResult sendResult = producer.send(msg);
System.out.printf("%s%n", sendResult);
} catch (Exception e) {
e.printStackTrace();
Thread.sleep();
}
}
producer.shutdown();
}
}

  SendResult中,有一个sendStatus状态,表示消息的发送状态。一共有四种状态:

  1. FLUSH_DISK_TIMEOUT : 表示没有在规定时间内完成刷盘(需要Broker 的刷盘策Ill创立设置成SYNC_FLUSH 才会报这个错误) 。
  2. FLUSH_SLAVE_TIMEOUT :表示在主备方式下,并且Broker 被设置成SYNC_MASTER 方式,没有在设定时间内完成主从同步。
  3. SLAVE_NOT_AVAILABLE : 这个状态产生的场景和FLUSH_SLAVE_TIMEOUT 类似, 表示在主备方式下,并且Broker 被设置成SYNC_MASTER ,但是没有找到被配置成Slave 的Broker 。
  4. SEND_OK :表示发送成功,发送成功的具体含义,比如消息是否已经被存储到磁盘?消息是否被同步到了Slave 上?消息在Slave 上是否被写入磁盘?需要结合所配置的刷盘策略、主从策略来定。这个状态还可以简单理解为,没有发生上面列出的三个问题状态就是SEND OK

3.消费者consumer:

public class RocketMqConsumer {
public static void main(String[] args) throws MQClientException {
//消费者的组名,这个和kafka是一样,这里需要注意的是,
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("unique_consumer_group_name");
//指定NameServer地址,多个地址以 ; 隔开
consumer.setNamesrvAddr("192.168.1.101:9876");
//设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费
//如果非第一次启动,那么按照上次消费的位置继续消费
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
//订阅PushTopic下Tag为push的消息
consumer.subscribe("testTopic", "*"); //*表示不过滤,可以通过tag来过滤,比如:”tagA”
/*
     * 注册消息监听回调这里有两种监听,MessageListenerConcurrently以及MessageListenerOrderly
* 前者是普通监听,后者是顺序监听。这块在后续单独说明
     */
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt>
msgs,
ConsumeConcurrentlyContext context) {
System.out.printf("%s Receive New Messages: %s %n",
Thread.currentThread().getName(), msgs);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;//返回消息消费状态
}
});
consumer.start();
System.out.printf("Consumer Started.%n"); }

  Rocketmq中支持广播消息,就意味着同一个group中的消费者可以消费同一个消息。

  consumerGroup:位于同一个consumerGroup中的consumer实例和producerGroup中的各个produer实例承担的角色类似;同一个group中可以配置多个consumer,可以提高消费端的并发消费能力以及容灾,和kafka一样,多个consumer会对消息做负载均衡,意味着同一个topic下的不同messageQueue会分发给同一个group中的不同consumer。同时,如果我们希望消息能够达到广播的目的,那么只需要把consumer加入到不同的group就行。

  RocketMQ提供了两种消息消费模型,一种是pull主动拉去,另一种是push,被动接收。但实际上RocketMQ都是pull模式,只是push在pull模式上做了一层封装,也就是pull到消息以后触发业务消费者注册到这里的callback. RocketMQ是基于长轮训来实现消息的pull。

  nameServer的地址:name server地址,用于获取broker、topic信息。

SpringBoot整合RocketMq:

1.pom.xml:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.5.</version>
</dependency>

2.application.yml :

rocketmq:
# 生产者配置
producer:
isOnOff: on
# 发送同一类消息的设置为同一个group,保证唯一
groupName: unique_producer_group_name
# 服务地址
namesrvAddr: 192.168.1.101:
# 消息最大长度 默认1024*(4M)
maxMessageSize:
# 发送消息超时时间,默认3000
sendMsgTimeout:
# 发送消息失败重试次数,默认2
retryTimesWhenSendFailed:
# 消费者配置
consumer:
isOnOff: on
# 官方建议:确保同一组中的每个消费者订阅相同的主题。
groupName: unique_consumer_group_name
# 服务地址
namesrvAddr: 192.168.1.101:
# 接收该 Topic 下所有 Tag
topics: testTopic~*;
consumeThreadMin:
consumeThreadMax:
# 设置一次消费消息的条数,默认为1条
consumeMessageBatchMaxSize: # 配置 Group Topic Tag
plat:
plat-group: unique_group_name
plat-topic: testTopic
plat-tag: testTag

3. ProducerConfig 生产者配置:

@Configuration
public class ProducerConfig {
private static final Logger LOG = LoggerFactory.getLogger(ProducerConfig.class) ;
@Value("${rocketmq.producer.groupName}")
private String groupName;
@Value("${rocketmq.producer.namesrvAddr}")
private String namesrvAddr;
@Value("${rocketmq.producer.maxMessageSize}")
private Integer maxMessageSize ;
@Value("${rocketmq.producer.sendMsgTimeout}")
private Integer sendMsgTimeout;
@Value("${rocketmq.producer.retryTimesWhenSendFailed}")
private Integer retryTimesWhenSendFailed;
@Bean
public DefaultMQProducer defaultMQProducer() {
DefaultMQProducer producer;
producer = new DefaultMQProducer(this.groupName);
producer.setNamesrvAddr(this.namesrvAddr);
//如果需要同一个jvm中不同的producer往不同的mq集群发送消息,需要设置不同的instanceName
if(this.maxMessageSize!=null){
producer.setMaxMessageSize(this.maxMessageSize);
}
if(this.sendMsgTimeout!=null){
producer.setSendMsgTimeout(this.sendMsgTimeout);
}
//如果发送消息失败,设置重试次数,默认为2次
if(this.retryTimesWhenSendFailed!=null){
producer.setRetryTimesWhenSendFailed(this.retryTimesWhenSendFailed);
}
try {
producer.start();
} catch (MQClientException e) {
e.printStackTrace();
}
return producer;
}
}

4.ConsumerConfig 消费者配置:

@Configuration
public class ConsumerConfig {
private static final Logger LOG = LoggerFactory.getLogger(ConsumerConfig.class) ;
@Value("${rocketmq.consumer.namesrvAddr}")
private String namesrvAddr;
@Value("${rocketmq.consumer.groupName}")
private String groupName;
@Value("${rocketmq.consumer.consumeThreadMin}")
private int consumeThreadMin;
@Value("${rocketmq.consumer.consumeThreadMax}")
private int consumeThreadMax;
@Value("${rocketmq.consumer.topics}")
private String topics;
@Value("${rocketmq.consumer.consumeMessageBatchMaxSize}")
private int consumeMessageBatchMaxSize;
@Resource
private RocketMsgListener msgListener;
@Bean
public DefaultMQPushConsumer defaultMQPushConsumer(){
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(groupName);
consumer.setNamesrvAddr(namesrvAddr);
consumer.setConsumeThreadMin(consumeThreadMin);
consumer.setConsumeThreadMax(consumeThreadMax);
consumer.registerMessageListener(msgListener);
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
consumer.setConsumeMessageBatchMaxSize(consumeMessageBatchMaxSize);
try {
String[] topicTagsArr = topics.split(";");
for (String topicTags : topicTagsArr) {
String[] topicTag = topicTags.split("~");
consumer.subscribe(topicTag[],topicTag[]);
}
consumer.start();
}catch (MQClientException e){
e.printStackTrace();
}
return consumer;
}
}

5.RocketMsgListener 监听器:

@Component
public class RocketMsgListener implements MessageListenerConcurrently {
private static final Logger LOG = LoggerFactory.getLogger(RocketMsgListener.class) ;
@Resource
private ParamConfigService paramConfigService ; @Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext context) {
if (CollectionUtils.isEmpty(list)){
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
MessageExt messageExt = list.get();
LOG.info("接受到的消息为:"+new String(messageExt.getBody()));
int reConsume = messageExt.getReconsumeTimes();
// 消息已经重试了3次,如果不需要再次消费,则返回成功
if(reConsume ==){
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
if(messageExt.getTopic().equals(paramConfigService.platTopic)){
String tags = messageExt.getTags() ;
switch (tags){
case "testTag":
LOG.info("匹配到testTag"+tags);
break ;
default:
LOG.info("未匹配到Tag == >>"+tags);
break;
}
}
// 消息消费成功
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
}

6.参数配置类:

@Service
public class ParamConfigService { @Value("${plat.plat-group}")
public String platGroup ;
@Value("${plat.plat-topic}")
public String platTopic ;
@Value("${plat.plat-tag}")
public String accountTag ;
   //省略 get set
}

7.测试类:

@RestController
public class TestController { @Autowired
private DefaultMQProducer defaultMQProducer; @Autowired
private ParamConfigService paramConfigService; @RequestMapping(value = "/testStringQueue.json", method = {RequestMethod.GET})
public SendResult testStringQueue() {
// 可以不使用Config中的Group
defaultMQProducer.setProducerGroup(paramConfigService.platGroup);
SendResult sendResult = null;
String msgInfo = "rocketmq message 1";
try {
Message sendMsg = new Message(paramConfigService.platTopic,
paramConfigService.accountTag, msgInfo.getBytes());
sendResult = defaultMQProducer.send(sendMsg);
} catch (Exception e) {
e.printStackTrace();
}
return sendResult;
}
}

  启动项目访问接口就可以看到效果。

RocketMQ安装部署及整合Springboot的更多相关文章

  1. RabbitMQ从概念到使用、从Docker安装到RabbitMQ整合Springboot【1.5w字保姆级教学】

    @ 目录 一.前言 二.RabbitMQ作用 1. 异步处理 2. 应用解耦 3. 流量控制 三.RabbitMQ概念 1. RabbitMQ简介 2. 核心概念 四.JMS与AMQP比较 五.Rab ...

  2. rocketmq安装部署过程(4.0.0版本)

    准备工作 3个虚拟机节点的构成如下 : 安装步骤 操作过程 1.安装包已经上传至其中1个节点. 2.解压缩安装包 命令:unzip rocketmq-all-4.0.0-incubating-bin- ...

  3. rocketMQ安装部署详细解析

    近来研究了Apache开源项目rocketMQ(原为阿里项目),并在两台linux服务器上完成了部署,现在整理下,供大家参考学习. 一.简介rocketMQRocektMQ是阿里巴巴在2012年开源的 ...

  4. windows下RocketMQ安装部署

    一.预备环境 1.系统 Windows 2. 环境 JDK1.8.Maven.Git 二. RocketMQ部署 1.下载 1.1地址:http://rocketmq.apache.org/relea ...

  5. RocketMQ安装部署

    一.简介RocketMQ RocektMQ是阿里巴巴在2012年开源的一个纯java.分布式.队列模型的第三代消息中间件,不仅在传统高频交易链路有着低延迟的出色表现,在实时计算等大数据领域也有着不错的 ...

  6. linux环境上 rocketmq 安装部署

    Rocketmq-简单部署   一.准备环境 1.系统:Centos7.3(无硬性要求) 2. jdk:1.8 3.maven:3.5(无硬性要求) 4.git 5.rocketmq 4.2 二.环境 ...

  7. RocketMQ安装与部署说明

    一.安装说明1.下载安装包,下载地址:https://github.com/alibaba/RocketMQ/releases/download/v3.1.7/alibaba-rocketmq-3.1 ...

  8. docker安装部署、fastDFS文件服务器搭建与springboot项目接口

    一.docker安装部署 1.更新yum包:sudo yum update 2.安装需要的软件包,yum-util 提供yum-config-manager功能,另外两个是devicemapper驱动 ...

  9. windows下RocketMQ的安装部署

    一.预备环境 1.系统 Windows 2. 环境 JDK1.8.Maven.Git 二. RocketMQ部署 1.下载 1.1地址:http://rocketmq.apache.org/relea ...

随机推荐

  1. vuex使用方法

    vuex是一个专门为vue.js设计的集中式状态管理架构.状态?我把它理解为在data中的属性需要共享给其他vue组件使用的部分,就叫做状态.简单的说就是data中需要共用的属性.比如:我们有几个页面 ...

  2. 对Moment.js的研究

    创建npm install moment --save-dev 日期格式化 moment().format('MMMM Do YYYY, h:mm:ss a'); // 六月 4日 2019, 6:2 ...

  3. 对npm的认识

    npm由三个不同的组件组成:1,网站 2.命令行界面(CLI)3.注册表 需要在网站注册 命令行界面用来进行交互 注册表来进行保存 安装本地软件包 npm install 包名 更新本地软件包 npm ...

  4. springboot自定义错误页

    静态错误页放在         动态可以放在freemaker或者thymeleaf         匹配规则: 先找动态页面再找静态页面 先找精确错误页面再找模糊页面     注:精确错误页面=50 ...

  5. C#笔试总结

    题一: 程序设计: 猫大叫一声,所有的老鼠都开始逃跑,主人被惊醒.(C#语言)要求:              <1>.构造出Cat.Mouse.Master三个类,并能使程序运行     ...

  6. Day_02-Python的分支结构和循环结构

    分支结构 应用场景 迄今为止,我们写的Python代码都是一条一条语句顺序执行,这种结构的代码我们称之为顺序结构.然而仅有顺序结构并不能解决所有的问题,比如我们设计一个游戏,游戏第一关的通关条件是玩家 ...

  7. sql2008 误操作还原至指定时间点

    --drop database db --创建一个测试库 create database db go --备份一个完整备份文件 backup database db to disk = 'd:\db. ...

  8. Android与IOS的优缺点比较 对 Android 与 IOS 比较是个个人的问题。 就好比我来说,我两个都用。我深知这两个平台的优缺点。所以,我决定分享我关于这两个移动平台的观点。另外,然后谈谈我对新的 Ubuntu 移动平台的印象和它的优势。 IOS 的优点 虽然这些天我是个十足的 Android 用户,但我必须承认 IOS 在某些方面做的是不错。首先,苹果公司在他们的设备更新方面有更

    Android与IOS的优缺点比较 对 Android 与 IOS 比较是个个人的问题. 就好比我来说,我两个都用.我深知这两个平台的优缺点.所以,我决定分享我关于这两个移动平台的观点.另外,然后谈谈 ...

  9. jsoncpp 能做什么

    jsoncpp能做什么1)跨平台跨语言动态信息数据交换.2)作为格式化配置文件使用3)对应数据结构数据类型做序列化和反序列化4)value::toStyledString 格式化json串输出 一.w ...

  10. Socket网络通信编程(二)

    1.Netty初步 2.HelloWorld 3.Netty核心技术之(TCP拆包和粘包问题) 4.Netty核心技术之(编解码技术) 5.Netty的UDP实现 6.Netty的WebSocket实 ...