页面

2010年4月29日星期四

Handles the timer

/**
* Handles the timer
*/
private function enterFrameHandler(event:Event):void
{
tickPosition = int((getTimer() % 1000) / framePeriod);

if (tickLastPosition != tickPosition)
{
tickLastPosition = tickPosition;
canvas.lock();
canvas.fillRect(canvasClearRect, 0x000000);
render();
canvas.unlock();
}
}
继续看Rendering game assets in ActionScript using blitting techniques and Flash Builder 4

2010年4月27日星期二

敏捷不是过程,SCRUM只是框架,XP也是别人的实践

敏捷不是过程
经常听到有人说我们采用敏捷开发过程,我自己原来也这么说,但经过做几年的项目,我突然意识到敏捷不是过程。好像敏捷的那些之“父”也没有说过敏捷是一种过程。我们拿它来和瀑布模型,V模型来比较是没有意义的。看过敏捷宣言的人,都知道他只提到了四大主题思想和十二项原则
Individuals and interactions over processes and tools 
Working software over comprehensive documentation 
Customer collaboration over contract negotiation 
Responding to change over following a plan
我发现提出敏捷的那帮人,真是太聪明了,他们没有提出一个具体的过程,因为是过程都不是银弹,都只能解决一些特定领域的项目,或者是某一过程只是更擅长解决某一类型的项目。但是,思想这东西可不一样,就像我们每个人都知道有 “态度决定一切”这个帽子。尤其适合我们中国人,大部分都是如果我好好做,我一定会想办法做好,不好好做,我也让你找不出毛病。很多 “老油条”做工作,分的非常清,到领导那里是 “进可功,退可守,关键时候能转手”。这样的人,我的建议是不用,想想,你和你老婆把事情分的那么清,那能感情好吗?一个没有感情的团体能有多大的产出呢?所以,我一直说,项目管理有时就是帮大家建立感情,大家不但要有共同的目标,而且要有共同的指导思想。
所以,我说,敏捷提出了一个正确思想,让大家有共同的语言,有人说敏捷宣言,其实就是废话。实际上大凡能够得到很多人认可的事情,就是很明显的事,这样大家才容易理解。大家认为是“废话”,认为本来这样的嘛,还用你说,这是不一样的,首先说明大家承认这个“废话”是对的,只要是都认为是对,接下来继续讨论的意义就有了。
做了这么多年的项目,我对敏捷不是过程更坚定了,因为我之前做过的项目,用的是瀑布过程,同样成功了,那是为什么呢?因为瀑布有适合瀑布的场景。而且从我知道敏捷到实践敏捷这近三年的时间里,我越来越发现,如果我把敏捷的思想,至少是部分的思想用到之前的瀑布过程中,那一定是一件“很爽”的事情,我说的“很爽”可能你用了敏捷一段时间后也能体会到。比如:自动集成,单元测试,客户尽早参与等等。我实在不想谈具体的办法,因为那样来论证“敏捷不是过程”这个标题就庸俗了,因为你一定可以找到我说话的疼点。
很多人说,我没有推行“敏捷”,我项目用的是XX过程,我也成功了,我们也都很爽,我想说的是你可能就是把敏捷当过程了次这么说,实际上你已经接近或者使用的正是我所说的敏捷,只是你不好意思承认罢了, :), 算了,嗯啊,你要还是不承认的话,你就承认你找到了“银弹吧”。
SCRUM只是框架
SCRUM这个东西,要想知道那几个名词,太easy了,backlog, sprint, user story等等,这些东西你用一天时间就看完了,随便在网上搜搜就明白了,还不明白,看看《scrum-xp-from-the-trenches》或者查查《scrum checklist》,你很快就会明白了。然后,你要是认为scrum就是那几根毛,我靠,SCRUM一定笑了。
所以,我这个标题就是SCRUM只是框架,他只是从管理上来看项目的,也就是如果,你只告诉大家“累堆子安的砖头们(ladies and gentleman),告诉大家一个好消息,我发现了一个叫SCRUM的东西,今天我们来一,二,三,向前,向前…” 我想说的是,你可能调到水里连响声都没有。
SCRUM只是个框架,框架这个东西,做开发的人都知道,那他是以不变应万变的东西,他是不可能包括你的所有的“上层”应用的,如果这样,没几个人会喜欢这个框架的,指定几个规则,怎么“打拳”,那是需要随机应变的,说通俗点,就是这个是要根据context来调整的。
比如,sprint是多长,每周工作40小时,需要成以0.75还是0.5,那是要看具体团队的情况,每个user story需要多少时间,多少个点,这个水是很深的,不信, 看看《人月神话》。
总结一下,SCRUM只是一个框架,是站在管理层或者用户层的,不要强制从上向下推,每一个scrum里的内容,需要经过“实践--反思--实践--反思”这个过程的。
XP也是别人的实践
image
XP是很好的实践,但是我们要知道,这些实践来自于那些技术高手们,在项目里我们不是那些高手,我们甚至找不到那样一个高手,所以,我们千万别照搬所有的XP实践,降龙十八掌学完十八式,那是要有慧根的。
比如结对编程,你有很好的理解吗?如果没有,那就还是别大规模的使用,先找两个人试试吧。
比如TDD,单元测试都写不好,设计代码的根本没法写单元测试,就想推行TDD?是测试驱动开发还是测试驱动设计清楚吗?
当然,说了这些不是说是别人的实践,你就不能用了,我要说的是恰恰是别人的实践,我们要用,问题就是我们要变成自己的实践,行不行,先试试吧
image
下面是我的实践,当然对你可是“别人的实践”,仅供参考,切勿照搬
a.共享信息空间,中文还是没有英文好表达,Informative Workspace
b.坐在一起
c. 站立会议
d. 版本控制
e. 持续集成
f.  集体代码所有权
g. 简单设计
h. 重构
等等。
不同于SCRUM, XP是注重自底向上的,就是先关心的是程序员。这也是我最推崇的一种方式,程序员的问题解决了,推行SCRUM那就是易于反掌的事了。因为推不推行SCRUM已经是不重要的事的。因为你SCRUM需要的东西,我程序员都能做到,相反,程序员每天工作出现很多困扰,谈管理也只是一句“空中的话”了。
那么该如何做呢? 就是:我们拥护敏捷的思想,采用SCRUM的框架,再加上实践XP的一些好的实践,坚定不移的走我们自己的路,让别人去说吧。一小部分人先敏捷起来,带动后一部分人一起XP吧。
最后,申明一点,信敏捷,则敏捷则灵,不信,也别强求吧。但是,春哥,你一定要信,因为“信春哥,得永生”。当然,水哥,你也是要信,因为“上善若水”嘛。你们可以不同意我的观点,但请誓死捍卫我说话的权利。
王德水 北京 2010-04-27 23:10
作者: 王德水
出处:http://www.cnblogs.com/cnblogsfans
版权:本文版权归作者和博客园共有

关于flash中大地图分块加载的优化方式

目录:
1)讨论下flash Tile方式实现大地图怎么能获得较高的效率;
2)扩展讨论下如何利用同样的思想去实现其它一些耗时或大数据量操作,如:分时间片模拟多线程实现对大地图数据的处理而不导致界面卡死;
3)如何很好的利用下flash核心的跑道模型

2010年4月26日星期一

屏蔽子级光标响应,节省cpu消耗的测试

转载自:http://bbs.blueidea.com/thread-2982019-1-1.html
昨晚老衰在群里聊起了光标响应对cpu消耗的问题,也介绍了他的思路,所以今晚就写了一个用DisplayObjectContainer的getObjectsUnderPoint方法的方案. 写的粗糙,请诸位大大指导.

奥 如果有评分的话,请版主分给老衰一半,免得他抑郁了再找我麻烦.

下载地址:http://aaaqetools.googlecode.com/files/MouseEnabledTest.rar

闲话少说,上代码.

package com.aaaqe.common.utils.mouseProxy
{
    import flash.events.IEventDispatcher;
    /**
     * 需要被代理鼠标事件的接口
     * @author 汪汪
     *
     */
    public interface INeedMouseProxy extends IEventDispatcher
    {
        /**
         * 设置鼠标是否响应.
         * @return
         *
         */      
        function get enabled():Boolean;
        function set enabled(value:Boolean):void;
    }
}

