from:http://blog.csdn.net/yetyongjin/article/details/7594447

近日分析Asterisk 1.8源码。Asterisk trunk上有这篇架构描述的文章,根据自己的理解整理一下,以享*友。本人英语水平有限,疏漏难免,请大家指正。听雨轩。

英文出处:http://www.asterisk.org/doxygen/trunk/AsteriskArchitecture.html#ArchInterfaceCodec

 

作者:

RussellBryant <russell@digium.com>

注意:

这篇文档所描述的内容,可能已经过时。为了保证您所获取的信息是最新的,请您确保您使用的文档是从Asterisk的trunk上生成的。

1    引言

本文档从一个开发者的角度出发,概要描述Asterisk的体系架构。至于详细的API讨论,请参考公开API头文件所关联的文档。本文档假定您了解Asterisk的一些知识,并知道如何使用它。

本文的意图是:从一个高的层次开始了解Asterisk,并逐步深入。它从Asterisk的组件差异开始,最终讨论这些组件在不同应用场景里的协作关系。

文中,提供了很多交叉引用链接,指向相关API的一些引用参考,也可能指向相关的源码链接。

欢迎对本文档的反馈和贡献。请将您的真知灼见发给asterisk开发组的邮件组:http://lists.digium.com/.

谢谢,并预祝您享受Asterisk!

2      模块构架

Asterisk是一个高度模块化的应用。在源码的main/目录下,建立了内核应用。然而,它(内核)本身的用处并不是很大。

运行时,Asterisk加载了许多模块。Asterisk的模块都有具体的名称,以标识模块所提供的功能,但是,这些名称没有任何技术意义上的特殊。Asterisk加载一个模块时,模块向内核注册它所提供的功能。整个流程看起来是这样的:

1.   启动Asterisk

2.   Asterisk加载模块

3.   模块跟内核说:嗨,Asterisk,我是一个模块,我能提供X、Y、Z三种功能,用得着的时候要记得我哦。

3    抽象接口类型

Asterisk提供了许多不同类型的接口,具体的模块可以实现这些接口并注册给内核调用。任何模块,都可以注册任意多种的接口。通常,一个模块内整合了某些相关的功能。

本节讨论接口的类型,后续将讨论各种场景下不同组件间的协作关系。

3.1   编码解释器CodecInterpreter

编码解释器接口的实现,提供了两种编码间的转换能力。Asterisk当前只有音频编码转换的能力。

这些模块不了解话务相关的任何信息,也不知为什么要调用它们进行音频转换。它们仅需要知道音频采样率、音频的输入格式、期待的输出格式这些信息。

如果注册了多个编码解释器,那么编码A转换为编码B的过程,就可能有多种不同的转换路径。在(编码)模块加载之后,Asterisk建立一张转换表,表中包含不同translator的转换开销评估值,因此,Asterisk能够找出A转换B的最佳路径。

在源码树中,编码模块通常在codecs/目录下。

已有编码解释器的实现列表,请参考:Module:Codecs

更多编码解释器API的信息,请参考接口定义文件:include/asterisk/translate.h.

内核关于编码解释器的相关实现,参考源码:main/translate.c

3.2   文件格式处理器File Format Handler

文件格式处理器接口的实现,为Asterisk提供了读写文件的能力。文件格式处理器可以提供音频、视频或图像文件的处理能力。

文件处理器的接口是相当原始的。模块简单地告诉Asterisk内核:它能处理某种具有待定扩展名的文件,比如说".wav"。同时,它还说明读取文件之后,将以编码X的形式提供音频。如果它还提供写文件的能力,那么它还必须说明用它写文件的音频编码要求(即说明它能把什么编码格式的音频编码写成带什么扩展名的文件)。

源码树中,文件格式模块通存放常在formats/目录下。

现有的实现,请参考:Module:Media File Formats

文件格式处理器的API定义信息,请参考头文件:include/asterisk/file.h

内核中,与文件格式相关的实现,请参考:main/file.c

3.3     C API Providers

Asterisk有一些可选的C API。内核API是主应用内置的,始终可用。可选CAPI则是由某个模块提供的,只有在相应模块加载后,才可用。某些API的提供方,也提供了它自身的接口,供其它模块实现和注册(接口)。

提供C API的模块,通常存放在res/目录下。

一些提供C API的模块有:

o   提供一种日历技术接口

