2006年04月18日

  1965IBM7044机上首次实现虚拟技术以来,这一名词对于计算机世界来说已经不是一个新名词。但当我们面对硬分区、软分区、逻辑分区、Solaris ContainerVMwareXenVirtual Server等名词的时候,真能准确分辨出它们的应用特性么?

  40多年来,虚拟技术已经发展到这样的一个阶段—我们每天享受着这一技术的好处,但并未意识到—例如当你打开Windows Server的命令行窗口时,便是在Windows上使用着虚拟机的功能。既然已经如此成熟,为什么我们还要关心虚拟技术?原因很简单,虚拟技术的概念虽然成熟,但它的重要性还尚未引起国内用户和读者的足够重视,尤其是在服务器和存储领域,虚拟技术将发挥决定性的作用。

何谓虚拟?

  当我们尝试着理解虚拟技术时,首先必须要理解“虚拟”的概念。“虚拟”这个词最早来源于光学,用于理解镜子里的物体。现在,“虚拟”这个词已经经过演化,用来描述任何真实物体的模拟了,例如分区、虚拟机、虚拟内存、虚拟磁盘和虚拟现实。在讨论虚拟技术的时候,使用“虚拟”这个词,是因为我们希望虚拟机看起来和工作起来都和真正的机器一模一样。这意味着,虚拟机并不是真正的机器,但是它能像真正的机器一模一样地工作。

  实际上,从原理上看,所有虚拟技术虚拟的是指令集。所有的IT设备,不管是PC、服务器还是存储,都有一个共同点:它们被设计用来完成一组特定的指令。这些指令组成一个指令集。对于虚拟技术而言,“虚拟”实际上就是指的这些指令集。虚拟机有许多不同的类型,但是它们有一个共同的主题就是模拟一个指令集的概念。每个虚拟机都有一个用户可以访问的指令集。虚拟机把这些虚拟指令“映射”到计算机的实际指令集。

  定义完“虚拟”的概念,我们可以清楚知道,目前所能看到的硬分区、软分区、逻辑分区、Solaris ContainerVMwareXen、微软Virtual Server2005这些虚拟技术,都是同样的原理,只是虚拟指令集所处的位置不同而已。

  按照虚拟层所处位置的不同,目前所有的虚拟技术大致可以分为硬件虚拟、逻辑虚拟、软件虚拟和应用虚拟四种类型。

虚拟原动力:服务器效率

  目前,一般企业内的服务器仅能达到15%30%的系统处理能力,绝大部分的服务器负载都低于40%,大部分的服务器处理能力并没有得到利用,IT投资回报率偏低。正如41年前IBM研发虚拟技术的出发点,让一台机器尽可能多地让更多用户和应用程序有效使用,一直都是虚拟技术发展的原动力。

  中科院物理所量子模拟科学中心(量子中心)的徐力方研究员对此感触颇深。2002年底,物理所定购了两台满配32Power4IBM p690服务器,一台用于后台作业运算,一台作为登录节点和交互作业运算。但到了20039月,由于研究所的科研项目和学生迅速增加,交互作业节点作业拥挤,导致整机效率下降。

  怎么办?徐力方咨询了IBM的技术人员,得到的答复是可以采用逻辑分区的技术,将登录节点机划分为8/24两个分区,8CPU的分区用于节点登录,另24CPU用于后台作业。但这样做仍然存在问题,因为8CPU又不够交互作业使用,徐力方介绍说,由于科研项目运行的并行程序众多, 其中有学生们自行编写或修改自开放源码,难以避免多数子作业运行完毕,而少数子作业还在运算的情况,这样就会出现计算能力的浪费。如果用逻辑分区把分区细分,又会出现某些项目在细分区上无法计算的情况——分区资源变更又浪费时间。

  这一问题最终得到了圆满解决,200310月,IBM发布了AIX 5L v5.2IBM的工程师随后以动态逻辑分区的方式配置了5个动态分区,高峰时每个研究组各占20%的资源,但闲暇时则每个分区都能调用所有的计算资源,这样,既做到资源的合理分配,又做到了资源的充分利用。

  量子中心的案例中,虽然使用了5个分区,但都采用的是AIX操作系统,那么多操作系统的虚拟应用情况如何?据中国惠普CSG企业服务器产品经理王镝介绍,国内已有实际用户实施了多操作系统虚拟。王镝介绍说,2005年,国内某用户采购了1台配置32颗安腾2处理器的(其中16颗为待激活状态)HP Integrity Superdome服务器,系统先以硬分区技术划分为两个物理分区,然后每个物理分区用vPARHP Virtual Machine技术划分为三类逻辑分区,分别运行社保交易服务器、BEA Weblogic应用服务器、Oracle数据库服务器,分别运行在HP-UXLinux平台上,统一以HP Workload Management管理。这样,当日常白天医保交易繁忙时,可将数据库服务器分区的计算资源调配到社保交易分区,晚上进行批处理业务的时候再调配;而在月末各分区的业务都繁忙时,以iCOD(按需扩容)或TiCOD(购买待激活CPU若干小时的点卡)的方式,将待激活CPU临时调配到各个分区。这样,用户既获得了足够的计算资源和安全性,但又只需较低的成本,保证了投资回报率。

24.jpg

25b.jpg

实施虚拟应注意什么?

  虚拟技术虽然成熟,但实施起来可不能想当然。那么在虚拟实施过程中应该注意些什么?

  中国人民银行清算总中心是IBM大型主机和p系列服务器的老用户,从1992年前后开始使用大型主机,算来也有14年的历史。清算中心开发部的副总经理贝劲松在谈起实施虚拟技术时,认为尤其应该关注两点。

  首先是实施前要对业务系统、对计算资源的需求有明确了解。贝劲松笑谈,清算总中心在这方面是摸索出来的经验。2002年时,清算总中心曾购买了两台配置81.1GHz Power4处理器的IBM eServer p690服务器,当时按照4/2/2的方式划分为了三个逻辑分区,但通过压力测试发现第一个分区负载较小,反而是第二个分区经常过载,于是将分区调整为2/4/2的配置,解决了这个问题。

  其次是实施前要有充分的测试期。贝劲松认为,像银行这样的自行开发业务系统的行业,相对比较容易了解业务系统对计算资源的压力,但即使这样,也需要进行充分测试,如不具备对等配置测试环境,也应在处理能力稍低的同类硬件平台上进行测试。例如,清算总中心2005年购买了8IBM eServer p570(用于生产)和两路的eServer p570 p550(用于测试),分别按照2/60.5/1.5的配置进行分区,在系统的开发期和测试期,p550上的测试数据有助于他们了解业务系统对计算资源的需求。如果是不自行开发业务系统的行业,就更有必要在近似系统上进行测试,毕竟生产系统的安全性是第一位的。

  除了老用户的经验之谈,笔者认为在实施虚拟技术之前,还应参照左表,决定采用哪种虚拟技术最合适。从表中容易得知,如果是简单的单机应用开发,那么采用应用虚拟技术最合适;如果需要开发Web应用,那么软件虚拟技术才能满足需求。

虚拟并非万能

  或许上面的内容会给读者一个错觉,即虚拟技术是如此优异,如果自己的企业还没有使用,将会在未来的竞争中出于劣势,实际上,仍有许多用户还不需要用到虚拟技术。

  国家气象中心是高性能计算机集群和IBM p系列的老用户,尤其是2004年采购的IBM eServer p655集群,以32001.7GHzPower4+处理器实现了10.31TeraFlops/sLinpack性能,牢牢占据着系统中国高性能计算机的头把交椅,如此高性能的系统,气象中心是如何看待分区或者虚拟机技术呢?

  答案出乎笔者先前的预料,据国家气象中心计算机室主任田浩的介绍,他们并没有采用任何分区和虚拟技术。为什么?原因就在于气象行业应用软件的特殊性上。田浩解释说,气象预报主要采用的短期和中期预报,大都采用将预测区域划分为近似正方的栅格(边长越小预报越精确),然后用各种计算模型(如MM5GRAPES等)进行运算。虽然气象中心集群的性能是国内目前最强大的,系统的满载率也接近60%(7×24×365),但如果是24小时的短期预报,目前一般在34小时内得出结果;而中期预报(10天,30公里)则大概需要56小时,这一速度虽然比以前快很多,但还算不上完美——也就是说,目前的整个系统跑生产系统没问题,但富余的资源并不多。田浩笑说,像气象中心这样对计算资源需求永无止境的行业,恐怕是很难体验到虚拟技术的好处的。不过,田浩同时认为,如果气象中心在业务系统的并行算法上能够取得突破,气象中心未来采用虚拟技术的可能性同样存在。

  除了气象预报和石油物探这样对计算资源需求永无止境的高端领域,低端应用当然也不需要用到虚拟技术,倒不是没这样的需求,而是因为通常前端应用的硬件平台性能还不够好,即使是一个勇于尝试的IT主管,也不会轻易在一台单路至强服务器上用VMware GSX Server虚拟出若干LinuxWindows操作系统,把公司的邮件、Web和文件服务整合到一台机器上。

25.jpg

25.jpg

25.jpg

25.jpg

  40多年来,虚拟技术已经发展到这样的一个阶段—我们每天享受着这一技术的好处,但并未意识到—例如当你打开Windows Server的命令行窗口时,便是在Windows上使用着虚拟机的功能。既然已经如此成熟,为什么我们还要关心虚拟技术?原因很简单,虚拟技术的概念虽然成熟,但它的重要性还尚未引起国内用户和读者的足够重视,尤其是在服务器和存储领域,虚拟技术将发挥决定性的作用。

何谓虚拟?

  当我们尝试着理解虚拟技术时,首先必须要理解“虚拟”的概念。“虚拟”这个词最早来源于光学,用于理解镜子里的物体。现在,“虚拟”这个词已经经过演化,用来描述任何真实物体的模拟了,例如分区、虚拟机、虚拟内存、虚拟磁盘和虚拟现实。在讨论虚拟技术的时候,使用“虚拟”这个词,是因为我们希望虚拟机看起来和工作起来都和真正的机器一模一样。这意味着,虚拟机并不是真正的机器,但是它能像真正的机器一模一样地工作。

  实际上,从原理上看,所有虚拟技术虚拟的是指令集。所有的IT设备,不管是PC、服务器还是存储,都有一个共同点:它们被设计用来完成一组特定的指令。这些指令组成一个指令集。对于虚拟技术而言,“虚拟”实际上就是指的这些指令集。虚拟机有许多不同的类型,但是它们有一个共同的主题就是模拟一个指令集的概念。每个虚拟机都有一个用户可以访问的指令集。虚拟机把这些虚拟指令“映射”到计算机的实际指令集。

  定义完“虚拟”的概念,我们可以清楚知道,目前所能看到的硬分区、软分区、逻辑分区、Solaris ContainerVMwareXen、微软Virtual Server2005这些虚拟技术,都是同样的原理,只是虚拟指令集所处的位置不同而已。

  按照虚拟层所处位置的不同,目前所有的虚拟技术大致可以分为硬件虚拟、逻辑虚拟、软件虚拟和应用虚拟四种类型。

虚拟原动力:服务器效率

  目前,一般企业内的服务器仅能达到15%30%的系统处理能力,绝大部分的服务器负载都低于40%,大部分的服务器处理能力并没有得到利用,IT投资回报率偏低。正如41年前IBM研发虚拟技术的出发点,让一台机器尽可能多地让更多用户和应用程序有效使用,一直都是虚拟技术发展的原动力。

  中科院物理所量子模拟科学中心(量子中心)的徐力方研究员对此感触颇深。2002年底,物理所定购了两台满配32Power4IBM p690服务器,一台用于后台作业运算,一台作为登录节点和交互作业运算。但到了20039月,由于研究所的科研项目和学生迅速增加,交互作业节点作业拥挤,导致整机效率下降。

  怎么办?徐力方咨询了IBM的技术人员,得到的答复是可以采用逻辑分区的技术,将登录节点机划分为8/24两个分区,8CPU的分区用于节点登录,另24CPU用于后台作业。但这样做仍然存在问题,因为8CPU又不够交互作业使用,徐力方介绍说,由于科研项目运行的并行程序众多, 其中有学生们自行编写或修改自开放源码,难以避免多数子作业运行完毕,而少数子作业还在运算的情况,这样就会出现计算能力的浪费。如果用逻辑分区把分区细分,又会出现某些项目在细分区上无法计算的情况——分区资源变更又浪费时间。

  这一问题最终得到了圆满解决,200310月,IBM发布了AIX 5L v5.2IBM的工程师随后以动态逻辑分区的方式配置了5个动态分区,高峰时每个研究组各占20%的资源,但闲暇时则每个分区都能调用所有的计算资源,这样,既做到资源的合理分配,又做到了资源的充分利用。

  量子中心的案例中,虽然使用了5个分区,但都采用的是AIX操作系统,那么多操作系统的虚拟应用情况如何?据中国惠普CSG企业服务器产品经理王镝介绍,国内已有实际用户实施了多操作系统虚拟。王镝介绍说,2005年,国内某用户采购了1台配置32颗安腾2处理器的(其中16颗为待激活状态)HP Integrity Superdome服务器,系统先以硬分区技术划分为两个物理分区,然后每个物理分区用vPARHP Virtual Machine技术划分为三类逻辑分区,分别运行社保交易服务器、BEA Weblogic应用服务器、Oracle数据库服务器,分别运行在HP-UXLinux平台上,统一以HP Workload Management管理。这样,当日常白天医保交易繁忙时,可将数据库服务器分区的计算资源调配到社保交易分区,晚上进行批处理业务的时候再调配;而在月末各分区的业务都繁忙时,以iCOD(按需扩容)或TiCOD(购买待激活CPU若干小时的点卡)的方式,将待激活CPU临时调配到各个分区。这样,用户既获得了足够的计算资源和安全性,但又只需较低的成本,保证了投资回报率。

24.jpg

25b.jpg

实施虚拟应注意什么?

  虚拟技术虽然成熟,但实施起来可不能想当然。那么在虚拟实施过程中应该注意些什么?

  中国人民银行清算总中心是IBM大型主机和p系列服务器的老用户,从1992年前后开始使用大型主机,算来也有14年的历史。清算中心开发部的副总经理贝劲松在谈起实施虚拟技术时,认为尤其应该关注两点。

  首先是实施前要对业务系统、对计算资源的需求有明确了解。贝劲松笑谈,清算总中心在这方面是摸索出来的经验。2002年时,清算总中心曾购买了两台配置81.1GHz Power4处理器的IBM eServer p690服务器,当时按照4/2/2的方式划分为了三个逻辑分区,但通过压力测试发现第一个分区负载较小,反而是第二个分区经常过载,于是将分区调整为2/4/2的配置,解决了这个问题。

  其次是实施前要有充分的测试期。贝劲松认为,像银行这样的自行开发业务系统的行业,相对比较容易了解业务系统对计算资源的压力,但即使这样,也需要进行充分测试,如不具备对等配置测试环境,也应在处理能力稍低的同类硬件平台上进行测试。例如,清算总中心2005年购买了8IBM eServer p570(用于生产)和两路的eServer p570 p550(用于测试),分别按照2/60.5/1.5的配置进行分区,在系统的开发期和测试期,p550上的测试数据有助于他们了解业务系统对计算资源的需求。如果是不自行开发业务系统的行业,就更有必要在近似系统上进行测试,毕竟生产系统的安全性是第一位的。

  除了老用户的经验之谈,笔者认为在实施虚拟技术之前,还应参照左表,决定采用哪种虚拟技术最合适。从表中容易得知,如果是简单的单机应用开发,那么采用应用虚拟技术最合适;如果需要开发Web应用,那么软件虚拟技术才能满足需求。

