分类 五花八门 下的文章

要开发出用户满意的软件并不是件容易的事,软件架构师必须全面把握各种各样的需求、权衡需求之间有可能的矛盾之处,分门别类地将不同需求一一满足。本文从理解需求种类的复杂性谈起,通过具体案例的分析,展示了如何通过RUP的4+1视图方法,针对不同需求进行架构设计,从而确保重要的需求一一被满足。

呼唤架构设计的多重视图方法

灵感一闪,就想出了把大象放进冰箱的办法,这自然好。但希望每个架构设计策略都依靠灵感是不现实的--我们需要系统方法的指导。

需要架构设计的多重视图方法,从根本上来说是因为需求种类的复杂性所致。以工程领域的例子开道吧。比如设计一座跨江大桥:我们会考虑"连接南北的公路交通"这个"功能需求",从而初步设计出理想化的桥墩支撑的公路桥方案;然后还要考虑造桥要面临的"约束条件",这个约束条件可能是"不能影响万吨轮从桥下通过",于是细化设计方案,规定桥墩的高度和桥墩之间的间距;另外还要顾及"大桥的使用期质量属性",比如为了"能在湍急的江流中保持稳固",可以把大桥桥墩深深地建在岩石层之上,和大地浑然一体;其实,"建造期间的质量属性"也很值得考虑,比如在大桥的设计过程中考虑"施工方便性"的一些措施。

和工程领域的功能需求、约束条件、使用期质量属性、建造期间的质量属性等类似,软件系统的需求种类也相当复杂,具体分类如图1所示。
2014122621.jpg

超市系统案例:理解需求种类的复杂性

例子是最好的老师。为了更好地理解软件需求种类的复杂性,我们来分析一个实际的例子。在表1中,我们列举了一个典型的超市系统的需求子集,从这个例子中可以清晰地看到需求可以分为两大类:功能需求和非功能需求。
2014122622.jpg
简单而言,功能需求就是"软件有什么用,软件需要做什么"。同时,注意把握功能需求的层次性是软件需求的最佳实践。以该超市系统为例:
超市老板希望通过软件来"提高收银效率"。

那么,你可能需要为收银员提供一系列功能来促成这个目的,比如供收银员使用的"任意商品项可单独取消"功能有利于提供收银效率(笔者曾在超市有过被迫整单取消然后一车商品重新扫描收费的痛苦经历)。

而具体到这个超市系统,系统分析员可能会决定要提供的具体功能为:通过收银终端的按键组合,可以使收银过程从"逐项录入状态"进入"选择取消状态",从而取消某项商品。

从上面的例子中我们还惊讶地发现,非功能需求--人们最经常忽视的一大类需求--包括的内容非常宽、并且极其重要。非功能需求又可以分为如下三类:

约束。要开发出用户满意的软件并不是件容易的事,而全面理解要设计的软件系统所面临的约束可以使你向成功迈进一步。约束性需求既包括企业级的商业考虑(例如"项目预算有限"),也包括最终用户级的实际情况(例如"用户的平均电脑操作水平偏低");既可能包括具体技术的明确要求(例如"要求能在Linux上运行"),又可能需要考虑开发团队的真实状况(例如"开发人员分散在不同地点")。这些约束性需求当然对架构设计影响很大,比如受到"项目预算有限"的限制,架构师就不应选择昂贵的技术或中间件等,而考虑到开发人员分散在不同地点",就更应注重软件模块职责划分的合理性、松耦合性等等。

运行期质量属性。这类需求主要指软件系统在运行期间表现出的质量水平。运行期质量属性非常关键,因为它们直接影响着客户对软件系统的满意度,大多数客户也不会接受运行期质量属性拙劣的软件系统。常见的运行期质量属性包括软件系统的易用性、性能、可伸缩性、持续可用性、鲁棒性、安全性等。在我们的超市系统的案例中,用户对高性能提出了具体要求(真正的性能需求应该量化,我们的表1没体现),他们不能容忍金额合计超过2秒的延时。

开发期质量属性。这类非功能需求中的某些项人们倒是念念不忘,可惜很多人并没有意识到"开发期质量属性"和"运行期质量属性"对架构设计的影响到底有何不同。开发期质量属性是开发人员最为关心的,要达到怎样的目标应根据项目的具体情况而定,而过度设计(overengineering)会花费额外的代价。

什么是软件架构视图

那么,什么是软件架构视图呢?Philippe Kruchten在其著作《Rational统一过程引论》中写道:

一个架构视图是对于从某一视角或某一点上看到的系统所做的简化描述,描述中涵盖了系统的某一特定方面,而省略了于此方面无关的实体。
也就是说,架构要涵盖的内容和决策太多了,超过了人脑"一蹴而就"的能力范围,因此采用"分而治之"的办法从不同视角分别设计;同时,也为软件架构的理解、交流和归档提供了方便。

值得特别说明的,大多数书籍中都强调多视图方法是软件架构归档的方法,其实不然。多视图方法不仅仅是架构归档技术,更是指导我们进行架构设计的思维方法。

Philippe Kruchten提出的4+1视图方法

1995年,Philippe Kruchten在《IEEE Software》上发表了题为《The 4+1 View Model of Architecture》的论文,引起了业界的极大关注,并最终被RUP采纳。如图2所示。
2014122623.jpg
该方法的不同架构视图承载不同的架构设计决策,支持不同的目标和用途:

逻辑视图:当采用面向对象的设计方法时,逻辑视图即对象模型。

开发视图:描述软件在开发环境下的静态组织。

处理视图:描述系统的并发和同步方面的设计。

物理视图:描述软件如何映射到硬件,反映系统在分布方面的设计。

运用4+1视图方法:针对不同需求进行架构设计

如前文所述,要开发出用户满意的软件并不是件容易的事,软件架构师必须全面把握各种各样的需求、权衡需求之间有可能的矛盾之处,分门别类地将不同需求一一满足。

Philippe Kruchten提出的4+1视图方法为软件架构师"一一征服需求"提供了良好基础,如图3所示。
2014122624.jpg
逻辑视图。逻辑视图关注功能,不仅包括用户可见的功能,还包括为实现用户功能而必须提供的"辅助功能模块";它们可能是逻辑层、功能模块等。

开发视图。开发视图关注程序包,不仅包括要编写的源程序,还包括可以直接使用的第三方SDK和现成框架、类库,以及开发的系统将运行于其上的系统软件或中间件。开发视图和逻辑视图之间可能存在一定的映射关系:比如逻辑层一般会映射到多个程序包等。

处理视图。处理视图关注进程、线程、对象等运行时概念,以及相关的并发、同步、通信等问题。处理视图和开发视图的关系:开发视图一般偏重程序包在编译时期的静态依赖关系,而这些程序运行起来之后会表现为对象、线程、进程,处理视图比较关注的正是这些运行时单元的交互问题。
物理视图。物理视图关注"目标程序及其依赖的运行库和系统软件"最终如何安装或部署到物理机器,以及如何部署机器和网络来配合软件系统的可靠性、可伸缩性等要求。物理视图和处理视图的关系:处理视图特别关注目标程序的动态执行情况,而物理视图重视目标程序的静态位置问题;物理视图是综合考虑软件系统和整个IT系统相互影响的架构视图。

设备调试系统案例概述

本文的以下部分,将研究一个案例:某型号设备调试系统。

设备调试员通过使用该系统,可以察看设备状态(设备的状态信息由专用的数据采集器实时采集)、发送调试命令。该系统的用例图如图4所示。
2014122625.jpg
经过研制方和委托方的紧密配合,最终确定的需求可以总括地用表2来表示。
2014122626.jpg
下面运用RUP推荐的4+1视图方法,从不同视图进行架构设计,来分门别类地将不同需求一一满足。

