[汇编]深入系统底层--之--教你用0101写程序

mikel阅读(806)

准备你的行囊—-建立环境

为了让大家更为轻松,除非迫不得已,我们尽量使用系统上已经安装的工具,在这一章里,下面两个外部工具是必须的

同时,读者应该稍微具备的汇编知识,不用太多,知道下面这些指令的意义和用法即可

MOV 数据传送指令
ADD 加法指令
PUSH,POP 堆栈指令
CMP 比较指令
LEA 取地址指令
XOR 异或指令

所有的转移指令:JMP,JZ,JE

如果你还想进一步了解机器码的规范,可以下载 http://download.csdn.net/source/1103630,里面有Intel的文档,以及本文用到的操作码查询表

用0和1写程序

曾经有人发给我一张图片,说世界上"最牛程序员"的键盘,键盘上一共三个键,01,当时年少无知,崇拜到抓狂,今天就让我们当回"顶尖高手",用01直接写程序

请打开一个十六进制编辑器比如UltraEdit

把下面的二进制代码化为16进制输入进去(主要无法直接输入二进制代码)

1011 1000 0000 0001 0000 0000 0000 0101 0000 0001 0000 0000

十六进制为B8 01 00 05 01 00

将文件保存为test.com文件,恭喜你,你刚刚完成了一个伟大"壮举",你 成功的让CPU计算出了1+1等于几,如果你兴匆匆的运行它,什么结果都看不到,那是因为为了保证代码简单,还没有告诉CPU输出结果的缘故,你愿意的 话,可以运行cmd,切换到保存test.com的目录,通过执行Debug test.com,来看看我们到底输入了什么

image

1011 1000 代表 MOV ax
0000 0001 0000 0000 代表1
0000 0101 代表 ADD ax
0000 0001 0000 0000 代表 0001h

全文加起来表示
MOV ax,01h
ADD ax,01h

可以看出,我们的代码对应了两条机器指令,每个指令分成两个部分,比如MOV ax,1的二进制代码,1011 1000 代表 MOV ax他指定了本条指令的操作,叫做指令操作码(Opcode),0000 0001 0000 0000 代表1,指定了操作的操作数,可以看出机器码是有自己固定的格式,只要掌握了这个格式,查询对应的操作码,应该就可以掌握机器语言了

当然,事情也有复杂的一面,同一条汇编指令其操作码可能根据寻址方式或寄存器或操作数的位数的变化发生变化,比如同样是MOV指令,MOV al,1 和MOV ax,1中Mov的操作码分别为B0(1011 0000)和B8(1011 1000),而MOV ax,[ES:100]操作码会变成26 A1(前面26是段超越前缀,现在不用仔细追究),Intel8086规定的MOV指令就有12种之多,而且操作码的长度还有可能不同,这些操作码都可以 在表<x86操作码查询表>中对应的查到,不需要记忆,下面我们就来了解机器语言指令的格式

自己设计机器语言指令格式

在阅读Intel公司的实现前,为了不让您陷入一堆的解释和说明中迷惘无助,我们先来热热身,做点有趣的事情—思考一下如果让你自己来设计机器语言指令的格式,那么你会做出怎样的设计,下面是我的设计思路

首先汇编代码和机器代码是对应的,所以让我们来看看一条典型x86汇编指令:

MOV ax,1

这条指令由三个部分组成:指令,目的操作数,源操作数

指令为Mov,目的操作数ax,源操作数1,

ADD bx,2

指令为Add,目的操作数bx,源操作数2

相对应的我们可以考虑把机器指令格式也分成三个部分:指令码,目的操作数,原操作数