虚拟并非万能

  或许上面的内容会给读者一个错觉,即虚拟技术是如此优异,如果自己的企业还没有使用,将会在未来的竞争中出于劣势,实际上,仍有许多用户还不需要用到虚拟技术。

  国家气象中心是高性能计算机集群和IBM p系列的老用户,尤其是2004年采购的IBM eServer p655集群,以32001.7GHzPower4+处理器实现了10.31TeraFlops/sLinpack性能,牢牢占据着系统中国高性能计算机的头把交椅,如此高性能的系统,气象中心是如何看待分区或者虚拟机技术呢?

  答案出乎笔者先前的预料,据国家气象中心计算机室主任田浩的介绍,他们并没有采用任何分区和虚拟技术。为什么?原因就在于气象行业应用软件的特殊性上。田浩解释说,气象预报主要采用的短期和中期预报,大都采用将预测区域划分为近似正方的栅格(边长越小预报越精确),然后用各种计算模型(如MM5GRAPES等)进行运算。虽然气象中心集群的性能是国内目前最强大的,系统的满载率也接近60%(7×24×365),但如果是24小时的短期预报,目前一般在34小时内得出结果;而中期预报(10天,30公里)则大概需要56小时,这一速度虽然比以前快很多,但还算不上完美——也就是说,目前的整个系统跑生产系统没问题,但富余的资源并不多。田浩笑说,像气象中心这样对计算资源需求永无止境的行业,恐怕是很难体验到虚拟技术的好处的。不过,田浩同时认为,如果气象中心在业务系统的并行算法上能够取得突破,气象中心未来采用虚拟技术的可能性同样存在。

  除了气象预报和石油物探这样对计算资源需求永无止境的高端领域,低端应用当然也不需要用到虚拟技术,倒不是没这样的需求,而是因为通常前端应用的硬件平台性能还不够好,即使是一个勇于尝试的IT主管,也不会轻易在一台单路至强服务器上用VMware GSX Server虚拟出若干LinuxWindows操作系统,把公司的邮件、Web和文件服务整合到一台机器上。

25.jpg

25.jpg

25.jpg

25.jpg

归类于: Programming — freed @ 2:41 pm 评论(0)
2006年04月13日
自己动手写操作系统
作者:伊梅 本文选自:开放系统世界——赛迪网 2002年10月10日


自由软件社区是一个充满自由和梦想的地方,在10余年的时间里它创造了一个又一个奇迹。然而,这些奇迹的创造者不只是Stallman,也不只是Linus Torvalds,而是活跃在世界各地的不计其数的开发人员。

在使用各种功能强大的自由软件时,我总会对其开发者充满崇敬之情,期盼有朝一日自己也能成为他们中的一员。很多对自由社区充满向往之情的人,虽然也想努力融身于其中,但又不知该怎么做。那么,就请与我们一起从编写一个简单的操作系统开始吧!

我们要做的事情


有人可能担心自己既没有学过计算机原理,也没有学过操作系统原理,更不懂汇编语言,对C语言也一知半解,能写操作系统吗?答案是没问题。我将带大家一步一步完成自己的操作系统。当然如果学一学上述内容再好不过。

首先要明确处理器(也就是CPU)控制着计算机。对PC而言,启动的时候,CPU都处在实模式状态,相当于只是一个Intel 8086处理器。也就是说,即使你现在拥有一个奔腾处理器,它的功能也只能是8086级别。从这一点上来讲,可以使用一些软件把处理器转换到著名的保护模式。只有这样,我们才可以充分利用处理器的强大功能。

编写操作系统开始是对BIOS控制,取出存储在ROM里的程序。BIOS是用来执行POST(Power On Self Test,自检)的。自检是检查计算机的完整性(比如外设是否工作正常、键盘是否连接等)。这一切完成以后,你就会听到PC喇叭发出一声清脆的响声。如果一切正常,BIOS就会选择一个启动设备,并且读取该设备的第一扇区(即启动扇区),然后控制过程就会转移到指定位置。启动设备可能是一个软盘、光盘、硬盘,或者其它所选择的设备。在此我们把软盘作为启动设备。如果我们已经在软盘的启动扇区里写了一些代码,这时它就被执行。因此,我们的目的很明确,就是往软盘的启动扇区写一些程序。

首先使用8086汇编来写一个小程序,然后将其拷贝至软盘的启动扇区。为了实现拷贝,要写一个C程序。最后,使用软盘启动计算机。

需要的工具


● as86:这是一个汇编程序,它负责把写的代码转换成目标文件。

● ld86:这是一个连接器,as86产生的目标代码由它来转换成真正的机器语言。机器语言是8086能够解读的形式。

● GCC:著名的C编程器。因为我们需要写一个C程序将自己的OS转移到软盘中。

● 一张空软盘:它用于存储编写的操作系统,也是启动设备。

● 一台装有Linux的计算机:这台机器可以很旧,386、486都可以。

