存档:2009年四月

浅谈程序员的数学修养 (转)

四月 30, 2009 | 数学 | RSS 2.0

浅谈程序员的数学修养

    可能有很多朋友在网上看过google公司早几年的招聘广告,它的第一题如下了:{first 10-digit primefound in consecutive digitse}.com,e中出现的连续的第一个10个数字组成的质数。据说当时这个试题在美国很多地铁的出站口都有大幅广告,只要正确解答了这道题,在浏览器的地址栏中输入这个答案,就可以进入下一轮的测试,整个测试过程如同一个数学迷宫,直到你成为google的一员。

  又如Intel某年的一道面试题目:巴拿赫病故于1945年8月31日。他的出生年份恰好是他在世时某年年龄的平方,问:他是哪年出生的?这道看似很简单的数学问题,你能不能能快地解答呢?

  下面则是一道世界第一大软件公司微软的招聘测试题:中间只隔一个数字的两个素数被称为素数对,比如5和7,17和19,证明素数对之间的数字总能被6整除(假设这两个素数都大于6),现在证明没有由三个素数组成的素数对。这样的试题还有很多很多,这些题目乍初看上去都是一些数学问题。但是世界上一些著名的公司都把它们用于招聘测试,可见它们对新员工数学基础的重视。数学试题与应用程序试题是许多大型软件公司面试中指向性最明显的一类试题,这些试题就是考察应聘者的数学能力与计算机能力。

  某咨询公司的一名高级顾问曾说:微软是一家电脑软件公司,当然要求其员工有一定的计算机和数学能力,面试中自然就会考察这类能力。微软的面试题目就考察了应聘人员对基础知识的掌握程度、对基础知识的应用能力,甚至暗含了对计算机基本原理的考察。所以,这样的面试题目的确很“毒辣”,足以筛选到合适的人。

  四川大学数学学院的曹广福教授曾说过:“一个大学生将来的作为与他的数学修养有很大的关系”。大学计算机专业学生都有感触,计算机专业课程中最难的几门课程莫过于离散数学、编译原理、数据结构,当然像组合数学、密码学、计算机图形学等课程也令许多人学起来相当吃力,很多自认为数据库学得很好的学生在范式、函数依赖、传递依赖等数学性比较强的概念面前感到力不从心,这些都是因为数学基础或者说数学知识的缺乏所造成的。

  数学是计算机的基础,这也是为什么考计算机专业研究生数学都采用最难试题(数学一)的原因,当然这也能促使一些新的交叉学科如数学与应用软件、信息与计算科学专业等飞速发展。许多天才程序员本身就是数学尖子,众所周知,BillGates的数学成绩一直都很棒,他甚至曾经期望当一名数学教授,他的母校——湖滨中学的数学系主任弗雷福·赖特曾这样谈起过他的学生:“他能用一种最简单的方法来解决某个代数或计算机问题,他可以用数学的方法来找到一条处理问题的捷径,我教了这么多年的书,没见过像他这样天分的数学奇才。他甚至可以和我工作过多年的那些优秀数学家媲美。当然,比尔也各方面表现得都很优秀,不仅仅是数学,他的知识面非常广泛,数学仅是他众多特长之一。”。影响一代中国程序人的金山软件股份有限公司董事长求伯君当年高考数学成绩满分进一步说明了问题。很多数学基础很好的人,一旦熟悉了某种计算机语言,他可以很快地理解一些算法的精髓,使之能够运用自如,并可能写出时间与空间复杂度都有明显改善的算法。

  程序设计当中解决的相当一部分问题都会涉及各种各样的科学计算,这需要程序员具有什么样的基础呢?实际问题转换为程序,要经过一个对问题抽象的过程,建立起完善的数学模型,只有这样,我们才能建立一个设计良好的程序。从中我们不难看出数学在程序设计领域的重要性。算法与计算理论是计算机程序设计领域的灵魂所在,是发挥程序设计者严谨,敏锐思维的有效工具,任何的程序设计语言都试图将之发挥得淋漓尽致。

  程序员需要一定的数学修养,不但是编程本身的需要,同时也是培养逻辑思维以及严谨的编程作风的需要。数学可以锻炼我们的思维能力,可以帮助我们解决现实中的问题。可以帮助我们更高的学习哲学。为什么经常有人对一些科学计算程序一筹莫展,他可以读懂每一行代码,但是却无法预测程序的预测结果,甚至对程序的结构与功能也一知半解,给他一个稍微复杂点的数学公式,他可能就不知道怎么把它变成计算机程序。很多程序员还停留在做做简单的MIS,设计一下MDI,写写简单的Class或用SQL语句实现查询等基础的编程工作上,对于一些需要用到数学知识的编程工作就避而远之,当然实现一个累加程序或者一个税率的换算程序还是很容易的,因为它们并不需要什么高深的数学知识。

  一名有过10多年开发经验的老程序员曾说过:“所有程序的本质就是逻辑。技术你已经较好地掌握了,但只有完成逻辑能力的提高,你才能成为一名职业程序员。打一个比方吧,你会十八般武艺,刀枪棍棒都很精通,但就是力气不够,所以永远都上不了战场,这个力气对程序员而言就是逻辑能力(其本质是一个人的数学修养,注意,不是数学知识)。”

  程序员的数学修养不是一朝一夕就可以培养的。数学修养与数学知识不一样,修养需要一个长期的过程,而知识的学习可能只是一段短暂的时间。下面是一些我个人对于程序员如何提高与培养自己的数学修养的基本看法。

  首先,应该意识到数学修养的重要性。作为一个优秀的程序员,一定的数学修养是十分重要也是必要的。数学是自然科学的基础,计算机科学实际上是数学的一个分支。计算机理论其实是很多数学知识的融合,软件工程需要图论,密码学需要数论,软件测试需要组合数学,计算机程序的编制更需要很多的数学知识,如集合论、排队论、离散数学、统计学,当然还有微积分。计算机科学一个最大的特征是信息与知识更新速度很快,随着数学知识与计算机理论的进一步结合,数据挖掘、模式识别、神经网络等分支科学得到了迅速发展,控制论、模糊数学、耗散理论、分形科学都促进了计算机软件理论、信息管理技术的发展。严格的说,一个数学基础不扎实的程序不能算一个合格的程序员,很多介绍计算机算法的书籍本身也就是数学知识的应用与计算机实现手册。

  其次,自身数学知识的积累,培养自己的空间思维能力和逻辑判断能力。数学是一门分支众多的学科,我们无法在短暂的一生中学会所有的数学知识,像泛函理论、混沌理论以及一些非线性数学问题不是三五几天就可以掌握的。数学修养的培养并不在与数学知识的多少,但要求程序员有良好的数学学习能力,能够很快地把一些数学知识和自己正在解决的问题联系起来,很多理学大师虽然不是数学出身,但是他们对数学有很强的理解能力和敏锐的观察力,于是一系列新的学科诞生了,如计算化学、计算生物学、生物信息学、化学信息学、计算物理学,计算材料学等等。数学是自然学科的基础,计算机技术作为理论与实践的结合,更需要把数学的一些精髓融入其中。从计算机的诞生来看它就是在数学的基础上产生的,最简单的0、1进制就是一个古老的数学问题。程序设计作为一项创造性很强的职业,它需要程序员有一定的数学修养,也具有一定的数学知识的积累,可以更好地把一些数学原理与思想应用于实际的编程工作中去。学无止境,不断的学习是提高修养的必经之路。

  第三,多在实践中运用数学。有些高等学校开设了一门这样的课程——《数学建模》。我在大学时期也曾学过,这是一门内容很丰富的课程。它把很多相关的学科与数学都联系在一起,通过很多数学模型来解决实际的生产生活问题,很多问题的解决需要计算机程序来实现。我在大学和研究生阶段都参加过数学建模竞赛,获得了不少的经验,同时也进一步提高了自己的数学修养。实际上,现在的程序设计从某些角度来看就是一个数学建模的过程,模型的好坏关系到系统的成败,现在数学建模的思想已经用于计算机的许多相关学科中,不单只是计算机程序设计与算法分析。应该知道,数学是一门需要在实践中展示其魅力的科学,而计算机程序也是为帮助解决实际问题而编制的,因此,应该尽量使它们结合起来,在这个方面,计算机密码学是我认为运用数学知识最深最广泛的,每一个好的加密算法后面都有一个数学理论的支持,如椭圆曲线、背包问题、素数理论等。作为一名优秀的程序员,应该在实际工作中根据需要灵活运用数学知识,培养一定的数学建模能力,善于归纳总结,慢慢使自己的数学知识更加全面,数学修养得到进一步提高。

  第四,程序员培养制度与教学的改革。许多程序员培养体制存在很多缺陷,一开始就要求学员能够快速精通某种语言,以语言为中心,对算法的核心思想与相关的数学知识都一笔带过,讲得很少,这造成很多程序员成为背程序的机器,这样不利于程序员自身的快速成长,也不利于程序员解决新问题。我在长期的程序员培训与计算机教学工作采用了一些与传统方式不一致的方法,收到了一定的效果。很多初学程序的人往往写程序时有时候会有思维中断,或者对一些稍难的程序觉得无法下手,我采用了一些课前解决数学小问题的方法来激励大家的学习兴趣,这些小问题不单单是脑筋急转弯,其中不少是很有代表意义的数学思考题。通过数学问题来做编程的热身运动,让学员在数学试题中激发自己的思维能力,记得有位专家曾经说过,经常做做数学题目会使自己变聪明,很长时间不去接触数学问题会使自己思维迟钝。通过一些经典的数学问题来培养学员的思维的严谨性和跳跃性。很多人可能不以为然,其实有些看似简单的问题并不一定能够快速给出答案,大脑也是在不断的运用中变更加灵活的。不信吗?大家有兴趣可以做做下面这道题目,看看能不能在1分钟之内想到答案,这只是一道小学数学课后习题。很多人认为自己的数学基础很好,但是据说这道题目90%以上的人不能在一个小时内给出正确答案。试试,如果你觉得我说的是错的。

  证明:AB+AC>DB+DC(D为三角形ABC的一个内点)。

  最后,多学多问,多看好书,看经典。我在这里向大家推荐两部可能大家已经很熟悉的经典的计算机算法教材,它们中间很多内容其实就是数学知识的介绍。第一部是《算法导论》,英文名称:Introduction to Algorithms,作者:Thomas H. Cormen,Charles E. Leiserson ,Ronald L. Rivest ,CliffordStein。本书的主要作者来自麻省理工大学计算机,作者之一Ronald L.Rivest由于其在公开秘钥密码算法RSA上的贡献获得了图灵奖。这本书目前是算法的标准教材,美国许多名校的计算机系都使用它,国内有些院校也将本书作为算法课程的教材。另外许多专业人员也经常引用它。本书基本包含了所有的经典算法,程序全部由伪代码实现,这更增添了本书的通用性,使得利用各种程序设计语言进行程序开发的程序员都可以作为参考。语言方面通俗,很适合作为算法教材和自学算法之用。另一部是很多人都应该知道的Donald.E.Knuth所著《计算机程序设计艺术》,英文名称:The Art of Computer Programming。Donald.E.Knuth人生最辉煌的时刻在斯坦福大学计算机系渡过,美国计算机协会图灵奖的获得者,是本领域内当之无愧的泰斗。有戏言称搞计算机程序设计的不认识Knuth就等于搞物理的不知道爱因斯坦,搞数学的不知道欧拉,搞化学的不知道道尔顿。被简称为TAOCP的这本巨著内容博大精深,几乎涵盖了计算机程序设计算法与理论最重要的内容。现在发行的只有三卷,分别为基础运算法则,半数值算法,以及排序和搜索(在写本文之际,第四卷已经出来了,我也在第一时间抢购了一本)。本书结合大量数学知识,分析不同应用领域中的各种算法,研究算法的复杂性,即算法的时间、空间效率,探讨各种适用算法等,其理论和实践价值得到了全世界计算机工作者的公认。书中引入的许多术语、得到的许多结论都变成了计算机领域的标准术语和被广泛引用的结果。另外,作者对有关领域的科学发展史也有深入研究,因此本书介绍众多研究成果的同时,也对其历史渊源和发展过程做了很好的介绍,这种特色在全球科学著作中是不多见的。至于本书的价值我觉得BillGates先生的话足以说明问题:“如果你认为你是一名真正优秀的程序员读Knuth的《计算机程序设计艺术》,如果你能读懂整套书的话,请给我发一份你的简历”。作者数学方面的功底造就了本书严谨的风格,虽然本书不是用当今流行的程序设计语言描述的,但这丝毫不损伤它“程序设计史诗”的地位。道理很简单,它内涵的设计思想是永远不会过时的。除非英语实在有困难,否则建议读者选用英文版。我个人就是阅读的该书的英文版,虽然花了不少money和时间,但是收获颇丰,值得。

  总之,要想成为一名有潜力有发展前途的程序员,或者想成为程序员中的佼佼者,你一定要培养良好的数学修养。切记:对于一名能够灵活自如编写各种程序的人,数学是程序的灵魂。

