我们都承认一个好的程序设计需要拥有许多能力。适当的执行错误检查、预先考虑系统上的可用资源以及预估来自不同使用者之所有可能输入情形-这就是程序可视为一种艺术形式工作的原因。编写服务必须完全精通这种艺术形式。
就应用程序软件而言,忘了处理其细微差别的部份并不会造成很大的问题;因为这些疏忽通常只会影响到单一使用者,而不是整个企业。但「服务器」软件属于关键性的任务,因此需要严谨地注意所有的细节部份。遵守纪律的服务器开发者应设计出能够满足这些要求的软件。以下的章节会叙述一些服务器程序设计者必须加以注意的规则及纪律。
说明
在整本书中,频繁使用「服务器」和「服务」这二个术语。当使用「服务器」这个术语时,是指为客户端执行任务的机器或应用程序。使用「服务」这个术语时,是指一个特别的Microsoft Windows应用程序,它为客户端执行任务,也包含经由作业系统使它能接受特别处理的额外基础建设。这些术语有些部份显然是重叠的,有时候会交替使用,假如很明确地在讨论某种Windows服务时,将使用「服务」这个术语来表示。
容错和整洁的程序代码
现今的软件非常的复杂,以至于无法预先考虑到在每个环境下执行的情形。本书所说的「环境」是指您们的处理程序之位址空间内容、函数的参数值以及在同一个系统上执行其他处理程序的影响。因为非常错综复杂的缘故,需要连续不断执行好几个月的程序,且程序必须要有容错的机制。对于如何执行适当的错误检查以及如何从函数内部复原的方法,在我们的脑海中大概早已存有大学教授所坚决陈述的观点。编写可容错的程序代码是应该做的,然而我们仍然屡次地将必须注意的细节视为冗长乏味且省略不做。我们开始变得自满,认为作业系统会「照顾我们」。许多开发者竟然相信内存是无穷尽的,并认为泄漏各种资源是可行的,因为他们知道当处理程序死掉时,作业系统会自动地清理每件事。
许多应用程序确实是以这种方式执行,所导致的结果并不具破坏性,因为应用程序倾向在短时间的执行后就重新启动。然而,服务是持续地执行着;若是省略适当的错误复原机制和清理资源的程序代码会是悲惨的!
笔者的看法是,为了要编写能够执行24小时、甚至一个礼拜的应用程序,唯一的方法就是使用例外处理(Exception handling)。基于这个理由,我极度地建议您要开始熟悉例外处理的技术。例外处理有两种类型:结构化例外处理(Structured exception handling),由Windows作业系统提供的机制;及C++ 例外处理,由编译器(Compiler)提供的机制。两者皆可让您编写那些无法预测以及例如从违法存取、除以零、堆叠溢位(Stack overflow)等硬体错误中复原的程序代码。
这两种例外处理机制可以在不同的情形下使用,且幸运的是Microsoft Visual C++ 允许我们在单一的应用程序中交替地使用它们。本书中的范例程序示范了一些设计健全的服务器应用程序的概念,有很多是大量地使用例外处理的方式。假如您想要获得更多有关例外处理的资讯,请参考《Programming Applications for Microsoft Windows, Fourth Edition》(Jeffrey Richter, Microsoft Press, 1999)。
在服务中适当的使用C++ 可使您更容易的编写程序。在C++ 类别中封装简单的Windows物件,对以下几个原因来说是有帮助的,本书中有许多范例应用程序正好是因为这些原因而使用了C++ 类别。
- 放置程序代码以结束在C++ 类别解构函数(Destructor)中的物件,编译器会确保物件被清除。
- 将呼叫到C++ 类别方法内的Windows函数封装起来,可让您适当的执行Windows函数呼叫。
- 经由C++ 类别方法来呼叫Windows函数,可让您在某个地方放置一些检查和核对的机制。使它能够更容易在您的程序代码中发现错误。
- C++ 类别替您减少了编写程序的数量,使您的程序代码更易读且易于维护。
- 经由使用C++ 类别可以重覆利用程序代码。假如您使用了C++ 样版类别(Template Class),在维护型别安全的同时,您可以创造一般的解决办法。
可伸缩性(Scalability)与效能
在早期的程序设计方法中,开发者只能取得有限的系统资源。这强迫开发者实行一些狡猾且无法维护的演算法,以尽可能地增加系统效能。现今的电脑系统效能和储存能力已经大大地提升,所以允许开发者设计更简单且易于维护的演算法。
可惜的是这些发展也使得开发者变得懒惰。我知道有一些不经过考虑就将那些只需要几KB(Kilobytes)的任务分配到使用MB(Megabytes)储存器的最高阶开发者。我也了解使用Mutex物件开发者的评论部份会比满足部分多。这些开发者完全没有关心到参照Mutex的函数需要经由使用者模式(User-Mode)到核心模式(Kernel-Mode)之来回转换时间的情形-大约需要1000 CPU周期,而那甚至并不包含必须在核心模式执行一次的程序代码。相形之下,关键性部分通常会完全停留在使用者模式且需要大约100 CPU周期去执行。
越来越多人开始使用电脑,因为服务器提供了可改善我们生活品质的资讯。调查显示出使用者对反应迟钝的服务器容易感到失望,然后会到别处找出想要的资讯,这些都表示了损失生意和收入的情形。同样地,拥有一个反应迟钝服务器的企业使员工感到失望,最后会影响生产力-这也会损失生意和收入。
在某些情况下,您可以经由增加更多的电脑来改进服务器的反应速度。然而,由于许多原因,当您在单一的机器上执行您的服务器软件时,通常情况较佳。首先,编写那些本身有许多部分在不同机器上执行的服务器软件比编写在单一机器上执行的软件更难;其次,管理并联机器的复杂性常常远大于线性增加的速率。再者,您可能会采用数个极可能失败的论点,使得更难找出且改正错误。您不会永远只使用单机服务器,但当您在实行服务器程序代码时应该记住这个想法。
单机服务器可以经由增加内存、处理器、磁盘储存器、网路卡等来增加效能,但前提是以有效的方法在服务器程序代码中实行这些资源的使用时。例如,一个使用每个客户端一个线程来连接的服务器,其效能通常不会仅仅因为加装内存的数量而增加。然而,如果使用效率高的集区多工缓冲处理(Thread-pooling)演算法来设计同样的服务器,则服务器的效能将会提升到与加装额外资源的情形相同。
编写高度可伸缩性的应用程序需要许多的纪律。在每一阶段,您必须考虑所编写程序代码的每一个交错情形:
- 我所编写的程序代码是否已避免了使用者模式到核心模式的转换?
- 我有没有做到连续性内存存取以至于不用测量快取内存的范围?
- 我有没有确保变数被正确的排列?
- 我有没有使用线程代替程序以减少使用系统资源?
- 我有没有归还一些可执行的线程数量以避免浪费context转换?
- 当等待设备I/O操作完成时,我有没有使线程做有用的工作?
- 我有没有中止使用ANSI字串,以有利于Unicode字串改善Windows函数的效能?
- 我有没有利用Windows所提供的特征?
希望当您在编写程序代码时能将我这里所提到的问题记住。上述的每一项都会在本书中被详细的讨论。
管理
不同的组织有不同的需求。软件开发者试着经由设计系统和接触大量结构设定的应用软件以符合这些需求。这么多的选择使得大多数使用者和系统管理者不知所措。由于大多数的系统是经由许多连接在一起的电脑所构成的事实,使得问题被合并了。此外,有安装机器之专门知识的人通常并非实际上坐在机器前的人,于是导致延长了机器的停工时间。
尤其服务器应用程序通常会在壁橱似房间里的电脑上执行,所以您的服务器软件应该要有远端管理的能力。换言之,当管理者坐在另一台机器前,她应该要能够启动某一台机器并且停止执行服务。管理者也应该能够修改远端安装服务器的许多设定。
从服务器的观点来看,您的服务可能会需要向管理者传达资讯。例如,您可能会要您的服务器报告连接的客户端数量及处理一般客户端请求所需要的时间总数。您的服务器也可能需要报告例外事件,例如磁盘空间不足或企图破坏安全性的情形。
Microsoft已经增加许多基础建设到Windows中,使得远端管理毫不费力。Service Control Manager(或SCM,发音为「scum」)可以控制某个服务的执行。登录(Registry)被用来储存服务器的安装设定。效能监视器工具可让服务器将例外事件发生的情形通知管理者。所有这些工具皆有经过计划的介面且可以被远端存取。适当的使用这些工具将使得管理者的生活更安逸。上述的每个工具都会在本书中被讨论。
Microsoft Management Console(MMC)是以MMC.exe执行的一个工具,它为系统管理者提供了一次购足的功能。它将会存取整合到系统的各种元件中,包括被安装的服务。您也应该能够经由MMC来安装您的服务。
安全
每天都有越来越多的电脑被连接在一起,其利益是显着的。现在,电脑(和人)可以互相通讯,结合不同来源的资讯而产生几年前想像不到的结果。我确信阅读本书的每一位都相信分享资讯对所有的人类而言是一件好事。
但我绝对相信我们都有某些想要保留隐私的资讯。我们生活在某些人会做出恶意行为的世界,例如从磁盘设备或资料库删除资讯的重要部分。甚至有些并非有目的的恶意行为,有些人可能会不小心地删除或改变了某些资讯的重要部分。要满足安全的问题,我们的服务器必须要对产生资讯的客户端做验证,且确保未被认证操作的客户端的执行权限是被限制的。如果服务器软件不正确地处理安全确认程序,其结果会是悲惨的。可惜的是我们每天还是听到了一些相关的报导。
尽管安全的需求每年都在成长,许多开发者仍然忽视它。现今的作业系统,例如Microsoft Windows 2000,提供了极大数量的内建安全特色,使应用程序开发者免于直接在他们的程序代码中处理安全议题。内建的特色给予个别使用者只能在服务器操作环境中透过以前可用的方法来确保文件安全的能力。应用程序代码只需要处理企图打开经由安全保护的文件所导致之「拒绝存取」的结果。
虽然内建的安全使得应用程序开发者的生活更简单,但它并不提供相同的享受给服务开发者。您的服务器是个出入口,它开放系统给外面的世界。假如您不在出入口放置有能力的警卫,将会使服务器软件和它的资料容易受到攻击。最糟的情况中,您可能向外界暴露了整个系统或区域网路。为了防范攻击,您的服务器软件必须包含安全知觉的程序代码。Windows 2000提供了健全的安全架构和函数,大大地简化了服务器开发者的工作。
Windows提供可让软件侦测是谁连上服务器的机制。它也可让您的软件为服务所控制的资料设定存取权限。一旦您知道哪个客户端要存取什么资料,Windows可以执行存取检查以认证这个客户端的请求。利用系统的内建机制使得开发更容易,但当您在设计服务时仍必须考虑到安全议题。除了决定谁可以存取什么之外,当它从一台机器转换到另一台时,Windows也提供了允许加解密资料的特色。
本书的第四篇即专注在Windows的安全特色部份。在这些章节中将会解释Windows的安全架构,以及给予您开发不受网路骇客的攻击与因为人为疏失造成的安全性疑虑之服务器所必需的资讯。
说明
为了合乎Microsoft的「Certified for Windows 2000 Server」及BackOffice程序设计方法的条件,您的服务必需支援Single SignOn(SSO)。Single SignOn是指当使用者首次登入机器时,只需输入一次密码;之后,使用者再也不会被要求输入密码。一旦经过认证,所有的服务即可以合理的使用该使用者的认证资讯。Windows内建的安全机制(尤其是人格化)使您的服务容易支援统一的登录过程。下列的网站有Microsoft认证及程序设计方法的额外资讯:
http://msdn.microsoft.com/winlogo/
http://www.microsoft.com/backoffice/designed/
为什么开发服务?
当您在设计伺服端应用程序时,可以让它以相同的方法执行任何应用程序,如同一个简单的Windows应用程序。然而,Windows支援一种称作service的特殊型态应用程序。服务是一种标准的Windows应用程序,包括附加的基础建设。这个附加的基础建设允许使用服务控制管理员(Service Control Manager, SCM)(一种内建在执行Windows机器上的元件)加以控制并且监视应用程序的执行。
SCM可让管理者在本机或远端启动、停止、暂停以及继续服务的执行。SCM也可以监视且自动重新启动服务,假如服务意外地终止,它可以重新启动机器。所有附加在Windows上的伺服端应用程序都会被视为服务般执行。MMC提供了Services嵌入式管理单元,可允许管理者经由使用一般的介面来控制所有已被安装的服务。
由于这些原因,我强烈的建议您将附加的基础建设到加到您的应用程序里,并将它转成服务。操作的方式将在本书的第叁章做详细的叙述。而控制SCM标准介面的内容则在第四章叙述。
说明
为了合乎Microsoft BackOffice程序设计方式的条件,您的伺服端应用程序必须如同Windows服务般的执行。
网路通讯
有许多服务是在单机上的应用程序,它存放在一个单一的机器上,监督着许多动作的执行。以下是一些附加在Windows中的服务:
- 不断电供电系统(Uninterruptible Power Supply, UPS) 当电源中断时,UPS设备会发出警告。
- Indexing Service 负责监督硬盘上的文件异动情形,所以它可以对一个主要的目录做更新以及快速搜寻的动作。
- Windows Installer 管理机器上的安装资讯、修复以及软件的移除等。
- RunAs Service 允许一个使用者可以使用不同的安全身份来执行应用程序。
- Task Scheduler 在特定的时间大量产生应用程序。
以上所提的服务没有一个需要使用到网路的通讯,然而有许多服务会需要。以下举出一些附加在Windows上之网路服务的例子:
所有的网路通讯机制都可以很复杂,每一种都有其差别。事实上,本书只会讨论Socket、RPC以及COM的部份。因此,我决定不在本书中提出有关通讯机制的内容,在此建议您去寻找其他相关的资讯。上述所提的通讯机制中,命名管道(Name Pipe)是最容易了解以及使用的,所以本书大部份需用到网路通讯部份的范例程序皆是以命名管道(Name Pipe)为例。
说明
您可以使用原生的(Native)网路通讯协定(IPX/SPS/NetBIOS、NetBEUI、TCP/IP、AppleTalk等等)来当做您所使用的通讯机制;然而,Microsoft强烈反对您使用他们,因为它会使您的应用程序受限于特定的通讯协定。反之,使用较高阶的机制(Mailsolt、Pipes、Sockets、RPC以及COM)可以在所有原生的通讯协定上工作,并使您的程序可以在不同的环境下执行。
- Event Log 传送事件日志记录的要求至远端机器上,允许远端管理。
- NetMeeting Remote Desktop Sharing 允许远端使用者使用Microsoft NetMeeting工具去观看及控制您的机器。
- Server 允许远端使用者存取共用资料夹、打印机以及命名管道(Name Pipe)。
- Workstation 允许您的机器连接到远端机器上的共用资料夹、打印机以及命名管道(Name Pipe)。
假如您的服务需要使用到网路通讯的功能,您可以使用以下所提的机制:
- MailSlot 允许在执行Windows的机器之间让未定义资料做单向的行程通讯(Interprocess Communication, IPC)。
- 命名管道(Name Pipe) 允许在执行Windows的机器之间让未定义资料做双向的行程通讯。
- Socket 允许在执行任何支援Socket的作业系统机器之间让未定义资料做双向的行程通讯。
- 远端程序呼叫(Remote Procedure Call, RPC) 允许在执行任何支援RPC的作业系统机器之间让已定义资料做双向的行程通讯。
- 元件物件模型(Component Object Model, COM) 允许在执行任何支援COM的作业系统机器之间让已定义资料做双向的行程通讯。