o   提供一种语音识别引擎接口

3.4     Manager Interface (AMI) Actions

Asterisk管理接口是一个socket接口,用于监视和控制Asterisk。它是建立于主应用的核心功能。继而,其它模块可以向AMI注册自己的action供客户端调用。

向AMI注册action的模块,通常提供了某种辅助功能以补充扩展某项主要的功能(这个功能不一定是内核功能,可能是模块自身的功能)。比方说:一个提供电话会议功能的模块,提供了一个管理action接口,用于返回与会者列表。

3.5     CLI Commands

Asterisk CLI是主应用实现的命令行管理功能。外围模块可以注册附加的CLI命令。

3.6     Channel Drivers

Asterisk通道接口是最复杂、最重要的接口。Asterisk通道API提供了电话协议的抽象,这样,所有Asterisk的其它特性,才能不依赖于具体的电话协议。

通道驱动实现的具体接口是ast_channel_tech所封装的接口。一个通道驱动,必须实现执行各种呼叫信令任务的回调函数。比如说,必须实现一个初始化呼叫的方法,实现一个挂断呼叫的方法,等等。数据结构ast_channel是抽象通道数据结构。每个ast_channel实例,有一个关联的ast_channel_tech以标识通道类型。一个ast_channel实例,描述了呼叫中的一条腿(call leg的概念,也就是Asterisk与终端设备间的连接概念)。

源码中,通道驱动通常在channels/目录里。

当前实现的通道驱动列表,请参考:Module:Asterisk Channel Drivers

需要进一步了解通道API,请参考头文件include/asterisk/channel.h

内核中,关于ast_channel API的实现,则在main/channel.c中。

3.7     桥接技术

桥接,是把两个或多个通道连接在一起的操作。通道,A到B的呼叫,用的是简单的双通道桥接,而在三方通话或会议中,用的就是多方桥接技术。

桥接API允许其它模块注册桥接技术。桥接技术的实现,知道如何选择两个(或多个)通道,并将它们连接在一起。具体是怎样发生的,取决于实现。

这些接口的代码,需要在两个(或多个)通道间交换音频,却又不需要知道交换的实现细节。在底层,会议可能由操作系统内核实现(通过DAHDI);也可能由Asterisk的内部方法实现;如果有人实现了硬件扩展模块,还可能用硬件实现。

写这篇文档时,桥接API相对来说还比较新,所以执行桥接应用的操作,还没有全部使用这些API。在拨号计划应用实现里,ConfBridge是在桥接API之上实现的一个会议应用。

桥接技术实现模块,存放在bridges/目录下。

桥接技术的实现列表,请参考:bridges

桥接API的更多信息,请参考头文件:include/asterisk/bridging.hinclude/asterisk/bridging_technology.h.。

内核关于桥接技术的实现细节,请参考:main/bridging.c

3.8     CDR处理器

Asterisk内核实现了保留通话记录的功能。这些记录在呼叫处理过程中建立,并缓存在数据结构里。在通话结束时,这些数据结构将被释放。在记录丢弃之前,这些数据会传给已注册的CDR处理器。而处理器则会把记录写入文件或存入DB。

通常,CDR模块的代码存放在cdr/目录下。

CDR处理器的实现列表,请参考:Module:CDR Drivers

CDR API相关的更多信息,请参考头文件include/asterisk/cdr.h

内核中,与CDR相关的实现,请参考main/cdr.c

3.9     CEL处理器

Asterisk内核实现了一个通用的事件系统,这个系统允许Asterisk组件报告事件,订阅事件。呼叫事件记录(CEL)就是建立在事件系统之上的一个应用。

CEL和CDR有点类似,它们都跟踪通话历史记录。通常CDR记录和呼叫是一一对应的关系;而CEL事件和通话则是多对一的关系。CEL模块和CDR模块看起来很相似。

通常CEL模块存放在cel/目录下。

CEL API相关的更多信息,请参考头文件include/asterisk/cel.h

内核关于CEL API的实现细节,请参考main/cel.c

3.10拨号计划应用(APP)

app实现Asterisk拨号方案中可以与呼叫交互的功能。比如说:在extensions.conf文件中:

exten=> 123,1,NoOp()

在上例中,NoOp是一个APP。当然,实际上NoOp什么事也没做。

