转自:http://wangkuiwu.github.io/2014/09/01/Binder-Introduce/

1. Binder架构解析

1.1 Binder模型

上图中涉及到Binder模型的4类角色:Binder驱动ServiceManagerServerClient。 因为后面章节讲解Binder时,都是以MediaPlayerService和MediaPlayer为代表进行讲解的;这里就使用MediaPlayerService代表了Server,而MediaPlayer则代表了Client。

Binder机制的目的是实现IPC(Inter-Process Communication),即实现进程间通信。在上图中,由于MediaPlayerService是Server的代表,而MediaPlayer是Client的代表;因此,对于上图而言,Binder机制则表现为"实现MediaPlayerService和MediaPlayer之间的通信"。

1.2 Binder驱动存在的原因和意义

在回答"Binder机制中Binder驱动存在的原因和意义"之前,先介绍几个基本的概念。

1. Linux系统中的内存划分

Android是基于Linux内核而打造的操作系统。
以32位Linux系统而言,它的内存最大是4G。在这4G内存中,0~3G为用户空间,3~4G为内核空间。应用程序都运行在用户空间,而Kernel
和驱动都运行在内核空间。用户空间和内核空间若涉及到通信(即,数据交互),两者不能简单地使用指针传递数据,而必须在"内核"中通过
copy_from_user(),copy_to_user(),get_user()或put_user()等函数传递数据。
copy_from_user()和get_user()是将内核空间的数据拷贝到用户空间,而copy_to_user()和put_user()则是
将用户空间的数据拷贝到内核空间。

2. 进程的基本概念

进程拥有独立的内存单元,它是系统进行资源分配和调度的基本单位。对于Linux系统而言,每一个运行在用户空间的应用程序都可以看作一个进程。
不同的进程在不同的内存中,因此当一个程序崩溃之后,不会对其它的程序造成影响。

通过上面的"Linux的内存划分"和"进程",我们可以了解到:应用程序都运行在用户空间,每个应用程序都有它自己独立的内存空间;若不同的应用程序之间涉及到通信,需要通过内核进行中转,因为需要用到内核的copy_from_user()和copy_to_user()等函数。
现在,再回到上面的框架图中。图中的ServiceManager,
MediaPlayerService和MediaPlayer都位于用户空间,它们是不同的进程。前面说过,Binder机制的最终目的是实
现"MediaPlayerService和MediaPlayer这两个不同进程之间的通信"。而这两个不同进程的通信必须要内核进行中转,对于
Android而言,在内核中起中转作用便是Binder驱动。那么Binder驱动是如何进行数据中转的呢?这里概括的介绍一下,后面再详细说明。
Android的通信是基于Client-Server架构的,进程间的通信无非就是Client向Server发起请求,Server响应Client
的请求。这里以发起请求为例:当Client向Server发起请求(例如,MediaPlayer向MediaPlayerService发起请
求),Client会先将请求数据从用户空间拷贝到内核空间(将数据从MediaPlayer发给Binder驱动);数据被拷贝到内核空间之后,再通过
驱动程序,将内核空间中的数据拷贝到Server位于用户空间的缓存中(Binder驱动将数据发给MediaPlayerService)。这样,就成
功的将Client进程中的请求数据传递到了Server进程中。

实际上,Binder驱动是整个Binder机制的核心。除了实现上面所说的数据传输之外,Binder驱动还是实现线程控制(通过中断等待队列实现线程的等待/唤醒),以及UID/PID等安全机制的保证。

1.3 ServiceManager存在的原因和意义

Binder是要实现Android的C-S架构的,即Client-Server架构。而ServiceManager,是以服务管理者的身份存在的。

ServiceManager也是运行在用户空间的一个独立进程。
(01) 对于Binder驱动而言,ServiceManager是一个守护进程,更是Android系统各个服务的管理者。Android系统中的各个服务,都是添加到ServiceManager中进行管理的,而且每个服务都对应一个服务名。当Client获取某个服务时,则通过服务名来从ServiceManager中获取相应的服务。
(02) 对于MediaPlayerService和MediaPlayer而言,ServiceManager是一个Server服务端,是一个服务器
当要将MediaPlayerService等服务添加到ServiceManager中进行管理时,ServiceManager是服务器,它会收到
MediaPlayerService进程的添加服务请求。当MediaPlayer等客户端要获取MediaPlayerService等服务时,它会
向ServiceManager发起获取服务请求。

