FDS’s Blog

2011年5月12日

轻松应对高程软件设计题

Filed under: 计算机网络 — liaowei @ 14:29

根据软件水平与资格考试大纲的要求,高级程序员不仅要具备高水平的程序编制能力,而且要熟练掌握软件设计的方法和技术,具备一定的软件设计能力。软件设计题是下午试题的难点,本文针对软件设计题,给出了解答的一般方法,并且给出了解答实例,最后对历年试题进行了分析,希望能够给广大考生提供帮助。

 解题的一般方法

  一、软件设计题要点

  综观1990年到2002年的高程下午设计题,主要包括以下考点:

  1. 完善处理流程,或指出处理内容,或指出处理结果。

  2. 指出流程图中错误,或为避免错误应在某处添加处理项目。

  3. 为达到某目的,需要改动哪些处理,或改变处理方法会引起什么后果。

  4. 指出错误清单内容。

  5. 文件的记录应包含哪些内容。

  6. 完成处理需要什么样的文件,或文件有什么特征,或引入某文件有什么好处。

  7. 文件怎样分类,或指出关键字。

  8. 某处理的频度。

  9. 输入数据的格式。

  10. 题意中的分类有何好处。

  11. 为实现某目的适当修改文件的记录内容。

  12. 指出图中数据流名,或指出哪些位置数据可增加或删去。

考点最集中的部分是关于文件记录的内容,其次是文件的分类排序,再次是当目的改变应该改动哪些。

  二、答题注意事项

  事务处理流程图一般由若干处理与若干数据组成,在评估流程图并回答试题的问题时,应该注意下面一些问题。

  从“处理”的角度来说,必须注意:

  1. 每个事务处理均有一个特定目标,这一目标往往都是文字说明的。与此对应的处理应能覆盖所设定的目标。对于说明处理要求,都应从问题目标进行考虑。

  2. 除覆盖问题目标的处理外,还有两类处理应予考虑:一是为了保证处理的正确性,设计一些处理框,以检查输入数据的数据项及数据的值域;另一种是检查数据会合时数据的一致性。二是为了处理的效率,如速度、次数、减少处理访问等而引进了一些处理。

  3. 在一个流程图内,在一条流程上的各个处理不能有功能上的重复。如有重复,不是有错,就是流程还可优化。

  4. 每个处理都是由“处理的依据”到“使用数据”,以及从“处理结果”到“产生数据”。“使用数据”或者“产生数据”与处理相互匹配是十分重要的。 

    从“数据”的角度来说,必须注意:

  1. 注意流程图最初的输入数据与最终输出数据,考虑从输入到输出之间数据演变的情况。根据数据的演变与流程,关于从输入到输出应有哪些数据就比较清楚了,其作用也可以从演变方面了解。

  2. 考虑数据流程中,哪些数据应作为文件形式出现,哪些是中间使用的临时数据。在数据演变中,一些数据经多个“处理”加工后得到结果,每加工一次就产生一个新数据,对这些数据分析,就能得出各数据的存储要求。

  3. 对数据按问题要求设计数据结构。输入(输出)数据的结构与问题有关,而中间数据的结构除与输入(输出)数据有关外,还与处理有关。

  4. 为减少数据冗余,要保证数据一致性,数据文件设计中应考虑关系,亦即各种文件的记录之间的联系。
我们对高级程序员软件设计题的解题方法做了分析,现在我们来分析一道例题。

  ★解答实例

  2000年试题3:阅读以下说明和流程图,回答问题1和问题2。

【说明】

  某供销系统接受顾客的订货单。当库存中某配件的数量小于订购量或库存量低于一定数量时,向供应商发出采购单;当某配件的库存量大于或等于订购量时,或者收到供应商的送货单并更新了库存后,向顾客发出提货单。该系统还可随时向总经理提供销售和库存情况表。该供销系统的分层数据流图中部分数据流和文件的组成如下:

文件

  配件库存=配件号+配件名+规格+数量+允许的最低率库存量

数据流

  订货单=配件号+配件名+规格+数量+顾客名+地址

  提货单=订货单+金额

  采货单=配件号+配件名+规格+数量+供应商名+地址

  送货单=配件号+配件名+规格+数量+金额

  假定顶层图是正确的,“供应商”文件已由其他系统生成。

[问题1]

    指出哪张图中的哪些文件可不必画出。
[问题2]
    指出在哪些图中遗漏了哪些数据流。回答时用如下形式之一。
    (1) X X 图中遗漏了 X X 加工 (或文件) 流向 X X 加工 (或文件) 的 x x 数据流;
    (2) X X 图中 X X 加工遗漏了 X X 输入 (或输出) 数据流。
[流程图]
顶层图

  

    在顶层图中,供销系统的3个输入数据流(订货单、送货单与销售及库存情况)和5个输出数据流(不合法的订货单、不合法的送货单、提货单、采购单与销售及库存情况表)在0层图中都得到反映。考查0层图中所有的输入数据流和输出数据流,是否有遗漏的加工呢?在顶层图中总经理的查询是单独的加工,但在0层图中却给包括到加工1中去了,成为加工1.3,其输入或输出数据流也都包括在该加工中。考查加工1子图,加工1.3包含在加工1中是科学的,因为该加工需要来自加工1数据支持,且事务简单,包含在加工1中可以大大减小系统分析和设计的复杂程度。因此不能说0层图遗漏了加工。至于文件,在0层图中凡是需要文件的地方都是从文件输入的,未发生遗漏现象。

  仔细研究0层图,大体上确定加工1有3个输入数据流(订货单、到货通知和查询销售及库存情况),加工1子图中只有订货单与查询销售及库存情况两个数据流,显然遗漏了输入数据到货通知,该数据流应该从哪个子加工输入呢?看试题中的说明“收到供应商的送货单并更新了库存后,向顾客发出了提货单”。据此至少可以判定输入数据流到货通知是子加工1.4更新库存的前提条件,没有到货通知,就无法更新库存。是直接输入加工1.4吗?再看子加工1.4与其他子加工的关系。子加工1.5输出数据流的去向是子加工1.4。而子加工1.5需要到货通知的支持,但从子加工1.4到子加工1.5却没有数据流。综合以上分析,应该在子加工1.5处输入数据流到货通知。

    首先看配件库存文件。在加工1子图(图3)中,配件库存文件经过加工1.4更新库存而修改,然后还要为加工1.1和加工1.2提供数据支持,在加工1.1中,通过检查订货单中订购的配件在配件库存中是否有记录,来确定该配件是否属于经销范围,然后以此来确定订货单是否合法。其次,还要在加工1.2中比较合法订货单的数量与库存数量,以此确定是否需要发出采购请求。再次,在发出提货单后,如果库存量低于允许的最低库存量,也要发出采购单。可以看出,在该加工图中,未发现对配件库存文件的不适当的使用。

  在加工2子图(图4)中,在加工2.4核对送货单后要更新库存文件然后加工2.3计算增量提供数据支持,计算出需要采购的配件的数量。应该说这里对配件库存文件的使用是正确的。因此我们可以确定配件库存文件在加工1和加工2中都有应用,画在0层图中是合适的。

  缺货订单文件在试题说明中没有提到,具体的应用需要我们自己分析。0层图中显示该文件被加工1和加工2使用。现在的任务就是确定两个加工中对该文件的使用是正确的。在加工1中缺货订单的数据来自加工1.2和加工1.4,在加工1.2中,接受合法的订货单后,如果订货单上数量大于配件库存文件的数量,将产生缺货订单文件。在加工1.4中,在有货订单送达后,需要交出提货单,然后更新库存。如果库存量低于允许的最低库存量,应该将缺货信息反映到缺货订单中。在加工2中,缺货订单为加工2.3提供部分数据支持,在计算配件增量以明确需要增加的配件的清单时,需要参照缺货订单。结合以上对使用缺货订单文件的分析,可以认为,因此将该文件画在0层图中是有必要的。

    我们分析了配件库存文件和缺货订单文件,再来看采购清单。

  0层图显示该文件只应用于加工2。采购清单如果只应用在加工1中,最有可能的地方是加工1.2及其后的采购请求数据流,但在这里采购清单是有必要的吗?从加工2中可以看出,采购清单是按供应商对采购单进行汇总来产生的,对采购单进行汇总,显然已经属于加工2的工作,不应在加工1中予以反映。由此可以断定在加工1中不应该有采购单文件。所以在0层图中画采购清单是不合适的。

  以上考查了0层图、加工1子图和加工2子图中缺货订单、配件库存及采购清单的使用,下面研究加工1中的销售历史文件。如果没有该文件支持,加工1.3是无法制作销售及库存情况表的;而且该文件由加工1.4更新库存产生,其输入数据流和输出数据流均在加工1中,在加工1中使用该文件是合适的。

  问题2要求指出哪些图中遗漏了哪些元素。一般来说,这类题目的解答首先要考虑各层次图的数据平衡,其次要考虑加工的输入数据流和输出数据流要平衡,即保证加工的输出数据流都有其对应的输入数据流。所谓数据平衡,就是在多层次数据流程图中,父图和子图之间的数据流必须保持一致,比如说在父图中某加工有2个输入数据流和一个输出数据流,那么在该加工的子图中的输入(输出)数据流必须在数目上和内容上与父图保持一致。

    考查0层图,加工1有不合格订货单、销售及库存情况表、提货单与采购请求4个输出数据流。但在加工1子图中,却没有输出数据流提货单,这显然是不符合数据平衡原则的。但该数据流应从何处输出呢?根据试题说明,在更新库存后应向顾客发出提货单。显然这里合适的位置是子加工1.4。