没有评论 »

常见的数学思想(转)

四月 30, 2009 | 数学 | RSS 2.0

  对于搞工程和技术的朋友来讲,在工作中常常遇到一些实际问题,而采用常规的思维方式无法很好的解决这些问题,那么这个时候我们就需要用数学语言和数学工具,而使用数学工具的前提却是用数学思想的方法来描述问题。。下面转帖几种常用的数学思想方法,仅供学习和参考

  函数思想
  把某一数学问题用函数表示出来,并且利用函数探究这个问题的一般规律。这是最基本、最常用的数学方法。

  数形结合思想
  “数无形,少直观,形无数,难入微”,利用“数形结合”可使所要研究的问题化难为易,化繁为简。把代数和几何相结合,例如对几何问题用代数方法解答,对代数问题
用几何方法解答,这种方法在解析几何里最常用。例如求根号((a-1)^2+(b-1)^2)+根号(a^2+(b-1)^2)+根号((a-1)^2+b^2)+根号(a^2+b^2)的最小值,就可以把它放在坐标系中
,把它转化成一个点到(0,1)、(1,0)、(0,0)、(1,1)四点的距离,就可以求出它的最小值。

  分类讨论思想
  当一个问题因为某种量的情况不同而有可能引起问题的结果不同时,需要对这个量的各种情况进行分类讨论。比如解不等式|a-1|>4的时候,就要讨论a的取值情况。

  方程思想
  当一个问题可能与某个方程建立关联时,可以构造方程并对方程的性质进行研究以解决这个问题。例如证明柯西不等式的时候,就可以把柯西不等式转化成一个二次方程的判别式。

  整体思想
  从问题的整体性质出发,突出对问题的整体结构的分析和改造,发现问题的整体结构特征,善于用“集成”的眼光,把某些式子或图形看成一个整体,把握它们之间的关联