当MediaPlayer和MediaPlayerService通信时,MediaPlayerService是服务端;而当
MediaPlayerService则ServiceManager通信时,ServiceManager则是服务端。这样,就造就了
ServiceManager的特殊性。于是,在Binder驱动中,将句柄0指定为ServiceManager对应的句柄,通过这个特殊的句柄就能获
取ServiceManager对象。这部分的知识后面会详细介绍。

1.4 为什么采用Binder机制,而不是其他的IPC通信方式

前面说过,Android是在Linux内核的基础上设计的。而在Linux中,已经拥有"管道/消息队列/共享内存/信号量/Socket等等"众多的IPC通信手段;但是,Google为什么单单选择了Binder,而不是其它的IPC机制呢?

这肯定是因为Binder具有无可比拟的优势。下面就从 "实用性(Client-Server架构)/传输效率/操作复杂度/安全性" 等几方面进行分析。

第一. Binder能够很好的实现Client-Server架构

对于Android系统,Google想提供一套基于Client-Server的通信方式。
例如,将"电池信息/马达控制/wifi信息/多媒体服务"等等不同的服务都由不同的Server提供,当Client需要获取某Server的服务时,
只需要Client向Server发送相应的请求,Server收到请求之后进行处理,处理完毕再将反馈内容发送给Client。

但是,目前Linux支持的"传统的管道/消息队列/共享内存/信号量/Socket等"IPC通信手段中,只有Socket是Client-Server的通信方式。但是,Socket主要用于网络间通信以及本机中进程间的低速通信,它的传输效率太低。

第二. Binder的传输效率和可操作性很好

前面已经说了,Socket传输效率很低,已经被排除。而消息队列和管道又采用存储-转发方式,使用它们进行IPC通信时,需要经过2次内存拷贝!效率太低!

为什么消息队列和管道的数据传输需要经过2次内存拷贝呢?
首先,数据先从发送方的缓存区(即,Linux中的用户存储空间)拷贝到内核开辟的缓存区(即,Linux中的内核存储空间)中,是第1次拷贝。接着,再
从内核缓存区拷贝到接收方的缓存区(也是Linux中的用户存储空间),这是第2次拷贝。
而采用Binder机制的话,则只需要经过1次内存拷贝即可! 即,从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到同一块物理地址的,因此只需要1次拷贝即可。

至于共享内存呢,虽然使用它进行IPC通信时进行的内存拷贝次数是0。但是,共享内存操作复杂,也将它排除。

第三. Binder机制的安全性很高

传统IPC没有任何安全措施,完全依赖上层协议来确保。传统IPC的接收方无法获得对方进程可靠的UID/PID(用户ID/进程ID),从而无法
鉴别对方身份。而Binder机制则为每个进程分配了UID/PID来作为鉴别身份的标示,并且在Binder通信时会根据UID/PID进行有效性检
测。

1.5 Binder中各角色之间关系

先看看下面的关系图

在解释上面的图之前,先解释图中涉及到的几个非常重要的概念。

1. Binder实体

Binder实体,是各个Server以及ServiceManager在内核中的存在形式。

Binder实体实际上是内核中binder_node结构体的对象,它的作用是在内核中保存Server和ServiceManager的信息(例
如,Binder实体中保存了Server对象在用户空间的地址)。简言之,Binder实体是Server在Binder驱动中的存在形式,内核通过
Binder实体可以找到用户空间的Server对象。
在上图中,Server和ServiceManager在Binder驱动中都对应的存在一个Binder实体。

2. Binder引用

说到Binder实体,就不得不说"Binder引用"。所谓Binder引用,实际上是内核中binder_ref结构体的对象,它的作用是在表
示"Binder实体"的引用。换句话说,每一个Binder引用都是某一个Binder实体的引用,通过Binder引用可以在内核中找到它对应的
Binder实体。

如果将Server看作是Binder实体的话,那么Client就好比Binder引用。Client要和Server通信,它就是通过保存一个
Server对象的Binder引用,再通过该Binder引用在内核中找到对应的Binder实体,进而找到Server对象,然后将通信内容发送给
Server对象。

Binder实体和Binder引用都是内核(即,Binder驱动)中的数据结构。每一个Server在内核中就表现为一个Binder实体,而每一个
Client则表现为一个Binder引用。这样,每个Binder引用都对应一个Binder实体,而每个Binder实体则可以多个Binder引
用。

3. 远程服务