八皇后问题的高效解法-递归版

Filed under: 计算机网络 — liaowei @ 14:29

// Yifi  2003  have fun!  : )

//8 Queen 递归算法
//如果有一个Q 为 chess[i]=j;
//则不安全的地方是 k行  j位置,j+k-i位置,j-k+i位置

class Queen8{

  static final int QueenMax = 8;
  static int oktimes = 0;
  static int chess[] = new int[QueenMax];//每一个Queen的放置位置


  public static void main(String args[]){
    for (int i=0;i<QueenMax;i++)chess[i]=-1; 
    placequeen(0);
    System.out.println(“\n\n\n八皇后共有”+oktimes+”个解法 made by yifi 2003″);
  }


  public static void placequeen(int num){ //num 为现在要放置的行数
    int i=0;
    boolean qsave[] = new boolean[QueenMax];
    for(;i<QueenMax;i++) qsave[i]=true;
   
    //下面先把安全位数组完成
    i=0;//i 是现在要检查的数组值
    while (i<num){
      qsave[chess[i]]=false;
      int k=num-i;
      if ( (chess[i]+k >= 0) && (chess[i]+k < QueenMax) ) qsave[chess[i]+k]=false;
      if ( (chess[i]-k >= 0) && (chess[i]-k < QueenMax) ) qsave[chess[i]-k]=false;
      i++;
    }
    //下面历遍安全位
    for(i=0;i<QueenMax;i++){
      if (qsave[i]==false)continue;
      if (num<QueenMax-1){
        chess[num]=i;
        placequeen(num+1);
      }
      else{ //num is last one
      chess[num]=i;
      oktimes++;
      System.out.println(“这是第”+oktimes+”个解法 如下:”);
      System.out.println(“第n行:  1 2 3 4 5 6 7 8″);
     
      for (i=0;i<QueenMax;i++){
       String row=”第”+(i+1)+”行:  “;
       if (chess[i]==0);
       else
        for(int j=0;j<chess[i];j++) row+=”–”;
        row+=”++”;
        int j = chess[i];
        while(j<QueenMax-1){row+=”–”;j++;}
       System.out.println(row);
      }
      }
    }
  //历遍完成就停止
  }

异 常 处 理

Filed under: 计算机网络 — liaowei @ 14:29

简介
大型应用软件往往是分层构建的。在最底层你会发现库函数,API函数,和私有的底层函数。然而在最高层则是用户接口组件,比如一个电子制表软件让用户填写数据表单。下面来看一种普通的航空订票系统:它的最高端是由一些GUI组件所组成,用来在用户的屏幕上显示内容。这些高端组件与那些封装了数据库API的数据存取对象相互作用。再往底层一些,那些数据库API与数据库引擎相交互,然而数据库引擎自己又会调用系统服务来处理底层的硬件资源,比如物理内存,文件系统和安全模型。一般情况下,及其严格的运行期错误会在这些底层代码中被检测出来,但是它们不能—–或者说不应该—-试图自己处理这些错误。解决这些严格的运行期错误的责任应该由高端组件来承担。为了解决一个错误,高端组件必须得到错误发生的通知。本质上,错误处理包括错误检测和通知高端组件。这些组件依次处理错误并且试图从错误中恢复。

传统的错误处理方法
在早些时期,C++本身并没有处理运行期错误的能力。取而代之的是那些传统的C方法。这些方法可以被归为三类设计策略:
返回一个状态码来表明成功或失败
把错误码赋值给一个全局标记并且让其他的函数来检测
终止整个程序
上述的任何一个方法在面向对象环境下都有明显的缺点和限制。其中的一些根本就不可接受,尤其是在大型应用程序中。接下来的部分将会仔细检查一下这些方法,目的是发现他们与生俱来的限制和危险性。

返回一个错误码
在某种程度上这个方法是有用的,比如一个小型程序有着一致而且有限的错误码存在,并且严格的报告错误和检查一个函数返回值的策略被应用。然而,这种方法也有着显著的局限性;例如,错误类型和它们的列举值必须标准化。因为一个库的实现者可能选择返回值0来代表一个错误,然而另一个实现者却选择0来代表成功并且用那些非0值代表出现错误。通常,那些返回码会在一个公共头文件中以符号常量的形式存在,从而在整个软件的开发过程中或者在一个开发团队里达成一致。但是,这些码并不是标准的。
不用说,在结合那些不兼容的软件库的时候,如何处理非标准的错误码将会是一件极其头疼的事。另外一个缺点是对于每一个返回码都必须查阅和解释——一个乏味并且昂贵的操作。这个策略的实现需要调用者在每一次调用的时候对返回值进行检查,如果没有这样做将会导致运行期错误。当一个错误码被检测,就会终止正常的执行流程并且把错误码传递给调用者。那些附加的包裹每一个函数调用的代码会很轻易的使程序的大小翻倍并且引起软件维护和程序可读性的降低。更糟的是,有时要想返回一个error value是不可能的。例如,构造函数没有返回值,所以就不能应用这种方法在对象构造失败的情况下报告错误。

求助于全局标记
一个可以选择的用来报告运行期错误的途径是使用全局标记,它表明了最后的操作是否成功。不像返回码策略,这个方法是标准化的。C 的<errno.h>头文件中定义了一种机制用来检查和给一个全局整型标记errno赋值。这种策略固有的缺陷也是不能被忽视的。在一个多线程环境中,被一个线程赋予了一个错误码的errno有可能不经意的被另一个线程所改写,而调用者还未对errno进行检查。另外,对错误码而不是一个更为可读的信息的使用是很不利的,因为那些错误码可能会在不同的环境中不兼容。最终,这种方法需要严格的良好的编程样式,也就是不断的对errno的当前值进行检查。
全局标记策略和函数返回值策略是相似的:二者都提供一种机制来报告错误,但是二者却都不能保证错误被处理。例如,一个函数没有成功打开一个文件可以通过给errno赋予一个合适的值来表明错误的发生。然而,它不能阻止另一个函数试图写入和关闭那个文件。更进一步,如果errno表明一个错误并且程序员检测到而且按照预期处理了它,那么errno还应该被显式的复位。如果一个程序员忘记了做这件事,那么将会引起其他函数误以为错误还没有被处理,从而去校正那个问题,引起不可预知的结果。

终止程序
最为残酷的处理运行期错误的方法是简单的终止程序。这种解决方案去除了上面两种方法的一些缺点;例如,没有必要反复的检查每个函数返回值的状态,而且程序员也不必赋值给一个全局标记,反复的测试和清除它的值。在标准C的函数库中有两个函数用来终止一个程序:exit()和abort()。exit()被调用能够表明程序被成功终止,或者它可以在遇到运行期错误的时候被调用。在把控制权交还给运行环境之前,exit()首先会清空流和关闭打开的文件。abort()却不一样,它表示程序被意外终止,不会清空流和关闭打开的文件。
关键性的程序不应该在任何运行期错误存在的情况下突然终止。如果一个生命支持系统突然停止工作仅仅是因为它的控制器检测到0做除数,那么将是一种灾难。同样,一个控制由人驾驶的航天飞机自动运行的计算机系统也不应该因为暂时的和地面控制系统失去联系就停止工作。类似的,电话公司的账目系统或者银行系统都不应该在运行期错误出现的时候就中止。健壮的真实世界的应用程序应该做的更好。
程序终止甚至对于应用程序都是有问题的。一个检测到错误的函数通常都没有必要的信息来衡量错误的严重性。例如一个内存分配函数并不能说出内存分配失败是由于用户正在使用调试器,网页浏览器,电子制表软件,文字处理软件,还是由于系统因为硬件错误变得不稳定。在第一种情况下,系统可以简单的显示一条信息来告诉用户关闭不必要的应用程序。第二种情况下,就需要一种更为残酷的措施了。然而,在终止程序的策略下,那个内存分配函数就会简单的终止程序,而不考虑错误的严重性。这种方法在一些关键性应用程序中是无法应用的。好的系统设计应该保证运行期错误被检测和报告,但是它也应该确保最小限度的容错水平。
终止程序在极限环境下或者在调试阶段是可以被接受的。然而,abort()和exit()却不应该在面向对象环境中使用,甚至即使在调试阶段,因为他们并没有意识到C++对象模型的存在。

exit()和abort()不销毁对象
对象可以持有从构造函数或者某个成员函数中获得的资源:从free store中分配的内存,文件句柄,通信端口,I/O设备等等。这些资源必须在适当时候被释放。通常,资源都是由析构函数来释放。这种设计方法被称为resource initialization is acquisition。在栈上建立的局部对象会自动销毁。然而abort() 和exit()并不调用这些局部对象的析构函数。因此,程序的意外终止将会引起无法挽回的损害:数据库被破坏,文件可能丢失,并且一些有价值的数据可能丢失。基于这个原因,请不要在面向对象环境中使用abort()和exit()。

进入异常处理
正如你所见,传统C的错误处理方法并不适合C++,C++的一个设计目标就是让用C++进行大规模软件开发比C更好更安全。
C++的设计者们已经意识到缺乏合适的错误处理机制使得实现这一目标相当的困难。他们试图寻找一种完全摆脱C的错误处理缺陷的解决方案。其中的一种想法就是建立在当异常被触发的时候程序自动把控制权传递给系统。机制必须简单,并且它能够使程序员从不断的检查一个全局标记或者返回值的苦差事中解脱出来。另外,它还必须保证异常处理程序能够自动获得异常信息。最终它还要确保当一个异常没有在本地处理的时候,本地对象能够被适当的销毁,并且把它所持有的资源释放。
1989年,在多年的研究和多方建议下,异常处理进入C++。C++并不是第一个对结构化运行期错误处理进行支持的语言。早在20世纪60年代,PL/1就提供了一种内建的异常处理机制;Ada也在20世纪80年代提供了自己的异常处理,另外还有几种语言也做到了这一点。但是这些异常处理模型没有一个适合C++对象模型和程序结构。因此,被提议的C++异常处理是独一无二的,并且它已经作为了一种模型出现在一些新产生的语言之中。
异常处理机制的实现被证明是一种挑战。第一个C++编译器,cfront,在UNIX环境下运行。和许多UNIX编译器一样,它首先是作为一个翻译器把C++代码转换成C,接着再编译C代码。Cfront 4.0计划引入异常处理,然而,异常处理机制的实现是如此的复杂,以至于cfront 4.0的开发团队在用了一年时间设计它之后完全的放弃了这个项目。Cfront 4.0再也没有出台。然而,异常处理却成为了标准C++的有机组成部分。后来出现的一些编译器都支持了它。在接下来的部分里将会解释为什么在cfront以及任何编译器下实现异常处理是如此的困难。 实现异常处理所面临的挑战
实现异常处理所遇到的困难主要来自于以下几个因素:第一,实现必须保证对于某一异常的合适的handler被找到。
第二,异常对象必须是多态的;这样,当实现无法通过派生类对象定位handler的时候可以考虑基类的handler。这种需要表明必须引入运行期类型检测。然而那时C++还没有任何运行期类型检测的能力。因此这种能力必须首先被实现。
作为一个附加的复杂性,实现必须能够调用所有局部对象的析构函数。这个过程被称为stack unwinding 。因为早期的C++编译器首先要把C++源文件转换为纯C,然后再把C代码编译成机器码。异常处理的实现者们不得不用C来实现运行期类型鉴别和stack unwinding。幸运的是,这些障碍已经被克服。

应用异常处理
异常处理是一种灵活并且精巧的工具。它克服了C的传统错误处理方法的缺点并且能够被用来解决一系列运行期错误。但是,异常处理也像其他语言特性一样,很容易被误用。为了能够有效的使用这一特性,理解运行期机制是如何工作的以及相关的性能花费是非常重要的。接下来的部分里将会进入异常处理的内部并且论证如何使用这一工具来建立安全的应用系统。

异常处理要素
异常处理是一种把控制权从异常发生的地点转移到一个匹配的handler的机制。异常是内建数据类型变量或者是对象。异常处理机制包括四个部分:a try block,一个或多个和try block相关的handler,throw表达式,以及异常自己。Try block包含可能抛出异常的代码。例如:
try
{
int * p = new int[1000000]; //may throw std::bad_alloc
}
一个try block后面将跟有一个或多个catch语句或者说是handlers, 每一个handler 处理不同类型的异常。例如:
try
{
int * p = new int[1000000]; //may throw std::bad_alloc
//…
}
catch(std::bad_alloc& )
{
}
catch (std::bad_cast&)
{
}
handler仅仅被在try block中的throw表达式以及函数所调用。throw表达式包括一个关键字throw以及assignment expression。例如:
try
{
throw 5; // 5 is assigned to n in the following catch statement
}
catch(int n)
{
}
throw表达式和返回语句很相似。empty throw是没有操作数的throw语句。例如:

throw;

在handler中的empty throw表明它在重新抛出异常,后面我们会讨论到它。另外,如果目前没有异常被处理,那么执行一个empty throw将会调用terminate()。

Stack Unwinding
当一个异常被抛出,运行时机制首先在当前的作用域寻找合适的handler。如果不存在这样一个handler,那么将会离开当前的作用域,进入更外围的一层继续寻找。这个过程不断的进行下去直到合适的handler被找到为止。此时堆栈已经被解开,并且所有的局部对象被销毁。如果始终都没有找到合适的handler,那么程序将会终止。注意,C++保证局部对象被适当的销毁仅仅是在抛出的异常被处理的情况下。一个未被扑获得异常是否引起局部对象的销毁由实现决定的。为了保证局部对象的析构函数在异常未被捕获情况下也能够被正常调用,你应该在main()里加入捕获任何异常的catch语句。例如:
int main()
{
try
{
//…
}
catch(std::exception& stdexc) // handle expected exceptions
{
//…
}
catch(…) // ensure proper cleanup in the case of an uncaught exception
{
}
return 0;
}
stack unwinding的过程就好比一个返回语句序列,每一个都返回相同的对象给它的调用者。

传递异常对象给handler
一个异常能够按值或者按引用的方式传递给它的handler。为异常对象分配的内存是通过一种未被定义的途径(但是并没有在自由存储区)。一些实现使用专门的异常堆栈,在那里,异常对象被创建。当一个异常按引用的方式传递,handler获得是在异常堆栈上建立的对象的引用。通过引用方式传递异常保证了它的多态行为。按值传递的异常被建立在调用者的堆栈上。例如:
#include <cstdio>
class ExBase {/*…*/};
class FileEx: public ExBase {/*…*/};
void Write(FILE *pf)
{
if (pf == NULL) throw FileEx();
//… process pf normally
}
int main ()
{
try
{
Write(NULL); //will cause a FileEx exception to be thrown
}
catch(ExBase& exception) //catch ExBase or any object derived from it
{
//diagnostics and remedies }
}
按值传递异常将会造成反复的复制对象,并且它的花费是昂贵的,因为异常对象在匹配的handler被找到以前会被构造和销毁许多次。然而,在比较罕见的情况下也会发生按值传递,由于为了保持应用系统的整体性,性能考虑往往被放在了第二位。 异常类型匹配
异常的类型决定了哪个handler能够捕获它。异常的匹配规则比函数重载的匹配规则更为严格。考虑下面这种情况:
try
{
throw int();
}
catch (unsigned int) //will not catch the exception from the previous try-block
{
}
抛出异常的类型是int型,然而handler却期待一个unsigned int。异常处理机制不认为二者是能够匹配的类型;结果,抛出的异常没有被捕获。异常匹配规则仅仅允许一个非常有限的转换集。对于一个异常E和一个带有T或T&参数的handler,符合下面的条件可以进行匹配:
T和E是同一类型(const 和volatile被忽略)
T是E的没有歧义的公共基类
如果E和T都是指针类型,当二者的类型相同时可以进行匹配或者E所指向对象的类型公有无歧义的继承自T指向对象的类型。

作为对象的异常
正如你所发现的,传统的通过返回一个整型错误码的方法在OOP中已经不再适用。C++异常处理机制提供了更多的弹性,安全性和稳固性。一个异常既可以是int 或char等基本类型,也可以是更为丰满的对象,有着数据成员和成员函数。这样一个对象可以为handler提供更多的选择进行恢复。一个聪明的异常对象,可以通过成员函数返回错误的详细描述,而不是让handler查阅某个表或文件。它也可以拥有在错误被适当处理之后使程序从运行期错误中恢复的成员函数。考虑有这样一个日志类想要添加新的纪录到一个已存在的日志文件中:如果打开日志文件失败,它会抛出一个异常。当它被匹配的handler所捕获,异常对象能够拥有一个成员函数,这个成员函数建立一个对话框。操作者可以从对话框中选择恢复方法,包括建立一个新的日志文件,选择另一个日志文件,或者是允许系统在没有日志的情形下运行。

Exception Specification
一个函数可以通过指定一个它所能抛出的异常列表来提醒它的用户。Exception specifications在用户只能看到函数的原型但是却无法获得它的源文件的时候将会十分的有用。下面是一个指定异常的例子:

class Zerodivide{/*..*/};
int divide (int, int) throw(Zerodivide); // function may throw an exception
// of type Zerodivide, but no other

如果你的函数永远不会抛出任何异常,它可以像下面这样声明:
bool equals (int, int) throw(); //no exception is thrown from this function

注意一个函数被声明为没有exception specification例如:
bool equals (int, int);

Exception specification在运行期生效
一个exception specification不会在编译期被检查,而是在运行期。当一个函数试图抛出一个在exception specification中未被指定的异常的时候,异常处理机制将会检测到这种违规并且调用标准函数unexpected()。unexpected()的默认行为是调用terminate()终止程序。违背exception specification就好比是一个bug,不应该发生,这就是为什么默认行为是终止程序。不过默认的行为也可以被改变,通过使用函数set_unexpected()。
因为exception specifications在运行期才有效,所以编译期可能会故意忽略那些违背exception specifications的代码。好比下面:
int f(); // no exception specification, f can throw any type of exception
void g(int j) throw() // g promises not to throw any exception at all
{
int result = f(); // if f throws an exception, g will violate its guarantee
//not to throw an exception. still, this code is legal
}
在上面这个例子中,函数g()并不允许抛出任何异常。它调用函数f(),然而f()却可能抛出任何异常因为它没有exception specification。如果f()抛出一个异常,它将会通过g()传播出去,但是这却破坏了g()不会抛出任何异常的保证。这也许看起来会很奇怪,有一些违背在编译期就应该被发现报错的,为什么一定要等到运行期呢?然而许多问题并不像想象的那么简单,以下几个原因就要求必须采用运行期检测策略。在前面的那个程序中,f()可能是一个被遗留下来的C函数。我们不可能强迫每个C函数有exception specification。并且因为这个原因就强迫程序员在g()中写不必要的try和catch(…)块也是不实际的。通过强迫exception specification只在运行期才有效,C++采取了“信任程序员”的策略而不是强加负担给程序员和实现。

Exception specification的一致性
C++需要派生类中的exception specification与基类保持一致。这意味着派生类的virtual function重载函数的exception specification必须是基类的限制性子集,例如:
// various exception classes
class BaseEx{};
class DerivedEx: public BaseEx{};
class OtherEx {};
class A
{
public:
virtual void f() throw (BaseEx);
virtual void g() throw (BaseEx);
virtual void h() throw (DerivedEx);
virtual void i() throw (DerivedEx);
virtual void j() throw(BaseEx);
};
class D: public A
{
public:
void f() throw (DerivedEx); //OK, DerivedEx is derived from BaseEx
class D: public A
{
public:
void f() throw (DerivedEx); //OK, DerivedEx is derived from BaseEx
void g() throw (OtherEx); //error; exception specification is
//incompatible with A’s
void h() throw (DerivedEx); //OK, identical to the exception
//specification in base
void i() throw (BaseEx); //error, BaseEx is not a DerivedEx nor is it
//derived from DerivedEx
void j() throw (BaseEx,OtherEx); //error, less restrictive than the
//specification of A::j
};
};
相同的一致性限制也应用于函数指针。一个拥有exception specification函数指针只能被赋予一个有着相同或更为局限的exception specification的函数。这说明一个没有exception specification的函数指针不能被赋予一个有exception specification的函数。注意,因为exception specification不能被认为是函数类型的一部分,因此你不能声明两个仅仅是exception specification不同的函数。例如:
void f(int) throw (Y);
void f(int) throw (Z); //error; redefinition of ‘void f(int)’
同样的原因,声明一个包含exception specification的typedef也是错误的:
typedef void (*PF) (int) throw(Exception); // error

在对象构造和销毁时出现异常
构造函数和析构函数被自动调用,并且它们不能够利用返回值来表明发生运行期错误。从表面上看,在对象构造和销毁时抛出一个异常似乎是报告运行期错误的最好方法。但事实上你还必须考虑一些额外的因素。你尤其应该对从析构函数中抛出异常保持警惕。

从析构函数中抛出异常是危险的
从析构函数中抛出异常是不应该被推荐的,这是因为一个析构函数可能会在另一个异常进行stack unwinding的时候被调用,在这种情况下,异常处理机制就会调用terminate()终止程序。如果你真的想从一个析构函数中抛出异常的话,一种可取的做法是首先检查一下是否还有未被捕获的异常存在。

检查未被捕获的异常
一个异常被捕获是在它相应的handler被找到的情况下。为了检查一个异常是否被捕获,你可以使用标准函数uncaught_exception()(它被定义在标准头文件<stdexcept>)。例如:
class FileException{};
File::~File() throw (FileException)
{
if ( close(file_handle) != success) // failed to close current file?
{
if (uncaught_exception() == true ) // is there any uncaught exception
//being processed currently?
return; // if so, do not throw an exception
throw FileException(); // otherwise, it is safe to throw an exception
// to signal an error
}
return; // success
}
然而,一个更好的选择是直接在析构函数内部处理异常,而不是让他们扩散到外面。例如:
void cleanup() throw (int);
class C
{
public:
~C();
};
C::~C()
{
try
{
cleanup();
}
catch(int)
{
//handle the exception within the destructor
}
}
如果一个异常被函数cleanup()抛出,那么它在析构函数内部就被处理。否则,被抛出的异常就会传播到析构函数的外部,并且如果这个析构函数是在stack unwinding 的过程中被调用,那么程序将会通过terminate()的调用而终止。

全局对象:构造和销毁
我们都知道,全局对象的构造发生在程序开始之前。因此,任何从全局对象的构造函数中抛出的异常将不会被捕获。这一点对于全局对象的析构函数也是一样的—–全局对象的析构函数在程序结束之后被运行。因此,一个从全局对象的析构函数中抛出的异常也不会被捕获。 高级异常处理技术
简单的try-throw-catch模型可以被扩展来处理更为复杂的运行期错误。这一节将会讨论一些更为高级的异常处理技术,包括异常层次,重新抛出异常,function try blocks以及auto_ptr 类。

标准异常
C++定义了一个标准异常层次,当在运行时发生反常情形时抛出。标准异常类从std::exception(在<stdexcept>头文件中定义)派生。这一层次使得应用程序能够在单一的catch语句中捕获这些异常:
catch (std::exception& exc)
{
// handle exception of type std::exception as well as
//any exception derived from it
}
那些通过语言内建操作符抛出的标准异常是:
std::bad_alloc //by operator new
std::bad_cast //by operator dynamic_cast < >
std::bad_typeid //by operator typeid
std::bad_exception //thrown when an exception specification of
所有的标准异常都提供了成员函数what(),它返回一个用来描述异常细节的字符串。注意,标准库还有另外一个被它的组件抛出的的异常集合。

异常处理层次
异常在一个自下向上的层次中捕获:派生层次越深的异常越先被处理,例如:
#include <stdexcept>
#include <iostream>
using namespace std;
int main()
{
try
{
char * buff = new char[100000000];
//…use buff
}
catch(bad_alloc& alloc_failure) // bad_alloc is
//derived from exception
{
cout<<”memory allocation failure”;
//… handle exception thrown by operator new
}
catch(exception& std_ex)
{
cout<< std_ex.what() <<endl;
}
catch(…) // exceptions that are not handled elsewhere are caught here
{
cout<<”unrecognized exception”<<endl;
}
return 0;
}
派生层次越深的handler必须出现在其基类的前面。这是因为handler的匹配过程是按照出现的顺序进行的。因此有可能某个handler永远不会被执行,例如,把一个处理派生类异常的handler放在处理基类异常的handler的后面。例如:
catch(std::exception& std_ex) //bad_alloc exception is always handled here
{
//…handle the exception
}
catch(std::bad_alloc& alloc_failure) //unreachable
{
cout<<”memory allocation failure”;
}

重新抛出异常
异常的抛出表明了一种反常的状态。先捕获到异常的handler试图解决这个问题,但是它如果没有成功或者只完成了部分恢复,那么它可以重新抛出这个异常,让更高一层的try block来处理它。基于这种目的,try blocks可以在一个分等级的顺序上进行嵌套,使得一个从低层重新抛出的异常能够被重新捕获。重新抛出用一个没有操作数的throw语句来表示。例如:
#include <iostream>
#include <string>
using namespace std;
enum {SUCCESS, FAILURE};
class File
{
public: File (const char *) {}
public: bool IsValid() const {return false; }
public: int OpenNew() const {return FAILURE; }
};
class Exception {/*..*/}; //general base class for exceptions
class FileException: public Exception
{
public: FileException(const char *p) : s(p) {}
public: const char * Error() const { return s.c_str(); }
private: string s;
};
void func(File& );
int main()
{
try //outer try
{
File f (“db.dat”);
func; // 1
}
catch(…) // 7
//this handler will catch the re-thrown exception;
//note: the same exception type is required
{
cout<<”re-thrown exception caught”;
}
return 0;
}
void func(File & f)
{
try //inner try
{
if (f.IsValid() == false )
throw FileException(“db.dat”); // 2
}
catch(FileException &fe) // 3
//first chance to cope with the exception
{
cout<<”invalid file specification” <<fe.Error()<<endl;
if (f.OpenNew() != SUCCESS) (5)
//re-throw the original exception and let a higher handler deal with it
throw; // 6
}
}
在上面的例子中,函数func()在main()中的try block里被调用(1)。第二个在func()中的try block抛出一个FileException类型的异常(2)。这个异常被func()内的catch block所捕获(3)。那个catch block试图通过打开一个新文件进行补救,但是失败了(5),并且FileException异常被重新抛出(6)。最终,那个重新抛出的异常被main()中的catch(…)所捕获(7)。

Function try Blocks
Function try blocks是一个函数体本身就含有一个try block以及它的相关handler的函数。比如:
class Bad{};
void foo()try
{
throw Bad();
}
catch(…)
{
std::cout<<”error catch!!”;
}
function try block使得一个handler能够捕获构造函数中以及初始化列表中发生的异常。然而,它并不像普通异常的handler,function try block很少能够捕获异常继续对象的构建。这是因为被部分构造的对象要被销毁。另外,一个function try block的handler不能执行返回语句(或者说,handler必须通过一个throw离开)。那么究竟function try block的用处是什么呢?handler使得你可以抛出另一个异常而不是你刚才捕获的那个,这样可以阻止一个违背exception specification的情况发生。例如:
class X{};
C::C(const std::string& s) throw (X) // allowed to throw X only
try
: str(s) // str’s constructor might throw a bad_alloc exception,
// might violate C’s exception specification
{
// constructor function body
}
catch (…) //handle any exception thrown from ctor initializer or ctor body
{
//…
throw X(); //replace bad_alloc exception with an exception of type X
}
在这个例子中,一个string对象首先被创建作为class c 的一个成员。String在它的创建过程中可能抛出一个bad_alloc异常。那个function try block能够捕获bad_alloc异常并且抛出类型为x的异常使得它满足c的构造函数的exception specification的需要。

异常处理的性能分析
异常处理机制主要取决于运行期类型检查,当一个异常被抛出,实现必须确定异常是不是从try block中抛出。如果异常是从try block中抛出的话,实现就需要比较异常的类型,试图从当前的作用域中找到匹配的handler。如果找到控制权将会传递给handler。然而这些都是乐观的情况。如果实现没有找到一个匹配的handler的话,或者异常不是从try block中抛出,那么就会进行stack unwinding,这个过程会一直进行下去直到一个匹配的handler被找到为止。当匹配的handler未被找到,terminate()就会被调用,终止程序。

额外的运行期类型信息
异常处理机制为了完成异常和它的handler之间的运行期匹配,它必须储存关于每个异常对象的类型以及每个catch语句的额外信息。因为异常可以是任何类型,并且也可以是多态的,所以它的动态类型信息必须被获得通过使用runtime type information(RTTI),RTTI给程序在速度和大小上增加了额外的负担。并且只有RTTI还不够,实现也需要运行期代码信息,关于每个函数的结构。这些信息需要拿来确定一个异常是不是从try block中抛出。这个信息可以通过编译器以下面的方式产生:编译器把每个函数体划分为三个部分:第一个部分在try block之外并且没有活动的对象,第二个部分也在try block之外但是有活动的对象而且要通过stack unwinding来进行销毁,第三部分在try block中。

抓住对异常处理的支持
异常处理技术的实现在不同的编译器以及平台下是不一样的。但是它们都会强加额外的负担给程序,即使没有异常被抛出。一些编译器可以选择是否对异常处理进行支持。当异常处理被关掉的时候,那些额外的数据结构,查找表,以及一些附加的代码都不会被生成。然而,关闭异常处理往往很少被选择,因为即使你不直接使用异常,你也会隐含的使用它们:例如operator new,会抛出std::bad_alloc异常;STL容器可能会抛出他们自己的异常;第三方代码库也可能使用异常。因此,只有在你导入纯C代码的时候,才应该考虑关闭异常处理来避免额外的负担。

异常的误用
异常处理并不是用来限制出现错误,一些程序员可能会简单的使用它来作为循环的选择控制结构。例如,一个简单的应用程序让用户输入数据直到一个特定的条件满足:
#include <iostream>
using namespace std;
class Exit{}; //used as exception object
int main()
{
int num;
cout<< “enter a number; 99 to exit” <<endl;
try
{
while (true) //infinitely
{
cin>>num;
if (num == 99)
throw Exit(); //exit the loop
cout<< “you entered: ” << num << “enter another number ” <<endl;
}
}
catch (Exit& )
{
cout<< “game over” <<endl;
}
return 0;
}
在上面的例子中,程序员把一个无限循环放在了try block中,throw语句终止循环并且把控制权传递给后面的catch语句。这种编程样式不应该被推荐。它的效率会非常低下因为异常处理存在。在上面小的演示程序中,或许仅仅是程序样式的差异,但是在大规模的应用系统中,使用异常处理来作为控制选择控制结构的话,那么将会带来显著的效率损失。

结论
C++的异常处理机制克服了传统方法所带来的问题。它使得程序员从检查函数的返回状态的乏味的代码中解放出来。另外一个重要的优点是自动的stack unwinding,它保证了局部活动对象被正确销毁以及他们的资源被安全释放。
实现异常处理机制并不是一项简单的工作。对获取异常的动态类型的需要使得RTTI被引入C++。异常可以按照种类进行分组,标准异常就是一个很好的例子。在最近几年里,一些异常处理机制已经得到了修正。第一个就是把exception specifications加入到了函数原型中。另一个是function try block的引入,它使得程序能够处理从构造函数体或初始化列表中抛出的异常。
异常处理是一个用来有效的处理运行期错误的非常强大并且灵活的工具。然而使用它也是有代价的。

哲学家就餐问题

Filed under: 计算机网络 — liaowei @ 14:29

有五个哲学家围坐在一圆桌旁,桌中央有一盘通心粉,每人面前有一只空盘子,每两人之间放一只筷子每个哲学家的行为是思考,感到饥饿,然后吃通心粉.为了吃通心粉,每个哲学家必须拿到两只筷子,并且每个人只能直接从自己的左边或右边去取筷子
#define  N  5
 void philosopher (int i)  
{
    while (true)  
   {
    思考;
    取fork[i]; 取fork[(i+1) % 5];
    进食;
    放fork[i]; 放fork[(i+1) % 5];
    }
}
为防止死锁发生可采取的措施:
最多允许4个哲学家同时坐在桌子周围仅当一个哲学家左右两边的筷子都可用时,才允许他拿筷子(&#61654;)给所有哲学家编号,奇数号的哲学家必须首先拿左边的筷子,偶数号的哲学家则反之 为了避免死锁,把哲学家分为三种状态,思考,饥饿,进食,并且一次拿到两只筷子,否则不拿.

 

哲学家就餐问题解法(1)
#define  N  5
 void philosopher (int i)  
{
    while (true)  
   {
    思考;
    取fork[i]; 取fork[(i+1) % 5];
    进食;
    放fork[i]; 放fork[(i+1) % 5];
    }
}
哲学家就餐问题解法(2)
#define  N  5
#define  THINKING  0
#define  HUNGRY    1
#define  EATING    2
#typedef  int semaphore;
int state[N];
semaphore mutex=1;
semaphore s[N];
void test(int i)
{
      if (state[ i ] == HUNGRY) 
          && (state [ (i-1) % 5] != EATING) 
          && (state [ (i+1) % 5] != EATING)
          {
               state[ i ] = EATING;
               V(&s[ i ]);
           }
 }
void  philosopher (int i) 
{   while (true)  
     {
      思考;
       P(&mutex);
       state[i] = HUNGRY;
       test(i);
       V(&mutex);
       P(&s[i]);
      拿左筷子;
      拿右筷子;
       进食;
      放左筷子;
      放右筷子;
        P(&mutex)
        state[ i ] = THINKING;
        test([i-1] % 5);
        test([i+1] % 5);
        V(&mutex);
        }
}
state[ i ] = THINKING
s[ i ] = 0

网络管理员的职业生涯与规划

Filed under: 计算机网络 — liaowei @ 14:29

中国目前注册网吧12~13万家,加上其它网吧估计总数会有20万家左右,这样庞大的就业通道,是其它任何行业都无法比拟的。随着政府部门对网吧行业管理的放开,关于网吧经营管理经验、网吧技术提升、网吧生意经等等一系列的行业问题都受到了相关人士的关注。但作为产业化、标准化、品牌化发展脚步加快的网吧业,此行业中的一个很重要的个体—网络管理员(也就是我们常说的网管)的职业生涯规划却很少有人关注。

相较一般在机关单位的网络管理人员的工作性质上,又有着更高的复杂度。在机关单位的网络及计算机的使用上,通常是专人专机使用,而且使用的都是一些较单纯的办公软件或是稳定的应用程序,使用的人员在计算机知识的水平也较高,因特网运用的纪律与规范也比较好要求,所以维护起整个网络系统及计算机的妥善率比较容易;但在网吧内网络以及计算机的使用情形就大大的不同,每一部计算机每天可能就得供数十人来使用,而每一个人使用的习惯都有很大的差异,从网络黑客、游戏高手到连输入法都不会计算机新手都有,使用的软件从近千款的游戏、上万部的电影到聊天工具…等,还有一大堆危险性极高的游戏外挂、私服、下载工具,所以维护一个网吧网络安全及计算机妥善率的工作真是非常艰苦。更令人烦的是,到网吧的每一个玩家可都是花钱的大爷,每一个也都得罪不起,他们才不会管你什么「网络安全使用规定」,让网吧的维护工作更是难上加难。

谈到生涯规划,我们就不得不再拿机关单位的网络管理人员跟网吧网管再来做一次比较。网络管理人员每天穿着整齐洁白的衣服,在有着恒温空调的办公室上班,可以跟气质高雅的OL朝夕相处,朝九晚五工作时间固定,薪资至少3000起跳,有空的时候还可以考考证照、炒炒股票,如果命好还可以随着公司上市坐领股票,发一笔小财。我们的网吧网管呢!网吧里烟雾弥漫,不但工作时间长还得24小时待命处理突发状况,薪资低,还没有三金保险,每天还得应付水平不高的玩家,一不小心还得挨过受罚,命不好的话还会碰到一个无心经营的老板,不知道哪天网吧关门还得落个失业的下场。所以,君不见北大、清华的高材生在网吧当网管的。

跟众多的网吧网管兄弟们接触后,我们可以大致了解到大家会来担任这样的工作,通常会有以下两类心态的驱使所致:

第一类的网吧网管是热爱游戏,沉迷于虚幻的网络世界中,心想着到网吧担任网管可以继续玩游戏还免机时费,又有薪水领,真是一举数得。通常这一类的网管兄弟,很少留心技术上的改变及店内经营上的状况。我们必须语重心长奉劝这些网管兄弟们,随着年龄渐长,技术水准并没有因为工作而提高,相对却不断失去了学习进取的时机与兴趣。躲藏于虚幻的网络世界中,终有一天必须回头面对现实生存压力。

第二类的网吧网管通常学历不高但深爱计算机、网络知识的钻研,所以只能选择到网吧来磨练技术。这类网管工作态度比较积极,随时都在学习并储备专业知识,也深受网吧经营者的欢迎,两者的关系真是如同伯乐与良马。但是在网吧这样一个工作环境理,计算机、网络专业知识只是一个网吧经营成功的因素之一,还有很多方面的专业知识可以学习,包括:财务管理、服务的理念、库存的管理,连锁经营…..等等,都是可以从你的老板身上学习的,或是可以透过你的用心去提升倚重你的老板。

不论你是怀抱着什么样的态度选择了担任这个工作,我们希望大家应有以下的态度来经营自己的未来:

热爱自己投身的网吧产业

今天我们提供了全国最大众化兼具教育与休闲的娱乐模式,在政府的重视与辅导下,他即将成为一个具影响力的产业,而遍布全国的网吧未来的影响力将不可限量,这是笔者再看过世界各地的网吧,深深觉得在中国这个市场里网吧将是最独特的一个产业,我们应该很有幸能够早别人一步投入这一个产业,把自己所在的网吧当作是自己的事业来经营,我们也应该自许这个产业因为我们的投入而将傲视全球。

终身学习的目标与方法

想要成为一位老板倚重的技术网管,首要的当然是技术要过硬,但现今技术发展的速度快速,以一个个人的学习速度都有所追赶不急,更不用说到对新技术的应用与创造。所以,在学习的目标上应该针对未来网吧运用上的相关技术来做个界定,例如:游戏内容的更新技术、各式各样功能的服务器架设与规划、安全性较高的LINUX系统、各种网络设备的调校与设定…等等。我们常说在巨人的肩膀上看世界,可以看得更高更远,所以在学习的方法上可以利用一些课程来学习,以提升学习的速度。像笔者任职的竞网数码就精心规划了一系列的网管提升课程,这些都是网管兄弟们可以好好利用的。另外还有就是可以团体学习的方法来加快成长的速度,组织网吧内、外的同好大家一起分头学习,定期交流,发挥整合的综效。最后,学习有时是需要经过一段苦涩枯燥的过程,才能尝到甜美有成就的果实,唯有毅力与坚持才能达到成长的境界。

新技术的时间价值

随着网络时代的来临,笔者发现一项新的科技技术它的生命周期是越来越短,也就是它的科技价值随着后续科技技术发展的推陈出新,导致他贬值的速度变的非常的快。所以,身为网管当你努力学习所得的成果应该尽速无私的跟你自己的团队和老板分享,共同思考如何创造出价值来提升网吧的服务质量、改善网吧的经营现况,而不是孤芳自赏,还深怕同侪学会后自己丧失优势地位,我们也常看到一些技术人员抱着这样的心态,到最后这一些人唯一存在的价值就是还记得一堆服务器的账号跟密码。所以,希望我们在了解了技术价值与时间的关联性以后,我们能好好利用新技术来创造自己的价值。

深刻去体认管理的价值

我们前面提过一个网吧经营成功的科技技术只因素之一,相对的是需要成本的投入,这意味着只要愿意投入成本,也就能获得相应的竞争条件,所以,在网吧的经营上最低阶的竞争就是「比配备」,配备比输人家以后就开始「比价格」,坦率的说,这种竞争很无趣也很幼稚,对产业的提升一点帮助也没有。所以透过适切管理方式去提升服务质量才是提升竞争优势的最佳方法。举一个简单的例子,要让网吧的厕所干净清香,会有人说只要请一个阿姨每天定时打扫即可,但我们认为只要有一张每日定期清洁值班表就可以了。其实管理也没有什么大道理,只要用心去观察网吧经营的每一个细节,再用心的去思考如何让每一个细节都能让玩家对我们的服务满意、员工们工作愉快、老板们都能赚钱开心。台湾的经营之神王永庆、香港的首富李嘉诚他们也都学历不高而能成功之道也就不外乎「用心」二字。我们衷心的希望我们的网管兄弟们,千万别再定位自己只是网吧的「网络管理者」甚至只是「机修」,应该提升自己眼界与心境去成为一个「网吧管理者」。

改变自己建立自信

我们常见到的网管兄弟们常常个性比较内向保守缺乏自信,其实很多的道理大家都明白,但就是没办法改变自己实践它,所以明白越多的道理心中的包袱越重,就越没自信,最后干脆躲到网络游戏的虚拟世界中,还自圆其说是因为工作需要才沉迷于游戏世界中,如果陈天桥、丁磊也跟各位一样相信绝不会造就今天的盛大跟网易。要了解游戏的趋势你可以走近店里的玩家,去跟他们聊一聊,玩家们会觉得你很亲切,你也可以节省很多时间跟精力。自己喜欢的道理或者自己觉得最需要的道理,先挑一两条改变自己确实去实践它,你的老板、你的同侪会看到你的改变、你的成长,老板们会给你更大的发挥空间,同侪们会给你更多的支持,信心会从而建立,人生会因此而不同。

最后,想给网吧的经营者一点友情的提示,我们辛苦的网管的兄弟们不舍昼夜地为网吧的正常营运而付出精力与青春,他们的生涯规划实质上已经与你的网吧与整个产业的发展紧紧的结合在一起,基于我们自身的收益与社会责任,让我们一起努力让「网管」所代表的不仅仅是「网络管理者」而能真正成为位业者赚钱为产业付出贡献的「网吧管理者」。

作者简介:高振修,台湾新竹交通大学MBA毕业,曾任巨盛资讯总经理、智冠电子市场总监、竞网数码总经理,在电子商务、网络游戏运营、网吧经营管理等领域有着多年的丰富经验。

交换机配置物理连接技术操作与图例

Filed under: 计算机网络 — liaowei @ 14:29

 由于笔记本电脑携带方便,所以配置交换机通常是采用笔记本电脑来进行的,实在没有笔记本的情况下,也可以采用台式机,但移动起来麻烦些。 
交换机的本地配置方式是通过计算机与交换机的“Console”端口直接连接的方式进行通信的,它的连接图如图1所示。


  可进行网络管理的交换机上一般都有一个“Console”端口(这个在前面介绍集线器时已作介绍,交换机也一样),它是专门用于对交换机进行配置和管理的。通过Console端口连接并配置交换机,是配置和管理交换机必须经过的步骤。虽然除此之外还有其他若干种配置和管理交换机的方式(如Web方式、Telnet方式等),但是,这些方式必须依靠通过Console端口进行基本配置后才能进行。因为其他方式往往需要借助于IP地址、域名或设备名称才可以实现,而新购买的交换机显然不可能内置有这些参数,所以通过Console端口连接并配置交换机是最常用、最基本也是网络管理员必须掌握的管理和配置方式。



图1



  不同类型的交换机Console端口所处的位置并不相同,有的位于前面板(如Catalyst 3200和Catalyst 4006),而有的则位于后面板(如Catalyst 1900和Catalyst 2900XL)。通常是模块化交换机大多位于前面板,而固定配置交换机则大多位于后面板。不过,倒不用担心无法找到Console端口,在该端口的上方或侧方都会有类似“CONSOLE”字样的标识,如图2所示。



图2



  除位置不同之外,Console端口的类型也有所不同,绝大多数(如Catalyst 1900和Catalyst 4006)都采用RJ-45端口(如图2所示),但也有少数采用DB-9串口端口(如Catalyst 3200)或DB-25串口端口(如Catalyst 2900)。


  无论交换机采用DB-9或DB-25串行接口,还是采用RJ-45接口,都需要通过专门的Console线连接至配置用计算机(通常称作终端)的串行口。与交换机不同的Console端口相对应,Console线也分为两种:一种是串行线,即两端均为串行接口(两端均为母头),两端可以分别插入至计算机的串口和交换机的Console端口;另一种是两端均为RJ-45接头(RJ-45-to-RJ-45)的扁平线。由于扁平线两端均为RJ-45接口,无法直接与计算机串口进行连接,因此,还必须同时使用一个如图3所示的RJ-45-to-DB-9(或RJ-45-to-DB-25)的适配器。通常情况下,在交换机的包装箱中都会随机赠送这么一条Console线和相应的DB-9或DB-25适配器。


了解路由器中的管理间距和量度参数

Filed under: 计算机网络 — liaowei @ 14:29

谈到路由协议和路由器时,管理间距(administrative distance)和量度(Metrics)是两组重要的参数。这两组参数真正的意思是什么呢?David Davis将向你介绍这两组参数,并解释了使用Cisco路由器需要了解管理参数重要性的原因。

当提到路由协议和路由器使用哪条通道时,管理间距和量度是两组重要参数。充分熟悉这两组参数对了解网络性能、可靠性以及回路选择等各个部分具有非常重要的作用。

如果你对管理间距和量度不太熟悉,你即便是看到了这些参数,也不会重视它们。如果你输入一条show ip route命令,你就会注意到在路由器后面的括弧里出现这两个参数。这里为一个例子:

O 10.1.103.0/24 [110/791] via 10.1.100.2, 00:39:44, Serial1/0:0.21

在这一例子中,110表示管理间距,791代表量度。通过输入相同的show ip route命令并指定路由器的方式,你可以看到更详细的信息,这有一个例子:

Router# show ip route 10.1.103.0Routing entry for 10.1.103.0/24 Known via “ospf 100″, distance 110, metric 791, type intra area Last update from 10.1.100.2 on Serial1/0:0.21, 01:09:25 ago Routing Descriptor Blocks: * 10.1.100.2, from 172.16.1.1, 01:09:25 ago, via Serial1/0:0.21 Route metric is 791, traffic share count is 1

但是,这些数字真正的意思是什么呢?让我们详细了解每一个参数的含义。

管理间距

管理间距(简称AD)即为路由器面对不同来源的两路相同通道时决定对哪路通道的选择。也就是说,如果路由器收到来自不同来源但是内容相同的信息的时候,路由器信任哪一条通道。一个比较好的办法是,由于要尽力选择局部信息,而全局信息多少有些重复事件,所以局部信息更值得信任。

如果你的路由器只有一个路由协议和一条WAN回路,或者如果你只使用静态路由,管理间距不会对你产生影响。但是这并不表示你不需要了解管理间距的作用。

但是如果你有一个比较复杂的网络系统,比如有两条WAN回路,或者你使用了两个路由协议(即使其中有一个是静态路由),你就更应该了解管理间距的重要性。

路由资源不只是诸如RIP、OSPF或者BGP这样一些路由协议,另外可能还有一些与路由器相连接的资源(比如路由器的界面)和静态路由(你作为管理服务器使用的路由器)。

路由器根据管理间距来选择信任哪路资源。管理间距越小,其路由资源就越值得信任。

为了便于作出这一决定,路由器安装了一个在所有可能资源和默认管理间距中展示的预程序安排表。表A提供了这一表格的示范。(虽然通过使用路由器配置模式中的distance命令,使管理器改变默认的管理间距,但这通常是一种不可取的方法。)

例如,如果路由器收到一个来自OSPF的路由和一个来自RIP的路由,它就会选择OSPF路由。因为OSPF的管理间距是110,而RIP的管理间距是120。

这里有另外一个例子:比如说,你的路由器收到一个来自EIGRP Internal路由,它的管理间距是90,但是你不小心把一个静态路由输入到一个IP地址中,这个IP地址的间距管理地址是1。那么路由器将使用静态路由而不会使用EIGRP路由。

最后强调一点:管理间距是CCNA考试中的重点。如果你正准备参加这场考试的话,一定要知道一般路由协议的管理间距。

量度

路由协议使用量度来确定当有两路有效路由可以送往同一目标文件时,把路由表放入哪个路由中。路由器把路由表放入量度最小的路由中,因为它认为这个路由是最近的因此是最好的。

与管理间距相反,量度只有一个路由协议。他们不能处理多个资源库中路由。

例如:输入一个show ip eigrp topology命令:

P 10.55.103.0/24, 1 successors, FD is 6049536 via 10.220.100.1 (6049536/5537536), Serial3/0 via 10.55.100.14 (52825600/281600), Tunnel55

注意这个EIGRP路由协议有两路路由输送给这个网络。但是,这个路由器只接受路由表中量度最短的其中一个路由。这有一个关于路由表条目的例子:

Router# show ip route 10.55.103.0Routing entry for 10.55.103.0/24 Known via “eigrp 100″, distance 120, metric 6049536, type internal Redistributing via eigrp 100 Last update from 10.220.100.1 on Serial3/0, 00:56:12 ago Routing Descriptor Blocks: * 10.220.100.1, from 10.220.100.1, 00:56:12 ago, via Serial3/0 Route metric is 6049536, traffic share count is 1 Total delay is 41000 microseconds, minimum bandwidth is 512 Kbit Reliability 226/255, minimum MTU 1500 bytes Loading 1/255, Hops 2

不同的路由协议对量度有不同的算法。RIP的算法是基于跳数的,OSPF是基于带宽,而EIGRP根据带宽、延滞时间、负荷和可靠度来决定的。

David Davis从事IT业已长达12年之久,而且获得了包括CCIE,MCSE+I, CISSP, CCNA, CCDA和CCNP在内的一系列证书。目前,他在一家私有零售公司担任系统/网络管理员职务,并担任网络系统的兼职顾问。

网络管理员与网吧管理员大相径庭

Filed under: 计算机网络 — liaowei @ 14:29

网络铺天盖地的普及以及网络行业不断细化的职责,使得多数人认为网络管理员其实就跟网吧管理员一样。这种认识是不正确的,也是由于对这两类职业的具体工作不熟悉导致的。在本文中即将向大家解释这两者所承担的不同行业职责。

一、起步要求及发展方向不一致

网吧属于赢利性场所,网吧管理员充当的其实是服务员的角色,其进入门槛低,通常会一些简单的单机及网络故障诊断技能即可。自然网吧管理员在整个网吧环境中,并不具备决定性的权利,相反顾客是上帝,而网吧管理员的工作是随顾客的意愿而开展。另外随着网络在家庭中的普及,相信这个行业会慢慢的淡出市场,从而导致网吧管理员这一职业也会随即消失。

而随着企业信息化的不断深入,每个企业都会将网络环境作为重要工作来开展,这对网络管理员就有一定要求,需要掌握较全面的网络综合知识。网络管理员具备对整个企业网络的绝对处理权,企业员工通常都需要按照网络管理员既定的规范去使用网络。而且随着企业网络的不断发展,企业对高素质的网络管理员必定求贤若渴,从这方面来说,网络管理员具有更长远的发展方向。

二、工作职责不一致

前面已经提到,网吧管理员所作的工作其实很少。稍正规的网吧,会要求网吧管理员具备一定的故障排查能力,以及对网络的简单维护;其中重要的工作是保证计算机软件系统的正常运行,比如用户出现的连网游戏故障等。而整个网吧网络环境的组建,通常与网吧管理员无任何关系,在网吧管理员进入某个网吧工作时,其网络组建已经完成;因此可以说,网吧管理员从事的工作实质上就是对顾客的服务。涉及到的专业知识相当少。

而网络管理员的工作职责则大不一样,其主要体现在以下几点:

首先,网络管理员需要具备必要的网络基础知识和基本素质,比如网络规划设计、网络传输协议、英文阅读能力、培训与沟通能力等。

其次,在具体的管理范围中要涉及到设计规划网络、配置和维护网络设备、搭建网络服务器、保障系统正常运行、制作和维护企业网站、保护网络安全以及保证数据安全等内容,要想胜任这些工作并良好的开展下去,对于网络管理员自身素质的要求是非常高的。

安全与保密是工作是网络管理重要的工作职责。安全主要指防止外部对网络的攻击和入侵,保密主要指防止网络内部信息的泄漏等。对于普通级别的网络,网络管理员的任务主要是配置管理好系统防火墙。为了能够及时发现和阻止网络黑客的攻击,可以加配入侵检测系统对关键服务提供安全保护。而对于安全保密级别要求高的网络,网络管理员除了应该采取上述措施外,还应该配备网络安全漏洞扫描系统,并对关键的网络服务器采取容灾的技术手段。

总结:综上所述,网吧管理员从事的是网络管理最表面的工作,甚至可以说其根本不具备网络管理的能力;此职业所体现的职责可以用“服务”来解释。而网络管理员倾向于较专业的网络方向,其职责范围涉及网络的方方面面,并对从业人员有较高的要求,此职业所体现的职责则可以用“管理”来解释。

网络中双绞线制作规范与连接规则

Filed under: 计算机网络 — liaowei @ 14:29

在网络组建过程中,双绞线的接线质量会影响网络的整体性能。双绞线在各种设备之间的接法也非常有讲究,应按规范连接。本文主要介绍双绞线的标准接法及其与各种设备的连接方法,目的是使大家掌握规律,提高工作效率,保证网络正常运行。

双绞线的标准接法

双绞线一般用于星型网络的布线,每条双绞线通过两端安装的RJ-45连接器(俗称水晶头)将各种网络设备连接起来。双绞线的标准接法不是随便规定的,目的是保证线缆接头布局的对称性,这样就可以使接头内线缆之间的干扰相互抵消。


超五类线是网络布线最常用的网线,分屏蔽和非屏蔽两种。如果是室外使用,屏蔽线要好些,在室内一般用非屏蔽五类线就够了,而由于不带屏蔽层,线缆会相对柔软些,但其连接方法都是一样的。一般的超五类线里都有四对绞在一起的细线,并用不同的颜色标明。


双绞线有两种接法:EIA/TIA 568B标准和EIA/TIA 568A标准。

T568A线序
1 2 3 4 5 6 7 8
绿白 绿 橙白 蓝 蓝白 橙 棕白 棕

T568B线序
1 2 3 4 5 6 7 8
橙白 橙 绿白 蓝 蓝白 绿 棕白 棕

直通线:两头都按T568B线序标准连接。

交叉线:一头按T568A线序连接,一头按T568B线序连接。

我们平时制作网线时,如果不按标准连接,虽然有时线路也能接通,但是线路内部各线对之间的干扰不能有效消除,从而导致信号传送出错率升高,最终影响网络整体性能。只有按规范标准建设,才能保证网络的正常运行,也会给后期的维护工作带来便利。

设备之间的连接方法
1. 网卡与网卡
10M、100M网卡之间直接连接时,可以不hub,应采用交叉线接法。

2.网卡与光收发模块
将网卡装在计算机上,做好设置;给收发器接上电源,严格按照说明书的要求操作;用双绞线把计算机和收发器连接起来,双绞线应为交叉线接法;用光跳线把两个收发器连接起来,如收发器为单模,跳线也应用单模的。光跳线连接时,一端接RX,另一端接TX,如此交叉连接。不过现在很多光模块都有调控功能,交叉线和直通线都可以用。

3.光收发模块与交换机
用双绞线把计算机和收发器连接起来,双绞线为直通线接法。

4.网卡与交换机
双绞线为直通线接法。

5.集线器与集线器(交换机与交换机)
两台集线器(或交换机)通过双绞线级联,双绞线接头中线对的分布与连接网卡和集线器时有所不同,必须要用交叉线。这种情况适用于那些没有标明专用级联端口的集线器之间的连接,而许多集线器为了方便用户,提供了一个专门用来串接到另一台集线器的端口,在对此类集线器进行级联时,双绞线均应为直通线接法。

用户如何判断自己的集线器(或交换机)是否需要交叉线连接呢?主要方法有以下几种:

★ 查看说明书
如果该集线器在级联时需要交叉线连接,一般会在设备说明书中进行说明。

★ 查看连接端口

如果该集线器在级联时不需要交叉线,大多数情况下都会提供一至两个专用的互连端口,并有相应标注,如“Uplink”、“MDI”、“Out to Hub”,表示使用直通线连接。

★ 实测

这是最管用的一种方法。可以先制作两条用于测试的双绞线,其中一条是直通线,另一条是交叉线。之后,用其中的一条连接两个集线器,这时注意观察连接端口对应的指示灯,如果指示灯亮表示连接正常,否则换另一条双绞线进行测试。

6. 交换机与集线器之间

交换机与集线器之间也可通过级联的方式进行连接。级联通常是解决不同品牌的交换机之间以及交换机与集线器之间连接的有效手段。

MAC地址的原理分析以及相关应用介绍

Filed under: 计算机网络 — liaowei @ 14:29

大家都知道在现实的生活中,我们每个人都有属于自己的一个ID号–身份证号码,你可以去派出所把你的姓名改了,但是你的身份证号却不能随着你自己的姓名更改而更改。在网络世界中,我们常常可以听到IP地址的概念,不过MAC地址这个专业术语却很少被人提起,我们往往只知道IP地址,而MAC地址则是幕后英雄。正如我们在日常交流的时候,常常叫别人的姓名而不会去称呼别人的身份证号道理是一样的。

  IP地址与MAC地址

  在日常的计算机使用过程中,大家都知道IP地址只要规划合理,你可以任意更改IP地址。修改的方法也是比较简单的,只要在对应网卡的TCP/IP协议上双击一下然后修改参数就行了。那么MAC地址与IP地址同为地址,它们之间有什么地方相似又有什么地方不同呢?下面就让我们一起来看看吧,了解它们的差异与类似之处便于我们更好的掌握。在OSI(Open System Interconnection,开放系统互连)7层网络协议参考模型中,第二层为数据链路层(Data Link)。MAC地址也叫物理地址、硬件地址或链路地址,由网络设备制造商生产时写在硬件内部。IP地址与MAC地址在计算机里都是以二进制表示的,IP地址是32位的,而MAC地址则是48位的。MAC地址的长度为48位(6个字节),通常表示为12个16进制数,每2个16进制数之间用冒号隔开,如:08:00:20:0A:8C:6D就是一个MAC地址,其中前6位16进制数08:00:20代表网络硬件制造商的编号,它由IEEE(电气与电子工程师协会)分配,而后3位16进制数0A:8C:6D代表该制造商所制造的某个网络产品(如网卡)的系列号。只要你不去更改自己的MAC地址,那么你的MAC地址在世界是惟一的。

  MAC地址的作用

  IP地址就如同一个职位,而MAC地址则好像是去应聘这个职位的人才,职位可以既可以让甲坐,也可以让乙坐,同样的道理一个节点的IP地址对于网卡是不做要求,基本上什么样的厂家都可以用,也就是说IP地址与MAC地址并不存在着绑定关系。本身有的计算机流动性就比较强,正如同人才可以给不同的单位干活的道理一样的,人才的流动性是比较强的。职位和人才的对应关系就有点像是IP地址与MAC地址的对应关系。比如,如果一个网卡坏了,可以被更换,而无须取得一个新的IP地址。如果一个IP主机从一个网络移到另一个网络,可以给它一个新的IP地址,而无须换一个新的网卡。当然MAC地址除了仅仅只有这个功能还是不够的,就拿人类社会与网络进行类比,通过类比,我们就可以发现其中的类似之处,更好地理解MAC地址的作用。无论是局域网,还是广域网中的计算机之间的通信,最终都表现为将数据包从某种形式的链路上的初始节点出发,从一个节点传递到另一个节点,最终传送到目的节点。数据包在这些节点之间的移动都是由ARP(Address Resolution Protocol:地址解析协议)负责将IP地址映射到MAC地址上来完成的。其实人类社会和网络也是类似的,试想在人际关系网络中,甲要捎个口信给丁,就会通过乙和丙中转一下,最后由丙转告给丁。在网络中,这个口信就好比是一个网络中的一个数据包。数据包在传送过程中会不断询问相邻节点的MAC地址,这个过程就好比是人类社会的口信传送过程。相信通过这两个例子,我们就可以进一步理解MAC地址的作用。

本新闻共3页,当前在第1页  1  2  3  

Powered by WordPress