这些app使用Asterisk提供的一系列API与通道进行交互。App最重要的任务之一,是源源不断地从通道里读取音频,同时向通道回写音频。完成这一任务的细节,通常隐藏在一个API调用的后面,比如说播放文件或等待用户按键输入。

除了与原先执行应用的通道交互之外,APP有时还能创建额外的通道。比如说:Dial()这个APP会创建一个外呼通道,并将它与入呼通道桥接在一块。有关APP功能的进一步讨论,将在场景细节中展开。

源码中,APP的实现代码通常存放在apps/目录下。

APP的实现列表,请参考:Module:Dial plan applications

Asterisk内核注册APP相关的API定义信息,请参考头文件:include/asterisk/pbx.h

3.11   拨号计划功能(FUN)

顾名思义,FUN和APP相同,是提供给Asterisk拨号方案用的。FUN在拨号方案中的使用方式,大部分和方案中的变量相同。它们提供读/写接口,还有可选参数。虽然它们行为上和变量类似,但比起简单的文本值,APP的存储和检索要复杂得多了。

比方说:CHANNEL()这个FUN能让您访问当前通道上的数据。

exten=> 123,1,NoOp(This channel has the name: ${CHANNEL(name)})

通常,FUN的实现代码存放在funcs/目录下。

FUN的实现列表,请参考:Module:Dial plan functions

Asterisk内核注册FUN相关的API定义信息,请参考头文件:include/asterisk/pbx.h

3.12RTP引擎

Asterisk内核提供处理RTP流的API。但是,实际上处理这些流的是实现RTP引擎接口的模块。

RTP引擎的实现代码,存放在/res目录下,通常以res_rtp_为文件名前缀。

3.13   定时接口

Asterisk内核实现了定时API,供需要定时服务的组件调用。比如说,在向主叫方播放语音文件时,插入一个定时器来限定播放时间长度。这些API依赖定时接口的实现来提供稳定可靠的计时源。

通常,这些接口实现的代码可以在res/目录中找到。

定时接口实现列表,请参考:timing_interfaces

与定时API的定义信息,请参考头文件include/asterisk/timing.h

内核的定时API实现代码,请参考main/timing.c

4      Asterisk线程模型

Asterisk是一个多线程应用程序。它用POSIX线程API来管理线程和相碰的服务,比如说锁。Asterisk中,几乎所有与pthread交互相关的代码,都通过一套统一的封装实现,这样可以减少调试和代码量。

Asterisk里的线程,可以划分为以下几种类型“

  • 通道线程(有时也称为PBX线程)
  • 网络监视线程
  • 服务连接线程
  • 其它线程

4.1     通道线程

通道是Asterisk的一个基本概念。通道不是inbound的,就是outbound的。呼叫到达Asterisk系统时,创建一个inbound通道。这些通道是Asterisk拨号方案的执行方。每个执行拨号方案的通道,都建立一个线程。这些线程称为通道线程。因为这些线程的主要任务是为inbound呼叫执行Asterisk的拨号方案,所以有时也称它们为PBX线程。

一个通道线程开始只负责一个Asterisk通道。然而,有时一个通道线程里也会有第二个通道的存在。当inbound通道执行了诸如Dial()的APP之后,就在inbound线程里创建了一个outbound通道,并在对方应答之后将两个通道桥接在一起。

拨号方案的APP始终在一个通道线程的上下文里执行。FUN也是如此。虽然可以通过AMI或CLI之类的异步接口读写FUN,但无论如何,通道线程始终是ast_channel数据结构的执行主体。

4.2     网络监视线程

Asterisk中,几乎所有主要通道驱动都有网络监视线程。这些线程负责监视网络连接(无论是IP网络还是PSTN等)、入呼和其它请求。它们处理呼叫连接建立的前期步骤,如权鉴和拨号验证。最后,当呼叫建立之后,监视线程创建一个Asterisk通道 (ast_channel),并启动一个通道线程来处理余下的呼叫时间。

4.3     服务连接线程

有许多基于TCP的服务也使用线程。比如SIP和AMI。在这些场景下,用线程来处理每个 TCP连接。

Asterisk的CLI也以同样的方式操作。然而,它用的不是TCP,而是UNIX socket连接。

4.4     其它线程

系统里,存在着各种执行某项待定任务的线程。比如说:事件API(include/asterisk/event.h)使用一个内部线程(main/event.c)来处理异步事件分发。又如devicestateAPI (include/asterisk/devicestate.h)使用一个内部线程(main/devicestate.c)来处理异步的设备状态变化信息。