Server都是以服务的形式注册到ServiceManager中进行管理的。如果将Server本身看作是"本地服务"的话,那么Client中的"
远程服务"就是本地服务的代理。如果你对代理模式比较熟悉的话,就很容易理解了,远程服务就是本地服务的一个代理,通过该远程服务Client就能和
Server进行通信。

理解上面3个概念之后,下面再通过几个典型的通信示例来解析该关系图。

ServiceManager守护进程

ServiceManager是用户空间的一个守护进程。当该应用程序启动时,它会和Binder驱动进行通信,告诉Binder驱动它是服务管理者;对
Binder驱动而言,它则会新建ServiceManager对应的Binder实体,并将该Binder实体设为全局变量。为什么要将它设为全局变量
呢?这点应该很容易理解--因为Client和Server都需要和ServiceManager进行通信,不将它设为全局变量的话,怎么找到
ServiceManager呢!

Server注册到ServiceManager中

Server首先会向Binder驱动发起注册请求,而Binder驱动在收到该请求之后就将该请求转发给ServiceManager进程。但是
Binder驱动怎么才能知道该请求是要转发给ServiceManager的呢?这是因为Server在发送请求的时候,会告诉Binder驱动这个请
求是交给0号Binder引用对应的进程来进行处理的。而Binder驱动中指定了0号引用是与ServiceManager对应的。
在Binder驱动转发该请求之前,它其实还做了两件很重要的事:(01)
当它知道该请求是由一个Server发送的时候,它会新建该Server对应的Binder实体。 (02)
它在ServiceManager的"保存Binder引用的红黑树"中查找是否存在该Server的Binder引用;找不到的话,就新建该
Server对应的Binder引用,并将其添加到"ServiceManager的保存Binder引用的红黑树"中。简言之,Binder驱动会创建
Server对应的Binder实体,并在ServiceManager的红黑树中添加该Binder实体的Binder引用。

当ServiceManager收到Binder驱动转发的注册请求之后,它就将该Server的相关信息注册到"Binder引用组成的单链表"中。这
里所说的Server相关信息主要包括两部分:Server对应的服务名 + Server对应的Binder实体的一个Binder引用。

Client获取远程服务
Client要和某个Server通信,需要先获取到该Server的远程服务。那么Client是如何获取到Server的远程服务的呢?

Client首先会向Binder驱动发起获取服务的请求。Binder驱动在收到该请求之后也是该请求转发给ServiceManager进程。
ServiceManager在收到Binder驱动转发的请求之后,会从"Binder引用组成的单链表"中找到要获取的Server的相关信息。至于
ServiceManager是如何从单链表中找到需要的Server的呢?答案是Client发送的请求数据中,会包括它要获取的Server的服务
名;而ServiceManager正是根据这个服务名来找到Server的。

接下来,ServiceManager通过Binder驱动将Server信息反馈给Client的。它反馈的信息是Server对应的Binder实体
的Binder引用信息。而Client在收到该Server的Binder引用信息之后,就根据该Binder引用信息创建一个Server对应的远程
服务。这个远程服务就是Server的代理,Client通过调用该远程服务的接口,就相当于在调用Server的服务接口一样;因为Client调用该
Server的远程服务接口时,该远程服务会对应的通过Binder驱动和真正的Server进行交互,从而执行相应的动作。

2. Binder设计解析

有了上面Binder模型的理论基础,接下来就可以逐步来讲解Binder的设计了。实际上,在设计C-S架构时,要考虑以下两个非常重要的因素。

第一,Server要提供接入点

如果C-S架构中的Client和Server属于同一进程的话,那么Client和Server之间的通信将非常容易。只需要在Client端先获取相
应的Server端对象;然后,再通过Server对象调用Server的相应接口即可。但是,Binder机制中涉及到的Client和Server是
位于不同的进程中的,这也就意味着,不可能直接获取到Server对象。那么怎么办呢? 那就需要Server提供一个接入点给Client。
这个接入点就是"Server的远程服务代理"

Client能够获取到Server的远程服务,它就相当于Server的代理。Client要和Server通信时,它只需要调用该远程服务的相应接口
即可,其他的工作都交给远程服务来处理。远程服务收到Client请求之后,会和Binder驱动通信;因为远程服务中有Server在Binder驱动
中的Binder引用信息,因此远程服务就能轻易的找到对应的Server,进而将Client的请求内容发送Server。

第二,通信协议

Binder机制中,涉及到大量的"内核的Binder驱动 和 用户空间的引用程序"之间的通信。需要指定对应的通信协议,确保通信的安全和正常。关于这部分,稍候再详细展开。

