结构_结构体 - CSDN
  • C语言中的结构体(struct)

    万次阅读 多人点赞 2016-02-15 20:23:39
    C语言中,结构体类型属于一种构造类型(其他的构造类型还有:数组类型,联合类型)。本文主要介绍关于结构体以下几部分。 1、概念为什么要有结构体?因为在实际问题中,一组数据往往有很多种不同的数据类型。...

    C语言中,结构体类型属于一种构造类型(其他的构造类型还有:数组类型,联合类型)。本文主要介绍关于结构体以下几部分。
    这里写图片描述

    1、概念

    为什么要有结构体?

    因为在实际问题中,一组数据往往有很多种不同的数据类型。例如,登记学生的信息,可能需要用到 char型的姓名,int型或 char型的学号,int型的年龄,char型的性别,float型的成绩。又例如,对于记录一本书,需要 char型的书名,char型的作者名,float型的价格。在这些情况下,使用简单的基本数据类型甚至是数组都是很困难的。而结构体(类似Pascal中的“记录”),则可以有效的解决这个问题。
    结构体本质上还是一种数据类型,但它可以包括若干个“成员”,每个成员的类型可以相同也可以不同,也可以是基本数据类型或者又是一个构造类型。
    结构体的优点:结构体不仅可以记录不同类型的数据,而且使得数据结构是“高内聚,低耦合”的,更利于程序的阅读理解和移植,而且结构体的存储方式可以提高CPU对内存的访问速度。

    结构声明(structure declaration)

    结构声明(也见有称做定义一个结构体)是描述结构如何组合的主要方法。
    一般形式是:
    struct 结构名{
    成员列表
    };
    struct关键词表示接下来是一个结构。
    如声明一个学生的结构:

    struct Student{         //声明结构体
        char name[20];      //姓名
        int num;            //学号
        float score;        //成绩
    };

    上面的声明描述了一个包含三个不同类型的成员的结构,但它还没创建一个实际的数据对象,类似C++中的模板。每个成员变量都用自己的声明来描述,以分号结束。花括号之后的分号表示结构声明结束。结构声明可以放在函数外(此时为全局结构体,类似全局变量,在它之后声明的所有函数都可以使用),也可以放在函数内(此时为局部结构体,类似局部变量,只能放在该函数内使用,如果与全局结构体同名,则会暂时屏蔽全局结构体)。

    要定义结构变量,则一般形式是:
    struct 结构体名 结构体变量名;
    如:

    struct Student stu1;    //定义结构体变量

    这里写图片描述
    1)、结构体变量的定义可以放在结构体的声明之后:

    struct Student{         //声明结构体
        char name[20];      //姓名
        int num;            //学号
        float score;        //成绩
    };
    struct Student stu1;    //定义结构体变量

    2)、结构体变量的定义也可以与结构体的声明同时,这样就简化了代码:

    struct Student{        
        char name[20];       
        int num;             
        float score;         
    }stu1;                  //在定义之后跟变量名

    3)、还可以使用匿名结构体来定义结构体变量:

    struct {                //没有结构名
        char name[20];       
        int num;            
        float score;         
    }stu1;  
    

    但要注意的是这样的方式虽然简单,但不能再次定义新的结构体变量了。

    访问结构成员

    虽然结构类似一个数组,只是数组元素的数据类型是相同的,而结构中元素的数据类型是可以不同的。但结构不能像数组那样使用下标去访问其中的各个元素,而应该用结构成员运算符点(.)。即访问成员的一般形式是:
    结构变量名 . 成员名
    如 stu1 . name 表示学生stu1的姓名。

    但如果结构体中的成员又是一个结构体,如:

    struct Birthday{                //声明结构体 Birthday
        int year;
        int month;
        int day;
    };
    struct Student{                 //声明结构体 Student
        char name[20];              
        int num;                    
        float score;                 
        struct Birthday birthday;   //生日
    }stu1;

    则用 stu1.birthday.year 访问出生的年份。

    结构体变量的初始化

    1)、结构体变量的初始化可以放在定义之后:

    可以对结构体的成员逐个赋值:

    struct Student stu1, stu2;      //定义结构体变量
    strcpy(stu1.name, "Jack");
    stu1.num = 18;
    stu1.score = 90.5;

    注意:不能直接给数组名赋值,因为数组名是一个常量。如:

    stu1.name = "Jack"; //…main.c:26:15: Array type 'char [20]' is not assignable

    或者可以对结构体进行整体赋值:

    stu2 = (struct Student){"Tom", 15, 88.0};

    注意:此时要进行强制类型转换,因为数组赋值也是使用{},不转换的话系统无法区分!如:

    int arr[5] = {1, 2, 3, 4, 5};       //数组的初始化
    stu2 = {"Tom", 15, 88.0};           //…main.c:31:12: Expected expression

    2)、结构体变量的初始化也可以与定义同时:

    struct Student{                 //声明结构体 Student
        char name[20];               
        int num;                    
        float score;                 
    }stu = {"Mike", 15, 91};        //注意初始化值的类型和顺序要与结构体声明时成员的类型和顺序一致

    此时不需要强制类型转换
    也可以部分初始化:

    struct Student stu4 = {.name = "Lisa"};

    也可以按照任意的顺序使用指定初始化项目:

        struct Student st = { .name = "Smith",
                              .score = 90.5,
                              .num = 18 };

    3)、可以用一个已经存在的结构体去初始化一个新的相同类型的结构体变量,是整体的拷贝(每一个成员都一一赋值给新的结构体变量),而不是地址赋值。如:

    stu3 = stu1;
    printf("stu1 addr: %p\nstu3 addr: %p\n", &stu1, &stu3);
    printf("stu1.num: %d\nstu3.num: %d\n", stu1.num, stu3.num);
    printf("stu1.num addr: %p\nstu3.num addr: %p\n", &stu1.num, &stu3.num);
    //输出结果:
    stu1 addr: 0x10000104c
    stu3 addr: 0x100001084
    stu1.num: 18
    stu3.num: 18
    stu1.num addr: 0x100001060
    stu3.num addr: 0x100001098

    2、结构体变量的存储原理

    1)结构体数据成员对齐的意义

    内存是以字节为单位编号的,某些硬件平台对特定类型的数据的内存要求从特定的地址开始,如果数据的存放不符合其平台的要求,就会影响到访问效率。所以在内存中各类型的数据按照一定的规则在内存中存放,就是对齐问题。而结构体所占用的内存空间就是每个成员对齐后存放时所占用的字节数之和。
    计算机系统对基本数据类型的数据在内存中存放的限制是:这些数据的起始地址的值要求是某个数K的倍数,这就是内存对齐,而这个数 K 就是该数据类型的对齐模数(alignment modulus)。这样做的目的是为了简化处理器与内存之间传输系统的设计,并且能提升读取数据的速度。
    结构体对齐不仅包括其各成员的内存对齐(即相对结构体的起始位置),还包括结构体的总长度。

    2)结构体大小的计算方法和步骤
    i. 将结构体内所有数据成员的长度值相加,记为 sum_a ;
    ii. 将各数据成员为了内存对齐,按各自对齐模数而填充的字节数累加到sum_a上,记为sum_b。
    对齐模数是 #pragma pack 指定的数值与该数据成员自身长度相比较得到的数值较小者。该数据相对起始位置应该是对齐模数的整数倍。
    iii. 将和 sum_b 向结构体模数对齐。
    该模数则是 #pragma pack 指定的数值与结构体内最大的基本数据类型成员长度相比较得到的数值较小者。结构体的长度应该是该模数的整数倍。

    数据类型自身对齐:
    这里写图片描述
    所谓“对齐在N上”,是指“存放的起始位置是%N = 0”.

    3)在没有#pragma pack宏的情况下:
    例子1:

    这里写图片描述

    内存分配状态为:

    这里写图片描述

    对于结构体的第一个成员 a,起始位置为0x…38 (也为 4 的倍数),所占内存为 0x…38 ~ 0x…3b,共占4个字节;
    对于结构体的第二个成员 b,自身长度为1,对齐模数也为1,所以内存分配可以紧接着a的结尾位置 0x…3b,所以起始位置为 0x…3c,共占1个字节;
    对于结构体的第三个成员 c,自身长度为2,对齐模数也为2,所以起始位置距离a 的起始位置应该是2的倍数,所以 0x…3d处只距离5,不符合要求,所以空着,继续往下找,而在 0x…3e处满足要求,所以可以作为c的起始位置,共占2个字节;
    此时3个成员及其中间空着的位置所占内存单元总和为8,而结构体内最大的基本数据成员是 a,其长度为4,所以结构体模数为 4,而8是4的倍数,满足要求,故不再加内存。

    例子2:
    与例子1相比,三个类型的声明顺序变了:

    这里写图片描述

    内存分配状态为:
    这里写图片描述

    要注意的是,对 a而言,对齐模数为 4,所以当 b的起始位置在0x7f…830之后,0x7f…831、0x7f…832、0x7f…833的位置距离起始位置0x7f…830分别是1,2,3,都不是 4 的倍数,所以那三个位置都空着,直到0x7f…834才满足要求,所以作为 a 的起始位置。当最后 一个成员 c 占的内存末尾在0x7f…839时,所有数据成员及其之间的空位所占内存单元总和为10,而结构体模数为4,10不是4的倍数,所以要扩大到12才满足要求,此时又多了2个空位置,就是0x7f…83a和0x7f…83b。

    例子3:
    当结构体中有数组时:

    这里写图片描述

    内存分配状态为:
    这里写图片描述

    亦即相同类型数据的数组之间多分配的空间会被相邻数组的元素所占用。

    4)在存在#pragma pack宏的情况下:
    方法类似,只是模数可能会按上面说的规则而有所变化。

    这里写图片描述

    内存分配状态为:
    这里写图片描述

    注意,当没有#pragma pack(2)时,成员a要确定自身的q起始位置,是以自身的长度4为对齐模数,但有了#pragma pack(2),则将括号里的2与a的长度4比较,2为较小者,所以以2为a的对齐模数,即地址从0x7f…839往下找到0x7f…83a时,已经距离结构体的起始位置0x7f…838为2,是2的倍数,满足要求(虽然不是4的倍数),可以作为a的起始位置。而最后,所有数据成员及其之间的空位所占内存单元总和为8,因为2和4(结构体中最大的数据成员长度)的较小者为2,而8是2的倍数,所以刚好满足要求,不用在分配空位置,所以结构体总长度即为8。

    3、结构体数组

    结构类型作为一种数据类型,也可以像基本数据类型那样,作为数组的元素的类型。元素属于结构类型的数组成为结构型数组。如开头提出的问题,生活中经常用到结构数组来表示具有相同数据结构的一个群体,如一个班的学生的信息,一个书店或图书馆的书籍信息等。

    1)结构数组定义
    一般格式:
    struct 结构名 {
    成员列表
    } 数组名[数组长度];
    如:

    struct Student{                 //声明结构体 Student
        char name[20];
        int num;
        float score;
    }stu[5];                        //定义一个结构结构数组stu,共有5个元素

    2)结构数组的初始化

    定义结构数组的同时进行初始化

    struct Student stu[2] = {{"Mike", 27, 91},{"Tom", 15, 88.0}};  

    先定义,后初始化
    整体赋值:

    stu[2] = (struct Student){"Jack", 12, 85.0};

    或者将结构体变量的成员逐个赋值:

    strcpy(stu[3].name, "Smith");
    stu[3].num = 18;
    stu[3].score = 90.5;

    输出结构体:

    //结构体数组的长度:
    int length = sizeof(stu) / sizeof(struct Student);
    //逐个输出结构数组的元素
    for (int i = 0; i < length; i++) {
        printf("姓名:%s  学号:%d  成绩:%f \n", stu[i].name, stu[i].num, stu[i].score);
    }

    //输出结果:
    这里写图片描述

    在这个例子中,要注意的是:
    这里写图片描述

    4、结构与指针

    当一个指针变量用来指向了一个结构变量,这个指针就成了结构指针变量。
    结构指针变量中的值是所指向的结构变量的首地址。可以通过指针来访问结构变量。

    1)定义结构指针变量的一般形式:
    struct 结构名 * 结构指针变量名
    如:

    struct Student *pstu;       //定义了一个指针变量,它只能指向Student结构体类型的结构体变量

    结构指针变量的定义也可以与结构体的定义同时。而且它必须先赋值后使用。
    数组名表示的是数组的首地址,可以直接赋值给数组指针。但结构变量名只是表示整个结构体变量,不表示结构体变量的首地址,所以不能直接赋值给结构指针变量,而应该使用 & 运算符把结构变量的的地址赋值给结构指针变量。即:

    这里写图片描述

    注意:结构名、结构变量名、结构体指针的区别。

    2)通过结构指针间接访问成员值

    访问的一般形式:
    (*结构指针变量). 成员名 或 结构指针变量 -> 成员名
    如:

    (*pstu).name    
    pstu->name

    注意(pstu)的小括号不能省略,因为成员符“.”优先级为1,取地址符“”优先级为2,去掉括号就相当于*(pstu.name)了。

    5、结构体的嵌套

    1)结构体中的成员可以又是一个结构体,构成结构体的嵌套:

    struct Birthday{                //声明结构体 Birthday
        int year;
        int month;
        int day;
    };
    struct Student{                 //声明结构体 Student
        char name[20];              
        int num;                    
        float score;                 
        struct Birthday birthday;   //生日
    }; 
    

    这里写图片描述
    2)结构体不可以嵌套跟自己类型相同的结构体,但可以嵌套定义自己的指针。如:

    struct Student{                 //声明结构体 Student
        char name[20];
        int num;
        float score;
        struct Student *friend;     //嵌套定义自己的指针
    }

    3)甚至可以多层嵌套:

    struct Time{                    //声明结构体 Time
        int hh;                     //时
        int mm;                     //分
        int ss;                     //秒
    };
    struct Birthday{                //声明结构体 Birthday
        int year;
        int month;
        int day;
        struct Time dateTime        //嵌套结构
    };
    struct Student{                 //声明结构体 Student
        char name[20];
        int num;
        float score;
        struct Birthday birthday;   //嵌套结构
    }
    //定义并初始化
    struct Student stud = {"Jack", 32, 85, {1990, 12, 3, {12, 43, 23}}};
    //访问嵌套结构的成员并输出
    printf("%s 的出生时刻:%d时 \n", stud.name, stud.birthday.dateTime.hh);
    //输出结果:Jack 的出生时刻:12时 

    注意如何初始化和对嵌套结构的成员进行访问。

    6、结构与函数

    结构体的成员可以作为函数的参数,属于值传递(成员是数组的除外)。如:

    struct Student{                 //声明结构体 Student
        char name[20];
        int num;
        float score;
    };
    void printNum(int num){         //定义一个函数,输出学号
        printf("num = %d \n", num);
    }
        struct Student student0 = {"Mike", 27, 91};
        printNum(student0.num);     //调用printNum 函数,以结构成员作函数的参数
    //运行结果:num = 27 

    注意,函数printNum并不知道也不关心实际参数是不是结构成员,它只要求实参是int类型的就可以了。

    结构变量名也可以作为函数的参数传递,如:

    void PrintStu(struct Student student){      //定义 PrintStu 函数,以结构变量作函数的形参
        student.num = 100;                      //修改学号
        printf("PrintStu 修改后:姓名: %s, 学号: %d, 内存地址: %p \n", student.name, student.num, &student);
    }
        struct Student student0 = {"Mike", 27, 91};
        PrintStu(student0);                     //调用 PrintStu 函数,以结构变量名作函数的参数
        printf("           原来:姓名: %s, 学号: %d,  内存地址: %p \n", student0.name, student0.num, &student0);
    

    //输出结果:
    这里写图片描述

    形参和实参的地址不一样,是在函数中创建了一个局部结构体,然后实参对形参进行全部成员的逐个传送,在函数中对局部结构体变量进行修改并不影响原结构体变量。这样传送的时间空间开销都比较大,特别是当成员有数组的时候,程序效率较低。所以可以考虑使用指针:

    void PrintStu2(struct Student *student){      //定义 PrintStu2 函数,以结构指针作函数的形参
        student->num = 100;                       //修改学号
        printf("PrintStu2 修改后:姓名: %s, 学号: %d, 内存地址: %p \n", student->name, student->num, student);
    }
        struct Student student0 = {"Mike", 27, 91};
        PrintStu2(&student0);                     //调用 PrintStu 函数,以结构变量的地址作函数的参数
        printf("           原来:姓名: %s, 学号: %d,  内存地址: %p \n", student0.name, student0.num, &student0);

    //输出结果:
    这里写图片描述

    形参和实参的地址是一样的,所以是地址传递,在 PrintStu2 函数中,student 与&student0 指向同一块内存单元,用指针student修改结构变量会影响原结构变量。

    展开全文
  • C语言的四种程序结构

    万次阅读 2018-01-02 12:13:59
    1、顺序结构 顺序结构的程序设计是最简单的,只要按照解决问题的顺序写出相应的语句就行,它的执行顺序是自上而下,依次执行。例如;a = 3,b = 5,现交换a,b的值,这个问题就好像交换两个杯子水,这当然要用到第...

    1、顺序结构
    顺序结构的程序设计是最简单的,只要按照解决问题的顺序写出相应的语句就行,它的执行顺序是自上而下,依次执行。

    例如;a = 3,b = 5,现交换a,b的值,这个问题就好像交换两个杯子水,这当然要用到第三个杯子,假如第三个杯子是c,那么正确的程序为: c = a; a = b; b = c; 执行结果是a = 5,b = c = 3。

    如果改变其顺序,写成:a = b; c = a; b = c; 则执行结果就变成a = b = c = 5,不能达到预期的目的,这是我们初学者最容易犯这种错误,所以一定要多加注意。

    顺序结构可以独立使用构成一个简单的完整程序,常见的输入、计算,输出三步曲的程序就是顺序结构,例如计算圆的面积,其程序的语句顺序就是输入圆的半径r,计算s = 3.14159*r*r,输出圆的面积s。

    不过大多数情况下顺序结构都是作为程序的一部分,与其它结构一起构成一个复杂的程序,例如分支结构中的复合语句、循环结构中的循环体等

    这里写图片描述

    2、分支结构
    顺序结构的程序虽然能解决计算、输出等问题,但不能做判断再选择。对于要先做判断再选择的问题就要使用分支结构。

    分支结构的执行是依据一定的条件选择执行路径,而不是严格按照语句出现的物理顺序。分支结构的程序设计方法的关键在于构造合适的分支条件和分析程序流程,根据不同的程序流程选择适当的分支语句。

    分支结构适合于带有逻辑或关系比较等条件判断的计算,设计这类程序时往往都要先绘制其程序流程图,然后根据程序流程写出源程序,这样做把程序设计分析与语言分开,使得问题简单化,易于理解。

    程序流程图是根据解题分析所绘制的程序执行流程图。

    这里写图片描述

    学习分支结构不要被分支嵌套所迷惑,只要正确绘制出流程图,弄清各分支所要执行的功能,嵌套结构也就不难了。嵌套只不过是分支中又包括分支语句而已,不是新知识,只要对双分支的理解清楚,分支嵌套是不难的。下面我介绍几种基本的分支结构。

    ①if(条件)
    {
    分支体
    }

    这种分支结构中的分支体可以是一条语句,此时“{ }”可以省略,也可以是多条语句即复合语句。

    它有两条分支路径可选,一是当条件为真,执行分支体,否则跳过分支体,这时分支体就不会执行。如:要计算x的绝对值,根据绝对值定义,我们知道,当x>=0时,其绝对值不变,而x<0时其绝对值是为x的反号,因此程序段为:if(x<0) x=-x;

    ②if(条件)
    {分支1}
    else
    {分支2}

    这里写图片描述

    这是典型的分支结构,如果条件成立,执行分支1,否则执行分支2,分支1和分支2都可以是1条或若干条语句构成。如:求ax^2+bx+c=0的根

    分析:因为当b^2-4ac>=0时,方程有两个实根,否则(b^2-4ac<0)有两个共轭复根。其程序段如下:

    d=b*b-4*a*c; 
    if(d>=0) 
    {x1=(-b+sqrt(d))/2a; 
    x1=(-b-sqrt(d))/2a; 
    printf("x1=%8.4f,x2=%8.4f\n",x1,x2); 
    } 
    else 
    {r=-b/(2*a); 
    i =sqrt(-d)/(2*a); 
    printf("x1=%8.4f+%8.4fi\n"r, i); 
    printf("x2=%8.4f-%8.4fi\n"r,i); 
    } 

    ③嵌套分支语句:其语句格式为:
    if(条件1) {分支1};
    else if(条件2) {分支2}
    else if(条件3) {分支3}
    ……
    else if(条件n) {分支n}
    else {分支n+1}

    嵌套分支语句虽可解决多个入口和出口的问题,但超过3重嵌套后,语句结构变得非常复杂,对于程序的阅读和理解都极为不便,建议嵌套在3重以内,超过3重可以用下面的语句。

    ④switch开关语句:该语句也是多分支选择语句,到底执行哪一块,取决于开关设置,也就是表达式的值与常量表达式相匹配的那一路。

    它不同if…else 语句,它的所有分支都是并列的,程序执行时,由第一分支开始查找,如果相匹配,执行其后的块,接着执行第2分支,第3分支……的块,直到遇到break语句;如果不匹配,查找下一个分支是否匹配。

    这个语句在应用时要特别注意开关条件的合理设置以及break语句的合理应用。

    3、循环结构

    循环结构可以减少源程序重复书写的工作量,用来描述重复执行某段算法的问题,这是程序设计中最能发挥计算机特长的程序结构,C语言中提供四种循环,即goto循环、while循环、do –while循环和for循环。

    四种循环可以用来处理同一问题,一般情况下它们可以互相代替换,但一般不提倡用goto循环,因为强制改变程序的顺序经常会给程序的运行带来不可预料的错误,在学习中我们主要学习while、do…while、for三种循环。

    常用的三种循环结构学习的重点在于弄清它们相同与不同之处,以便在不同场合下使用,这就要清楚三种循环的格式和执行顺序,将每种循环的流程图理解透彻后就会明白如何替换使用。

    如把while循环的例题,用for语句重新编写一个程序,这样能更好地理解它们的作用。特别要注意在循环体内应包含趋于结束的语句(即循环变量值的改变),否则就可能成了一个死循环,这是初学者的一个常见错误。

    这里写图片描述

    在学完这三个循环后,应明确它们的异同点:用while和do…while循环时,循环变量的初始化的操作应在循环体之前,而for循环一般在语句1中进行的;

    while 循环和for循环都是先判断表达式,后执行循环体,而do…while循环是先执行循环体后判断表达式,也就是说do…while的循环体最少被执行一次,而while 循环和for就可能一次都不执行。

    另外还要注意的是这三种循环都可以用break语句跳出循环,用continue语句结束本次循环,而goto语句与if构成的循环,是不能用break和 continue语句进行控制的。

    顺序结构、分支结构和循环结构并不彼此孤立的,在循环中可以有分支、顺序结构,分支中也可以有循环、顺序结构,其实不管哪种结构,我们均可广义的把它们看成一个语句。

    这里写图片描述
    4、模块化程序结构

    C语言的模块化程序结构用函数来实现,即将复杂的C程序分为若干模块,每个模块都编写成一个C函数,然后通过主函数调用函数及函数调用函数来实现一大型问题的C程序编写。

    因此常说:C程序=主函数+子函数。 因此,对函数的定义、调用、值的返回等中要尤其注重理解和应用,并通过上机调试加以巩固。

    展开全文
  • 0.引言 ResNet在2015年被提出,在ImageNet比赛classification任务上获得第一名,因为它“简单与实用”并存,之后很多方法都建立在ResNet50或者ResNet101的基础上完成的,检测,分割,识别等领域都纷纷使用ResNet,...

    0.引言

             ResNet在2015年被提出,在ImageNet比赛classification任务上获得第一名,因为它“简单与实用”并存,之后很多方法都建立在ResNet50或者ResNet101的基础上完成的,检测,分割,识别等领域都纷纷使用ResNet,Alpha zero也使用了ResNet,所以可见ResNet确实很好用。下面我们从实用的角度去看看ResNet。

    1.ResNet意义

            总结:提出了一个identity

            随着网络的加深,出现了训练集准确率下降的现象,我们可以确定这不是由于Overfit过拟合造成的(过拟合的情况训练集应该准确率很高);所以作者针对这个问题提出了一种全新的网络,叫深度残差网络,它允许网络尽可能的加深,其中引入了全新的结构如图1;

    图1 Shortcut Connection

    残差指的是什么?

            其中ResNet提出了两种mapping:一种是identity mapping,指的就是图1中”弯弯的曲线”,另一种residual mapping,指的就是除了”弯弯的曲线“那部分,所以最后的输出是

            identity mapping顾名思义,就是指本身,也就是公式中的x,而residual mapping指的是“差”,也就是y−x,所以残差指的就是F(x)部分。

            为什么ResNet可以解决“随着网络加深,准确率不下降”的问题?

            除了实验证明外:

    表1,Resnet在ImageNet上的结果

            理论上,对于“随着网络加深,准确率下降”的问题,Resnet提供了两种选择方式,也就是identity mapping和residual mapping,如果网络已经到达最优,继续加深网络,residual mapping将被push为0,只剩下identity mapping,这样理论上网络一直处于最优状态了,网络的性能也就不会随着深度增加而降低了

    2.ResNet结构

    它使用了一种连接方式叫做“shortcut connection”,顾名思义,shortcut就是“抄近道”的意思,看下图我们就能大致理解:

    图1 Shortcut Connection

            这是文章里面的图,我们可以看到一个“弯弯的弧线“这个就是所谓的”shortcut connection“,也是文中提到identity mapping,这张图也诠释了ResNet的真谛,当然大家可以放心,真正在使用的ResNet模块并不是这么单一,文章中就提出了两种方式:

    图2 两种ResNet设计

            这两种结构分别针对ResNet34(左图)和ResNet50/101/152(右图),一般称整个结构为一个”building block“。其中右图又称为”bottleneck design”,目的一目了然,就是为了降低参数的数目。看右图,输入是一个3×3×256的特征,第一个步骤用64个1x1的卷积把256维channel降到64维,然后在最后通过1x1卷积恢复到256个channel,整体上用的参数数目:1x1x256x64 + 3x3x64x64 + 1x1x64x256 = 69632,而不使用bottleneck的话参考左图,输入假设是3x3x256,第一步经过256个卷积核3×3×256,第二部再经过256个卷积核3×3×256。所以参数数目: 3x3x256x256x2 = 1179648,差了16.94倍

            对于常规ResNet,可以用于34层或者更少的网络中,对于Bottleneck Design的ResNet通常用于更深的如101这样的网络中,目的是减少计算和参数量(实用目的)

     

            如图1所示,如果F(x)和x的channel个数不同怎么办,因为F(x)和x是按照channel维度相加的,channel不同怎么相加呢?

            针对channel个数是否相同,要分成两种情况考虑,如下图:

    图3 两种Shortcut Connection方式

            如图3所示,我们可以清楚的”实线“和”虚线“两种连接方式,实线的的Connection部分(”第一个粉色矩形和第三个粉色矩形“)都是执行3x3x64的卷积,他们的channel个数一致,所以采用计算方式: y=F(x)+x

           虚线的的Connection部分(”第一个绿色矩形和第三个绿色矩形“)分别是3x3x64和3x3x128的卷积操作,他们的channel个数不同(64和128),所以采用计算方式:

    y=F(x)+W·x   其中W是卷积操作,用来调整x的channel维度的;

    3.ResNet50和ResNet101

            这里把ResNet50和ResNet101特别提出,主要因为它们的出镜率很高,所以需要做特别的说明。给出了它们具体的结构:

    表2,Resnet不同的结构

            首先我们看一下表2,上面一共提出了5中深度的ResNet,分别是18,34,50,101和152,首先看表2最左侧,我们发现所有的网络都分成5部分,分别是:conv1,conv2_x,conv3_x,conv4_x,conv5_x,之后的其他论文也会专门用这个称呼指代ResNet50或者101的每部分。

            拿101-layer那列,我们先看看101-layer是不是真的是101层网络,首先有个输入7x7x64的卷积,然后经过3 + 4 + 23 + 3 = 33个building block,每个block为3层,所以有33 x 3 = 99层,最后有个fc层(用于分类),所以1 + 99 + 1 = 101层,确实有101层网络;

            注:101层网络仅仅指卷积或者全连接层,而激活层或者Pooling层并没有计算在内; 这里我们关注50-layer和101-layer这两列,可以发现,它们唯一的不同在于conv4_x,ResNet50有6个block,而ResNet101有23个block,查了17个block,也就是17 x 3 = 51层。

    4.基于ResNet101的Faster RCNN

    文章中把ResNet101应用在Faster RCNN上取得了更好的结果,结果如下:

    表3,Resnet101 Faster RCNN在Pascal VOC07/12 以及COCO上的结果

    这里有个问题:

            Faster RCNN中RPN和Fast RCNN的共享特征图用的是conv5_x的输出么?

            针对这个问题我们看看实际的基于ResNet101的Faster RCNN的结构图:

    图5 基于ResNet101的Faster RCNN

            图5展示了整个Faster RCNN的架构,其中蓝色的部分为ResNet101,可以发现conv4_x的最后的输出为RPN和RoI Pooling共享的部分,而conv5_x(共9层网络)都作用于RoI Pooling之后的一堆特征图(14 x 14 x 1024),特征图的大小维度也刚好符合原本的ResNet101中conv5_x的输入;

            最后一定要记得最后要接一个average pooling,得到2048维特征,分别用于分类和框回归。

    5.到底哪种是ResNet的结构?

            ResNet残差网络,想必大家一定很熟悉了,那么先考考大家,下面(1)-(5)的结构哪个是我们常用的ResNet结构?

            其中weight指conv层,BN指Batch Normalization层,ReLU指激活层,addition指相加;根据ResNet的描述,似乎以上五组都符合,那么2016年ResNet原文是哪一个结构呢?以及其他四组结构也都work么?我们不禁有了这两个疑问,伴随着疑问我们一一揭开谜题;

            针对第一个问题,ResNet原文中使用的结构是(1)

         (1)的特点有两个:1)BN和ReLU在weight的后面;2)最后的ReLU在addition的后面;

           对于特点1),属于常规范畴,我们平时也都这个顺序:Conv->BN->ReLU;

           对于特点2),为什么ReLU放在addition后面呢?按照常规,不是应该是图(3)这种么,那么我们接下来引出的问题就是:

             图(3)的结构work么?

            对于每个图右侧部分我们称作“residual”分支,左侧部分我们称作“identity”分支,如果ReLU作为“residual”分支的结尾,我们不难发现“residual”分支的结果永远非负,这样前向的时候输入会单调递增,从而会影响特征的表达能力,所以我们希望“residual”分支的结果应该在(-, +这点也是我们以后设计网络时所要注意的

            对于图(3)不OK的情况,那如果把BN也挪到addition后面呢?如图(2),同时也保证了“residual”分支的取值范围?

           这里BN改变了“identity”分支的分布,影响了信息的传递,在训练的时候会阻碍loss的下降;这里大家肯定又有个问题:

    为什么“identity”分支发生变化,会影响信息传递,从而影响训练呢?

    这里简单回顾ResNet的公式:

    在分析图(4)和图(5)之前,我们引出一个概念:”Post-activation激活之后和”Pre-activation激活之前,其中Post和Pre的概念是相对于weight(conv)层来说的,那么我们不难发现,图(1), (2), (3)都是”Post-activation”图(4), (5)都是”Pre-activation”,那么两种模式哪一个更好呢?这里我们就用实验结果说话。

    上图是5种结构在Cifar10上的实验结果,一共实验了两种网络ResNet110和ResNet164。

    从实验结果上,我们可以发现图(4)的结构与ResNet原结构伯仲之间,稍稍逊色,然而图(5)的结构却好于ResNet原结构。图5的结构好的原因在于两点:1)反向传播基本符合假设,信息传递无阻碍;2)BN层作为pre-activation,起到了正则化的作用;

    最后我们通常把图5的结构称作ResNetV2,这里我们给出ResNetV1和ResNetV2结构:

    参考文章:

    https://blog.csdn.net/lanran2/article/details/79057994

    https://blog.csdn.net/lanran2/article/details/80247515

    展开全文
  • 软件体系结构

    千次阅读 2018-12-18 20:38:34
    原文作者:Petter Liu ... 架构风格是一组原则。你可以把它看成是一组为系统家族提供抽象框架的粗粒度模式。... 常见的软件体系结构风格涉及: 设计词汇表是什么?或者构件和连接器的类型是什么? 可容许...

    原文作者:Petter Liu

    出处:http://www.cnblogs.com/wintersun/p/4869344.html

    架构风格是一组原则。你可以把它看成是一组为系统家族提供抽象框架的粗粒度模式。架构风格能改进分块,还能为频繁出现的问题提供解决方案,以此促进设计重用。

    常见的软件体系结构风格涉及:

    • 设计词汇表是什么?或者构件和连接器的类型是什么?
    • 可容许的结构模式是什么?
    • 基本的计算模型是什么?
    • 风格的基本不变性是什么?
    • 其使用的常见例子是什么?
    • 使用此风格的优缺点是什么?
    • 其常见特例是什么?

    软件体系结构设计的一个中心问题是能否重用软件体系结构模式,或者采用某种软件体系结构风格。有原则地使用软件体系结构风格具有如下意义:

    • 它促进了设计的复用,使得一些经过实践证实的解决方案能够可靠地解决新问题。
    • 它能够带来显著的代码复用,使得体系结构风格中的不变部分可共享同一个解决方案。
    • 便于设计者之间的交流与理解。
    • 通过对标准风格的使用支持了互操作性,以便于相关工具的集成。
    • 在限定了设计空间的情况下,能够对相关风格作出分析。
    • 能够对特定的风格提供可视化支持。

    与此同时,人们目前尚不能准确回答的问题是:

    • 系统设计的哪个要点可以用风格来描述;
    • 能否用系统的特性来比较不同的风格,如何确定用不同的风格设计系统之间的互操作;
    • 能否开发出通用的工具来扩展风格;
    • 如何为一个给定的问题选择恰当的体系结构风格,或者如何通过组合现有的若干风格来产生一个新的风格。

    M.Shaw等人根据此框架给出了管道与过滤器、数据抽象和面向对象组织、基于事件的隐式调用、分层系统、仓库系统及知识库和表格驱动的解释器等一些常见的软件体系结构风格。

     

    架构风格

    客户端-服务器
    将系统分为两个应用,其中客户端向服务器发送服务请求。

    基于组件的架构
    把应用设计分解为可重用的功能、逻辑组件,这些组件的位置相互透明,只暴露明确定义的通信接口。

    分层架构
    把应用的关注点分割为堆栈组(层)。

    消息总线
    指接收、发送消息的软件系统,消息基于一组已知格式,以便系统无需知道实际接收者就能互相通信。

    N层/三层架构
    用与分层风格差不多一样的方式将功能划分为独立的部分,每个部分是一个层,处于完全独立的计算机上。

    面向对象
    该架构风格是将应用或系统任务分割成单独、可重用、可自给的对象,每个对象包含数据,以及与对象相关的行为。

    分离表现层
    将处理用户界面的逻辑从用户界面(UI)视图和用户操作的数据中分离出来。

    面向服务架构(SOA)
    是指那些利用契约和消息将功能暴露为服务、消费功能服务的应用。

    这些架构风格分别适用于特定领域:

    通信
    SOA,消息总线,管道和过滤器

    部署
    客户端/服务器,三层架构,N层架构

    领域
    领域模型,网关

    交互
    分离表现层

    结构
    基于组件的架构,面向对象,分层架构

     

    下面介绍几种常见的架构风格:

    管道和过滤器风格

    在管道/过滤器风格的软件体系结构中,每个构件都有一组输入和输出,构件读输入的数据流,经过内部处理,然后产生输出数据流。这个过程通常通过对输入流的变换及增量计算来完成,所以在输入被完全消费之前,输出便产生了。因此,这里的构件被称为过滤器,这种风格的连接件就像是数据流传输的管道,将一个过滤器的输出传到另一过滤器的输入。此风格特别重要的 过滤器必须是独立的实体,它不能与其它的过滤器共享数据,而且一个过滤器不知道它上游和下游的标识。一个管道/过滤器网络输出的正确性并不依赖于过滤器进 行增量计算过程的顺序。

    图2-1是管道/过滤器风格的示意图。一个典型的管道/过滤器体系结构的例子是以Unix shell编写的程序。Unix既提供一种符号,以连接各组成部分(Unix的进程),又提供某种进程运行时机制以实现管道。另一个著名的例子是传统的编 译器。传统的编译器一直被认为是一种管道系统,在该系统中,一个阶段(包括词法分析、语法分析、语义分析和代码生成)的输出是另一个阶段的输入。

    clip_image001

    图 2‑1管道/过滤器风格的体系结构

    管道/过滤器风格的软件体系结构具有许多很好的特点:

    (1)使得软构件具有良好的隐蔽性和高内聚、低耦合的特点;

    (2)允许设计者将整个系统的输入/输出行为看成是多个过滤器的行为的简单合成;

    (3)支持软件重用。重要提供适合在两个过滤器之间传送的数据,任何两个过滤器都可被连接起来;

    (4)系统维护和增强系统性能简单。新的过滤器可以添加到现有系统中来;旧的可以被改进的过滤器替换掉;

    (5)允许对一些如吞吐量、死锁等属性的分析;

    (6)支持并行执行。每个过滤器是作为一个单独的任务完成,因此可与其它任务并行执行。

    但是,这样的系统也存在着若干不利因素。

    (1)通常导致进程成为批处理的结构。这是因为虽然过滤器可增量式地处理数据,但它们是独立的,所以设计者必须将每个过滤器看成一个完整的从输入到输出的转换。

    (2)不适合处理交互的应用。当需要增量地显示改变时,这个问题尤为严重。

    (3)因为在数据传输上没有通用的标准,每个过滤器都增加了解析和合成数据的工作,这样就导致了系统性能下降,并增加了编写过滤器的复杂性

     

    数据抽象与面向对象风格

    抽象数据类型概念对软件系统有着重要作用,目前软件界已普遍转向使用面向对象系统。这种风格建立在数据抽象和面向对象的基础上,数据的表示方法和它们的相应操作封装在一个抽象数据类型或对象中。这种风格的构件是对象,或者说是抽象数据类型的实例。对象是一种被称作管理者的构件,因为它负责保持资源的完整性。对象是通过函数和过程的调用来交互的。

    图2-2是数据抽象和面向对象风格的示意图。

    clip_image001[4]

    图 2‑2数据抽象和面向对象风格的体系结构

    面向对象的系统有许多的优点,并早已为人所知:

    (1) 因为对象对其它对象隐藏它的表示,所以可以改变一个对象的表示,而不影响其它的对象。

    (2) 设计者可将一些数据存取操作的问题分解成一些交互的代理程序的集合。

    但是,面向对象的系统也存在着某些问题:

    (1)为了使一个对象和另一个对象通过过程调用等进行交互,必须知道对象的标识。只要一个对象的标识改变了,就必须修改所有其他明确调用它的对象。

    (2)必须修改所有显式调用它的其它对象,并消除由此带来的一些副作用。例如,如果A使用了对象B,C也使用了对象B,那么,C对B的使用所造成的对A的影响可能是料想不到的。

     

    基于事件的隐式调用风格

    基于事件的隐式调用风格的思想是构件不直接调用一个过程,而是触发或广播一个或多个事件。系统中的其它构件中的过程在一个或多个事件中注册,当一个事件被触发,系统自动调用在这个事件中注册的所有过程,这样,一个事件的触发就导致了另一模块中的过程的调用。

    从体系结构上说,这种风格的构件是一些模块,这些模块既可以是一些过程,又可以是一些事件的集合。过程可以用通用的方式调用,也可以在系统事件中注册一些过程,当发生这些事件时,过程被调用。

    基于事件的隐式调用风格的主要特点是事件的触发者并不知道哪些构件会被这些事件影响。这样不能假定构件的处理顺序,甚至不知道哪些过程会被调用,因此,许多隐式调用的系统也包含显式调用作为构件交互的补充形式。

    支持基于事件的隐式调用的应用系统很多。例如,在编程环境中用于集成各种工具,在数据库管理系统中确保数据的一致性约束,在用户界面系统中管理数据,以及在编辑器中支持语法检查。例如在某系 统中,编辑器和变量监视器可以登记相应Debugger的断点事件。当Debugger在断点处停下时,它声明该事件,由系统自动调用处理程序,如编辑程 序可以卷屏到断点,变量监视器刷新变量数值。而Debugger本身只声明事件,并不关心哪些过程会启动,也不关心这些过程做什么处理。

    隐式调用系统的主要优点有:

    (1)为软件重用提供了强大的支持。当需要将一个构件加入现存系统中时,只需将它注册到系统的事件中。

    (2)为改进系统带来了方便。当用一个构件代替另一个构件时,不会影响到其它构件的接口。

    隐式调用系统的主要缺点有:

    (1)构件放弃了对系统计算的控制。一个构件触发一个事件时,不能确定其它构件是否会响应它。而且即使它知道事件注册了哪些构件的构成,它也不能保证这些过程被调用的顺序。

    (2)数据交换的问题。有时数据可被一个事件传递,但另一些情况下,基于事件的系统必须依靠一个共享的仓库进行交互。在这些情况下,全局性能和资源管理便成了问题。

    (3)既然过程的语义必须依赖于被触发事件的上下文约束,关于正确性的推理存在问题。

     

    层次系统风格

    层次系统组织成一个层次结构,每一层为上层服务,并作为下层客户。在一些层次系统中,除了一些精心挑选的输出函数外,内部的层只对相邻的层可见。这样的系统中构件在一些层实现了虚拟机(在另一些层次系统中层是部分不透明的)。连接件通过决定层间如何交互的协议来定义,拓扑约束包括对相邻层间交互的约束。

    这种风格支持基于可增加抽象层的设计。这样,允许将一个复杂问题分解成一个增量步骤序列的实现。由于每一层最多只影响两层,同时只要给相邻层提供相同的接口,允许每层用不同的方法实现,同样为软件重用提供了强大的支持。

    图2-3是层次系统风格的示意图。层次系统最广泛的应用是分层通信协议。在这一应用领域中,每一层提供一个抽象的功能,作为上层通信的基础。较低的层次定义低层的交互,最低层通常只定义硬件物理连接。

    clip_image002

    图 2‑3层次系统风格的体系结构

    层次系统有许多可取的属性:

    (1)支持基于抽象程度递增的系统设计,使设计者可以把一个复杂系统按递增的步骤进行分解;

    (2)支持功能增强,因为每一层至多和相邻的上下层交互,因此功能的改变最多影响相邻的上下层;

    (3)支持重用。只要提供的服务接口定义不变,同一层的不同实现可以交换使用。这样,就可以定义一组标准的接口,而允许各种不同的实现方法。

    但是,层次系统也有其不足之处:

    (1)并不是每个系统都可以很容易地划分为分层的模式,甚至即使一个系统的逻辑结构是层次化的,出于对系统性能的考虑,系统设计师不得不把一些低级或高级的功能综合起来;

    (2)很难找到一个合适的、正确的层次抽象方法。

     

    仓库风格

    在仓库风格中,有两种不同的构件:中央数据结构说明当前状态,独立构件在中央数据存贮上执行,仓库与外构件间的相互作用在系统中会有大的变化。

    控制原则的选取产生两个主要的子类。若输入流中某类时间触发进程执行的选择,则仓库是一传统型数据库;另一方面,若中央数据结构的当前状态触发进程执行的选择,则仓库是一黑板系统。

    图2-4是黑板系统的组成。黑板系统的传统应用是信号处理领域,如语音和模式识别。另一应用是松耦合代理数据共享存取。

    clip_image001[6]

    图 2‑4黑板系统的组成

    我们从图2-4中可以看出,黑板系统主要由三部分组成:

    (1)知识源。知识源中包含独立的、与应用程序相关的知识,知识源之间不直接进行通讯,它们之间的交互只通过黑板来完成。

    (2)黑板数据结构。黑板数据是按照与应用程序相关的层次来组织的解决问题的数据,知识源通过不断地改变黑板数据来解决问题。

    (3)控制。控制完全由黑板的状态驱动,黑板状态的改变决定使用的特定知识。

     

    C2风格

    C2体系结构风格可以概括为:通过连接件绑定在一起的按照一组规则运作的并行构件网络。C2风格中的系统组织规则如下:

    (1)系统中的构件和连接件都有一个顶部和一个底部;

    (2)构件的顶部应连接到某连接件的底部,构件的底部则应连接到某连接件的顶部,而构件与构件之间的直接连接是不允许的;

    (3)一个连接件可以和任意数目的其它构件和连接件连接;

    (4)当两个连接件进行直接连接时,必须由其中一个的底部到另一个的顶部。

    图2-5是C2风格的示意图。图中构件与连接件之间的连接体现了C2风格中构建系统的规则。

    clip_image002[4]

    图 2‑5 C2风格的体系结构

    C2风格是最常用的一种软件体系结构风格。从C2风格的组织规则和结构图中,我们可以得出,C2风格具有以下特点:

    (1)系统中的构件可实现应用需求,并能将任意复杂度的功能封装在一起;

    (2)所有构件之间的通讯是通过以连接件为中介的异步消息交换机制来实现的;

    (3)构件相对独立,构件之间依赖性较少。系统中不存在某些构件将在同一地址空间内执行,或某些构件共享特定控制线程之类的相关性假设。

     

    二层C/S我们不再介绍了,直接说三层C/S

    三层C/S的基本硬件结构

    传统的二层C/S结构存在以下几个局限:

    l 它是单一服务器且以局域网为中心的,所以难以扩展至大型企业广域网或Internet;

    l 受限于供应商;

    l 软、硬件的组合及集成能力有限;

    l 难以管理大量的客户机。

    因此,三层C/S结构应运而生。三层C/S结构是将应用功能分成表示层、功能层和数据层三部分。其解决方案是:对这三层进行明确分割,并在逻辑上使其独立。原来的数据层作为DBMS已经独立出来,所以关键是要将表示层和功能层分离成各自独立的程序,并且还要使这两层间的接口简洁明了。

    将上述三层功能装载到硬件的方法基本上有三种(如图所示)。其中表示层配置在客户机中,而数据层配置在服务器中。

    一般情况是只将表示层配置在客户机中,与二层C/S结构相比,其程序的可维护性要好得多,是其他问题并未得到解决。客户机的负荷太重,其业务处理所需的数据要从服务器传给客户机,所以系统的性能容易变坏。

    如果将功能层和数据层分别放在不同的服务器中,则服务器和服务器之间也要进行数据传送。但是,由于在这种形态中三层是分别放在各自不同的硬件系统上的,所以灵活性很高,能够适应客户机数目的增加和处理负荷的变动。例如,在追加新业务处理时,可以相应增加装载功能层的服务器。因此,系统规模越大这种形态的优点就越显著。

    值得注意的是:三层C/S结构各层间的通信效率若不高,即使分配给各层的硬件能力很强,其作为整体来说也达不到所要求的性能。此外,设计时必须慎重考虑三层间的通信方法、通信频度及数据量。这和提高各层的独立性一样是三层C/S结构的关键问题。

    clip_image001

    三层C/S的功能

    1.表示层

    表示层是应用的用户接口部分,它担负着用户与应用间的对话功能。它用于检查用户从键盘等输入的数据,显示应用输出的数据。为使用户能直观地进行操作,一 般要使用图形用户接口(GUI),操作简单、易学易用。在变更用户接口时,只需改写显示控制和数据检查程序,而不影响其他两层。检查的内容也只限于数据的 形式和值的范围,不包括有关业务本身的处理逻辑。

    图形界面的结构是不固定的,这便于以后能灵活地进行变更。例如,在一个窗口中不是放入几个功能,而是按功能分割窗口,以便使每个窗口的功能简洁单纯。在这层的程序开发中主要是使用可视化编程工具。

    2. 功能层

    功能层相当于应用的本体,它是将具体的业务处理逻辑地编入程序中。例如,在制作订购合同的时要计算合同金额,按照定好的格式配置数据、打印订购合同,而 处理所需的数据则要从表示层或数据层取得。表示层和功能层之间的数据交往要尽可能简洁。例如,用户检索数据时,要设法将有关检索要求的信息一次传送给功能 层(参见图2),而由功能层处理过的检索结果数据也一次传送给表示层。在应用设计中,一定要避免进行一次业务处理,在表示层和功能层间进行多几次数据交换 的笨拙设计。

    通常,在功能层中包含有:确认用户对应用和数据库存取权限的功能以及记录系统处理日志的功能。这层的程序多半是用可视化编程工具开发的,也有使用COBOL和C语言的。

    3. 数据层

    数据层就是DBMS,负责管理对数据库数据的读写。DBMS必须能迅速执行大量数据的更新和检索。现在的主流是关系数据库管理系统(RDBMS)。因此,一般从功能层传送到数据层的要求大都使用SQL语言。

    三层C/S结构的优点

    1。 具有灵活的硬件系统构成

    对于各个层可以选择与其处理负荷和处理特性相适应的硬件。这是一个与系统可缩放性直接相关的问题。例如,最初用一台Unix工作站作为服务器,将数据层 和功能层都配置在这台服务器上。随着业务的发展,用户数和数据量逐渐增加,这时就可以将Unix工作站作为功能层的专用服务器,另外追加一台专用于数据层 的服务器。若业务进一步扩大,用户数进一步增加,则可以继续增加功能层的服务器数目,用以分割数据库。清晰、合理地分割三层结构并使其独立,可以使系统构 成的变更非常简单。因此,被分成三层的应用基本上不需要修正。

    2。 提高程序的可维护性

    三层C/S结构中,应用的各层可以并行开发,各层也可以选择各自最适合的开发语言。

    3。 利于变更和维护应用技术规范

    因为是按层分割功能,所以各个程序的处理逻辑变得十分简单。

    4。 进行严密的安全管理

    越关键的应用,用户的识别和存取权限设定愈重要。在三层C/S结构中,识别用户的机构是按层来构筑的,对应用和数据的存取权限也可以按层进行设定。例如,即使外部的入侵者突破了表示层的安全防线,若在功能层中备有另外的安全机构,系统也可以阻止入侵者进入其他部分。

    此外,系统管理简单,可支持异种数据库,有很高的可用性。

     

    C/S和B/S 的优缺点比较

    C/S和B/S是当今世界开发模式技术架构的两大主流技术。C/S是美国 Borland公司最早研发,B/S是美国微软公司研发。目前,这两项技术以被世界各国所掌握,国内公司以C/S和B/S技术开发出产品也很多。这两种技术都有自己一定的市场份额和客户群,各家企业都说自己的管理软件架构技术功能强大、先进、方便,都能举出各自的客户群体,都有一大群文人墨客为自己摇旗呐 喊,广告满天飞,可谓仁者见仁,智者见智。

    1、C/S架构软件的优势与劣势

    (1)、应用服务器运行数据负荷较轻。

    最简单的C/S体系结构的数据库应用由两部分组成,即客户应用程序和数据库服务器程序。二者可分别称为前台程序与后台程序。运行数据库服务器程序 的机器,也称为应用服务器。一旦服务器程序被启动,就随时等待响应客户程序发来的请求;客户应用程序运行在用户自己的电脑上,对应于数据库服务器,可称为 客户电脑,当需要对数据库中的数据进行任何操作时,客户程序就自动地寻找服务器程序,并向其发出请求,服务器程序根据预定的规则应答,送回结果,应用 服务器运行数据负荷较轻。

    (2)、数据的储存管理功能较为透明。

    在数据库应用中,数据的储存管理功能,是由服务器程序和客户应用程序分别独立进行的,前台应用可以违反的规则,并且通常把那些不同的(不管是已知 还是未知的)运行数据,在服务器程序中不集中实现,例如访问者的权限,编号可以重复、必须有客户才能建立定单这样的规则。所有这些,对于工作在前台程序上 的最终用户,是“透明”的,他们无须过问(通常也无法干涉)背后的过程,就可以完成自己的一切工作。在客户服务器架构的应用中,前台程序不是非常“瘦小 ”,麻烦的事情都交给了服务器和网络。在C/S体系的下,数据库不能真正成为公共、专业化的仓库,它受到独立的专门管理。

    (3)、C/S架构的劣势是高昂的维护成本且投资大。

    首先,采用C/S架构,要选择适当的数据库平台来实现数据库数据的真正“统一”,使分布于两地的数据同步完全交由数据库系统去管理,但逻辑上两地 的操作者要直接访问同一个数据库才能有效实现,有这样一些问题,如果需要建立“实时”的数据同步,就必须在两地间建立实时的通讯连接,保持两地的数据库服 务器在线运行,网络管理工作人员既要对服务器维护管理,又要对客户端维护和管理,这需要高昂的投资和复杂的技术支持,维护成本很高,维护任务量大。

    其次,传统的C/S结构的软件需要针对不同的操作系统系统开发不同版本的软件,由于产品的更新换代十分快,代价高和低效率已经不适应工作需要。在JAVA这样的跨平台语言出现之后,B/S架构更是猛烈冲击C/S,并对其形成威胁和挑战。

    2、B/S架构软件的优势与劣势

    (1)、维护和升级方式简单。

    目前,软件系统的改进和升级越来越频繁,B/S架构的产品明显体现着更为方便的特性。对一个稍微大一点单位来说,系统管理人员如果需要在几百甚至 上千部电脑之间来回奔跑,效率和工作量是可想而知的,但B/S架构的软件只需要管理服务器就行了,所有的客户端只是浏览器,根本不需要做任何的维护。无论 用户的规模有多大,有多少分支机构都不会增加任何维护升级的工作量,所有的操作只需要针对服务器进行;如果是异地,只需要把服务器连接专网即可,实现远程 维护、升级和共享。所以客户机越来越“瘦”,而服务器越来越“胖”是将来信息化发展的主流方向。今后,软件升级和维护会越来越容易,而使用起来会越来越简单,这对用户人力、物力、时间、费用的节省是显而易见的,惊人的。因此,维护和升级革命的方式是“瘦”客户机,“胖”服务器。

    (2)、成本降低,选择更多。

    大家都知道windows在桌面电脑上几乎一统天下,浏览器成为了标准配置,但在服务器操作系统上windows并不是处于绝对的统治地位。现在 的趋势是凡使用B/S架构的应用管理软件,只需安装在Linux服务器上即可,而且安全性高。所以服务器操作系统的选择是很多的,不管选用那种操作系统都 可以让大部分人使用windows作为桌面操作系统电脑不受影响,这就使的最流行免费的Linux操作系统快速发展起来,Linux除了操作系统是免费的 以外,连数据库也是免费的,这种选择非常盛行。

    (3)、应用服务器运行数据负荷较重。

    由于B/S架构管理软件只安装在服务器端(Server)上,网络管理人员只需要管理服务器就行了,用户界面主要事务逻辑在服务器 (Server)端完全通过WWW浏览器实现,极少部分事务逻辑在前端(Browser)实现,所有的客户端只有浏览器,网络管理人员只需要做硬件维护。 但是,应用服务器运行数据负荷较重,一旦发生服务器“崩溃”等问题,后果不堪设想。因此,许多单位都备有数据库存储服务器,以防万一。
     

    C/S 与 B/S 区别

          Client/Server是建立在局域网的基础上的,Browser/Server是建立在广域网的基础上的。

    (1)硬件环境不同:

          C/S 一般建立在专用的网络上, 小范围里的网络环境, 局域网之间再通过专门服务器提供连接和数据交换服务。

    B/S 建立在广域网之上的, 不必是专门的网络硬件环境,例如电话上网, 租用设备, 信息自己管理, 有比C/S更强的适应范围, 一般只要有操作系统和浏览器就行。

    (2)、对安全要求不同

          C/S 一般面向相对固定的用户群, 对信息安全的控制能力很强。 一般高度机密的信息系统采用C/S 结构适宜,可以通过B/S发布部分可公开信息。

    B/S 建立在广域网之上, 对安全的控制能力相对弱, 面向是不可知的用户群。

    (3)、对程序架构不同

          C/S 程序可以更加注重流程,可以对权限多层次校验,对系统运行速度可以较少考虑。

    B/S 对安全以及访问速度的多重的考虑, 建立在需要更加优化的基础之上。 比C/S有更高的要求,B/S结构的程序架构是发展的趋势,从MS的。Net系列的BizTalk 2000 Exchange 2000等,全面支持网络的构件搭建的系统。SUN和IBM推的JavaBean构件技术等,使B/S更加成熟。

    (4)、软件重用不同

          C/S 程序可以不可避免的整体性考虑, 构件的重用性不如在B/S要求下的构件的重用性好。

          B/S 对的多重结构,要求构件相对独立的功能。 能够相对较好的重用。就如买来的餐桌可以再利用,而不是做在墙上的石头桌子。

    (5)、系统维护不同

    系统维护是软件生存周期中,开销大,相当重要。C/S 程序由于整体性,必须整体考察,处理出现的问题以及系统升级难, 可能是再做一个全新的系统。 B/S 构件组成方面构件个别的更换,实现系统的无缝升级。系统维护开销减到最小,用户从网上自己下载安装就可以实现升级。

    (6)、处理问题不同

          C/S 程序可以处理用户面固定,并且在相同区域, 安全要求高的需求,与操作系统相关, 应该都是相同的系统。 B/S 建立在广域网上, 面向不同的用户群,分散地域, 这是C/S无法作到的,与操作系统平台关系最小。

    (7)、用户接口不同

          C/S 多是建立在Window平台上,表现方法有限,对程序员普遍要求较高。 B/S 建立在浏览器上, 有更加丰富和生动的表现方式与用户交流, 并且大部分难度减低,降低开发成本。

    (8)、信息流不同

          C/S 程序一般是典型的中央集权的机械式处理,交互性相对低。 B/S 信息流向可变化, B-B、 B-C、 B-G等信息流向的变化, 更像交易中心。

     

    基于层次消息总线的架构风格

    JB/HMB风格的基本特征

    目前对软件体系结构的研究集中在以下方面:各种体系结构风格的汇编和总结、体系结

    构描述语言(architectural description languages,简称ADLS)、体系结构的形式化基础、体系结构分析技术、基于体系结构的软件开发、体系结构恢复和再工程、支持体系结构设计的工具和环境及特定领域的软件体系结构等。 青鸟工程在“九五”期间,对基于构件构架模式的软件工业化生产技术进行了研究,并实现了青鸟软件生产线系统151。以青鸟软件生产线的实践为背景,提出了基于层次消息总线的软件体系结构(Jade bird hierarchical message bus based style,以下简称JB/HMB风格),设计了相应的体系结构描述语言,开发了支持软件体系结构设计的辅助工具集,并研究了采用JB/HMB风格进行应用系统开发的过程框架。

    JB/HMB风格的提出基于以下的实际背景:

    (1) 随着计算机网络技术的发展,特别是分布式构件技术的日渐成熟和构件互操作标准的出现,如CORBA,DCOM和EJB等,加速了基于分布式构件的软件开发趋势,具有分布和并发特点的软件系统已成为一种普遍的应用需求。

    (2) 基于事件驱动的编程模式已在图形用户界面程序设计中获得广泛应用。在此之前的

    程序设计中,通常使用一个大的分支语句(switch Statement)控制程序的转移,对不同的输人情况分别进行处理,程序结构不甚清晰。基于事件驱动的编程模式在对多个不同事件响应的情况下,系统自动调用相应的处理函数,程序具有清晰的结构。

    (3) 计算机硬件体系结构和总线的概念为软件体系结构的研究提供了很好的借鉴和启发,

    在统一的体系结构框架下(即总线和接口规范),系统具有良好的扩展性和适应性。任何计算机厂商生产的配件,甚至是在设计体系结构时根本没有预料到的配件,只要遵循标准的接口规范,都可以方便地集成到系统中,对系统功能进行扩充,甚至是即插即用(即运行时刻的系统演化)。正是标准的总线和接口规范的制定,以及标准化配件的生产,促进了计算机硬件的产业分工和蓬勃发展。

    clip_image001[4]

    JB/HMB风格基于层次消息总线、支持构件的分布和并发,构件之间通过消息总线进行通讯,如图所示。消息总线是系统的连接件,负责消息的分派、传递和过滤以及处理结果的返回。各个构件挂接在消息总线上,向总线登记感兴趣的消息类型。构件根据需要发出消息,由消息总线负责把该消息分派到系统中所有对此消息感兴趣的构件,消息是构件之间通讯的唯一方式,构件接收到消息后,根据自身状态对消息进行响应,并通过总线返回处理结果。由于构件通过总线进行连接,并不要求各个构件具有相同的地址空间或局限在一台机器上。该风格可以较好地刻画分布式并发系统,以及基于CORBA,DCOM和EJB规范的系统。

    如图所示,系统中的复杂构件可以分解为比较低层的子构件,这些子构件通过局部消息

    总线进行连接,这种复杂的构件称为复合构件。如果子构件仍然比较复杂,可以进一步分解。

    如此分解下去,整个系统形成了树状的拓扑结构,树结构的末端结点称为叶结点,它们是系统中的原子构件,不再包含子构件,原子构件的内部可以采用不同于JB/HMB的风格,例如数据流风格、面向对象风格及管道/过滤器风格等,这些属于构件的内部实现细节。但要集成到JB/HMB风格的系统中,必须满足JB/HMB风格的构件模型的要求,主要是在接口规约方面的要求。另外,整个系统也可以作为一个构件,通过更高层的消息总线,集成更大的系统中。于是,可以采用统一的方式刻画整个系统和组成系统的单个构件。


    构建模型

    系统和组成系统的成分通常是比较复杂的,难以从一个视角获得对它们的完整理解,因

    此一个好的软件工程方法往往从多个视角对系统进行建模,一般包括系统的静态结构、动态行为和功能等方面。例如,在Rumbaugh等人提出的OMT(object modeling technology)方法中,

    采用了对象模型、动态模型和功能模型刻画系统的以上3个方面。

    借鉴上述思想,为满足体系结构设计的需要,JB/HMB风格的构件模型包括了接口、静态结构和动态行为3个部分,如图所示。

    clip_image002

    在图中所示的构件模型中,左上方是构件的接口部分,一个构件可以支持多个不同的接口,每个接口定义了一组输入和输出的消息,刻画了构件对外提供的服务以及要求的环境服务,体现了该构件同环境的交互.右上方是用带输出的有限状态自动机刻画的构件行为,构件接收到外来消息后,根据当前所处的状态对消息进行响应,并可能导致状态的变迁.下方是复合构件的内部结构定义,复合构件是由更简单的子构件通过局部消息总线连接而成的.消息总线为整个系统和各个层次的构件提供了统一的集成机制。


    构件接口

    在体系结构设计层次上,构件通过接口定义了同外界的信息传递和承担的系统责任,构件接口代表了构件同环境的全部交互内容,也是唯一的交互途径.除此之外,环境不应对构件做任何其他与接口无关的假设,例如实现细节等。JB/HMB风格的构件接口是一种基于消息的互联接口,可以较好地支持体系结构设计。

    构件之间通过消息进行通讯,接口定义了构件发出和接收的消息集合.同一般的互联接口相比.JB/HMB的构件接口具有两个显著的特点.首先,构件只对消息本身感兴趣,并不关心消息是如何产生的,消息的发出者和接收者不必知道彼此的情况,这样就切断了构件之间的直接联系,降低了构件之间的藕合强度,进一步增强了构件的复用潜力,并使得构件的替换变得更为容易。另外,在一般的互联接口定义的系统中,构件之间的连接是在要求的服务和提供的服务之间进行固定的匹配,而在JB/HMB的构件接口定义的系统中,构件对外来消息的响应,不但同接收到的消息类型相关,而且同构件当前所处的状态相关.构件对外来消息进行响应后,可能会引起状态的变迁.因此,一个构件在接收到同样的消息后,在不同时刻所处的不同状态下,可能会有不同的响应。

    消息是关于某个事件发生的信息,上述接口定义中的消息分为两类:(i)构件发出的消息,通知系统中其他构件某个事件的发生或请求其他构件的服务;(ii)构件接收的消息,对系统中某个事件的响应或提供其他构件所需的服务.接口中的每个消息定义了构件的一个端口,具有互补端口的构件可以通过消息总线进行通讯,互补端口指的是除了消息进出构件的方向不同之外,消息名称、消息带有的参数和返回结果的类型完全相同的两个消息. 当某个事件发生后,系统或构件发出相应的消息,消息总线负责把该消息传递到对此消息感兴趣的构件.按照响应方式的不同,消息可分为同步消息和异步消息.同步消息是指消息的发送者必须等待消息处理结果返回才可以继续运行的消息类型.异步消息是指消息的发送者不必等待消息处理结果的返回即可继续执行的消息类型.常见的同步消息包括(一般的)过程调用。
     
    消息总线

    JB/HMB风格的消息总线是系统的连接件,构件向消息总线登记感兴趣的消息,形成构件-消息响应登记表.消息总线根据接收到的消息类型和构件一消息响应登记表的信息,定位并传递该消息给相应的响应者,并负责返回处理结果.必要时,消息总线还对特定的消息进行过滤和阻塞.下图给出了采用对象类符号表示的消息总线的结构。

    clip_image003


    运行时的演化

    在许多重要的应用领域中,例如金融、电力、电信及空中交通管制等,系统的持续可用性是一个关键性的要求,运行时刻的系统演化可减少因关机和重新启动而带来的损失和风险。此外,越来越多的其他类型的应用软件也提出了运行时刻演化的要求,在不必对应用软件进行重新编译和加载的前提下,为最终用户提供系统定制和扩展的能力。JBI/HMB风格方便地支持运行时刻的系统演化,主要体现在以下3个方面:

    (1) 动态增加或删除构件。在JB/HMB风格的系统中,构件接口中定义的输人和输出消息刻画了一个构件承担的系统责任和对外部环境的要求,构件之间通过消息总线进行通讯,彼此并不知道对方的存在。因此只要保持接口不变,构件就可以方便地替换。一个构件加人到系统中的方法很简单,只需向系统登记其所感兴趣的消息即可。但删除一个构件可能会引起系统中对于某些消息没有构件响应的异常情况,这时可以采取两种措施:一是阻塞那些没有构件响应的消息,二是首先使系统中的其他构件或增加新的构件对该消息进行响应,然后再删除相应的构件。系统中可能增删改构件的情况包括:当系统功能需要扩充时,往系统中增加新的构件。当对系统功能进行裁剪,或当系统中的某个构件出现问题时,需要删除系统中的某个构件。用带有增强功能或修正了错误的构件新版本代替原有的旧版本。

    (2) 动态改变构件响应的消息类型。类似地,构件可以动态地改变对外提供的服务(即接收的消息类型),这时应通过消息总线对发生的改变进行重新登记。

    (3) 消息过滤。利用消息过滤机制,可以解决某些构件集成的不匹配问题,详见“消息过滤”一节。消息过滤通过阻塞构件对某些消息的响应,提供了另一种动态改变构件对消息进行响应的方式。


    JB/HMB风格的优点

    以上讨论了JB/HMB风格的各组成要素,下面对JB/HMB风格的主要特点作总结。

    (1) 从接口、结构和行为方面对构件进行刻画。在JB/HMB风格中,构件的描述包括接口、静态结构和动态行为3个方面。接口:构件可以提供一个或多个接口,每个接口定义了一组发送和接收的消息集合,刻画了构件对外提供的服务以及要求的环境服务,接口之间可以通过继承表达相似性。

    静态结构:复合构件是由子构件通过局部消息总线连接而成的,形成该复合构件的内部结构。

    动态行为:构件行为通过带输出的有限状态机刻画,构件接收到外来消息后,不但根

    据消息类型,而且根据构件当前所处的状态对消息进行响应,并导致状态的变迁。

    基于层次消息总线:消息总线是系统的连接件,负责消息的传递、过滤和分派,以及

    处理结果的返回。各个构件挂接在总线上,向系统登记感兴趣的消息。构件根据需要发出消息,由消息总线负责把该消息分派到系统中对此消息感兴趣的所有构件。构件接收到消息后,根据自身状态对消息进行响应,并通过总线返回处理结果。由于构件通过总线进行连接,并不要求各个构件具有相同的地址空间或局限在一台机器上,系统具有并发和分布的特点。系统和复合构件可以逐层分解,子构件通过(局部)消息总线相连。每条消息总线分别属于系统和各层次的复合构件,我们把这种特征的总线称为层次消息总线。在系统开发方面,由于各层次的总线局部在相应的复合构件中,因此可以更好地支持系统的构造性和演化性。

    统一描述系统和组成系统的构件:组成系统的构件通过消息总线进行连接,复杂构

    件又可以分解为比较简单的子构件,通过局部消息总线进行连接,如果子构件仍然比较复杂,

    可以进一步分解。系统呈现出树状的拓扑结构。另外,整个系统也可以作为一个构件,集成到更大的系统中。于是,就可以对整个系统和组成系统的各层构件采用统一的方式进行描述。

    支持运行时刻的系统演化:系统的持续可用性是许多重要的应用系统的一个关键性

    要求,运行时刻的系统演化可减少因关机和重新启动而带来的损失和风险。JB/HMB风格方便地支持运行时刻的系统演化,主要包括动态增加或删除构件、动态改变构件响应的消息类型和消息过滤。


    REST架构风格

    首先,REST是Web自身的架构风格。REST也是Web之所以取得成功的技术架构方面因素的总结。REST是世界上最成功的分布式应用架构风格(成功案例:Web,还不够吗?)。它是为运行在互联网环境 的 分布式 超媒体系统量身定制的。互联网环境与企业内网环境有非常大的差别,最主要的差别是两个方面:

    • 可伸缩性需求无法控制:并发访问量可能会暴涨,也可能会暴跌。
    • 安全性需求无法控制:无法控制客户端发来的请求的格式,很可能会是恶意的请求。

    而所谓的“超媒体系统”,即,使用了超文本的系统。可以把“超媒体”理解为超文本+媒体内容。

    REST是HTTP/1.1协议等Web规范的设计指导原则,HTTP/1.1协议正是为实现REST风格的架构而设计的。新的Web规范,其设计必须符合REST的要求,否则整个Web的体系架构会因为引入严重矛盾而崩溃。这句话不是危言耸听,做个类比,假如苏州市政府同意在市区著名园林的附近大型土木,建造大量具有后现代风格的摩天大楼,那么不久之后世界闻名的苏州园林美景将不复存在。

    上述这些关于“REST是什么”的描述,可以总结为一句话:REST是所有Web应用都应该遵守的架构设计指导原则。当然,REST并不是法律,违反了REST的指导原则,仍然能够实现应用的功能。但是违反了REST的指导原则,会付出很多代价,特别是对于大流量的网站而言。

    要深入理解REST,需要理解REST的五个关键词:

    1. 资源(Resource)
    2. 资源的表述(Representation)
    3. 状态转移(State Transfer)
    4. 统一接口(Uniform Interface)
    5. 超文本驱动(Hypertext Driven)

    什么是资源?

    资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成。每个资源是服务器上一个可命名的抽象概念。因为资源是一个抽象的概念,所以它不仅仅能代表服务器文件系统中的一个文件、数据库中的一张表等等具体的东西,可以将资源设计的要多抽象有多抽象,只要想象力允许而且客户端应用开发者能够理解。与面向对象设计类似,资源是以名词为核心来组织的,首先关注的是名词。一个资源可以由一个或多个URI来标识。URI既是资源的名称,也是资源在Web上的地址。对某个资源感兴趣的客户端应用,可以通过资源的URI与其进行交互。

    什么是资源的表述?

    资源的表述是一段对于资源在某个特定时刻的状态的描述。可以在客户端-服务器端之间转移(交换)。资源的表述可以有多种格式,例如HTML/XML/JSON/纯文本/图片/视频/音频等等。资源的表述格式可以通过协商机制来确定。请求-响应方向的表述通常使用不同的格式。

    什么是状态转移?

    状态转移(state transfer)与状态机中的状态迁移(state transition)的含义是不同的。状态转移说的是:在客户端和服务器端之间转移(transfer)代表资源状态的表述。通过转移和操作资源的表述,来间接实现操作资源的目的。

    什么是统一接口?

    REST要求,必须通过统一的接口来对资源执行各种操作。对于每个资源只能执行一组有限的操作。以HTTP/1.1协议为例,HTTP/1.1协议定义了一个操作资源的统一接口,主要包括以下内容:

    • 7个HTTP方法:GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS
    • HTTP头信息(可自定义)
    • HTTP响应状态代码(可自定义)
    • 一套标准的内容协商机制
    • 一套标准的缓存机制
    • 一套标准的客户端身份认证机制

    REST还要求,对于资源执行的操作,其操作语义必须由HTTP消息体之前的部分完全表达,不能将操作语义封装在HTTP消息体内部。这样做是为了提高交互的可见性,以便于通信链的中间组件实现缓存、安全审计等等功能。

    什么是超文本驱动?

    “超文本驱动”又名“将超媒体作为应用状态的引擎”(Hypermedia As The Engine Of Application State,来自Fielding博士论文中的一句话,缩写为HATEOAS)。将Web应用看作是一个由很多状态(应用状态)组成的有限状态机。资源之间通过超链接相互关联,超链接既代表资源之间的关系,也代表可执行的状态迁移。在超媒体之中不仅仅包含数据,还包含了状态迁移的语义。以超媒体作为引擎,驱动Web应用的状态迁移。通过超媒体暴露出服务器所提供的资源,服务器提供了哪些资源是在运行时通过解析超媒体发现的,而不是事先定义的。从面向服务的角度看,超媒体定义了服务器所提供服务的协议。客户端应该依赖的是超媒体的状态迁移语义,而不应该对于是否存在某个URI或URI的某种特殊构造方式作出假设。一切都有可能变化,只有超媒体的状态迁移语义能够长期保持稳定。

     

    image

    理解REST风格的架构所具有的6个的主要特征:

    • 面向资源(Resource Oriented)
    • 可寻址(Addressability)
    • 连通性(Connectedness)
    • 无状态(Statelessness)
    • 统一接口(Uniform Interface)
    • 超文本驱动(Hypertext Driven)

    这6个特征是REST架构设计优秀程度的判断标准。其中,面向资源是REST最明显的特征,即,REST架构设计是以资源抽象为核心展开的。可寻址说的是:每一个资源在Web之上都有自己的地址。连通性说的是:应该尽量避免设计孤立的资源,除了设计资源本身,还需要设计资源之间的关联关系,并且通过超链接将资源关联起来。无状态、统一接口是REST的两种架构约束,超文本驱动是REST的一个关键词,在前面都已经解释过,就不再赘述了。

    从架构风格的抽象高度来看,常见的分布式应用架构风格有三种:

    • 分布式对象(Distributed Objects,简称DO)

    架构实例有CORBA/RMI/EJB/DCOM/.NET Remoting等等

    • 远程过程调用(Remote Procedure Call,简称RPC)

    架构实例有SOAP/XML-RPC/Hessian/Flash AMF/DWR等等

    • 表述性状态转移(Representational State Transfer,简称REST)

    架构实例有HTTP/WebDAV

    DO和RPC这两种架构风格在企业应用中非常普遍,而REST则是Web应用的架构风格,它们之间有非常大的差别。

    REST与DO的差别在于:

    • REST支持抽象(即建模)的工具是资源,DO支持抽象的工具是对象。在不同的编程语言中,对象的定义有很大差别,所以DO风格的架构通常都是与某种编程语言绑定的。跨语言交互即使能实现,实现起来也会非常复杂。而REST中的资源,则完全中立于开发平台和编程语言,可以使用任何编程语言来实现。
    • DO中没有统一接口的概念。不同的API,接口设计风格可以完全不同。DO也不支持操作语义对于中间组件的可见性。
    • DO中没有使用超文本,响应的内容中只包含对象本身。REST使用了超文本,可以实现更大粒度的交互,交互的效率比DO更高。
    • REST支持数据流和管道,DO不支持数据流和管道。
    • DO风格通常会带来客户端与服务器端的紧耦合。在三种架构风格之中,DO风格的耦合度是最大的,而REST的风格耦合度是最小的。REST松耦合的源泉来自于统一接口+超文本驱动。

    REST与RPC的差别在于:

    • REST支持抽象的工具是资源,RPC支持抽象的工具是过程。REST风格的架构建模是以名词为核心的,RPC风格的架构建模是以动词为核心的。简单类比一下,REST是面向对象编程,RPC则是面向过程编程。
    • RPC中没有统一接口的概念。不同的API,接口设计风格可以完全不同。RPC也不支持操作语义对于中间组件的可见性。
    • RPC中没有使用超文本,响应的内容中只包含消息本身。REST使用了超文本,可以实现更大粒度的交互,交互的效率比RPC更高。
    • REST支持数据流和管道,RPC不支持数据流和管道。
    • 因为使用了平台中立的消息,RPC风格的耦合度比DO风格要小一些,但是RPC风格也常常会带来客户端与服务器端的紧耦合。支持统一接口+超文本驱动的REST风格,可以达到最小的耦合度。

    比较了三种架构风格之间的差别之后,从面向实用的角度来看,REST架构风格可以为Web开发者带来三方面的利益:

    • 简单性

    采用REST架构风格,对于开发、测试、运维人员来说,都会更简单。可以充分利用大量HTTP服务器端和客户端开发库、Web功能测试/性能测试工具、HTTP缓存、HTTP代理服务器、防火墙。这些开发库和基础设施早已成为了日常用品,不需要什么火箭科技(例如神奇昂贵的应用服务器、中间件)就能解决大多数可伸缩性方面的问题。

    • 可伸缩性

    充分利用好通信链各个位置的HTTP缓存组件,可以带来更好的可伸缩性。其实很多时候,在Web前端做性能优化,产生的效果不亚于仅仅在服务器端做性能优化,但是HTTP协议层面的缓存常常被一些资深的架构师完全忽略掉。

    • 松耦合

    统一接口+超文本驱动,带来了最大限度的松耦合。允许服务器端和客户端程序在很大范围内,相对独立地进化。对于设计面向企业内网的API来说,松耦合并不是一个很重要的设计关注点。但是对于设计面向互联网的API来说,松耦合变成了一个必选项,不仅在设计时应该关注,而且应该放在最优先位置。

     

    架构风格和架构模式之间的细微差别

    • 架构风格是系统主要的、组织性的设计。
    • 架构模式从子系统或模块、及其之间的关系层次上描述了粗粒度的解决方案。
    • 系统隐喻则更为概念化,比起软件工程概念,它更多地涉及现实世界的概念。

     

    David Calvert在1996年给出了一份架构风格/模式的部分清单:

    • 数据流系统——批处理,管道-过滤器。
    • 调用-返回系统——主程序和子程序,面向对象系统,分层。
    • 独立组件——通信过程,事件系统。
    • 虚拟机——解释器,基于规则的系统。
    • 以数据为中心的系统(仓库)——数据库,超文本系统,黑板。

     

    其它比较现代的风格/模式还有:插件点对点无共享架构表述性状态转移(REST)、前端-后端。在维基百科上有更为完整的列表

    展开全文
  • 哈佛结构/冯诺依曼结构详细分析

    千次阅读 2019-06-16 17:33:56
    CISC与RISC的区别: CISC(复杂指令集):复杂指令集就是CPU在工作的时候需要有很多的汇编指令来完成,它可以用一个汇编指令来完成一件复杂的工作。例如:乘法,加法,乘加,乘减等处理的时候,他会每个处理方式用...
  • 图——基本的图算法(一)图的存储结构 1. 图的表示方法 对于图G=(V, E)来说,可以有两种标准的表示方法,这两种方法都可以表示有向图和无向图: (1)邻接矩阵 (2)邻接链表 ...
  • MP4文件结构解析

    千次阅读 2019-06-21 00:36:56
    文章目录MP4相关文档MP4分析工具术语和缩略语Mp4文件的组织结构Box对象结构FullBox对象结构常见的BoxFile Type BoxMovie 结构Movie BoxMovie Header BoxTrack BoxTrack Header BoxMedia BoxMedia Header BoxHandler ...
  • 数据结构:八大数据结构分类

    万次阅读 多人点赞 2018-09-05 18:23:28
    数据结构分类 数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成 。 常用的数据结构有:数组,栈,链表,队列,树,图,堆,散列表等,如图所示: 每一种数据结构都...
  • 小猪的数据结构辅助教程——前言

    万次阅读 多人点赞 2019-05-31 13:56:34
    面试给人上了一课,突然感觉数据结构很重要;还有,帮助后来者,刚接触数据结构的 童鞋们一点点方向,不至于学完什么都不知道!大部分学校采用的教程应该是严蔚敏老师的 《数据结构(C语言版)》吧,而讲数据结构课程...
  • 数据结构分别为逻辑结构、(存储)物理结构和数据的运算三个部分。 为什么要学数据结构? 首先,因为数据结构作为计算机专业的专业基础课程,是计算机考研的必考科目之一,如果打算报考计算机专业的研究生,你...
  • Java数据结构和算法(一)——开篇

    万次阅读 多人点赞 2019-09-18 12:31:42
    看的是——《Java数据结构和算法》一书,作者Robert Lafore。 目录 1)数据结构算法有什么用? 2)技术与通俗 3)驱动力学习 1)数据结构算法有什么用? 当你用着java里面的容器类很爽的时候,你有没有想过,怎么...
  • 数据结构 - 逻辑结构和存储结构

    万次阅读 2017-10-15 22:19:16
    程序=算法+数据结构 N.沃思(Niklaus Wirth)教授提出:  程序=算法+数据结构  以上公式说明了如下两个问题:  (1)算法决定如何构造和组织数据(算法→数据结构)。  (2)算法的选择依赖于作为基础的...
  • 小猪的数据结构辅助教程——3.2 栈与队列中的链栈标签(空格分隔): 数据结构1.本节引言: 嗯,本节没有学习路线图哈,因为栈我们一般都用的是顺序栈,链栈还是顺带提一提吧, 栈因为只是栈顶来做插入和删除操作...
  • 结构化数据和非结构化数据、半结构化数据的区别

    万次阅读 多人点赞 2019-06-15 22:27:41
    计算机信息化系统中的数据分为结构化数据和非结构化数据、半结构化数据。 结构化数据 结构化数据,是指由二维表结构来逻辑表达和实现的数据,严格地遵循数据格式与长度规范,主要通过关系型数据库进行存储和管理...
  • 小猪的数据结构辅助教程——1.数据结构与算法绪论标签(空格分隔): 数据结构本节学习路线图与学习要点学习要点: 1.了解数据结构的相关概念 2.了解算法的相关概念 3.熟悉时间复杂度的计算 4.了解空间复杂度...
  • 数据结构(C语言版本)

    万次阅读 多人点赞 2018-04-22 19:42:32
    数据结构(C语言版本) 第1章 绪论 1.常用的数据结构类型:集合、线性、树形、图状。 2.数据结构: - 逻辑结构:数据元素之间的关系 - 存储结构:数据结构在计算机中的表示。存储结构分为:顺序存储结构和...
  • 什么是拓扑结构_拓扑结构

    万次阅读 多人点赞 2016-12-07 22:24:21
    什么是拓扑结构?  首先我们来解释一下拓扑的含义,所谓“拓扑”就是把实体抽象成与其大小、形状无关的“点”,而把连接实体的线路抽象成“线”,进而以图的形式来表示这些点与线之间关系的方法,其目的在于研究...
  • 数据结构之逻辑结构与物理结构(存储结构

    万次阅读 多人点赞 2018-06-27 21:23:29
    1.逻辑结构:逻辑结构分为四种类型:集合结构,线性结构,树形结构,图形结构。1.1 所谓集合结构:表面意思,没有什么深刻意义,就是数据元素同属一个集合,单个数据元素之间没有任何关系。如下图所示。 1.2 线性...
  • 为什么要学数据结构

    万次阅读 多人点赞 2019-11-19 13:54:58
    一、前言 在可视化化程序设计的今天,借助于...1) 能够熟练地选择和设计各种数据结构和算法 2) 至少要能够熟练地掌握一门程序设计语言 3) 熟知所涉及的相关应用领域的知识 其中,后两个条件比较容易实现,而第一个...
  • 系统总体结构设计

    万次阅读 2016-02-18 15:38:18
    系统总体结构设计   系统设计工作应该自顶向下地进行。首先设计总体结构,然后再逐层深入,直至进行每一个模块的设计。总体设计主要是指在系统分析的基础上,对整个系统的划分(子系统)、机器设备(包括软、硬...
1 2 3 4 5 ... 20
收藏数 5,196,822
精华内容 2,078,728
关键字:

结构