在大部分标准Linux发行版中都会带有as86和ld86。在我使用的Red Hat 7.3中就包含有这两个工具,并且在默认的情况下,它已经安装在机器里。如果使用的Linux没有这两个工具,可以从网上下载(http://www.cix.co.uk/~mayday/),这两个工具都包含在一个名为bin86的软件包中。此外,有关的文档也可以在网上获得(www.linux.org/docs/ldp/howto/Assembly-HOWTO/as86.html)。

开始工作


使用一个你喜欢的编辑器输入以下内容:


entry start
start:
      mov ax,#0xb800
      mov es,ax
      seg es
      mov [0],#0x41
      seg es
      mov [1],#0x1f
loop1: jmp loop1



这是as86可以读懂的一段汇编程序。第一个句子指明了程序的入口点,声明整个过程从start处开始。第二行指明了start的位置,说明整个程序要从start处开始执行。0xb800是显存的开始地址。#表明其后是一个立即数。执行语句:


mov ax,#oxb800



ax寄存器的值就变为0xb800,这就是显存的地址。下面再将这个值移至es寄存器,es是附加段寄存器。请记住8086有一个分段的体系结构。它的各段寄存器为代码段、数据段、堆栈段和附加段,对应的寄存器名称分别为cs、ds、ss和es。事实上,我们把显存地址送入了附加段,因此,任何送入附加段的东西都会被送到显存中。

要在屏幕上显示字符,就需要向显存中写两个字节。前一个是所要显示字符的ASCⅡ值,第二个字节表示该字符的属性。属性包括字符的前景色、背景色及是否闪烁等等。seg es指明下一个将要执行的指令是指向es段的。所以,我们把值0×41(在ASCⅡ中表示的字符是A)送到显存的第一个字节中。接下来要把字符的属性送到下一个字节当中。在此输入的是0×1f,该属性指的是在蓝色背景下显示白色的字符。因此,如果执行这个程序,就可以在屏幕上得到显示在蓝底上的一个白色的A。接着是一个循环。因为在执行完显示字符的任务后,要么让程序结束,要么使用一个循环使其永远运行下去。把该文件命名为boot.s,然后存盘。

此处显存的概念说得不是很清楚,有必要进一步解释一下。假设屏幕由80列×25行组成,那么第一行就需要160字节,其中一个字节用于表示字符,另外一个字节用于表示字符的属性。如果要在第三行显示某一字符的话,就要跳过显存的第0和1字节(它们是用于显示第1列的),第2和3字节(它们是用于显示第2列的),然后把需要显示字符的ASCⅡ码值入第4字节,把字符的属性写入第5字节。

把程序写至启动扇区


下面写一个C程序,把我的操作系统写入软盘第一扇区。程序内容如下:


#include  /* unistd.h 需要这个文件 */
#include     /* 包含有read和write函数 */
#include 
int main()
{
  char boot_buf[512];
   int floppy_desc, file_desc;
  file_desc = open("./boot", O_RDONLY);
  read(file_desc, boot_buf, 510);
  close(file_desc);
  boot_buf[510] = 0x55;
  boot_buf[511] = 0xaa;
  floppy_desc = open("/dev/fd0", O_RDWR);
  lseek(floppy_desc, 0, SEEK_CUR);
  write(floppy_desc, boot_buf, 512);
  close(floppy_desc);
}



首先,以只读模式打开boot文件,然后在打开文件时把文件描述符复制到file_desc变量中。从文件中读取510个字符,或者读取直到文件结束。在本例中由于文件很小,所以是读取至文件结束。然后关闭文件。

最后4行代码打开软盘驱动设备(一般来说是/dev/fd0)。使用lseek找到文件开始处,然后从缓冲中向软盘写512个字节。

在read、write、open和lseek的帮助页中,可以看到与函数所有有关的参数及其使用方法。程序中有两行比较难懂:


boot_buf[510] = 0x55;
boot_buf[511] = 0xaa;



该信息是用于BIOS的,如果它识别出该设备是一个可启动的设备,那么在第510和511的位置,该值就应该是0×55和0xaa。程序会把文件boot读至名为boot_buf的缓冲中。它要求改变第510和第511字节,然后把boot_buf写至软盘之上。如果执行代码,软盘上的前512字节就包含了启动代码。最后,把文件存为write.c。

编译运行


使用下面的命令把文件变为可执行文件:


as86 boot.s -o boot.o
ld86 -d boot.o -o boot
cc write.c -o write



首先将boot.s文件编译成目标文件boot.o,然后将该文件连接成最终的boot文件。最后C程序编译成可执行的write文件。

插入一个空白软盘,运行以下程序:


./write



重新启动电脑,进行BIOS的界面设置,并且把软盘设为第一个启动的设备。然后插入软盘,电脑从软盘上启动。

启动完成后,在屏幕上可以看到一个字母A(蓝底白字),启动速度很快,几乎是在瞬间完成。这就意味着系统已经从我们制作的软盘上启动了,并且执行了刚才写入启动扇区的程序。现在,它正处在一个无限循环的状态。所以,如果想进入Linux,必需拿掉软盘,并且重启机器。

至此,这个操作系统就算完成了,虽然它没有实现什么功能,但是它已经可以启动机器了。

下一期我将在这个启动扇区程序里加入一些代码,使它可以做一些比较复杂的事情(比如使用BIOS中断、保护模式切换等等)。

 

上一期,我讲述了如何在软盘的启动扇区写一些代码,然后再从软盘启动的过程。制作好一个启动扇区,在切换到保护模式之前,我们还应该知道如何使用BIOS中断。BIOS中断是一些由BIOS提供的、为了使操作系统的创建更容易的低级程序。在本文中,我们将学习处理BIOS的中断。

为什么要用BIOS


BIOS会把启动扇区拷贝至RAM中,并且执行这些代码。除此之外,BIOS还要做很多其它的事情。当一个操作系统刚开始启动时,系统中并没有显卡驱动、软盘驱动等任何驱动程序。因此,启动扇区中不可能包含任何一个驱动程序,我们要采取其它的途径。这个时候,BIOS就可以帮助我们了。BIOS中包含有各种可以使用的程序,包括检测安装的设备、控制打印机、计算内存大小等用于各种目的的程序。这些程序就是所说的BIOS中断。

如何调用BIOS中断


在一般的程序设计语言中,函数的调用是一件非常容易的事情。比如在C语言中,如果有一个名为display的程序,它带有两个参数,其中参数noofchar表示显示的字符数,参数attr表示显示字符的属性。那么要调用它,只需给出程序的名称即可。对于中断的调用,我们使用的是汇编语言中的int指令。

比如,在C语言中要显示一些东西时,使用的指令如下所示:


display(nofchar,attr);



而使用BIOS时,要实现相同功能使用的指令如下:


int 0x10



如何传递参数


在调用BIOS中断之前,我们需要先往寄存器中送一些特定的值。假设要使用BIOS的中断13h,该中断的功能是把数据从软盘传送至内存之中。在调用该中断之前,要先指定拷贝数据的段地址,指定驱动器号、磁道号、扇区号,以及要传送的扇区数等等。然后,就要往相应的寄存器送入相应的值。在进行下面的步骤前,读者有必要对这一点有比较明确地认识。

此外,一个比较重要的事实是同一个中断往往可以实现各种不同的功能。中断所实现的确切功能取决于所选择的功能号,功能号一般都存在ah寄存器之中。比如中断13h可以用于读磁盘、写磁盘等功能,如果把3送入ah寄存器中,那么中断选择的功能就是写磁盘;如果把2送入ah寄存器中,选择的功能则是读磁盘等。

我们要做的事情


这次我们的源代码由两个汇编语言程序和一个C程序组成。第一个汇编文件是引导扇区的代码。在引导扇区中,我们写的代码是要把软盘中第二扇区拷贝至内存段的0×500处(地址是0×5000,即偏移地址为0)。这时我们需要使用BIOS的中断13h。这时启动扇区的代码就会把控制权转移至0×500处。在第二个汇编文件中,代码会使用BIOS中断10h在屏幕上显示一个信息。C程序实现的功能则是把可执行的文件1拷贝至启动扇区,把可执行的文件2拷贝至软盘的第二扇区。

启动扇区代码


使用中断13h,启动扇区把软盘第二扇区里的内容加载至内存的0×5000处(段地址为0×500)。下面的代码是用于实现这一目的的代码,将其保存至文件sbect.s中。


LOC1=0x500
entry start
start:
  mov ax,#LOC1
  mov es,ax
  mov bx,#0
  mov dl,#0
  mov dh,#0
  mov ch,#0
  mov cl,#2
  mov al,#1
  mov ah,#2
  int 0x13
  jmpi 0,#LOC1



上面代码第一行类似于一个宏。接下去的两行则是把值0×500加载至es寄存器中,这是软盘上第二扇区代码将拷贝到的地方(第一扇区是启动扇区)。这时,把段内的偏移设为0。

接下来把驱动器号送入dl寄存器中,其中磁头号送入dl寄存器中,磁道号送入ch寄存器中,扇区号送入cl寄存器中,扇区数送入al寄存器之中。我们想要实现的功能是把扇区2、磁道号为0、驱动器号为0的内容送至段地址0×500处。所有这些参数都和1.44MB的软盘相对应。

把2送入ah寄存器中,是选择了由中断13h提供的相应功能,即实现从软驱转移数据的功能。

最后调用中断13h,并且转至偏移为0的段地址0×500处。

第二个扇区的代码


第二个扇区中的代码如下所示(把这些代码保存至文件sbect2.s之中):


entry start
start:
  mov     ah,#0x03
  xor     bh,bh
  int     0x10

  mov     cx,#26
  mov     bx,#0x0007
  mov     bp,#mymsg
  mov     ax,#0x1301
  int     0x10

loop1:  jmp     loop1
mymsg:
  .byte  13,10
  .ascii “Operating System is Loading......”



上面代码将被加载至段地址为0×500处,并且被执行。在这段代码中,使用了中断10h来获取目前的光标位置,然后显示信息。

从第3行到第5行用于得到目前光标的位置,在此中断10h选用的是功能3。然后,清除了bh寄存器的内容,并把字符串送至ch寄存器中。在bx中,我们送入了页码及显示的属性。此处,我们想要在黑背景上显示白色的字符。然后,把要显示字符的地址送到bp之中,信息由两个字节组成,其值分别为13的10,它们分别对应回车和LF(换行)的ASCⅡ值。接下来是一个由29个字符组成的串;在下面实现的功能是输出字符串然后移动光标;最后是调用中断,然后进入循环。

C程序代码


C程序的源代码如下所示,将其存储为write.c文件。


#include  /* unistd.h needs this */
#include     /* contains read/write */
#include 
int main()
{
  char boot_buf[512];
  int floppy_desc, file_desc;
  file_desc = open(“./bsect”, O_RDONLY);
  read(file_desc, boot_buf, 510);
  close(file_desc);
  boot_buf[510] = 0x55;
  boot_buf[511] = 0xaa;
  floppy_desc = open(“/dev/fd0”, O_RDWR);
  lseek(floppy_desc, 0, SEEK_SET);
  write(floppy_desc, boot_buf, 512);
  file_desc = open(“./sect2”, O_RDONLY);
  read(file_desc, boot_buf, 512);
  close(file_desc);
  lseek(floppy_desc, 512, SEEK_SET);
  write(floppy_desc, boot_buf, 512);
  close(floppy_desc);
}



在上一期中,我曾经介绍过如何操作能启动的软盘。现在这一个过程稍微有点不同,首先把由bsect.s编译出来的可执行文件bsect拷贝至软盘的启动扇区。然后再把由sect2.s产生的可执行文件sect2拷贝至软盘的第二个扇区。

把上述文件置于同一目录之下,然后分别对其进行编译,方法如下所示:


as86 bsect.s -o bsect.o
ld86 -d bsect.o -o bsect



对sect2.s文件重复以上的操作,得出可执行文件sect2。编译write.c,插入软盘后执行write文件,命令如下所示:


cc write.c -o write
./write



下一步我们要做的事情


从软盘启动以后,可以看到显示出来的字符串。这是使用了BIOS中断来完成的。下一期要做的事情是在这个操作系统中实现实模式向保护模式的转换。

发布时间:2005-1-25

 

在上两期中,我向大家讲述了如何使用Linux提供的开发工具在软盘的启动扇区写一些代码,以及如何调用BIOS的问题。现在,这个操作系统已经越来越接近当年Linus Torvalds的那个具有“历史意义”的Linux内核了。因此,要马上把这个系统切换到保护模式之下。

什么是保护模式


自从1969年推出第一个微处理器以来,Intel处理器就在不断地更新换代,从8086、8088、80286,到80386、80486、奔腾、奔腾Ⅱ、奔腾4等,其体系结构也在不断变化。80386以后,提供了一些新的功能,弥补了8086的一些缺陷。这其中包括内存保护、多任务及使用640KB以上的内存等,并仍然保持和8086家族的兼容性。也就是说80386仍然具备了8086和80286的所有功能,但是在功能上有了很大的增强。早期的处理器是工作在实模式之下的,80286以后引入了保护模式,而在80386以后保护模式又进行了很大的改进。在80386中,保护模式为程序员提供了更好的保护,提供了更多的内存。事实上,保护模式的目的不是为了保护程序,而是要保护程序以外的所有程序(包括操作系统)。

简言之,保护模式是处理器的一种最自然的模式。在这种模式下,处理器的所有指令及体系结构的所有特色都是可用的,并且能够达到最高的性能。

保护模式和实模式


从表面上看,保护模式和实模式并没有太大的区别,二者都使用了内存段、中断和设备驱动来处理硬件,但二者有很多不同之处。我们知道,在实模式中内存被划分成段,每个段的大小为64KB,而这样的段地址可以用16位来表示。内存段的处理是通过和段寄存器相关联的内部机制来处理的,这些段寄存器(CS、DS、SS和ES)的内容形成了物理地址的一部分。具体来说,最终的物理地址是由16位的段地址和16位的段内偏移地址组成的。用公式表示为:

物理地址=左移4位的段地址+偏移地址。

在保护模式下,段是通过一系列被称之为“描述符表”的表所定义的。段寄存器存储的是指向这些表的指针。用于定义内存段的表有两种:全局描述符表(GDT)和局部描述符表(LDT)。GDT是一个段描述符数组,其中包含所有应用程序都可以使用的基本描述符。在实模式中,段长是固定的(为64KB),而在保护模式中,段长是可变的,其最大可达4GB。LDT也是段描述符的一个数组。与GDT不同,LDT是一个段,其中存放的是局部的、不需要全局共享的段描述符。每一个操作系统都必须定义一个GDT,而每一个正在运行的任务都会有一个相应的LDT。每一个描述符的长度是8个字节,格式如图3所示。当段寄存器被加载的时候,段基地址就会从相应的表入口获得。描述符的内容会被存储在一个程序员不可见的影像寄存器(shadow register)之中,以便下一次同一个段可以使用该信息而不用每次都到表中提取。物理地址由16位或者32位的偏移加上影像寄存器中的基址组成。实模式和保护模式的不同可以从图1和图2中很清楚地看出来。

此外,还有一个中断描述符表(IDT)。这些中断描述符会告诉处理器到那里可以找到中断处理程序。和实模式一样,每一个中断都有一个入口,但是这些入口的格式却完全不同。因为在切换到保护模式的过程中没有使用到IDT,所以在此就不多做介绍了。

进入保护模式


80386有4个32位控制寄存器,名字分别为CR0、CR1、CR2和CR3。CR1是保留在未来处理器中使用的,在80386中没有定义。CR0包含系统的控制标志,用于控制处理器的操作模式和状态。CR2和CR3是用于控制分页机制的。在此,我们关注的是CR0寄存器的PE位控制,它负责实模式和保护模式之间的切换。当PE=1时,说明处理器运行于保护模式之下,其采用的段机制和前面所述的相应内容对应。如果PE=0,那么处理器就工作在实模式之下。

切换到保护模式,实际就是把PE位置为1。为了把系统切换到保护模式,还要做一些其它的事情。程序必须要对系统的段寄存器和控制寄存器进行初始化。把PE位置1后,还要执行跳转指令。过程简述如下:

1.创建GDT表;

2.通过置PE位为1进入保护模式;

3.执行跳转以清除在实模式下读取的任何指令。

下面使用代码来实现这个切换过程。

需要的东西


◆ 一张空白软盘

◆ NASM编译器

下面是整个程序的源代码:


org 0x07c00; 起始地址是0000:7c00
jmp short begin_boot   ; 跳过其它的数据,跳转到引导程序的开始处
bootmesg db "Our OS boot sector loading ......"
pm_mesg  db "Switching to protected mode ...."
dw 512  ; 每一扇区的字节数
db 1  ; 每一簇的扇区数
dw 1  ; 保留的扇区号
db 2
dw 0x00e0
dw 0x0b40
db 0x0f0
dw 9
dw 18
dw 2  ; 读写扇区号
dw 0  ; 隐藏扇区号
print_mesg :
   mov ah,0x13  ; 使用中断10h的功能13,在屏幕上写一个字符串
   mov al,0x00  ; 决定调用函数后光标所处的位置
   mov bx,0x0007    ; 设置显示属性
   mov cx,0x20  ; 在此字符串长度为32
   mov dx,0x0000    ; 光标的起始行和列
   int 0x10  ; 调用BIOS的中断10h
   ret  ; 返回调用程序
get_key :
   mov ah,0x00
   int 0x16  ; Get_key使用中断16h的功能0,读取下一个字符
   ret
clrscr :
   mov ax,0x0600  ; 使用中断10h的功能6,实现卷屏,如果al=0则清屏
   mov cx,0x0000  ; 清屏
   mov dx,0x174f  ; 卷屏至23,79
   mov bh,0  ; 使用颜色0来填充
   int 0x10  ; 调用10h中断
   ret
begin_boot :
   call clrscr   ; 先清屏
   mov bp,bootmesg  ; 提供串地址
   call print_mesg  ; 输出信息
   call get_key	  ; 等待用户按下任一键
bits 16
   call clrscr  ; 清屏
   mov ax,0xb800  ; 使gs指向显示内存
   mov gs,ax  ; 在实模式下显示一个棕色的A
   mov word [gs:0],0x641  ; 显示
   call get_key  ; 调用Get_key等待用户按下任一键
   mov bp,pm_mesg  ;  设置串指针
   call print_mesg  ; 调用print_mesg子程序
   call get_key  ; 等待按键
   call clrscr  ; 清屏
   cli  ; 关中断
   lgdt[gdtr]  ; 加载GDT
   mov eax,cr0
   or al,0x01  ; 设置保护模式位
   mov cr0,eax  ; 将更改后的字送至控制寄存器中
   jmp codesel:go_pm
bits 32
go_pm :
   mov ax,datasel
   mov ds,ax  ; 初始化ds和es,使其指向数据段
   mov es,ax
   mov ax,videosel  ; 初始化gs,使其指向显示内存
   mov gs,ax
   mov word [gs:0],0x741 ; 在保护模式下显示一个白色的字符A
spin : jmp spin  ; 循环
bits 16
gdtr :
   dw gdt_end-gdt-1  ; gdt的长度
   dd gdt  ; gdt的物理地址
gdt
nullsel equ $-gdt  ; $指向当前位置,所以nullsel = 0h
gdt0  ; 空描述符
   dd 0
   dd 0  ; 所有的段描述符都是64位的
codesel equ $-gdt  ; 这是8h也就是gdt的第二个描述符
code_gdt
   dw 0x0ffff  ; 段描述符的界限是4Gb
   dw 0x0000
   db 0x00
   db 0x09a
   db 0x0cf
   db 0x00
datasel equ $-gdt
data_gdt
   dw 0x0ffff
   dw 0x0000
   db 0x00
   db 0x092
   db 0x0cf
   db 0x00
videosel equ $-gdt
   dw 3999
   dw 0x8000  ; 基址是0xb8000
   db 0x0b
   db 0x92
   db 0x00
   db 0x00
gdt_end
times 510-($-$$)  db 0
dw 0x0aa55



把上面的代码存在一个名为abc.asm的文件之中,使用命令nasm abc.asm,将得出一个名为abc的文件。然后插入软盘,输入命令:dd if=abc of=/dev/fd0。该命令将把文件abc写入到软盘的第一扇区之中。然后重新启动系统,就会看到如下的信息:


*Our os booting................
* A (棕色)
* Switching to protected mode....
* A (白色)



对代码的解释


上面给出了所有的代码,下面我对上述代码做一些解释。

◆ 使用的函数

下面是代码中一些函数的说明:

print_mesg 该子程序使用了BIOS中断10h的功能13h,即向屏幕写一字符串。属性控制是通过向一些寄存器中送入不同的值来实现的。中断10h是用于各种字符串操作,我们把子功能号13h送到ah中,用于指明要打印一个字符串。al寄存器中的0说明了光标返回的起始位置,0表示调用函数后光标返回到下一行的行首。如果al为1则表示光标位于最后一个字符处。

显存被分成了几页,在同一时刻只能显示其中的一页。bh指明的是页号;bl则指明要显示字符的颜色;cx指明要显示字符串的长度;dx指明光标的位置(即起始的行和列)。所有相关寄存器初始化完成以后,就可以调用BIOS中断10h了。

get_key 使用中断16h的子功能00h,从屏幕得到下一个字符。

clrscr 该函数使用了中断10h的另外一个子功能06h,用于输出开始前清屏。初始化时给al中送入0。寄存器cx和dx指明要清屏的屏幕范围,在本例中是整个屏幕。寄存器bh指明屏幕填充的颜色,在本例中是黑色。

◆ 其它内容

程序一开始是一条短跳转指令,跳到begin_boot处。在实模式下,在此打印一个棕色的“A”,并且设置一个GDT。切换到保护模式,并且打印一个白色的“A”。这两种模式使用的都是自己的寻址方法。

在实模式下,使用段寄存器gs指示显存位置,我们使用的是CGA显卡(默认基址是0xb8000)。在代码中是不是漏了一个0呢?没有,因为实模式下会提供一个附加的0。这种方式也被80386继承下来了。A的ASCⅡ是0×41,0×06指明了需要一个棕色的字符。该显示会一直持续直至按下任意键。下面要在屏幕上显示一句话,告诉使用者下面马上要进入保护模式了。

启动到保护模式,在进行切换时不希望此时有中断的影响,故要关闭所有的中断(使用cli来实现)。然后对GDT初始化。在整个切换过程中,对4个描述符进行了初始化。这些描述符对代码段(code_gdt)、数据和堆栈段(data_gdt),以及为了访问显存而对显示段进行初始化。此外,还会对一个空描述符进行初始化。

GDT的基址要加载至GDTR系统寄存器之中。gdtr段的第一个字加载的是GDT的大小,在下一个双字中则加载的是基址。然后,lgdt指令把把gdt段加载至GDTR寄存器中。现在已经做好了切换到保护模式前的所有准备。最后一件事情就是把CR0寄存器的PE位置1。不过,即使这样还没有处于保护模式状态之下。

设置了PE位以后,还需要通过执行JMP指令来清除处理器指令预取队列。在80386中,使用指令前总是先将其从内存中取出,并且进行解码和寻址。然而,当进入保护模式以后,预取指令信息(它还处于实地址模式)就无效了。使用JMP指令的目的就是强迫处理器放弃无效的信息。

现在,已经在保护模式下了。那么,如何检测是在保护模式状态之下呢?让我们来看一看屏幕上这个白色的字母A。在这里,使用了数据段选择符(datase1)对数据段和附加段进行了初始化,使用显示段选择符(videose1)对gs进行了初始化。告示的字符“A”其ASCⅡ值和属性位于[gs:0000]处,也就是b8000:0000处。循环语句使得该字符一直在屏幕上显示,直至重新启动系统。

下一步要做的事


现在,这个操作系统已经工作在保护模式下了,但是实际上它并不实现什么具体的功能。你可以在这个基础上为它增加各种操作系统所具有的功能。我们自己动手写操作系统到此也就告一段落。

发布时间:2005-1-25
归类于: Programming — freed @ 3:48 pm 评论(0)
如何把图片存入到数据库(ZZ)

选择自 capsicum29 的 Blog

如果你不希望别人轻易地在其他站点引用你的图片,你可以把图片存入在数据库.下面介绍的是主要有二个:1如何把图片上传到数据库,2如何显示数据库并加上验证.
     首先我们先来熟悉一下将要使用的对象方法。我们用来获取上一个页面传递过来的数据一般是使用request对象。同样的,我们也可以使用request对象 来获取上传上来的文件数据,使用的方法是request.binaryread()。而我们要从 数据库中读出来图片的数据显示到网页上面要用到的方法是: 
request.binarywrite()。在我们得到了图片的数据,要保存到数据库中的时候, 不可以直接使用insert语句对数据库进行操作,而是要使用ado的 appendchunk方法,同样的,读出数据库中的图片数据,要使用getchunk方 法。各个方法的具体语法如下: 

1) request.binaryread语法: 
variant = request.binaryread(count) 
参数 :variant       返回值保存着从客户端读取到数据。 
        count       指明要从客户端读取的数据量大小,这个值小于或者等于使用方法request.totalbytes得到的数据量。 

2)request.binarywrite语法: 
request.binarywrite data 
参数: data         要写入到客户端浏览器中的数据包。 

3) request.totalbytes语法: 
variant = request.totalbytes 
参数 :variant       返回从客户端读取到数据量的字节数。 

4) appendchunk语法 
   将数据追加到大型文本、二进制数据 field 或 parameter 对象。 
object.appendchunk data 
参数: 
object field 或 parameter 对象
data 变体型,包含追加到对象中的数据。 
说明 
使用 field 或 parameter 对象的 appendchunk 方法可将长二进制或字符数据填写到对象中。在系统内存有限的情况下,可以使用 appendchunk 方法对长整型值进行部分而非全部的操作。 

5) getchunk语法 
返回大型文本或二进制数据 field 对象的全部或部分内容 。
variable = field.getchunk( size ) 
返回值 
返回变体型。 
参数 :size            长整型表达式,等于所要检索的字节或字符数。
说明:
  使用 field 对象的 getchunk 方法检索其部分或全部长二进制或字符数据。在系统内存有限的情况下,可使用 getchunk 方法处理部分而非全部的长整型值。 
getchunk 调用返回的数据将赋给“变量”。如果 size 大于剩余的数据,则 getchunk 仅返回剩余的数据而无需用空白填充“变量”。如果字段为空,则 
getchunk 方法返回 null。 每个后续的 getchunk 调用将检索从前一次 getchunk 调用停止处开始的数 据。但是,如果从一个字段检索数据然后在当前记录中设置或读取另一个字段的值,ado 将认为已从第一个字段中检索出数据。如果在第一个字段上再次调用 getchunk 方法,ado 将把调用解释为新的 getchunk 操作并从记录的起始处开始读取。如果其他 recordset 对象不是首个 recordset 对象的副本,则 访问其中的字段不会破坏 getchunk 操作。 如果 field 对象的 attributes 属性中的 adfldlong 位设置为 true,则可以对该字段使用 getchunk 方法。 
如果在 field 对象上使用 getchunk 方法时没有当前记录,将产生错误 3021 (无当前记录)。 