逻辑视图:设计满足功能需求的架构

首先根据功能需求进行初步设计,进行大粒度的职责划分。如图5所示。

应用层负责设备状态的显示,并提供模拟控制台供用户发送调试命令。

应用层使用通讯层和嵌入层进行交互,但应用层不知道通讯的细节。

通讯层负责在RS232协议之上实现一套专用的"应用协议"。

当应用层发送来包含调试指令的协议包,由通讯层负责按RS232协议将之传递给嵌入层。

当嵌入层发送来原始数据,由通讯层将之解释成应用协议包发送给应用层。

嵌入层负责对调试设备的具体控制,以及高频度地从数据采集器读取设备状态数据。

设备控制指令的物理规格被封装在嵌入层内部,读取数采器的具体细节也被封装在嵌入层内部。
2014122627.jpg

开发视图:设计满足开发期质量属性的架构

软件架构的开发视图应当为开发人员提供切实的指导。任何影响全局的设计决策都应由架构设计来完成,这些决策如果"漏"到了后边,最终到了大规模并行开发阶段才发现,可能造成"程序员碰头儿临时决定"的情况大量出现,软件质量必然将下降甚至导致项目失败。

其中,采用哪些现成框架、哪些第三方SDK、乃至哪些中间件平台,都应该考虑是否由软件架构的开发视图确定下来。图6展示了设备调试系统的(一部分)软件架构开发视图:应用层将基于MFC设计实现,而通讯层采用了某串口通讯的第三方SDK。
2014122628.jpg
在说说约束性需求。约束应该是每个架构视图都应该关注和遵守的一些设计限制。例如,考虑到"一部分开发人员没有嵌入式开发经验"这条约束情况,架构师有必要明确说明系统的目标程序是如何编译而来的:图7展示了整个系统的桌面部分的目标程序pc-moduel.exe、以及嵌入式模块rom-module.hex是如何编译而来的。这个全局性的描述无疑对没有经验的开发人员提供了实感,利于更全面地理解系统的软件架构。
2014122629.jpg
处理视图:设计满足运行期质量属性的架构

性能是软件系统运行期间所表现出的一种质量水平,一般用系统响应时间和系统吞吐量来衡量。为了达到高性能的要求,软件架构师应当针对软件的运行时情况进行分析与设计,这就是我们所谓的软件架构的处理视图的目标。处理视图关注进程、线程、对象等运行时概念,以及相关的并发、同步、通信等问题。图8展示了设备调试系统架构的处理视图。

可以看出,架构师为了满足高性能需求,采用了多线程的设计:

应用层中的线程代表主程序的运行,它直接利用了MFC的主窗口线程。无论是用户交互,还是串口的数据到达,均采取异步事件的方式处理,杜绝了任何"忙等待"无谓的耗时,也缩短了系统响应时间。

通讯层有独立的线程控制着"上上下下"的数据,并设置了数据缓冲区,使数据的接收和数据的处理相对独立,从而数据接收不会因暂时的处理忙碌而停滞,增加了系统吞吐量。

嵌入层的设计中,分别通过时钟中断和RS232口中断来激发相应的处理逻辑,达到轮询和收发数据的目的。
20141226210.jpg

物理视图:和部署相关的架构决策

软件最终要驻留、安装或部署到硬件才能运行,而软件架构的物理视图关注"目标程序及其依赖的运行库和系统软件"最终如何安装或部署到物理机器,以及如何部署机器和网络来配合软件系统的可靠性、可伸缩性等要求。图9所示的物理架构视图表达了设备调试系统软件和硬件的映射关系。可以看出,嵌入部分驻留在调试机中(调试机是专用单板机),而PC机上是常见的桌面可执行程序的形式。
20141226211.jpg
我们还可能根据具体情况的需要,通过物理架构视图更明确地表达具体目标模块及其通讯结构,如图10所示。
20141226212.jpg
小结与说明

所谓本立道生。深入理解软件需求分类的复杂性,明确区分功能需求、约束、运行期质量属性、开发期质量属性等不同种类的需求就是"本",因为各类需求对架构设计的影响截然不同。本文通过具体案例的分析,展示了如何通过RUP的4+1视图方法,针对不同需求进行架构设计,从而确保重要的需求一一被满足。

本文重点在于方法的解说,因此省略了对架构设计中不少具体问题的说明,同时本文提供的说明架构设计方案的模型也经过了简化。请读者注意。

HDFS是一个不错的分布式文件系统,它有很多的优点,但也存在有一些缺点。目前而言,它在以下几个方面就效率不佳:
 

  1. 低延时访问

  HDFS不太适合于那些要求低延时(数十毫秒)访问的应用程序,因为HDFS是设计用于大吞吐量数据的,这是以一定延时为代价的。HDFS是单Master的,所有的对文件的请求都要经过它,当请求多时,肯定会有延时。当前,对于那些有低延时要求的应用程序,HBase是一个更好的选择。现在HBase的版本是0.20,相对于以前的版本,在性能上有了很大的提升,它的口号就是goes real time。
  使用缓存或多master设计可以降低client的数据请求压力,以减少延时。还有就是对HDFS系统内部的修改,这就得权衡大吞吐量与低延时了,HDFS不是万能的银弹。
  

  1. 大量小文件

   因为Namenode把文件系统的元数据放置在内存中,所以文件系统所能容纳的文件数目是由Namenode的内存大小来决定。一般来说,每一个文件、 文件夹和Block需要占据150字节左右的空间,所以,如果你有100万个文件,每一个占据一个Block,你就至少需要300MB内存。当前来说,数 百万的文件还是可行的,当扩展到数十亿时,对于当前的硬件水平来说就没法实现了。还有一个问题就 是,因为Map task的数量是由splits来决定的,所以用MR处理大量的小文件时,就会产生过多的Map task,线程管理开销将会增加作业时间。举个例子,处理10000M的文件,若每个split为1M,那就会有10000个Map tasks,会有很大的线程开销;若每个split为100M,则只有100个Map tasks,每个Map task将会有更多的事情做,而线程的管理开销也将减小很多。
  要想让HDFS能处理好小文件,有不少方法:
  1、利用SequenceFile、MapFile、Har等方式归档小文件,这个方法的原理就是把小文件归档起来管理,HBase就是基于此的。对于这种方法,如果想找回原来的小文件内容,那就必须得知道与归档文件的映射关系。
  2、横向扩展,一个Hadoop集群能管理的小文件有限,那就把几个Hadoop集群拖在一个虚拟服务器后面,形成一个大的Hadoop集群。google也是这么干过的。
  3、多Master设计,这个作用显而易见了。正在研发中的GFS II也要改为分布式多Master设计,还支持Master的Failover,而且Block大小改为1M,有意要调优处理小文件啊。
附带个Alibaba DFS的设计,也是多Master设计,它把Metadata的映射存储和管理分开了,由多个Metadata存储节点和一个查询Master节点组成。
多用户写,任意文件修改
  目前Hadoop只支持单用户写,不支持并发多用户写。可以使用Append操作在文件的末尾添加数据,但不支持在文件的任意位置进行修改。这些特性可能会在将来的版本中加入,但是这些特性的加入将会降低Hadoop的效率,就拿GFS来说吧,这篇文章里就说了google自己的人都用着Multiple Writers很不爽。
  利用Chubby、ZooKeeper之类的分布式协调服务来解决一致性问题。

