Download presentation
Presentation is loading. Please wait.
1
客户端-服务器框架 第一部分
2
客户端-服务器框架 本讲讲解 Symbian OS中的客户端-服务器框架模型 描述客户端-服务器模式背后的理论
设计的实现类 使用客户端-服务器模型的运行时性能
3
客户端-服务器模式 知道Symbian OS中客户端-服务器框架的结构和好处
理解系统和暂时服务器的不同角色,为服务器应用程序的例子匹配适当的服务器类型
4
客户端-服务器模式 在客户端-服务器模式中 服务器典型的被用于管理对系统资源和服务的共享访问 注意 客户端使用服务器提供的服务
服务器接收来自客户端的请求消息并处理它们 以同步或者异步的方式 服务器典型的被用于管理对系统资源和服务的共享访问 使用服务器是高效的,因为它可以为通过客户端会话提供服务 以及被相互独立的线程中的客户端并发访问 注意 客户端-服务器框架也能被用于包括含有可写静态数据的代码
5
客户端-服务器概念示意图 客户端 服务器 调用客户端#1 服务器 资源 This is not an architecture diagram of the Symbian OS implementation but demonstrates the concepts 客户端 API 调用客户端#2 调用客户端#3
6
客户端-服务器模式 服务器保护系统的完整性 在Symbian OS中 通过在独立的进程中使用服务器 它能保证资源在客户端之间正确的共享
因此所有的客户端能正确使用资源 在Symbian OS中 服务器运行在自己的进程中 具有独立隔离的地址空间 客户端对服务的访问是通过定义良好的接口进行的 通过在独立的进程中使用服务器 操作系统能够保证不好的编程或者恶意客户端不能破坏服务器管理的资源
7
客户端-服务器模式 服务器必须防止非法和失序的客户端请求 Symbian OS上绝大多数的系统服务是用客户端-服务器框架实现的 例如
它应该终结冒犯者 典型的,会对客户端引发一个致命错误 Symbian OS上绝大多数的系统服务是用客户端-服务器框架实现的 特别的它们提供了异步的功能 例如 窗口服务器 (用于访问UI资源,例如屏幕和键盘) 串行通信服务器 (用于访问串口) 文件系统服务器
8
客户端-服务器模式 有多种方法用以启动和停止服务器: 系统服务器 (例如文件服务器) 对于操作系统运行是必 需的 如果它们需要意外终止
它们作为Symbian OS的一个部分被启动 它们在OS运行期间都一直运行 如果它们需要意外终止 通常会强制手机重启
9
客户端-服务器模式 应用程序服务器只有当特定应用程序运行时才需要运行 当最后的客户端会话关闭时 这类服务器被称为暂时服务器
当客户端需要连接它们时启动 只有服务器的单个实例运行 (有第一个需要服务器的客户端启动) 当最后的客户端会话关闭时 服务器应该终止以节约系统资源 这类服务器被称为暂时服务器
10
客户端-服务器模式 其他服务器要求每个应用程序一个实例 例如POSIX 服务器 有该应用程序启动并在它终止时关闭服务器
多个应用程序可以使用相同的服务器实现 每个应用程序拥有其私有的实例
11
Symbian OS客户端-服务器框架基础
12
客户端-服务器框架基础 一个Symbian OS 服务器 每个进程的内存是隔离的 因为这个原因 总是 运行在独立于客户端的线程中
通常 – 但不是总是 – 运行在一个独立的进程中 每个进程的内存是隔离的 所以客户端和服务器不能访问彼此的地址空间 因为这个原因 所有的客户端-服务器通信通过消息传送发生的 分离的线程/进程之间数据的传输由内核中转 分别被称为线程间传输 (ITC) 或进程间传输 (IPC)
13
进程 vs 线程 线程#1 客户端 进程 B 服务器 线程#1 进程 进程 A 线程#1 客户端 服务器 线程#2 内核中转的消息
共享相同进程空间的客户端和服务器通过ITC通信 在分离线程中的客户端和服务器通过IPC通信
14
客户端-服务器框架基础 客户端和服务器之间用以传送消息的通信通道被称为会话(session ) 会话由客户端初始化
服务器端的代表已经有内核创建 内核也可用作所有客户端-服务器通信的中间人
15
客户端-服务器框架基础 客户端向服务器发送一个请求 对于简单的事务这就足够了——但是对于复杂数据 通过一个标识了请求性质的消息对象
还包含了其他额外参数 对于简单的事务这就足够了——但是对于复杂数据 需要用线程间数据转移函数传递参数数据和返回值
16
客户端-服务器框架基础 一个典型的服务器 例如 具有客户端代码,用于将传递给服务器的请求进行格式化 请求通过内核发送
Symbian OS文件服务器的客户端 (efile.exe) 是一个真正的文件服务器客户方实现的客户端 链接提供它的DLL (efsrv.dll) 客户端方实现DLL对使用它的代码隐藏了私有的客户端-服务器通信协议
17
客户端方的服务器实现Implementation
具体的文件服务器例子 进程边界 链接到efsrv.lib 并调用其API方法,如RFs::Connect() 客户端-服务器通信 (内核中转的消息) efile.exe mytest.exe efsrv.dll 客户端方的服务器实现Implementation 文件服务器
18
客户端-服务器框架基础 服务器常被用于 一个客户端 为其客户端提供异步服务 因为它们运行在独立与客户端的线程中
可以提交向一个服务器提交多个请求 (最多可以255个) 但是只能有一个激活的同步请求
19
Symbian OS 客户端-服务器类 知道被Symbian OS客户端-服务器框架使用的类,以及每个类的角色的基本信息:
RSessionBase TIpcArgs TSecurityPolicy RMessage2 CSession2 CServer2 CPolicyServer 认识服务器在启动时必须初始化的对象 理解用于防止Symbian OS中的服务器欺骗的机制
20
Symbian OS 客户端-服务器类 本节介绍 被用来实现Symbian OS客户端-服务器框架的类
这里讨论的类是Symbian OS v8.1b及其以后版本引入的用于安全进程见通信的类
21
Symbian OS客户端-服务器类 RHandleBase RSessionBase 客户端方表示 一个RSessionBase 对象
是所有拥有其他对象句柄的类的基类 那些对象通常是在内核中创建的 一个RSessionBase 对象 唯一的标识一个客户端-服务器会话 被用于给服务发送消息 RSessionBase RHandleBase
22
Symbian OS客户端-服务器类 RSessionBase 的大多数方法 相反, 从RSessionBase 派生的类
是受保护的,以确保用于访问服务器的客户端一侧的类不会直接暴露对服务器的访 问接口 相反, 从RSessionBase 派生的类 导出的函数会调用受保护的RSessionBase 方法 这样 “包装”了与服务器的通信
23
客户端-服务器类 CServer2 CSession2 服务器 客户端 RSessionBase RMyClientAPI
受保护的方法 (如. 私有协议) 公开API 方法
24
Symbian OS 客户端-服务器类 例如 提供了对文件服务器的访问 类RFs 从RSessionBase 派生
通过诸如RFs::Delete() 和 RFs::GetDir() 的方法 允许与文件服务器通信,而不必暴露对它的直接访问
25
Symbian OS 客户端-服务器类 类RSessionBase 典型的,由客户端一侧实现的代码在其导出函数中调用
具有几个CreateSession() 的重载函数 用以开始新的客户端-服务器会话 典型的,由客户端一侧实现的代码在其导出函数中调用 例如Open() 或 Connect(), 它们并用于初始化会话
26
Symbian OS 客户端-服务器类 例如 开始一个与文件服务器的会话需要调用RFs::Connect()
它调用了RSessionBase::CreateSession() 当成功打开时,相应的内核和服务器一侧对象也被创建 Kernel side aspects are not covered in any depth refer to Harrison/Shackman “Symbian OS C++ for Mobile Phones vol 3: Application Development for Symbian OS v9) chapter 8
27
Symbian OS 客户端-服务器类 一个服务器具有唯一的名称 客户端一侧的实现完成这些工作
它必须被传递给RSessionBase::CreateSession() 以将客户端连接到正确的服务器 客户端一侧的实现完成这些工作 所以调用代码不需要知道服务器的名字
28
Symbian OS 客户端-服务器类 为了阻止“欺骗”服务器使用关键系统服务器的名字
命名空间被分为“普通”和“保护”两个部分 保护服务器命名空间被定义为所有以“!”字符开头的服务器名称 只有拥有ProtServ 能力的进程能被允许注册以该字母开头的服务器对象 ProtServ 能力 允许服务器进程用一个被保护的名字注册 我们在后面的讲义会再讨论这个问题 Chapter 15 in the primer deals with platform security and the capability model
29
类 RSessionBase 多个RSessionBase::CreateSession() 的重载函数 槽位是为用于保存 已激活的异步请求
接收一个整型参数aAsyncMessageSlots 该值指明了要保留的槽位个数 槽位是为用于保存 已激活的异步请求 那是客户端会话可以拥有的向服务器的请求数目 可以为每个服务器保留的最大槽位数目是255
30
RSessionBase类 其他CreateSession() 的重载函数 在这个系统中每个服务器只有255个槽位可用
不预先非配消息槽位的最大数目 槽位从内核为该服务器管理的全局消息槽位池中获得,槽位池共有255个槽 位 在这个系统中每个服务器只有255个槽位可用
31
类RSessionBase 如果已激活请求的数目超过了系统池的槽位数目 提交异步请求将失败 或者超过了为特定线程保留的槽位数目
并且立即返回,其错误码为KErrServerBusy
32
类 RSessionBase CreateSession() 重载函数的其他参数还包括TSecurityPolicy 对象 例如
它允许客户端代码向服务器指定标准 例如 服务器进程的安全ID (secure ID) 或者调用进程的能力 TSecurityPolicy 类将在后面更加详细的讨论 安全IDs, 能力以及平台安全也将在后续讲义中讨论 Again in security is discussed in chapter 15 of the ASD primer.
33
类 RSessionBase 给服务器发送请求 RSessionBase::SendReceive() 方法被重载以处理异步和同步请求
通过调用RSessionBase::SendReceive() 或者 RSessionBase::Send() RSessionBase::SendReceive() 方法被重载以处理异步和同步请求 异步请求方法接收一个TRequestStatus& 参数 同步请求则范围一个TInt类型的结果值 RSessionBase::Send() 方法 发送消息给服务器,但是并不接收答复 实践中,这个函数很少用到
34
The Send() 和 SendReceive() 方法
接收一个32位的参数用以标识客户端请求 这典型的定义为客户端和服务器共享的枚举类型 请求标识不需要是公开的 传递给TIpcArgs 对象的参数数据的顺序也不是
35
The Send() 和 SendReceive() 方法
请求标识和参数数据 是客户端一侧和服务器一侧实现的私有协议 该协议对每个客户端-服务器实现都是特定的 被包装在客户端一侧实现所导出的公开方法中 调用代码不需要“理解”协议
36
从服务器断开 典型的,用于访问服务器的类都有终结方法 从内部看,该方法会调用 RHandleBase::Close()
通常是调用Close() 因为该类通常是R类,而该方法是清除R类对象清楚的一 般方法 从内部看,该方法会调用 RHandleBase::Close() 它发送一个断开消息给服务器 并设置会话句柄为零 服务器一接收到该消息,就结束会话 通过销毁代表会话的服务器一侧的所有关联对象 如果客户端有任何已激活的请求 它们不被保证能够完成
37
从服务器断开 如果客户端激活请求后面跟着Close()调用,则Symbian OS认为其是一个 客户端一侧的编程错误
因此,引发一个致命错误 在会话关闭后,服务器可能努力完成会话已激活的请求 并这并不总是这样! 所以客户端... 应该保证在调用 Close()之前取消所有请求
38
从服务器断开 如果没有调用RSessionBase::Close()就终止了 如果服务器线程意外终止了
内核负责进行服务器一侧进行必要的清除工作 如果服务器线程意外终止了 所有等待的客户端请求将以错误码KErrServerTerminated完成 这让客户端有机会对所有会话句柄进行清除 一旦客户端或者服务器终止(即时后面又重新启动) 终止前的会话都不能被再用
39
会话共享 可能会有多个用户客户端 一个打开的客户端会话可以被共享 一些服务器对连接到服务器上的线程限制会话共享
共享同一个RSessionBase 对象 一个打开的客户端会话可以被共享 同一个客户端进程的所有线程之间 以及与其他进程的线程之间 假定服务器支持会话共享的话 一些服务器对连接到服务器上的线程限制会话共享 如果尝试共享会话,会引发致命错误 这是在Symbian OS v6.0引入可共享会话以前总是发生的事情
40
会话共享 CServer2 CSession2 服务器 线程 #1 线程#2 (可以与线程#1是同一个进程或者在不同的进程中)
RSessionBase::CreateSession() 对相同的进程共享RSessionBase::ShareAuto() 对不同的进程共享RSessionBase::ShareProtected() 服务器 线程 #1 线程#2 (可以与线程#1是同一个进程或者在不同的进程中) RSessionBase 客服端 1 客服端 2
41
会话共享 在客户端一侧 第一个连接到服务器的会话 一旦会话打开 如果一个会话能被共享...
应当像通常那样用RSessionBase::CreateSession() 创建 一旦会话打开 应该调用它的RSessionBase::ShareAuto() 以使其对同一进程中其他线程是可共享的
42
会话共享 如果会话句柄 一个替代方法 该重载函数使用一个TIpcSessionType 参数 能够被不同进程的线程使用
则必须对其调用RSessionBase::ShareProtected() 一个替代方法 可共享会话通过调用RSessionBase::CreateSession() 的重载函数创 建 该重载函数使用一个TIpcSessionType 参数 EIpcSession_Sharable 用于进程内可共享 (相同进程) EIpcSession_GlobalSharable 用于全局可共享会话 (不同进程)
43
类 TIpcArgs 类TIpcArgs 用来包裹要发送给服务器的参数 例如 一个TIpcArgs 对象 该对象也可以不包含任何参数
构成了客户端-服务器请求的有效载荷 它能够将最多四个参数包装在一起 并且包含了每个参数的类型信息 该对象也可以不包含任何参数 用于没有关联载荷数据的请求 TInt SendReceive(TInt aFunction, const TIpcArgs &aArgs);
44
类 TIpcArgs 类TIpcArgs 从内部看 该类也有 有一个缺省构造函数和四个模板化构造函数 允许这类的对象用零到四个参数构造
参数保存在一个简单的包含四个32位值的数组中 构造函数中的参数依次的放到数组的槽位中 该类也有 一组重载的 Set() 函数 用于显式的将参数值设置到数组的特定槽位
45
类 TIpcArgs 方法 Set() 它可以是 TIpcArgs 类的更多内容会很快在后面介绍 指明要使用的槽位和要保存的32位值
TInt RHandleBase TAny* TDes16* TDes8* TIpcArgs 类的更多内容会很快在后面介绍 Consult the SDK for more in-depth information
46
类 TSecurityPolicy 类TSecurityPolicy 拥有它的客户端 代表了一种一般性的安全策略
由客户端一侧实现代码传递给服务器 拥有它的客户端 将一个或多个这种对象到服务器 指定客户端进程应该进行那些安全检查 它指明了客户端API用户对服务器 的访问权限 在用于它访问服务器之前
47
类 TSecurityPolicy TSecurityPolicy 指定的安全策略包括: 如果指定了多个能力 检查能力在0到7之间
检查给定的安全标识(Security Identifier)其能力0-3之间 检查给定的商家标识(Vendor Identifier)其能力0-3之间 如果指定了多个能力 它们全部要通过安全检查
48
类 RMessage2 类 RMessage2 最初的类RMessage 代表了服务器一层的客户端请求
名字中的 2 指明这是该类的第二个版本 最初的类RMessage 用于Symbian OS v8.1b及其以前的版本 但是后来已经本替换 因为引入了安全进程间通信
49
RMessage2 与 TIpcArgs的关系
CServer2 服务器 客户端 Client #1 TIpcArgs 类 对数据和函数标识操作码 进行排列(marshal) MySet(TInt aVal) TIpcArgs(T0 a0) RMyClassSession aVal 被包裹在TIpcArgs 模板类中(T0 is TInt and a0 is aVal) 为客户端提供访问服务器的简单接口的基本包装器 TIpcArgs在通过内核中转之后在服务器一侧被转化成一个一个RMessage2 对象 RMessage2::Function()提供了对 MySet()的操作码 RMessage2::Int0() 提供第一个参数 进程边界 The kernel mediation is cover in Symbian OS Internals and Symbian OS C++ for Mobile Phones vol3
50
类 RMessage2 RMessagePtr2 RMessage2 类RMessage2 从RMessagePtr2 派生
51
类 RMessage2 RMessage2 对象 例如 是TIpcArgs 和请求标识符在服务器一侧的等价对象 在客户端发出请求时由内核创建
通过调用RSessionBase::SendReceive() 连接服务器或者关闭会话
52
类 RMessage2 32位的请求标识符 TIpcArgs 提供的四个参数槽位 也被称为操作码 可以通过Function()提取
可以通过调用Int0() 访问 以返回请求数组的第一个32位元素值 Int1() 返回第二个元素 直到通过Int3()返回第四个函数
53
类 RMessage2 以相似的方法 这样,请求数据参数的结构 Ptr0() 以TAny*指针的形式返回请求数组的第一个元素的内容
对每个客户端请求都是预先确定的 它可以从RMessage2 对象适当的槽位中提取数据
54
类 RMessage2 指针 作为替代,服务器必须使用 从Ptr0(), Ptr1(), Ptr2() 和 Ptr3() 返回的
不能由服务器代码直接使用,如果它们指向运行在不同进程的客户端的地址空间的话 作为替代,服务器必须使用 RMessagePtr2中的ReadL()和WriteL()重载方法 它们使用由内核中转的进程间通信来传输数据
55
类 RMessage2 当服务器已经服务了一个客户端请求 该方法 传递给RequestComplete() 的整型参数
它会调用RMessage2的RMessagePtr2::Complete() 方法 该方法通知客户端请求已经完成 该方法 包装了对客户端线程句柄函数RThread::RequestComplete()的调用 传递给RequestComplete() 的整型参数 被写入到客户端的TRequestStatus 值 客户端线程的请求信号量将被通知
56
类 CSession2 CBase CSession2 类CSession2 是一个抽象基类
它代表了服务器中的一个客户端会话 对于客户端一侧每个从RSessionBase派生的对象 在服务器一侧有一个关联CSession2派生的对象 被作为客户端连接请求的一部分创建,通过函数RSessionBase::CreateSession() 该类的名称反映了它是这个类的第二个版本 为Symbian OS v8.1中的安全IPC进行升级的 CSession2 CBase See diagram on slide 40
57
类 CSession2 从 CSession2 派生的类 典型的,ServiceL()检查进入的消息参数以了解客户端提交了那个请求
然后通过对消息进行解包 从而使用输入的参数 当请求被处理后 调用关联消息对象的Complete() 以通知请求的客户端线程请求完成
58
类 CServer2 CActive CServer2 服务器一侧的基类是CServer2 系统确保 CServer2::StartL()
它是所有服务器必须实现的抽象基类 系统确保 对每个唯一命名的服务器只有一个CServer2派生的活动对象 CServer2::StartL() 将服务器添加到活动调度器(active scheduler ) 初始化第一个消息以接收请求 CServer2 CActive
59
类 CServer2 CServer2 活动对象将从服务器所有的客户端接收请求当作事 件 事件处理方法RunL() 执行
从内核接收每个进入请求的通知 事件处理方法RunL() 执行 使用 Message() 检查 RMessage2 对象 以确定是否CServer2 派生的对象自己能够处理请求 例如 – 连接或者断开请求
60
类 CServer2 如果CServer2::RunL() 函数不能处理请求 服务完每个请求之后
它将请求交给服务器一侧合适的CSession2派生类的 ServiceL() 方法 服务完每个请求之后 RunL() 重新提交一个 “消息接收(message receive)” 请求 然后进一步等待客户端请求
61
活动对象协定 由于CServer2 从CActive派生 特别是 例如 活动对象的协定应该遵守 请求应该在服务器中得到快速而高效的处理
一个长时运行的异步请求应该委托给服务器内的另一个活动对象 这样它就可以被处理成一个增量任务 这使得服务可以在后台处理长时运行请求请求时,仍能保持对进入请求的响应
62
类 CPolicyServer CServer2 CPolicyServer CPolicyServer 实现了安全策略 当接收到一个消息时
该类根据安全策略检查接收到的消息 根据检查的结果采取行动 正确的行为在构造时定义 当接收到一个消息时 消息的操作码数被用于提取关联的策略索引 不同的索引值有下列不同的用法… CPolicyServer CServer2
63
CPolicyServer 策略枚举 TSpecialCase::EAlwaysPass
该消息像通常的那样被传递到会话的ServiceL() 方法予以处理 如果是一个连接消息——创建一个新会话 TSpecialCase::ENotSupported 返回一个错误码 KErrNotSupported 即完成 TSpecialCase::ECustomCheck 调用虚函数 CustomSecurityCheckL() 返回一个数值,指示是否处理该消息 或者使其失败
64
The CPolicyServer Policy Enumerations
如果没有使用任何枚举值If none of the enumerations apply 策略索引被当作查找值(lookup value) 用于CPolicyServer 服务构造时定义的安全策略数组 关联的策略元素 被用于间车正被处理消息 如果消息满足策略指定的属性 它将被正常处理
65
类 CPolicyServer 最后的动作是 该动作要么 调用CPolicyServer::CheckFailedL()
使得在关联的策略元素中指定的动作值 该动作要么 以错误码KErrPermisisonDenied完成请求 对客户端线程引发致命错误 调用CPolicyServer::CustomFailureActionL() 以执行指定的失败动作 For more information about the CPolicyServer class, consult the Symbian Developer Library documentation.
66
服务器一侧启动 当创建一个服务器时,需要为服务器分配 这应该在启动时完成
一个清除栈 一个活动调度器 这应该在启动时完成 清除栈通过调用CTrapCleanup::New() 创建 活动调度器按照活动对象一章所介绍的方法创建和安装 ASD Primer Cleanup stack section 5.5 and active scheduler 9.3
67
客户端-服务器框架 客户端-服务器模式 Symbian OS 客户端-服务器框架基础 Symbian OS 客户端服务器类
客户端-服务器数据传输
68
客户端-服务器框架 第二部分
69
客户端-服务器框架 本讲介绍 一个“经典的”客户端服务器例子 使用客户端-服务器模型的运行时性能
70
回顾:客户端-服务器类 CServer2 CSession2 服务器 客户端 RSessionBase RMyClientAPI
受保护方法 (例如私有协议) 公开API 方法 23
71
回顾: RMessage2 与TIpcArgs的关系
CServer2 服务器 客户端 Client #1 TIpcArgs 类 对数据和函数标识操作码 进行排列(marshal) MySet(TInt aVal) TIpcArgs(T0 a0) RMyClassSession aVal 被包裹在TIpcArgs 模板类中(T0 is TInt and a0 is aVal) 为客户端提供访问服务器的简单接口的基本包装器 TIpcArgs在通过内核中转之后在服务器一侧被转化成一个一个RMessage2 对象 RMessage2::Function()提供了对 MySet()的操作码 RMessage2::Int0() 提供第一个参数 进程边界 The kernel mediation is cover in Symbian OS C++ for Mobile Phones vol3 and Symbian OS Internals
72
客户端-服务数据传输 知道客户端服务器为同步和异步请求传输数据的基本方法
了解从派生自RSessionBase的客户端传送数据给Symbian OS服务器的正确代码 知道如何提交同步和起步请求 知道如何将基本及定制数据类转换成适当的载荷,该载荷能够以只读以及可读写请 求参数的形式被传送到服务器
73
客户端-服务器数据传输 我们将要介绍的例子假定客户端和服务器运行在不同的进程中 参数数据永远不能用简单的C++指针进行传递
这意味着在它们之间进行数据传输需要使用进程间通信 (IPC) 参数数据永远不能用简单的C++指针进行传递 因为服务器绝不直接访问客户端的地址空间(反之亦然) 数据从客户端传递给服务器 以请求消息中32位值的形式 或者以指向客户端地址空间中一个描述符的指针形式 服务器通过内核中转数据传输的方式访问它
74
客户端-服务器数据传输 给对此感兴趣的朋友... 本例子使用大量的精力(赫克拉斯的磨难)来验证数据传输
赫克拉斯是一个希腊神话任务,网站 有这个神话的背景介绍
75
客户端-服务器数据传输 一组枚举值用于标识客户端向服务器请求的是那个服务 enum THerculeanLabors {
ESlayNemeanLion, ESlayHydra, ECaptureCeryneianHind, ESlayErymanthianBoar, ECleanAugeanStables, ESlayStymphalianBirds, ECaptureCretanBull, ECaptureMaresOfDiomedes, EObtainGirdleOfHippolyta, ECaptureOxenOfGeryon, ETakeGoldenApplesOfHesperides, ECaptureCerberus, ECancelCleanAugeanStables, ECancelSlayStymphalianBirds };
76
客户端-服务器数据传输 一个 TIpcArgs 对象 参数数据也被传递给服务器 对于没有请求参数的情况
会被实例化并与客户端请求一起传递给相应服务器 参数数据也被传递给服务器 它们在TIpcArgs对象构造是传递该对象 对于没有请求参数的情况 缺省的,TIpcArgs 的构造函数是不带参数 下面的例子描述了如何为一组不同的参数类型实现客户端-服务器请求代码 void RHerculesSession::CancelCleanAugeanStables() { SendReceive(ECancelCleanAugeanStables, TIpcArgs()); }
77
只读基本类型 下面的代码显示了如何传递描述符和整数给服务器: // 请求传递了一个常量描述符 (aDes)
// 和一个”只读” 整数 (aVal) 给服务器 TInt RHerculesSession::SlayNemeanLion(const TDesC8& aDes, TInt aVal) { TIpcArgs args(&aDes, aVal); return (SendReceive(ESlayNemeanLion,args)); }
78
自定义类型: 简单类型 (T-类对象) 前一页幻灯片显示了如何将整型和描述符传递给服务器 但是自定义的数据类型怎么办呢?
RHerculesSession::SlayHydra() 传递一个THydraData 类型的对象 THydraData 是只包含内建类型的简单结构 TVersion 是定义在e32cmn.h中的一个Symbian OS 类 见下一页 struct THydraData { TVersion iHydraVersion; TInt iHeadCount; };
79
自定义类型: 简单类型 (T-类对象) TVersion 是定义在e32cmn.h中的一个Symbian OS 类
一个THydraData 对象大小为64位 class TVersion { public: IMPORT_C TVersion(); IMPORT_C TVersion(TInt aMajor, TInt aMinor, TInt aBuild); IMPORT_C TVersionName Name() const; TInt8 iMajor; TInt8 iMinor; TInt16 iBuild; };
80
自定义类型: 简单类型 (T-类对象) 因为THydraData 对象的大小为64 位 服务器一侧代码
它用于传递给服务器显得太大了,因为请求数据数组的每个元素是32位的 一个指向对象的指针必须在客户端-服务器边界进行排列(marshaled). 服务器一侧代码 不应当试图通过由客户端传递给服务器的C++指针直接访问客户端一侧对象 这个例子中服务器代码运行在不同的进程中,因此属于不同的虚拟地址空间 客户端一侧任何使用客户端一侧指针的尝试,都将引发访问冲突(一个致命错误)
81
自定义类型: 简单类型 (T-类对象) 数据传输必须通过类RMessagePtr2 的进程间通信方法来执行
因此,在THydraData 对象被传递给服务器之前,它必须被“描述符化 ( descriptorized)” 包裹指针模板类TPckg 和 TPckgC 可以用于包装平坦数据对象,如THydraData, 在一个 指针描述符 TPtr8 中 Section 7.9 of the ASD primer discusses the use of the package pointer template classes TPckg and TPckgC
82
客户端-服务器数据传输 RHerculesSession 的SlayHydra() 方法
创建一个包裹THydraData 参数的TPckg<THydraData> 产生的描述符的大小与它所包裹的模板化对象的大小一样 (64 bits = 2 bytes) TPtr8 的数据指针iPtr 指向THydraData 对象的开始处 TInt RHerculesSession::SlayHydra(THydraData& aData) { TPckg<THydraData> data(aData); TIpcArgs args(&data); return (SendReceive(ESlayHydra, args)); }
83
自定义类型: 简单类型 (T-类对象) 如果是自定义数据
长度可变吗? 或者不仅仅包含平坦数据,而是拥有指向其他对象的指针? 后面的代码显示了一个包含了指向另一个对象或者变长数据的指针的C类,是如 何排列,以便从客户端传递到服务器的 类CHerculesData 拥有两个对描述符指针和一个整型数值 它必须使用工具代码 以将其所有成员变量放入描述符中:客户端一侧外化(descriptor client-side - externalization) 以及相应的代码将其从描述符中重建出来:服务器一侧内化(server-side – internalization) Some of the standard construction code is omitted for clarity in the example
84
自定义类型: 简单类型 (T-类对象) class CHerculesData : public CBase { public:
static CHerculesData* NewLC(const TDesC8& aDes1, const TDesC8& aDes2, TInt aVal); static CHerculesData* NewLC(const TDesC8& aStreamData); ~CHerculesData(); HBufC8* MarshalDataL() const; protected: void ExternalizeL(RWriteStream& aStream) const; void InternalizeL(RReadStream& aStream); ... private: HBufC8* iDes1; HBufC8* iDes2; TInt iVal; }; 创建一个代表“this”的HBufC8 将’this’ 写入到流中 从流中初始化’this’ 清晰起见,省略了构造函数
85
从描述符参数的内容创建一个CHerculesData 实例
用于服务器一侧 NewLC 为描述符打开一个读入流 完成实例化 析构函数 CHerculesData* CHerculesData::NewLC(const TDesC8& aStreamData) { CHerculesData* data = new(ELeave) CHerculesData(); CleanupStack::PushL(data); RDesReadStream stream(aStreamData); CleanupClosePushL(stream); data->InternalizeL(stream); CleanupStack::PopAndDestroy(&stream); return (data); } CHerculesData::~CHerculesData() { delete iDes1; delete iDes2; }
86
创建并返回一个含有“this”内容的堆描述符
用于客户端一侧 HBufC8* CHerculesData::MarshalDataL() const { const TInt KExpandSize = 128; CBufFlat* buf = CBufFlat::NewL(KExpandSize); CleanupStack::PushL(buf); RBufWriteStream stream(*buf); CleanupClosePushL(stream); ExternalizeL(stream); CleanupStack::PopAndDestroy(&stream); HBufC8* des = HBufC8::NewL(buf->Size()); TPtr8 ptr(des->Des()); buf->Read(0, ptr, buf->Size()); CleanupStack::PopAndDestroy(buf); return (des); } 创建一个动态平坦缓冲区以保存该对象的成员数据 data 动态数组的“粒度” 将 ’this’ 写入流 从缓冲区创建一个堆描述符 完成读入 返回 (将对象拥有权传递给调用者) See Chapter 12 of the ASD primer for Streams
87
将 ’this’ 写入aStream 以进行从客户端一侧到服务器一侧的排列
void CHerculesData::ExternalizeL(RWriteStream& aStream) const { if (iDes1) aStream << *iDes1; } else aStream << KNullDesC8; if (iDes2) aStream << *iDes2; aStream.WriteInt32L(iVal); 将iDes1 写入流 或者写入一个空描述符 将iDes2 写入流 将iVal 写入流
88
自定义类型: 简单类型 (T-类对象) 为了在服务器一侧重新构造CHerculesData 对象
使用aStream 的内容实例化’this’ void CHerculesData::InternalizeL(RReadStream& aStream) { const TInt KMaxReadSize = 1024; iDes1 = HBufC8::NewL(aStream, KMaxReadSize); iDes2 = HBufC8::NewL(aStream, KMaxReadSize); iVal = aStream.ReadInt32L(); } 限制最多1024 字节 读入iDes1 读入iDes2 读入 iVal
89
自定义类型: 简单类型 (T-类对象) 请求传递了一个C 类对象 客户端一侧
TInt RHerculesSession::SlayErymanthianBoar(const CHerculesData& aData) { HBufC8* dataDes=NULL; TRAPD(r, dataDes = aData.MarshalDataL()); if (dataDes) TPtr8 ptr(dataDes->Des()); TIpcArgs args(&ptr); r = SendReceive(ESlayErymanthianBoar, args); delete dataDes; } return (r);
90
可读写请求参数 客户端请求提交必须区分 传递了可读写整数给服务器的请求 非修改参数用以传递常量数据给服务器 可修改参数用于从服务器提取数据
使用TPckg 来描述符化TInt& (例如 可修改引用) TInt RHerculesSession::CaptureCeryneianHind(TInt& aCaptureCount) { TPckg<TInt> countBuf(aCaptureCount); TIpcArgs args(&countBuf); return (SendReceive(ECaptureCeryneianHind,args)); }
91
异步请求 有一点是很重要的,即客户端一侧传递给异步请求的数据必须不是基于栈的 那些参数必须存在直到 这不仅仅应用于可读写参数
因为服务器在客户端提交请求之后,服务器什么时候处理进来的请求数据是不确定的 那些参数必须存在直到 它们不能存在于栈中,当客户端一侧提交请求的函数返回了 (因为这会销毁栈结构) 这不仅仅应用于可读写参数 也应用与只读参数,因为请求可能在服务器端进行排队 也就是说——它可能直到SendReceive() 调用完成也没有被读到
92
异步请求 客户端必须也提交一个TRequestStatus 对象,用以接收请求完成的通知
在本例中,TIpcArgs 参数被构造成空的,因为没有请求是不带参数的 void RHerculesSession::CleanAugeanStables(TRequestStatus& aStatus) { SendReceive(ECleanAugeanStables, TIpcArgs(), aStatus); }
93
客户端-服务器框架的影响 理解使用客户端-服务器会话对运行速度潜在的影响,区分什么环境使用它是有用 和必要的,什么环境是低效的
客户端-服务器框架的影响 理解使用客户端-服务器会话对运行速度潜在的影响,区分什么环境使用它是有用 和必要的,什么环境是低效的 认识什么场景使用客户端子会话(subsession) 理解当发送客户端-服务器请求时上下文切换的影响,以及管理客户端和服务器之 间通信的最好方法,以其获得最大限度的运行时效率
94
会话创建代价 一个客户端可以跟一个服务器有多个会话 对每个打开的客户端会话 另外 每个客户端会话也会消耗服务器和内核的资源
内核创建和保存一个代表它的对象 服务器创建一个派生自CSession2的对象 另外 每个会话会带来内核、客户端以及服务器一侧线程的速度和内存的消耗
95
会话创建代价 客户端创建会话的数据应该最小化 而不是按需创建和打开很多会话 为了效率 —— 当需要使用多会话时
客户端代码应该努力共享单个会话 为了效率 —— 当需要使用多会话时 客户端-服务器实现可以提供子会话类 这将降低打开多个会话的开销
96
会话创建代价 为了使用子会话 一个典型的客户端子会话实现从RSubSessionBase 派生 该实现类
客户端必须打开一个与服务器的普通会话 它可以用于创建子会话,子会话可以消耗更少的资源而且创建的速度也更快 一个典型的客户端子会话实现从RSubSessionBase 派生 和从RSessionBase派生客户端会话类似 该实现类 提供了简单的包装函数以隐藏子会话的细节
97
会话创建代价 使用子会话的一个很好例子就是RFile 它从RSubSessionBase 派生
是一个连接到文件服务器的RFs 客户端会话的子会话 一个RFile 对象表示访问单个文件的子会话
98
性能代价 当使用客户端-服务器模型时,了解其系统性能问题是非常重要的 客户端和服务器之间传输数据的数量并不会引起太大的开销 主要的开销是由于
但是通信的频率则会引起 主要的开销是由于 从客户端线程到服务器线程之间传送消息所需要的线程上下文切换 反之亦然 如果客户端线程和服务器线程运行在不同进程中,则还需要进行进程上下 文切换
99
性能代价 线程间的上下文切换 如果客户端和服务器线程在同一个进程中 如果客户端和服务器运行在两个独立的进程中——除了线程上下文切换
保存当前运行线程的状态,用要替换线程以前的状态来覆盖它 如果客户端和服务器线程在同一个进程中 线程上下文切换保存线程的处理器的寄存器数据 如果客户端和服务器运行在两个独立的进程中——除了线程上下文切换 进程上下文(对线程可访问的地址空间)必须必须保存和恢复 MMU必须对每个进程重新映射内存块 在一些硬件上,这意味着高速缓冲存储器(cache)必须进行清洗(flushed)
100
性能代价 线程或进程间上下文切换所需要的代价的准确性质 运行在不同进程中的线程间数据传递也有代价 依赖于实际的硬件
因为属于客户端的数据区域必须被重新映射到服务器的地址空间
101
性能提高 因为性能的原因, 当在客户端和服务器之间传递数据时 客户端和服务器之间传输数据的数量没有上限
在单个事务中传递大量数据比进行多次传递要更可取 客户端和服务器之间传输数据的数量没有上限 但是节省上下文切换时间的考虑必须与存储和管理大块请求数据所消耗的内存之间进行 平衡
102
性能提高 例如 而是倾向于使用流存储 APIs 当存储数据到文件时,流APIs 将其缓存在客户端一侧
向或者从文件服务器频繁传输数据的组件通常不会直接使用文件系统访问方法,如 RFile::Read() or RFile::Write() 而是倾向于使用流存储 APIs 这些API方法已经被优化以提高访问文件服务器的效率 当存储数据到文件时,流APIs 将其缓存在客户端一侧 然后一次性的将其传递给文件服务器 (而不是每接收到一次就发送一块数据) Streams are described in more detail in Chapter 12 of the ASD primer and later in the course.
103
性能提高 RWriteStream RReadStream 使用客户端一侧缓冲区保存它要传递的数据
只有昂缓冲区满时才访问文件服务器将数据写入 或者流的拥有者调用了CommitL() 方法 RReadStream 当它创建时从源文件读取数据对缓冲区进行预填充 当流拥有者希望访问文件中的数据时,流使用缓冲区获取需要的部分数据 (而不是直接调用文件服务器)
104
性能提高 当编写使用服务器(例如文件服务器)的代码时 例如,文件服务器提供获取文件系统中单个目录项的方法
很值得仔细考虑一下如何使文件访问最高效 例如,文件服务器提供获取文件系统中单个目录项的方法 但是通常读入整个目录项然后在客户端一侧扫描它们往往更加高效... ...而不是跨越进程边界,多次的获取目录项 一个编码良好的客户端实现户层现高层的API,这样一个事务会执行许多服务器一 侧动作
105
客户端-服务器框架 客户端-服务器数据传输(例子) 客户端-服务器框架的影响
Similar presentations