5      其它架构概念

本节涵盖了其它一些重要的Asterisk架构概念。

5.1     通道桥接

正如前面讨论通道技术接口时所提及的,桥接动作把一个或多个通道连接在一起,使它们之间能够彼此交换音频包。然而,前面也提到,现在的Asterisk代码中,很多地方还没有使用新的桥接架构设计。因为,本节讨论传统的桥接功能,它在Dial()和Queue()这些APP里还在使用。

当调用这些APP,决定把两个通道桥接在一起时,它执行ast_channel_bridge()API调用。从这里开始,有可能出现两种不同的桥接:

1.   通用桥接Generic Bridge:通用桥接(ast_generic_bridge())是一种与具体使用的通道技术无关的桥接方法。它通过Asterisk抽象的通道和帧接口交换音频数和信令,因此,它可以在任意两种通道驱动间通信。虽然这是最灵活的桥接方式,但同时它也是最低效的方式,因此它需要抽象层参与。

2.   本地桥接Native Bridge:通道驱动可以选择实现自己的桥接功能函数。具体说来,这意味着要实现ast_channel_tech结构中的bridge回调函数。如果被桥接双方的驱动类型相同,并且驱动程序实现了本地桥接方法,那么Asterisk没理由迫使呼叫驻留在内核处理,这时它会调用本地桥接函数。这使得通道驱动能够利用类型相同的优势,优化桥接处理。在使用DAHDI的场合中,这意味着通道在硬件层面直接桥接了。在使用SIP时,这意味着Asterisk可以让音频流直接在终端间交互,而只要求信令流经过Asterisk。

6      代码流程实例

现在,我们已经讨论了Asterisk的各种组件,本节通过实例来说明这些组件是如何协同工作,向外提供强大的功能的。

6.1     SIP呼叫到Playback

这个例子假设通过SIP协议呼入Asterisk。Asterisk接受这通呼叫,然后向呼叫方播放一个语音文件,最后挂机。

实例拨号规则:

exten => 5551212,1,Answer()
exten => 5551212,n,Playback(demo-congrats)
exten => 5551212,n,Hangup()

1.   呼叫建立:从一个SIP INVITE开始这个场景。SIP通道驱动(chan_sip.c)收到这条消息。具体地说,是chan_sip的监听线程接收并处理这条请求消息。进一步,监听消息负责完成呼叫建立的握手过程(SIP权鉴)。

2.   接受呼叫:一旦SIP通道驱动完成呼叫建立流程,它接受呼叫并启动Asterisk处理流程。为了完成这一任务,它必须先调用ast_channel_alloc()API分配一个抽象通道的实例(ast_channel)。这个通道实例暂且称之为SIP通道。SIP通道驱动负责完成SIP通道的初始化。SIP通道创建并初始化之后,创建一个通道线程来处理后续的呼叫流程(ast_pbx_start())。

3.   执行拨号方案::在通道线程的主循环中,查找对应extension的并执行。这些实现代码在main/pbx.cast_pbx_run()函数里。

4.   接听电话:一旦开始执行拨号方案,第一个执行的APP是Answer()。这个APP是一个内置APP,在main/pbx.c中实现的。Answer()的实现代码简单地调用了ast_answer()API。这个API调用直接操作ast_channel。它可以处理通常的ast_channel挂机,最终执行answer回调函数,这个回调函数关联在活跃通道的ast_channel_tech实例中。在这个场景中,最终执行的是实现chan_sip.csip_answer()函数,这个函数将按SIP规范回应一个接听信令。

5.   播放语音文件:拨号方案的下一步动作是向呼叫方播放一个语音文件。执行的是Playback()这个APP。这个APP是在apps/app_playback.c实现的。这个APP的实现代码是非常简单的。它先作参数处理,然后调用API来播放语音文件:ast_streamfile()ast_waitstream()ast_stopstream()分别对应设置文件,等待文件播放完成和释放资源这三个动作。这些API调用的一些重要操作步骤描述如下:

a.   打开文件:文件格式API负责打开语音文件的操作。它首先查找是否有以通道期待格式编码存储的文件。如果没有,它会找一个能转换成通道期待编码的文件。一旦找到,调用恰当的文件格式接口来读取文件,并将文件内容转换为Asterisk音频帧。