大数据技术从业人员必读
一、Hadoop相关工具

  1. Hadoop
    Apache的Hadoop项目已几乎与大数据划上了等号。它不断壮大起来,已成为一个完整的生态系统,众多开源工具面向高度扩展的分布式计算。

支持的操作系统:Windows、Linux和OS X。
相关链接:http://hadoop.apache.org

  1. Ambari
    作为Hadoop生态系统的一部分,这个Apache项目提供了基于Web的直观界面,可用于配置、管理和监控Hadoop集群。有些开发人员想把Ambari的功能整合到自己的应用程序当中,Ambari也为他们提供了充分利用REST(代表性状态传输协议)的API。

支持的操作系统:Windows、Linux和OS X。
相关链接:http://ambari.apache.org

  1. Avro
    这个Apache项目提供了数据序列化系统,拥有丰富的数据结构和紧凑格式。模式用JSON来定义,它很容易与动态语言整合起来。

支持的操作系统:与操作系统无关。
相关链接:http://avro.apache.org

  1. Cascading
    Cascading是一款基于Hadoop的应用程序开发平台。提供商业支持和培训服务。

支持的操作系统:与操作系统无关。
相关链接:http://www.cascading.org/projects/cascading/

  1. Chukwa
    Chukwa基于Hadoop,可以收集来自大型分布式系统的数据,用于监控。它还含有用于分析和显示数据的工具。

支持的操作系统:Linux和OS X。
相关链接:http://chukwa.apache.org

  1. Flume
    Flume可以从其他应用程序收集日志数据,然后将这些数据送入到Hadoop。官方网站声称:“它功能强大、具有容错性,还拥有可以调整优化的可靠性机制和许多故障切换及恢复机制。”

支持的操作系统:Linux和OS X。
相关链接:https://cwiki.apache.org/confluence/display/FLUME/Home

  1. HBase
    HBase是为有数十亿行和数百万列的超大表设计的,这是一种分布式数据库,可以对大数据进行随机性的实时读取/写入访问。它有点类似谷歌的Bigtable,不过基于Hadoop和Hadoop分布式文件系统(HDFS)而建。

支持的操作系统:与操作系统无关。
相关链接:http://hbase.apache.org

  1. Hadoop分布式文件系统(HDFS)
    HDFS是面向Hadoop的文件系统,不过它也可以用作一种独立的分布式文件系统。它基于Java,具有容错性、高度扩展性和高度配置性。

支持的操作系统:Windows、Linux和OS X。
相关链接:https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-hdfs/HdfsUserGuide.html

  1. Hive
    Apache Hive是面向Hadoop生态系统的数据仓库。它让用户可以使用HiveQL查询和管理大数据,这是一种类似SQL的语言。

支持的操作系统:与操作系统无关。
相关链接:http://hive.apache.org

  1. Hivemall
    Hivemall结合了面向Hive的多种机器学习算法。它包括诸多高度扩展性算法,可用于数据分类、递归、推荐、k最近邻、异常检测和特征哈希。

支持的操作系统:与操作系统无关。
相关链接:https://github.com/myui/hivemall

  1. Mahout
    据官方网站声称,Mahout项目的目的是“为迅速构建可扩展、高性能的机器学习应用程序打造一个环境。”它包括用于在Hadoop MapReduce上进行数据挖掘的众多算法,还包括一些面向Scala和Spark环境的新颖算法。

支持的操作系统:与操作系统无关。
相关链接:http://mahout.apache.org

  1. MapReduce
    作为Hadoop一个不可或缺的部分,MapReduce这种编程模型为处理大型分布式数据集提供了一种方法。它最初是由谷歌开发的,但现在也被本文介绍的另外几个大数据工具所使用,包括CouchDB、MongoDB和Riak。

支持的操作系统:与操作系统无关。
相关链接:http://hadoop.apache.org/docs/current/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html

  1. Oozie
    这种工作流程调度工具是为了管理Hadoop任务而专门设计的。它能够按照时间或按照数据可用情况触发任务,并与MapReduce、Pig、Hive、Sqoop及其他许多相关工具整合起来。

支持的操作系统:Linux和OS X。
相关链接:http://oozie.apache.org

  1. Pig
    Apache Pig是一种面向分布式大数据分析的平台。它依赖一种名为Pig Latin的编程语言,拥有简化的并行编程、优化和可扩展性等优点。

支持的操作系统:与操作系统无关。
相关链接:http://pig.apache.org

  1. Sqoop
    企业经常需要在关系数据库与Hadoop之间传输数据,而Sqoop就是能完成这项任务的一款工具。它可以将数据导入到Hive或HBase,并从Hadoop导出到关系数据库管理系统(RDBMS)。

支持的操作系统:与操作系统无关。
相关链接:http://sqoop.apache.org

  1. Spark
    作为MapReduce之外的一种选择,Spark是一种数据处理引擎。它声称,用在内存中时,其速度比MapReduce最多快100倍;用在磁盘上时,其速度比MapReduce最多快10倍。它可以与Hadoop和Apache Mesos一起使用,也可以独立使用。

支持的操作系统:Windows、Linux和OS X。
相关链接:http://spark.apache.org

  1. Tez
    Tez建立在Apache Hadoop YARN的基础上,这是“一种应用程序框架,允许为任务构建一种复杂的有向无环图,以便处理数据。”它让Hive和Pig可以简化复杂的任务,而这些任务原本需要多个步骤才能完成。

支持的操作系统:Windows、Linux和OS X。
相关链接:http://tez.apache.org

  1. Zookeeper
    这种大数据管理工具自称是“一项集中式服务,可用于维护配置信息、命名、提供分布式同步以及提供群组服务。”它让Hadoop集群里面的节点可以彼此协调。

支持的操作系统:Linux、Windows(只适合开发环境)和OS X(只适合开发环境)。
相关链接:http://zookeeper.apache.org

二、大数据分析平台和工具

  1. Disco
    Disco最初由诺基亚开发,这是一种分布式计算框架,与Hadoop一样,它也基于MapReduce。它包括一种分布式文件系统以及支持数十亿个键和值的数据库。

支持的操作系统:Linux和OS X。
相关链接:http://discoproject.org

  1. HPCC
    作为Hadoop之外的一种选择,HPCC这种大数据平台承诺速度非常快,扩展性超强。除了免费社区版外,HPCC Systems还提供收费的企业版、收费模块、培训、咨询及其他服务。

支持的操作系统:Linux。
相关链接:http://hpccsystems.com

  1. Lumify
    Lumify归Altamira科技公司(以国家安全技术而闻名)所有,这是一种开源大数据整合、分析和可视化平台。你只要在Try.Lumify.io试一下演示版,就能看看它的实际效果。

支持的操作系统:Linux。
相关链接:http://www.jboss.org/infinispan.html

  1. Pandas
    Pandas项目包括基于Python编程语言的数据结构和数据分析工具。它让企业组织可以将Python用作R之外的一种选择,用于大数据分析项目。

支持的操作系统:Windows、Linux和OS X。
相关链接:http://pandas.pydata.org

  1. Storm
    Storm现在是一个Apache项目,它提供了实时处理大数据的功能(不像Hadoop只提供批任务处理)。其用户包括推特、美国天气频道、WebMD、阿里巴巴、Yelp、雅虎日本、Spotify、Group、Flipboard及其他许多公司。

支持的操作系统:Linux。
相关链接:https://storm.apache.org

三、数据库/数据仓库

  1. Blazegraph
    Blazegraph之前名为“Bigdata”,这是一种高度扩展、高性能的数据库。它既有使用开源许可证的版本,也有使用商业许可证的版本。