,进行有目的的、有意识的整体处理。整体思想方法在代数式的化简与求值、解方程(组)、几何解证等方面都有广泛的应用,整体代入、叠加叠乘处理、整体运算、整体设元
、整体处理、几何中的补形等都是整体思想方法在解数学问题中的具体运用。

(整体思想和系统论密切相关,请参看我的博文,钱学森系统方法论)

  转化思想
  在于将未知的,陌生的,复杂的问题通过演绎归纳转化为已知的,熟悉的,简单的问题。三角函数,几何变换,因式分解,解析几何,微积分,乃至古代数学的尺规作等数
学理论无不渗透着转化的思想。常见的转化方式有:一般 特殊转化,等价转化,复杂 简单转化,数形转化,构造转化,联想转化,类比转化等。

  隐含条件思想
  没有明文表述出来,但是根据已有的明文表述可以推断出来的条件,或者是没有明文表述,但是该条件是一个常规或者真理。

  类比思想
  把两个(或两类)不同的数学对象进行比较,如果发现它们在某些方面有相同或类似之处,那么就推断它们在其他方面也可能有相同或类似之处。

  建模思想
  为了描述一个实际现象更具科学性,逻辑性,客观性和可重复性,人们采用一种普遍认为比较严格的语言来描述各种现象,这种语言就是数学。使用数学语言描述的事物就
称为数学模型。有时候我们需要做一些实验,但这些实验往往用抽象出来了的数学模型作为实际物体的代替而进行相应的实验,实验本身也是实际操作的一种理论替代。

  化归思想
  化归思想就是化未知为已知,化繁为简,化难为易.如将分式方程化为整式方程,将代数问题化为几何问题,将四边形问题转化为三角形问题等.实现这种转化的方法有:待定系数法,配方法,整体代人法以及化动为静,由抽象到具体等转化思想

  归纳推理思想
  由某类事物的部分对象具有某些特征,推出该类事物的全部对象都具有这些特征的推理,或者由个别事实概括出一般结论的推理称为归纳推理(简称归纳),简言之,归纳推理是由部分到整体,由个别到一般的推理

  概率统计思想
  另外,还有概率统计思想等数学思想,例如概率统计思想是指通过概率统计解决一些实际问题,如摸奖的中奖率、某次考试的综合分析等等。另外,还可以用概率方法解决一些面积问题。

1条评论 »

分类算法的思考

四月 23, 2009 | 数据结构算法 | RSS 2.0

1 常见的多极分类和无限级的分类可以考虑用关系数据库实现
如设计的数据结构是
CREATE TABLE `category` (
  `categoryID` smallint(5) unsigned NOT NULL auto_increment,
  `categoryParentID` smallint(5) unsigned NOT NULL default ‘0′,
  `categoryName` varchar(50) NOT NULL default ”,
  PRIMARY KEY  (`categoryID`)
)
通过sql就可以做到多极分类和无限级别分类。
2 通过一个大数字,按照类别进行分段,如1110,1000000,000000,000000,和1110,10101001,0100010,0010001,相与,如果第一段相等则第一类相同,有点像ip的分类地址思想
如a类地址,b类地址,求起网络地址,和子网掩码相与的思想,这个思想也基本上满足常见的分类。
3 这个算法是google的数学之美的,对新闻进行自动分类,人工智能分类,用到的知识竟然是余弦定理。
地址是http://www.googlechinablog.com/2006/07/12.html

没有评论 »

数据结构系列八 简单树

四月 23, 2009 | 数据结构算法 | RSS 2.0

好久没更新博客了,一周时间都在思考些东西,有些东西都是不好写在博客上,感觉太浅薄,最近是继续看了好多数学等其它方面的东西,如生命的游戏,数学史等,总感觉自己思考的不够成熟,思维不够严密。暂时记录点自己开学习c的路程,though it’s to simple ,but this can give me a   hope .

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
struct tnode {
  char data;
  struct tnode *lchild;
  struct tnode *rchild;
 };
struct tnode tree;
void createtree(struct tnode *t) {
    struct tnodeee *p=t;
    char check;
    if(p->lchild == NULL || p->rchild ==NULL) {}
        printf(”please enter the data”);
        scanf(”%c”,&(p->data));
        getchar();
    }
    if(p->lchild == NULL) {
        printf(”%c left child is null Add ? y”,p->data);
        scanf(”%c”,&check);
        getchar();
        if(check == ‘y’) {
            struct tnode *q =(struct tnode*)malloc(sizeof(struct tnode));

            q->lchild =NULL;
            q->rchild =NULL;
            p->lchild =q;
            createtree(q);
        }
    
    
    }
    if(p->rchild == NULL) {
        printf(”%c right child is null Add ? y”,p->data);
        scanf(”%c”,&check);
        getchar();
        if(check == ‘y’) {
            struct tnode *q =(struct tnode*)malloc(sizeof(struct tnode));

            q->lchild =NULL;
            q->rchild =NULL;
            p->rchild =q;
            createtree(q);
        }
        
    
    
    
    }

}
 

void preorder(struct tnode *t)  {

    if(t) {
        printf(”%c”,t->data);
        preorder(t->lchild);
        preorder(t->rchild);
    }
}