package com.aaaqe.common.utils.mouseProxy
{
    import flash.display.InteractiveObject;
    import flash.events.MouseEvent;
    /**
     * 代理的鼠标事件
     * @author 汪汪
     *
     */  
    public class ProxyMouseEvnet extends MouseEvent
    {
        public static const PROXY_CLICK:String = "proxyClick";
        public static const PROXY_MOUSEDOWN:String = "proxyMousedown";
        public static const PROXY_MOUSEUP:String = "proxyMouseup";
        public static const PROXY_MOUSEOVER:String = "proxyMouseover";
        public static const PROXY_MOUSEOUT:String = "proxyMouseout";
        public static const PROXY_ROLLOVER:String = "proxyRollover";
        public static const PROXY_ROLLOUT:String = "proxyRollout";
        public function ProxyMouseEvnet(type:String, bubbles:Boolean=true, cancelable:Boolean=false, localX:Number=NaN, localY:Number=NaN, relatedObject:InteractiveObject=null, ctrlKey:Boolean=false, altKey:Boolean=false, shiftKey:Boolean=false, buttonDown:Boolean=false, delta:int=0)
        {
            super(type, bubbles, cancelable, localX, localY, relatedObject, ctrlKey, altKey, shiftKey, buttonDown, delta);
        }
        /**
         * 复制一个正常鼠标事件
         * @param event 鼠标事件
         * @param relatedObject 这个参数我不理解在MouseEvent里究竟怎么用,暂时先放到这里.
         * 希望看到的达人来教教我.多谢.
         * @return
         *
         */      
        public static function copyMouseEvent(event:MouseEvent, relatedObject:InteractiveObject):ProxyMouseEvnet
        {
            var typeName:String;
            var evt:ProxyMouseEvnet;
            if(( typeName =ProxyMouseEvnet["PROXY_" + event.type.toLocaleUpperCase()]))
            {
                with(event)
                    evt = new ProxyMouseEvnet(typeName, bubbles, cancelable, localX, localY,relatedObject, ctrlKey, altKey, shiftKey, buttonDown, delta);
            }
            return evt;
        }
    }
}

package com.aaaqe.common.utils.mouseProxy
{
  
    import flash.display.DisplayObject;
    import flash.display.DisplayObjectContainer;
    import flash.display.InteractiveObject;
    import flash.display.Shape;
    import flash.events.MouseEvent;
    import flash.geom.Point;
  
    /**
     * 光标代理的静态工具类.
     * @author 汪汪
     *
     */
    public class MouseProxyUtil
    {
        /**
         * 代理被取消鼠标响应对象重新派发事件
         * @param evt 一个displayObjectContianer的鼠标事件
         * @param parent 非例外的话,parent应该是evt的响应者.
         * @return 如果此事件归属一个INeedMouseProxy对象派发的话就返回true,反正则是false.
         *
         */      
        public static function dispatchProxyMouseEvent(evt:MouseEvent,parent:DisplayObjectContainer):Boolean
        {
          
            var children:Array;
            var needMouseProxy:INeedMouseProxy;
            var child:DisplayObject;
            var pme:ProxyMouseEvnet;
            children = parent.getObjectsUnderPoint( new Point(parent.mouseX,parent.mouseY));
            while(children.length)
            {
                // *** 据本人暂时不可靠观察发现 -- 舞台创建绘制的话,底层会是shape,而代码在sprite上绘制的话,
                // *** 第一个对象就是Sprite,这点是舞台的Sprite和脚本的不同.
                // *** 然后就是getObjectsUnderPoint得到的数组最后一个就是最顶上的显示对象,
                // *** 所以如果child是Shape的话可以用child.parent来得到可以用来正确判断的对象.
                child = children.pop();
                if(child is Shape)
                    child = child.parent;
                if((needMouseProxy = child as INeedMouseProxy))
                {
                    pme = ProxyMouseEvnet.copyMouseEvent(evt, null);
                    if(pme)
                        needMouseProxy.dispatchEvent(pme);
                    else
                    {
                        return false;
                    }
                    return true;
                }
                // *** 如果上层是InteractiveObject,且没禁止鼠标事件,则应当停止检测.
                // *** 这个检测在例如自定义光标的情况下有用.
                else if( InteractiveObject(child).mouseEnabled)
                {
                    return false;
                }
            }
            return false;
        }
    }
}

package ui
{
    import com.aaaqe.common.utils.mouseProxy.ProxyMouseEvnet;
  
    import flash.display.Sprite;
    import flash.events.MouseEvent;
    import com.aaaqe.common.utils.mouseProxy.INeedMouseProxy;
    [Event(type="proxyClick",name="com.aaaqe.common.utils.mouseProxy.ProxyMouseEvent")]
    [Event(type="proxyMousedown",name="com.aaaqe.common.utils.mouseProxy.ProxyMouseEvent")]
    [Event(type="proxyMouseup",name="com.aaaqe.common.utils.mouseProxy.ProxyMouseEvent")]
    [Event(type="proxyRollover",name="com.aaaqe.common.utils.mouseProxy.ProxyMouseEvent")]
    [Event(type="proxyRollout",name="com.aaaqe.common.utils.mouseProxy.ProxyMouseEvent")]
    public class MouseSprite extends Sprite implements INeedMouseProxy
    {
        public function MouseSprite()
        {
            super();
            init();
        }
        private function init():void
        {
            enabled = false;
            draw();
            addEventListener(ProxyMouseEvnet.PROXY_CLICK, click);
        }
        private function click(evt:MouseEvent):void
        {
            trace(evt.type, evt.currentTarget, evt.currentTarget == evt.target);
        }
        private var _enabled:Boolean;
        public function get enabled():Boolean
        {
            return _enabled;
        }
        public function set enabled(value:Boolean):void
        {
            mouseChildren = mouseEnabled = _enabled = value;
        }
        public function draw():void
        {
            with(graphics)
            {
                beginFill(Math.random() * 0xFFFFFF);
                drawRect(0,0,Math.random() * 20 + 10 , Math.random() * 20 + 10);
                endFill();
            }
        }
    }
}

package
{
    import com.aaaqe.common.utils.mouseProxy.INeedMouseProxy;
    import com.aaaqe.common.utils.mouseProxy.MouseProxyUtil;
    import com.aaaqe.common.utils.mouseProxy.ProxyMouseEvnet;
  
    import flash.display.DisplayObject;
    import flash.display.InteractiveObject;
    import flash.display.Shape;
    import flash.display.Sprite;
    import flash.events.MouseEvent;
    import flash.geom.Point;
  
    import ui.MouseSprite;
  
    import view.Container;
    [SWF(height=700,width=998,frameRate=50)]
    public class MouseEnabledTest extends SimpleSprite
    {
        public function MouseEnabledTest()
        {
            createChildren();
            addEvents();
        }
        private var container:Container;
        private function createChildren():void
        {
            container = new Container();
            addChild(container);
        }
        private function addEvents():void
        {
            addEventListener(MouseEvent.CLICK, click);
            addEventListener(MouseEvent.ROLL_OVER ,rollOver);
            addEventListener(MouseEvent.MOUSE_OUT,mouseOut);
        }
        private function click(evt:MouseEvent):void
        {
            MouseProxyUtil.dispatchProxyMouseEvent(evt,container);
        }
        private function rollOver(evt:MouseEvent):void
        {
            MouseProxyUtil.dispatchProxyMouseEvent(evt,container);
        }
        private function mouseOut(evt:MouseEvent):void
        {
            MouseProxyUtil.dispatchProxyMouseEvent(evt,container);
        }
    }
}

2010年4月21日星期三

Flash Player 10.1内部机制(第二部分) - 执行模型之可变跑道(转)

理解执行模型
执行模型是指Flash Player在每一个帧周期中如何执行相应的指令操作。Flash Player后台事实上运行着n多线程,只是AS并没有给开发人员提供多线程编程模型。这意味着从概念上来讲我们要把Flash Player看做是单线程运行实体,有关这一单线程编程模型的优势/劣势的争论从未休止过,我不想对这一具有争议性的问题做过多评论,但请大家记住这一事实。

可变跑道(Elastic Racetrack)
可变跑道是Flash Player的帧执行模型,这个模型描述了在一帧的处理周期中,代码执行和帧渲染的工作是怎样彼此平衡的。Flash Player 9和AVM2对这一模型进行了一些改进,这一信息是基于对事件机制和渲染模型的研究总结出来的,完整的模型尚未被官方公布。


基本的跑道理论没有发生改变,在Flash Player执行一帧的周期里,前一部分时间用于执行代码,剩余时间用于渲染显示列表中的对象。每个执行阶段都可以根据实际需求增加执行时间来执行更多代码或做更多的渲染工作,而跑道的总长度也将相应增长。

elasticracetrackexport.png

在前一模型基础上发生改变的是每一阶段在一个微观周期里的样子以及他们怎样形成一帧。