b.   设置转换:如果文件里的音频编码格式和通道预期格式不匹配,那么文件API将通过编码转换API来设置转换路径。转换API将调用对应的编码转换接口,以最小的开销将码流从源格式转换为目标格式。

c.   把音频发送给呼叫方:文件API将调用定时器API,以适时地将文件转换为音频帧并发送出去。与此同时,Asterisk会持续地从通道中读取处理音频包,音频包是持续实时抵达的。然而,在本例场景中,它仅是将这些包丢弃而已。

6.   挂机:Playback()这个APP执行结束之后,拨号方案继续执行下一个APP,本例中就是Hangup()。这个操作和Answer()非常相似,它处理与通道类型无关的挂机操作,然后调用SIP通道的回调接口来处理SIP规范的挂机流程。在这个点上,即使拨号方案中还有其它步骤没处理,处理也必须停止,因为通道已经被挂断了。紧接着,通道线程将退出拨号计划处理循环,并销毁ast_channel数据结构。

6.2     SIP到 IAX2 的呼叫桥接

这个例子假设外部通过SIP协议入呼到Asterisk系统,然后Asterisk通过IAX2协议发起一个outbound呼叫,对端通过IAX2应答之后,建立桥接。

实例拨号方案:

exten => 5551212,n,Dial(IAX2/mypeer)

1.   呼叫建立:从一个SIP INVITE开始这个场景。SIP通道驱动(chan_sip.c)收到这条消息。具体地说,是chan_sip的监听线程接收并处理这条请求消息。进一步,监听消息负责完成呼叫建立的握手过程(SIP权鉴)。

2.   接受呼叫:一旦SIP通道驱动完成呼叫建立流程,它接受呼叫并启动Asterisk处理流程。为了完成这一任务,它必须先调用ast_channel_alloc()API分配一个抽象通道的实例(ast_channel)。这个通道实例暂且称之为SIP通道。SIP通道驱动负责完成SIP通道的初始化。SIP通道创建并初始化之后,创建一个通道线程来处理后续的呼叫流程(ast_pbx_start())。

3.   执行拨号方案:在通道线程的主循环中,查找对应extension的并执行。这些实现代码在main/pbx.cast_pbx_run()函数里。

4.   执行 Dial():本例中,拨号方案里执行的唯一APP就是Dial():

    1. 创建一个Outbound通道: The Dial()需要创建一个outbound的ast_channel。它首先调用ast_request()API请求分配一个名为IAX2/mypeer的通道。这个API是内核通道API(include/asterisk/channel.h)的一部分。它会查找类型为IAX2的通道驱动,然后调用ast_channel_tech接口中的requeste回调函数。在这里,回调指向channels/chan_iax2.c实现的iax2_request()函数。这个函数请求IAX2通道驱动分配一个IAX2类型的ast_channel通道,并初始化它。然后Dial()为新的通道调用ast_call() API。这个API,调用ast_channel_tech接口里的call()回调函数,请求IAX2通道驱动初始化outbound呼叫。在IAX2的实现代码(channels/chan_iax2.c)里,call回调指向iax2_call()函数。

b.   等待应答:这时候Dial()开始等待outbound通道应答呼叫。与此同时,它必须持续地为inbound和outbound两个通道所接收的音频包提供服务。完成这项工作的循环体,和Asterisk的其它通道服务循环体相似。通道服务循环的核心功能就是调用ast_waitfor()等待通道帧的到来,然后调用ast_read()读取帧。

c.   处理应答:一旦远端用户接听电话,Dial()将会把这个信息反馈给inbound通道。它是通过ast_answer()这个内核通道API调用实现这个功能的。

d.   通道协调:在连接两个呼叫终端之前,Asterisk必须先协调两个通道,才能保证他们间的通话。具体地说,两个通道收发的音频编码格式可能不同。必要时,调用ast_channel_make_compatible() API来为设置每个通道的编码转换路径。

e.   桥接通道:现在,inbound和outbound通道都已经完整建立,可以连接在一块了。这个连接是建立在两个通道之间的,这样它们间可以来回地交换音频和信令,我们称之为桥接。处理桥接的API是ast_channel_bridge()。在这个例子中,桥接的处理过程是一个通用桥接,调用的是ast_generic_bridge(),通用桥接是与通道类型无关的桥接过程。如果两个桥接通道的类型不一样,那么只能用通用桥接了。桥接的核心功能是调用ast_waitfor()等待两个通道的数据。然后,如果某个通道有数据到达,则调用ast_read()读取数据帧,然后调用ast_write(),把数据帧写给另一个通道。