支持的操作系统:与操作系统无关。
相关链接:http://www.systap.com/bigdata

  1. Cassandra
    这种NoSQL数据库最初由Facebook开发,现已被1500多家企业组织使用,包括苹果、欧洲原子核研究组织(CERN)、康卡斯特、电子港湾、GitHub、GoDaddy、Hulu、Instagram、Intuit、Netfilx、Reddit及其他机构。它能支持超大规模集群;比如说,苹果部署的Cassandra系统就包括75000多个节点,拥有的数据量超过10 PB。

支持的操作系统:与操作系统无关。
相关链接:http://cassandra.apache.org

  1. CouchDB
    CouchDB号称是“一款完全拥抱互联网的数据库”,它将数据存储在JSON文档中,这种文档可以通过Web浏览器来查询,并且用JavaScript来处理。它易于使用,在分布式上网络上具有高可用性和高扩展性。

支持的操作系统:Windows、Linux、OS X和安卓。
相关链接:http://couchdb.apache.org

  1. FlockDB
    由推特开发的FlockDB是一种非常快、扩展性非常好的图形数据库,擅长存储社交网络数据。虽然它仍可用于下载,但是这个项目的开源版已有一段时间没有更新了。

支持的操作系统:与操作系统无关。
相关链接:https://github.com/twitter/flockdb

  1. Hibari
    这个基于Erlang的项目自称是“一种分布式有序键值存储系统,保证拥有很强的一致性”。它最初是由Gemini Mobile Technologies开发的,现在已被欧洲和亚洲的几家电信运营商所使用。

支持的操作系统:与操作系统无关。
相关链接:http://hibari.github.io/hibari-doc/

  1. Hypertable
    Hypertable是一种与Hadoop兼容的大数据数据库,承诺性能超高,其用户包括电子港湾、百度、高朋、Yelp及另外许多互联网公司。提供商业支持服务。

支持的操作系统:Linux和OS X。
相关链接:http://hypertable.org

  1. Impala
    Cloudera声称,基于SQL的Impala数据库是“面向Apache Hadoop的领先的开源分析数据库”。它可以作为一款独立产品来下载,又是Cloudera的商业大数据产品的一部分。

支持的操作系统:Linux和OS X。
相关链接:http://www.cloudera.com/content/cloudera/en/products-and-services/cdh/impala.html

  1. InfoBright社区版
    InfoBright为数据分析而设计,这是一种面向列的数据库,具有很高的压缩比。InfoBright.com提供基于同一代码的收费产品,提供支持服务。

支持的操作系统:Windows和Linux。
相关链接:http://www.infobright.org

  1. MongoDB
    mongoDB的下载量已超过1000万人次,这是一种极其受欢迎的NoSQL数据库。MongoDB.com上提供了企业版、支持、培训及相关产品和服务。

支持的操作系统:Windows、Linux、OS X和Solaris。
相关链接:http://www.mongodb.org

  1. Neo4j
    Neo4j自称是“速度最快、扩展性最佳的原生图形数据库”,它承诺具有大规模扩展性、快速的密码查询性能和经过改进的开发效率。用户包括电子港湾、必能宝(Pitney Bowes)、沃尔玛、德国汉莎航空公司和CrunchBase。

支持的操作系统:Windows和Linux。
相关链接:http://neo4j.org

  1. OrientDB
    这款多模型数据库结合了图形数据库的一些功能和文档数据库的一些功能。提供收费支持、培训和咨询等服务。

支持的操作系统:与操作系统无关。
相关链接:http://www.orientdb.org/index.htm

  1. Pivotal Greenplum Database
    Pivotal声称,Greenplum是“同类中最佳的企业级分析数据库”,能够非常快速地对庞大的海量数据进行功能强大的分析。它是Pivotal大数据库套件的一部分。

支持的操作系统:Windows、Linux和OS X。
相关链接:http://pivotal.io/big-data/pivotal-greenplum-database

  1. Riak
    Riak“功能完备”,有两个版本:KV是分布式NoSQL数据库,S2提供了面向云环境的对象存储。它既有开源版,也有商业版,还有支持Spark、Redis和Solr的附件。

支持的操作系统:Linux和OS X。
相关链接:http://basho.com/riak-0-10-is-full-of-great-stuff/

  1. Redis
    Redis现在由Pivotal赞助,这是一种键值缓存和存储系统。提供收费支持。要注意:虽然该项目并不正式支持Windows,不过微软在GitHub上有一个Windows派生版。

支持的操作系统:Linux。
相关链接:http://redis.io

四、商业智能

  1. Talend Open Studio
    Talend的下载量已超过200万人次,其开源软件提供了数据整合功能。该公司还开发收费的大数据、云、数据整合、应用程序整合和主数据管理等工具。其用户包括美国国际集团(AIG)、康卡斯特、电子港湾、通用电气、三星、Ticketmaster和韦里逊等企业组织。

支持的操作系统:Windows、Linux和OS X。
相关链接:http://www.talend.com/index.php

  1. Jaspersoft
    Jaspersoft提供了灵活、可嵌入的商业智能工具,用户包括众多企业组织:高朋、冠群科技、美国农业部、爱立信、时代华纳有线电视、奥林匹克钢铁、内斯拉斯加大学和通用动力公司。除了开源社区版外,它还提供收费的报表版、亚马逊网络服务(AWS)版、专业版和企业版。

支持的操作系统:与操作系统无关。
相关链接:http://www.jaspersoft.com

  1. Pentaho
    Pentaho归日立数据系统公司所有,它提供了一系列数据整合和业务分析工具。官方网站上提供了三个社区版;访问Pentaho.com,即可了解收费支持版方面的信息。

支持的操作系统:Windows、Linux和OS X。
相关链接:http://community.pentaho.com

  1. SpagoBI
    Spago被市场分析师们称为“开源领袖”,它提供商业智能、中间件和质量保证软件,另外还提供Java EE应用程序开发框架。该软件百分之分免费、开源,不过也提供收费的支持、咨询、培训及其他服务。

支持的操作系统:与操作系统无关。
相关链接:http://www.spagoworld.org/xwiki/bin/view/SpagoWorld/

  1. KNIME
    KNIME的全称是“康斯坦茨信息挖掘工具”(Konstanz Information Miner),这是一种开源分析和报表平台。提供了几个商业和开源扩展件,以增强其功能。

支持的操作系统:Windows、Linux和OS X。
相关链接:http://www.knime.org

  1. BIRT
    BIRT的全称是“商业智能和报表工具”。它提供的一种平台可用于制作可以嵌入到应用程序和网站中的可视化元素及报表。它是Eclipse社区的一部分,得到了Actuate、IBM和Innovent Solutions的支持。

支持的操作系统:与操作系统无关。
相关链接:http://www.eclipse.org/birt/

五、数据挖掘

44.DataMelt
作为jHepWork的后续者,DataMelt可以处理数学运算、数据挖掘、统计分析和数据可视化等任务。它支持Java及相关的编程语言,包括Jython、Groovy、JRuby和Beanshell。
支持的操作系统:与操作系统无关。
相关链接:http://jwork.org/dmelt/

  1. KEEL
    KEEL的全称是“基于进化学习的知识提取”,这是一种基于Java的机器学习工具,为一系列大数据任务提供了算法。它还有助于评估算法在处理递归、分类、集群、模式挖掘及类似任务时的效果。

支持的操作系统:与操作系统无关。
相关链接:http://keel.es

  1. Orange
    Orange认为数据挖掘应该是“硕果累累、妙趣横生”,无论你是有多年的丰富经验,还是刚开始接触这个领域。它提供了可视化编程和Python脚本工具,可用于数据可视化和分析。