AVM2是由Flash Player中一个叫做Marshal的元帅级组件所操控,Marshal负责将时间切割成Flash Player工作所依的基本时间片,在这里我希望澄清一下Flash Player的时间片跟swf文件运行时的帧速率没有任何关系,我们将最终看到Flash Player是如何将这些时间片合成为一帧。在Mac OS版Firefox中执行一个由Flex编译得来的swf文件,Marshal通常会将时间切割成19-20毫秒的时间片,时间片大小根据平台和浏览器的不同而存在差异.为方便我们接下来的讨论,我们假定时间片大小为20毫秒,也就是说Marshal每秒钟会产生不超过50个时间片,每个时间片中,五步可能的操作按如下顺序执行:
 

  1. Player事件调度 - 比如Timer事件,鼠标事件,ENTER_FRAME事件,URLLoader事件等等。
  2. 用户代码执行 - 所有侦听上一步相应事件的代码被执行。
  3. RENDER事件调度 - 在用户代码执行期间调用stage.invalidate()会触发这一特殊事件。
  4. 最后的用户代码执行 - 侦听上述第三步特殊事件的用户代码此时被执行。
  5. Player更改显示列表。

      marshalledsliceexport.png
      Marshal如此反复的执行20毫秒时间片并在运行中决定下一步操作。一个时间片中执行的所有这些操作最终归纳为上述两段式跑道(代码执行,图像渲染)也就是我们所说的一帧。用户代码和失效操作填充在代码执行区,渲染操作填充在跑道的渲染区段。需要指出的是相关操作只能在Marshal预定的时间内发生,如果你的用户代码很短,那么Marshal仍然会在执行完用户代码后等待一段时间然后进入渲染阶段。

      为了更好阐述哪些action被如何执行以及可变跑道如何被创建,请参考如下示例,分别描述了以5fps, 25fps和50fps帧速率工作的swf中时间片是如何被处理的。

      framemarshalingexport.png
      以上示例可以看出,不同的帧速率下,一个帧周期中的可变跑道会执行不同操作,例如对于5fps的swf,每帧处理10个用户action,1个失效action,1个渲染action;帧速率25fps的swf,每帧处理2个用户action,1个失效action,1个渲染action;对于50fps的swf,每帧只能处理1个用户action,1个失效action,1个渲染action。需要指出的很重要的一点是,某些事件只可能能发生在某些特定的时间片里,比如,Event.ENTER_FRAME事件只能在某一帧的初始时间片中被调度。

      1. 一个时间片中代码部分和渲染部分都有可能过长而导致相应时间片大于20毫秒,这就是"可变"的含义,为了保证帧的播放速率仍然接近swf编译时设定的帧率,Marshal会选择丢掉一些时间片。
      2. swf文件的播放速率不可能超过当前Flash Player切割时间片的速率,你可以为swf文件设定120fps的播放速率,但Flash最多可以按照50帧的速度播放(具体数值取决于当前系统的设置)。
      3. 代码的执行频率可能比swf的帧率更高,播放一个1fps的swf文件,播放一帧时间为1秒,也就是50个时间片,但在每个时间片里,都会有触发鼠标或计时器事件,尽管只有最后一个时间片才会渲染,另外你可以选择通过调用函数updateAfterEvent() 提前渲染,但只能在鼠标,计时器和键盘事件处理函数中调用。在这种情况下,Marshal会认为当前帧已结束并从下一个时间片起进入下一帧。 最后,如果一个Sprite的外观属性比如width,height等发生变化而将鼠标从该Sprite上方掠过,Flash会进行强制渲染。
      4. 如果帧率不是每秒时间片数量的整数因子,那么该平台的渲染时间间隔将变得不固定,比如帧率20fps的swf运行在50个时间片每秒的系统上,Flash Player的行为将是每5个时间片播放两帧,那swf的渲染频率将是2-3-2-3-2-3(时间片)。

        2010年4月20日星期二

        使用MXML编写纯Flash应用

        转自jinni的博客http://swfever.com/?p=245

        需要说明的是,这里的“纯Flash应用”是指不依赖于Flex框架的Flash应用程序。这似乎是个匪夷所思的命题,MXML不是Flex所特有的语言吗?
        Ryan Campbell的这篇文章给出了一个实例。MinimalComps是一个开源的AS类库,实现了一些轻量级的UI控件。 Ryan的实例演示了如何使用MXML和MinimalComps来创造一个完全不依赖于Flex框架的Flash应用。该实例编译后的结果只有23K。更酷的是,MXML中的一些高级特性,如Data Binding也完全可以正常工作!

        如果你肯花一点时间稍作分析的话,会发现实现它其实并不需要什么高深的技巧,主要在于你对编译器工作方式的了解:
        1) Flex编译器(mxmlc)并不要求MXML主文件必须继承自mx.core.Application类,任何一个继承自flash.display.Sprite的类都可以作为应用程序入口。
        因此理论上最简单的MXML应用程序如下所示:


        这个应用程序经过Flex 4编译器编译后的文件大小只有不到200字节!而一个Flex应用程序则至少在200KB以上。

        2)Flex编译器对[DefaultProperty]的支持
        在Flex 4中,一个MXML组件的直接子标签被解释为对该组件的默认属性赋值,默认属性通过[DefaultProperty]来声明。
        (Flex 3编译器也支持组件的[DefaultProperty]声明,但经过我的测试,Flex 3编译器会忽略应用程序根节点的[DefaultProperty]声明)
        例如:


        MyContainer的定义如下:

        package com.swfever { import flash.display.DisplayObject; import flash.display.Sprite; [DefaultProperty("content")] public class MyContainer extends Sprite { private var _content:Array; public function MyContainer() { } public function set content(value:Array):void { _content = value; updateContent(); } public function get content():Array { return _content; } private function updateContent():void { while(numChildren) { removeChildAt(0); } for each(var item:DisplayObject in content) { trace("Add item:"+item); addChild(item); } } } }
        上面的代码只是为了简单说明其工作原理,如果你打算编写自己的UI框架,应该参考Flex的“推迟”机制,将UI更新(addChild/removeChild)推迟到渲染前进行,而不是每次content被赋值时都进行更新。

        单例的事件管理器

        对象内部发送事件时可以使用一个继承自EventDispatcher的单例来dispatchEvent,
        然后在需要侦听该对象事件的对象当中再使用该单例来addEventListener()

        利用sharedObject模拟lib

        首先声明:读SharedObject是一个耗时的操作。
        package{
        import flash.display.Loader;
        import flash.display.Sprite;
        import flash.events.Event;
        import flash.net.SharedObject;
        import flash.utils.ByteArray;
        import load.LoadEvent;
        import load.LoadFileBase;
        import load.LoadFileData;
        /***完成本地共享对象的读取,版本比较,加载,替换等操作.*/
        public class LocalRunLib extends Sprite
        {
        public const LOADFAILED:String="load_local_Lib_failed";
        public const LOADSUCCESS:String="load_local_Lib_success";
        private var ver:String;
        public var Lib:*;

        public function LocalRunLib():void{
        getLib("Lib.swf","0708a01");
        addEventListener(LOADSUCCESS,testSuccess);
        addEventListener(LOADFAILED,testFailed);
        }
        private function testSuccess(event:Event):void{
        trace(event.target.Lib.applicationDomain.getDefinition("Tt"));
        }
        private function testFailed(event:Event):void{
        trace(event.target);
        }
        /***获取运行库.*@param_url获取本地失败后,向远程加载.*@paramver当前需要的版本.*/
        private function getLib(_url:String,ver:String):void{
        var flag:Boolean=false;
        var share:SharedObject=SharedObject.getLocal("resources");
        var Obj:Object=share.data;
        this.ver=ver;
        if(Obj!=null)
        if(Obj.version==ver){
        converToLib(Obj.lib);
        flag=true;
        }
        share.close();
        share=null;
        if(!flag){
        var loadFile:LoadFileBase=new LoadFileBase(newLoadFileData(_url,LoadFileBase._SWF));
        loadFile.addEventListener(LoadFileBase.SUCCESS,success);
        loadFile.addEventListener(LoadFileBase.FAILED,failed);
        }
        }
        /***将共享对象变成可运行的库.*/
        private function _onLoaderComplete(event:Event):void{
        Lib=event.currentTarget;
        event.target.removeEventListener(Event.COMPLETE,_onLoaderComplete);
        dispatchEvent(new Event(LOADSUCCESS));
        }
        /***写入到库.*/
        private function writeLocalLib(swf:*,ver:String):*{
        var share:SharedObject=SharedObject.getLocal("resources");
        share.data.version=ver;
        share.data.lib=swf;
        share.flush();
        share.close();
        share=null;
        }
        /****将二进制内容转化为可运行库.*/
        private function converToLib(data:ByteArray):void{
        var loader:Loader=new Loader();
        loader.contentLoaderInfo.addEventListener(Event.COMPLETE,_onLoaderComplete);
        loader.loadBytes(data);
        }
        /***加载指定url的运行库成功.*/
        private function success(event:LoadEvent):void{
        converToLib(event.loadData.data as ByteArray);
        writeLocalLib(event.loadData.data,ver);
        removeEvent(event.target as LoadFileBase);
        }
        /***加载指定url的运行库失败.*/
        private function failed(event:LoadEvent):void{
        removeEvent(event.targetasLoadFileBase);
        dispatchEvent(new Event(LOADFAILED));
        }
        /***删除事件.*/
        private function removeEvent(loadFile:LoadFileBase):void{
        loadFile.removeEventListener(LoadFileBase.SUCCESS,success);
        loadFile.removeEventListener(LoadFileBase.FAILED,failed);
        }
        }
        }

        程序设计悟

        1)利用flash中的事件实现“管道过滤器模式”:在父容器中不要急于一次即要让子对象处理问题还要自己处理问题,可以先让子对象处理,在子对象中形成事件冒泡上来,再在父对象中处理一次。
        2)在flash中使用大递归来将复杂操作分段化,如flex框架当中处理显示对象延迟刷新的核心方法:LayoutManager::doPhasedInstantiation方法,该方法是否有invalidate元素需要validate操作(当然有validateProperties,validateSize,validateDisplayList),然后再来看还有没需要validate的操作,如有,最关键的它 callLaterObject.callLater(doPhasedInstantiation),没执行完的放到下次再来执行吧!(不知道这个理解的对不对),而在callLater中所做的操作仅仅是将方法入栈stage.invalidate(),然后等下次enter_frame或render时再来操作。
        3)代码要清晰的表达意图,代码清晰程度的优先级应该排在执行效率之前,因为看代码的时间更多,还因为团队合作,比如一些数值参数应定义成静态变态以使意思更明确一点coffeeShop.PlaceOrder(2)变为 coffeeShop.PlaceOrder(CoffeeCupSize.Largxe);
        4)做项目架构和模块划分时首要也是最重要应该考虑的是如何划分利于团队分工合作,而不是程序设计的优雅

        flex 3源代码学习记录之LayoutManager

        本来只是想扩展一下Button,以实现按钮上字体描边及按钮各状态其lable位置变化功能,很简单,但发现对flex结构太过生疏,想读一下Button源码,结果这一看兴趣来了,也是需求吧,得了解flex的整个大致结构,UIComponent,LayoutManager,SystemManager,Application等等,看了半天终于给找见个头,算是个开始吧,在这里记录下,加上连载两个字,算是给自己一个督促吧!不然肯定看看就又不看了。
        好了,闲话就先不说了,进入正题,经过一个多小时的了解结构,发现LayoutManager很重要,这个类管理flex显示对象的显示吧,话说flex对渲染所做到的优化,大部分应该归功于这个类吧!这个类来决定播放器什么时候再去渲染显示对象,什么时候可以将更新交给下再来渲染,其实说简单点就是放一个数组,当有显示对象有任何更新,如:属性改变、样式改变、displayList改变等则先把它放到一个失效数组当中,然后在一个适当的时候,重绘之前,从该失效数组当中取出相应失效的显示对象执行其内部更新,重绘。finish!
        很晚了,明天继续写!

        flash p2p理解

        公司策划要制作一款对战游戏,这几天一直在想消息交互的方案,之前一直在考虑的是服务器中转的方式,已经完成两种比较合适的方案,不过不在今天 讨论范围之内,因为了解到flash player 10是支持P2P功能的,所以今天花了一早上的时间了解了一下,现把所得记录一下:   首先,所要用的API类是NetConnection和NetStream两个类及NetStatusEvent事件类,然后先说一下 NetConnection和NetStream的关系,至少是在p2p时的关系,其它Rtmp的时候没有研究过。
          先用 NetConnection来连接Adobe的stratus服务来获得唯一的PeerID,以供p2p时作为客户端的标识,同时该peerID保存在 NetConnection的nearID中,然后需要建立一个流,一个真正进行交互的流,一个DIRECT_CONNECTIONS类型的 NetStream,来发布自己的身份,同时也可以作为以后交互的时候的消息sender,并同时需要做OnPeerConnect侦听,在其它 NetStream订阅该流时触发,同时该流的farID等同于NetConnectiont的nearID。
          当有一个指定为该流 peerID的NetStream订阅该流时,首先会触发该NetConnection的netStatus事件,e.info.code 为"NetStream.Connect.Success",同时订阅流保存在 NetConnection.unConnectedPeerStreams当中,接来触发NetStream.onPeerConnect(该方法注册 为Netstream.client来支持回调),当执行完onPeerConnect后,订阅流从 NetConnection.unConnectedPeerStreams中移到NetStream.peerStreams中,这样就可以完成从该发 布流向订阅流发消息(通过send("方法名",参数),然后在订阅流方的client对象中定义相应方法名的回调函数)。
          但这样只完成了单 向的数据传送,订阅流同时还要向发布流发送消息,因此需要在订阅流订阅的时候同时建立一个发布流,publish(),然后在 NetStream.onPeerConnect()时创建一个指定为订阅方PeerID的订阅流,play(),这样对呼叫方就有两种作法:1)呼叫方 创建发布流时指定回调对象实现onPeerConnect方法,这样发布方创建订阅流并订阅时会触发该方法;2)呼叫方为发布流建立netStatus事 件侦听,因为发布方订阅流的订阅会触发该发布流的两个事件(NetStream.play.reset和NetStream.play.start),这 样就实现了双向交互。

          当然这是最简单的方法,比较完美一点的办法是各有一个publishStream和CallStream来管理 双方的连接,而在连接的时候创建出两个NetStream:OutgoingStream和inComingStream,分别来处理这一对peer的消 息交互
          发布方:创建publishStream,发布自己的身份,注册onPeerConnect回调;
          呼叫方:创建 callStream,订阅发布者;同时创建一个outgoingStream,注册一个被订阅的事件侦听,发布一个流,同时创建一个 incomingStream,并注册一个回调;
          发布方:接收到呼叫方的连接事件,在onPeerConnect中创建一个对应呼叫方的 incomingStream,注册一个消息回调,订阅;
          呼叫方:发布方的incomingStream的订阅触发呼叫方 outgoingStream的"NetStream.play.start"事件,发送一个"onIncomingCall"消息;
          发布 方:呼叫方的outgoingStream发送的"onIncomingCall"消息触发了发布方的消息回调,在消息回调中,创建一个 outgoingStream,发送一个"onConnectSuccess"消息;
          至此,一个连接已经建立,在创建 outgoingStream和incomingStream时都为其创建有onIm()的回调,以后的消息交互均是采用该回调。

        如何用Java实现Web服务器

        一、HTTP协议的作用原理

        WWW是以Internet作为传输媒介的一个应用系统,WWW网上最基 本的传输单位是Web网页。WWW的工作基于客户机/服务器计算模型,由Web 浏览器(客户机)和Web服务器(服务器)构成,两者之间采用超文本传送协议(HTTP)进行通信。HTTP协议是基于TCP/IP协议之上的协议,是 Web浏览器和Web服务器之间的应用层协议,是通用的、无状态的、面向对象的协议。HTTP协议的作用原理包括四个步骤:

        (1) 连接:Web浏览器与Web服务器建立连接,打开一个称为socket(套接字)的虚拟文件,此文件的建立标志着连接建立成功。

        (2) 请求:Web浏览器通过socket向Web服务器提交请求。HTTP的请求一般是GET或POST命令(POST用于FORM参数的传递)。GET命令 的格式为:

        GET 路径/文件名 HTTP/1.0

        文件名指出所访问的文件,HTTP/1.0指出Web浏览器使用 的HTTP版本。

        (3) 应答:Web浏览器提交请求后,通过HTTP协议传送给Web服务器。Web服务器接到后,进行事务处理,处理结果又通过HTTP传回给Web浏览器,从 而在Web浏览器上显示出所请求的页面。

        例:假设客户机与www.mycompany.com:8080/mydir /index.html建立了连接,就会发送GET命令:GET /mydir/index.html HTTP/1.0.主机名为 www.mycompany.com的Web服务器从它的文档空间中搜索子目录mydir的文件index.html.如果找到该文件,Web服 务器把该文件内容传送给相应的Web浏览器。

        为了告知 Web浏览器传送内容的类型,Web服务器首先传送一些HTTP头信息,然后传送具体内容(即HTTP体信息),HTTP头信息和HTTP体信息之间用一 个空行分开。

        常用的HTTP头信息有:

        ① HTTP 1.0 200 OK

        这是Web服务器应答 的第一行,列出服务器正在运行的HTTP版本号和应答代码。代码“200 OK”表示请求完成。

        ② MIME_Version:1.0

        它指示MIME类型的版本。

        ③ content_type:类型

        这个头信息非常重要,它指示 HTTP体信息的MIME类型。如:content_type:text/html指示传送的数据是HTML文档。

        ④ content_length:长度值

        它指示HTTP体信息的长度(字节)。

        (4) 关闭连接:当应答结束后,Web浏览器与Web服务器必须断开,以保证其它Web浏览器能够与Web服务器建立连接。

        二、Java实现 Web服务器功能的程序设计

        根据上述HTTP协议的作用原理,实现GET请求的Web服务器程序的方法如下:

        (1) 创建ServerSocket类对象,监听端口8080.这是为了区别于HTTP的标准TCP/IP端口80而取的;

        (2) 等待、接受客户机连接到端口8080,得到与客户机连接的socket;

        (3) 创建与socket字相关联的输入流instream和输出流outstream;

        (4) 从与socket关联的输入流instream中读取一行客户机提交的请求信息,请求信息的格式为:GET 路径/文件名 HTTP/1.0

        (5) 从请求信息中获取请求类型。如果请求类型是GET,则从请求信息中获取所访问的HTML文件名。没有HTML文件名时,则以index.html作为文件 名;

        (6) 如果HTML文件存在,则打开HTML文件,把HTTP头信息和HTML文件内容通过socket传回给Web浏览器,然后关闭文件。否则发送错误信息给 Web浏览器;

        (7) 关闭与相应Web浏览器连接的socket字。

        下面的程序是根据上述方法编写的、可实现多线 程的Web服务器,以保证多个客户机能同时与该Web服务器连接。

        程序1:WebServer.java文件

        //WebServer.java 用JAVA编写Web服务器

        import java.io.*;

        import java.net.*;

        public class WebServer {

        public static void main(String args[]) {

        int i=1, PORT=8080;

        ServerSocket server=null;

        Socket client=null;

        try {

        server=new ServerSocket(PORT);

        System.out.println("Web Server is listening on port "+server.getLocalPort());

        for (;;) {client=server.accept(); //接受客户机的连接请求

        new ConnectionThread(client,i)。start();

        i++;

        }

        } catch (Exception e) {System.out.println(e);}

        }

        }

        /* ConnnectionThread类完成与一个Web浏览器的通信 */

        class ConnectionThread extends Thread {

        Socket client; //连接Web浏览器的socket字

        int counter; //计数器

        public ConnectionThread(Socket cl,int c) {

        client=cl;

        counter=c;

        }

        public void run() //线程体

        {try {

        String destIP=client.getInetAddress()。toString(); //客户机IP地址

        int destport=client.getPort(); //客户机端口号

        System.out.println("Connection "+counter+":connected to "+destIP+" on port "+destport+".");

        PrintStream outstream=new PrintStream(client.getOutputStream());

        DataInputStream instream=new DataInputStream(client.getInputStream());

        String inline=instream.readLine(); //读取Web浏览器提交的请求信息

        System.out.println("Received:"+inline);

        if (getrequest(inline)) { //如果是GET请求

        String filename=getfilename(inline);

        File file=new File(filename);

        if (file.exists()) { //若文件存在,则将文件送给Web浏览器

        System.out.println(filename+" requested.");

        outstream.println("HTTP/1.0 200 OK");

        outstream.println("MIME_version:1.0");

        outstream.println("Content_Type:text/html");

        int len=(int)file.length();

        outstream.println("Content_Length:"+len);

        outstream.println("");

        sendfile(outstream,file); //发送文件

        outstream.flush();

        } else { //文件不存在时

        String notfound="



        Error 404-file not found
        ";

        outstream.println("HTTP/1.0 404 no found");

        outstream.println("Content_Type:text/html");

        outstream.println("Content_Length:"+notfound.length()+2);

        outstream.println("");

        outstream.println(notfound);

        outstream.flush();

        基于iframe的HTTP长连接实现

        关于什么是http长连接我不废吐沫了,有专业的解释(http://www.ibm.com/developerworks/cn/web/wa-lo-comet/)你可以去看看
        我们介绍一下在struts下的实现
        首先写一个test.jsp(写一些片段)
        view plaincopy to clipboardprint?

        <%-- 这里写你的页面代码 --%>





        <%-- 这里写你的页面代码 --%>




        特别注意‘’中的target属性的值一定要等于form结尾那个iframe的名称(即name属性),这是该实现方式的原理所在,就是说这个MyForm提交服务器后由服务器响应回来的数据填充到这个iframe里面,而这个iframe是不可见的(display: none)。从而实现了提交后页面没有刷新的感觉。
        接下来是服务器端的实现
        view plaincopy to clipboardprint?
        public class MyAction extends DispatchAction{
        public ActionForward test(ActionMapping mapping, ActionForm actionForm,
        HttpServletRequest request, HttpServletResponse response)
        throws Exception {
        String result = "hello I'm server";//要打印到前台的字符
        sendMsg(result,response,"msg");//msg是test.jsp中的那个js方法的名称
        return null;//必须返回null
        }
        //以下方法的意思是将msg打到前台页面调用前台的“function msg(m)”方法进行显示
        protected void sendMsg(String msg, HttpServletResponse response, String javascriptMethod) {
        try {
        response.setContentType("text/html;charset=GBK");
        response.getWriter().write(
        "");
        response.flushBuffer();
        } catch (Exception e) {
        e.printStackTrace();
        }
        }
        }
        public class MyAction extends DispatchAction{
        public ActionForward test(ActionMapping mapping, ActionForm actionForm,
        HttpServletRequest request, HttpServletResponse response)
        throws Exception {
        String result = "hello I'm server";//要打印到前台的字符
        sendMsg(result,response,"msg");//msg是test.jsp中的那个js方法的名称
        return null;//必须返回null
        }
        //以下方法的意思是将msg打到前台页面调用前台的“function msg(m)”方法进行显示
        protected void sendMsg(String msg, HttpServletResponse response, String javascriptMethod) {
        try {
        response.setContentType("text/html;charset=GBK");
        response.getWriter().write(
        "");
        response.flushBuffer();
        } catch (Exception e) {
        e.printStackTrace();
        }
        }
        }
        sendMsg这个java生成了一段js代码将服务端的数据展示在了页面,页面会打出一个alert,如果你明白了这个原理那么你可以改写sendMsg和function msg(m),你可以将数据填到一个div中,这个效果就好像ajax一样页面连闪都不闪一下。
        接下来实现长连接
        view plaincopy to clipboardprint?
        public class MyAction extends DispatchAction{
        public ActionForward test(ActionMapping mapping, ActionForm actionForm,
        HttpServletRequest request, HttpServletResponse response)
        throws Exception {
        int i = 0;
        boolean boo = true;
        String result = null;
        while(boo){
        try {
        Thread.sleep(1000);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        result = "hello I'm server"+i;//要打印到前台的字符
        sendMsg(result,response,"msg");//msg是test.jsp中的那个js方法的名称
        i++;
        if(i==100){
        boo = false;
        }
        }
        return null;
        }
        //以下方法的意思是将msg打到前台页面调用前台的“function msg(m)”方法进行显示
        protected void sendMsg(String msg, HttpServletResponse response, String javascriptMethod) {
        try {
        response.setContentType("text/html;charset=GBK");
        response.getWriter().write(
        "");
        response.flushBuffer();
        } catch (Exception e) {
        e.printStackTrace();
        }
        }
        }
        public class MyAction extends DispatchAction{
        public ActionForward test(ActionMapping mapping, ActionForm actionForm,
        HttpServletRequest request, HttpServletResponse response)
        throws Exception {
        int i = 0;
        boolean boo = true;
        String result = null;
        while(boo){
        try {
        Thread.sleep(1000);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        result = "hello I'm server"+i;//要打印到前台的字符
        sendMsg(result,response,"msg");//msg是test.jsp中的那个js方法的名称
        i++;
        if(i==100){
        boo = false;
        }
        }
        return null;
        }
        //以下方法的意思是将msg打到前台页面调用前台的“function msg(m)”方法进行显示
        protected void sendMsg(String msg, HttpServletResponse response, String javascriptMethod) {
        try {
        response.setContentType("text/html;charset=GBK");
        response.getWriter().write(
        "");
        response.flushBuffer();
        } catch (Exception e) {
        e.printStackTrace();
        }
        }
        }
        不停的做循环操作以求达到长连接,前台会不停的打alert出来,这样做实现了服务器端向客户端推数据,同时服务器端的状态可以实时反映到前台,如果在你的项目中客户点击按钮后服务器将执行大量的长时间的操作的时候而客户又要实时监控操作的情况的时候不妨使用这种方式提升用户体验。随便说一句IE支持这种玩法firefox等浏览器也支持

        B/S架构中的数据推送技术

        B/S架构中服务器向浏览器推送数据在很多场合都有需求,比如实时的监控报警、实时的调度,等等。凡是对实时性要求越高的场景,越是需要服务器及时、准确地向浏览器推送数据。这里我们就讨论一下在B/S架构下,可以实现从服务器向浏览器推送数据的几种技术及其相应的特点。

        基于HTTP协议

        1. HTTP协议的特点

        纯的HTTP协议在本质上是无状态、无连接的,它基于请求/响应的工作模式,使得浏览器在每次发生请求的时候和服务器建立连接,当接收到响应的时候断开连接;在这种情况下,要让服务器主动向浏览器发送数据是不可能的。

        技术总是为需求服务的,在很多浏览器需要及时获取服务器数据更新的需要下,技术人员们变通地发明了一些基于HTTP协议的“伪”长连接技术,实现了服务器数据向浏览器的“准”推送。

        2. 定时刷新

        定时刷新是最粗糙的实现快速感知服务器数据变化的方法,原理就是不停地刷新页面从而显示最新的服务器端数据。定时刷新从技术上大体有两种实现方法:一是通过HTML的META标签设置页面刷新间隔,比如以下的标签会在浏览器中每隔10秒刷新当前页面:


        这种方式会看到页面有明显的刷新,用户体验会比较差;与此相对应的另外一种方式就是AJAX,在JavaScript脚本中添加一个定时器,每隔一定时间向服务器发送一个AJAX请求,得到响应以后去更新页面的内容。以下JavaScript代码会每隔10秒发送一个AJAX请求去更新当前页面的部分内容:
        setInterval(function(){sendAJAXRequest()},10000);

        如图 1是定时刷新的模型图:
        图 1 定时刷新的模型图

        定时刷新这种模式相当于一个下级每隔一段时间打电话给上级,请示当前指示。当然,如果在这个时间段内发生了一些情况,他们之间是没办法沟通的,所以信息并不是“实时”的;另外,刷新间隔到底设置多少合适也是个问题,服务器的负担也比较大。

        3. 轮询
        轮询也是通过AJAX进行的,它与AJAX定时刷新的区别仅在于接收到AJAX响应以后的工作。定时刷新完全是浏览器主动的,在浏览器和服务器之间存在一定的断开间隔;而轮询这种方式会在AJAX响应中再次发送AJAX请求,也就是说当响应结束,浏览器和服务器之间一旦断开连接的时候,浏览器会再次发送请求建立连接。

        如图 2是轮询的模型图:
        图 2 轮询的模型图

        轮询这种模式相当于一个下级不停给上级打电话请示,上级指示下达以后挂了电话,下级可能去执行指示或者不执行,但是随即下级将再次给上级打电话继续请示。这种模式可以保证数据更新比较实时,但是服务器负担也是一个问题。

        顺便提一下,轮询这种模式一般会在浏览器的XMLHttpRequest的readyState为4(响应数据传输结束)的时候调用回调函数,此时连接已经关闭;但是在Firefox中支持Streaming AJAX模式,也就是XMLHttpRequest的readyState为3(响应数据仍在传输)的时候调用回调函数,此时连接尚未关闭。

        4. 基于iframe的流模式

        iframe可以在当前页面中嵌入一个文档页面,如果把这个页面设为隐藏,并将其src属性设置为一个特殊的请求,数据将会源源不断地从服务器发送到浏览器。

        比如以下的JSP页面,它将每隔10秒更新一个数输出到浏览器,不出意外它将会永远执行下去,浏览器和服务器之间的连接也永远不会关闭:
        <% int i = 1; try { while (true) { out.print("
        "+(i++)+"

        ");
        out.flush();

        try {
        Thread.sleep(10000);
        } catch (InterruptedException e) {}
        }
        } catch (Exception e) {}
        %>

        但是这样做有个很显然的不足,由于响应始终没有结束,因此浏览器里面的加载进度条会始终显示加载没有完成。当然,这也并不是没有办法解决,比如http://www.zeitoun.net/articles/comet_and_php/start就提供了IE、Opera、WebKit核心(Chrome/Safari)、Gecko核心(Firefox)等浏览器的解决方案。

        如图 3是基于iframe流模式的模型图:
        图 3 基于iframe流模式的模型图

        基于iframe的流模式相当于一个下级给上级打电话,上级不停发出指示,下级一边接收指示、一边执行。这种模式有着比较好的实时性,比如Gmail就是采用这种模式。

        5. 开源框架Pushlets
        Pushlets是一个实现了AJAX轮询和iframe流模式的开源框架(Java+JavaScript),对此有兴趣的可以参考:http://www.pushlets.com/,Pushlets采用LGPL许可。

        基于消息

        1. 基于消息的架构

        使用消息可以实现松散耦合的分布式数据通讯。通过消息从服务器向浏览器推送数据一般需要一个消息中间件(Message Oriented Middleware,MOM),服务器将数据推送给消息中间件,消息中间件再将数据以消息的方式推送给浏览器。基于消息的架构有个特别大的优点,那就是不但可以实现服务器向单浏览器、服务器向多浏览器推送数据;还支持浏览器到浏览器之间的数据推送。这将会在预警、调度等场合有非常大的用武之地。

        图 4和图 5分别是基于订阅/发布模式和点到点模式的消息传递示意。在B/S架构中,服务器可以发布消息,所有订阅该主题的浏览器都会接受到该消息,这就实现了从服务器向多浏览器的数据推送;浏览器或者服务器也可以向特定的对象发送消息,消息将在一个消息队列中被发送,对方浏览器就可以收到该消息,这就实现了服务器向某特定浏览器或者浏览器之间的数据推送。

        2. Java消息服务(JMS)


        JMS是一组公开的Java API,它定义了与消息相关的接口和语义,目前JMS已经成为J2EE中的重要组成部分。
        图 6 JMS工作原理

        如图 6是JMS工作原理。当有客户端连接到JMS服务器的时候,JMS的连接工厂会根据连接类型来创建一个虚拟连接,这个连接会具体负责消息的传递;在这个连接建立完成后,会产生一个会话,会话中保存了消息生产者或消息消费者的信息;消息的消费者会对感兴趣的消息目的地(队列或主题)建立一个监听,消息的生产者则负责把消息发送到这个目的地上。

        另外JMS还有一些值得一提的特点,比如支持事务性会话、可以设置消息的持久性、设置消息的优先级、允许消息过期、可以构建长期订阅等。这些特性在各种企业级应用环境下都有可能提高应用的功能或性能。

        实现JMS的商业中间件有IBM MQSeries、BEA WebLogic JMS等;开源中间件有OpenJMS、Apache ActiveMQ等。

        3. ActiveMQ

        ActiveMQ是Apache基金会的著名开源项目,目前Release版本5.2完整支持JMS1.1和J2EE 1.4规范。ActiveMQ采用Apache 2.0许可发布。ActiveMQ的优势在于其支持集群部署、支持多种应用层协议和诸多客户端开发语言等特点。

        ActiveMQ主要支持以下协议:
         OpenWire
         REST
         Stomp
         WS Notification
         XMPP
         AMQP

        下面这里将使用ActiveMQ和Stomp协议来演示各种方式的数据的推送。

        4. Stomp协议

        Stomp是一种简单、实现容易的协议,因此支持非常广泛,这里采用Stomp协议的主要原因也是因为其支持的客户端开发语言最多,在各种环境下都有用武之地。这些开发语言主要包括:
         ActionScript 3
         C
         C++
         .Net
         Delphi
         Perl
         PHP
         Python
         Ruby

        下面主要用到了ActionScript和.Net语言。

        5. 发布/订阅模式的实现

        发布/订阅模式适用于广播性质的数据推送。比如在实时监控系统中,当我们的指令中心需要向所有监控目标发送信息的时候,这种模式就比较适合。为了简单起见,这里的服务器端和浏览器端都使用了ActionScript实现。

        在浏览器端,我们需要订阅服务器主题。比如所有的接收指令的目标都需要监听“Alarm”频道,那么在浏览器中的代码应该如下:
        private function sub():void
        {
        var ch:ConnectHeaders = new ConnectHeaders();
        stomp.connect("localhost", 61613, ch);
        stomp.subscribe( "/topic/Alarm" );
        }
        private function onStompMessage(event:MessageEvent):void
        {
        var byteArray:ByteArray = event.message.body;
        var str:String = byteArray.toString();
        }

        图 7 发布/订阅模式的数据推送

        6. 点对点模式的实现

        点对点模式适合类似调度的功能场景。比如在监控系统中,当某个指令需要下达给具体某个目标的时候,就需要点对点模式的数据推送。

        实现点对点模式的数据推送需要知道数据发送的目的地,以下代码演示了如何从指令中心“党中央”发送指令到“毒蛇”的过程:
        private function sendMsg():void
        {
        var destination:String = "/queue/毒蛇";
        stomp.sendTextMessage(destination, “注意隐蔽”);
        }

        当然,“毒蛇”需要监听所有发送给他的消息:
        private function login():void
        {
        var ch:ConnectHeaders = new ConnectHeaders();
        stomp.connect("localhost", 61613, ch);
        stomp.subscribe( "/queue/毒蛇" );
        }
        图 8 点对点模式的数据推送

        7. 实时监控Demo

        这里通过消息模式实现了一个实时监控的演示。服务器端是使用.Net Stomp API写的控制台程序,Demo测试使用1000个监控目标,数据发送间隔0.5秒,数据推送到“realmonitor”频道。浏览器端使用Flex,监听“realmonitor”频道。

        在服务器端,这个Demo中设置每100个目标信息拼装成一条消息,也就是说每0.5秒会发送10条消息。如果每条消息的信息(坐标和一些属性信息)大约50字节的话,每条消息大概5KB;每秒发送两次,总共大概100KB数据量会被推送。

        图 9 Flex中实时监控效果

        如图 9是在Flex中实现的实时监控效果。

        再看shader及shaderJob

        其实可以把shaderJob用起来当作一个多线程,尤其对于复杂计算,这个更加高效一些,看了下这几个关系:shader,shaderData,shaderInput,shaderParamer,shaderJob,等,总的来说ShaderJob是来管理类,而shader代表具体的执行过程,其它的是一些边缘参数类,随后好好看看

        一个组件被化完成后自动执行待执行函数的技巧

        在flex中往往有这样的需求:想调用一个组件类的某个方法,但这个方法中用到了该自定义组件当中的一些组件,所以直接调用就会出错,因为组件还没有初始化完成,要完成这样的功能可能就得设置多个变量,然后初始化完成设为true,然后……,觉得比较麻烦,需要在两个地方都调这个函数(初始化完成后和调用处),不过我这里有个方法可以简化这个过程,好与不好还待验证:
        准备两个变量:一个为是否被化完成--isInitalized;一个数据组funcArr;
        [data]
        private function onComplete(){
        isInitalized = true;
        while(funcArr.length>0){
        (funcArr.shift() as Function).apply(null,funcArr.shift());
        }
        }
        public function outFunc(aParam1:String,aParam2:String):void{
        if(isInitalized){
        myFunc(aParam1,aParam2);
        }
        else{
        funcArr.push(myFunc);
        funcArr.push([aParam1,aParam2]);
        }
        }
        private function myFunc(aParam1:String,aParam2:String):void{

        }
        [/data]

        RTMP协议理解

        一、基于TCP之上的高层协议;
        二、自定义新的包结构chunk;
        三、基于TCP,又有自己的包,所以提出个概念——消息块流 chunk message stream;
        怎么讲呢?就是说包肯定不能太大了,TCP包都有个最大值,所以得分开成自己的包,但还要考虑节省带宽,而又基于TCP流,是一种安全的流,所以当然还是分为包头和包体,但这个包头是可以节省的,从1个字节到12个字节不等,这又是怎么回事呢?是这样的,首先有一个基本的chunk basic header这个是必须都有的,但就这个header都还是一个1到3字节可变的头,再加上一个0到11字节的chunk message header都是由chunk basic header头的第一个字节的前2bit分成的四个类型来控制的,这四种类型就说明了这个消息块是一个新消息包的开始块?一个跟随前面块的消息块?等!从而来决定这个chunk message header中的四种内容是需要还是不需要,所以就出现0,3,7,11字节这四种情况,chunk message header中包括时间戳、消息长度、stream id、消息类型、扩展时间戳这种数据。
        四、其实主要还是得知道chunk basic header的构成,它是一个2 + 6 + 8 + 8 bit的情况,2就是前面说的类型,后面的这22bit是个chunk stream id,这个好像很重要,现在还没有搞清楚,大致是6处为0的话,第一个8是有效的,而得到的chunk stream id的值为1片8的值+64,如果6处为1,则第一8第二8都是有效的,最终的chunk stream id的值就是第二8*256 + 第一8 + 64,这就说明6处0 1 都为保留值,当然现在2也是个说什么低层协议的消息(在RTMP协议中控制信令的chunk stream ID都是2)这个暂不明白什么是控制信令,“2~7都是约定的,8是用来传输publish play等命令”,这个就得继续再找资料看了!

        ==============================================================


        完整的12字节RTMP包头每个字节的含义:
        用途 大小(Byte) 含义
        Head_Type 1 包头
        TiMMER 3 时间戳
        AMFSize 3 数据大小
        AMFType 1 数据类型
        StreamID 4 流ID

        一、Head_Type
        第一个字节Head_Type的前两个Bit决定了包头的长度.它可以用掩码0xC0 进行"与"计算:
        Head_Type的前两个Bit和长度对应关系:
        Bits Header Length
        00 12 bytes
        01 8 bytes
        10 4 bytes
        11 1 byte
        Head_Type的后面6个Bit和StreamID决定了ChannelID。 StreamID和ChannelID对应关 系:StreamID=(ChannelID-4)/5+1 参考red5
        ChannelID Use
        02 Ping 和ByteRead通道
        03 Invoke通道 我们的connect() publish()和自字写的NetConnection.Call() 数据都是在这个通道的
        04 Audio和Vidio通道
        05 06 07 服务器保留,经观察FMS2用这些Channel也用来发送音频或视频数据

        例如在 rtmp包里面经常看到的0xC2, 就表示一字节的包头,channel=2.

        四、AMFType
        AMFSize占三个字节,这个长度是AMF长度,可超过RTMP包的最大长度128字 节。
        AMFType是包的类型

        0×01 Chunk Size changes the chunk size for packets
        0×02 Unknown
        0×03 Bytes Read send every x bytes read by both sides
        0×04 Ping ping is a stream control message, has subtypes
        0×05 Server BW the servers downstream bw
        0×06 Client BW the clients upstream bw
        0×07 Unknown
        0×08 Audio Data packet containing audio
        0×09 Video Data packet containing video data
        0x0A-0x0E Unknown
        0x0F FLEX_STREAM_SEND TYPE_FLEX_STREAM_SEND
        0x10 FLEX_SHARED_OBJECT TYPE_FLEX_SHARED_OBJECT
        0x11 FLEX_MESSAGE TYPE_FLEX_MESSAGE
        0×12 Notify an invoke which does not expect a reply
        0×13 Shared Object has subtypes
        0×14 Invoke like remoting call, used for stream actions too.
        0×16 StreamData 这是FMS3出来后新增的数据类型,这种类型数据中包含AudioData和VideoData
        五、StreamID
        StreamID是音视频流的ID,如果AMFType!=0x08 或!=0x09那么 StreamID为0。
        ChannelID 和StreamID之间的计算公式:StreamID=(ChannelID-4)/5+1 参考red5
        例如当ChannelID为2、3、4 时StreamID都为1 当ChannelID为9的时候StreamID为2

        六、封包分析
        例如有一个RTMP封包的数据03 00 00 00 00 01 02 14 00 00 00 00 02 00 07 63 6F 6E 6E 65 63 74 00 3F F0 00 00 00 00 00 00 08 ,,,
        数据依次解析的含义
        03表示12字节头,channelid=3
        000000表示Timmer=0
        000102 表示AMFSize=18
        14表示AMFType=Invoke 方法调用
        00 00 00 00 表示StreamID = 0
        // 到此,12字节RTMP头结束

        下面的是AMF数据分析,具体的AMF0数据格式请参考
        http://www.cnweblog.com/fly2700/archive/2008/04/09/281432.html
        02表示String
        0007表示String长度7
        63 6F 6E 6E 65 63 74 是String的Ascall值"connect"
        00表示Double
        3F F0 00 00 00 00 00 00 表示double的0.0
        08表示Map数据开始

        ===================================================================
        例如完成握手后,Flash向FMS发送的第一个RTMP数据,内容如下:


        上面一段数据由2个RTMP包组成,2个RTMP包头分别用蓝色表示,第一个蓝色的是12字节的包头,后面一个蓝色的C3是一个字节 的包头,绿色部分是AMF数据,红色的是AMF数据类型,整个RTMP解码过程如下
        [2008-06-18 16:59:20] DecodeInvoke:
        [2008-06-18 16:59:20] InvokeName:String:connect
        [2008-06-18 16:59:20] InvokeID:Double:0
        [2008-06-18 16:59:20] Map:MapNum:0
        [2008-06-18 16:59:20] Params:{
        [2008-06-18 16:59:20] Key:String:objectEncoding
        [2008-06-18 16:59:20] Value:Double:0
        [2008-06-18 16:59:20] Key:String:app
        [2008-06-18 16:59:20] Value:String:mediaserver
        [2008-06-18 16:59:20] Key:String:fpda
        [2008-06-18 16:59:20] Value:Bool:0
        [2008-06-18 16:59:20] Key:String:tcUrl
        [2008-06-18 16:59:20] Value:String:rtmp://127.0.0.1/mediaserver
        [2008-06-18 16:59:20] Key:String:audioCodecs
        [2008-06-18 16:59:20] Value:Double:615
        [2008-06-18 16:59:20] Key:String:videoCodecs
        [2008-06-18 16:59:20] Value:Double:76
        [2008-06-18 16:59:20] }End Params
        [2008-06-18 16:59:20] InvokeParams:String:PUBLISHER
        [2008-06-18 16:59:20] InvokeParams:String:streamRecode

        2.5D下人物行走

        需要记录

        2.5D基础--菱形Tile

        @    当然菱形格子有好几种,我接触到的,也是平时用最多的,就算是这一种了吧,还有个名字的不过给忘了,鉴于平时做东西总是用完忘完,所以乘着还在做这个东西,把一些东西给记录下来,下次用的时候也方便点,这个帖子估计得分好几次来写完吧!
            现在进入正题,下图是自己在windows自带的画图工具里草草画的,说明意思就可以了


        如上图,地图里用tile,可以是一个地图用一片tile,也可以按功能区分为几片tile,这样可以省略点没有功能的地图也消耗一些资源吧!功能区用Field来管理,也就是说一个地图可以这么来分层(容器就叫World吧):
        一、GBPanel,背景层,里面加载一张地表的位图;
        二、TilePanel,格子层,里面管理所有的格子,当然这些格子又是按照区来组织起来的
              TileList:在TilePanel里保存一个的数组来存储几个区Field; 
                Field:在这里面才是真正管理Tile的地方,每一个Field就如上图所画的东西样,管理着其下的一片Tile,完成诸如建筑占地啦,拖动建筑时的映像啦,人物寻路啦等功能;
                  Tile:这个就是单个的菱形格子了,要完成按照不能的信息绘出不同的形状啦,保存一些信息啦。
        三、BuildingPanel,建筑层,也包括人物,主要地图之上要表现和交互的元素基本都在这一层了,要管理元素的排序啦等;
        四、EffectPanel,这一层主要放些不需要交互的效果,如放些飞鸟啦,云啦什么的。

            哇!好像扯了很远啊!赶紧返回到主题来,话说这个格子层是分有逻辑坐标和物理坐标的,这两个应该很好理解,那这个Field的物理(0,0)坐标就在它的中间最上面的那一个Tile的上顶点位置了,而以该顶点沿X轴正方向向下延伸的即为逻辑横坐标,而沿X轴负方向向下延伸的为逻辑纵坐标,看看上图格子里标的逻辑坐标就比较清楚了
            接下就应该了解下两种坐标体系的转化公式了,在说这个之前先分析个问题:初始化Field时肯定是要生成每个Tile(row,col),然后把这个addChild(Tile)的,那在addChild时怎么确定这个Tile的物理坐标呢?
             ====================================================
            不要混乱,我们分开来看,不要把思路给搞乱,计算其X坐标值吧!我们不要直接去找跟(0,0)点有什么关系,我们首先看某个Tile的逻辑横坐标row相对于Tile(row,0)偏移了多少,这个很好计算出来offsetX_1=-col*WIDTH/2,而Tile(row,0)相对于Tile(0,0)的偏移量是offsetX_2=row*WIDTH/2,所以我们就把这个Tile的物理横坐标给求出来了
        @#    pX=row*WIDTH/2-col*WIDTH/2;
        接下来再来搞它的纵坐标,其实还是用上面的方法
        @#    pY=row*HEIGHT/2+col*HEIGHT/2;
        这样我们就顺利的生成出了一片的Tile了,但是这还不够的,我们平时操作的时候更多的用到的是从物理坐标取到相应的逻辑格子出来,那这个反方向的计算好不好弄呢?这是个很容易的解二元一次方程啊!1式两边同乘HEIGHT,2式两边同乘WIDTH,然后两式相加得row,相减得col
        #    row=pY/HEIGHT+pX/WIDTH;
        #    col=pY/HEIGHT-pX/WIDTH;
        这就好了吗?不对的,因为取坐标可不会刚好某个Tile的(0,0)坐标的,所有得先把那个物理坐标调整下才能用,那怎么调整呢?从Tile(0,0)就能看出来横坐标大于0但小于WIDTH/2的都得算0,纵坐标大于0小于HEIGHT/2的都得算0,所以调整后的式子就是这样的
        #    row=int(pY/HEIGHT/2)*HEIGHT/2  /HEIGHT+ int(pX/WIDTH/2)*WIDTH/2  /WIDTH;
        #    col=int(pY/HEIGHT/2)*HEIGHT/2  /HEIGHT- int(pX/WIDTH/2)*WIDTH/2  /WIDTH;
        最后结果就是这样了:
        @#    row=0.5*(int(pY/HEIGHT/2)+int(pX/WIDTH/2));
        @#    col=0.5*(int(pY/HEIGHT/2)-int(pX/WIDTH/2));
        好,坐标转换到此结束!
            ====================================================
            坐标转换只是个基础而已,是个不涉及业务的基础,即使不了解这个转换机制一样能写出个好的例程来,只需要查到工式直接套用就可以了,所以以上内容了解下就可以了,重点精力还是要放到具体怎么来使用这些格子的知识来转换为我们的业务功能。
            首先,这个TilePanel或者说是Field吧,跟BuildingPanel是处于同一层的,同被World管理,那么就明白BuildingPanel其实只是管理显示,而逻辑是放在TilePanel(也可以是Field,看是什么粒度了)中的,下面就展开来说明我所遇到的这些功能:
            一、根据Field来决定初始时哪些地方可用,哪些地方不可用。虽然在一张大地图上已经为功能区和非功能区划分出了几个实际的Field,那没有Field肯定是没有功能的了,但不是每个Field包含的所有Tile都是有功能的,功能区不可能总是规则的那么一块大菱形吧!所以就要根据数据来决定一个Field中的哪些地方是可用的,哪些地方是不可用的。而TilePanel中的每个Field都应该包括以下属性:x,y,rowCount,colCount,type,data,其中前四项好理解,分别为在地图中的坐标及总格子数,而type算是个标记吧,总得分开每个Field,最后一个data属性这个是关键的,这个值应该是个数组,一个包含Field中每个Tile属性的数据,如0代表可用,1代表不可用等,而这个值应该是用地图编辑器来生成的。
            二、动态刷新逻辑数据。这个是用来处理比如建造个建筑,移除个建筑,焦点某块区域等,需要注意一点:Field来管理区域,而Tile处理单个格子。
        比如:
        1)要在一个Field当中的某个坐标上建筑一个3*3格子大小的建筑,那就要由Field来找出坐标对应的这个3*3区域内的(0,0)格子,再把接下来的8个格子全部做一个处理,如:通知这九个格子已经被占、这九个格子绘成红色等
        2)拖着一个建筑时要显示该建筑映像的格子,那就要在拖动的过程当中一直去寻找当前建筑对应的(0,0)格子,再根据建筑占地大小把那一块区域的格子置为焦点状态,这里有个技巧:不要一次去处理找(0,0)格子和绘焦点状态,而应该找到(0,0)格子,然后抛一个TILE_CHANGE事件到World里再去处理Field.focusArea(),因为可能随着焦点格子的变化需要处理多方面的数据如BuildingPanel里要处理,Field里要处理,还有其它层里也要处理等
            
            最后想说一下Tile的处理,Tile虽说是只处理个根据自己状态绘不同表现的功能,但也有技巧的,它的状态可以用二进制来记录,然后根据传进来的值, 或者 异或 其state,这样就可以保存多个值并都可以单独处理,如block的状态时也可以focus状态,focus状态消失后不影响其block状态,因为它们在不同的位上记录着!

        这个后期最好把代码给加上


        程序开发悟

        对程序进行修改应从最小粒度进行,因为程序架构已经成型,最小粒度的修改得影响最小

        2010年4月19日星期一

        fiddler使用

            Fiddler站在用户与Web服务器的中间,由它转发请求与响应。
            Fiddler 是一款免费的记录主机HTTP(S)通信的代理(proxy),具有丰富的用户界面,支持监察请求和响应、设置断点,以及修改输入输出数据。同时,它也支持多种数据转换和预览,比如解压缩GZIP、DEFLATE,或者BZIP2格式的文件,以及在预览面板里显示图片。
            除了数据监察和统计分析,Fiddler也支持设置断点,修改请求和响应的数据。这一点在安全性和数据正确性的测试上非常有用。
            用这个可以做外挂啊!

        用我的邮箱来发博客

        好多东西被和谐,但又想用,不得不曲线发贴啊!试试这个方式是否奏效!

        ColorMatrixFilter用法

        这个类很强大,它有一个matrix数组的属性,这个数组有4*5个元素组成一个4*5的矩阵,若要把该滤镜应用于某个显示对象,其效果为以该matrix矩阵乘显示对象的每一个像素(以列向量表示从上到下依次为RGBA),直观的计算为下图:
        所以能看出来,
        1)matrix[4]matrix[9]matrix[14]分别对应亮度,因为都是每个通道增加的alpha值
        2)matrix[0]matrix[6]matrix[12]则对应的对比度,因为值越大相当于放大各通道的不同

        2010年4月18日星期日

        和谐无处不在

            很悲剧,找到这么个好博客竟然是被和谐了的。
            一直以来也算是google迷,不过不是盲目的迷恋google,用过它的很多的功能了,像它的最大的功能:搜索,邮箱啦,云平台啦,博客啦,论坛啦,阅读器啦。当然除了搜索,用邮箱最多,觉得很有感觉,然后这几天玩了下它的博客,觉得也很不错,正打算作为一个博客之家,结果悲剧的发现这个被和谐了。

        初用google博客功能

            第一次使用google的博客功能,不知怎么样,设置起来感觉很专业,但又觉得有点不习惯,可能是用的外国人的习惯吧!而且也不是这个博客的活跃度怎么样,有待观察!
            现在只是玩玩而已

        2010年4月10日星期六

        C语言的一点东西

        1)指针的const,int const *p代表p所指向的内存单元被锁定,int * const p代表p被锁定,即p内存中所存的指向某个int型内存的地址是不能再被改变了。
        2)函数指针,函数就像数组一样,函数名本身就是一个指向内存中一段代表的地址,因此调用函数也可以写成(*myFunc)(20),只是太麻烦了,所以简化成myFunc(20),同理声明一个函数指针void (*funcP)(int),给该函数指针赋值funcP = &myFunc或者funcP = myFunc,而通过函数指针调用函数时就可以(*funcP)(20)或者直接funcP(20);同时既然是指针,那函数指针也可以作为一个函数参数传给函数,不过做这之前先要学会给函数指针定义下类型,就像typedef (int*) PIN让PIN代表int*,typedef void(*FuncType)(int)来给函数指针定义出一种类型,名字叫FuncType,然后再声明类型函数指针时可以直接FuncType funcp1,funcp2等,同时函数指针作为函数参数传递时void myFunc(FuncType func,int i),出现函数指针及函数指针能作为函数参数这个突然给C扩展出了好多功能,如回调。
        3)指针与引用,引用在初始化时就被指定值且整个运行过程当中不能被重新赋值,而指针没有这些限制,这个表面区别也就说明引用和指针是完全不同的东西,

        =====================================================================
        结构体
        1)定义与使用结构的方式,定义:struct myStruct{},使用:struct myStruct s1。需要注意一点就是struct关键字与结构体名必须放在一起才起作用,当然借助在上文中用过的typedef关键字可以简化这种用法
        typedef struct myStruct *PStruct
        struct myStruct{}
        当然两者可以合并起来使用
        typedef struct myStruct
        {
        } *PStruct
        不过需要注意一点,如果在结构内部需要使用自身,则要么使用第一用方式,要么用的地方用struct myStruce的方式而不能使用PStruct,因为有可以在使用的时候结构还没有建完,所以PStruct是无效的