f.    打破桥接:桥接状态会一直持续下去,直到某个打破桥接的事件触发,跳出桥接循环体,控制权返回给Dial()应用。比如说,呼叫双方之一挂机,桥接就停止了。

5.   挂机::桥接停止之后,控制权返回给Dial()应用。因为是Dial()创建了outbound通道,所以这个通道隶属于Dial()。因此,outboundIAX2通道将在Dial()结束之前被销毁。销毁通道是通过调用ast_hangup()这个API实现的。Dial()执行结束之后,控制权返回到拨号方案执行循环体。这时,它会发现拨号方案已经执行到头了,因此,它会挂断inbound通道,同样,调用的API是ast_hangup()ast_hangup()执行一系列与通道类型无关的任务,也调用接ast_channel_tech接口里的hangup回调函数来执行与通道类型相关的任务,在本例中,调用的chan_sip模块的sip_hangup()函数。最后,通道线程自然退出。

7      Asterisk数据结构

Asterisk提供了一些数据结构的通用实现。

7.1     Astobj2

Astobj2代表Asterisk对象模型,第二版。它的API定义在头文件include/asterisk/astobj2.h中。在main/astobj2.c文件里有astobj2的实现细节。在源码树中,还保留着第一版的代码,然而我们不赞成继续使用它。

Astobj2提供引用计数对象处理。同时它还为astobj2对象提供了一套容器接口。容器提供的是一个哈希表。

关于astobj API的更多使用细节,请参考astobj2。在源码中,到处可以看到它的使用实例。

7.2     链表

Asterisk提供了一套宏,用于链表的处理。这些宏定义在头文件include/asterisk/linkedlists.h.中。

7.3     双端链表

同样的,Asterisk提供了一套宏,用于处理双端链表。这些宏在头文件include/asterisk/dlinkedlists.h.中定义。

7.4     堆Heap

Asterisk提供了一个最大堆数据结构的实现。堆相关的API定义,可以在include/asterisk/heap.h头文件中看到。堆的实现代码则在main/heap.c文件中。

8    Asterisk调试工具

Asterisk提供了一些内置的调试工具,以帮助诊断一些常见的问题。

8.1   线程调试

Asterisk保持跟踪系统中的所有活跃线程。通过AsteriskCLI,执行core showthreads命令,可以看到系统中的线程列表。

Asterisk有一个叫DEBUG_THREADS的编译选项。这个编译开关打开后,Asterisk封装的pthread API就会保持记录与线程和锁相关的一些附加信息,以帮助调试。除了线程列表之外,Asterisk还维护了系统中每个线程锁的信息。它也知道一个线程因为尝试获取一个锁资源而堵塞的信息。在调试死锁时,所有这些信息都非常有用。这些数据,可以通过Asterisk CLI,执行core show locks命令获取。

这些封装的定义信息,可以在头文件include/asterisk/lock.hinclude/asterisk/utils.h中找到。大部分实现代码都在main/utils.c里。

8.2   内存调试

Asterisk的动态内存管理,是通过一套封装的接口处理的。这些封装在头文件include/asterisk/utils.h中定义。缺省情况下,这些封装使用标准C库函数里的malloc()free(),等。如果编译时打开MALLOC_DEBUG编译开关,则会加入一些内存调试信息。

Asterisk内存调试系统提供以下几种功能:

  • 跟踪当前分配的所有内存块,包括内存初始化时的大小、文件、函数和行号。
  • 内存释放时,做一些基本的防御检查,检查内存块的写入情况。
  • 释放非法内存时,给出通知

Asterisk提供了一些CLI命令,用于查询当前内存分配状况:

  • memory show summary
  • memory show allocations

实现内存调试系统的代码文件是main/astmm.c