支持的操作系统:Windows、Linux和OS X。
相关链接:http://orange.biolab.si

  1. RapidMiner
    RapidMiner声称拥有250000多个用户,包括贝宝、德勤、电子港湾、思科和大众。它提供一系列广泛的开源版和收费版,不过要注意:免费的开源版只支持CSV格式或Excel格式的数据。

支持的操作系统:与操作系统无关。
相关链接:https://rapidminer.com

  1. Rattle
    Rattle的全称是“易学易用的R分析工具”。它为R编程语言提供了一种图形化界面,简化了这些过程:构建数据的统计或可视化摘要、构建模型以及执行数据转换。

支持的操作系统:Windows、Linux和OS X。
相关链接:http://rattle.togaware.com

  1. SPMF
    SPMF现在包括93种算法,可用于顺序模式挖掘、关联规则挖掘、项集挖掘、顺序规则挖掘和集群。它可以独立使用,也可以整合到其他基于Java的程序中。

支持的操作系统:与操作系统无关。
相关链接:http://www.philippe-fournier-viger.com/spmf/

  1. Weka
    怀卡托知识分析环境(Weka)是一组基于Java的机器学习算法,面向数据挖掘。它可以执行数据预处理、分类、递归、集群、关联规则和可视化。

支持的操作系统:Windows、Linux和OS X。
相关链接:http://www.cs.waikato.ac.nz/~ml/weka/

六、查询引擎

  1. Drill
    这个Apache项目让用户可以使用基于SQL的查询,查询Hadoop、NoSQL数据库和云存储服务。它可用于数据挖掘和即席查询,它支持一系列广泛的数据库,包括HBase、MongoDB、MapR-DB、HDFS、MapR-FS、亚马逊S3、Azure Blob Storage、谷歌云存储和Swift。

支持的操作系统:Windows、Linux和OS X。
相关链接:http://drill.apache.org

七、编程语言

  1. R
    R类似S语言和环境,旨在处理统计计算和图形。它包括一套整合的大数据工具,可用于数据处理、计算和可视化。

支持的操作系统:Windows、Linux和OS X。
相关链接:http://www.r-project.org

  1. ECL
    企业控制语言(ECL)是开发人员用来在HPCC平台上构建大数据应用程序的语言。HPCC Systems官方网站上有集成开发环境(IDE)、教程以及处理该语言的众多相关工具。

支持的操作系统:Linux。
相关链接:http://hpccsystems.com/download/docs/ecl-language-reference

八、大数据搜索

  1. Lucene
    基于Java的Lucene可以非常迅速地执行全文搜索。据官方网站声称,它在现代硬件上每小时能够检索超过150GB的数据,它含有强大而高效的搜索算法。开发工作得到了Apache软件基金会的赞助。

支持的操作系统:与操作系统无关。
相关链接:http://lucene.apache.org/core/

  1. Solr
    Solr基于Apache Lucene,是一种高度可靠、高度扩展的企业搜索平台。知名用户包括eHarmony、西尔斯、StubHub、Zappos、百思买、AT&T、Instagram、Netflix、彭博社和Travelocity。

支持的操作系统:与操作系统无关。
相关链接:http://lucene.apache.org/solr/

九、内存中技术

  1. Ignite
    这个Apache项目自称是“一种高性能、整合式、分布式的内存中平台,可用于对大规模数据集执行实时计算和处理,速度比传统的基于磁盘的技术或闪存技术高出好几个数量级。”该平台包括数据网格、计算网格、服务网格、流媒体、Hadoop加速、高级集群、文件系统、消息传递、事件和数据结构等功能。

支持的操作系统:与操作系统无关。
相关链接:https://ignite.incubator.apache.org

  1. Terracotta
    Terracotta声称其BigMemory技术是“世界上数一数二的内存中数据管理平台”,声称拥有210万开发人员,250家企业组织部署了其软件。该公司还提供商业版软件,另外提供支持、咨询和培训等服务。

支持的操作系统:与操作系统无关。
相关链接:http://www.terracotta.org

  1. Pivotal GemFire/Geode
    今年早些时候,Pivotal宣布它将开放其大数据套件关键组件的源代码,其中包括GemFire内存中NoSQL数据库。它已向Apache软件基金会递交了一项提案,以便在“Geode”的名下管理GemFire数据库的核心引擎。还提供该软件的商业版。

支持的操作系统:Windows和Linux。
相关链接:http://pivotal.io/big-data/pivotal-gemfire

  1. GridGain
    由Apache Ignite驱动的GridGrain提供内存中数据结构,用于迅速处理大数据,还提供基于同一技术的Hadoop加速器。它既有收费的企业版,也有免费的社区版,后者包括免费的基本支持。

支持的操作系统:Windows、Linux和OS X。
相关链接:http://www.gridgain.com

  1. Infinispan
    作为一个红帽JBoss项目,基于Java的Infinispan是一种分布式内存中数据网格。它可以用作缓存、用作高性能NoSQL数据库,或者为诸多框架添加集群功能。

支持的操作系统:与操作系统无关。

昨天无意看到群里讨论一个名为 MicroChat 基于Mars的微信Android通讯协议客户端代码!!
Github地址:https://github.com/InfiniteTsukuyomi/MicroChat/ (已消失)
声明:
本文仅提供Mars学习交流参考,请勿用于非法用途,否则后果自负!!!
本人才疏学浅,文章参考了部分资料,欢迎各位指正,共同交流学习!
感谢原作者:原作2位大神的神作

编译准备:
开发环境:Visual Studio 2015 及以上版本,部分测试VS13编译通过
抓包工具 Wireshark
分析工具 tcpdump
依赖:Mars / Duilib / SQLite3
主要项目 MicroChatSDK.dll
UI层 MicroChat (DUILIB)
使用前的准备配置
/MicroChatSDK/Business/define.h
//测试请手动修改登录设备信息
//登录设备硬件信息

define DEVICE_INFO_GUID "A31d2152a33d83e7" //GUID

define DEVICE_INFO_CLIENT_SEQID "A31cc712ad2d83e6_1512965043210" //GUID_LOCATION地址

define DEVICE_INFO_CLIENT_SEQID_SIGN "e89b238e77cf988ebd09eb65f5378e99" //MD5

define DEVICE_INFO_IMEI "865167123366678" //手机IMEI

define DEVICE_INFO_ANDROID_ID "eabe1f220561a49f" //设备ID

define DEVICE_INFO_ANDROID_VER "android-26" //安卓版本

define DEVICE_INFO_MANUFACTURER CStringA2Utf8("iPhone") //设备名称 随便填

define DEVICE_INFO_MODELNAME CStringA2Utf8("X") //型号名称 随便填

define DEVICE_INFO_MOBILE_WIFI_MAC_ADDRESS "01:67:33:56:78:11" //WIFI MAC地址

define DEVICE_INFO_AP_BSSID "41:25:99:22:3f:14" //手机信号基站 MAC地址

define DEVICE_INFO_LANGUAGE "zh_CN" //语言

//下面2个是设备 com.tencent.mm 包信息 及 设备信息(使用上面宏)