由于寄存器的数目是有限的,我们可以列个寄存器机器码指令表,这样代码中的寄存器就可以被替换为如下的机器代码,比如

  • 000   AX
  • 001  CX
  • 010  DX 
  • 011  BX 
  • 100  SP 
  • 101  BP 
  • 110  SI 
  • 111  DI

     

    然后我们再列一个指令码表,比如

    MOV=00000000
    ADD=00000001
    AND=00000010
    .
    .
    .

    则MOV ax,1就可以变成 00000000 00000000 00000001(ax是000)

    但是这样简单清晰的三个部分会出现一些问题mov bx,0,和mov bx,ax就有可能混淆了,因为ax的代码是000,和立即数0相同

    所以我们需要一个标志位来确定是那种操作数,操作数有下面5种可能

    目的操作数和原操作数的大小就比较难了,因为操作数可能是

    1)一个立即数 比如1

    2)一个寄存器 ax,bx,cx,dx

    3)一个内存地址 [StringLable]

    4)一个由一个或多个寄存器组成的内存地址

    [ebx],[ebx+esi],[es:ebx+esi]

    5)一个由一个或多个寄存器再加上一个偏移量组成的内存地址

    [ebx+esi]

    显然我们需要两个标志字段,每个5个值,(每个操作数一个)来标定自己是哪种操作数,每个标志字段只要3位就够了,我把这两个标志字段放到一个字节里,放在两个操作数前面

    格式一:

      指令码 保留2位|标志1|标志2| 操作数1 操作数2
    Mov ax,1 00000000 00|001|000 00000000 00000001

     

    标志的意义

    000:立即数
    001:寄存器
    010:内存地址
    011:多个寄存器
    100:多个寄存器加偏移量

    问题又出来了,当标志位为100,这时,操作数应该是多个寄存器+偏移量,假设每个寄存器占3位,两个就是6位,留给我们的偏移量的空间只有两位, 也就是说偏移量最大只有3,这显然是不够的,所以我们必须加上一个字节表示偏移量,而当不需要偏移量的时候,这两个字段可以不存在,也就是说表格变成了

    格式二:

      指令码 00|标志1|标志2 操作数1 偏移量 00|操作数2
    bbb|iii
    偏移量
    Mov ax,[bp+si+5] 00000000 00|001|100 00000000   00|101|110 00000110

    怎么样,有点像样子了吧,固定长度8位的指令码可能有256种指令,我想最基本的操作,AND,OR,XOR,ADD,SHR,SHL等等不会太多,而其他的操作都可以由这些操作组合而成,比如减法是补码的加法,乘法是重复相加等

    似乎大部分问题都已经解决了,但是稍微熟悉x86汇编的朋友就会知道,不可能有任何指令的两个操作数都是内存,也就是永远不会出现
    MOV [dx+di],[ex+si]这样的语句,要想实现这样的移动我们必须要把源操作数移动到一个寄存器里,然后再从寄存器里移动到目的地

    反应在我们的设计上,我们就会发现两个偏移量是多余的,任何情况下最多会有一个被使用到,所以表格可以修改成这样

    格式三:

      指令码 00|标志1|标志2 偏移量 操作数1 操作数2
    00|bbb|iii
    MOV ax,[bp+si+5] 00000000 00|001|100 00000110 00000000 00|101|110
    MOV ax,bx 00000000 00|001|001 00000000 00000011

     

    其实看看上表的第二条语句,我们就会发现一个很重大的问题,那就是空间浪费,第二行中所有黑体的部分都是被浪费掉的空间,浪费了12位,总共才32 位的代码,居然就浪费了12位,心疼啊,而且看看标志字段,占了三位,总共可以表示8个标志,确只用了5个,我们能不能想办法把这些空间利用起来呢?

    我们重新仔细考虑第二个字节,也就是标志字节,把最高位的两位利用起来,称作寄存器标志,他的值如下表

    00:操作数中没有寄存器

    01:操作数的后一个为寄存器

    10:操作数的前一个为寄存器

    11:两个操作数都是寄存器

    如果此位指明某操作数为寄存器,则后面的标志位直接为寄存器值,如果为00,则后面的操作数只可能为 (内存,立即数) 形式,这样MOV ax,bx的机器码就变成了下面的样子

    格式四:

      指令码 寄存器标志|标志1|标志2 偏移量 操作数1 操作数2
    00|bbb|iii
    MOV ax,bx 00000000 11|000|011

     

    好了,指令系统的雏形已经出来了,虽然和Intel的实现有很多不同,并且本身还有各种问题,比如依然有浪费空间的情况,功能也不太健全,不过基本体现了指令格式的特点:

    • 分成几个字段表示不同意义
    • 尽量短小精干
    • 不能浪费任何一位

    下面让我们来看看Intel公司的实现方法

     

    让书写机器码像填表一样简单

    从上面的叙述,我们已经大概能看出点门道,每条指令分为几个部分,表示不同的含义.Intel规定,机器指令都可以被表示成六个部 分,Prefix,Opcode,ModR/M,SIB,Displacement,Immediate,除了Opcode部分是必须的外,其他部分都有 可能不存在

    好像有点复杂不是?不要着急,我们稍作解释就可以把书写机器指令变得像填写表格一样简单

    下面我们把几条命令按照六个部分进行分割,填写到这张表里,后面会解释六个部分的含义

      Prefix
    前缀
    0-4个前缀,每个1字节
    可选
    Opcode
    操作码
    1-2字节
    一定存在
    ModR/M
    寻址与寄存器
    1个字节
    可选
    SIB
    内存寻址模式
    一个字节
    可选
    Displayment
    偏移量
    1,2或4个字节
    可选
    Immeidate
    立即数
    1,2或4个字节
    可选
          oo|rrr|mmm cc|iii|bbb    
    MOV ax,1 1011 1000 0001 0000
    ADD ax,1 0000 0101 0001 0000
    MOV ax,[ES:0100h] 0010 0110(26h代表es的段超越前缀) 1010 0001 0000 0000
    0001 0000
    mov ax,[ebx+esi*2+1] 0110 0111
    (67h,代表使用了32位
    1000 1011 01 000 100 01 110 011 0000 0001
    mov [ebx+esi*2+1],01h 67 1100 0111 01 000 100 01 110 011 0000 0001 0000 00001

     
    只要会填这个表,我们就可以写出所有的机器代码.

    可以看到,Intel的格式中并没有明确的标出两个操作数,而是把偏移量和立即数单独拿了出来,而且同一条指令的操作码会根据寻址方式的不同而变化,不像我们的设计,MOV就是MOV,所有的MOV指令都对应同样的操作码,Prefix部分也是我们的设计所没有的

    下面简单的解释下这六个部分,每个部分的具体含义和使用,后面的例子里会逐步阐述

    prefix:

    指令前缀,为了一些特殊的定义或者操作而存在,只有10个可能的值,可以在下表里面查到,我们大致了解下就是了
    • 锁(Lock)和重复前缀:
    锁前缀用于多CPU环境中对共享存储的排他访问。重复前缀用于字符串的重复操作,他可以获得比软件循环方法更快的速度。
    — F0H—LOCK 前缀.
    — F2H—REPNE/REPNZ 前缀.
    — F3H—REP 前缀
    — F3H—REPE/REPZ prefix (used only with string instructions).
    • Segment override:
    根据指令的定义和程序的上下文,一条指令所使用的段寄存器名称可以不出现在指令格式中,这称为段缺省规则。当要求一条指令不按缺省规则使用某个段寄存器时,必须以段取代前缀明确指明此段寄存器。
    — 2EH—CS  段前缀
    — 36H—SS 段前缀.
    — 3EH—DS 段前缀.
    — 26H—ES 段前缀.
    — 64H—FS 段前缀.
    — 65H—GS 段前缀.
    • 操作大小前缀 66H 和 地址长度前缀 67H

    Opcode:

    操作码,这个操作码指定了具体的操作,他的值可以在下表查到,注意查表时候要根据操作类型,操作数类型和寻址方式来查询,比如Mov指令有12种操 作操作码,我们需要根据操作数的类型,比如Mov bx,1,的两个操作数一个是寄存器,一个是立即数,即Reg,Imm,查下表,应为1011wrrr

        MemOfs,Acc     1010001w    
          Acc,MemOfs     1010000w    
          Reg,Imm     1011wrrr    
          Mem,Imm     1100011woo000mmm    
          Reg,Reg     1000101woorrrmmm    
          Reg,Mem     1000101woorrrmmm    
          Mem,Reg     1000100woorrrmmm    
          Reg16,Seg     10001100oosssmmm    
          Seg,Reg16     10001110oosssmmm    
          Mem16,Seg     10001100oosssmmm    
          Seg,Mem16     10001110oosssmmm    
          Reg32,CRn     000011110010000011sssrrr    
          CRn,Reg32     000011110010001011sssrrr    
          Reg32,DRn     000011110010000111sssrrr    
          DRn,Reg32     000011110010001111sssrrr    
          Reg32,TRn     000011110010010011sssrrr    
          TRn,Reg32     000011110010011011sssrrr

    表中rrr,w,mmm,oo都可以看做几个变量,会根据寄存器,和寻址方式的变化而变化,如果使用4位寄存器,比如al,ah,bl,bh等,则其值为0,否则为1,表<x86操作码查询表>可以查到,注意所查的结果中已经包含了后面的ModR/M字节

    ModR/M和SIB:

    这两个字节共同决定了寻址方式,ModR/M包含三个部分oo|rrr|mmm:这三个部分联合表示了寻址方式,oo指示了寻址模式,rrr:指明所用寄存器,注意使用<x86操作码查询表>查询得到的结果里已经包含ModR/M字节,而SIB是辅助的寻址方式确定位,也包含三个部分

    • ss:放大倍数
    • iii:变址寄存器
    • bbb:基址寄存器

    比如如果用到这样的地址[ebp+5*esi],则ebp为基址寄存器,esi为变址寄存器,5为放大倍数

    Displayment偏移量位:寻址方式中的偏移量,如[ebp+5]中的5

    Immediate:立即数,操作数中的立即数

     

    一起练练手:人肉翻译汇编代码

    一) mov bx,cx 

    查询其操作码为1000 100w,由于使用16位寄存器,则w=1 得到100010001即16进制的89H

    ModR/M包含三个部分oo|rrr|mmm:这三个部分联合表示了寻址方式,这里由于没有内存寻址,查表得,oo=11,rrr和mmm各表示 一个寄存器,那么问题来了:哪个表示目的寄存器bx,哪个表示源寄存器cx呢?翻文档太累了,不如用nasm汇编一下这条指令瞧瞧.得到的ModR/M字 节为对应寄存器代码可以看出来,rrr表示的是源寄存器bx,则这一个字节为:11 001 011,即16进制CBH

    由于这条语句没有内存寻址,SIB列为空,也没有偏移量列Displayment,这条语句也没有立即数作为操作数,所以Immediate列为空

    至于Prefix列,我们稍微看下Prefix的说明和他的值表就能知道,Prefix列只有少数的几种情况才能出现,比如段超越啊,16位/32位切换啊,锁定啊,像mov bx,cx这样普通的语句自然也没有Prefix列

    所以我们可以得到mov bx,cx的最终代码为

      Prefix Opcode ModR/M
    oo|rrr|mmm
    SIB
    ss|iii|bbb
    Displayment Immeidate
    mov bx,cx   100010001 11 001 011      
    mov cx,bx            
    mov cl,bl            

    既然已经掌握了mov bx,cx,那么mov cx,bx呢? mov cl,bl呢?大家自己想想

    如果觉得上面例子还是太简单了,毕竟6列只用了2列,那么我们就来挑战一个有点难度的怎么样

    二) mov [ebx+esi+1],dword 00h

    word是nasm的关键字,表明存入内存的操作数是一个双字,在内存中占32位,即4个字节

    查询Opcode,得1100011w,w=1,即C7

    现在来看ModR/M,这里会有些变化了,我们要仔细分析我们的内存寻址方式ebx+esi+1,有一个8位的偏移量1,所以oo=01,后面的 rrr和mmm该指明用于寻址的两个寄存器,ebp和esi,查询rrr表,应该分别是011,110,则rrr=011,mmm=110,但是我偏偏不 这样作,我设置rrr为000(EAX),mmm为100(ESP),于是代码变为了01000100,44h

    奇怪?明明是ebx+esi,怎么偏偏让你给变成了eax+esp了?

    其实在查询mmm的时候,我们不应该查询rrr表,应该查询iii表,iii表是专门查询变址寄存器号码的,rrr表和iii表基本上完全相同,只 是rrr表中100代表ESP,而iii表中呢…..no index….,这不是表示没有变址寄存器,而是表示设置两个寄存器的工作交给后面的SIB来做,44h可以看做是个特殊的数字,这个数字就表明寻址 方式所用的寄存器会让SIB位来完成.

    上面的做法不是我别出心裁,其实如果你用nasm编译这句话,也会得到这个结果,让SIB来设置内存寻址,我想至少有两个好处,

    一是可以更加灵活一些,毕竟人家SIB有整整一个字节专门来作这件事情,比如如果寻址模式位改为ebx+esi*2+1,SIB里专门有两位ss,表示这个倍数,而ModR/M里呢,对不起,没地方放了

    二是可以让汇编编译器简单一些:统一成一种格式方便处理

    ok,那么如果我们严格按照寄存器查表的结果(ebx=011,esi=110)能不能运行呢,大家自己去试试吧
    SIB
    ss:没有倍数,ss=00
    iii:刚才查过了esi=110
    bbb:ebx=011
    合起来是00110011即33

    后面是8位的偏移量,01h,最后是立即数00h,注意这里是个双字,所以占4个字节

    填在表里

      Prefix Opcode ModR/M
    oo|rrr|mmm
    SIB
    ss|iii|bbb
    Displayment Immeidate
    mov [ebx+esi+1],dword 00h 67,66 C7 44 33 01 0000
                 
                 

    你可能用nasm汇编了一下这条语句,发现前面多了个67,66,恭喜你,67和66正是Prefix,由于你是在16位环境下汇编的,所以如果某 条指令使用到32位的数据和地址,指令前面就会出现前缀,67表示使用了32位地址,66表示使用了32位数据.消除的方法是在文件头上加上[BITS 32]

    推荐一个好的机器码入门<老罗的OPCODE教程:http://www.luocong.com/learningopcode.htm>,x86 OPCODE规范下载<>

    让人迷惑的倒置 -LittleEndian

    参见上面的代码,MOV到ax的操作数为16位二进制的一,即0001h(h表示16进制)可是从这里看上去,是0100h,这是为什么呢?

    其实这是著名的Little Endian存储格式捣的鬼,Little Endian的意思是高位在高地址,低位在低地址,比如0100 0011 0010 0001这个二进制数(十六进制为4321h),在内存里类似

    位置 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
    1 0 0 0 0 1 0 0 1 1 0 0 0 0 1 0

     

    显示的时候,显示程序一般都以一个字节为整体显示这个数,即先解析处0-7位,为数字21h,显示在前面,然后解析8-16位,为数据43h,显示 在后面,则变为了21h 43h,如果显示程序能按照字为整体解析并显示,就能没有这个倒装了,但是显示是不会知道你到底需要怎么显示的,比如你可以定义一个32位数据,也可能定 义64位数据,即使是按照16位,也仍然会有倒装发生,所以现在一般显示程序都简单按照字节显示

    除了LittleEndian反过来当然也有BigEndian,这种存储格式就和咱平时的数字理解习惯没有冲突了

    LittleEndian是Intel x86(8086/8088,80286,80×86,PentiumX)系列CPU所采用的格式,而BigEndian是Motorola的 PowerPC系列CPU所采用的标准,网络传输也采用BigEndian,二者各有优缺点,有兴趣的读者可以参考1980年的著名论文<On Holy Wars and a Plea for Peace>

    别看LittleEndian这个是个细节,却绊倒了不少初学者的腿,比如你刚打开Windbg,想尝试利用调试工具修改某个游戏角色的体力值,从 157110修改为100000000,157110的16进制为265B6,而你在内存里怎么都找不到02 65 B6这个序列,那就是LittleEndian搞的鬼

  • 据Jargon File记载,endian这个词来源于Jonathan Swift在1726年写的讽刺小说 "Gulliver's Travels"(《格利佛游记》)。该小说在描述Gulliver畅游小人国时碰到了如下的一个场景。在小人国里的小人因为非常小(身高6英寸)所以总 是碰到一些意想不到的问题。有一次因为对水煮蛋该从大的一端(Big-End)剥开还是小的一端(Little-End)剥开的争论而引发了一场战争,并 形成了两支截然对立的队伍:支持从Big- End剥开的人Swift就称作Big-Endians而支持从Little-End剥开的人就称作Little-Endians……(后缀ian表明的 就是支持某种观点的人:-)。Endian这个词由此而来。
    1980年,Danny Cohen在其著名的论文"On Holy Wars and a Plea for Peace"中为了平息一场关于在消息中字节该以什么样的顺序进行传送的争论而引用了该词。该文中,Cohen非常形象贴切地把支持从一个消息序列的 MSB开始传送的那伙人叫做Big-Endians,支持从LSB开始传送的相对应地叫做Little-Endians。此后Endian这个词便随着这 篇论文而被广为采用。

    思考:指令的起止

    既然每条指令都可能不一样常,我们的CPU怎么知道每条指令从哪里开始,到哪里结束?

    要知道变长指令的起止,系统就必须自己知道各个指令的长度,可以说系统内部有个登记簿,登记了每个指令的长度.

    程序执行的时候,系统会把eip指向的指令加载到cpu,cpu会尝试翻译指令,这样系统会知道这条指令的长度,比如长 度为6,则将eip增加6,指向下一条语句.如何正确计算指令长度本身是采用CISC(复杂指令集)计算机特有的问题,因为使用RISC(精简指令集)的 cpu,他的指令长度是固定的,让指令变长的优势在于可以节省空间,也方便以后的扩展,缺点是cpu实现会比较复杂

  • 输出结果

    也许你觉得虽然cpu已经执行了我们的工作,但是由于看不到结果,不能满足我们小小的虚荣心,那么下面我们就告诉系统,让他把结果展示在屏幕上

    打开刚才建立的test.com,在刚才的程序后面附加上下面这段

    04 30 88 C2 B4 02 CD 21 E9 FD FF

    程序变为:

    B8 01 00 05 01 00 04 30 88 C2 B4 02 CD 21 E9 FD FF

    保存运行一下看看是不是输出了结果

    感觉好多了吧,至少看见了自己劳动的结晶,后面附加的那段机器码是调用了Dos的int 21中断输出了一个字符,我们直接给出他对应的汇编代码

    mov ax,1
    add ax,1
    add al,'0'    ;数字到ascii的粗糙转换
    mov dl,al    ;—–|
    mov ah,02h;—–|–调用中断
    int 21h      ;—–|
    jmp $        ;保证程序不会立即退出,好让我们看到结果

    image

    从上面的图上我们可以清晰的看到机器码和汇编指令的对应关系,不再赘述

    add al,'0',是把结果转化成ascii,'0'的值为30h,2+30h=32h,是'2'这个字符的ascii值,当然这是个非常粗糙的转换,一旦数字大过9,就会输出奇怪的结果,这样作是为了机器码尽量简单,方便大家输入

    通过上面的二进制编码与汇编代码的对比,我们大概能知道汇编和机器指令是一一对应的,但是由于机器指令实在是太不方便人类记忆,写起来也非常繁琐,所以需要汇编语言,也就是说汇编语言实际上是机器语言的助记符号

     

    总结

     

  • 我们会算1+1了

  • [C#]C#.net同步异步SOCKET通讯和多线程总结

    mikel阅读(740)

    同步套接字通信

    Socket支持下的网上点对点的通信

    服务端实现监听连接,客户端实现发送连接请求,建立连接后进行发送和接收数据的功能

    服务器端建立一个socket,设置好本机的ip和监听的端口与socket进行绑定,开始监听连接请求,当接收到连接请求后,发送确认,同客户端建立连接,开始与客户端进行通信。

    客户端建立一个socket,设置好服务器端的IP和提供服务的端口,发出连接请求,接收到服务的确认后,尽力连接,开始与服务器进行通信。

    服务器端和客户端的连接及它们之间的数据传送均采用同步方式。

     

    Socket

    Socket是tcp\ip网络协议接口。内部定义了许多的函数和例程。可以看成是网络通信的一个端点。在网络通信中需要两个主机或两个进程。通过网络传递数据,程序在网络对话的每一端需要一个socket。

           Tcp/IP传输层使用协议端口将数据传送给一个主机的特定应用程序,协议端口是一个应用程序的进程地址。传输层模块的网络软件模块要于另一个程序通信, 它将使用协议端口,socket是运行在传输层的api,使用socket建立连接发送数据要指定一个端口给它。

    Socket:

    Stream Socket流套接字 Socket提供双向、有序、无重复的数据流服务,出溜大量的网络数据。

    Dgram socket数据包套接字 支持双向数据流,不保证传输的可靠性、有序、无重复。

    Row socket 原始套接字 访问底层协议

    建立socket 用C#

    命名空间:using System.Net;using System.Net.Socket;

    构造新的socket对象:socket原型:

    Public socket (AddressFamily addressFamily,SocketType sockettype,ProtocolType protocolType)

    AddressFamily 用来指定socket解析地址的寻址方案。Inte.Network标示需要ip版本4的地址,Inte.NetworkV6需要ip版本6的地址

    SocketType参数指定socket类型Raw支持基础传输协议访问,Stream支持可靠,双向,基于连接的数据流。

    ProtocolType表示socket支持的网络协议

     

    定义主机对象:

    IPEndPoint类:IPEndPoint构造方法  位置:System.Net

    原型:1)   public IPEndPoint(IPAddress address,int port)     2)public IPEndPoint(long address,int port) 参数1整型int64如123456,参数2端口int32

    主机解析:

    利用DNS服务器解析主机,使用Dns.Resolve方法

    原型:public static IPHostEntry Resolve(string hostname) 参数:待解析的主机名称,返回IPHostEntry类值,IPHostEntry为Inte.Net主机地址信息提供容器,该容器提供存有IP地址列 表,主机名称等。

    Dns.GetHostByName获取本地主机名称

    原型:public static IPHostEntry GetHostByName(string hostname)

    GetHostByAddress

    原型:1)public static IPHostEntry GetHostByAddress(IPAddress address) 参数:IP地址 2)public static IPHostEntry GetHostByAddress(string address) IP地址格式化字符串

     

    端口绑定和监听:

    同步套接字服务器主机的绑定和端口监听

    Socket类的Bind(绑定主机),Listen(监听端口),Accept(接收客户端的连接请求)

    Bind:原型:public void Bind(EndPoint LocalEP)参数为主机对象 IPEndPoint

    Listen:原型:public void Listen(int backlog) 参数整型数值,挂起队列最大值

    accept:原型:public socket accept() 返回为套接字对象

    演示程序:

    IPAddress myip=IPAddress.Parse(“127.0.0.1”);

    IPEndPoint myserver=new IPEndPoint(myip,2020);

    Socket sock=new Socket(AddressFamily.Inte.Network,SocketType.Stream,ProtocolType.Tcp);

    Sock.Bind(myserver);

    Sock.Listen(50);

    Socket bbb=sock.Accept();

    发送数据:方法1:socket类的send方法二.NetworkStream类Write

    send原型:public int Send(byte[] buffer) 字节数组 

    public int Send(byte[],SocketFlags)原型2说明,SocketFlags成员列表:DontRoute(不使用路由表发 送),MaxIOVectorLength(为发送和接收数据的wsabuf结构数量提供标准值)None 不对次调用使用标志) OutOfBand(消息的部分发送或接收)Partial(消息的部分发送或接收) Peek(查看传入的消息)

    原型三:public int Send(byte[],int,SocketFlags) 参数二要发送的字节数

    原型四:public int Send(byte[],int,int,SocketFlags) 参数二为Byte[]中开始发送的位置

    演示:

    Socket bbb=sock.Accept();

    Byte[] bytes=new Byte[64];

    string send="aaaaaaaaaaaa";

    bytes=System.Text.Encoding.BigEndianUnicode.GetBytes(send.ToCharArray());

    bbb.Send(bytes,bytes.length,0);//将byte数组全部发送

    .NetWordStream类的Write方法发送数据

    原型:public override void write(byte[] buffer,int offset,int size) 字节数组,开始字节位置,总字节数

     

    Socket bbb=sock.Accept();

    .NetWorkStream stre=new NewWorkStream(bbb);

    Byte[] ccc=new Byte[512];

    string sendmessage="aaaaaaaaaaaaaa";

    ccc=System.Text.Encoding.BigEndianUnicode.GetBytes(sendmessage);

    stre.Write(ccc,0,ccc.length);

    接收数据:Socket类Receive.NetworkStream类Read

    Socket类Receive方法

    原型:public int Receive(byte[] buffer)   

    2)public int Receive(byte[],SocketFlags)

    3)public int Receive(byte[],int,SocketFlags)   

    4)public int Receive(byte[],int,int,SocketFlags)

    …..

    Socket bbb=sock.Accept();

    ……..

    Byte[] ccc=new Byte[512];

    bbb.Receive(ccc,ccc.Length,0);

    string rece=System.Text.Encoding.BigEndianUnicode.GetString(ccc);

    richTextBox1.AppendText(rece+"\r\n");

     

    .NetworkStream类的Read方法接收数据

    public override int Read(int byte[] buffer,int offset,int size)

     

    演示:bbb=sock.Accept();

    …….

    .NetworkStream stre=new.NetworkStream(bbb);

    Byte[] ccc=new Byte[512];

    stre.Read(ccc,0,ccc.Length);

    string readMessage=System.Text.Encoding.BigEndianUnicode.GetString(ccc);

    线程

    线程创建:System.Threading空间下的Thread类的构造方法:

    原型:public Thread(ThreadStart start) ThreadStart类型值     

           Thread thread=new Thread(new ThreadStart(accp));

           Private void accp(){}//使用线程操作

    线程启动

    Thread thread=new Thread(new ThreadStart(accp));

    线程暂停与重新启动

    启动线程使用Thread.Sleep是当前线程阻塞一段时间Thread.Sleep(Timeout.Infinite)是线程休眠,直到被调用Thread.Interrrupt的另一个线程中断或被Thread.Abort中止。

    一个线程不能对另一个调用Sleep,可以使用Thread.Suspend来暂停线程,当线程对自身 调用Thread.Suspend将阻塞,直到该线程被另一个线程继续,当一个线程对另一个调用,该调用就成为使另一个线程暂停的非阻塞调用。调用 Thread.Resume使另一个线程跳出挂起状态并使该线程继续执行,而与调用Thread.Suspend的次数无关

    线程休眠:Thread.Sleep(10000);

    线程挂起:Thread thread=new Thread(new ThreadStart(accp));

                    Thread.start();

                    Thread.Suspend();

    重新启动:Thread thread=new Thread(new ThreadStart(accp));

                   Thread.start();

                   Thread.Suspend();

                   Thread.Resume();

    阻塞线程的方法:thread.Join使用一个线程等待另一个线程停止

    Thread.Join

    Public void Join();

    Public void Join(int millisecondsTimeout);毫秒

    Public bool Join(TimeSpan timeout);时间间隔类型值

    实例:Thread thread=new Thread(new ThreadStart(accp));

                  Thread.start();

                  Thread.Join(10000);

    线程销毁:

    Thread.Abort,Thread.Interrupt

    Abort方法引发ThreadAbortException,开始中止此线程的过程,是一个可以由应 用程序代码捕获的特殊异常,ResetAbort可以取消Abort请求,可以组织ThreadAbortException终止此线程,线程不一定会立 即终止,根本不终止。

    对尚未启动的线程调用Abort,则当调用Start时该线程将终止。对已经挂起的线程调用Abort,则该线程将继续,然后终止。对阻塞或正在休眠的线程调用Abort,则该线程被中断,然后终止。

    Thread类的Abort方法:

    Public void Abort()

    Public void Abort(object stateinfo);

    演示:

    Thread thread=new Thread(new ThreadStart(accp));

    Thread.Start();

    Thread.Abort();

    Thread.Join(10000);

     

    Socket编程原理:

    Unix的i/o命令集,模式为开-读/写-关 open write/read close

    用户进程进行i/o操作

    用户进程调用打开命令,获取文件或设备的使用权,并返回描述文件或设备的整数,以描述用户打开的进程,该进程进行读写操作,传输数据,操作完成,进程关闭,通知os对哪个对象进行了使用。

    Unix网络应用编程:BSD的套接字socket,unix的System V 的TLI。

    套接字编程的基本概念:

    网间进程通信:源于单机系统,每个进程在自己的地址范围内进行运行,保证互相不干扰且协调工作。操作系统为进程之间的通信提供设施:

    Unix BSD 管道pipe,命名管道named pipe软中断信号signal

    Unix System V 消息message 共享存储区 shared memory 信号量semaphore

    以上仅限于本机进程之间通信。

    端口:网络上可以被命名和寻址的通信端口,是操作系统可以分配的一种资源,网络通信的最终地址不是主机地址,是可以描述进程的摸中标识符。TCP/IP提出协议端口porotocol port端口,表示通信进程。

           进程通过os调用绑定连接端口,而在传输层传输给该端口的数据传入进程中处理,同样在进程的数据需要传给传输层也是通过绑定端口实现。进程对端口的操作相 当于对os中的i/o文件进行操作,每一个端口也对应着一个端口号,tcp/ip协议分为tcp和udp,虽然有相同port number的端口,但是互相也不冲突。 端口号的分配有全局分配,本地分配(动态分配),当进程需要访问传输层,os分配给进程一个端口号。全局分配,就是os固定分配的端口,标准的服务器都有 固定的全局公认的端口号提供给服务。小于256的可以作为保留端口。

           地址:网络通信中的两台机器,可以不再同一个网络,可能间隔(网关,网桥,路由器等),所以可以分为三层寻址

    机器在不同的网络则有该网络的特定id

    同一个网络中的机器应该有唯一的机器id

    一台机器内的进程应该有自己的唯一id

    通常主机地址=网络ID+主机ID  tcp/ip中使用16位端口号来表示进程。

    网络字节顺序,高价先存,tcp和udp都使用16或32整数位的高价存储,在协议的头文件中。

    半相关:在网络中一个进程为协议+本地地址+端口号=三元组,也叫半相关,表示半部分。

    全相关:两台机器之间通信需要使用相同协议

                  协议+本地地址+本地端口号+远程地址+远程端口号 五元组 全相关。

    顺序:两个连续的报文在网络中可能不会通过相同的路径到达,所以接收的顺序会和发送的顺序不一致。顺序是接收顺序与发送顺序一致。Tcp/ip提供该功能。

    差错控制:检查数据差错:检查和CheckSum机制 检查连接差错:双方确认应答机制。

    流控制:双方传输数据过程中,保证数据传输速率的机制,保证数据不丢失。

    字节流:把传输中的报文当作一个字节序列,不提供任何数据边界。

    全双工/半双工:两个方向发送或一个方向发送

    缓存/带外数据:字节流服务中,没有报文边界,可以同一时刻读取任意长度的数据。为保证传输正确或流协议控制,需要使用缓存,交互型的应用程序禁用缓存。

    数据传送中,希望不通过常规传输方式传送给用户以便及时处理的某一类信息(unix系统的中断键delete,Control-c)、终端流控制符Control-s、Control-q)为带外数据。

    客户/服务器模式主动请求方式:

    1.       打开通信通道,通知本地主机,在某一个公认地址上接收客户请求

    2.       等待客户请求到达端口

    3.       接收到重复服务请求,处理请求发送应答信号。接收到并发服务请求。要激活一个新进程处理客户请求,unix系统fork、exec,新进程处理客户请求,不需要对其他请求作出应答,服务完成后,关闭此进程与客户的通信链路。终止

    4.       返回第二步,等待另一个客户请求。

    5.       关闭服务端

    客户方:

    1.       打开一通信通道,并连接到服务器所在主机的特定端口。

    2.       向服务器发送服务请求报文,等待并接收应答;继续提出请求…….

    3.       请求结束以后关闭通信通道并终止。

    1.       客户与服务器进程的作用非对称,编码不同

    2.       服务进程先于客户请求而启动,系统运行,服务进程一致存在,直到正常退出或强迫退出

    套接字类型:

    TCP/IP的socket

    Sock_stream可靠的面对连接数据传输,无差错、无重复发送,安照顺序发送接收,内设流量控制,避免数据流超限,数据为字节流,无长度限制,ftp流套接字。

    Sock_DGRAM 无连接的服务,数据包以独立包的形式发送,不提供无措保证,数据可能丢失重复,发送接收的顺序混乱,网络文件系统nfs使用数据报式套接字。

    Sock_Ram 接口允许较底层协议,IP,ICMP直接访问,检查新的协议实现或访问现有服务中配置的新设备。

    服务端:

    using System.Net;

    using System.Net.Sockets;

    using System.Text;

    using System.Threading;

    Thread mythread ;

    Socket socket;

    // 清理所有正在使用的资源。

            protected override void Dispose( bool disposing )

             {

                  try

                 {

                socket.Close();//释放资源

                mythread.Abort ( ) ;//中止线程

                 }

                 catch{ }

                 if( disposing )

                  {

                       if (components != null)

                       {

                           components.Dispose();

                       }

                  }

                  base.Dispose( disposing );

             }       

             public static IPAddress GetServerIP()

             {

                  IPHostEntry ieh=Dns.GetHostByName(Dns.GetHostName());

                  return ieh.AddressList[0];

             }

             private void BeginListen()

             {

                  IPAddress ServerIp=GetServerIP();

                  IPEndPoint iep=new IPEndPoint(ServerIp,8000);

                  socket=new

                           Socket(AddressFamily.Inte.Network,SocketType.Stream,ProtocolType.Tcp);

                  byte[] byteMessage=new byte[100]; 

                  this.label1.Text=iep.ToString();

                  socket.Bind(iep); 

    //            do

                  while(true)

                  {

                       try

                       {

                           socket.Listen(5);

                           Socket newSocket=socket.Accept();

                           newSocket.Receive(byteMessage);

                           string sTime = DateTime.Now.ToShortTimeString ( ) ;

    string msg=sTime+":"+"Message from:";

    msg+=newSocket.RemoteEndPoint.ToString()+Encoding.Default.GetString(byteMessage);

                           this.listBox1.Items.Add(msg);

                       }

                       catch(SocketException ex)

                       {

                           this.label1.Text+=ex.ToString();

                       }

                  }

    //            while(byteMessage!=null);

             }

             //开始监听

             private void button1_Click(object sender, System.EventArgs e)

             {

                  try

                  {

                       mythread = new Thread(new ThreadStart(BeginListen));

                       mythread.Start();

                  }

                  catch(System.Exception er)

                  {

                       MessageBox.Show(er.Message,"完成",MessageBoxButtons.OK,MessageBoxIcon.Stop);

                  }

             }

    客户端:

    using System.Net;

    using System.Net.Sockets;

    using System.Text;

             private void button1_Click(object sender, System.EventArgs e)

             {

                  BeginSend();      

             }

             private void BeginSend()

             {            

                  string ip=this.txtip.Text;

                  string port=this.txtport.Text;

                  IPAddress serverIp=IPAddress.Parse(ip);           

                  int serverPort=Convert.ToInt32(port);

                  IPEndPoint iep=new IPEndPoint(serverIp,serverPort); 

                  byte[] byteMessage; 

    //            do

    //            {

                       Socket socket=new Socket(AddressFamily.Inte.Network,SocketType.Stream,ProtocolType.Tcp);

                       socket.Connect(iep);

                       byteMessage=Encoding.ASCII.GetBytes(textBox1.Text);

                       socket.Send(byteMessage);

                       socket.Shutdown(SocketShutdown.Both);

                       socket.Close();

    //            }

    //            while(byteMessage!=null);

             }

    基于TCP协议的发送和接收端

     

                                                                                                                                                张海

                                                                                                                                            2009/03/13

    [Flex]FMS3系列(三):创建基于FMS的流媒体播放程序,看山寨帮的山寨传奇

    mikel阅读(821)

       本文主要介绍怎么去创建基于FMS的流媒体播放程序,Flash客户端通过网络加载FMS服务器上的视频流文件(.flv,.mp4等),实现视频流的播放。

         要实现媒体流文件的播放是非常简单的,只要在FMS服务器上提供好流媒体文件,Flash客户端通过NetConnection连接到 FMS服务器,然后通过NetStream加载就OK。关于怎么连接FMS在本系列的前两篇已有详细介绍,首先得在fms上建立好服务器应用并部署好媒体 文件,如下图示:

               

     

         下面是在Flash中开发的流媒体文件播放示例程序:

     1 import flash.display.*;
     2 import flash.events.*;
     3 import flash.net.*;
     4 
     5 var nc:NetConnection = new NetConnection();
     6 var ns:NetStream;
     7 var video:Video;
     8 
     9 nc.connect("rtmp://localhost/PlayStreams");
    10 nc.addEventListener(NetStatusEvent.NET_STATUS,onStatusHandler);
    11 
    12 function onStatusHandler(evt:NetStatusEvent):void
    13 {
    14     trace(evt.info.code);
    15     if(evt.info.code=="NetConnection.Connect.Success")
    16     {
    17         ns=new NetStream(nc);
    18         ns.addEventListener(NetStatusEvent.NET_STATUS,onStatusHandler);
    19         ns.client=new CustomClient();
    20         video=new Video();
    21         video.attachNetStream(ns);
    22         ns.play("2009031301",0);
    23         addChild(video);
    24     }
    25 }

         看看上面的程序代码是不是非常简单,现在我对上面的代码进行详细的分析。程序从上到下思路很清晰,首先将程序中需要的相关包导入,然后定义了连接对象(NetConnection),流对象(NetStream)和视频对象(Video)。

         通过NetConnection的connect方法连接到fms服务器(rtmp://localhost/PlayStreams),并添加网络连接的事件处理函数,在此函数内判断网络连接状态,如果连接成功(连接状态:NetConnection.Connect.Success)则通过NetStream建立视频流,调用NetStream的play方法播放指定的流媒体文件,然后将流附加到视频对象并显示在flash界面上。如下图示:

              

         OK,我们已经实现了流媒体文件的播放,下面我们来扩展程序的功能,为前面的视频播放程序加上播放、暂停、停止以及重新播放等功能。这时可以在界面上放置几个按扭来驱动这些功能,添加按扭代码如下(当然也可以直接拖拽Botton组件):

     1 var btnPlay:Button=new Button();
     2 btnPlay.x=10;
     3 btnPlay.y=250;
     4 btnPlay.width=50;
     5 btnPlay.label="播放";
     6 btnPlay.addEventListener(MouseEvent.CLICK,onPlayHandler);
     7 addChild(btnPlay);
     8 
     9 var btnPause:Button=new Button();
    10 btnPause.x=80;
    11 btnPause.y=250;
    12 btnPause.width=50;
    13 btnPause.label="暂停";
    14 btnPause.addEventListener(MouseEvent.CLICK,onPauseHandler);
    15 addChild(btnPause);
    16 
    17 var btnStop:Button=new Button();
    18 btnStop.x=150;
    19 btnStop.y=250;
    20 btnStop.width=50;
    21 btnStop.label="停止";
    22 btnStop.addEventListener(MouseEvent.CLICK,onStopHandler);
    23 addChild(btnStop);
    24 
    25 var btnReplay:Button=new Button();
    26 btnReplay.x=220;
    27 btnReplay.y=250;
    28 btnReplay.width=80;
    29 btnReplay.label="重新播放";
    30 btnReplay.addEventListener(MouseEvent.CLICK,onReplayHandler);
    31 addChild(btnReplay);
    32 
    33 function onPlayHandler(evt:MouseEvent):void
    34 {}
    35 
    36 function onPauseHandler(evt:MouseEvent):void
    37 {}
    38 
    39 function onStopHandler(evt:MouseEvent):void
    40 {}
    41 
    42 function onReplayHandler(evt:MouseEvent):void
    43 {}

     

         这里我们需要对上面的代码进行一下重构,将流和控制视频播放的代码重构为方法,以便在重新播放的时候直接调用。

     

     1 function playStream():void
     2 {
     3     ns=new NetStream(nc);
     4     ns.addEventListener(NetStatusEvent.NET_STATUS,onStatusHandler);
     5     ns.client=new CustomClient();
     6     video=new Video();
     7     video.attachNetStream(ns);
     8     ns.play("2009031302",0);
     9     addChild(video);
    10 }

     

         上面我们已经将控制视频播放、暂停、停止和重新播放的按扭构造在了flash界面上,现在只需要完成这些按扭的功能就是,要实现视频的播放、暂停、停止和重新播放同样是非常简单的,NetStream为我们提供了可直接调用的API。详细如下:

     1 function onPlayHandler(evt:MouseEvent):void
     2 {
     3     ns.resume();
     4 }
     5 
     6 function onPauseHandler(evt:MouseEvent):void
     7 {
     8     ns.pause();
     9 }
    10 
    11 function onStopHandler(evt:MouseEvent):void
    12 {
    13     ns.close();
    14 }
    15 
    16 function onReplayHandler(evt:MouseEvent):void
    17 {
    18     ns.close();
    19     playStream();
    20 }

     

         一切搞定 ,可以按下Ctrl+Enter测试了,看到了flash已经成功的加载到了fms上的视频文件(.flv)。打开FMS管理控制台就会看到,在应 用"PlayStreams"下有一个NetStream的连接,代表当前应用有一个网络流正在传输,如下图:

     

    完整示例代码

     

         如果在Flex环境下开发,更方便实现,详细本文就不做介绍了,核心代码和Flash里开发是一样的。

    版权说明

      本文属原创文章,欢迎转载,其版权归作者和博客园共有。  

      作      者:Beniao

     文章出处:http://beniao.cnblogs.com/  或  http://www.cnblogs.com/

    [PHP]PHP动态生成flash动画

    mikel阅读(750)

    了解 Flash

    字串6

    Flash Player 是集成到运行 Microsoft® Windows®、Mac OS X 和 Linux® 的计算机的 Web 浏览器中的一个插件。截至本文完稿时,最新版本的 Flash Player 是 V8。它是可以免费获得的,大多数浏览器都附带安装了此插件。它十分流行并且具有优秀的客户机渗透力 —— 而这种渗透力随着 YouTube 和 Google Video 这类服务的出现得到了提高,这些服务都使用 Flash 显示视频流。

    字串9

    Flash Player 只是天平的一端。要发挥作用,Flash Player 还需要使用一个 Flash 动画。此类动画通常是使用一种 Flash 的开发工具编译的文件,其文件扩展名为 .swf。但正如您将在本文中看到的那样,还可以使用 Ming 库用几乎与动态创建图片相同的方法来动态构建 .swf 文件,并在 Web 服务器上绘制图形。Ming 库利用由 PHP 代码构建的对象和方法在新的 .swf 文件中构建操作代码。 字串6

    您可以通过两种方法中的任意一种方法来查看 Web 站点中的 .swf 文件。第一种方法只需导航到 .swf 文件的 URL。这样做将把 Web 服务器的整个内容区域替换为 Flash 动画。此方法便于进行调试,但主要的用法还是将动画嵌入到 HTML Web 页面的 标记中。该 标记然后再通过 URL 引用 SWF 动画。方法的优点在于您可以把动画放在页面的任意位置,并可通过JavaScript 代码进行动态控制,就像处理页面中的任何其他元素一样。

    字串1

    清单 1 显示的是一个引用 SWF 动画的 <object> 标记的示例。 字串6

    清单 1. 嵌入式 Flash 动画 字串8

    <OBJECT classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
    codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#
            version=6,0,40,0"
    WIDTH="550" HEIGHT="400">
    <PARAM NAME="movie" VALUE="lines.swf">
    <EMBED src="lines.swf" WIDTH="550" HEIGHT="400"
    TYPE="application/x-shockwave-flash"
    PLUGINSPAGE="http://www.macromedia.com/go/getflashplayer">
    </EMBED>
    </OBJECT> 字串1

    这组标记将引用一个名为 lines.swf 的动画。内部的 <embed> 标记用于确保 Flash 动画可以在安装了插件的各种浏览器中播放。 字串5

    标记还把 Flash Player 的高度和宽度分别指定为 550 像素和 400 像素。非常值得注意的是,Flash 动画中的图形都是基于矢量的,这意味着当您使用 Flash 命令绘制线条和文本时,那些元素都被存储为坐标并且按照匹配显示区域的比例进行缩放。如您所见,Flash 动画有自己的坐标系统,您可以按照适合自己的方法使代码尽可能整洁。 字串7

    Ming

    字串2

    本文中提供的使用 Flash 动画的第一种方法是使用 Ming 库动态生成它们。Ming 库是一个 PHP 库,其中有一组映射到 SWF 动画中的数据类型的对象:子图形、图形、文本、位图等等。我将不讨论如何构建和安装 Ming,因为其操作是特定于平台的而且并不特别简单(请参阅 参考资料)。在本文中,我使用了预编译的扩展 php_ming.dll 库用于 Windows 版本的 PHP。

    字串6

    必须指出的是,Ming 仍处于开发阶段。截至本文完稿时,库的版本是 V0.4,并且较老版本中的一些命令在最新版本中不能使用。我使用了 V0.4 撰写本文,因此,要使用这段代码,您需要使用这个版本。 字串9

    清单 2 显示了使用 Ming 库实现的 HelloWorld 示例。

    字串3

    清单 2. Hello.php

    字串9

    <?php
    $f = new SWFFont( '_sans' );
    $t = new SWFTextField();
    $t->setFont( $f );
    $t->setColor( 0, 0, 0 );
    $t->setHeight( 400 );
    $t->addString( 'Hello World' );
    $m = new SWFMovie();
    $m->setDimension( 2500, 800 );
    $m->add( $t );
    $m->save( 'hello.swf' );
    ?>

    字串2

    在命令行中运行这段代码将生成文件 hello.swf。当我在 Web 浏览器中打开该文件时, 字串8

      字串4

    回过头来查看这段代码,我做的第一件事是创建指向一个内置字体(_sans)的指针,然后创建文本字段,设定字体、颜色和大小,最后为其提供一些文 本内容(“Hello World”)。再接下来创建了一个 SWFMovie 对象并设定其尺寸。最后,向动画中添加了文本元素并将动画保存到文件中。

    字串6

    作为直接构建文件的替代性方法,也可以使用下面的代码,使 SWF 动画像页面那样输出,而无需使用 save 方法: 字串7

    header( 'Content-type: application/x-shockwave-flash' );
    $m->output( );

    字串9

    此过程类似于使用 PHP 中的 ImageMagick 库来构建位图。对于所有 Ming 示例,我都将使用 save 方法,但您可以根据喜好来选择是否使用 save 方法。

    字串7

    让文本动起来

    字串5

    只是将一些文本放入 Flash 动画中是没有多大意义的,除非您能让它动起来。因此我整合了清单 2 中的示例,它包括两段文本:一部分开始很小后来变得越来越大,而另一部分保持静态。

    字串9

    清单 3. Text.php 字串9

    <?php
    $f = new SWFFont( '_sans' );
    $pt = new SWFTextField();
    $pt->setFont( $f );
    $pt->setColor( 0, 0, 0 );
    $pt->setHeight( 400 );
    $pt->addString( '1000' );
    $tt = new SWFTextField();
    $tt->setFont( $f );
    $tt->setColor( 192, 192, 192, 90 );
    $tt->setHeight( 350 );
    $tt->addString( 'Points' );
    $m = new SWFMovie();
    $m->setDimension( 2500, 800 );
    $pts = $m->add( $pt );
    $pts->moveTo( 0, 0 );
    $tts = $m->add( $tt );
    $tts->moveTo( 1300, 200 );
    for( $i = 0; $i < 10; $i++ ) {
      $m->nextframe();
      $pts->scaleTo( 1.0 + ( $i / 10.0 ), 1.0 + ( $i / 10.0 ) );
    }
    $m->save( 'text.swf' );
    ?> 字串2

    [Flex]Flex 开发架构渐变

    mikel阅读(781)

    Flex 无疑是RIA第一位得选择,而JAVA 可以是Enterprise application 的第一选择。 结合二者来开发Web App 无疑是一种走向流行的方案。 前端Flex+后端JAVA的简单架构如图:

    flex and java

     

     

    在实际得开发中实现上述结构的方法非常之多。我只是将自己开发的渐变过程记录下来。希望对后来者有所借鉴。

    这个系列blog包括:

     

    一。混沌未开-Flex-all-in-one

    二。中央管理-Flex Central Managerment

    三。MVC框架-Flex Cairngorm

    四。咔嚓Front ControllerCairngorm

    五。轮回转世-Mate

     

    样例。

    为简洁起见,用一个非常简单的样例来演示开发过程:密友列表

    系统只有二个画面:

      登录-LoginView:

     

     login view

     

     

    输入用户名和密码并登录, 进入密友列表画面(BuddyListView):

     

     buddylist

     

     

    由于这个系列blog焦点在Flex应用开发方式,所以只选择Remote Object  作为和后端通讯的方式。AMF的实现使用Adobe 的opensource data service-BlazeDS。Java代码非常简单。仅限于配合这个blog系列。

    相关得配置文件和JAVA class 如下:

    1。remot-config.xml:

     

    Xml代码
    1. <?xml version=”1.0″ encoding=”UTF-8″?>  
    2. <service id=”remoting-service”  
    3.     class=”flex.messaging.services.RemotingService”>  
    4.   
    5.     <adapters>  
    6.         <adapter-definition id=”java-object” class=”flex.messaging.services.remoting.adapters.JavaAdapter” default=”true”/>  
    7.     </adapters>  
    8.   
    9.     <default-channels>  
    10.         <channel ref=”my-amf”/>  
    11.     </default-channels>  
    12.     <destination id=”flexmvcRO”>  
    13.      <properties>  
    14.       <source>com.ny.blog.flex.mvc.accessor.DummyAccessor</source>  
    15.       <scope>session</scope>  
    16.       </properties>  
    17.      <adapter ref=”java-object” />  
    18.     </destination>  
    19.   
    20. </service>  

     

    2.DummyAccessor.java:

    Java代码
    1. package com.ny.blog.flex.mvc.accessor;  
    2.   
    3. import java.util.ArrayList;  
    4. import java.util.List;  
    5.   
    6. import com.ny.blog.flex.mvc.pojo.Friend;  
    7.   
    8. public class DummyAccessor {  
    9.   
    10.  public DummyAccessor() {  
    11.  }  
    12.    
    13.  public boolean login(String userName,String password){  
    14.   return true;  
    15.  }  
    16.    
    17.  public  List<Friend> getAllFriends(String userName){  
    18.   List<Friend> myBuddy = new ArrayList<Friend>();  
    19.   Friend dummy1 = new Friend();  
    20.   dummy1.setFirstName(”John”);  
    21.   dummy1.setLastName(”Smith”);  
    22.   myBuddy.add(dummy1);  
    23.     
    24.   Friend dummy2 = new Friend();  
    25.   dummy2.setFirstName(”Andy”);  
    26.   dummy2.setLastName(”Jones”);  
    27.   myBuddy.add(dummy2);  
    28.     
    29.   Friend dummy3 = new Friend();  
    30.   dummy3.setFirstName(”Michael”);  
    31.   dummy3.setLastName(”Niu”);  
    32.   myBuddy.add(dummy3);  
    33.   return myBuddy;  
    34.  }  

     

    3.相关  pojo Friend.java:

    Java代码
    1. public class Friend {  
    2.  private String firstName;  
    3.  private String lastName;  
    4.  private String nickName;  
    5.   
    6. public Friend() {  
    7.  }  
    8.   
    9. //getter and setters  
    10. …  
    11. }  

     

     

    Lets go :混沌未开-Flex-all-in-one

    [架构]做人、做事,做架构师——架构师能力模型解析

    mikel阅读(683)

    要想从一名普通程序员发展成为优秀的架构师,“个人特性”与“技术技能”缺一不可;而“技术专业能力”、“人际关系能力”和“业务能力”更是优秀架构师重要的三种能力。

    文 / 周爱民(《程序员》2008年4月刊)

    引子

    究竟是什么让你在同一个位置上——例如程序员或技术负责人——工作了三年、五年或者更久,而 仍然得不到任何的发展空间?你觉得自己已成为技术圈中的大牛,并信心满满地去拿明天就要颁发的某某大奖,然而却仍然停留在同样的技术职位上,去年到今年涨 的薪水甚至填不平物价升幅?于是,你开始对老板不满,对员工不满,对昨天升职的那个同事不满……你开始计划明天就要跑单,或者准备考虑提出加薪却又心怀忐 忑。

    如果技术人员有发展的轨迹,那么他要么“看透工具的本质,把关注点转移到‘团队’的圈子里去 ”,要么“顺着代码铺就的道路,亦步亦趋地成为良匠大师”。仅以技术方向而言,你大概可以做到架构师、总架构师甚至首席架构师;但问题是:你现在还只是一 个程序员。那要如何才能踏上通往架构师之路呢?本文为你解析一个架构师的能力模型。

    你能不能做一个好的架构师?

    架构师不是界定一个技术高下的职位名称,而是一个职务。所谓职务,包括职——职位,务——工作。前者决定了你具备哪些资源,可以影响到怎样的范围,以及面向的机构,后者则简单地是你需要完成的工作列表。

    所以我说“架构师”不是指“一个能做架构的人”。前者是把架构师当职能,后者是当工人。能做 一份工作列表中的事,并不等于就成为相应职位上的人。在管理体系里面,你的个人特性决定了你在哪个位置,而技术技能只是做事实施的必需。架构师这个职务, 同时要求较高的个人素质和技术能力,因此它的进取之路总结起来就是:做人、做事,做架构师。

    因此“模型”由“个人特性”和“技术技能”两个方面构成,在第一张图中,我特别说明“个人特性”既包括人际关系的能力,也包括(具体)业务能力;“技术技能”也是如此。所以个人特性主要与“做人”有关,部分地也包含“做事”的要素。

    (转)做人、做事,做架构师——架构师能力模型解析 - oliwen - oliwen

                                                图1 架构师能力模型

    “有效沟通”以及“学会谈判”与做具体的事无关,是个人能力特性的公共方面。前者是过程,后 者是知道如何定目标与求结果。而“风险与防备”是做事过程控制的关键,与前面两项正好构成了一个做事基本能力的完整体系。基本上,这三项个人特性都是一个 “普通程序员”所不具备的,甚至在大多数情况下,普通程序员并不愿意去具备这样的个人特性,因为在许多陷于技术泥淖的开发人员看来:沟通总是会使事情变得 更加麻烦,谈判则徒耗时间而无济于事。然而事实上,在整个的架构决策过程中,架构师需要不停地沟通与谈判。将“架构”变成“决策”的过程,其实就是对各个 技术角色(及其思想)兼容并包的过程,你需要不断地协调需求、实现之间的各种问题,也需要面对各种投资者(时间、资金、人才等方面的决策者)进行谈判,以 确定项目的规模——没有规模也就没有范围,没有范围如何展开设计呢?

    一部分开发人员会认为上述过程是“项目经理”的事情,但真的如此吗?当你作为一个更高级别的架构师,以至于要影响到多个项目的决策时,你就全然不会有这种感受了。因为这种情况下,你的决策将先于项目的启动,或者说你已经不单单是一个技术角色了。

    设计是架构能力的一部分,但架构师不是设计师——看清楚二者之间的不同,你才真正迈出了架构师职业生涯的第一步。

    抽象是思维能力、模型化是表达能力

    个人特性中另一个非常重要的方面是“抽象思维”,而这是与架构师角色直接相关的一种能力。这种能力既有职业技能特征,又是普遍性的能力。

    所谓普遍性的能力,是指“抽象”在我们——作为人这种个体的——生活中无处不在。例如我们说 花、草,说桌、椅……我们用语言去指称任何一个既已存在的(可以脱离我们的语言而自然存在的)事物时,就用到了抽象。说“桌子”的时候,既没有描述桌子的 具体形式,也没有说明它的规格,但我们用这个名词时,所有人都知道“桌子是什么”。所以,名词概念是整个抽象逻辑系统中的主体。如果失去了这些名词定义, 我们基本上不能说话,也不能描述任何东西——那便到了“只可意会不可言传”的境地。

    用现有的成熟语汇去描述你的系统时,大多数人会理解你所表达的含义,例如我们说“这个系统设 计为一个三层结构”。然而架构师面临的系统在许多细节上并不见得能够用成熟的语汇去描述,因此必须自已构建一个抽象系统,这就需要概念抽象能力、概念表达 能力和基于概念的逻辑表达能力。

    概念抽象能力是一种思维能力。简单地说,就是“把目标分解或概括清楚”:你要么概而言之“它 是什么”,要么详细地说明“它包括什么”。必须使用大量的语汇来陈述这个“什么”,这不单单是表达为文字,也表达为你在思想过程中的一个完整系统。通常用 的方法是“映射系统”。例如你可以用数学中的“数轴”来映射“实数域”。将目标系统形式化为一个概念化的、可讨论的结构系统后,你的抽象过程就基本结束 了。

    (转)做人、做事,做架构师——架构师能力模型解析 - oliwen - oliwen

                                                图2 能力模型中的个人特性

    然而这个“抽象系统”可能只构建在你的思维意识里,还必须把它描绘出来。因为不能只是你自己思考清楚,系统就能设计完成。这个“描绘”就依赖于后面两种表达能力,一种是描绘概念实体,一种是描绘实体上的逻辑——有趣的是,这似乎又回到了“程序=结构+算法”。

    现在大家回过头来看看UML,或者更多种类的ML(建模语言),他们就用于表达这两个方面的东西:要么是概念实体(例如用一个框表明系统边界),要么是实体上的逻辑(例如用箭头表明逻辑时序)。

    所以大家应该清楚,我们再如何称赞UML,它也只是一种对模型化系统的“表达能力”,你只能把它当一种辅助表达的工具去使用,它本身既不能帮助思考,也不见得能作为抽象过程中的或抽象思维环境中的参考。

    任何一个优秀的架构师都有自己独特的思考方式,这决定了他如何抽象系统,以及如何“创造性地 ”设计与构画这个系统。这种“独特的思考方式”贯彻他从孩童开始的整个成长过程,直至他形成独立的社会观、人生观与世界观。他认识世界的方式和接受世界的 能力决定于他如何思考,也反映了他这种思考方式的“独特性”。但这并不表明他有特立独行的行为特性(我们这里只说他的思考方式),我们不应介意他是否用某 种语言(例如UML或者形式化编程语言)来表达他的思考结果。

    推动:设计做大,实施做小

    架构师首先是把问题的真正目标确定下来,然后变成系统设计、平台设计或架构设计。而在此之后 设计输出将会有两个方向的发展,一是被忠实地贯彻下来,二是被变形地发展下去。两个方向都存在致命的危险:架构最终能否被完整实现。对前者来说,可能是架 构设计过度,或设计本身出现了错误;后者则是对架构直接的伤害。

    所以架构师必须参与实施的全程——尤其是在架构被映射为目标系统的前期。在这个阶段中,架构 师的任务就是推动架构实施,以保证在项目全程的设计/架构/体系的一致性。除了直接跟设计师或设计团队沟通,以保证他们的设计在你可以控制的范围之内以 外,架构师还必须有阶段化设计的能力。这种能力用于将一个原本规模宏大的架构设计,变成较小的、易于实施的、对开发团队来说可控的关键点。例如在体系层次 的规划上,设计可能是独立、异质的、可迁移的存储框架来实现数据层,但在(前期的)实施上,这里可能被表达为本地数据库,并要求前端开发人员必须通过一个 清晰的数据交互层来访问——例如一组数据存取接口,或一个独立数据服务组件。开发人员可能在这里遇到障碍:因为要通过这些中间层来访问本地数据库,纯粹是 多余的。然而,正是这“多余的工作”提供了系统弹性,为并行团队开发公共存储服务争取了周期,也为将来的灵活部署与数据迁移提供了可能。

    这里的关键就在于,无论原始系统设定有多大,实施时总是在“做小”。每一个局部的实施块都是 可控的,并为它在整个体系空间中留下了位置和接口,这样才可能由“小的部分”做大。一个大系统的架构师可能同时在考虑许多个项目中的、不同位置的架构,并 且清楚这些项目最终的总体规模。而这,就是平台架构师和体系架构师所涉的领域。

    (转)做人、做事,做架构师——架构师能力模型解析 - oliwen - oliwen

                                                图3 架构师模型图中的“实现能力”

    架构真的是“好不好”的问题吗?如同我对工程的理解一样,架构问题的根本,也并不在于它是否 完美或漂亮,而是在于是否合用。因此架构师必须对实施架构的团队以及实施过程有充分了解,知道他们的能力缺陷,知道实现过程要消耗的资源,清楚每个环节可 能的故障以及先兆。只有这样,架构师才能设计一个让这个团队能实现,而且在实现过程中能受控的架构。

    要知道,你作为架构师被请来,不是画几张图纸交给项目经理,说:你们去做吧,做不出来是你们 不会做。即使你可以身体力行,在这个团队中教大家、培养大家,那么公司的开销呢?风险呢?这些东西难道就不考虑了?项目的周期因为实现的复杂程度而无法控 制时,项目就死掉了。那么,追根究底来说,是不是架构师的问题?是啊,你为什么会做了一份“不合用”的架构呢?——你都不知道项目如何开发、由谁实施、如 何管理等等,又如何能面对这些实际环境去设计架构呢?

    所以这一部分能力,是要在你的开发经验、团队经验以及用人识人的经验中去找的。参考模型图的“实现能力”下的“设计能力→了解你的主要沟通对象”和“架构推行”等分支,对你或有一些可用的提示。

    局部与全局

    架构是一个从全局到局部的过程,而实施正好反过来,是从局部到全局。这也正是“设计做大,实施做小”的另一个层面的含义。设计大才可以见到全局,才知道此全局对彼全局的影响;实施小才可能关注细节,才谈得上品质与控制。

    事实上,大多数情况下架构是在为“当前项目之外”去考虑,这可以看成全局关注的一个组成部分。因此我们需要界定所谓“全局”的范围:超出公司或整个产品系列、产品线或规划的范围才是多余的。

    所以当架构决策谈及“全局”时,其目标并不见得是“保障当前项目”,而又必须由当前项目去完成。

    一个经常被用到的例子是:如果仅为当前项目考虑,那么只需要做成DLL模块;如果为产品线考虑,可能会是“管道+插件”的结构形式。而“管道+插件”的形式显然比做成DLL模块更费时,这个时间成本(以及其它成本)就变成了当前项目的无谓开销。

    这种全局策略对局部计划的影响是大多数公司不能忍受的,也被很多团队所垢病。然而这却是架构 师角色对体系的“近乎必然”的影响——如果你试图在体系中引用架构师这个角色的话。一些情况下,体系能够容纳这种影响,例如“技术架构师”试图推动某种插 件框架,而正好开发人员对这项技术感兴趣,那就顺其自然地花点工夫去实现了。但如果不是这样,实施者或实施团队看不到“多余的部分”对他们的价值时,来自 局部的抵触就产生了。

    这种情况下,平衡这些抵触就成了架构推行的实务之一。在我看来,“平衡”是全局的艺术和局部 的技术。也就是说,一方面架构师要学会游说,另一方面也要寻求更为简洁的、成本更小的实现技术。只有当整个体系都意识到(你所推行的)架构的重要性,而且 实施成本在他们可以接受的范围之内时,他们才会积极行动起来。

    所以所谓平衡,其实也是折衷的过程。构架师只有眼中见大,才知道哪些折衷可以做,而哪些不 能。所谓设计评估(模型图中的实现能力->设计能力->设计评估分支)并不是去分析一个设计结果好或不好,而是从中看到原始的需求,看到体系 全局的意图,然后知道在将设计变得更为“适当”时可以做哪些折衷。同样的原因,架构师也必须知道自己的决策会产生的影响,才能控制它们,以防它们变成团队 的灾难。有些时候,架构师甚至需要抛弃一些特性,以使得项目能够持续下去。因为产品的阶段性产出只是整个战略中的一个环节,而不是全部。

    其它

    “怎么做一个架构师”这个问题得分成两个部分来看,一个是“做到”,一个是“做好”。由于架构师本身不过是一个技术职位,所以时机成熟了自然会做得到。但问题是,真有一天你被放在这个位置上了,你能做得好吗?

    我浏览过几套所谓培训机构的有关架构师的教程,也翻阅过一些讲架构的书。我发现他们普遍地是 将架构作为一种“职业技术”来讲,就像培养程序员或者缝纫工一样来教育。但就我的经验来说,架构并不是一件纯粹表现技术能力的工作,所以并不是翻几本书学 几种方法就可以投入“实战”的。更深层的问题是,架构师其实不是“战”出来的。昨天跟同事讨论这个话题,他把我们这几年来的一些思考用了三句话来概括,非 常精彩:从无到有的,是架构;从表到里的,是抽象;从粗到细的,是设计。

    那么到底什么是架构呢?从上面的概括中你是看不到答案的。到底如何做架构呢?从本文中你也是 看不到答案的。然而我说,“你看不到答案”的根源其实是在于你的眼光与心性——后面这个词换成现代白话,就是“思想”。真正阻碍了你成为优秀架构师的,也 许正是你既有的知识与思想方法,扔掉它们,接受一些全然有别的信息,也许正是良好的开端。

    或许你现在正愤愤然:这篇文章怎么空洞无物?——我甚至能想象到一些读者的表情。然而请在问题面前停下来,不要急于给出答案。正如你将“?”稍微变下形,它就成为了“!”一样,问题的本身,就是答案。

    作者简介

    周爱民(aimingoo),具有十余年的软件开发、项目管理和团队建设的经验,现担任盛大网络的平台架构师,著有《大道至简》、《Delphi源代码分析》等。

     

    原文地址: http://vipnews.csdn.net/newscontent.aspx?pointid=2008_05_30_150239242

    [Python]Python开发web(1) -- 系统环境与框架的选择

    mikel阅读(802)

    学了多种语言,今天起把目标瞄向了Python和Perl。先从Python开始。

    ———————我是pangpang分隔线———————

    官网上对Python的定义:

     

    Python is a great object-oriented, interpreted, and interactive programming language. It is often compared (favorably of course  ) to Lisp, Tcl, Perl, Ruby, C#, Visual Basic, Visual Fox Pro, Scheme or Java… and it's much more fun.

     

    Python combines remarkable power with very clear syntax. It has modules, classes, exceptions, very high level dynamic data types, and dynamic typing. There are interfaces to many system calls and libraries, as well as to various windowing systems. New built-in modules are easily written in C or C++ (or other languages, depending on the chosen implementation). Python is also usable as an extension language for applications written in other languages that need easy-to-use scripting or automation interfaces.

    ———————-我是pangpang分隔线———————

    已搭建的系统环境:

    CentOS 5.1 kernel 2.6.18

    Python 2.5

     

    ———————我是pangpang分隔线———————

    Python的流行框架有很多,纳入考虑范围的有django、pylons、web2py,另外,因豆瓣网而关注quixote。

     

    排序原则

    平台

    有大把時間做企業級應用

    plone,zope

    適合 python 專家用

    pylons, webpy, twisted.web, zope

    追求一體框架

    django, zope/plone, karingel

    適合快速上手

    karingel, cherrypy. turbogears

    快速 CMS

    django

    支援度

    django, turbogears, pylons, zope/plone

    框架自由度

    pylons, turbogears

    一般用途

    django, turbogears, pylons

    文檔優勢

    django, turbogears, pylons

    有 Rails 背景

    pylons, turbogears

    有 AJAX/JavaScript helper/widgets

    turbogears/pylons

    WSGI 支持度

    pylons, turbogears(django努力中)

    JSON(AJAX server side)

    turbogears

    並用 flash/flex

    turbogears

    摘自:http://wiki.woodpecker.org.cn/moin/PyWebAppFrameworks

    在比较后,决定使用pylons。

    ———————我是pangpang分隔线———————

    相关链接:

    Python  http://www.python.org/

    Python wiki  http://wiki.python.org/moin/FrontPage

    Python Tutorial  http://docs.python.org/tutorial/index.html

    Learn Python in 10 minutes http://www.poromenos.org/tutorials/python

    pylons  0.9.7  http://www.pylonshq.com

    django  1.0.2  http://www.djangoproject.com/

    web2py  1.5.7  http://mdp.cti.depaul.edu/

    quixote  2.6  http://quixote.ca/

    [C#]从零开始学习ASP.NET MVC(三) Controller/Action 深入解析与应用

    mikel阅读(896)

    一.摘要

    一个Url请求经过了Routing处理后会调用Controller的Action方法. 中间的过程是怎样的? Action方法中返回ActionResult对象后,如何到达View的? 本文将讲解Controller的基本用法,  深入分析Controller的运行机制, 并且提供了创建所有类型Action的代码. 值得学习ASP.NET MVC时参考.

    二.承上启下

    在上一篇文章中, 我已经学会了如何使用Routing获取Controller和Action, 随后的程序会调用Controller中的Action方法.

    每个Action方法都要返回一个ActionResult对象. 一个Action会将数据传递给View,如图:

    image

    三.Controller与Action的作用

    1.职责

    Controller负责将获取Model数据并将Model传递给View对象.通知View对象显示.

    2.ASP.NET MVC中的Controller和Action

    ASP.NET MVC中, 一个Controller可以包含多个Action. 每一个Action都是一个方法, 返回一个ActionResult实例.

    ActionResult类包括ExecuteResult方法, 当ActionResult对象返回后会执行此方法.

    下面分层次的总结Controller 处理流程:

    1. 页面处理流程

    发送请求 –> UrlRoutingModule捕获请求 –> MvcRouteHandler.GetHttpHandler() –> MvcHandler.ProcessRequest()

    2.MvcHandler.ProcessRequest() 处理流程:

    使用工厂方法获取具体的Controller –> Controller.Execute() –> 释放Controller对象

    3.Controller.Execute() 处理流程

    获取Action –> 调用Action方法获取返回的ActionResult –> 调用ActionResult.ExecuteResult() 方法

    4.ActionResult.ExecuteResult() 处理流程

    获取IView对象-> 根据IView对象中的页面路径获取Page类-> 调用IView.RenderView() 方法(内部调用Page.RenderView方法)

    通过对MVC源代码的分析,我们了解到Controller对象的职责是传递数据,获取View对象(实现了IView接口的类),通知View对象显示.

    View对象的作用是显示.虽然显示的方法RenderView()是由Controller调用的,但是Controller仅仅是一个"指挥官"的作用, 具体的显示逻辑仍然在View对象中.

    需 要注意IView接口与具体的ViewPage之间的联系.在Controller和View之间还存在着IView对象.对于ASP.NET程序提供了 WebFormView对象实现了IView接口.WebFormView负责根据虚拟目录获取具体的Page类,然后调用 Page.RenderView().

    四.ActionResult解析

    通过上面的流程,我们知道了ActionResult对象在整个流程中的作用.ActionResult是一个抽象类, 在Action中返回的都是其派生类.下面是我整理的RC2版本中提供的ActionResult派生类:

    类名 抽象类 父类 功能
    ContentResult     根据内容的类型和编码,数据内容.
    EmptyResult     空方法.
    FileResult abstract   写入文件内容,具体的写入方式在派生类中.
    FileContentResult   FileResult 通过 文件byte[] 写入文件.
    FilePathResult   FileResult 通过 文件路径 写入文件.
    FileStreamResult   FileResult 通过 文件Stream 写入文件.
    HttpUnauthorizedResult     抛出401错误
    JavaScriptResult     返回JavaScript文件
    JsonResult     返回Json格式的数据
    RedirectResult     使用Response.Redirect重定向页面
    RedirectToRouteResult     根据Route规则重定向页面
    ViewResultBase abstract   调用IView.Render()
    PartialViewResult   ViewResultBase 调用父类ViewResultBase 的ExecuteResult方法.
    重写了父类的FindView方法.
    寻找用户控件.ascx文件
    ViewResult   ViewResultBase 调用父类ViewResultBase 的ExecuteResult方法.
    重写了父类的FindView方法.
    寻找页面.aspx文件

    目前ASP.NET MVC还没有提供官方的ActionResult列表.上面的列表是我在源代码中分析得出的.有些解释的可能不够清楚,请谅解.

    下面我将列举各个ActionResult的实例.

    五.实例应用

    1.添加Controller

    安装了ASP.NET MVC后, 在项目上点击右键会找到添加Controller项:

    image

    2.添加Action

    下面这个类提供了返回各种类型的ActionResult的Action实例:

    public class DemoController : Controller
    {
    /// <summary>
    /// http://localhost:1847/Demo/ContentResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult ContentResultDemo()
    {
    string contentString = "ContextResultDemo!";
    return Content(contentString);
    }
    /// <summary>
    /// http://localhost:1847/Demo/EmptyResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult EmptyResultDemo()
    {
    return  new EmptyResult();
    }
    /// <summary>
    /// http://localhost:1847/Demo/FileContentResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult FileContentResultDemo()
    {
    FileStream fs = new FileStream(Server.MapPath(@"/resource/Images/1.gif"), FileMode.Open, FileAccess.Read);
    byte[] buffer = new byte[Convert.ToInt32(fs.Length)];
    fs.Read(buffer, 0, Convert.ToInt32(fs.Length) );
    return File(buffer, @"image/gif");
    }
    /// <summary>
    /// http://localhost:1847/Demo/FilePathResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult FilePathResultDemo()
    {
    //可以将一个jpg格式的图像输出为gif格式
    return File(Server.MapPath(@"/resource/Images/2.jpg"), @"image/gif");
    }
    /// <summary>
    /// http://localhost:1847/Demo/FileStreamResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult FileStreamResultDemo()
    {
    FileStream fs = new FileStream(Server.MapPath(@"/resource/Images/1.gif"), FileMode.Open, FileAccess.Read);
    return File(fs, @"image/gif");
    }
    /// <summary>
    /// http://localhost:1847/Demo/HttpUnauthorizedResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult HttpUnauthorizedResultDemo()
    {
    return new HttpUnauthorizedResult();
    }
    /// <summary>
    /// http://localhost:1847/Demo/JavaScriptResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult JavaScriptResultDemo()
    {
    return JavaScript(@"alert(""Test JavaScriptResultDemo!"")");
    }
    /// <summary>
    /// http://localhost:1847/Demo/JsonResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult JsonResultDemo()
    {
    var tempObj = new { Controller = "DemoController", Action = "JsonResultDemo" };
    return Json(tempObj);
    }
    /// <summary>
    /// http://localhost:1847/Demo/RedirectResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult RedirectResultDemo()
    {
    return Redirect(@"http://localhost:1847/Demo/ContentResultDemo");
    }
    /// <summary>
    /// http://localhost:1847/Demo/RedirectToRouteResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult RedirectToRouteResultDemo()
    {
    return RedirectToAction(@"FileStreamResultDemo");
    }
    /// <summary>
    /// http://localhost:1847/Demo/PartialViewResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult PartialViewResultDemo()
    {
    return PartialView();
    }
    /// <summary>
    /// http://localhost:1847/Demo/RedirectToRouteResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult ViewResultDemo()
    {
    //如果没有传入View名称, 默认寻找与Action名称相同的View页面.
    return View();
    }
    }

    在文章最后提供有完整实例代码下载.

    六.Controller 深入分析

    在研究Controller/Action的流程过程中, 发现了ASP.NET MVC一些问题.

    1.Routing组件与MVC框架的结合

    Routing组件和ASP.NET MVC并不是一个项目, 在ASP.NET MVC中仅仅是使用了Routing组件, 在源代码中是通过dll的方式引用的.Routing组件已经包含在.net framework 3.5 sp1中了.而ASP.NET MVC还未出正式版.

    那么ASP.NET MVC是如何应用Routing组件的呢?

    Routing组件获取了Url中的数据后, 会将数据保存在一个 RouteData 对象中.并将请求传递给一个实现了IRouteHandler接口的对象. 在Asp.net MVC中提供的MvcRouteHandler类实现了此接口, Routing 将请求传递给MvcRouteHandler的GetHttpHandler方法.下面是源代码:

    IRouteHandler接口:

        public interface IRouteHandler
    {
    IHttpHandler GetHttpHandler(RequestContext requestContext);
    }

     

    MvcRouteHandler类:

        public class MvcRouteHandler : IRouteHandler {
    protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) {
    return new MvcHandler(requestContext);
    }
    #region IRouteHandler Members
    IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext) {
    return GetHttpHandler(requestContext);
    }
    #endregion
    }

     

    曾经我认为IRouteHandler是多余的, 用IHttpHandler就够了. 现在知道了为何要定义这个接口. 主要是为了传递RouteData对象.GetHttpHandler方法需要一个RequestContext 对象.RequestContext 是 System.Web.Routing程序集中的类, 里面除了处理请求需要的HttpContextBase对象,还包括了一个RouteData对象.

    RequestContext类:

        public class RequestContext
    {
    public RequestContext(HttpContextBase httpContext, RouteData routeData);
    public HttpContextBase HttpContext { get; }
    public RouteData RouteData { get; }
    }

    Routing组件在Web.Config中注册了一个HttpModule: System.Web.Routing.UrlRoutingModule, 而不是HttpHandler:

    <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

     

    可惜看不到这个类的源代码. 所有请求最后都是要传递给IHttpHandler对象处理, 主要的工作是编译页面, 所以我猜测这个Module将请求截获后通过IRouteHandler接口对象获取一个HttpHandler, 然后将处理移交给获取到的HttpHandler.

     

    ASP.NET MVC 中实现了IHttpHandler接口的类是MvcHandler, MvcRouteHandler.GetHttpHandler方法就是返回一个MvcHandler对象. MvcHandler类的构造函数需要传入一个RequestContext对象. 实现的IHttpHandler接口方法处理过程中都需要依赖这个对象.

    但是微软在这里的处理有一些不足. MvcHandler虽然实现了IHttpHandler接口但是不能被当作IHttpHandler接口使用. 因为IHttpHandler中没有定义RequestContext属性, 如果一个MvcHandler对象此属性没有赋值则会出错, 也没有将默认的无参数构造函数设置为private, 所以理论上可以很随意的实例化一个MvcHandler而不为其RequestContext属性赋值.

    IRouteHandler想实现的语意是: 返回一个具有RequestContext属性的IHttpHandler对象.

    但是最后的实现结果是: 提供"返回IHttpHandler对象"的方法,  此方法接收RequestContext对象参数.

    还需要注意ControllerContext类. 在Controller的处理过程中使用此对象作为保存上下文数据的容器.下面是这几个类的包含关系:

    image

    可以看到在ControllerContext中包含了RequestContext对象,但是又将RequestContext对象中的两个属性 提取到自己的类中.如果仅仅是为了使用方便而这么做, 个人认为不是一个好的设计.数据对象的存储职责也应该明确,使用ControllerContext.RequestContext.RouteData 的方式更容易被人理解.

    PS:这种方式类似于方法内联.对于属性JIT为了效率会帮助我们做内联.而仅仅是为了使用方便.

    2.IView 与 View对象的关系

    所以从系统的角度上看, 实现了IView接口的对象才是View.

    但是从实现效果上看, 具体的aspx或者ascx页面才是View.

    当第一次看到IView接口时我认为它应该是"View角色"需要实现的接口. 但是结果并不是这样.

    在我们的系统中View对象应该是aspx或者ascx文件. 而且并不是所有的ActionResult都需要找到aspx或者ascx文件, 事实上只有PartialViewResult 和 ViewResult 才会去寻找View对象.其他的ActionResult要么是返回文件, 要么是跳转等等.

    那么两者的关系到底是怎样的? 其实其中的过程需要牵扯到这几个接口和类:

    IViewEngine, ViewEngineResult, ViewEngineCollection

    ViewEngine是View引擎, ViewEngineCollection是一个引擎集合,里面保存了各种寻找View的引擎.但是在目前的源代码中只有 WebFormViewEngine : VirtualPathProviderViewEngine : IViewEngine

    这一系列WebForm使用的引擎.引擎的作用有两个:

    1.寻找Page/用户控件的路径

    2.根据路径创建IView对象.也就是根据页面的物理文件创建IView接口对象.

    而且目前实现了IView接口的对象也只有一个:

    WebFormView

    WebFormViewEngine 根据页面路径, 将一个页面地址转化为一个WebFormView对象,也就是一个IView接口对象.

    至此IView接口和Page页面类仍然没有任何关系, IView对象只是保存了页面的物理路径.

    接着在IView的Render事件中,根据物理路径创建了一个页面的object实例,注意看这一段代码:

                object viewInstance = BuildManager.CreateInstanceFromVirtualPath(ViewPath, typeof(object));
    if (viewInstance == null) {
    throw new InvalidOperationException(
    String.Format(
    CultureInfo.CurrentUICulture,
    MvcResources.WebFormViewEngine_ViewCouldNotBeCreated,
    ViewPath));
    }
    ViewPage viewPage = viewInstance as ViewPage;
    if (viewPage != null) {
    RenderViewPage(viewContext, viewPage);
    return;
    }
    ViewUserControl viewUserControl = viewInstance as ViewUserControl;
    if (viewUserControl != null) {
    RenderViewUserControl(viewContext, viewUserControl);
    return;
    }

    viewInstance 就是通过物理路径创建的页面对象.但是他的类型是object, 而且程序尝试将其分别转化为ViewPage对象和ViewUserControl对象.

    我想很多人都看到了这里的设计不足.现在我们只能"约定": 所有的MVC中的页面对象都必须继承自ViewPage或者ViewUserControl类, 否则程序就会出错.产生这种不足的原因就是IView接口和ViewPage没有任何的耦合性, 完全是硬编码进去的.

    为什么不让页面直接实现IView接口? 然后尝试将页面转化为IView接口对象, 而不是ViewPage, 这样才是好的设计. 其实微软知道什么是好的设计, 我猜测他们遇到的困难是Page对象和IView接口的冲突. 因为两者都需要Render. 如果在IView中定义自己的Render名称, 那就意味着ASP.NET MVC开发小组要自己处理页面的显示逻辑, 而现在ASP.NET WebForm模式下面的页面显示引擎又不能复用, 重新开发自己的一套显示引擎成本又太大, 才出此下策.

    以上只是猜测.这种设计的缺陷虽然可以接受, 但是真的是让我好几天陷入了看不懂代码的痛苦之中.还好, 现在可以解脱了.

     

    七.如何在MVC项目中使用MVC源代码项目

    另外在为了跟踪实现过程, 我将ASP.NET MVC的源代码项目添加到了实例项目中, 其中有一些需要注意的地方:

    1. 将实例项目中的System.Web.Mvc引用删除, 改成项目引用.

    2. 需要在Web.Config中注释掉程序集引用:

            <compilation debug="true">
    <assemblies>
    <add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
    <add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
    <add assembly="System.Web.Abstractions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
    <add assembly="System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
    <!-- <add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>-->
                    <add assembly="System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
    <add assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
    <add assembly="System.Data.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
    </assemblies>
    </compilation>

    注释掉的程序集存在于GAC中, 但是我们现在不希望使用GAC中的程序集, 而是引用项目.

    3. 将View目录下的Web.Config中的所有System.Web.Mvc相关的 PublicKeyToken 都修改为 null:

        <pages
    validateRequest="false"
    pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
    pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
    userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
    <controls>
    <add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" namespace="System.Web.Mvc" tagPrefix="mvc" />
    </controls>
    </pages>

    八.总结

    首先很抱歉在本系列文章开篇时承诺的每日一篇仅仅坚持了2天.具体原因就不解释了.这篇文章的出炉历时半个月, 并且经历了ASP.NET MVC版本从RC到RC2的演变. 在查看MVC源代码上花费了大量的时间, 希望付出的努力能够为大家研究学习ASP.NET MVC带来帮助. 我也会把这一系列的文章写完, 关于ASP.NET MVC还有太多的地方没有学习.

    实例源代码下载地址:

    http://files.cnblogs.com/zhangziqiu/Asp.net-MVC-3-Demo.rar

    [Flex]FMS3系列(二):创建可交互的FMS连接--I can say:Hello World

    mikel阅读(716)

      在做FMS开发中,flash客户端与FMS服务器通信交互数据等是常见的,比如flash客户端需要一播放一个视频,需要获得FMS发向flash端的一条消息等。那么我们要怎么才能实现flash客户端与FMS服务器建立可交互的连接、通信呢?

         本文将以flash客户端于FMS服务器通信为核心,以经典的"Hello World"示例详细介绍flash客户端于FMS服务器通信的实现。要实现两端通信,在客户端和服务器端都需要编码,一边发起通信请求和接收通信响应信 息(flash客户端),一端则提供接收请求进行业务处理等(FMS服务器端)。

         首先从flash客户端入手,本文的实例非常简单,要实现的功能就是flash客户端向FMS服务器端发起请求,调用FMS服务器上的一个方法,然后将FMS服务器上的方法返回值输出到控制台。

         flash端的开发可以有两种方式实现:Flash和Flex。本文会将这两种方式的实现都给出实例。首先看看Flash里的实现。
         启动Flash CS开发环境,新建立ActionScript 3.0的Flash文件,如下图:
                  

         然后在上面新建立的ActionScript 3.0的Flash文件上,按F9进入动作面板(输入程序代码的地方,当然也可以将代码封装到单独的类文件里),如下图:             

          上图中已经将通过Flash开发连接到FMS服务器的代码全部贴出,代码很简单。NetConnection这个类在 Flash Player 和 Flash Media Server 应用程序之间或者 Flash Player 和运行 Flash Remoting 的应用程序服务器之间创建双向连接。

      通过NetConnection建立于FMS服务器的连接,然后使用NetConnection类的公共方法connect()通过RTMP协 议连接到指定的FMS服务器上指定的应用,如上图示为连接的FMS服务器上名为的“HelloWorld”的这个应用。如果对NetConnection 还不熟悉的朋友请先阅读下我的这系列文章的第一篇:《FMS3系列(一):第一个FMS程序,连接到FMS服务器(Flash/Flex两种实现) 》,随后通过调用call()方法调用FMS服务器上的方法,这里通过调用服务器上提供的sayHelloWorld()方法。

         Responder 类提供了一个对象,该对象在 NetConnection.call() 中使用以处理来自与特定操作成功或失败相关的服务器的返回值。详细见构造Responder对象的时候构造方法的参数,一个方法处理操作成功的逻辑,一个 方法处理操作失败的逻辑。
         OK,完成了上面的flash客户端的开发现在就只差FMS服务器端的开发了,FMS服务器理需要有一个通信文件来负责于flash客户端的连 接交互,通常情况下就是建立一个main.asc的通信文件。本文中的通信文件的程序代码非常简单,就是接受客户端的连接,然后提供一个客户端调用的方 法。完整代码入下:

    1 application.onConnect = function(client)
    2 {
    3     client.sayHelloWorld=function(str)
    4     {
    5         return "I can say:Hello "+ str;
    6     }
    7       this.acceptConnection(client);
    8 }

     

         通过上面的客户端和FMS服务器的开发,现在这样可以按Ctrl+Enter测试了,看看我们的flash小程序是否能够成功的连接到 FMS服务器上指定的HelloWorld,并成功调用服务器端指定的方法呢?打开FMS管理控制台可以看到如下截图效果。OK,我们的小程序已经成功的 连接到了FMS服务器上指定的应用(HelloWorld):

              
         下面是测试输出结果截图:
              

      上面的实现是直接将代码写在Flash中,我们也可以将代码提取出来形成ActionScript文件(类),只要该类文件继承于显示对象,通过Flash CS3的新特性设置舞台文档类就可以调用了,下面是提取为ActionScript类的编程实现:

     1 package
     2 {
     3     import flash.net.*;
     4     import flash.events.*;
     5     import flash.display.*;
     6     
     7     public class ClientCallServer extends Sprite
     8     {
     9         private var nc:NetConnection;
    10         private var rs:Responder;
    11         public function ClientCallServer():void
    12         {
    13             nc=new NetConnection();
    14             rs=new Responder(onSuccess,onFailed);
    15             nc.connect("rtmp://localhost/HelloWorld");
    16             nc.client=this;
    17             nc.call("sayHelloWorld",rs,"World");
    18         }
    19         
    20         private function onSuccess(rs:Object):void
    21         {
    22             trace(rs.toString());
    23         }
    24         
    25         private function onFailed(rs:Object):void
    26         {
    27             trace(rs.description());
    28         }
    29     }
    30 }

     

      或许有的朋友已经习惯了使用Flex开发,喜欢用拖拽控件的方式来完成一些常用的功能,其实在Flex下开发和Flash差别不是很大,其实在编码层次上是没什么区别的,不同的只是界面的呈现方式不一样。

     1 private var nc:NetConnection;
     2 private var fmsServer:String="rtmp://localhost/HelloWorld";
     3 private var rs:Responder;
     4 
     5 private function initApp():void
     6 {
     7     nc = new NetConnection();
     8     nc.connect(fmsServer);
     9     nc.client=this;
    10 }

      在Flex下开发,建立好mxml后可以直接在其内部的<mx:Script>组件里编写ActionScript代码,如上定义 了连接FMS服务器的NetConnection类的实例等。还定义了一个在Flex应用初始化的使用调用的方法initApp(),用来 完成flash客户端与FMS服务器的连接。

    1 private function onClick():void
    2 {
    3     rs = new Responder(onSuccess,onFailed);
    4     nc.call("sayHelloWorld",rs,"World");
    5     nc.addEventListener(NetStatusEvent.NET_STATUS,onStatus);
    6     nc.addEventListener(AsyncErrorEvent.ASYNC_ERROR,onAsyncHandler);
    7 }

    同Flash中开发一样,给Responder指定了成功和失败后的处理函数,详细如下:

    /**
     * 通信成功并返回结果时被调度
     
    */
    private function onSuccess(result:Object):void
    {

        Alert.show(result.toString(),"调用结果");

    }

    /**
     * 通信失败并返回结果时被调度
     
    */
    private function onFailed(result:Object):void
    {
        Alert.show(result.description);
        Alert.show(result.code);
    }

     

      如上就完成了Flex中调用FMS服务器并调用FMS上所提供的方法,服务器端的程序和前面 Flash中的一样。到此我们只需要调用onClick()方法就可以测试了,通过一个按钮组件来调用,如下:

    <mx:Button x="91" y="219" label="Call" click="onClick()"/>

    完整的Flex代码

     

      在平时的开发当中,要与FMS服务器创建可交互的连接有很多种方式,本文只是简单的介绍了最基本的一种调用,希望本文对想学FMS开发的朋友有所帮助。

     

    版权说明

      本文属原创文章,欢迎转载,其版权归作者和博客园共有。  

      作      者:Beniao

     文章出处:http://beniao.cnblogs.com/  或  http://www.cnblogs.com/

    推荐文章:http://www.cnblogs.com/aierong/archive/2009/01/15/fms3_main.html

    [JQuery]模拟开心网消息提示

    mikel阅读(760)

    <script type="text/JavaScript">
    $(document).ready(
    function()
    {
        setInterval(shine,
    1000);
    }
    );
    var shine=function()
    {
        
    //document.title = new Date().getTime();
        document.title = (document.title=='你有新的短消息'?'':'你有新的短消息')
    }
    </script>