接下来,我们就要来设计我们的数据库了,作为测试我们的数据库结构如下(access): 
字段名称    类型    描述 
  id    自动编号   主键值
img ole对象   用来保存图片数据  
现在开始正式编写我们的纯asp代码上传部分了,首先,我们有一个提供给用户的上传界面,可以让用户选择要上传的图片。代码如下
(upload.htm): 
<html> 
<body> 
<center> 
   <form name="mainform" enctype="multipart/form-data" action="process.asp" method=post> 
    <input type=file name=upfile><br> 
   <input type=submit name=ok value="ok"> 
   </form> 
</center> 
</body> 
</html> 

  注意代码中<<enctype="multipart/form-data" >>,一定要在form中有这个属性,否则,将无法得到上传上来的数据。 
接下来,我们要在process.asp中对从浏览器中获取的数据进行必要的处 理,因为我们在process.asp中获取到的数据不仅仅包含了我们想要的上传上来的图片的数据,也包含了其他的无用的信息,我们需要剔除冗余数据,并将处理过的图片数据保存到数据库中,这里我们以access为例。具体代码如下(process.asp): 
<% 
response.buffer=true 
formsize=request.totalbytes 
formdata=request.binaryread(formsize) 
bncrlf=chrb(13) & chrb(10) 
divider=leftb(formdata,clng(instrb(formdata,bncrlf))-1) 
datastart=instrb(formdata,bncrlf & bncrlf)+4 
dataend=instrb(datastart+1,formdata,divider)-datastart 
mydata=midb(formdata,datastart,dataend) 
set conngraph=server.createobject("adodb.connection") 
conngraph.connectionstring="Provider=microsoft.jet.oledb.4.0;data source="& server.mappath("images.mdb") 
conngraph.open 
set rec=server.createobject("adodb.recordset") 
rec.open "select * from [images] where id is null",conngraph,1,3 
rec.addnew 
rec("img").appendchunk mydata 
rec.update 
rec.close 
set rec=nothing 
set conngraph=nothing 
%> 

  好了,这下我们就把上传来的图片保存到了名为images.mdb的数据库中 了,剩下的工作就是要将数据库中的图片数据显示到网页上面了。一般在html中,显示图片都是使用<img>标签,也就是<img src="图片路径">,但 是我们的图片是保存到了数据库中,“图片路径”是什么呢?呵呵,其实这个 src属性除了指定路径外,也可以这样使用哦: 
<img src="showimg.asp?id=xxx"> 

  所以,我们所要做的就是在showimg.asp中从数据库中读出来符合条件的 数据,并返回到src属性中就可以了,具体代码如下(showimg.asp):
<% 
fromwhere=lcase(request.ServerVariables("SERVER_NAME"))      
if Instr(fromwhere,"arhwen.com")=0 then                    ’判断是否从本站访问,如果不是就停止下面的执行.这样就可以防止盗链了.
response.end
end if           

set conngraph=server.createobject("adodb.connection") 
conngraph.connectionstring="provider=microsoft.jet.oledb.4.0;data source="& server.mappath("images.mdb")
conngraph.open 
set rec=server.createobject("adodb.recordset") 
strsql="select img from images where id=" & trim(request("id")) 
rec.open strsql,conngraph,1,1 
response.contenttype = "image/*" 
response.binarywrite rec("img").getchunk(7500000) 
rec.close 
set rec=nothing 
set conngraph=nothing 
%> 

  注意在输出到浏览器之前一定要指定response.contenttype = "image/*",以便正常显示图片。 
  通过这样的处理,相信要盗链你的图片就不容易了.

如果你不希望别人轻易地在其他站点引用你的图片,你可以把图片存入在数据库.下面介绍的是主要有二个:1如何把图片上传到数据库,2如何显示数据库并加上验证.
     首先我们先来熟悉一下将要使用的对象方法。我们用来获取上一个页面传递过来的数据一般是使用request对象。同样的,我们也可以使用request对象 来获取上传上来的文件数据,使用的方法是request.binaryread()。而我们要从 数据库中读出来图片的数据显示到网页上面要用到的方法是: 
request.binarywrite()。在我们得到了图片的数据,要保存到数据库中的时候, 不可以直接使用insert语句对数据库进行操作,而是要使用ado的 appendchunk方法,同样的,读出数据库中的图片数据,要使用getchunk方 法。各个方法的具体语法如下: 

1) request.binaryread语法: 
variant = request.binaryread(count) 
参数 :variant       返回值保存着从客户端读取到数据。 
        count       指明要从客户端读取的数据量大小,这个值小于或者等于使用方法request.totalbytes得到的数据量。 

2)request.binarywrite语法: 
request.binarywrite data 
参数: data         要写入到客户端浏览器中的数据包。 

3) request.totalbytes语法: 
variant = request.totalbytes 
参数 :variant       返回从客户端读取到数据量的字节数。 

4) appendchunk语法 
   将数据追加到大型文本、二进制数据 field 或 parameter 对象。 
object.appendchunk data 
参数: 
object field 或 parameter 对象
data 变体型,包含追加到对象中的数据。 
说明 
使用 field 或 parameter 对象的 appendchunk 方法可将长二进制或字符数据填写到对象中。在系统内存有限的情况下,可以使用 appendchunk 方法对长整型值进行部分而非全部的操作。 

5) getchunk语法 
返回大型文本或二进制数据 field 对象的全部或部分内容 。
variable = field.getchunk( size ) 
返回值 
返回变体型。 
参数 :size            长整型表达式,等于所要检索的字节或字符数。
说明:
  使用 field 对象的 getchunk 方法检索其部分或全部长二进制或字符数据。在系统内存有限的情况下,可使用 getchunk 方法处理部分而非全部的长整型值。 
getchunk 调用返回的数据将赋给“变量”。如果 size 大于剩余的数据,则 getchunk 仅返回剩余的数据而无需用空白填充“变量”。如果字段为空,则 
getchunk 方法返回 null。 每个后续的 getchunk 调用将检索从前一次 getchunk 调用停止处开始的数 据。但是,如果从一个字段检索数据然后在当前记录中设置或读取另一个字段的值,ado 将认为已从第一个字段中检索出数据。如果在第一个字段上再次调用 getchunk 方法,ado 将把调用解释为新的 getchunk 操作并从记录的起始处开始读取。如果其他 recordset 对象不是首个 recordset 对象的副本,则 访问其中的字段不会破坏 getchunk 操作。 如果 field 对象的 attributes 属性中的 adfldlong 位设置为 true,则可以对该字段使用 getchunk 方法。 
如果在 field 对象上使用 getchunk 方法时没有当前记录,将产生错误 3021 (无当前记录)。 

接下来,我们就要来设计我们的数据库了,作为测试我们的数据库结构如下(access): 
字段名称    类型    描述 
  id    自动编号   主键值
img ole对象   用来保存图片数据  
现在开始正式编写我们的纯asp代码上传部分了,首先,我们有一个提供给用户的上传界面,可以让用户选择要上传的图片。代码如下
(upload.htm): 
<html> 
<body> 
<center> 
   <form name="mainform" enctype="multipart/form-data" action="process.asp" method=post> 
    <input type=file name=upfile><br> 
   <input type=submit name=ok value="ok"> 
   </form> 
</center> 
</body> 
</html> 

  注意代码中<<enctype="multipart/form-data" >>,一定要在form中有这个属性,否则,将无法得到上传上来的数据。 
接下来,我们要在process.asp中对从浏览器中获取的数据进行必要的处 理,因为我们在process.asp中获取到的数据不仅仅包含了我们想要的上传上来的图片的数据,也包含了其他的无用的信息,我们需要剔除冗余数据,并将处理过的图片数据保存到数据库中,这里我们以access为例。具体代码如下(process.asp): 
<% 
response.buffer=true 
formsize=request.totalbytes 
formdata=request.binaryread(formsize) 
bncrlf=chrb(13) & chrb(10) 
divider=leftb(formdata,clng(instrb(formdata,bncrlf))-1) 
datastart=instrb(formdata,bncrlf & bncrlf)+4 
dataend=instrb(datastart+1,formdata,divider)-datastart 
mydata=midb(formdata,datastart,dataend) 
set conngraph=server.createobject("adodb.connection") 
conngraph.connectionstring="Provider=microsoft.jet.oledb.4.0;data source="& server.mappath("images.mdb") 
conngraph.open 
set rec=server.createobject("adodb.recordset") 
rec.open "select * from [images] where id is null",conngraph,1,3 
rec.addnew 
rec("img").appendchunk mydata 
rec.update 
rec.close 
set rec=nothing 
set conngraph=nothing 
%> 

  好了,这下我们就把上传来的图片保存到了名为images.mdb的数据库中 了,剩下的工作就是要将数据库中的图片数据显示到网页上面了。一般在html中,显示图片都是使用<img>标签,也就是<img src="图片路径">,但 是我们的图片是保存到了数据库中,“图片路径”是什么呢?呵呵,其实这个 src属性除了指定路径外,也可以这样使用哦: 
<img src="showimg.asp?id=xxx"> 

  所以,我们所要做的就是在showimg.asp中从数据库中读出来符合条件的 数据,并返回到src属性中就可以了,具体代码如下(showimg.asp):
<% 
fromwhere=lcase(request.ServerVariables("SERVER_NAME"))      
if Instr(fromwhere,"arhwen.com")=0 then                    ’判断是否从本站访问,如果不是就停止下面的执行.这样就可以防止盗链了.
response.end
end if           

set conngraph=server.createobject("adodb.connection") 
conngraph.connectionstring="provider=microsoft.jet.oledb.4.0;data source="& server.mappath("images.mdb")
conngraph.open 
set rec=server.createobject("adodb.recordset") 
strsql="select img from images where id=" & trim(request("id")) 
rec.open strsql,conngraph,1,1 
response.contenttype = "image/*" 
response.binarywrite rec("img").getchunk(7500000) 
rec.close 
set rec=nothing 
set conngraph=nothing 
%> 

  注意在输出到浏览器之前一定要指定response.contenttype = "image/*",以便正常显示图片。 
  通过这样的处理,相信要盗链你的图片就不容易了.

作者Blog:http://blog.csdn.net/capsicum29/
归类于: Programming — freed @ 2:38 pm 评论(0)
如何编写异常安全的C++代码(ZZ)

 选择自 wingfiring 的 Blog

    关于C++中异常的争论何其多也,但往往是一些不合事实的误解。异常曾经是一个难以用好的语言特性,幸运的是,随着C++社区经验的积累,今天我们已经有足够的知识轻松编写异常安全的代码了,而且编写异常安全的代码一般也不会对性能造成影响。

    使用异常还是返回错误码?这是个争论不休的话题。大家一定听说过这样的说法:只有在真正异常的时候,才使用异常。那什么是“真正异常的时候”?在回答这个问题以前,让我们先看一看程序设计中的不变式原理。
    对象就是属性聚合加方法,如何判定一个对象的属性聚合是不是处于逻辑上正确的状态呢?这可以通过一系列的断言,最后下一个结论说:这个对象的属性聚合逻辑上是正确的或者是有问题的。这些断言就是衡量对象属性聚合对错的不变式。
    我们通常在函数调用中,实施不变式的检查。不变式分为三类:前条件,后条件和不变式。前条件是指在函数调用之前,必须满足的逻辑条件,后条件是函数调用后必须满足的逻辑条件,不变式则是整个函数执行中都必须满足的条件。在我们的讨论中,不变式既是前条件又是后条件。前条件是必须满足的,如果不满足,那就是程序逻辑错误,后条件则不一定。现在,我们可以用不变式来严格定义异常状况了:满足前条件,但是无法满足后条件,即为异常状况。当且仅当发生异常状况时,才抛出异常。
    关于何时抛出异常的回答中,并不排斥返回值报告错误,而且这两者是正交的。然而,从我们经验上来说,完全可以在这两者中加以选择,这又是为什么呢?事实上,当我们做出这种选择时,必然意味着接口语意的改变,在不改变接口的情况下,其实是无法选择的(试试看,用返回值处理构造函数中的错误)。通过不变式区别出正常和异常状况,还可以更好地提炼接口。
    对于异常安全的评定,可分为三个级别:基本保证、强保证和不会失败。
基本保证:确保出现异常时程序(对象)处于未知但有效的状态。所谓有效,即对象的不变式检查全部通过。
强保证:确保操作的事务性,要么成功,程序处于目标状态,要么不发生改变。
不会失败:对于大多数函数来说,这是很难保证的。对于C++程序,至少析构函数、释放函数和swap函数要确保不会失败,这是编写异常安全代码的基础。
    首先从异常情况下资源管理的问题开始.很多人可能都这么干过:
    Type* obj = new Type;
    try{  do_something…}
    catch(…){ delete obj; throw;}

    不要这么做!这么做只会使你的代码看上去混乱,而且会降低效率,这也是一直以来异常名声不大好的原因之一. 请借助于RAII技术来完成这样的工作:
   
auto_ptr<Type> obj_ptr(new Type);
    do_something…

    这样的代码简洁、安全而且无损于效率。当你不关心或是无法处理异常时,请不要试图捕获它。并非使用try…catch才能编写异常安全的代码,大部分异常安全的代码都不需要try…catch。我承认,现实世界并非总是如上述的例子那样简单,但是这个例子确实可以代表很多异常安全代码的做法。在这个例子中,boost::scoped_ptr是auto_ptr一个更适合的替代品。
    现在来考虑这样一个构造函数:
    Type() : m_a(new TypeA), m_b(new TypeB){}
    假设成员变量m_a和m_b是原始的指针类型,并且和Type内的申明顺序一致。这样的代码是不安全的,它存在资源泄漏问题,构造函数的失败回滚机制无法应对这样的问题。如果new TypeB抛出异常,new TypeA返回的资源是得不到释放机会的.曾经,很多人用这样的方法避免异常:
   
Type() : m_a(NULL), m_b(NULL){
        auto_ptr<TypeA> tmp_a(new TypeA);
        auto_ptr<TypeB> tmp_b(new TypeB);
        m_a = tmp_a.release();
        m_b = tmp_b.release();
    }

当然,这样的方法确实是能够实现异常安全的代码的,而且其中实现思想将是非常重要的,在如何实现强保证的异常安全代码中会采用这种思想.然而这种做法不够彻底,至少析构函数还是要手动完成的。我们仍然可以借助RAII技术,把这件事做得更为彻底:shared_ptr<TypeA> m_a; shared_ptr<TypeB> m_b;这样,我们就可以轻而易举地写出异常安全的代码:
    Type() : m_a(new TypeA), m_b(new TypeB){}
如果你觉得shared_ptr的性能不能满足要求,可以编写一个接口类似scoped_ptr的智能指针类,在析构函数中释放资源即可。如果类设计成不可复制的,也可以直接用scoped_ptr。强烈建议不要把auto_ptr作为数据成员使用,scoped_ptr虽然名字不大好,但是至少很安全而且不会导致混乱。
    RAII技术并不仅仅用于上述例子中,所有必须成对出现的操作都可以通过这一技术完成而不必try…catch.下面的代码也是常见的:
   
a_lock.lock();
    try{ …} catch(…) {a_lock.unlock();throw;}
    a_lock.unlock();

可以这样解决,先提供一个成对操作的辅助类:
   
struct scoped_lock{
        explicit scoped_lock(Lock& lock) : m_l(lock){m_l.lock();}
        ~scoped_lock(){m_l.unlock();}
    private: 
        Lock& m_l;
    };

然后,代码只需这样写:
   
scoped_lock guard(a_lock);
    do_something…

清晰而优雅!继续考察这个例子,假设我们并不需要成对操作, 显然,修改scoped_lock构造函数即可解决问题。然而,往往方法名称和参数也不是那么固定的,怎么办?可以借助这样一个辅助类:
   