有了上面的两个中心思想之后,再来对Binder驱动的设计和协议进行逐步展开。

2.1 Binder设计

讲解Binder设计时,分为"内核空间"和"用户空间"这两部分进程讲解。内核空间就是Binder驱动中的Binder设计,而用户空间则是Android的C++层中的Binder设计。

2.1.1 内核空间的Binder设计

内核空间的Binder设计涉及到3个非常重要的结构体:binder_proc,binder_node和binder_ref。由于本文的重点
是介绍Binder机制的理论知识,因此,在这里我并不打算展开这3个结构体对它们进行详细介绍。当然,后面会再撰文对这些类进行详细说明。这里只需要了
解个大概即可。

binder_proc是描述进程上下文信息的,每一个用户空间的进程都对应一个binder_proc结构体。
binder_node是Binder实体对应的结构体,它是Server在Binder驱动中的体现。
binder_ref是Binder引用对应的结构体,它是Client在Binder驱动中的体现。

如上图所示,binder_proc中包含了3棵红黑树。
(01) Binder实体红黑树是保存"binder_proc对应的进程"所包含的Binder实体的,而Binder实体是与Server的服务对应的。可以将Binder实体红黑树理解为Server进程中包行的Server服务的红黑树。
(02)
图中有两棵Binder引用红黑树,这两棵树所包含的Binder引用都是一样的。不同的是,红黑树的排序基准不同,一个是以Binder实体来排序,而
另一个则是以Binder引用描述(Binder引用描述实际上就是一个32位的整型数)来排序。以Binder引用描述的红黑树是为了方便进行快速查
找。

上图是描述Binder驱动中Binder实体结构体的。如图所示,Binder实体中有一个Binder引用的哈希表,专门来存放该Binder
实体的Binder引用。这也如我们之前所说,每个Binder实体则可以多个Binder引用,而每个Binder引用则都只对应一个Binder实
体。

2.1.2 用户空间的Binder设计

上面是用户空间中Binder模型图,该图仅仅只描述出Server的相关类图,并没有Client部分。不过不要紧,通过这个Server的模型图,就能理清用户空间的Binder框架。

前面说过,Server是以服务的形式注册到ServiceManager中,而Server在Client中则是以远程服务的形式存在的。因此,这个图的主干就是理清楚本地服务和远程服务这两者之间的关系。

"本地服务"就是Server提供的服务本身,而"远程服务"就是服务的代理;"服务接口"则是抽象出了它们的通用接口。这3个角色都是通用的,对于不同
的服务而言,它们的名称都不相同。例如,对于MediaPlayerService服务而言,本地服务就是MediaPlayerService自身,远
程服务是BpMediaPlayerService,而服务接口是IMediaPlayerService。当Client需要向
MediaPlayerService发送请求时,它需要先获取到服务的代理(即,远程服务对象),也就是BpMediaPlayerService实
例,然后通过该实例和MediaPlayerService进行通信。

图中的ProcessState和IPCThreadState都是采用单例模式实现的,它们的实例都是全局的,而且只有唯一一个。

(01)
当Server启动之后,它会先将自己注册到ServiceManager中。注册时,Binder驱动会创建Server对应的Binder实体,并
将"Server对应的本地服务对象的地址"保存到Binder实体中。注册成功之后,Server就进入消息循环,等待Client的请求。
(02)
当Client需要和Server通信时,会先获取到Server接入点,即获取到远程服务对象;而且Client要获取的远程服务对象是"服务接口"类
型的。Client向ServiceManager发送获取服务的请求时,会通过IPCThreadState和Binder驱动进行通信;当
ServiceManager反馈之后,IPCThreadState会将ServiceManager反馈的"Server的Binder引用信息"保
存BpBinder中(具体来说,BpBinder的mHandle成员保存的就是Server的Binder引用信息)。然后,会根据该
BpBinder对象创建对应的远程服务。这样,Client就获取到了远程服务对象,而且远程服务对象的成员中保存了Server的Binder引用信
息。
(03)
当Client获取到远程服务对象之后,它就可以轻松的和Server进行通信了。当它需要向Server发送请求时,它会调用远程服务接口;远程服务能
够获取到BpBinder对象,而BpBinder则通过IPCThreadState和Binder驱动进行通信。由于BpBinder中保存了
Server在Binder驱动中的Binder引用;因此,IPCThreadState和Binder驱动通信时,是知道该请求是需要传给哪个
Server的。Binder驱动通过Binder引用找到对应的Binder实体,然后将Binder实体中保存的"Server对应的本地服务对象的
地址"返回给用户空间。当IPC收到Binder驱动反馈的内容之后,它从内容中找到"Server对应的本地服务对象",然后调用该对象的
onTransact()。不同的本地服务都可以实现自己的onTransact();这样,不同的服务就可以按照自己的需求来处理请求。