define DEVICE_INFO_SOFTINFO "<softtype><lctmoc>0</lctmoc><level>1</level><k1>ARMv7 processor rev 1 (v7l) </k1><k2></k2><k3>5.1.1</k3><k4>%s</k4><k5>460007337766541</k5><k6>89860012221746527381</k6><k7>%s</k7><k8>unknown</k8><k9>%s</k9><k10>2</k10><k11>placeholder</k11><k12>0001</k12><k13>0000000000000001</k13><k14>%s</k14><k15></k15><k16>neon vfp swp half thumb fastmult edsp vfpv3 idiva idivt</k16><k18>%s</k18><k21>"wireless"</k21><k22></k22><k24>%s</k24><k26>0</k26><k30>"wireless"</k30><k33>com.tencent.mm</k33><k34>Android-x86/android_x86/x86:5.1.1/LMY48Z/denglibo08021647:userdebug/test-keys</k34><k35>vivo v3</k35><k36>unknown</k36><k37>%s</k37><k38>x86</k38><k39>android_x86</k39><k40>%s</k40><k41>1</k41><k42>%s</k42><k43>null</k43><k44>0</k44><k45></k45><k46></k46><k47>wifi</k47><k48>%s</k48><k49>/data/data/com.tencent.mm/</k49><k52>0</k52><k53>0</k53><k57>1080</k57><k58></k58><k59>0</k59></softtype>"

define DEVICE_INFO_DEVICEINFO "<deviceinfo><MANUFACTURER name="%s"><MODEL name=%s"><VERSION_RELEASE name="5.1.1"><VERSION_INCREMENTAL name="eng.denglibo.20171224.164708"><DISPLAY name="android_x86-userdebug 5.1.1 LMY48Z eng.denglibo.20171224.164708 test-keys"></DISPLAY></VERSION_INCREMENTAL></VERSION_RELEASE></MODEL></MANUFACTURER></deviceinfo>"

LOGIN_RSA_VER //秘钥版本
LOGIN_RSA_VER158_KEY_E //秘钥加密
LOGIN_RSA_VER158_KEY_N //混淆后??

微信功能请求

define CGI_NEWSYNC "/cgi-bin/micromsg-bin/newsync" //同步服务端最新消息

define CGI_MANUALAUTH "/cgi-bin/micromsg-bin/manualauth" //登录

define CGI_NEWSENDMSG "/cgi-bin/micromsg-bin/newsendmsg" //发送文字消息

define CGI_NEWINIT "/cgi-bin/micromsg-bin/newinit" //首次登录,初始化数据库

define CGI_GETPROFILE "/cgi-bin/micromsg-bin/getprofile" //获取个人信息

define CGI_SEARCHCONTACT "/cgi-bin/micromsg-bin/searchcontact" //搜索新朋友

define CGI_GETCONTACT "/cgi-bin/micromsg-bin/getcontact" //查找新朋友

define CGI_VERIFYUSER "/cgi-bin/micromsg-bin/verifyuser" //添加好友

define CGI_BIND "/cgi-bin/micromsg-bin/bindopmobileforreg" //首次登录短信授权

比如可以添加
findnearby , getmoment , 等~

/MicroChatSDK/Business/AuthInfo.h

pragma once

include <string>

include "db/db.h"

class CAuthInfo
{
public:

CAuthInfo()
{
    InitializeCriticalSection(&m_cs_syncKey);
}

string    m_UserName; //昵称
string    m_WxId;  //wxid 或 老微信号
DWORD   m_uin = 0;   //uin 唯一标识
string    m_Alias;     //微信号
string    m_Session;    //SessionKey
DWORD   m_ClientVersion;   //客户端版本
string  m_guid_15;    //guid 15位
string  m_guid;       //guid
string  m_androidVer;  //安卓版本
string  m_launguage;   //lang
string  m_cookie;      //置入浏览器的Cookie

string GetSyncKey();
void SetSyncKey(string strSyncKey);
static CAuthInfo *GetInstance();

//获取短信验证码凭据
string m_mobilecode_authticket;
//接受短信号码(当前默认使用登录账号)
string m_mobileNum;

private:

static CAuthInfo * m_Instance;

CRITICAL_SECTION   m_cs_syncKey;

};

define pAuthInfo (CAuthInfo::GetInstance())

编译常见错误:
BOOST错误 重新引用 mars/boost
MARS 的引用错误,重新引用 mars mars/comm mars/boost 等文件夹
DUILIB编译错误 参考 github.com/duilib
SQLITE3编译 需要修改一次 编译为 DLL & LIB
LIB缺失 手动引用..
VS13及以下版本会有部分语法错误!!请使用VS15及以上版本!!!

一.微信协议概览

OK,我们言归正传。先看下对微信通讯的研究
远程端口有: 80 443 8080 5222 5223 5228 这几个
特定域名:
support.weixin.qq.com 80/8080
short.weixin.qq.com 443/8080
long.weixin.qq.com 80/443
wx.qlogo.cn 80

微信网络行为

1.程序启动后,优先尝试DNS解析特定域名(support.weixin.qq.com,short.weixin.qq.com,long.weixin.qq.com,wx.qlogo.cn);
2.如果DNS查询不可用,程序转为使用hardcode的ip链接服务;
3.如果dns可用,返回的ip为根据ISP智能解析的结果,程序使用返回的ip链接服务;
4.程序仅在注册阶段使用https链接,内容不详;
5.程序使用tcp 80/8080链接服务器,其中80为http协议,8080为未知协议;
6.80/8080两个端口同时或任何单独一个,均可提供服务;
7.80端口为短链接,8080为长链接, 程序会优先使用8080端口;
8.没有使用udp传输数据;
9.当1-2次发送失败时,客户端会弹出提示“当前网络状况不好,是否提交反馈数据”,确认后客户端试图通过web提交反馈数据;

dns查询
dns.weixin.qq.com
返回一组IP地址long.weixin.qq.com
返回一组IP地址,本次通信中,微信使用了最后一个IP作为TCP长连接的连接地址。
http://dns.weixin.qq.com/cgi-bin/micromsg-bin/newgetdns?uin=0&clientversion=620888113&scene=0&net=1
用于请求服务器获得最优IP路径。服务器通过结算返回一个xml定义了域名:IP对应列表。
仔细阅读,可看到微信已经开始了国际化的步伐:香港、加拿大、韩国等。
具体文本,请参考:https://gist.github.com/yongboy/9341884
获取到long.weixin.qq.com最优IP,然后建立到101.227.131.105的TCP长连接
POST http://short.weixin.qq.com/cgi-bin/micromsg-bin/getprofile HTTP/1.1 (application/octet-stream)
返回一个名为“micromsgresp.dat”的附件,估计是未阅读的离线消息
POST http://short.weixin.qq.com/cgi-bin/micromsg-bin/whatsnews HTTP/1.1 (application/octet-stream)
大概是资讯、订阅更新等
GET http://wx.qlogo.cn/mmhead/Q3auHgzwzM7NR4TYFcoNjbxZpfO9aiaE7RU5lXGUw13SMicL6iacWIf2A/96
图片等一些静态资源都会被分配到wx.qlogo.cn域名下面
下载缓存
POST http://short.weixin.qq.com/cgi-bin/micromsg-bin/downloadpackage HTTP/1.1 (application/octet-stream)
输出为micromsgresp.dat文件
GET http://support.weixin.qq.com/cgi-bin/mmsupport-bin/reportdevice?channel=34&deviceid=A952001f7a840c2a&clientversion=620888113&platform=0&lang=zh_CN&installtype=0 HTTP/1.1
返回chunked分块数据
POST http://short.weixin.qq.com/cgi-bin/micromsg-bin/reportstrategy HTTP/1.1 (application/octet-stream)

  1. 心跳频率约为5分钟
    上次使用Wireshark分析有误(得出18分钟结论),再次重新分析,心跳频率在5分钟左右。
  2. 登陆之后,会建立一个长连接,端口号为8080
    简单目测为HTTP,初始以为是双通道HTTP,难道是自定义的用于双通道通信的HTTP协议吗,网络上可见资料都是模棱两可、语焉不详。