template<typename FEnd, typename FBegin>
    struct pair_guard{
        pair_guard(FEnd fe, FBegin fb) : m_fe(fe) {if (fb) fb();}
        ~pair_guard(){m_fe();}
    private:
        FEnd m_fe;
        …//禁止复制
    };
    typedef pair_guard<function<void () > , function<void()> > simple_pair_guard;

好了,借助boost库,我们可以这样来编写代码了:
   
simple_pair_guard guard(bind(&Lock::unlock, a_lock), bind(&Lock::lock, a_lock) );
    do_something…

我承认,这样的代码不如前面的简洁和容易理解,但是它更灵活,无论函数名称是什么,都可以拿来结对。我们可以加强对bind的运用,结合占位符和reference_wrapper,就可以处理函数参数、动态绑定变量。所有我们在catch内外的相同工作,交给pair_guard去完成即可。
    考察前面的几个例子,也许你已经发现了,所谓异常安全的代码,竟然就是如何避免try…catch的代码,这和直觉似乎是违背的。有些时候,事情就是如此违背直觉。异常是无处不在的,当你不需要关心异常或者无法处理异常的时候,就应该避免捕获异常。除非你打算捕获所有异常,否则,请务必把未处理的异常再次抛出。try…catch的方式固然能够写出异常安全的代码,但是那样的代码无论是清晰性和效率都是难以忍受的,而这正是很多人抨击C++异常的理由。在C++的世界,就应该按照C++的法则来行事。
    如果按照上述的原则行事,能够实现基本保证了吗?诚恳地说,基础设施有了,但技巧上还不够,让我们继续分析不够的部分。
    对于一个方法常规的执行过程,我们在方法内部可能需要多次修改对象状态,在方法执行的中途,对象是可能处于非法状态的(非法状态 != 未知状态),如果此时发生异常,对象将变得无效。利用前述的手段,在pair_guard的析构中修复对象是可行的,但缺乏效率,代码将变得复杂。最好的办法是……是避免这么作,这么说有点不厚道,但并非毫无道理。当对象处于非法状态时,意味着此时此刻对象不能安全重入、不能共享。现实一点的做法是:
    a.每一次修改对象,都确保对象处于合法状态
    b.或者当对象处于非法状态时,所有操作决不会失败。
在接下来的强保证的讨论中细述如何做到这两点。

    强保证是事务性的,这个事务性和数据库的事务性有区别,也有共通性。实现强保证的原则做法是:在可能失败的过程中计算出对象的目标状态,但是不修改对象,在决不失败的过程中,把对象替换到目标状态。考察一个不安全的字符串赋值方法:
string& operator=(const string& rsh){
    if (this != &rsh){
        myalloc locked_pool(m_data);
        locked_pool.deallocate(m_data);
        if (rsh.empty())
        m_data = NULL;
        else{
        m_data = locked_pool.allocate(rsh.size() + 1);
        never_failed_copy(m_data, rsh.m_data, rsh.size() + 1);
        }
    }
    return *this;
    }

locked_pool是为了锁定内存页。为了讨论的简单起见,我们假设只有locked_pool构造函数和allocate是可能抛出异常的,那么这段代码连基本保证也没有做到。若allocate失败,则m_data取值将是非法的。参考上面的b条目,我们可以这样修改代码:
myalloc locked_pool(m_data);
    locked_pool.deallocate(m_data);   //进入非法状态
    m_data = NULL;            //立刻再次回到合法状态,且不会失败
    if(!rsh.empty()){
    m_data = locked_pool.allocate(rsh.size() + 1);
    never_failed_memcopy(m_data, rsh.m_data, rsh.size() + 1);
    }

现在,如果locked_pool失败,对象不发生改变。如果allocate失败,对象是一个空字符串,这既不是初始状态,也不是我们预期的目标状态,但它是一个合法状态。我们阐明了实现基本保证所需要的技巧部分,结合前述的基础设施(RAII的运用),完全可以实现基本保证了…哦,其实还是有一点疏漏,不过,那就留到最后吧。
   继续,让上面的代码实现强保证:
myalloc locked_pool(m_data);
    char* tmp = NULL;
    if(!rsh.empty()){
    tmp = locked_pool.allocate(rsh.size() + 1);
    never_failed_memcopy(tmp, rsh.m_data, rsh.size() + 1); //先生成目标状态
    }
    swap(tmp, m_data);       //对象安全进入目标状态
    m_alloc.deallocate(tmp);    //释放原有资源

强保证的代码多使用了一个局部变量tmp,先计算出目标状态放在tmp中,然后在安全进入目标状态,这个过程我们并没有损失什么东西(代码清晰性,性能等等)。看上去,实现强保证并不比基本保证困难多少,一般而言,也确实如此。不过,别太自信,举一种典型的很难实现强保证的例子,对于区间操作的强保证:
   
for (itr = range.begin(); itr != range.end(); ++itr){
    itr->do_something();
    }

如果某个do_something失败了,range将处于什么状态?这段代码仍然做到了基本保证,但不是强保证的,根据实现强保证的基本原则,我们可以这么做:
   
tmp = range;
    for (itr = tmp.begin(); itr != tmp.end(); ++itr){
    itr->do_something();
    }
    swap(tmp, range);

似乎很简单啊!呵呵,这样的做法并非不可取,只是有时候行不通。因为我们额外付出了性能的代价,而且,这个代价可能很大。无论如何,我们阐述了实现强保证的方法,怎么取舍则由您决定了。

    接下来讨论最后一种异常安全保证:不会失败。
    通常,我们并不需要这么强的安全保证,但是我们至少必须保证三类过程不会失败:析构函数,释放类函数,swap。析构和释放函数不会失败,这是RAII技术有效的基石,swap不会失败,是为了“在决不失败的过程中,把对象替换到目标状态”。我们前面的所有讨论都是建立在这三类过程不会失败的基础上的,在这里,弥补了上面的那个疏漏。
    一般而言,语言内部类型的赋值、取地址等运算是不会发生异常的,上述三类过程逻辑上也是不会发生异常的。内部运算中,除法运算可能抛出异常。但是地址访问错通常是一种错误,而不是异常,我们本应该在前条件检查中就发现的这一点的。所有不会发生异常操作的简单累加,仍然不会导致异常。

好了,现在我们可以总结一下编写异常安全代码的几条准则了:
1.只在应该使用异常的地方抛出异常
2.如果不知道如何处理异常,请不要捕获(截留)异常。
3.充分使用RAII,旁路异常。
4.努力实现强保证,至少实现基本保证。
5.确保析构函数、释放类函数和swap不会失败。

另外,还有一些语言细节问题,因为和这个主题有关也一并列出:
1.不要这样抛出异常:throw new exception;这将导致内存泄漏。
2.自定义类型,应该捕获异常的引用类型:catch(exception& e)或catch(const exception& e)。
3.不要使用异常规范,即使是空异常规范。编译器并不保证只抛出异常规范允许的异常,更多内容请参考相关书籍。

作者Blog:http://blog.csdn.net/wingfiring/
归类于: Programming — freed @ 2:31 pm 评论(0)
2006年04月12日
怎样成为一个Flash Lite Developer (开发篇)–ZZ

作者:luar

原文链接:http://www.luar.com.hk/flashbook/archives/001228.php

要開發Flash Lite內容,大部分人頭痛是那Flash 4語法,對於資深Flash開發者來說,Flash 4不難寫,程式設計美麗的地方,就是同一個需要,有很有多不同寫法,窮則變,變則通。相反,在手機上跑的東西,效能和記憶體佔用才是最大困難所在,往往就是要開發者用智能去克服效能的問題。所以,Flash 4語法是門外的人看以為的問題,克服效能才是平日Flash Lite開發者奮鬥的目標。

好了,廢話說完。Flash Lite ActionScript是怎樣?就是Flash 4 ActionScript、編譯器幫助下一些Flash 5指令、手機屬性和FSCommand2。

Flash 4 ActionScript

包含以下東西:

注意:不支援startDrag, stopDrag, _dropTarget, soundBufTime, _url和String()轉換。

 

Flash 5 Object

在Compiler幫助下,有一些Flash 5指令可以用,它們在編譯時,轉為Flash 4語法,包括:

 

手機屬性和FSCommand2

這些都是一些取得手機資料,和控制手機(例如震動、發SMS等)的指令,如果平常用Flash Lite開發遊戲,比較常用的有:

 

由Flash 4 Port到Flash Lite

開始編程Flash Lite時,往往由以前的Flash 4東西開始,例如將以前的東西改為Flash Lite版,在這些轉移過程中,要注意的地方:

归类于: Programming — freed @ 5:00 pm 评论(0)
怎样成为一个Flash Lite Developer (工具篇)–ZZ

作者:luar

原文链接:http://www.luar.com.hk/flashbook/archives/001227.php

功欲善其事,必先利其器,首先要有一部支援Flash Lite手機,目前香港普遍流行的有Nokia 7610和新的6680,或者3G機Sony Ericsson Z800i,不過支援Flash Lite手機仍然偏貴,如果自己目前已經有一部手機,就沒有必要買一部新機。

買支援Flash Lite手機

因此,普遍Flash Lite Developer都會去找二手的Nokia N-Gage,它是支援Flash Lite手機中最便宜(因為不受歡迎,跌價太厲害吧)。要找二手的N-Gage,可以去深水埗鴨寮街,路邊小販檔,約HK$1050-1200,一手價目前是HK$1600-1700,所以這些手機收買佬食水太深,N-Gage Trade-in價約HK$600而己!所以我建議上Yahoo! Bid,一般HK$800可以買到。

N-Gage分舊版(Classic)和QD版,兩者在一二手價錢都分別不大,功能上有什麼分別?當然不是關心QD版抽了MP3和FM功能。最主要留意跟PC連接能力和方便程度。QD版沒有USB Data Cable,一定要用藍牙,所以又要買多一個藍牙收發器(HK$70-140),Classic版用USB Data Cable,一連上電腦,就像多了一個Harddisk,直接複製File到手機的MMC Card裡。如果有MMC讀卡器,也可以複製File,不過Classic版要拆殼拆電池才能拿出卡,QD版MMC卡在機旁,可以直接抽出。

藍牙收發器始終要買,因為有秘技用藍牙連PC上網,省了GPRS費用,如果開發跟數據連接的Flash內容,測試時可以放心一點,嘻。

找舊Simcard

手機要開啟,需要Simcard,沒有理由放棄目前手機插入N-Gage,N-Gage講電話時實在太異相,怎可以出街見人?!要找一張舊Simcard不難,因為攜號碼轉台很容易,應該找到舊Simcard吧。

下載Flash Lite Player