2.2 Binder通信

Binder通信协议是基于Command-Reply的方式的。

2.2.1 Binder通信模型

下面是Client和Server的交互模型图。

图中的原理很简单。
(01) Server进程启动之后,会进入中断等待状态,等待Client的请求。
(02) 当Client需要和Server通信时,会将请求发送给Binder驱动。
(03) Binder驱动收到请求之后,会唤醒Server进程。
(04) 接着,Binder驱动还会反馈信息给Client,告诉Client:它发送给Binder驱动的请求,Binder驱动已经收到。
(05) Client将请求发送成功之后,就进入等待状态。等待Server的回复。
(06) Binder驱动唤醒Server之后,就将请求转发给Server进程。
(07) Server进程解析出请求内容,并将回复内容发送给Binder驱动。
(08) Binder驱动收到回复之后,唤醒Client进程。
(09) 接着,Binder驱动还会反馈信息给Server,告诉Server:它发送给Binder驱动的回复,Binder驱动已经收到。
(10) Server将回复发送成功之后,再次进入等待状态,等待Client的请求。
(11) 最后,Binder驱动将回复转发给Client。

2.2.2 Binder通信数据

上面是用户空间和内核空间进行交互时,数据的打包方式。例如,当Client向Server发送请求时,Client会将数据打包成上诉格式,然后通过ioctl()发送给Binder驱动。根据数据的层次,从外到里分为3层进行说明。

第一层:这是用户空间的进程调用ioctl(fd,BINDER_WRITE_READ,&bwr)时
传递给Binder驱动的信息。fd是Binder驱动的文件句柄,BINDER_WRITE_READ是ioctl()的一个标识,而bwr是传递的数
据,它对应是途中的binder_write_read结构体的指针。binder_write_read中以write开头的是保存请求数据的,而read开头的是保存反馈数据的。其中,write_size是请求数据的大小,write_buffer是请求数据的内容,而write_consumed是用来记录请求数据中已经被Binder驱动处理过的数据的大小。

第二层:这层的数据是"事务指令"+"binder_transaction_data结构体"组成的。图中给
出的事务指令是BC_TRANSACTION,表示该事务是请求;如果是回复,则是BR_开头的,例如BR_TRANSACTION。
binder_transaction_data是描述事务交互数据的结构体;例如,target是指定事务目标,用来表示这个事务是交给谁进行处理
的;code是事务编码,用来表示这是一个什么样的事务(例如,注册服务事务/获取服务事务等待);data是保存事务中具体数据的内存地址。

第三层:这层是有效数据。如果该请求是传递给ServiceManager进行处理的,则有效数据是:消息头+"Server的相关信息"。消息头是用来进行有效性检查的,而"Server的相关信息"则是请求要处理的信息。