具体查看长连接初始数据通信,没有发现任何包含"HTTP"字样的数据,以为是微信自定义的TCP/HTTP通信格式。据分析,用于可能用于获取数据、心跳交换消息等用途吧。这个后面会详谈微信是如何做到的。

2.0 初始消息传输
个人资料、离线未阅读消息部分等通过 POST HTTP短连接单独获取。

2.1 二进制简单分析
抽取微信某次HTTP协议方式通信数据,16进制表示,每两个靠近的数字为一个byte字节:

二.微信协议分析

微信协议可能如下:

一个消息包 = 消息头 + 消息体
消息头固定16字节长度,消息包长度定义在消息头前4个字节中。

单纯摘取第0000行为例,共16个字节的头部:

00 00 00 10 00 10 00 01 00 00 00 06 00 00 00 0f
16进制表示,每两个紧挨着数字代表一个byte字节。

微信消息包格式: 1. 前4字节表示数据包长度,可变 值为16时,意味着一个仅仅包含头部的完整的数据包(可能表示着预先定义好的业务意义),后面可能还有会别的消息包 2. 2个字节表示头部长度,固定值,0x10 = 16 3. 2个字节表示谢意版本,固定值,0x01 = 1 4. 4个字节操作说明数字,可变 5. 序列号,可变 6. 头部后面紧跟着消息体,非明文,加密形式 7. 一个消息包,最小16 byte字节

通过上图(以及其它数据多次采样)分析:

0000 - 0040为单独的数据包
0050行为下一个数据包的头部,前四个字节值为0xca = 202,表示包含了从0050-0110共202个字节数据
一次数据发送,可能包含若干子数据包
换行符n,16进制表示为0x0a,在00f0行,包含了两个换行符号
一个数据体换行符号用于更细粒度的业务数据分割 是否蒙对,需要问问做微信协议的同学
所有被标记为HTTP协议通信所发送数据都包含换行符号
2.2 动手试试猜想,模拟微信TCP长连接
开始很不解为什么会出现如此怪异的HTTP双通道长连接请求,难道基于TCP通信,然后做了一些手脚?很常规的TCP长连接,传输数据时(不是所有数据传输),被wireshark误认为HTTP长连接。这个需要做一个实验证实一下自己想法,设想如下:

写一个Ping-Pong客户端、服务器端程序,然后使用Wireshark看一下结果,是否符合判断。

2014-03-03_15h07_30_thumb.png
服务端:https://gist.githubusercontent.com/yongboy/9341037/raw/pong_server.c

/**
  • nieyong@youku.com
  • how to compile it:
  • gcc pong_server.c -o pong_server /usr/local/lib/libev.a -lm
    */

include <arpa/inet.h>

include <stdlib.h>

include <stdio.h>

include <string.h>

include <fcntl.h>

include <errno.h>

include <err.h>

include <unistd.h>

include "../include/ev.h"

static int server_port = 8080;

struct ev_loop *loop;
typedef struct {

int fd;
ev_io ev_read;

} client_t;

ev_io ev_accept;

static void free_res(struct ev_loop loop, ev_io ws);

int setnonblock(int fd) {

int flags = fcntl(fd, F_GETFL);
if (flags < 0)
    return flags;

flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0)
    return -1;

return 0;

}

static void read_cb(struct ev_loop loop, ev_io w, int revents) {

client_t *client = w->data;
int r = 0;
char rbuff[1024];
if (revents & EV_READ) {
    r = read(client->fd, &rbuff, 1024);
}

if (EV_ERROR & revents) {
    fprintf(stderr, "error event in read\n");
    free_res(loop, w);
    return ;
}

if (r < 0) {
    fprintf(stderr, "read error\n");
    ev_io_stop(EV_A_ w);
    free_res(loop, w);
    return;
}

if (r == 0) {
    fprintf(stderr, "client disconnected.\n");
    ev_io_stop(EV_A_ w);
    free_res(loop, w);
    return;
}

send(client->fd, rbuff, r, 0);

}

static void accept_cb(struct ev_loop loop, ev_io w, int revents) {

struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(w->fd, (struct sockaddr *) &client_addr, &client_len);
if (client_fd == -1) {
    fprintf(stderr, "the client_fd is  NULL !\n");
    return;
}

client_t *client = malloc(sizeof(client_t));
client->fd = client_fd;
if (setnonblock(client->fd) < 0)
    err(1, "failed to set client socket to non-blocking");

client->ev_read.data = client;

ev_io_init(&client->ev_read, read_cb, client->fd, EV_READ);
ev_io_start(loop, &client->ev_read);

}

int main(int argc, char const *argv[]) {

int ch;
while ((ch = getopt(argc, argv, "p:")) != -1) {
    switch (ch) {
    case 'p':
        server_port = atoi(optarg);
        break;
    }
}

loop = ev_default_loop(0);
struct sockaddr_in listen_addr;
int reuseaddr_on = 1;
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0)
    err(1, "listen failed");
if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_on, sizeof(reuseaddr_on)) == -1)
    err(1, "setsockopt failed");

memset(&listen_addr, 0, sizeof(listen_addr));
listen_addr.sin_family = AF_INET;
listen_addr.sin_addr.s_addr = INADDR_ANY;
listen_addr.sin_port = htons(server_port);

if (bind(listen_fd, (struct sockaddr *) &listen_addr, sizeof(listen_addr)) < 0)
    err(1, "bind failed");
if (listen(listen_fd, 5) < 0)
    err(1, "listen failed");
if (setnonblock(listen_fd) < 0)
    err(1, "failed to set server socket to non-blocking");

ev_io_init(&ev_accept, accept_cb, listen_fd, EV_READ);
ev_io_start(loop, &ev_accept);
ev_loop(loop, 0);

return 0;

}

static void free_res(struct ev_loop loop, ev_io w) {

client_t *client = w->data;
if (client == NULL) {
    fprintf(stderr, "the client is NULL !!!!!!");
    return;
}

ev_io_stop(loop, &client->ev_read);
close(client->fd);
free(client);

}

客户端代码 : https://gist.githubusercontent.com/yongboy/9319660/raw/PingClient.java

/**
  • Ping Client
  • @author nieyong
    */

package com.learn;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
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.NioSocketChannel;

import java.util.concurrent.TimeUnit;

class PingClientHandler extends ChannelInboundHandlerAdapter {

private final ByteBuf firstMessage;

public PingClientHandler() {
    firstMessage = PooledByteBufAllocator.DEFAULT.buffer(22);

    // weixin 16 byte's header
    firstMessage.writeByte(0);
    firstMessage.writeByte(0);
    firstMessage.writeByte(0);
    firstMessage.writeByte(16);

    firstMessage.writeByte(0);
    firstMessage.writeByte(16);

    firstMessage.writeByte(0);
    firstMessage.writeByte(1);

    firstMessage.writeByte(0);
    firstMessage.writeByte(0);
    firstMessage.writeByte(0);
    firstMessage.writeByte(6);

    firstMessage.writeByte(0);
    firstMessage.writeByte(0);
    firstMessage.writeByte(0);
    firstMessage.writeByte(1);

    // just for /n
    firstMessage.writeByte('\n'); // 1 byte

    // footer 16 byte
    String welcome = "hello"; // 5 byte
    firstMessage.writeBytes(welcome.getBytes());
}

@Override
public void channelActive(ChannelHandlerContext ctx) {
    ctx.writeAndFlush(firstMessage);
}

@Override
public void channelRead(final ChannelHandlerContext ctx, final Object msg)
        throws Exception {
    ctx.executor().schedule(new Runnable() {
        @Override
        public void run() {
            ctx.channel().writeAndFlush(msg);
        }
    }, 1, TimeUnit.SECONDS);
}

@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
    ctx.flush();
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
    System.err.println("Unexpected exception from downstream :"
            + cause.getMessage());
    ctx.close();
}

}