除了Sony Ericsson Z800i內置外,Nokia S60系列機都沒有,要到Macromedia網站買,購買時,地區選美國,Asia Pacific沒有Flash Lite賣,輸入手機IMEI(按*#06#看),信用卡或PayPal付款後,可以立即下載。

安裝CDK或等Flash Pro 8

Flash Lite 1.1 Content Development Kits(CDK)
是一些組件、範例、開發指引和ActionScript的教學PDF、Flash Lite PC上陽春模擬Player等。Flash Pro 8有增強Flash Lite開發內容的功能,等一下,所以目前下載CDK是必須。


附:

Flash Lite 2.0

 

[概念]Flash Lite 2.0 是Flash Player专为非个人电脑类电子设备推出的新版本,将为全球的手机产品和消费电子品提供更加丰富的用户界面。Flash Player SDK 7 是Flash Player专为消费电子产品优化的版本,它使消费电子产品制造商、系统集成商和浏览器提供商等,能够通过借助众多网站丰富的Flash内容,创造出更具影响力的,具有全面网络浏览能力的产品与服务。

 

[特点]Flash Lite 2.0完全基于Flash 7的标准,这意味着在Flash的PC开发平台上可以开发移动设备上的应用。它不仅可以支持动态的XML数据,能够加载和解析外部XML数据,而且在数据传递(Persistent Data)方面,它可以支持例如参数设置,最高分,用户名等等。

归类于: Programming — freed @ 4:59 pm 评论(0)
大餐还是鸡肋?让我们谈谈Flash Lite(ZZ)

原文链接:http://www.newwz.com/zblog/post/134.html


    从10月在0110的大S论坛里看到Flash Lite的资料以来,到现在已经有好几个月的时间了,也许当时已经掀起了一股研究Flash Lite的高潮,可惜那段时间我很忙,没留意这个方面….等到12月底,我因为有做Flash Lite软件的创意(也就是现在做出来的MyBattery),开始真正动手研究Flash Lite,才发现这个东西是如此精彩~ 我如痴如醉的寻找相关的资料,高手做的Flash Lite主题或者软件的源文件,沉醉于其中~并努力去更新自己的小软件MyBattery….每次版本的更新,伴随着软件功能的增加和完善,我对于Flash Lite的熟悉也与日俱增…但是随着我搜寻范围的扩大,我也发现,国内对Flash Lite所给予的热情还太少,尤其在于开发方面~ 本来由于Flash Lite是一个新鲜事物,知道的人不怎么多,用的人要少一些,而做的人就更少的可怜了!我想借这篇文章,能够让大家对Flash Lite有所了解,看的更全面一些,更希望能够带来玩Flash Lite的第二高潮!

    有些人可能会说,Flash Lite能做什么?不就是全屏主题吗?我只要会用就行了…还有些人可能一直以为Flash Lite是用来播放电脑上的flash动画的,所以一直没有关注…在这里,我要颠覆大家的这种错误观念!

    Flash Lite能做什么?Flash Lite可以分为两个部分来看,一个部分是手机端的播放器,也就是Flash Player,但是受限于手机的处理能力和硬件特点,这个播放器和电脑上的有很大不同,因此有与之对应的创作工具,实际上从flash MX 2004开始就已经提供了对Flash Lite的支持.按照Flash Lite的标准做出来的flash,就能够在手机端的Flash Player里打开并且播放.不知道大家对电脑上的flash是怎么样的一个印象?估计很多人听到flash,首先浮现在脑海中的会是一些经典的flash动画(比如大话三国系列,小小的动作系列等),那么,我可以告诉你,你的观念落后了!实际上,flash早就已经逐步转变为一种强大的开发工具,它不仅仅可以制作流畅的动画,而且可以用来开发各种功能强大的软件,游戏!而手机上的Flash Lite虽然功能弱了一些,但是还是很强大的,当然,同样可以开发软件和游戏!

那么,让我们来看看几个精彩的东西吧!


类似QQ的IM软件



漂亮的计时器



电视节目预告



发短信的工具


有些朋友可能会说,这些Flash Lite软件不是有SIS的类似软件吗?也许他们还更加方便和好用~那么,Flash Lite的优势在哪里呢?这里请允许我摘录一下李易修做的几点小结,以及我对这些优势的看法:
1.比原有开发流程快3-5倍
我不知道这里所指的原有开发流程是什么,估计是S60软件的开发流程.我同样不知道S60软件是如何被开发出来的,但是我用flash做过不少课件,它们当中的某些功能也很强大,和用其他程序开发工具创作的软件不相上下,那么,我想,Flash Lite开发的流程也不会有多么复杂.至少,入门肯定比做S60的软件要容易一些~

2.一致性的体验
由于Flash Lite的标准是由MacroMedia公司(现在已经被Adobe公司收购)所制定的,在统一的标准下,而且是同样的播放器,那么所获得的效果也是一致的.这样就是说,你对于开发者而言,只要开发一次,就能够在所有支持Flash Lite的手机上运行,而不必针对手机型号进行多次开发.而对于用户来说,你只要确保你的手机支持Flash Lite,并且你下载的是Flash Lite标准编写(创作)的swf文件,那么就可以确保它们在你的手机上正确的运行,而不必去寻找对应手机型号的版本!这种一致性,无论对于开发者还是用户所带来的便捷,是勿庸置疑的!

3.简易的开发工具
我不知道有多少人有电脑上的flash开发经验,也许我可以算是其中一个.那么对于这类人来说,在同样一个创作平台下的开发,难度会降低不少,你所需要做的,仅仅是一个适应的过程!从原有电脑上的鼠标键盘交互,到现在手机上仅仅0-9,*,#和方向键的交互.还有也许你已经用习惯的点语法,它们在Flash Lite中不被支持!不管怎么说,这些总比从头开始学习要容易的多了~ 而对于从未接触过flash开发的朋友,如果你只想做一些简单的东西,比如主题,那么也会很容易上手的~ 稍后也许我会抽空写一个教程~

事物总是有正反两个方面,Flash Lite也有它不可避免的劣势
目前来说,我觉得比较严重的,比较影响我做开发的,有这么几点:
1.在Flash Lite1.1中,对中文的支持不怎么好.主要体现在动态文本如果是中文的话,会是乱码!真是头疼,我只能尽量把文本打散,而保留其中的数字和英文部分.这样同时会增加最终输出文件的大小~
2.能够载入的外部图片资料有限制!据说是50K,总之这样的限制很不舒服,也决定了在主题中动态载入背景是多么容易引起程序崩溃的一种操作!
3.熟悉的点语法不能用了!尤其对于我这种习惯使用点语法的,现在又要回归flash 4的写法,真是很麻烦的一件事,但是又无可奈何.
4.Flash Lite仍然不适合作复杂的应用,包括商务和娱乐方面,从安全机制,存储能力,网络连接等层面,Flash Lite都比较薄弱,而且可能难以改进.幸好我现在的水平还无法支持我做这种程度的开发 -_-!

以上就Flash Lite,谈了一些我自己个人的见解,当然其中还引用了部分前辈的内容
撰写此文的目的,就在于希望能够吸引更多的人来关注,来研究Flash Lite,让它在我们的手机上大放光彩!

【背景资料】
什么是Flash lite?
Flash Lite 是专门为移动电话开发的 Macromedia Flash 配置文件。Flash Lite 已获得日本消费者和开发人员广泛采用,而且现在正在快速为日本以外地区运营商和制造商所采用。

此增长由功能强大的 Flash 再现引擎所推动,此引擎在不同操作系统、处理器和屏幕大小之间提供一致的体验。这受到世界各地强大的 Flash 开发人员群体支持。最初的反馈很清楚:Flash 显著加快用于移动电话的精彩内容和界面的开发。

Flash Lite 为手机提供了以下功能:

■ 基于 HTTP 的数据连接
■ SVG 支持*(播放)
■ 对电话功能的扩展访问
■ 新的内容开发套件 (CDK)
■ 向量图形表现
■ 位图图像
■ 条纹背景
■ 声音-事件音效
■ 静态文本(字体嵌入在 SWF 文件中)
■ 输入和动态文本(字体嵌入在 SWF 文件中)
■ 输入和动态设备文本
■ 基于帧的动画
■ 变形动画-动作和形状的改变
■ ActionScript(脚本)-支持 Flash 4 脚本
■ 键盘导航

Flash lite1.1和Flash lite2.0
在浏览器中搜索Flash Lite,可以发现大量的Flash Lite2.0的新闻报道,随着2.0的正式推出,闹的真是风风火火,让我们来看看2.0有哪些新的改变:

摘录luar的一段文字

1.Unicode:动态文字可以展示中文,在我们华文地区来说,非常重要!
2.Mobile Shared Object (MSO):即PC上LSO,可以储存资料到手机,不用再为游戏储存等问题寻求其它Fscommand Flash2File的解决方法。注意:MSO只在Standalone Mode支持,如果Flash内容是在Browser Mode、Messaging Application或者Nokia File Manager,MSO是不支持的。
3.可以LoadMovie加载图像档案,不用再寻求JPG2SWF等工具在Server先转换,由于Decode能力借助手机支持,所以可以加载JPG/GIF/PNG,不支持Animated GIF/PNG Animation,由于要知道手机是否支持该格式,所以有System.capabilities的ActionScript来侦测。
4.同样道理,loadSound支持动态加载MP3/MIDI,也是靠手机支持。
5.同样道理,Video可以播放3gp,其实也是靠手机支持,3gp影像一定盖在Flash内容上面,至于Embed Video in SWF也可以,不过File Size这么大,可行吗?
6.Stage Object可以知道SWF画面大小,onResize也支持,是否代表可以用一个SWF通吃不同手机画面大小?
7.SetInputTextType,可以限制Input TextField为Numeric, Alpha, Alphanumeric, Latin, NonLatin and NoRestriction。
8.ExtendBacklightDuration,控制手机光亮时间(如果手机支持的话),感动!

尤其让我喜欢的是动态文字可以使用中文而不必担心乱码和MSO,可以保存设置了~ 另外2.0又可以使用点语法了~

可惜需要告诉大家的是,大S中能够使用的Flash lite是1.1的.我发现2.0的需要Symbian 7.0以上的才能安装,目前没有大S能用的版本~ 非常遗憾~

归类于: Programming — freed @ 4:56 pm 评论(0)
使用Flash Lite 制作手机主题(ZZ)

Flash Lite

Flash Lite Macromedia公司为了行动电话开发的 Flash Player。于20032月发布,Flash Lite 1.0支持 Flash 5的对象和Flash 4 ActionScript, 也能够播放MIDI音乐。 2004年初Flash Lite 升级到1.1版,加强了网络连接、数据交换能力,支持的声音格式包含MP3 PCMADPCM SMAF,使其更适合发展多媒体应用程序。

    由于受到手机运算能力、内存、屏幕的限制,现有的Flash Lite player对于网络上流行的flash动画文件很多都不支持,但是Flash Lite的主要功能并不是要让我们可以浏览动画,而是是一个类似J2ME一样新的手机开发平台。

Flash Lite 在手机上情况

2003年日本NTT DoCoMo 公司推出的的505i是第一款支持Flash Lite的行动电 话。之后的每一支 i-mode 手机都内建了 Flash Lite 设定档,可以 浏览内嵌了Flash内容的 i-mode服务、以 Flash动画作为待机图案。目前已经有 超过 1000 家的公司提供了以 Flash 为基础的 i-mode 服务,内容包含游戏、交通 信息、气象、互动地图、动画铃声等等。

2004Macromedia公司推出了可以在Symbian或是MS Smartphone手机上可以使用的flash lite1.1播放器,同时,也和三星电子以及NOKIA达成了软件授权协议,以后这两家公司生产的手机将会内置flash lite播放器。现有的一些支持Flash Lite的手机

更多支持Flash Lite的手机大家可以通过这个网址查看:http://www.macromedia.com/mobile/supported_devices/

 

让我们使用flash lite来制作一个简单的手机应用程序

    使用flash lite很大的一个好处就是相对JAVA来说入门简单,怎么简单呢?下边我们就通过一个例子来向大家说明,一个非专业人员也可以轻松的来开发一个基于手机的小应用程序。

大家都知道N-gageN-gage QD76503650S60手机都是不支持主题功能的,看着别人S70手机上那些酷酷的主题,是不是很郁闷?别急,有了flash lite,让我们为自己的N-gageN-gage QD加入令人心动的主题功能,而且要更玄。

这个主题的基本原理就是通过flash lite里的按钮,来调用手机里对应的程序。就像电脑里的快捷方式一样。那接下来我们进入正题吧!(本文需要您对flash 2004以及一些基本的图像处理软件具有一定的基础)

准备工作
开始制作FlashLite内容之前,要先准备好下面这些东西:
• Flash MX 2004 Professional
• FlashLite 1.1 CDK
或者
Macromedia Flash MX 2004 7.2更新

FlashLite 1.1 CDK可以在Macromedia 网站上免费下载。http://www.macromedia.com/devnet/devices/development_kits.html

Macromedia Flash MX 2004 7.2更新也可以在Macromedia 网站上免费下载。http://www.macromedia.com/cn/software/flash/special/7_2updater/

建议安装Macromedia Flash MX 2004 7.2更新,虽然容量大了一些,但是更新了很多内容,而且安装过程比较简单,这里就不介绍了。

详细说一下安装CDK所需要的步骤 (如果你已经安装过Flash 7.2 update就可以跳过这个步骤。)
1.
下载完后解开压缩档,把Flash_Lite_1.1_CDKFlash_Lite_authoring_updater目录下的FlashLite1_1.dll,拷贝到下面的位置。
• Windows:
C:\Program Files\Macromedia\Flash MX 2004\language\Configuration\Players
• Mac OS X:
Macintosh HD::Applications:Macromedia Flash MX 2004:Configuration:Players

2.拷贝FlashLite1_1.xml到下面的位置。
• Windows:
C:\Program Files\Macromedia\Flash MX 2004\language\Configuration\Players
• Mac OS X:
Macintosh HD::Applications:Macromedia Flash MX 2004:Configuration:Players

3.拷贝DeviceMsg.cfg到下面的位置。
• Windows 2000/ WindowsXP:
C:\Documents and Settings\user name\Local Settings\Application Data\Macromedia\ Flash MX 2004\language\Configuration\
• Windows 98(SE):
C:\Windows\Profiles\user name\Application Data\Macromedia\Flash MX 2004\language\Configuration\
• Macintosh:
Macintosh HD::Users:user name:Library:Application Support:Macromedia:Flash MX 2004:language:Configuration:

要在手机上测试,还需要以下这两样:
支援Flash Lite的手机一只,Symbian60及以上的都可以。包括6600n-gageQD7610等等。
• FlashLite Player1.1
FlashLite Player1.1
前一段时间是可以免费申请的,但是现在不可以了,只能在Macromedia的网络商店购买,价格是10美元。地址如下http://www.macromedia.com/cfusion/store/index.cfm?store=OLS-US#view=ols_prod&loc=en_us&store=OLS-US&categoryOID=1141004&distributionOID=103

1、  要做FlashLite文件,可以从模板建立Create from Template开始。Flash MX 2004在发布的时候,已经内建了一些手机的模板Template,不过种类有点少。有国外网友自己做了一些Symbian S60手机的Template,可以到http://www.flash-lite.de/downloads.html下载。
打开Flash,建立新文件fileànew,选择模板templates、选择移动设备mobile devices,然后随便选择nokia 3650 7650都可以。(和你的手机不一样没关系)

2、  删除最上边的一层,这层是用作模版御览用的,对我们的工作没有用处。

3、  选择Properties里的设置Setting按钮

4、  在发布选项内把版本Version选择为Flash lite 1.1(默认为Flash lite 1.0,其他无需改动

5、  在时间轴中建立3个层,actions层(用来放置程序)、按钮层(用来放置按钮)、背景层(用来放置主题的背景)。

6、  接下来我们准备一张漂亮的图片作为主题的背景,大小要求宽176像素、高208像素,正好和我们的手机屏幕分辨率一样,格式要求是JPG或是PNG的(图片可以用你LPGF的,也许会有意外惊喜呀),以上可以采用一些图像处理软件来完成,在这里我准备了一张红色的中国龙的图片。

7、  选择背景层的第一帧,然后选择文件fileà导入importà导入到场景import to stage
然后选择已经做好的那张图片,确定,正确的话应该会是这个样子。这就是放到手机上的样子

8、  下一步,我们开始制作上边的按钮,我们可以使用一些现成的图标,像是XP本身的一些图标,您也可以去网上找到一些更好看的图标,然后使用图像处理软件,将其制作为背景色透明的PNG格式文件(推荐使用Firework软件)。为了知道当前是选择了哪一个按钮,要再给每一个图标制作一个略有不同的图片,可以采用一下外发光或阴影滤镜都可以。下边是我们做好的三个按钮。上边是普通状态,下边是该按钮被选中的样子。

9、  接下来我们回到flash中,选择插入Insertànew symbal,然后选择按钮button,名字就叫“联系人”就好。

10、              在新建立的“联系人”按钮中的UP这一帧上,我们选择文件fileà导入importà导入到场景import to stage,然后选择我们刚才创建的联系人的普通图标,完成后应该是这个样子。

11、              然后选择Over这一帧,按“F7”键,这样可以在Over这一帧建立一个新的关键帧,然后重复上边的文件fileà导入importà导入到场景import to stage,选择我们刚才创建的联系人的被选中图标,这样当“联系人”这个按钮被选中是就会显示刚才制作的被选中图标。

12、              现在我们已经完成了一个按钮,回到主场景。选择左上方的Scene 1

13、              我们刚才制作的按钮哪里去了?不要着急,它已经在我们的库文件里了。打开库文件的工具栏Windowsàlibrary,看看是不是有一个按钮库文件叫联系人?

14、              先选中时间轴中的按钮那一层,然后在库文件中把它选中,拖到主场景中,位置你可以随便放置。

15、              好,我们的第一个按钮就已经都做好了,现在我们为它加上功能,写一段非常简单的程序。先选中这个按钮,然后打开动作actions面板。写上如下内容:
on (release) {
getURL("file:///Z:/System/Apps/Phonebook/Phonebook.app");
}
意思也就是当这个按钮被按下然后抬起的时候,执行手机上Z:/System/Apps/Phonebook/Phonebook.app这个程序,对
Symbian手机比较了解得朋友可能已经看出来了,这就是手机上的联系人程序。

16、              按照上边的步骤,我们再添加两个按钮,分别是“电话”和“短消息”,程序代码分别是:
电话:on (release) {
getURL("file:///Z:/System/Apps/Phone/Phone.app");
}
短消息:on (release) {
getURL("file:///Z:/System/Apps/mce/mce.app");
}

17、              然后选择时间轴上的actions层,打开Actions面板,加入如下程序(//后为注释,可以不写):
fscommand2("FullScreen", true);//
当打开的时候自动全屏幕播放
fscommand2("SetQuality", "low");//
播放时细节设置为高级
_focusrect = false;//
不显示当前焦点框,不写的话当按钮被选中就会有一个黄色的框框,不好看。
stop();//
停止

18、              好,我们自己的第一个手机程序就要搞定了,先保存一下,然后同时按下”Ctrl”+”Enter”键,我们就可以看到主题运行的样子(此时使用Flash内建的Flash Lite模拟器来运行,有些情况下可能和在手机上运行有不同),然后选择文件fileà导出Exportà导出为动画Export Movie,找一个目录,起一个自己喜欢的名字。确认,然后弹出版本选择,因为我们前边已经作了选择,这里直接点确认OK就好了。

19、              接下来的事对于大家来说就简单了,和其他应用程序一样,把我们刚才制作的扩展名为swf的文件传到我们的手机上边,放在e:\Documents\flash目录下(flash lite安装后会自动建立)。然后运行flash lite player1.1,选择我们制作的文件,看,效果如何?你还可以加入更多的按钮和功能。

20、              Flash lite本身还有一些和手机的接口函数,利用这些函数可以得到手机的很多信息,包括当前电量、信号置、支持什么铃声等等,利用这些,就可以做出更多更好东西,笔者在这个主题的基础上制作了一个能显示手机电量、信号、以及时间的主题。在这里限于篇幅,就不做更多的介绍了,感兴趣的朋友可以自己查阅flash lite的相关文档,都在flash lite CDK中,也可以到我的网站上来一起讨论。

 

制作和测试Flash lite需要注意的一些问题

FlashLite模拟器和手机上播放同样档案的结果很可能不同,比如手机上的播放速度可能会较慢,或是图片出不来。所以最好边做边测,才不会白费功夫。有时候在手机上播放Flash的时候,会出现错误讯息例如:Problem with content: 6,这代表JPEG图档太大了。以下是错误讯息的意义。
1. Out of memory
(内存不够,是你的flash文件过大,现在flash lite对文件的大小限制是1M以下)
2. Stack limit reached
(堆栈超过限制)
3. Corrupt SWF data
(错误的swf数据)
4. ActionScript stuck
(代码错误)
5. Infinite AS loop
(代码有死循环)
6. Bad JPEG data
(错误的JPEG数据)
7. Bad sound data
(错误的Sound数据)
8. Cannot find host
(没有找到主机)
9. ActionScript error
(代码错误)
10. URL too long
(网址过长)

图形处理

毕竟手机的运算效能还不够强,尽量让图形简单一点,少用点渐层、半透明,必要时把向量图转为位图,才能确保播放的效果和你的预期相同。

Flash lite 的优势和前景

虽然现在来说Flash Lite功能相比J2ME还过于单薄。支持的手机还不是很多,但是它还是存在一些优势的:

开发人员数目庞大

全球有750000Flash开发人员,国内的“闪客”也绝对是为数不少

标准统一

    Flash Lite Player,任何安装了Flash Lite Player的设备都可以播放Flash Lite文件而不需要加以编译修改。而J2ME的基础规范虽然比较统一,但是各手机厂家的兼容性又有不同,经常可以看到NOKIA可以用的程序在索爱上就用不了。

开发简单、周期短

会用Flash的人只要经过短期的学习甚至不需要学习就可以直接制作开发基于手机的各种Flash动画、应用软件等等。

支持矢量格式,在设计制作动画、用户界面方面拥有得天独厚的优势

大家都知道Flash是一种矢量格式,也就是可以和屏幕的分辨率无关,而Flash Lite也是矢量格式,同时还支持SVG(也是一种矢量格式),在现在各种移动设备屏幕大小不一、分辨率多种多样的情况下,他的这种优势非常明显。而且对于制作动画方面,Flash要比逐桢绘图的J2ME拥有更多的优势。

FlashCast

FlashCast 是推播 Flash Lite内容的服务平台。20047 T-Mobile在英国、德国和奥地利推出以FlashLite为基础的News Express服务。News Express服务每天推播两次新闻、娱乐、气象、运动信息等多媒体内容到用户的手机上。

总体说来,Flash LiteJ2ME两者都具备自身的优势和劣势,这种优势和劣势也导致了两者必将在不同的领域发挥作用。

    下边是使用Flash Lite编写的一些手机应用程序,更多的Flash Lite应用大家可以通过这个网址来下载:
http://www.macromedia.com/cfusion/exchange/index.cfm#loc=en_us&view=sn310&viewName=Flash%20Lite%20Exchange

结语

由于Macromedia Flash是目前网络上最广泛使用的技术之一,进入门坎相对较低。开发效率也较其它技术快3 5倍。已经熟析Flash的开发人员,不需要学习对特定装置的技术,就能够把开发的内容部署在手机、PDA等不同的移动设备上。非常适合作为多媒体应用程序的行动平台。

Flash在手机方面的应用目前仍是处于刚起步的阶段,手机上Flash Lite的占有率远低于JavaFlash Lite的服务范围现在还仅限于日本、德国、英国、奥地利等地。但随着Macromedia和三星、NOKIA等手机厂商的签约,以及和一些内容服务商的合作或许在不久的将来,就能在中国看到Flash Lite 提供的各种服务。如果Flash Lite在手机上的的占有率能够提升到一定的比例、内建的 ActionScript 能够升级、功能与效能提升,Flash Lite应该能够成为一种新兴的手机应用平台。

相关信息

作者网站:www.flashfuture.net

MM公司手机网站:mobile.macromedia.com

归类于: Programming — freed @ 4:53 pm 评论(0)
让手机也“闪”起来!Flash Lite 2 新功能介绍(ZZ)

  Flash Lite 是专门用于为移动电话和消费电子设备进行开发的Flash技术。现在 Flash Lite 2 已经发布了。让我们来看看它都有些什么新特色、新功能:

  1、Flash Player 7 支持

  Flash Lite 2 基于 Flash 7 内容创作标准。这意味着在最新 Flash 创作环境中已开发的内容,能够为移动设备和消费电子设备进行更动(re-purposed )。

Flash Lite 2 基于 Flash 7 内容创作标准

  2、动态 XML 数据

  Flash Lite 2 支持在 Flash 内容中,使用与 Flash Player 7 相同的处理方法加载和解析外部XML数据。

支持动态 XML 数据

  3、数据传递(Persistent Data)

  Flash Lite 2 支持本地存储和返回相应的,特定应用程序信息,例如参数设置,最高分,用户名等等。这将提供一个更加强大的开发环境。

数据传递

  4、强大和动态的媒体

  Flash Lite 2 基于设备所支持的可用编码器,允许动态加载多媒体内容,例如图像、声音和视频。这包括加载和处理XML数据和SWF内容。Flash Lite 2 提供视频支持和外部多媒体支持。

视频回放基于设备可用的编码解码器

  5、文本增强

  Flash Lite 2 允许用户在程序运行时修改文本颜色、大小以及其他属性,提供改进的显示和文字处理。OEM商也能够选择嵌入矢量字体到他们用于渲染文本的应用程序中。Flash Lite 2还支持对象阿拉伯语这样从右到左的语言进行文本渲染。

增强的文本显示和处理功能

  6、ActionScript 绘图 API

  Flash Lite 2允许开发者在程序运行时使用ActionScript,简便的创建矢量图像和动态形状。

ActionScript 绘图 API

  7、ActionScript 2.0 支持

  Flash Lite 2 支持基于ECMA 262 的标准 ActionScript 2.0。Flash Lite 内容能够使用现代事件模型(影片剪辑和对象事件)、tab index control、绘图API以及更好的SWF压缩器完成开发创作。

  8、同步设备声音

  Flash Lite 2 允许内容开发者使用设备中特定的音频格式例如MIDI, SMAF等音频数据同步动画。

  9、压缩SWF文件

  Flash Lite 2 能够渲染被内容开发者使用Flash创作工具压缩过的 SWF 文件。在开始处理和渲染数据前,Flash Lite 2 将会解压 SWF 文件。

归类于: Programming — freed @ 4:50 pm 评论(0)
2006年04月10日
深入剖析JSP和Servlet对中文的处理(ZZ)

 

  世界上的各地区都有本地的语言。地区差异直接导致了语言环境的差异。在开发一个国际化程序的过程中,处理语言问题就显得很重要了。

  这是一个世界范围内都存在的问题,所以,Java提供了世界性的解决方法。本文描述的方法是用于处理中文的,但是,推而广之,对于处理世界上其它国家和地区的语言同样适用。

  汉字是双字节的。所谓双字节是指一个双字要占用两个BYTE的位置(即16位),分别称为高位和低位。中国规定的汉字编码为GB2312,这是强制性的,目前几乎所有的能处理中文的应用程序都支持GB2312。GB2312包括了一二级汉字和9区符号,高位从0xa1到0xfe,低位也是从0xa1到0xfe,其中,汉字的编码范围为0xb0a1到0xf7fe。

  另外有一种编码,叫做GBK,但这是一份规范,不是强制的。GBK提供了20902个汉字,它兼容GB2312,编码范围为0×8140到0xfefe。GBK中的所有字符都可以一一映射到Unicode 2.0。

  在不久的将来,中国会颁布另一种标准:GB18030-2000(GBK2K)。它收录了藏、蒙等少数民族的字型,从根本上解决了字位不足的问题。注意:它不再是定长的。其二字节部份与GBK兼容,四字节部分是扩充的字符、字形。它的首字节和第三字节从0×81到0xfe,二字节和第四字节从0×30到0×39。

  本文不打算介绍Unicode,有兴趣的可以浏览“http://www.unicode.org/”查看更多的信息。Unicode有一个特性:它包括了世界上所有的字符字形。所以,各个地区的语言都可以建立与Unicode的映射关系,而Java正是利用了这一点以达到异种语言之间的转换。

  在JDK中,与中文相关的编码有:

  表1 JDK中与中文相关的编码列表


编码名称 说明
ASCII 7位,与ascii7相同
ISO8859-1 8-位,与 8859_1,ISO-8859-1,ISO_8859-1,latin1…等相同
GB2312-80 16位,与gb2312,gb2312-1980,EUC_CN,euccn,1381,Cp1381, 1383, Cp1383, ISO2022CN,ISO2022CN_GB…等相同
GBK 与MS936相同,注意:区分大小写
UTF8 与UTF-8相同
GB18030 与cp1392、1392相同,目前支持的JDK很少


  在实际编程时,接触得比较多的是GB2312(GBK)和ISO8859-1。

  为什么会有“?”号

  上文说过,异种语言之间的转换是通过Unicode来完成的。假设有两种不同的语言A和B,转换的步骤为:先把A转化为Unicode,再把Unicode转化为B。

  举例说明。有GB2312中有一个汉字“李”,其编码为“C0EE”,欲转化为ISO8859-1编码。步骤为:先把“李”字转化为Unicode,得到“674E”,再把“674E”转化为ISO8859-1字符。当然,这个映射不会成功,因为ISO8859-1中根本就没有与“674E”对应的字符。

  当映射不成功时,问题就发生了!当从某语言向Unicode转化时,如果在某语言中没有该字符,得到的将是Unicode的代码“\uffffd”(“\u”表示是Unicode编码,)。而从Unicode向某语言转化时,如果某语言没有对应的字符,则得到的是“0×3f”(“?”)。这就是“?”的由来。

  例如:把字符流buf =“0×80 0×40 0xb0 0xa1”进行new String(buf, "gb2312")操作,得到的结果是“\ufffd\u554a”,再println出来,得到的结果将是“?啊”,因为“0×80 0×40”是GBK中的字符,在GB2312中没有。

  再如,把字符串String="\u00d6\u00ec\u00e9\u0046\u00bb\u00f9"进行new String (buf.getBytes("GBK"))操作,得到的结果是“3fa8aca8a6463fa8b4”,其中,“\u00d6”在“GBK”中没有对应的字符,得到“3f”,“\u00ec”对应着“a8ac”,“\u00e9”对应着“a8a6”,“0046”对应着“46”(因为这是ASCII字符),“\u00bb”没找到,得到“3f”,最后,“\u00f9”对应着“a8b4”。把这个字符串println一下,得到的结果是“?ìéF?ù”。看到没?这里并不全是问号,因为GBK与Unicode映射的内容中除了汉字外还有字符,本例就是最好的明证。

  所以,在汉字转码时,如果发生错乱,得到的不一定都是问号噢!不过,错了终究是错了,50步和100步并没有质的差别。

  或者会问:如果源字符集中有,而Unicode中没有,结果会如何?回答是不知道。因为我手头没有能做这个测试的源字符集。但有一点是肯定的,那就是源字符集不够规范。在Java中,如果发生这种情况,是会抛出异常的。

什么是UTF

  UTF,是Unicode Text Format的缩写,意为Unicode文本格式。对于UTF,是这样定义的:

  (1)如果Unicode的16位字符的头9位是0,则用一个字节表示,这个字节的首位是“0”,剩下的7位与原字符中的后7位相同,如“\u0034”(0000 0000 0011 0100),用“34” (0011 0100)表示;(与源Unicode字符是相同的);

  (2)如果Unicode的16位字符的头5位是0,则用2个字节表示,首字节是“110”开头,后面的5位与源字符中除去头5个零后的最高5位相同;第二个字节以“10”开头,后面的6位与源字符中的低6位相同。如“\u025d”(0000 0010 0101 1101),转化后为“c99d”(1100 1001 1001 1101);

  (3)如果不符合上述两个规则,则用三个字节表示。第一个字节以“1110”开头,后四位为源字符的高四位;第二个字节以“10”开头,后六位为源字符中间的六位;第三个字节以“10”开头,后六位为源字符的低六位;如“\u9da7”(1001 1101 1010 0111),转化为“e9b6a7”(1110 1001 1011 0110 1010 0111);

  可以这么描述JAVA程序中Unicode与UTF的关系,虽然不绝对:字符串在内存中运行时,表现为Unicode代码,而当要保存到文件或其它介质中去时,用的是UTF。这个转化过程是由writeUTF和readUTF来完成的。

  好了,基础性的论述差不多了,下面进入正题。

  先把这个问题想成是一个黑匣子。先看黑匣子的一级表示:

input(charsetA)->process(Unicode)->output(charsetB)

  简单,这就是一个IPO模型,即输入、处理和输出。同样的内容要经过“从charsetA到unicode再到charsetB”的转化。

  再看二级表示:

SourceFile(jsp,java)->class->output

  在这个图中,可以看出,输入的是jsp和java源文件,在处理过程中,以Class文件为载体,然后输出。再细化到三级表示:

jsp->temp file->class->browser,os console,db

app,servlet->class->browser,os console,db

  这个图就更明白了。Jsp文件先生成中间的Java文件,再生成Class。而Servlet和普通App则直接编译生成Class。然后,从Class再输出到浏览器、控制台或数据库等。

  JSP:从源文件到Class的过程

  Jsp的源文件是以“.jsp”结尾的文本文件。在本节中,将阐述JSP文件的解释和编译过程,并跟踪其中的中文变化。

  1、JSP/Servlet引擎提供的JSP转换工具(jspc)搜索JSP文件中用<%@ page contentType ="text/html; charset=<Jsp-charset>"%>中指定的charset。如果在JSP文件中未指定<Jsp-charset>,则取JVM中的默认设置file.encoding,一般情况下,这个值是ISO8859-1;

  2、jspc用相当于“javac –encoding <Jsp-charset>”的命令解释JSP文件中出现的所有字符,包括中文字符和ASCII字符,然后把这些字符转换成Unicode字符,再转化成UTF格式,存为JAVA文件。ASCII码字符转化为Unicode字符时只是简单地在前面加“00”,如“A”,转化为“\u0041”(不需要理由,Unicode的码表就是这么编的)。然后,经过到UTF的转换,又变回“41”了!这也就是可以使用普通文本编辑器查看由JSP生成的JAVA文件的原因;

  3、引擎用相当于“javac –encoding UNICODE”的命令,把JAVA文件编译成CLASS文件;

  先看一下这些过程中中文字符的转换情况。有如下源代码:


<%@ page contentType="text/html; charset=gb2312"%>
<html><body>
<%
 String a="中文";
 out.println(a);
%>
</body></html>


  这段代码是在UltraEdit for Windows上编写的。保存后,“中文”两个字的16进制编码为“D6 D0 CE C4”(GB2312编码)。经查表,“中文”两字的Unicode编码为“\u4E2D\u6587”,用 UTF表示就是“E4 B8 AD E6 96 87”。打开引擎生成的由JSP文件转变而成的JAVA文件,发现其中的“中文”两个字确实被“E4 B8 AD E6 96 87”替代了,再查看由JAVA文件编译生成的CLASS文件,发现结果与JAVA文件中的完全一样。

  再看JSP中指定的CharSet为ISO-8859-1的情况。


<%@ page contentType="text/html; charset=ISO-8859-1"%>
<html><body>
<%
 String a="中文";
 out.println(a);
%>
</body></html>


  同样,该文件是用UltraEdit编写的,“中文”这两个字也是存为GB2312编码“D6 D0 CE C4”。先模拟一下生成的JAVA文件和CLASS文件的过程:jspc用ISO-8859-1来解释“中文”,并把它映射到Unicode。由于ISO-8859-1是8位的,且是拉丁语系,其映射规则就是在每个字节前加“00”,所以,映射后的Unicode编码应为“\u00D6\u00D0\u00CE\u00C4”,转化成UTF后应该是“C3 96 C3 90 C3 8E C3 84”。好,打开文件看一下,JAVA文件和CLASS文件中,“中文”果然都表示为“C3 96 C3 90 C3 8E C3 84”。

  如果上述代码中不指定<Jsp-charset>,即把第一行写成“<%@ page contentType="text/html" %>”,JSPC会使用file.encoding的设置来解释JSP文件。在RedHat 6.2上,其处理结果与指定为ISO-8859-1是完全相同的。

  到现在为止,已经解释了从JSP文件到CLASS文件的转变过程中中文字符的映射过程。一句话:从“JspCharSet到Unicode再到UTF”。下表总结了这个过程:

  表2 “中文”从JSP到CLASS的转化过程


Jsp-CharSet JSP文件中 JAVA文件中 CLASS文件中
GB2312 D6 D0 CE C4(GB2312) 从\u4E2D\u6587(Unicode)到E4 B8 AD E6 96 87 (UTF) E4 B8 AD E6 96 87 (UTF)
ISO-8859-1 D6 D0 CE C4
(GB2312)
从\u00D6\u00D0\u00CE\u00C4 (Unicode)到C3 96 C3 90 C3 8E C3 84 (UTF) C3 96 C3 90 C3 8E C3 84 (UTF)
无(默认=file.encoding) 同ISO-8859-1 同ISO-8859-1 同ISO-8859-1


  下节先讨论Servlet从JAVA文件到CLASS文件的转化过程,然后再解释从CLASS文件如何输出到客户端。之所以这样安排,是因为JSP和Servlet在输出时处理方法是一样的。

Servlet:从源文件到Class的过程

  Servlet源文件是以“.java”结尾的文本文件。本节将讨论Servlet的编译过程并跟踪其中的中文变化。

  用“javac”编译Servlet源文件。javac可以带“-encoding <Compile-charset>”参数,意思是“用< Compile-charset >中指定的编码来解释Serlvet源文件”。

  源文件在编译时,用<Compile-charset>来解释所有字符,包括中文字符和ASCII字符。然后把字符常量转变成Unicode字符,最后,把Unicode转变成UTF。

  在Servlet中,还有一个地方设置输出流的CharSet。通常在输出结果前,调用HttpServletResponse的setContentType方法来达到与在JSP中设置<Jsp-charset>一样的效果,称之为<Servlet-charset>。

  注意,文中一共提到了三个变量:<Jsp-charset>、<Compile-charset>和<Servlet-charset>。其中,JSP文件只与<Jsp-charset>有关,而<Compile-charset>和<Servlet-charset>只与Servlet有关。

  看下例:


import javax.servlet.*;

import javax.servlet.http.*;

class testServlet extends HttpServlet
{
 public void doGet(HttpServletRequest req,HttpServletResponse resp)
 throws ServletException,java.io.IOException
 {
  resp.setContentType("text/html; charset=GB2312");
  java.io.PrintWriter out=resp.getWriter();
  out.println("<html>");
  out.println("#中文#");
  out.println("</html>");
 }
}


  该文件也是用UltraEdit for Windows编写的,其中的“中文”两个字保存为“D6 D0 CE C4”(GB2312编码)。

  开始编译。下表是<Compile-charset>不同时,CLASS文件中“中文”两字的十六进制码。在编译过程中,<Servlet-charset>不起任何作用。<Servlet-charset>只对CLASS文件的输出产生影响,实际上是<Servlet-charset>和<Compile-charset>一起,达到与JSP文件中的<Jsp-charset>相同的效果,因为<Jsp-charset>对编译和CLASS文件的输出都会产生影响。

  表3 “中文”从Servlet源文件到Class的转变过程


Compile-charset Servlet源文件中 Class文件中 等效的Unicode码
GB2312 D6 D0 CE C4
(GB2312)
E4 B8 AD E6 96 87 (UTF) \u4E2D\u6587 (在Unicode中=“中文”)
ISO-8859-1 D6 D0 CE C4
(GB2312)
C3 96 C3 90 C3 8E C3 84 (UTF) \u00D6 \u00D0 \u00CE \u00C4 (在D6 D0 CE C4前面各加了一个00)
无(默认) D6 D0 CE C4 (GB2312) 同ISO-8859-1 同ISO-8859-1


  普通Java程序的编译过程与Servlet完全一样。

  CLASS文件中的中文表示法是不是昭然若揭了?OK,接下来看看CLASS又是怎样输出中文的呢?

  Class:输出字符串

  上文说过,字符串在内存中表现为Unicode编码。至于这种Unicode编码表示了什么,那要看它是从哪种字符集映射过来的,也就是说要看它的祖先。这好比在托运行李时,外观都是纸箱子,里面装了什么就要看寄邮件的人实际邮了什么东西。

  看看上面的例子,如果给一串Unicode编码“00D6 00D0 00CE 00C4”,如果不作转换,直接用Unicode码表来对照它时,是四个字符(而且是特殊字符);假如把它与“ISO8859-1”进行映射,则直接去掉前面的“00”即可得到“D6 D0 CE C4”,这是ASCII码表中的四个字符;而假如把它当作GB2312来进行映射,得到的结果很可能是一大堆乱码,因为在GB2312中有可能没有(也有可能有)字符与00D6等字符对应(如果对应不上,将得到0×3f,也就是问号,如果对应上了,由于00D6等字符太靠前,估计也是一些特殊符号,真正的汉字在Unicode中的编码从4E00开始)。

  各位看到了,同样的Unicode字符,可以解释成不同的样子。当然,这其中有一种是我们期望的结果。以上例而论,“D6 D0 CE C4”应该是我们所想要的,当把“D6 D0 CE C4”输出到IE中时,用“简体中文”方式查看,就能看到清楚的“中文”两个字了。(当然了,如果你一定要用“西欧字符”来看,那也没办法,你将得不到任何有何时何地的东西)为什么呢?因为“00D6 00D0 00CE 00C4”本来就是由ISO8859-1转化过去的。

给出如下结论:

  在Class输出字符串前,会将Unicode的字符串按照某一种内码重新生成字节流,然后把字节流输入,相当于进行了一步“String.getBytes(???)”操作。???代表某一种字符集。

  如果是Servlet,那么,这种内码就是在HttpServletResponse.setContentType()方法中指定的内码,也就是上文定义的<Servlet-charset>。

  如果是JSP,那么,这种内码就是在<%@ page contentType=""%>中指定的内码,也就是上文定义的<Jsp-charset>。

  如果是Java程序,那么,这种内码就是file.encoding中指定的内码,默认为ISO8859-1。

  当输出对象是浏览器时

  以流行的浏览器IE为例。IE支持多种内码。假如IE接收到了一个字节流“D6 D0 CE C4”,你可以尝试用各种内码去查看。你会发现用“简体中文”时能得到正确的结果。因为“D6 D0 CE C4”本来就是简体中文中“中文”两个字的编码。

  OK,完整地看一遍。

  JSP:源文件为GB2312格式的文本文件,且JSP源文件中有“中文”这两个汉字

  如果指定了<Jsp-charset>为GB2312,转化过程如下表。

  表4 Jsp-charset = GB2312时的变化过程


序号 步骤说明 结果
1 编写JSP源文件,且存为GB2312格式 D6 D0 CE C4
(D6D0=中 CEC4=文)
2 jspc把JSP源文件转化为临时JAVA文件,并把字符串按照GB2312映射到Unicode,并用UTF格式写入JAVA文件中 E4 B8 AD E6 96 87
3 把临时JAVA文件编译成CLASS文件 E4 B8 AD E6 96 87
4 运行时,先从CLASS文件中用readUTF读出字符串,在内存中的是Unicode编码 4E 2D 65 87(在Unicode中4E2D=中 6587=文)
5 根据Jsp-charset=GB2312把Unicode转化为字节流 D6 D0 CE C4
6 把字节流输出到IE中,并设置IE的编码为GB2312(作者按:这个信息隐藏在HTTP头中) D6 D0 CE C4
7 IE用“简体中文”查看结果 “中文”(正确显示)


  如果指定了<Jsp-charset>为ISO8859-1,转化过程如下表。

  表5 Jsp-charset = ISO8859-1时的变化过程


序号 步骤说明 结果
1 编写JSP源文件,且存为GB2312格式 D6 D0 CE C4
(D6D0=中 CEC4=文)
2 jspc把JSP源文件转化为临时JAVA文件,并把字符串按照ISO8859-1映射到Unicode,并用UTF格式写入JAVA文件中 C3 96 C3 90 C3 8E C3 84
3 把临时JAVA文件编译成CLASS文件 C3 96 C3 90 C3 8E C3 84
4 运行时,先从CLASS文件中用readUTF读出字符串,在内存中的是Unicode编码 00 D6 00 D0 00 CE 00 C4
(啥都不是!!!)
5 根据Jsp-charset=ISO8859-1把Unicode转化为字节流 D6 D0 CE C4
6 把字节流输出到IE中,并设置IE的编码为ISO8859-1(作者按:这个信息隐藏在HTTP头中) D6 D0 CE C4
7 IE用“西欧字符”查看结果 乱码,其实是四个ASCII字符,但由于大于128,所以显示出来的怪模怪样
8 改变IE的页面编码为“简体中文” “中文”(正确显示)


  奇怪了!为什么把<Jsp-charset>设成GB2312和ISO8859-1是一个样的,都能正确显示?因为表4表5中的第2步和第5步互逆,是相互“抵消”的。只不过当指定为ISO8859-1时,要增加第8步操作,殊为不便。

  再看看不指定<Jsp-charset> 时的情况。

  表6 未指定Jsp-charset 时的变化过程


序号 步骤说明 结果
1 编写JSP源文件,且存为GB2312格式 D6 D0 CE C4
(D6D0=中 CEC4=文)
2 jspc把JSP源文件转化为临时JAVA文件,并把字符串按照ISO8859-1映射到Unicode,并用UTF格式写入JAVA文件中 C3 96 C3 90 C3 8E C3 84
3 把临时JAVA文件编译成CLASS文件 C3 96 C3 90 C3 8E C3 84
4 运行时,先从CLASS文件中用readUTF读出字符串,在内存中的是Unicode编码 00 D6 00 D0 00 CE 00 C4
5 根据Jsp-charset=ISO8859-1把Unicode转化为字节流 D6 D0 CE C4
6 把字节流输出到IE中 D6 D0 CE C4
7 IE用发出请求时的页面的编码查看结果 视情况而定。如果是简体中文,则能正确显示,否则,需执行表5中的第8步


  Servlet:源文件为JAVA文件,格式是GB2312,源文件中含有“中文”这两个汉字

  如果<Compile-charset>=GB2312,<Servlet-charset>=GB2312

  表7 Compile-charset=Servlet-charset=GB2312 时的变化过程


序号 步骤说明 结果
1 编写Servlet源文件,且存为GB2312格式 D6 D0 CE C4
(D6D0=中 CEC4=文)
2 用javac –encoding GB2312把JAVA源文件编译成CLASS文件 E4 B8 AD E6 96 87 (UTF)
3 运行时,先从CLASS文件中用readUTF读出字符串,在内存中的是Unicode编码 4E 2D 65 87 (Unicode)
4 根据Servlet-charset=GB2312把Unicode转化为字节流 D6 D0 CE C4 (GB2312)
5 把字节流输出到IE中并设置IE的编码属性为Servlet-charset=GB2312 D6 D0 CE C4 (GB2312)
6 IE用“简体中文”查看结果 “中文”(正确显示)


  如果<Compile-charset>=ISO8859-1,<Servlet-charset>=ISO8859-1

  表8 Compile-charset=Servlet-charset=ISO8859-1时的变化过程


序号 步骤说明 结果
1 编写Servlet源文件,且存为GB2312格式 D6 D0 CE C4
(D6D0=中 CEC4=文)
2 用javac –encoding ISO8859-1把JAVA源文件编译成CLASS文件 C3 96 C3 90 C3 8E C3 84 (UTF)
3 运行时,先从CLASS文件中用readUTF读出字符串,在内存中的是Unicode编码 00 D6 00 D0 00 CE 00 C4
4 根据Servlet-charset=ISO8859-1把Unicode转化为字节流 D6 D0 CE C4
5 把字节流输出到IE中并设置IE的编码属性为Servlet-charset=ISO8859-1 D6 D0 CE C4 (GB2312)
6 IE用“西欧字符”查看结果 乱码(原因同表5)
7 改变IE的页面编码为“简体中文” “中文”(正确显示)


  如果不指定Compile-charset或Servlet-charset,其默认值均为ISO8859-1。

  当Compile-charset=Servlet-charset时,第2步和第4步能互逆,“抵消”,显示结果均能正确。读者可试着写一下Compile-charset<>Servlet-charset时的情况,肯定是不正确的。

  当输出对象是数据库时

  输出到数据库时,原理与输出到浏览器也是一样的。本节只是Servlet为例,JSP的情况请读者自行推导。

  假设有一个Servlet,它能接收来自客户端(IE,简体中文)的汉字字符串,然后把它写入到内码为ISO8859-1的数据库中,然后再从数据库中取出这个字符串,显示到客户端。

  表9 输出对象是数据库时的变化过程(1)


序号 步骤说明 结果
1 在IE中输入“中文” D6 D0 CE C4 IE
2 IE把字符串转变成UTF,并送入传输流中 E4 B8 AD E6 96 87
3 Servlet接收到输入流,用readUTF读取 4E 2D 65 87(unicode) Servlet
4 编程者在Servlet中必须把字符串根据GB2312还原为字节流 D6 D0 CE C4
5 编程者根据数据库内码ISO8859-1生成新的字符串 00 D6 00 D0 00 CE 00 C4
6 把新生成的字符串提交给JDBC 00 D6 00 D0 00 CE 00 C4
7 JDBC检测到数据库内码为ISO8859-1 00 D6 00 D0 00 CE 00 C4 JDBC
8 JDBC把接收到的字符串按照ISO8859-1生成字节流 D6 D0 CE C4
9 JDBC把字节流写入数据库中 D6 D0 CE C4
10 完成数据存储工作 D6 D0 CE C4 数据库
以下是从数据库中取出数的过程
11 JDBC从数据库中取出字节流 D6 D0 CE C4 JDBC
12 JDBC按照数据库的字符集ISO8859-1生成字符串,并提交给Servlet 00 D6 00 D0 00 CE 00 C4 (Unicode)  
13 Servlet获得字符串 00 D6 00 D0 00 CE 00 C4 (Unicode) Servlet
14 编程者必须根据数据库的内码ISO8859-1还原成原始字节流 D6 D0 CE C4  
15 编程者必须根据客户端字符集GB2312生成新的字符串 4E 2D 65 87
(Unicode)
 
Servlet准备把字符串输出到客户端
16 Servlet根据<Servlet-charset>生成字节流 D6D0 CE C4 Servlet
17 Servlet把字节流输出到IE中,如果已指定<Servlet-charset>,还会设置IE的编码为<Servlet-charset> D6 D0 CE C4
18 IE根据指定的编码或默认编码查看结果 “中文”(正确显示) IE


  解释一下,表中第4第5步和第15第16步是用红色标记的,表示要由编码者来作转换。第4、5两步其实就是一句话:“new String(source.getBytes("GB2312"), "ISO8859-1")”。第15、16两步也是一句话:“new String(source.getBytes("ISO8859-1"), "GB2312")”。亲爱的读者,你在这样编写代码时是否意识到了其中的每一个细节呢?

  至于客户端内码和数据库内码为其它值时的流程,和输出对象是系统控制台时的流程,请读者自己想吧。明白了上述流程的原理,相信你可以轻松地写出来。

  行文至此,已可告一段落了。终点又回到了起点,对于编程者而言,几乎是什么影响都没有。

  因为我们早就被告之要这么做了。

  以下给出一个结论,作为结尾。

  1、 在Jsp文件中,要指定contentType,其中,charset的值要与客户端浏览器所用的字符集一样;对于其中的字符串常量,不需做任何内码转换;对于字符串变量,要求能根据ContentType中指定的字符集还原成客户端能识别的字节流,简单地说,就是“字符串变量是基于<Jsp-charset>字符集的”;

  2、 在Servlet中,必须用HttpServletResponse.setContentType()设置charset,且设置成与客户端内码一致;对于其中的字符串常量,需要在Javac编译时指定encoding,这个encoding必须与编写源文件的平台的字符集一样,一般说来都是GB2312或GBK;对于字符串变量,与JSP一样,必须“是基于<Servlet-charset>字符集的”。

归类于: Programming — freed @ 6:27 pm 评论(0)