主体程序
1、字母下落线程
游戏界面中每一个下落的字母对应一个字母下落线程DropCharThread的实例,这个线程负责将一个随机的字母在指定的画布栏中从上至下落下。在TypeTrainApplet内部定义这个线程类,之所以要将其作为成员内部类来定义,是因为这样可以减少类和类之间的通信,降低调用接口的复杂度。DropCharThread需要访问到TypeTrainApplet的众多成员,作为内部类就可以直接访问TypeTrainApplet类的成员变量了。其代码如下所示:
代码清单 4 DropCharThread字母下落线程
由于这个类比较关键,逻辑也比较复杂,为了方便说明,我们将其流程通过一个流程图来描述,如下图所示:
1) 首先在栏序号为colIndex的栏的第一个位置画出保存在变量c中的字母(第17行)。
2) 当这个字母未被击中,未到达画布底部且用户未结束游戏进行循环,这步判断对应程序的19行。如果这个判断条件通过进入第3步,即进入循环体,否则转到第5步。
3) 如果被暂停,这个线程进入等待态,直接被通知后才继续运行。需要指明一点的是,所有字母下落线程都用画布对象canvas进行同步,即用canvas进行通讯。线程间要进行通讯时,一定需要通讯线程都可以访问到的对象充当媒介将这些线程"串"起来,通过这个对象的notify()/notifyAll()/wait()在线程间通讯。这个对象好比一个"月下老人",在线程的情人间传递音讯。
4) 当线程被唤醒后,或原来就没有等待,则进入下一个循环的处理过程,在这个过程中,程序将原来位置的字母清除,下移纵坐标,并在新的位置画字母,以达到字母下落的动画效果,然后下落线程睡眠指定的毫秒数,毫秒数值为TypeTrainApplet成员变量stepInterval的值,而这个值可以在网页的<param name = "stepInterval" value = "50">标签中定义,达到控制下落速度的效果。
因为在画布上画字母后,这个字母并不会自动消失,如果直接移动纵坐标并在新位置画字母,原位置的字母依就存在。所以在新位置画字母之前,必须先将旧位置的字母清除。我们用了一个小技巧,即使用Graphics对象的setXORMode()方法,该方法两图像重叠部分的颜色。我们调用这个方法将图像重叠部分的颜色设置为画布的背景色,这样在原来的位置上再次画字母时,因为前后两次画个字母刚好重叠,就达到了清除原位置字母的效果。
画字母和清除字母的程序相似,我们把它抽出到一个方法中draw(int actionType),如第50~59行代码所示,通过入参决定是清除还是画新字母。为增强程序的可读性,我们在第8~9行中定义了两个用于表示清字母和画字母的动作常量。
5) 当程序出了循环体后,进行善后的处理:将用于保存用户按键字母的pressKeyChar变量置为空字符,在画布上清除移到底部的字母。如果游戏没有结束统计数据,并将数据写到界面的JLabel组件中。
2、添加击中音效
击中字母后播放一个短促的声音,将能大大提高游戏的听觉体验,这在节里,我们对字母下落线程稍作更改,以使其支持音效。
首先准备一个声音文件hit.wav,放在TypeTrainApplet.java相同的文件夹下。Applet类中定义了一个getAudioClip(URL url)方法,通过这个方法可以获取AudioClip的声音文件的对象。通过AudioClip的play()即可播放这个音效。
代码清单 5
在第5行定义一个音效的对象,在Applet初始化时获取音效对象,如第9行所示。更改字母下落线程,当击中下落的字母时播放音效,如第21行所示。
3、字母下落线程的产生器线程
指法练习需要"子子孙孙,无穷匮也"地不断产生字母下落线程,以使游戏持续进行,这个工作由产生器线程GenerateDropThread负责。GenerateDropThread线程出于和DropCharThread同样的原因,也作为TypeTrainApplet成员内部类来定义,其代码如下所示:
代码清单 6 GenerateDropThread 产生器线程
这个线程很简单:定期创建并启动一个DropCharThread字母下落线程。需要特别说明的是如何为字母下落线程提供一个随机字母和一个随机栏序号。我们通过一个随机对象java.util.Random的nextInt(int range)方法产生一个0~range-1的整数作为随机栏序号,在第29~32行定义了一个随机产生字母的getRandomChar()方法,因为小写字母a~z的ASCII代码是97~112,第30行即得到一个小写字母所对应的ASCII代码,通过第31行强制类型转换就可获取一个随机的小写字母字符。
在每次循环时,都判断游戏是否被暂停,如果暂停,则线程进入睡眠,暂停产生字母下落线程,如第8~13行所示。为了统一游戏总体的控制,所以这个线程也通过canvas对象进行同步,在其他地方调用canvas.notifyAll()方法后,暂停的线程就苏醒出来,继续工作。
在第18行,线程睡眠一小段时间,通过TypeTrainApplet的generateInterval成员变量就可以控制字母下落线程下落的速度,这个参数可以直接通过网页<param name = "generateInterval" value = "500">指定其值。
4、响应用户按键事件
所谓击中下落的字母,即是用户按下键盘中的一个键所对应的字母和某个字母下落线程的字母是一致的,对应的字母下落线程结束并将击中数递增1。
要让游戏自动监测到用户所按的按键,就需要Applet响应键盘按键事件,下面我们来为Applet生成按键事件的处理方法。
打开TypeTrainApplet.java,切换到Design视图页中,在结构窗格的组件树中选择this(BorderLayout)节点,切换属性查看器到Event标签页中,双击keyPressed项,如下图所示:
此时,JBuilder为Applet生成了一个按键事件监听器,并切换到Source视图页并将光标定位到事件处理方法中,在方法中键入如下粗体的代码。
第6行判断按键是否字符的按键,如果是在第7行中获取按键所对应的字符。
打包并进行数字签名
浏览器对Applet的数字签名支持并没有一个统一的标准,但是一些著名的浏览器如IE和Navigator对进行数字签名的Applet都可以开放大部分的权限。要对Applet进行数据签名必须先将Applet类和资源打成一个JAR包。
JBuilder提供一个Applet的打包向导,在向导的最后一步可以指定一个数据证书对最终生成的JAR包进行签名。所以在这一节里,我们先介绍数字签名的技术,而后再讲解如何使用JBuilder的Applet打包向导。
1、数字签名技术
数字签名涉及到以下几个基本的概念:
·消息摘要
消息摘要是对原始数据按照一定算法进行计算得到的结果,它主要检测原始数据是否被修改过。消息摘要与加密不同,加密是对原始数据进行变换,可以从变换后的数据中获得原始数据,而消息摘要是从原始数据中获得一部分信息,它比原始数据少得多,因此消息摘要可以看作是原始数据的指纹。
·消息验证码
当甲和乙通信时,甲将数据传给乙时,同时也将数据的消息摘要传给乙,乙收到后可以用该消息摘要验证甲传的消息是否正确。这时会产生问题,即若传递过程中别人修改了数据时,同时也修改了消息摘要。乙就无法确认数据是否正确。消息验证码可以解决这一问题。 使用消息验证码的前提是 甲和乙双方有一个共同的密钥,这样甲可以将数据计算出来的消息摘要加密后发给乙,以防止消息摘要被改。由于使用了共同的密钥,所以称为"验证码"。
·数字签名
使用消息摘要和消息验证码两种技术可以保证数据没有经过改变,但接收者还无法确定数据是否确实是某个人发来的。尽管消息码可以确定数据是某个有同样密钥的人发来的,但这要求双方具有共享的密钥,若有一组用户共享,我们就无法确定数据的来源了。
数字签名即是被设计用来解决这个问题的技术。数字签名利用非对称加密技术,发送者使用私钥加密数据产生的消息摘要(签名),接收者使用发送者的公钥解密消息摘要以验证签名是否是某个人的。由于私钥只有加密者才有,因此如果接收者用某个公钥解密了某个消息摘要,就可以确定这段消息摘要必然是对应的私钥持有者发来的。
·数字证书
使用数字签名的前提是接收数据者能够确信验证签名时(用发送者的私钥加密消息摘要)所用的公钥确实是发送者本人的,这需要通过数字证书来解决这个问题。
数字证书含有两部分数据:一部分是对应发送者(组织或个人)的信息,另一部分是这个发送者所对应的公钥。即数字证书保存了发送者和其公钥一一对应的关系。同样,数字证书也有可能被假造,有效的数字证书必须经过权威 CA的签名,即权威CA验证数字证书的内容的真实性,然后再在数字证书上使用权威CA自己的私钥签名,相当于在发送者的证书上盖章。
其实数字签名技术是现实生活在计算机领域的反映,我们不妨通过一个小故事将这此技术反向映射到生活中。
《永不消失的电波》是60年代一部著名的电影,讲述了1938年我党地下组织在国统区上海的电台被敌人破坏了,延安解放区我军电台政委李侠奉命前往的上海,加强秘密电台的工作,为了保证上海地下党组织能够正确接洽到真实的李侠而不被敌人蒙骗,我们不妨来设定这样的一个情节,李侠身上带一封介绍其身份的介绍信,这相当于消息摘要,还约定了接头暗号:地下党接头中说"山上杜鹃红艳艳",李侠则要接"山下溪水细潺潺",那么这个暗号就是"消息验证码"。而在李侠的介绍信上有延安电台台长的签名,这个就是"数字签名",签名之上还加盖了一个延安电台的公章,这个就是数字证书。还有一份证名电台公章的文件那就是CA证书了。当然实际情况可能只需要一个暗号,太多东西反而会在行动中暴露身份,但在计算机领域这些东西都是必要的。
2、数字证书的生成
在JBuilder的Applet打包向导中仅需要一个数字证书,向导会为最终的JAR包生成消息摘要、消息验证码并签名。通过JDK自带的Keytool工具可以为生成一个数据证书,这个工具位于JDK的bin目录下。
打开DOS命名窗口,定位到JBuilder 2005下自带的JDK的bin目录下,执行下面的Keytool命名生成一张自己的证书:
C:/Borland/JBuilder2005/jdk1.4/bin>keytool -genkey -alias chenxhCA -keyalg RSA -keystore superCALib -validity 3650
命令窗口将要求你输入一些个人信息如下图所示:
这里我们使用keytool工具生成了一个名为chenxhCA的证书,它存放到superCALib证书库中,有效期为10年,使用的加密算法上RSA。证书库superCALib的访问密码是123456,而chenxhCA证书条目的访问密码是123123。在输入作为发送者身份标识的信息后就会在当前目标,即C:/Borland/JBuilder2005/jdk1.4/bin下生成一个名为superCALib的证书库文件。
keytool参数较多,使用也比较复杂,详细使用说明,请参见Sun网站的帮助文档:
http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/keytool.html。
一般情况下你还需要将该证书发给权威的CA签名,这个证书才会被视为合法的证书,当然你也可以模拟创建一个CA证书,用这个CA证书为我们将用于签发Applet的chenxhCA证书签名,为了简单起见我们忽略这一步。
3、打包
现在已经万事俱备了,我们可以开始利用JBuilder的打包向导将Applet所以文件打包并签名的过程。
1) File->New...->Archive,在Archive页中双击Applet JAR图标启动Applet打包向导。
2) 在向导第1步中指定Applet JAR的名字和保存到目标文件,如下图所示:
为了加速网络下载速度,我们勾选上Compress the contents of the archive选项,压缩JAR文件,减小文件的体积。Always create archive when building the project选项使用每次编辑工程时都重新创建Applet JAR包。点击Next到下一步。
3) 在这一步里,指定JAR文件中所需包含的资源文件。
由于TypeTrainApplet程序引用了3张图片,所以JAR文件除包含TypeTrainApplet.class程序文件外,还需要将用于按钮图标的文件选择进来,如下图所示:
按Next到下一步。
注意:
当你指定game.TypeTrainApplet.class,start.gif,pause.gif,stop.gif,hit.wav时,打成的Applet JAR包将不能正确运行,那些和TypeTrainApplet类位于同一程序文件的事件监听器类将被排除在外,所以需要通过game/*.*来打包。
4) 由于向导第3~6步,我们不需要作特别的设置,所以一直按Next到第7步。
在这一步里,我们用上一小节中生成的数字证书签名Applet的目标JAR文件,如下图所示:
·Digitally sign this archive选项在默认的情况下是未选中的,首先勾选该选项
·点击Keystore后的…按钮,选择我们刚才在C:/Borland/JBuilder2005/jdk1.4/bin目录下所生成的superCALib证书库文件。
·在Keystore password中输入123456,即证书库的密码。
·点击Alias后的…按钮,由于我们在superCALib证书库中仅有一个chenxhca证书,所以在弹出的Select Alias对话框的Available Alias列表中仅有一个chenxhca选项,选择chenxhca证书。
·在Alias password中输入123123,即chenxhca证书的私钥密码。
·在Store type中输入JKS,由于Keytool工具的默认证书库类型是JKS,所以superCALib的类型为JKS。
在设置完以后的信息后,按Finish结束向导,在工程窗格的资源树中将出现一个TypeTrainJAR的节点。右击这个节点,在弹出的菜单中选择Rebuild,JBuilder将创建Applet的JAR包,并用chenxhca证书签名。
Rebuild完成后,工程窗格的TypeTrainJAR节点就可以展开了,展开这个节点,我们发现目标JAR文件中除了资源文件以外,在META-INF文件夹下还有3个文件,如下图所示:
META-INF文件夹下的3个文件是和数字签名有关的文件,说明如下:
·MANIFEST.MF:这个 manifest 文件定义了与扩展和包相关的数据。
·CHENXHCA.SF:这是 JAR 文件的签名文件,文件名标识了签名者。
·CHENXHCA.RSA:与签名文件相关联的签名程序块文件,它存储了用于签名 JAR 文件的公共签名。
4、在文件中引用Applet包文件
我们现在来更改TypeTrainApplet.html中<applet>的属性使其通过JAR来引用Applet程序。这个过程很简要,打开TypeTrainApplet.html文件,切换到Source视图页中,将光标定位在<applet>标签中。窗口右边出现<applet>标签的属性输入编辑器,在archive中输入game.JAR,按回车。
JBuilder为<applet>标签添加archive的属性。由于game.JAR文件位于工程根目录下,而TypeTrainApplet.html文件位于工程目录的classes子文件夹下,所以需要将TypeTrainApplet.html拷贝到工程根目录下,这样archiver="game.JAR"的属性声明才是正确的,因为在寻找程序资源保且訲ypeTrainApplet.html所在目录为相对路径的。
保存后,到工程目录下双击TypeTrainApplet.html文件,IE检测到网页中包含了经过签名的Applet程序,弹出一个安全警告的对话框,如下图所示:
由于我们的证书没有经过权威机构的签名认证,所以对话框提示"此安全证书是由不可信的公司签发的"信息。需要指出的是游览器的JRE版本不同,弹出的警告对话框并不相同,上图是JRE版本为1.5.0时的警告对话框。
可以通过点击"更多详细信息"按钮查看证书的信息,如下图所示:
点选"签发人"项,将可以看到证书执有者的个人信息。关闭这个对话框,回到"警告-安全"对话框中,点击"是"接受这个签名的Applet。IE就对这个Applet开放了安全权限,不再受沙盒模型的限制了。
试着启动游戏,玩一伙儿后,点击"保存"按钮,Applet将正确地将统计数据保存到D:/result.txt文件中。
使用插件下载JRE
如果客户端游览器还未安装JRE或已安装的JRE版本低于你Applet的要求,或浏览器自带的JRE不是Sun公司标准的Applet,你Applet都可能无法正常运行。可以通过JDK自带的HtmlConverter.exe工具对带Applet的HTML文件进行转换,转换后的文件可以指定浏览器在运行Applet时将特定版本的JRE以插件的方式下载并安装,就象带Flash插件或SVG插件一样。
我们先从game工程根目录的classes目录下,拷贝TypeTrainApplet.html到game工程根目录下,以使其和game.jar位于同一个目录。
HtmlConverter.exe工具位于JDK的bin目录下,我们使用JDK5.0下的,导航到JDK5.0所安装的bin目录中,双击HtmlConverter.exe,稍等片刻,将弹出如下的对话框:
1.点击"指定文件或目录路径"后的"浏览…"按钮,选择工程目录下的TypeTrainApplet.html。
2."将文件备份到文件夹"指定了将未转换前的TypeTrainApplet.html文件备份到的目录。
3.在"模板文件"中设置转换模板,根据你客户端用户所在的平台和使用的浏览器选择相应的选项。这里我们选择"只适用Windows和Solaris的标准组件(IE和Navigator)"。
4.点选"使用任何Java1.5,或更高版本",这样Java plug-in插件将使用JER1.5版本,这样将使用JRE1.5系统最新的版本,如果选择"JRE1.5.0"将保持插件版本不变,则不会去更新。
5.点击"转换(C)…"开始转换,原始的TypeTrainApplet.html被备份到备份文件夹下,在原位置的TypeTrainApplet.html已经被转换的结果覆盖。
提示:
如果你想使用JRE1.4或JRE1.3作为插件,则需要使用JDK1.4或JDK1.3所带的转换器进行转换。
打开转换后的TypeTrainApplet.html文件内容如下所示:
代码清单 11 转换后的TypeTrainApplet.html
第13行指定了下载JRE插件的地址,如果没有安装这样的版本,将自动下载当前 JRE 1.5 系列的缺省下载版本,如果不能自动安装,则将用户引导到下载页面中,用户可以手工下载JRE,下载页面在第44行指定。
如果你的Applet最终部署在一个Web服务器中,且Web服务器位于局域网中,则你事先可以将JRE1.5.0下载下来,放置到Web服务器的上下文中,并更改第13行和第44行的路径。
提示:
将jinstall-1_5_0-windows-i586.cab下载并放置到自己的Web服务器中,相应更改<object>的codebase属性值,并不会成功自动安装JRE1.5.0,因为jinstall-1_5_0-windows-i586.cab并未包含JRE1.5.0的安装程序,而是通过cab文件中的jinstall-1_5_0.inf文件声明根据http://java.sun.com/update/1.5.0/1.5.0-b64.xml的配置信息,从Sun网站下载后安装。如果你Web服务器所在的局域网不能直接访问Sun网站,安装过程将无法安成。你需要更改cab文件中的jinstall-1_5_0.inf文件,下载并更改1.5.0-b64.xml配置文件才可以使局域网的客户端到你自己的Web服务器指定地址下载。
如果Applet是在jsp而非html文件中调用,则可以使用<jsp:plugin>标签来引用applet,以使applet以插件方式引用JRE。关于<jsp:plugin>的使用方法,请查看jsp相关书籍。
总结
我们讲述了如何在JBuilder开发一个简单的Applet指法练习游戏程序的过程,虽然这个游戏在功能上属于不敢见公婆型,但它涵盖了Applet开发的大部分内容和技巧。我们特在Applet中设置了一个不安全的功能:在客户机器中保存文件,浏览器事先毫不留情地阻截了它,尔后我们通过数字签名技术晓之以情,动之以理"说服"了浏览器取消安全限制。
大千世界,纷繁复杂,客户端浏览器的JRE版本和厂家百家争鸣,百花齐放,为了使我们的Applet能够在Sun标准的JRE1.5.0的版本上运行,我们动用了JDK自带的转换器对原html进行转换,这样标准的JER1.5.0将作为插件的形式下载并安装以支持这个难伺候Applet
1、字母下落线程
游戏界面中每一个下落的字母对应一个字母下落线程DropCharThread的实例,这个线程负责将一个随机的字母在指定的画布栏中从上至下落下。在TypeTrainApplet内部定义这个线程类,之所以要将其作为成员内部类来定义,是因为这样可以减少类和类之间的通信,降低调用接口的复杂度。DropCharThread需要访问到TypeTrainApplet的众多成员,作为内部类就可以直接访问TypeTrainApplet类的成员变量了。其代码如下所示:
代码清单 4 DropCharThread字母下落线程
1. … 2. public class TypeTrainApplet extends JApplet { 3. … 4. private class DropCharThread extends Thread { 5. char c; //对应的字符 6. int colIndex; //在哪列下落 7. int x, y; //行列的坐标 8. private static final int ACTION_DRAW_FONT = 1; //画字符 9. private static final int ACTION_CLEAR_FONT = 2; //清字符 10. public DropCharThread(char c, int colIndex) { 11. this.c = c; 12. this.colIndex = colIndex; 13. this.x = (colIndex - 1) * colWidth + colWidth / 2; //所在的横坐标 14. } 15. //线程方法 16. public void run() { 17. draw(ACTION_DRAW_FONT); 18. try { 19. while (c != pressKeyChar && y < canvas.getHeight() && statusCode != 0) { 20. synchronized (canvas) { 21. while (statusCode == 2) { 22. canvas.wait(); 23. } 24. } 25. draw(ACTION_CLEAR_FONT); 26. y += stepLen; 27. draw(ACTION_DRAW_FONT); 28. Thread.sleep(stepInterval); 29. } 30. } catch (InterruptedException ex) { 31. } 32. 33. pressKeyChar = ' '; 34. draw(ACTION_CLEAR_FONT); 35. if (statusCode != 0) {//游戏没有停止 36. totalCount++; //统计总数 37. if (y < canvas.getHeight()) { 38. rightCount++; //击中 39. } else { 40. errorCount++; //打不中 41. } 42. drawResult(); 43. } 44. } 45. 46. /** 47. * 画字母 48. * @param actionType 1:画字母 2: 清除字母 49. */ 50. private void draw(int actionType) { 51. synchronized (canvas) { //必须对资源canvas进行同步,否则会产生线程不安全 52. Graphics g = canvas.getGraphics(); 53. if (actionType == ACTION_CLEAR_FONT) { 54. g.setXORMode(canvas.getBackground()); //清除 55. } 56. g.setFont(new Font("Times New Roman", Font.PLAIN, 12)); 57. g.drawString("" + c, x, y); 58. } 59. } 60. } 61. … 62. } |
由于这个类比较关键,逻辑也比较复杂,为了方便说明,我们将其流程通过一个流程图来描述,如下图所示:
图 11 字母下落线程流程图 |
1) 首先在栏序号为colIndex的栏的第一个位置画出保存在变量c中的字母(第17行)。
2) 当这个字母未被击中,未到达画布底部且用户未结束游戏进行循环,这步判断对应程序的19行。如果这个判断条件通过进入第3步,即进入循环体,否则转到第5步。
3) 如果被暂停,这个线程进入等待态,直接被通知后才继续运行。需要指明一点的是,所有字母下落线程都用画布对象canvas进行同步,即用canvas进行通讯。线程间要进行通讯时,一定需要通讯线程都可以访问到的对象充当媒介将这些线程"串"起来,通过这个对象的notify()/notifyAll()/wait()在线程间通讯。这个对象好比一个"月下老人",在线程的情人间传递音讯。
4) 当线程被唤醒后,或原来就没有等待,则进入下一个循环的处理过程,在这个过程中,程序将原来位置的字母清除,下移纵坐标,并在新的位置画字母,以达到字母下落的动画效果,然后下落线程睡眠指定的毫秒数,毫秒数值为TypeTrainApplet成员变量stepInterval的值,而这个值可以在网页的<param name = "stepInterval" value = "50">标签中定义,达到控制下落速度的效果。
因为在画布上画字母后,这个字母并不会自动消失,如果直接移动纵坐标并在新位置画字母,原位置的字母依就存在。所以在新位置画字母之前,必须先将旧位置的字母清除。我们用了一个小技巧,即使用Graphics对象的setXORMode()方法,该方法两图像重叠部分的颜色。我们调用这个方法将图像重叠部分的颜色设置为画布的背景色,这样在原来的位置上再次画字母时,因为前后两次画个字母刚好重叠,就达到了清除原位置字母的效果。
画字母和清除字母的程序相似,我们把它抽出到一个方法中draw(int actionType),如第50~59行代码所示,通过入参决定是清除还是画新字母。为增强程序的可读性,我们在第8~9行中定义了两个用于表示清字母和画字母的动作常量。
5) 当程序出了循环体后,进行善后的处理:将用于保存用户按键字母的pressKeyChar变量置为空字符,在画布上清除移到底部的字母。如果游戏没有结束统计数据,并将数据写到界面的JLabel组件中。
2、添加击中音效
击中字母后播放一个短促的声音,将能大大提高游戏的听觉体验,这在节里,我们对字母下落线程稍作更改,以使其支持音效。
首先准备一个声音文件hit.wav,放在TypeTrainApplet.java相同的文件夹下。Applet类中定义了一个getAudioClip(URL url)方法,通过这个方法可以获取AudioClip的声音文件的对象。通过AudioClip的play()即可播放这个音效。
代码清单 5
1. … 2. import java.applet.AudioClip; 3. public class TypeTrainApplet extends JApplet { 4. … 5. AudioClip hitSound;//声明音效对象 6. … 7. public void init() { 8. … 9. hitSound = getAudioClip( (TypeTrainApplet.class).getResource( 10. "hit.wav"));//初始化音效对象 11. } 12. … 13. private class DropCharThread extends Thread { 14. … 15. public void run() { 16. … 17. draw(ACTION_CLEAR_FONT); 18. if (statusCode != 0) { //游戏没有停止 19. totalCount++; //统计总数 20. if (y < canvas.getHeight()) { 21. hitSound.play();//击中时播放音效 22. rightCount++; //击中 23. } else { 24. errorCount++; //打不中 25. } 26. drawResult(); 27. } 28. } 29. } 30. … 31. } |
在第5行定义一个音效的对象,在Applet初始化时获取音效对象,如第9行所示。更改字母下落线程,当击中下落的字母时播放音效,如第21行所示。
3、字母下落线程的产生器线程
指法练习需要"子子孙孙,无穷匮也"地不断产生字母下落线程,以使游戏持续进行,这个工作由产生器线程GenerateDropThread负责。GenerateDropThread线程出于和DropCharThread同样的原因,也作为TypeTrainApplet成员内部类来定义,其代码如下所示:
代码清单 6 GenerateDropThread 产生器线程
1. … 2. public class TypeTrainApplet extends JApplet { 3. … 4. private class GenerateDropThread extends Thread { 5. Random random = new Random(); //随机数 6. public void run() { 7. try { 8. while (statusCode != 0) { //产生下落线程 9. synchronized (canvas) { 10. while (statusCode == 2) { 11. canvas.wait(); 12. } 13. } 14. DropCharThread dropCharThread = new DropCharThread( 15. getRandomChar(), 16. random.nextInt(columnCount) + 1); 17. dropCharThread.start(); 18. Thread.sleep(generateInterval); 19. } 20. } catch (InterruptedException ex) { 21. } 22. } 23. 24. /** 25. * <b>功能说明:</b><br> 26. * 返回一个随机字符 27. * @return 随机字符 28. */ 29. private char getRandomChar() { 30. int temp = 97 + random.nextInt(26); 31. return (char) temp; 32. } 33. } … 34. } |
这个线程很简单:定期创建并启动一个DropCharThread字母下落线程。需要特别说明的是如何为字母下落线程提供一个随机字母和一个随机栏序号。我们通过一个随机对象java.util.Random的nextInt(int range)方法产生一个0~range-1的整数作为随机栏序号,在第29~32行定义了一个随机产生字母的getRandomChar()方法,因为小写字母a~z的ASCII代码是97~112,第30行即得到一个小写字母所对应的ASCII代码,通过第31行强制类型转换就可获取一个随机的小写字母字符。
在每次循环时,都判断游戏是否被暂停,如果暂停,则线程进入睡眠,暂停产生字母下落线程,如第8~13行所示。为了统一游戏总体的控制,所以这个线程也通过canvas对象进行同步,在其他地方调用canvas.notifyAll()方法后,暂停的线程就苏醒出来,继续工作。
在第18行,线程睡眠一小段时间,通过TypeTrainApplet的generateInterval成员变量就可以控制字母下落线程下落的速度,这个参数可以直接通过网页<param name = "generateInterval" value = "500">指定其值。
4、响应用户按键事件
所谓击中下落的字母,即是用户按下键盘中的一个键所对应的字母和某个字母下落线程的字母是一致的,对应的字母下落线程结束并将击中数递增1。
要让游戏自动监测到用户所按的按键,就需要Applet响应键盘按键事件,下面我们来为Applet生成按键事件的处理方法。
打开TypeTrainApplet.java,切换到Design视图页中,在结构窗格的组件树中选择this(BorderLayout)节点,切换属性查看器到Event标签页中,双击keyPressed项,如下图所示:
图 12 为Applet生成响应按键的事件处理方法 |
此时,JBuilder为Applet生成了一个按键事件监听器,并切换到Source视图页并将光标定位到事件处理方法中,在方法中键入如下粗体的代码。
1. … 2. public class TypeTrainApplet extends JApplet { 3. … 4. /**获取用户点击按键所对应的字符*/ 5. public void this_keyPressed(KeyEvent e) { 6. if (!e.isActionKey()) { 7. pressKeyChar = e.getKeyChar(); 8. } 9. } 10. … 11. } |
第6行判断按键是否字符的按键,如果是在第7行中获取按键所对应的字符。
|
浏览器对Applet的数字签名支持并没有一个统一的标准,但是一些著名的浏览器如IE和Navigator对进行数字签名的Applet都可以开放大部分的权限。要对Applet进行数据签名必须先将Applet类和资源打成一个JAR包。
JBuilder提供一个Applet的打包向导,在向导的最后一步可以指定一个数据证书对最终生成的JAR包进行签名。所以在这一节里,我们先介绍数字签名的技术,而后再讲解如何使用JBuilder的Applet打包向导。
1、数字签名技术
数字签名涉及到以下几个基本的概念:
·消息摘要
消息摘要是对原始数据按照一定算法进行计算得到的结果,它主要检测原始数据是否被修改过。消息摘要与加密不同,加密是对原始数据进行变换,可以从变换后的数据中获得原始数据,而消息摘要是从原始数据中获得一部分信息,它比原始数据少得多,因此消息摘要可以看作是原始数据的指纹。
·消息验证码
当甲和乙通信时,甲将数据传给乙时,同时也将数据的消息摘要传给乙,乙收到后可以用该消息摘要验证甲传的消息是否正确。这时会产生问题,即若传递过程中别人修改了数据时,同时也修改了消息摘要。乙就无法确认数据是否正确。消息验证码可以解决这一问题。 使用消息验证码的前提是 甲和乙双方有一个共同的密钥,这样甲可以将数据计算出来的消息摘要加密后发给乙,以防止消息摘要被改。由于使用了共同的密钥,所以称为"验证码"。
·数字签名
使用消息摘要和消息验证码两种技术可以保证数据没有经过改变,但接收者还无法确定数据是否确实是某个人发来的。尽管消息码可以确定数据是某个有同样密钥的人发来的,但这要求双方具有共享的密钥,若有一组用户共享,我们就无法确定数据的来源了。
数字签名即是被设计用来解决这个问题的技术。数字签名利用非对称加密技术,发送者使用私钥加密数据产生的消息摘要(签名),接收者使用发送者的公钥解密消息摘要以验证签名是否是某个人的。由于私钥只有加密者才有,因此如果接收者用某个公钥解密了某个消息摘要,就可以确定这段消息摘要必然是对应的私钥持有者发来的。
·数字证书
使用数字签名的前提是接收数据者能够确信验证签名时(用发送者的私钥加密消息摘要)所用的公钥确实是发送者本人的,这需要通过数字证书来解决这个问题。
数字证书含有两部分数据:一部分是对应发送者(组织或个人)的信息,另一部分是这个发送者所对应的公钥。即数字证书保存了发送者和其公钥一一对应的关系。同样,数字证书也有可能被假造,有效的数字证书必须经过权威 CA的签名,即权威CA验证数字证书的内容的真实性,然后再在数字证书上使用权威CA自己的私钥签名,相当于在发送者的证书上盖章。
其实数字签名技术是现实生活在计算机领域的反映,我们不妨通过一个小故事将这此技术反向映射到生活中。
《永不消失的电波》是60年代一部著名的电影,讲述了1938年我党地下组织在国统区上海的电台被敌人破坏了,延安解放区我军电台政委李侠奉命前往的上海,加强秘密电台的工作,为了保证上海地下党组织能够正确接洽到真实的李侠而不被敌人蒙骗,我们不妨来设定这样的一个情节,李侠身上带一封介绍其身份的介绍信,这相当于消息摘要,还约定了接头暗号:地下党接头中说"山上杜鹃红艳艳",李侠则要接"山下溪水细潺潺",那么这个暗号就是"消息验证码"。而在李侠的介绍信上有延安电台台长的签名,这个就是"数字签名",签名之上还加盖了一个延安电台的公章,这个就是数字证书。还有一份证名电台公章的文件那就是CA证书了。当然实际情况可能只需要一个暗号,太多东西反而会在行动中暴露身份,但在计算机领域这些东西都是必要的。
2、数字证书的生成
在JBuilder的Applet打包向导中仅需要一个数字证书,向导会为最终的JAR包生成消息摘要、消息验证码并签名。通过JDK自带的Keytool工具可以为生成一个数据证书,这个工具位于JDK的bin目录下。
打开DOS命名窗口,定位到JBuilder 2005下自带的JDK的bin目录下,执行下面的Keytool命名生成一张自己的证书:
C:/Borland/JBuilder2005/jdk1.4/bin>keytool -genkey -alias chenxhCA -keyalg RSA -keystore superCALib -validity 3650
命令窗口将要求你输入一些个人信息如下图所示:
图 15 生成数字证书的命令窗口 |
这里我们使用keytool工具生成了一个名为chenxhCA的证书,它存放到superCALib证书库中,有效期为10年,使用的加密算法上RSA。证书库superCALib的访问密码是123456,而chenxhCA证书条目的访问密码是123123。在输入作为发送者身份标识的信息后就会在当前目标,即C:/Borland/JBuilder2005/jdk1.4/bin下生成一个名为superCALib的证书库文件。
keytool参数较多,使用也比较复杂,详细使用说明,请参见Sun网站的帮助文档:
http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/keytool.html。
一般情况下你还需要将该证书发给权威的CA签名,这个证书才会被视为合法的证书,当然你也可以模拟创建一个CA证书,用这个CA证书为我们将用于签发Applet的chenxhCA证书签名,为了简单起见我们忽略这一步。
3、打包
现在已经万事俱备了,我们可以开始利用JBuilder的打包向导将Applet所以文件打包并签名的过程。
1) File->New...->Archive,在Archive页中双击Applet JAR图标启动Applet打包向导。
2) 在向导第1步中指定Applet JAR的名字和保存到目标文件,如下图所示:
图 16 指定文件名 |
为了加速网络下载速度,我们勾选上Compress the contents of the archive选项,压缩JAR文件,减小文件的体积。Always create archive when building the project选项使用每次编辑工程时都重新创建Applet JAR包。点击Next到下一步。
3) 在这一步里,指定JAR文件中所需包含的资源文件。
由于TypeTrainApplet程序引用了3张图片,所以JAR文件除包含TypeTrainApplet.class程序文件外,还需要将用于按钮图标的文件选择进来,如下图所示:
图 17 指定JAR的内容 |
按Next到下一步。
注意:
当你指定game.TypeTrainApplet.class,start.gif,pause.gif,stop.gif,hit.wav时,打成的Applet JAR包将不能正确运行,那些和TypeTrainApplet类位于同一程序文件的事件监听器类将被排除在外,所以需要通过game/*.*来打包。
4) 由于向导第3~6步,我们不需要作特别的设置,所以一直按Next到第7步。
在这一步里,我们用上一小节中生成的数字证书签名Applet的目标JAR文件,如下图所示:
图 18 指定如何对JAR进行签名 |
·Digitally sign this archive选项在默认的情况下是未选中的,首先勾选该选项
·点击Keystore后的…按钮,选择我们刚才在C:/Borland/JBuilder2005/jdk1.4/bin目录下所生成的superCALib证书库文件。
·在Keystore password中输入123456,即证书库的密码。
·点击Alias后的…按钮,由于我们在superCALib证书库中仅有一个chenxhca证书,所以在弹出的Select Alias对话框的Available Alias列表中仅有一个chenxhca选项,选择chenxhca证书。
·在Alias password中输入123123,即chenxhca证书的私钥密码。
·在Store type中输入JKS,由于Keytool工具的默认证书库类型是JKS,所以superCALib的类型为JKS。
在设置完以后的信息后,按Finish结束向导,在工程窗格的资源树中将出现一个TypeTrainJAR的节点。右击这个节点,在弹出的菜单中选择Rebuild,JBuilder将创建Applet的JAR包,并用chenxhca证书签名。
Rebuild完成后,工程窗格的TypeTrainJAR节点就可以展开了,展开这个节点,我们发现目标JAR文件中除了资源文件以外,在META-INF文件夹下还有3个文件,如下图所示:
图 19 目标JAR中关于签名的文件 |
META-INF文件夹下的3个文件是和数字签名有关的文件,说明如下:
·MANIFEST.MF:这个 manifest 文件定义了与扩展和包相关的数据。
·CHENXHCA.SF:这是 JAR 文件的签名文件,文件名标识了签名者。
·CHENXHCA.RSA:与签名文件相关联的签名程序块文件,它存储了用于签名 JAR 文件的公共签名。
4、在文件中引用Applet包文件
我们现在来更改TypeTrainApplet.html中<applet>的属性使其通过JAR来引用Applet程序。这个过程很简要,打开TypeTrainApplet.html文件,切换到Source视图页中,将光标定位在<applet>标签中。窗口右边出现<applet>标签的属性输入编辑器,在archive中输入game.JAR,按回车。
图 20 更改网页的<applet>标签属性 |
JBuilder为<applet>标签添加archive的属性。由于game.JAR文件位于工程根目录下,而TypeTrainApplet.html文件位于工程目录的classes子文件夹下,所以需要将TypeTrainApplet.html拷贝到工程根目录下,这样archiver="game.JAR"的属性声明才是正确的,因为在寻找程序资源保且訲ypeTrainApplet.html所在目录为相对路径的。
保存后,到工程目录下双击TypeTrainApplet.html文件,IE检测到网页中包含了经过签名的Applet程序,弹出一个安全警告的对话框,如下图所示:
图 21 IE在运行签名的Applet前的安全警告 |
由于我们的证书没有经过权威机构的签名认证,所以对话框提示"此安全证书是由不可信的公司签发的"信息。需要指出的是游览器的JRE版本不同,弹出的警告对话框并不相同,上图是JRE版本为1.5.0时的警告对话框。
可以通过点击"更多详细信息"按钮查看证书的信息,如下图所示:
图 22 签名证书的信息 |
点选"签发人"项,将可以看到证书执有者的个人信息。关闭这个对话框,回到"警告-安全"对话框中,点击"是"接受这个签名的Applet。IE就对这个Applet开放了安全权限,不再受沙盒模型的限制了。
试着启动游戏,玩一伙儿后,点击"保存"按钮,Applet将正确地将统计数据保存到D:/result.txt文件中。
使用插件下载JRE
如果客户端游览器还未安装JRE或已安装的JRE版本低于你Applet的要求,或浏览器自带的JRE不是Sun公司标准的Applet,你Applet都可能无法正常运行。可以通过JDK自带的HtmlConverter.exe工具对带Applet的HTML文件进行转换,转换后的文件可以指定浏览器在运行Applet时将特定版本的JRE以插件的方式下载并安装,就象带Flash插件或SVG插件一样。
我们先从game工程根目录的classes目录下,拷贝TypeTrainApplet.html到game工程根目录下,以使其和game.jar位于同一个目录。
HtmlConverter.exe工具位于JDK的bin目录下,我们使用JDK5.0下的,导航到JDK5.0所安装的bin目录中,双击HtmlConverter.exe,稍等片刻,将弹出如下的对话框:
图 23 HTML转换工具 |
1.点击"指定文件或目录路径"后的"浏览…"按钮,选择工程目录下的TypeTrainApplet.html。
2."将文件备份到文件夹"指定了将未转换前的TypeTrainApplet.html文件备份到的目录。
3.在"模板文件"中设置转换模板,根据你客户端用户所在的平台和使用的浏览器选择相应的选项。这里我们选择"只适用Windows和Solaris的标准组件(IE和Navigator)"。
4.点选"使用任何Java1.5,或更高版本",这样Java plug-in插件将使用JER1.5版本,这样将使用JRE1.5系统最新的版本,如果选择"JRE1.5.0"将保持插件版本不变,则不会去更新。
5.点击"转换(C)…"开始转换,原始的TypeTrainApplet.html被备份到备份文件夹下,在原位置的TypeTrainApplet.html已经被转换的结果覆盖。
提示:
如果你想使用JRE1.4或JRE1.3作为插件,则需要使用JDK1.4或JDK1.3所带的转换器进行转换。
打开转换后的TypeTrainApplet.html文件内容如下所示:
代码清单 11 转换后的TypeTrainApplet.html
1. <html> 2. <head> 3. <meta http-equiv="Content-Type" content="text/html; charset=GBK"> 4. <title>HTML Test Page</title> 5. </head> 6. <body>game.TypeTrainApplet will appear below in a Java enabled browser. 7. <br> 8. <!--"CONVERTED_APPLET"--> 9. <!-- HTML CONVERTER --> 10. <object 11. classid = "clsid:CAFEEFAC-0015-0000-0000-ABCDEFFEDCBA" 12. codebase = 13. "http://java.sun.com/update/1.5.0/jinstall-1_5_0-windows-i586.cab#Version=1,5,0,0" 14. WIDTH = "400" HEIGHT = "400" NAME = "TestApplet" ALIGN = "middle" VSPACE = 15. "0" HSPACE = "0" > 16. <PARAM NAME = CODE VALUE = "game.TypeTrainApplet.class" > 17. <PARAM NAME = CODEBASE VALUE = "." > 18. <PARAM NAME = ARCHIVE VALUE = "game.JAR" > 19. <PARAM NAME = NAME VALUE = "TestApplet" > 20. <param name = "type" value = "application/x-java-applet;jpi-version=1.5"> 21. <param name = "scriptable" value = "false"> 22. <PARAM NAME = "stepLen" VALUE="2"> 23. <PARAM NAME = "stepInterval" VALUE="50"> 24. <PARAM NAME = "columnCount" VALUE="10"> 25. <PARAM NAME = "generateInterval" VALUE="500"> 26. 27. <comment> 28. <embed 29. type = "application/x-java-applet;jpi-version=1.5" / 30. CODE = "game.TypeTrainApplet.class" / 31. JAVA_CODEBASE = "." / 32. ARCHIVE = "game.JAR" / 33. NAME = "TestApplet" / 34. WIDTH = "400" / 35. HEIGHT = "400" / 36. ALIGN = "middle" / 37. VSPACE = "0" / 38. HSPACE = "0" / 39. stepLen ="2" / 40. stepInterval ="50" / 41. columnCount ="10" / 42. generateInterval ="500" 43. scriptable = false 44. pluginspage = "http://java.sun.com/products/plugin/index.html#download"> 45. <noembed> 46. 47. </noembed> 48. </embed> 49. </comment> 50. </object> 51. 52. <!-- 53. <APPLET CODE = "game.TypeTrainApplet.class" JAVA_CODEBASE = "." ARCHIVE 54. = "game.JAR" WIDTH = "400" HEIGHT = "400" NAME = "TestApplet" ALIGN = 55. "middle" VSPACE = "0" HSPACE = "0"> 56. <PARAM NAME = "stepLen" VALUE="2"> 57. <PARAM NAME = "stepInterval" VALUE="50"> 58. <PARAM NAME = "columnCount" VALUE="10"> 59. <PARAM NAME = "generateInterval" VALUE="500"> 60. </APPLET> 61. --> 62. <!--"END_CONVERTED_APPLET"--> 63. </body> 64. </html> |
第13行指定了下载JRE插件的地址,如果没有安装这样的版本,将自动下载当前 JRE 1.5 系列的缺省下载版本,如果不能自动安装,则将用户引导到下载页面中,用户可以手工下载JRE,下载页面在第44行指定。
如果你的Applet最终部署在一个Web服务器中,且Web服务器位于局域网中,则你事先可以将JRE1.5.0下载下来,放置到Web服务器的上下文中,并更改第13行和第44行的路径。
提示:
将jinstall-1_5_0-windows-i586.cab下载并放置到自己的Web服务器中,相应更改<object>的codebase属性值,并不会成功自动安装JRE1.5.0,因为jinstall-1_5_0-windows-i586.cab并未包含JRE1.5.0的安装程序,而是通过cab文件中的jinstall-1_5_0.inf文件声明根据http://java.sun.com/update/1.5.0/1.5.0-b64.xml的配置信息,从Sun网站下载后安装。如果你Web服务器所在的局域网不能直接访问Sun网站,安装过程将无法安成。你需要更改cab文件中的jinstall-1_5_0.inf文件,下载并更改1.5.0-b64.xml配置文件才可以使局域网的客户端到你自己的Web服务器指定地址下载。
如果Applet是在jsp而非html文件中调用,则可以使用<jsp:plugin>标签来引用applet,以使applet以插件方式引用JRE。关于<jsp:plugin>的使用方法,请查看jsp相关书籍。
总结
我们讲述了如何在JBuilder开发一个简单的Applet指法练习游戏程序的过程,虽然这个游戏在功能上属于不敢见公婆型,但它涵盖了Applet开发的大部分内容和技巧。我们特在Applet中设置了一个不安全的功能:在客户机器中保存文件,浏览器事先毫不留情地阻截了它,尔后我们通过数字签名技术晓之以情,动之以理"说服"了浏览器取消安全限制。
大千世界,纷繁复杂,客户端浏览器的JRE版本和厂家百家争鸣,百花齐放,为了使我们的Applet能够在Sun标准的JRE1.5.0的版本上运行,我们动用了JDK自带的转换器对原html进行转换,这样标准的JER1.5.0将作为插件的形式下载并安装以支持这个难伺候Applet