void inorder(struct tnode *t)  {

    if(t) {
        inorder(t->lchild);
        printf(”%c”,t->data);
        inorder(t->rchild);
    }
}
void postorder(struct tnode *t)  {

    if(t) {
        postorder(t->lchild);
        postorder(t->rchild);
        printf(”%c”,t->data);
    }
}
void main () {
    clrscr();
    tree.lchild = NULL;
    tree.rchild = NULL;
    createtree(&tree);
    preorder(&tree);
    printf(”");
    inorder(&tree);
    printf(”");
    po

没有评论 »

使用ETags减少Web应用带宽和负载(转)

四月 22, 2009 | linux, 网站架构 | RSS 2.0

转自http://www.infoq.com/cn/articles/etags

介绍

最近,大众对于REST风格应用架构表现出强烈兴趣,这表明Web的优雅设计开始受到人们的注意。现在,我们逐渐理解了“3W架构(Architectureof the World WideWeb)”内在所蕴含的可伸缩性和弹性,并进一步探索运用其范式的方法。本文中,我们将探究一个可被Web开发者利用的、鲜为人知的工具,不引人注意的“ETag响应头(ETag ResponseHeader)”,以及如何将它集成进基于Spring和Hibernate的动态Web应用,以提升应用程序性能和可伸缩性。

我们将要使用的Spring框架应用是基于“宠物诊所(petclinic)”的。下载文件中包含了关于如何增加必要的配置及源码的说明,你可以自己尝试。

什么是“ETag”?

HTTP协议规格说明定义ETag为“被请求变量的实体值” (参见 http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html —— 章节 14.19)。 另一种说法是,ETag是一个可以与Web资源关联的记号(token)。典型的Web资源可以一个Web页,但也可能是JSON或XML文档。服务器单独负责判断记号是什么及其含义,并在HTTP响应头中将其传送到客户端。

ETag如何帮助提升性能?

聪明的服务器开发者会把ETags和GET请求的“If-None-Match”头一起使用,这样可利用客户端(例如浏览器)的缓存。因为服务器首先产生ETag,服务器可在稍后使用它来判断页面是否已经被修改。本质上,客户端通过将该记号传回服务器要求服务器验证其(客户端)缓存。

其过程如下:

  1. 客户端请求一个页面(A)。
  2. 服务器返回页面A,并在给A加上一个ETag。
  3. 客户端展现该页面,并将页面连同ETag一起缓存。
  4. 客户再次请求页面A,并将上次请求时服务器返回的ETag一起传递给服务器。
  5. 服务器检查该ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304(未修改——Not Modified)和一个空的响应体。

本文的其余部分将展示在基于Spring框架的Web应用中利用ETag的两种方法,该应用使用SpringMVC。首先我们将使用Servlet 2.3 Filter,利用展现视图(renderedview)的MD5校验和(checksum)以实现生成ETag的方法(一个“浅显的”ETag实现)。 第二种方法使用更为复杂的方法追踪view中所使用的model,以确定ETag有效性(一个“深入的”ETag实现)。尽管我们使用的是Spring MVC,但该技术可以应用于任何MVC风格的Web框架。

在我们继续之前,强调一下这里所展现的是提升动态产生页面性能的技术。已有的优化技术也应作为整体优化和应用性能特性调整分析的一部分来考虑。(见下)。

自顶向下的Web缓存

本文主要涉及对动态生成页面使用HTTP缓存技术。当考虑提升Web应用的性能的时候,应采取一个整体的、自顶向下的方法。为了这一目的,理解HTTP请求经过的各层是很重要的,应用哪些适当的技术取决于你所关注的热点。例如:

  • 将Apache作为Servlet容器的前端,来处理如图片和javascript脚本这样的静态文件,而且还可以使用FileETag指令创建ETag响应头。
  • 使用针对javascript文件的优化技术,如将多个文件合并到一个文件中以及压缩空格。
  • 利用GZip和缓存控制头(Cache-Control headers)。
  • 为确定你的Spring框架应用的痛处所在,可以考虑使用 JamonPerformanceMonitorInterceptor
  • 确信你充分利用ORM工具的缓存机制,因此对象不需要从数据库中频繁的再生。花时间确定如何让查询缓存为你工作是值得的。
  • 确保你最小化数据库中获取的数据量,尤其是大的列表。如果每个页面只请求大列表的一个小子集,那么大列表的数据应由其中某个页面一次获得。
  • 使放入到HTTP session中的数据量最小。这样内存得到释放,而且当将应用集群的时候也会有所帮助。
  • 使用数据库明细(database profiling)工具来查看在查询的时候使用了什么索引,在更新的时候整个表没有被上锁。

当然,应用性能优化的至理名言是:两次测量,一次剪裁(measure twice, cut once)。哦,等等,这是对木工而言的!没错,但是它在这里也很适用!

ETag Filter内容体

我们要考虑的第一种方法是创建一个Servlet Filter,它将基于页面(MVC中的“View”)的内容产生其ETag记号。乍一看,使用这种方法所获得的任何性能提升看起来都是违反直觉的。我们仍然不得不产生页面,而且还增加了产生记号的计算时间。然而,这里的想法是减少带宽使用。在大的响应时间情形下,如你的主机和客户端分布在这个星球的两端,这很大程度上是有益的。我曾见过东京办公室使用纽约服务器上托管的应用,其响应时间达到了 350 ms。随着并发用户数的增长,这将变成巨大的瓶颈。

代码

我们用来产生记号的技术是基于从页面内容计算MD5哈希值。这通过在响应之上创建一个包装器来实现。该包装器使用字节数组来保存所产生的内容,在filter链处理完成之后我们利用数组的MD5哈希值计算记号。

doFilter方法的实现如下所示。

 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {  HttpServletRequest servletRequest = (HttpServletRequest) req;  HttpServletResponse servletResponse = (HttpServletResponse) res;

   ByteArrayOutputStream baos = new ByteArrayOutputStream();  ETagResponseWrapper wrappedResponse = new ETagResponseWrapper(servletResponse, baos);  chain.doFilter(servletRequest, wrappedResponse);

   byte[] bytes = baos.toByteArray();

   String token = '"' + ETagComputeUtils.getMd5Digest(bytes) + '"';  servletResponse.setHeader("ETag", token); // always store the ETag in the header

   String previousToken = servletRequest.getHeader("If-None-Match");  if (previousToken != null && previousToken.equals(token)) { // compare previous token with current one   logger.debug("ETag match: returning 304 Not Modified");   servletResponse.sendError(HttpServletResponse.SC_NOT_MODIFIED);   // use the same date we sent when we created the ETag the first time through   servletResponse.setHeader("Last-Modified", servletRequest.getHeader("If-Modified-Since"));  } else  {   // first time through - set last modified time to now    Calendar cal = Calendar.getInstance();   cal.set(Calendar.MILLISECOND, 0);   Date lastModified = cal.getTime();   servletResponse.setDateHeader("Last-Modified", lastModified.getTime());

    logger.debug("Writing body content");   servletResponse.setContentLength(bytes.length);   ServletOutputStream sos = servletResponse.getOutputStream();   sos.write(bytes);   sos.flush();   sos.close();  } } 

清单 1:ETagContentFilter.doFilter

你需注意到,我们还设置了Last-Modified头。这被认为是为服务器产生内容的正确形式,因为其迎合了不认识ETag头的客户端。

下面的例子使用了一个工具类EtagComputeUtils来产生对象所对应的字节数组,并处理MD5摘要逻辑。我使用了javax.security MessageDigest来计算MD5哈希码。

public static byte[] serialize(Object obj) throws IOException {  byte[] byteArray = null;  ByteArrayOutputStream baos = null;  ObjectOutputStream out = null;  try {   // These objects are closed in the finally.   baos = new ByteArrayOutputStream();   out = new ObjectOutputStream(baos);   out.writeObject(obj);   byteArray = baos.toByteArray();  } finally {   if (out != null) {    out.close();   }  }  return byteArray; }

  public static String getMd5Digest(byte[] bytes) {  MessageDigest md;  try {   md = MessageDigest.getInstance("MD5");  } catch (NoSuchAlgorithmException e) {   throw new RuntimeException("MD5 cryptographic algorithm is not available.", e);  }  byte[] messageDigest = md.digest(bytes);  BigInteger number = new BigInteger(1, messageDigest);  // prepend a zero to get a "proper" MD5 hash value  StringBuffer sb = new StringBuffer('0');  sb.append(number.toString(16));  return sb.toString(); }

清单 2:ETagComputeUtils

直接在web.xml中配置filter。

    <filter>       <filter-name>ETag Content Filter</filter-name>       <filter-class>org.springframework.samples.petclinic.web.ETagContentFilter</filter-class>     </filter>

      <filter-mapping>       <filter-name>ETag Content Filter</filter-name>       <url-pattern>/*.htm</url-pattern>     </filter-mapping>

清单 3:web.xml中配置filter。

每个.htm文件将被EtagContentFilter过滤,如果页面自上次客户端请求后没有改变,它将返回一个空内容体的HTTP响应。

我们在这里展示的方法对特定类型的页面是有用的。但是,该方法有两个缺点:

  • 我们是在页面已经被展现在服务器之后计算ETag的,但是在返回客户端之前。如果有Etag匹配,实际上并不需要再为model装进数据,因为要展现的页面不需要发送回客户端。
  • 对于类似于在页脚显示日期时间这样的页面,即使内容实际上并没有改变,每个页面也将是不同的。

下一节,我们将着眼于另一种方法,其通过理解更多关于构造页面的底层数据来克服这些问题的某些限制。

ETag拦截器(Interceptor)

Spring MVC HTTP请求处理途径中包括了在一个controller前插接拦截器(Interceptor)的能力,因而有机会处理请求。这儿是应用我们ETag比较逻辑的理想场所,因此如果我们发现构建一个页面的数据没有发生变化,我们可以避免进一步处理。

这儿的诀窍是你怎么知道构成页面的数据已经改变了?为了达到本文的目的,我创建了一个简单的ModifiedObjectTracker,它通过Hibernate事件侦听器清楚地知道插入、更新和删除操作。该追踪器为应用程序的每个view维护一个唯一的号码,以及一个关于哪些Hibernate实体影响每个view的映射。每当一个POJO被改变了,使用了该实体的view的计数器就加1。我们使用该计数值作为ETag,这样当客户端将ETag送回时我们就知道页面背后的一个或多个对象是否被修改了。

代码

我们就从ModifiedObjectTracker开始吧:

public interface ModifiedObjectTracker {  void notifyModified(> String entity); }

够简单吧?这个实现还有一点更有趣的。任何时候一个实体改变了,我们就更新每个受其影响的view的计数器:

public void notifyModified(String entity) {  // entityViewMap is a map of entity -> list of view names  List views = getEntityViewMap().get(entity);

   if (views == null) {   return; // no views are configured for this entity  }

   synchronized (counts) {   for (String view : views) {    Integer count = counts.get(view);    counts.put(view, ++count);   }  } }

一个“改变”就是插入、更新或者删除。这里给出的是侦听删除操作的处理器(配置为Hibernate 3 LocalSessionFactoryBean上的事件侦听器):

public class DeleteHandler extends DefaultDeleteEventListener {  private ModifiedObjectTracker tracker;

   public void onDelete(DeleteEvent event) throws HibernateException {   getModifiedObjectTracker().notifyModified(event.getEntityName());  }

  public ModifiedObjectTracker getModifiedObjectTracker() {   return tracker;  }   public void setModifiedObjectTracker(ModifiedObjectTracker tracker) {   this.tracker = tracker;  } }

ModifiedObjectTracker通过Spring配置被注入到DeleteHandler中。还有一个SaveOrUpdateHandler来处理新建或更新POJO。

如果客户端发送回当前有效的ETag(意味着自上次请求之后我们的内容没有改变),我们将阻止更多的处理,以实现我们的性能提升。在Spring MVC里,我们可以使用HandlerInterceptorAdaptor并覆盖preHandle方法:

public final boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {  String method = request.getMethod();  if (!"GET".equals(method))   return true;

   String previousToken = request.getHeader("If-None-Match");  String token = getTokenFactory().getToken(request);

   // compare previous token with current one  if ((token != null) && (previousToken != null && previousToken.equals('"' + token + '"'))) {   response.sendError(HttpServletResponse.SC_NOT_MODIFIED);   // re-use original last modified timestamp   response.setHeader("Last-Modified", request.getHeader("If-Modified-Since"))   return false; // no further processing required  }

   // set header for the next time the client calls  if (token != null) {    response.setHeader("ETag", '"' + token + '"');

    // first time through - set last modified time to now   Calendar cal = Calendar.getInstance();   cal.set(Calendar.MILLISECOND, 0);   Date lastModified = cal.getTime();   response.setDateHeader("Last-Modified", lastModified.getTime());  }

   return true; }

我们首先确信我们正在处理GET请求(与PUT一起的ETag可以用来检测不一致的更新,但其超出了本文的范围。)。如果该记号与上次我们发送的记号相匹配,我们返回一个“304未修改”响应并“短路”请求处理链的其余部分。否则,我们设置ETag响应头以便为下一次客户端请求做好准备。

你需注意到我们将产生记号逻辑抽出到一个接口中,这样可以插接不同的实现。该接口有一个方法:

public interface ETagTokenFactory {  String getToken(HttpServletRequest request); }

为了把代码清单减至最小,SampleTokenFactory的简单实现还担当了ETagTokenFactory的角色。本例中,我们通过简单返回请求URI的更改计数值来产生记号:

public String getToken(HttpServletRequest request) {  String view = request.getRequestURI();  Integer count = counts.get(view);  if (count == null) {   return null;  }

   return count.toString(); }

大功告成!

会话

这里,如果什么也没改变,我们的拦截器将阻止任何搜集数据或展现view的开销。现在,让我们看看HTTP头(借助于LiveHTTPHeaders),看看到底发生了什么。下载文件中包含了配置该拦截器的说明,因此owner.htm“能够使用ETag”:

我们发起的第一个请求说明该用户已经看过了这个页面:

----------------------------------------------------------  http://localhost:8080/petclinic/owner.htm?ownerId=10  

 GET /petclinic/owner.htm?ownerId=10 HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8 X-lori-time-1: 1182364348062 If-Modified-Since: Wed, 20 Jun 2007 18:29:03 GMT If-None-Match: "-1"

 HTTP/1.x 304 Not Modified Server: Apache-Coyote/1.1 Date: Wed, 20 Jun 2007 18:32:30 GMT

我们现在应该做点修改,看看ETag是否改变了。我们给这个物主增加一个宠物:

----------------------------------------------------------
http://localhost:8080/petclinic/addPet.htm?ownerId=10

GET /petclinic/addPet.htm?ownerId=10 HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://localhost:8080/petclinic/owner.htm?ownerId=10
Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8
X-lori-time-1: 1182364356265

HTTP/1.x 200 OK
Server: Apache-Coyote/1.1
Pragma: No-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache, no-store
Content-Type: text/html;charset=ISO-8859-1
Content-Language: en-US
Content-Length: 2174
Date: Wed, 20 Jun 2007 18:32:57 GMT
----------------------------------------------------------
http://localhost:8080/petclinic/addPet.htm?ownerId=10

POST /petclinic/addPet.htm?ownerId=10 HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1 ...

没有评论 »

How to set up mysql master/slave architecture(转)

四月 15, 2009 | mysql | RSS 2.0

http://blog.chinaunix.net/u/29134/showart_1711722.html

Allthe info below should be added in /etc/my.cnf. If you can’t find thisfile, just use the following statements to find where it is.

  1. To find the location of my.cnf.

ps aux | grep mysql | grep -v ‘grep’ | head -n 1

For example .

[root@localhost ~]# ps aux | grep mysql | grep -v ‘grep’ | head -n 1

root      2952  0.0  0.2   4512  1220 ?        S    16:12   0:00 /bin/sh ./mysqld_safe –defaults-file=/usr/local/mysql/my.cnf

Then we know the correct configuration file is /usr/local/mysql/my.cnf.

We should add the following lines to my.cnf in section [mysqld].

server-id = 1

log-bin=mysql-bin

binlog-do-db=db1

binlog-do-db=db2

binlog-ignore-db=ignore-db1

binlog-ignore-db=ignore-db2

expire_logs_days = 5

max_binlog_size=500M

log-slave-updates

Then restart mysqld manually and execute the following statements in mysql command line client.

  1. To grant valid user to slave.

To assume this thread called A.

grant file,replication slave on *.* to ‘mysql_ms’@'Slave IP’ identified by ‘Your password’;

flush privileges;

For example, if my slave’s ip address is “192.168.4.55″. The user is “mysql_ms” and his password is “123456″.

mysql> grant replication slave,file on *.* to ‘mysql_ms’@'192.168.4.55′ identified by ‘123456′;

Query OK, 0 rows affected (0.00 sec)

mysql> flush privileges;

Query OK, 0 rows affected (0.00 sec)

Be sure to send the username and password to me.

  1. Lock tables and get the master’s binary log file and position.

flush tables with read lock;

show master statusG

You must not quit the current mysql command line client.

mysql> show master statusG

*************************** 1. row ***************************

            File: mysql-bin.000004

        Position: 595

Binlog_Do_DB: test

Binlog_Ignore_DB: mysql

1 row in set (0.00 sec)

mysql>

Then send the results to me.

  1. Backup exact database.

Now we locked all thetables and got the exact binary log file and position in the previousstep. So begin to backup the exact database right now.

Use mysqldump to dump the necessary data to flat file, then use tool named gzip or gzip2 to compress it.

For example, if your database name is db1 and your mysql installation path was added in the environment variable named “PATH”.

Use the following statement to backup your databases’ data.

mysqldump -uroot -p –net_buffer_length=10M –max_allowed_packet=11M db1 > db1.txt

Here is my example in my machine, my database name is test.

[root@localhost~]# /usr/local/mysql/bin/mysqldump -uroot -p –net_buffer_length=100M–max_allowed_packet=120M test > test.txt

To compress the flat text file to a gzip file, using the following command.

gzip db1.txt

Then the compressed file named db1.txt.gz will be generated. You should send this file to me.

After all the above completes, go to thread A and execute the following command.

unlock tables.

Then quit thread A.

  1. What the slave machine want.

User name and password.

Master’s IP address.(192.168.4.54)

Master’s  mysql port.(3308)

Master’s  mysql binary log file and position

Master’s backup data.

  1. The following is the slave configuration.

Add the my.cnf on slave machine and restart mysqld.

[mysqld]

server-id = 2

replicate-do-db=db1

replicate-do-db=db2

replicate-ignore-db=ignore-db1

replicate-ignore-db=ignore-db2

log-bin=slave-bin

  1. Get the master information on slave machine.

Then exit from it and use the following command to import from the backup file in the shell environment.

gzip gb1.txt.gz

mysql –uroot –p –S/tmp/mysql3307.sock < gb1.txt

Enter the mysql command line client.

set @@global.max_allowed_packet=11*1024*1024;

Then exit it and enter it again.

Change master to

master_host=’192.168.4.54’,

master_port=3308,

master_user=’mysql_ms’,

master_password=’123456’

master_log_file=’mysql-bin.000004’,

master_log_pos=595;

start slave;

没有评论 »

如何避免僵死进程

四月 8, 2009 | c/c++, linux | RSS 2.0

综合和参考了几个网友的文章和apue的,不一一列举,致谢!!!

在fork()/execve()过程中,假设子进程结束时父进程仍存在,而父进程fork()之前既没安装SIGCHLD信号处理函数调用waitpid()等待子进程结束,又没有显式忽略该信号,则子进程成为僵尸进程,无法正常结束,此时即使是root身份kill-9也不能杀死僵尸进程。补救办法是杀死僵尸进程的父进程(僵尸进程的父进程必然存在),僵尸进程成为”孤儿进程”,过继给1号进程init,init始终会负责清理僵尸进程。

怎样来清除僵尸进程:
1.改写父进程,在子进程死后要为它收尸。具体做法是接管SIGCHLD信号。子进程死后,会发送SIGCHLD信号给父进程,父进程收到此信号后,执行waitpid()函数为子进程收尸。这是基于这样的原理:就算父进程没有调用wait,内核也会向它发送SIGCHLD消息,尽管对的默认处理是忽略,如果想响应这个消息,可以设置一个处理函数。
2.把父进程杀掉。父进程死后,僵尸进程成为”孤儿进程”,过继给1号进程init,init始终会负责清理僵尸进程.它产生的所有僵尸进程也跟着消失。
3.调用fork两次。

如何产生僵死进程:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
if (0 == fork()) {
printf(”In the child process:%d”, getpid());
} else {
printf(”In the parent process:%d”, getpid());
sleep(10);
exit(0);
}
printf(”Pid:%d”, getpid());
return 0;
}

如何避免:

若调用成功则返回清理掉的子进程id,若调用出错则返回-1。父进程调用waitwaitpid时可能会:

  • 阻塞(如果它的所有子进程都还在运行)。

  • 带子进程的终止信息立即返回(如果一个子进程已终止,正等待父进程读取其终止信息)。

  • 出错立即返回(如果它没有任何子进程)。

这两个函数的区别是:

  • 如果父进程的所有子进程都还在运行,调用wait将使父进程阻塞,而调用waitpid时如果在options参数中指定WNOHANG可以使父进程不阻塞而立即返回0。

  • wait等待第一个终止的子进程,而waitpid可以通过pid参数指定等待哪一个子进程。

可见,调用waitwaitpid不仅可以获得子进程的终止信息,还可以使父进程阻塞等待子进程终止,起到进

#include <sys/types.h>#include <sys/wait.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>

int main(void){ pid_t pid; pid = fork(); if (pid < 0) {  perror("fork failed");  exit(1); } if (pid == 0) {  int i;  for (i = 3; i > 0; i--) {   printf("This is the child");   sleep(1);  }  exit(3); } else {  int stat_val;  waitpid(pid, &stat_val, 0);  if (WIFEXITED(stat_val))   printf("Child exited with code %d", WEXITSTATUS(stat_val));  else if (WIFSIGNALED(stat_val))   printf("Child terminated abnormally, signal %d", WTERMSIG(stat_val)); } return 0;}

2 处理信号:#include <sys/types.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include<sys/wait.h>

void proc_child(int SIGNO)
{
int pid = -1;
int stat;
pid = waitpid(-1, &stat, WNOHANG);
}

int main(int argc, char **argv)
{
signal(SIGCHLD, proc_child);
if (0 == fork()) {
printf(”In the child process:%d”, getpid());
} else {
printf(”In the parent process:%d”, getpid());
sleep(10);
exit(0);
}
printf(”Pid:%d”, getpid());
return 0;
}

3 忽略信号int main(int argc, char **argv){signal(SIGCHLD, SIG_IGN); //加入此句即可,忽略SIGCHLD信号if (0 == fork()) {printf("In the child process:%d", getpid());} else {printf("In the parent process:%d", getpid());sleep(10);exit(0);}printf("Pid:%d", getpid());return 0;}4 apue上的fork两次:

fork两次在防止僵死方面来说,就是因为儿子进程先退出,孙子进程就被init接管了,实际上与最初的父进程脱离了关系,就不会僵死了。

#include <stdio.h>#include <sys/wait.h>#include <sys/types.h>#include <unistd.h>

int main(void)   {      pid_t pid;   

    if ((pid = fork()) < 0)       {           fprintf(stderr,"Fork error!");           exit(-1);       }       else if (pid == 0) /* first child */      {            if ((pid = fork()) < 0)           {                fprintf(stderr,"Fork error!");               exit(-1);           }           else if (pid > 0)               exit(0); /* parent from second fork == first child */          /*           * We're the second child; our parent becomes init as soon           * as our real parent calls exit() in the statement above.           * Here's where we'd continue executing, knowing that when           * we're done, init will reap our status.           */          sleep(2);           printf("Second child, parent pid = %d", getppid());           exit(0);       }   

    if (waitpid(pid, NULL, 0) != pid) /* wait for first child */      {           fprintf(stderr,"Waitpid error!");           exit(-1);       }   

    /*       * We're the parent (the original process); we continue executing,       * knowing that we're not the parent of the second child.       */      exit(0);   }   5 static voidsig_child(){    waitpid(-1, NULL, 0);}[...]signal(SIGCHLD, sig_child)sig_child这个函数有问题。这个函数的作用是每当有一个进程退出时,便会调用一次sig_child,sig_child便会调用waitpid处理一个已退出的子进程。但是假如程序正在sig_child里面,这时又有另外一个子进程退出了,这时该怎么处理呢?处理的方法应该是不会有另外一个sig_child响应,于是这时便有2个子进程退出了,但是sig_child只能处理一个,于是便有一个退出的子进程成了僵死进程了。

于是把sig_child改成:
static void
sig_child()
{
    while (waitpid(-1, NULL, 0) > 0);
}
 
这样假如程序正在sig_child里面,这时又有另外n个子进程退出了,由于用了while,于是sig_child便会将所以退出的子进程“一网打尽”。

这是一种处理僵死进程的方法,另外还有2种:
signal(SIGCHLD, SIG_IGN);
忽略SIGCHLD信号,父进程不需要处理,直接把退出的子进程推给init进程处理。

struct sigaction    sa;
sa.sa_handler = SIG_IGN;
sa.sa_flags = SA_NOCLDWAIT;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGCHLD, &sa, NULL) < 0) {
    exit(-1);
}

没有评论 »

socket笔记

四月 1, 2009 | c/c++, linux, 网站架构 | RSS 2.0

1什么是socket 利用标准的unix文件表述夫和别的程序进行交流的方式

unix中一切都是文件,unix程序i/o交换时通过多谢文件表述夫,一个文件表述夫就是一个简单的整形的和打开文件相关的东西。 thatfile can be a network connection, a

FIFO,a pipe, a terminal, a real on-the-disk file, or just about anything

else.Everything in Unix is a file! So when you want to communicate with

anotherprogram over the Internet you’re gonna do it through a file

descriptor,you’d better believeit.你可能要问,在网络交换中我如何得到文件表述夫呢?调用socket,它将返回一个文件夫,你和它进行交流调用通过专门的socketsendrecv。但是你现在可能会说,既然是文件表述夫,那我为什么不能用writeread呢?简单的说,可以,复杂的,但是在数据传输中sendrecv能更好的得到控制。

有多种socketdarpainternetsocket网络socket,路径名在本地节点,x25地址。。。。可能还支持多种unix支持的。本文主要说的是网络socket。两种网络socket,其实是在说谎,为了怕吓着你,这里只介绍两种情况,介绍一种简单的状态协议的socket。流套接字,可靠如果你输出的socket按照顺序是12,到达的则是1,2的相反,你听说过telnet吧,它用的就是流套接字,你可以模仿http协议在telnet,的两边连接,在某些方面类似,标准的输入输出流,提供一个有序的可靠的,双字节流的链接,发送的数据确保不会丢失,复制和乱序到到,并且在这一过程中发生的错误也不会显示出来,大的消息背分片,传输,再重组。这很像一个文件流,接受大的数据,然后以笑数据块的格式将他们写入底层硬盘。流套接字的行为是可以遇见的。有类型sock_stream指定,在af_inet域中通过tcp/ip连接实现的。SOCK_DGRAM数据包套接字有时被称为无连接的socket.流套接字用的是传输控制协议,网际协议。数据包套接字也用ip路由,但是他们不用tcp协议,他们用udp协议。什么是无连接的?基本上是你在进行流套接字时不用去维持一个打开的连接,你建立一个包,分片,ip头,在目的信息上,发出去,不需要连接了,包包传简单的应用如:tftpbootp等。

你可能惊奇,如果数据包丢失,程序将如如何工作,myhumanfriend,在udp上层也有自己的协议,tftp协议每个包发出,接受方返回说我得到了,如果没有回复,5秒后,它将继续传输,直到得到了ack

网际七层协议:

*Application

*Presentation

*Session

*Transport

*Network

*Data Link

  • Physical

  • 四层协议

    * Application Layer (telnet, ftp, etc.)

  • * Host-to-Host Transport Layer (TCP, UDP)

  • * Internet Layer (IP and routing)

  • * Network Access Layer (was Network, Data Link, and Physical)

多种struct

structsockaddr {

unsignedshort sa_family; /* address family, AF_xxx */

char sa_data[14]; /* 14 bytes of protocol address */

};

af_inet域中:

structsockaddr_in (”in” for “Internet”.)

structsockaddr_in {

shortint sin_family; /* Address family */

unsignedshort int sin_port; /* Port number */

structin_addr sin_addr; /* Internet address */

unsignedchar sin_zero[8]; /* Same size as struct sockaddr */

};

/*Internet address (a structure for historical reasons) */

structin_addr {

unsignedlong s_addr;

};

字节顺序:

*htons()–”Host to Network Short”

*htonl()–”Host to Network Long”

*ntohs()–”Network to Host Short”

  • ntohl()–”Network to Host Long”

  • 为什么地址协议不用网络字节顺序?

  • A final point: why do sin_addr and sin_port need to be in Network Byte Order

  • in a struct sockaddr_in, but sin_family does not? The answer: sin_addr and

  • sin_port get encapsulated in the packet at the IP and UDP layers,

  • respectively. Thus, they must be in Network Byte Order. However, the

  • sin_family field is only used by the kernel to determine what type of

  • address the structure contains, so it must be in Host Byte Order. Also,

  • since sin_family does not get sent out on the network, it can be in Host

  • Byte Order.

库函数介绍

    inet_addr(), converts an IP address in numbers-and-dots notation

  • into an unsigned long. The assignment can be made as follows:

  • ina.sin_addr.s_addr = inet_addr(”132.241.5.10″);

  • Notice that inet_addr() returns the address in Network Byte Order

  • already–you don’t have to call htonl(). Swell!

  • 不要忘记了错误检查

  • 相反你也可以有iplong得到ip地址:

  • printf(”%s”,inet_ntoa(ina.sin_addr));

  • 注意事项

  • That will print the IP address. Note that inet_ntoa() takes a struct in_addr

  • as an argument, not a long. Also notice that it returns a pointer to a char.

  • This points to a statically stored char array within inet_ntoa() so that

  • each time you call inet_ntoa() it will overwrite the last IP address you

  • asked for. For example:

  • char *a1, *a2;

  • .

  • .

  • a1 = inet_ntoa(ina1.sin_addr); /* this is 198.92.129.1 */

  • a2 = inet_ntoa(ina2.sin_addr); /* this is 132.241.5.10 */

  • printf(”address 1: %s”,a1);

  • printf(”address 2: %s”,a2);

  • will print:

  • address 1: 132.241.5.10

  • address 2: 132.241.5.10

  • If you need to save the address, strcpy() it to your own character array.

  • That’s all on this topic for now. Later, you’ll learn to convert a string

  • like “whitehouse.gov” into its corresponding IP address

创建socket

#include<sys/types.h>

#include<sys/socket.h>

intsocket(int domain, int type, int protocol);

Butwhat are these arguments? First, domain should be set to “AF_INET”,just

likein the struct sockaddr_in (above.) Next, the type argument tells the

kernelwhat kind of socket this is: SOCK_STREAM or SOCK_DGRAM. Finally,just

setprotocol to “0″. (Notes: there are many more domains thanI’ve listed.

Thereare many more types than I’ve listed. See the socket() man page.Also,

there’sa “better” way to get the protocol. See thegetprotobyname() man

page.)

socket()simply returns to you a socket descriptor that you can use in later

systemcalls, or -1 on error. The global variable errno is set to the

error’svalue (see the perror() man page.)

bind

#include<sys/types.h>

#include<sys/socket.h>

intbind(int sockfd, struct sockaddr *my_addr, int addrlen);

例子:

#include<string.h>

#include<sys/types.h>

#include<sys/socket.h>

#defineMYPORT 3490

main()

{

intsockfd;

structsockaddr_in my_addr;

sockfd= socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */

my_addr.sin_family= AF_INET; /* host byte order */

my_addr.sin_port= htons(MYPORT); /* short, network byte order */

my_addr.sin_addr.s_addr= inet_addr(”132.241.5.10″);

bzero(&(my_addr.sin_zero),8); /* zero the rest of the struct */

/*don’t forget your error checking for bind(): */

bind(sockfd,(struct sockaddr *)&my_addr, sizeof(struct sockaddr));

注意:

my_addr.sin_port= 0; /* choose an unused port at random */

my_addr.sin_addr.s_addr= INADDR_ANY; /* use my IP address */

See,by setting my_addr.sin_port to zero, you are telling bind() tochoose

theport for you. Likewise, by setting my_addr.sin_addr.s_addr to

INADDR_ANY,you are telling it to automatically fill in the IP address of

themachine the process is running on.

.Theconnect() call is as follows:

#include<sys/types.h>

没有评论 »