public class PingClient {

private final String host;
private final int port;

public PingClient(String host, int port) {
    this.host = host;
    this.port = port;
}

public void run() throws Exception {
    EventLoopGroup group = new NioEventLoopGroup();
    try {
        Bootstrap b = new Bootstrap();
        b.group(group).channel(NioSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY, true)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch)
                            throws Exception {
                        ch.pipeline().addLast(new PingClientHandler());
                    }
                });

        ChannelFuture f = b.connect(host, port).sync();

        f.channel().closeFuture().sync();
    } finally {
        // Shut down the event loop to terminate all threads.
        group.shutdownGracefully();
    }
}

public static void main(String[] args) throws Exception {
    String host = "127.0.0.1";
    int port = 8080;

    if (args.length == 3) {
        host = args[0];
        port = Integer.parseInt(args[1]);
    }

    new PingClient(host, port).run();
}

}

2014-03-03_14h53_19_thumb.png
结论是什么呢?
若使用原始TCP进行双向通信,则需要满足以下条件,可以被类似于Wireshark协议拦截器误认为是HTTP长连接:

1.使用80/8080端口(81/3128/8000经测试无效) 也许8080一般被作为WEB代理服务端口,微信才会享用这个红利吧。
输出的内容中,一定要包含换行字符"n"
2.因此,可以定性为微信使用了基于8080端口TCP长连接,一旦数据包中含有换行"n"符号,就会被Wireshark误认为HTTP协议。可能微信是无心为之吧。

  1. 新消息获取方式
    1.TCP长连接接收到服务器通知有新消息需要获取

    1. APP发起一个HTTP POST请求获取新状态消息,会带上当前SyncKey 地址为:http://short.weixin.qq.com/cgi-bin/micromsg-bin/reportstrategy HTTP/1.1,看不到明文
      3.APP获取到新的消息,会再次发起一次HTTP POST请求,告诉服务器已确认收到,同时获取最新SyncKey 地址为:http://short.weixin.qq.com/cgi-bin/micromsg-bin/kvreport,看不到明文

4.接受一个消息,TCP长连接至少交互两次,客户端发起两次HTTP POST请求 具体每次交互内容是什么,有些模糊
服务器需要支持:状态消息获取标记,状态消息确认收取标记。只有被确认收到,此状态消息才算是被正确消费掉
5.多个不同设备同一账号同时使用微信,同一个状态消息会会被同时分发到多个设备上
此时消息请求截图如下:
2014-03-03_15h58_15_thumb.png

  1. 发送消息方式
    发送消息走已经建立的TCP长连接通道,发送消息到服务器,然后接受确认信息等,产生一次交互。

小伙伴接收到信息阅读也都会收到服务器端通知,产生一次交互等。

可以确定,微信发送消息走TCP长连接方式,因为不对自身状态数据产生影响,应该不交换SyncKey。

在低速网络下,大概会看到消息发送中的提示,属于消息重发机制
网络不好有时客户端会出现发送失败的红色感叹号
已发送到服务器但未收到确认的消息,客户端显示红色感叹号,再次重发,服务器作为重复消息处理,反馈确认
上传图片,会根据图片大小,分割成若干部分(大概1.5K被划分为一部分),同一时间点,客户端会发起若干次POST请求,各自上传成功之后,服务器大概会合并成一个完整图片,返回一个缩略图,显示在APP聊天窗口内。APP作为常规的文字消息发送到服务器端
上传音频,则单独走TCP通道,一个两秒的录制音频,客户端录制完毕,分为两块传输,一块最大1.5K左右,服务端响应一条数据通知确认收到。共三次数据传输。音频和纯文字信息一致,都是走TCP长连接,客户端发送,服务器端确认。

四.微信协议小结

1.发布的消息对应一个ID(只要单个方向唯一即可,服务器端可能会根ID判断重复接收),消息重传机制确保有限次的重试,重试失败给予用户提示,发送成功会反馈确认,客户端只有收到确认信息才知道发送成功。发送消息可能不会产生新SyncKey。
2.基于版本号(SynKey)的状态消息同步机制,增量、有序传输需求水到渠成。长连接通知/短连接获取、确认等,交互方式简单,确保了消息可靠谱、准确无误到达。
3.客户端/服务器端都会存储消息ID处理记录,避免被重复消费客户端获取最新消息,但未确认,服务器端不会认为该消息被消费掉。下次客户端会重新获取,会查询当前消息是否被处理过。根据一些现象猜测。
4.总体上看,微信协议跨平台(TCP或HTPP都可呈现,处理方式可统一),通过“握手”同步,很可靠,无论哪一个平台都可以支持的很好
5.微信协议最小成本为16字节,大部分时间若干个消息包和在一起,批量传输。微信协议说不上最简洁,也不是最节省流量,但是非常成功的。
6.若服务器检测到一些不确定因素,可能会导致微启用安全套接层SSL协议进行常规的TCP长连接传输。短连接都没有发生变化

以上,根据有限资料和数据拦截观察总结得出,啰啰嗦嗦,勉强凑成一篇,会存在一些不正确之处,欢迎给予纠正。在多次

五.附录

Microsoft Exchange Active Sync协议,简称EAS,分为folderrsync(同步文件夹目录,即邮箱内有哪几个文件夹)和sync(每个文件夹内有哪些文档)两部分。

某网友总结的协议一次回话大致示范:
Client: synckey=0 //第一次key为0
Server: newsynckey=1235434 //第一次返回新key
Client: synckey=1235434 //使用新key查询
Server: newsynckey=1647645,data=*//第一次查询,得到新key和数据
Client: synckey=1647645
Server: newsynckey=5637535,data=null //第二次查询,无新消息
Client: synckey=5637535
Server: newsynckey=8654542, data=**//第三次查询,增量同步
上页中的相邻请求都是隔固定时间的,如两分钟
客户端每次使用旧key标记自己的状态,服务端每次将新key和增量数据一起返回。
key是递增的,但不要求连续
请求的某个参数决定服务器是否立即返回

1.编译问题,如果还没有编译成功的同学可以参考我分享的这个Release版本 (为防止黑产泛滥 不便公开修正过的代码 需要的可以问群里其他同学要)
MicroChat Release_vc141d
2.编译过程中需要安装ATL boost库支持
3.mars 依赖编译顺序 > MicroChatSDK > MicroChat
需要设置 VC++目录
..mars
..marscomm
..marscommwindows
..marsopensslinclude
..marsopensslincludeopenssl
..sqlite3
protoprotobuf

或填写附加包含目录
$(ProjectDir);$(ProjectDir)WTL;$(ProjectDir)../;$(ProjectDir)../mars;$(ProjectDir)../mars/comm/windows;$(ProjectDir)../mars/comm;$(ProjectDir)proto/protobuf
测试登录,手机版也可以看到设备 假iphoneX (android 26)
20180111172743.jpg
20180111172802.jpg
登录后,手机客户端就提示掉线了
20180111172814.jpg
20180111172823.jpg
发消息测试
20180111172832-768x326.png
收消息测试
20180111172842.png
其他类型消息 以XML方式发送,需要下载可通过 res.wx.qq.com / qpic.cn /wx.qlogo.cn 等下载...
20180111172850.png
测试加载好友列表
20180111172900-1024x677.png
20180111172909-1024x579.png
登录验证
20180111172946.png
登录
20180111172957-768x503.jpg
20180111173002.jpg