Asterisk 代码架构概述的更多相关文章

  1. 老李推荐: 第14章2节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-HierarchyViewer架构概述

    老李推荐: 第14章2节<MonkeyRunner源码剖析> HierarchyViewer实现原理-HierarchyViewer架构概述   HierarchyViewer库的引入让M ...

  2. Java生鲜电商平台-微服务架构概述

    Java生鲜电商平台-微服务架构概述 单体架构存在的问题 在传统的软件技术架构系统中,基本上将业务功能集中在单一应用内,或者是单一进程中.尽管现代化的软件架构理论以及设计原则已推广多年,但实际技术衍化 ...

  3. Httpd服务进阶知识-LAMP架构概述

    Httpd服务进阶知识-LAMP架构概述  作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.LAMP架构概述 1>.什么是LAM(M)P架构 L: linux A: apa ...

  4. Spring Cloud与Docker——微服务架构概述

    Spring Cloud与Docker--微服务架构概述 单体应用架构概述 微服务概述 微服务的特性 微服务架构的优点 微服务面临的挑战 微服务的设计原则 单体应用架构概述 传统的服务发布都是采用单体 ...

  5. netty系列之:netty架构概述

    目录 简介 netty架构图 丰富的Buffer数据机构 零拷贝 统一的API 事件驱动 其他优秀的特性 总结 简介 Netty为什么这么优秀,它在JDK本身的NIO基础上又做了什么改进呢?它的架构和 ...

  6. 【转】Android bluetooth介绍(二): android blueZ蓝牙代码架构及其uart 到rfcomm流程

    原文网址:http://blog.sina.com.cn/s/blog_602c72c50102uzoj.html 关键词:蓝牙blueZ  UART  HCI_UART H4  HCI  L2CAP ...

  7. MySQL逻辑架构概述

    1.MySQL逻辑架构 MySQL逻辑架构图 MySQL逻辑架构分四层 1.连接层:主要完成一些类似连接处理,授权认证及相关的安全方案. 2.服务层:在 MySQL据库系统处理底层数据之前的所有工作都 ...

  8. Scrapy架构概述

    Scrapy架构概述 1, 从最初自己编写的spiders,获取到start_url,并且封装成Request对象. 2,通过engine(引擎)调度给SCHEDULER(Requests管理调度器) ...

  9. 大型互联网架构概述 关于架构的架构目标 典型实现 DNS CDN LB WEB APP SOA MQ CACHE STORAGE

    大型互联网架构概述 目录 架构目标 典型实现 DNS CDN LB WEB APP SOA MQ CACHE STORAGE 本文旨在简单介绍大型互联网的架构和核心组件实现原理. 理论上讲,从安装配置 ...

随机推荐

  1. html常用标签详解5-表格标签

    表格标签(如果有不对的,请大家多多指正.谢谢!) 1.总的表格标签概览 <table><!--表格标签start--> <caption></caption& ...

  2. (转载)My97 datepicker使用指南

    这里先显示大家都可以看到的My97DatePicker的用法: WdatePicker日历控件使用方法(http://www.cnblogs.com/yuhanzhong/archive/2011/0 ...

  3. 【Redis安装】部署与基本配置 --基于Mac和Linux

    Redis安装与部署[基于Mac和Linux] 一.Redis简介 基于内存的Key-Value高性能NoSQL数据库 二.Redis下载和解压 进入官网下载最新版的Redis,目前是5.0.0,这个 ...

  4. compareTo)--list 根据某字段排序

    Collections.sort(actList, new Comparator<Act>() { @Override public int compare(Act o1, Act o2) ...

  5. OCP/OCA Oracle 学习001

    select * from TEST3 t select object_type, count(object_type) from user_objects group by object_type ...

  6. ubuntu 环境下向GitHub上传(push)每次都需要用户名密码问题

    这里使用的系统环境是ubuntu16.04,通过Git向GitHub仓库pull/push,使用https方式每次都需要输入用户名和密码,是解决此问题的方法. 一.应该确保你的系统上已经安装了Git ...

  7. js 表格合并

    1.合并 function autoRowSpan(tbid, row, col) { var tb = document.getElementById(tbid); var lastValue = ...

  8. hdu 5823 color II——子集dp(独立集)

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=5823 独立集染一种颜色.在这个基础上枚举子集来dp. 状压一样地存边真是美妙. 2^32是1ll<& ...

  9. loj2322 「清华集训 2017」Hello world!

    https://loj.ac/problem/2322 先吐槽一下,sb数据毁我青春败我前程. 首先,一个数开根开不了多少次. 当我们把它开到1的时候,我们以后就不需要开他了,我们可以利用并查集跳过他 ...

  10. Sublime keymap 个性修改

    [ // 保存全部 {"keys": ["ctrl+alt+s"], "command": "save_all"}, / ...