Binder的设计和框架的更多相关文章

  1. Android Binder机制(一) Binder的设计和框架

    这是关于Android中Binder机制的一系列纯技术贴.花了一个多礼拜的时间,才终于将其整理完毕.行文于此,以做记录:也是将自己所得与大家分享.和以往一样,介绍Binder时,先讲解框架,然后再从设 ...

  2. .NET架构设计、框架设计系列文章总结

    从事.NET开发到现在已经有七个年头了.慢慢的可能会很少写.NET文章了.不知不觉竟然走了这么多年,热爱.NET热爱c#.突然想对这一路的经历进行一个总结. 是时候开始下一阶段的旅途,希望这些文章可以 ...

  3. 构建NetCore应用框架之实战篇(一):什么是框架,如何设计一个框架

    一.系列简述 本篇起,将通过一系列文章,去描述如何构建一个应用开发框架,并以作者开发的框架为例,逐个点展开分析,如何从零开始,构建自己的开发框架. 本系列文章的目的,是带领有一编程经验的人,通过动手, ...

  4. 如何在Visual Studio 2017中使用C# 7+语法 构建NetCore应用框架之实战篇(二):BitAdminCore框架定位及架构 构建NetCore应用框架之实战篇系列 构建NetCore应用框架之实战篇(一):什么是框架,如何设计一个框架 NetCore入门篇:(十二)在IIS中部署Net Core程序

    如何在Visual Studio 2017中使用C# 7+语法   前言 之前不知看过哪位前辈的博文有点印象C# 7控制台开始支持执行异步方法,然后闲来无事,搞着,搞着没搞出来,然后就写了这篇博文,不 ...

  5. 王家林的81门一站式云计算分布式大数据&移动互联网解决方案课程第14门课程:Android软硬整合设计与框架揭秘: HAL&Framework &Native Service &App&HTML5架构设计与实战开发

    掌握Android从底层开发到框架整合技术到上层App开发及HTML5的全部技术: 一次彻底的Android架构.思想和实战技术的洗礼: 彻底掌握Andorid HAL.Android Runtime ...

  6. 基础知识漫谈(2):从设计UI框架开始

    说UI能延展出一丢丢的东西来,光java就有swing,swt/jface乃至javafx等等UI toolkit,在桌面上它们甚至都不是主流,在web端又有canvas.svg等等. 基于这些UI工 ...

  7. .NET框架设计(高级框架架构模式)—钝化程序、逻辑冻结、冻结程序的延续、瞬间转移

    阅读目录: 1.开篇介绍 2.程序书签(代码书签机制) 2.1ProgramBookmark 实现(使用委托来锚点代码书签) 2.2ProgramBookmarkManager书签管理器(对象化书签集 ...

  8. Android用户界面设计:框架布局(转)

    摘要:框架布局是Android开发者组织视图控件最简单和最有效的布局之一.通过本文,你将学到所有关于框架布局的知识,它们主要用来在屏幕上组织特别的或重叠的视图控件.使用得当的话,很多有趣的Androi ...

  9. 开放源代码的设计层面框架Spring——day04

    spring第四天     一.Spirng中的JdbcTemplate         1.1JbdcTemplate概述             他是spring框架中提供的一个对象,是对原始Jd ...

随机推荐

  1. LNMP服务器虚拟主机管理lnmp

    安装 系统需求: 需要2 GB硬盘剩余空间 安装步骤: 1.使用putty或类似的SSH工具登陆:登陆后运行:screen -S lnmp如果提示screen命令不存在可以执行:yum install ...

  2. 让android项目支持boost 支持c++11

    在Application.mk 里增加-D__GLIBC__  让项目支持boost 增加 -std=c++11 让项目支持c++11 (3.x的cocos本身已经支持了的) 看起来这样: APP_S ...

  3. java 泛型思考

    java泛型并没有像C++那样原生支持,因此,为了保证迁移兼容性,编译器在编译时会擦除具体类型,因此不能通过泛型调用具体方法. 如果调用必须用extends关键字限定范围,也正是由于这个原因,java ...

  4. DHCP服务器安装及配置

    一.什么是DHCP? DHCP (Dynamic Host Configuration protocol)动态主机设置协议,基于UDP(发送很小的数据报文,且对时效性要求较高)协议通信. 它是C/S架 ...

  5. javascript:void(0) ,设置a链接无效,设置点击a页面不刷新,不跳动

    http://www.cnblogs.com/opper/archive/2009/01/12/1373971.html 我想使用过ajax的都常见这样的代码: <a href="ja ...

  6. [翻译] WCF运行时架构

    原文地址 http://www.cnblogs.com/idior/articles/971252.html 介绍 WCF具有非常易用的编程模型,服务开发者在掌握ABC的概念后可以很容易的使用WCF去 ...

  7. JQuery AJAX 解析获得的JSON数据

    下面的解析的Json是一个二级循环. <!DOCTYPE html> <html> <head> <script src="https://code ...

  8. Java学习总结(二)----Java语言基础

    1.     Java语言基础 2.1 关键字 定义:被java语言赋予特殊含义的单词 特点:关键字中的字母都为小写 用于定义数据类型的关键字 class,interface,byte,short,i ...

  9. lumen 登陆 注册 demo

    本文将用Lumen来实现一个完整的用户注册.登录及获取用户信息的API. Lumen环境搭建和初始化详细步骤请参考上篇文章<Lumen安装配置使用入门>一文. 一.准备工作 1.Lumen ...

  10. docker跨容器之使用link大法通信

    容器1 docker run --name elixir -it edib/elixir-phoenix-dev /bin/bash ip address看看自己的ip 容器2 docker run ...