雷军:大家下午好!非常高兴有这么一个机会来这里跟大家交流。刚才听了志东和世平的讲话以后我觉得压力很大,因为我发现我想讲的他们已经讲过了,为什么呢?因为所有成功的创业故事虽然精彩纷呈,但是它的本质是一样的,一个成功的创业人,他们刚才两位其实只讲“三加一点”,第一点,就是要想创业成功一定要有梦想,这个梦想是支持我们创业成功的关键,第二点,他们讲了兴趣,如果你没有兴趣,你去做这件事情的话是一个苦难的事情,第三点他们讲的是要坚持和执着。
他们两位都比我年长,我看上去比他们年轻很多,实际上也不年轻,我觉得这是他们讲创业成功的最关键的三点。加上一点,对在座的提一些建议,所以我觉得没什么可说的,要不就从我的角度讲一讲我的历程。
我自己的第一轮创业就是大学生创业,我在很多场合讲过,我是不支持大学生创业,除非你认为你优秀尤如比尔盖茨你可以试试,我们一般的大学生就不要试了,为什么呢?我是很多年以后看到柳传志对大学生的讲话,讲得非常出色,他说他还是建议大学生去一家优秀的公司,先工作一段时间,等你有了能力你再去做。为什么?因为我自己在大学创业的时候就是个失败的案例,为什么会失败呢?因为实际上我们这个社会不是从幼儿园、小学、中学、大学就教你怎么做创业,当你不具备社会资源,没有资金,然后只有满腔的热血和冲动去创业的话,结果是什么呢?在生存的压力下你一定会干坑蒙拐骗的事情,对吗?为什么我们中关村叫“骗子一条街”呢?我说的是实话。所以柳传志讲这种创业的压力会逼着大家发挥“农民式的狡猾”,这话不是我讲的,因为我爷爷是农民,我对农民没有任何偏见,只是说农民用那种小的狡猾来对付税务和客户,坑蒙拐骗。今天时间有限,我没有时间讲我自己怎么创业失败的,但我想在我那个年代的时候,我自己还是基础实力非常好的工程师,也写了不少好的东西,看起来好像具备了创业条件,实际上进去趟水的时候才发现能力不足。
所以有个别的同学条件具备了,我非常支持,但是我还是希望绝大部分同学大学能毕业,然后有一份好的工作,经过三、五年的酝酿,做好准备再去创业。我也常开志东讲的玩笑,我说为什么你们没成功呢?是因为你大学毕业了,书念的越多越反复,在你脑海中限制了你的创新,限制了你的想象力。所以创业成功的前提是要有梦想,我一直说我最喜欢的名言是“我有一个梦想”!
在我上大学的时候,有时候出去帮别人兼职打工,躺在地板上的时候,因为忙了一通宵累的要死,就想我什么时候能办成世界一流的企业?因为我这一代也叫年轻人,是被硅谷之火这样的书籍所激励的,我们梦想的都是办世界一流的企业,我就在想,如果为这样的目标付出十年、二十年甚至一辈子都是值得的,我也坚信中国能产生世界一流的企业。就是怀抱这样的梦想,我在91年底进入了金山,然后当时我认为金山也许三、五年就能干出这样的事情,时间过得真的很快,我在金山已经干了十五年半,有时候看到一张张年轻的脸,就想如果金山是个人的话,他现在已经念大学二年级了,因为它十九岁了。所以大家说我很年轻的时候,我就开玩笑说,我是年轻的老革命,因为从事革命已经十八、九年了,这个时间不算短。
在创业的过程中我们有这个想法,想把金山办成世界一流的企业,这个想法对我们起了什么作用?1994年微软在中国推出了微软Office,不到两年时间我们金山公司就快关门了,眼看着发不出工资。在这个时候摆在我们的面前有非常多的选择,就是1996年,10年,我们真的快发不出工资了,账上只有几十万人民币,能干的人都跑光了,剩下的十来个人七、八条枪,这个革命也快坚持不下去了。我在金山最低潮的时候也动摇过,也犹豫过,在我面前的路太多了,我可以去IBM,微软,或者去XX公司,第三,实在不行,可以干房地产,干保健品,再不行,真的到街上擦鞋都比在金山好。你面临很多选择,这时候犹豫的是什么东西支撑你?我今天想起来,我觉得是两条。第一个是兴趣,因为我喜欢做软件,我在上大学的时候,开始学软件的时候就觉得我自己非常喜欢干这个工作,我觉得兴趣比什么都重要,我说市场上能赚钱的事情很多很多,有什么东西是你喜欢干,你愿意为它付出的东西,这一点我很庆幸,我找到了我喜欢的工作,就是写软件。第二个,我就在想产业报国,我们自己到底能为我们民族和国家做什么呢?能不能做出能让整个民族骄傲的事业呢?难道我们就这么认输了吗?这就是在1996年我想的问题。后来沿着这种思路我们提出了阵地战,游击战,以少胜多,然后鸡蛋撞石头,总而言之这十年下来金山就是在微软和盗版的围追堵截下,金山已经一步一步在跟微软和盗版产品练就了铮铮铁骨,过去我们的付出很难用语言表达,但是我们今天不管是木桶理论,还是优势理论,我相信金山至少夯实了基础。
所以在我内心深处非常佩服任政飞,因为任政飞说一个公司只有死过三次还能再活过来,才是一家真正的企业,烧不死的鸟是凤凰!我至少认为金山在过去的接近二十年的历程里面是死过去,又活过来的企业,我们更知道创业的艰辛。在五、六年前我们盼星星,盼月亮,终于盼来了党和政府支持正版,保护知识产权一系列的政策,结果当这样大好春天来临的时候,我们的产品说实话,大家在用,说垃圾,我在参加政府任何组织的扶持软件产业会议,怎么说呢?这个体会很难讲,我就讲一个细节,我主持一个会衣服都汗湿了,我觉得我辜负了大家对我的期望。所以在五、六年前我们下定决心,做出了一个非商业的决定,就是扔掉原来的WPS,重做一组。这个至少我认为做到了跟微软看起来一样,用起来一样,文件格式一样,只花了三千多万人民币,我觉得中国人有戏,因为我们只要三千万!
这样的产品,我们现在在网上针对所有个人用户是免费的,我说反正大家现在还没有买正版的习惯,何不我们就大方一点算了!为什么能这么大方呢?做企业一定要赚钱的,不赚钱怎么活下去?非常简单,我想的就是我们中国人有没有可能在世界上去挣钱?自从有这样的梦想,十年前,我就去过很多个国家和区域,去看怎么做,一步一步的,第一次出去灰溜溜的回来了,发现我们产品卖不出去。04年9月份,也就是三年多前,金山开始了第二轮正经的国际化,我很骄傲地说,我们两年前设立了日本的子公司,把中国人写的软件产品推到了日本市场,当时去的时候我们自己心里没底,因为我去过日本,我在日本卖过产品,觉得日本人很挑剔,能用我们的产品吗?第一次我们去日本做新闻发布会,我也在很多场合讲,日本媒体对我们的欢迎程度超出了想像,因为他们很惊讶中国的崛起,因为软件代表了高科技的文化。两年下来,我跟大家汇报一下成绩,金山毒霸,日本市场是什么市场呢?是全球12家所有做杀毒软件的都在那里有公司,都在下很大工夫去推,我很骄傲地说,我们只用了一年多时间,在日本占到了4%的市场份额,进入了行业第五位,只用了一年时间!
我们在日本投了三亿日元,一年多后我们日本子公司的价钱涨了八倍多,我跟他们讲我的目的是什么?是咱们在中国辛辛苦苦挣了点血汗钱在日本市场是不经杀的,我们要用日本人的钱打南美市场。
金山一千人以上的研发团队极具竞争力,我相信中小企业会很快选择WPS。第二个,整个是以日本试验田为核心,先在亚洲拉开,下周礼拜二毒霸正式宣布进入越南市场,他们所面临的就是现在越南的媒体铺天盖地的宣传金山,因为听说中国有个金山,十分之一的价钱就要进来,风雨欲来,越南也是我们金山的骄傲,因为金山的游戏在越南已经占到了80%份额,在座的年轻人如果有梦想的话去越南创业,越南是十五年前的中国。两年前的这个时候,苦大愁深的两个越南人来找我,我一个朋友去越南徒步旅行一个多月,就说越南人看起来很瘦,街上很黑,越南人就这样,但是越南在过去的十多年里GDP增长接近10%,经济增长的非常快,两年多前的年轻人怀揣30万美金来到中国进入我们的办公室,当时我就答应帮他,不瞒各位说,我还是觉得是社会主义兄弟,真的得帮一把!
你去越南的时候,一打开护照一看,国徽都一样,但是这个国家充满活力,这两个小伙子,二十七、八岁,差不多已经在越南接近首富了。我说世界上还有很多国家可以开疆拓土,中关村已经很挤了,我们有太多的机会,所以中国在一步一步强大的过程之中,我们获得了一个空间的发展机会,我在想金山的国际化到今天才刚刚起步,未来还有很长的路要走,也许我们还需要十年、二十年,甚至一辈子,但是我们将沿着华为的路一样,真的在世界上开疆拓土,这就是我的梦想。
所以说到这里,我认为支持我们金山这么多同事,有这么多凝聚力的核心是什么?是梦想,我们想把金山办成世界一流的技术公司,办成一家受员工尊重,受股东尊重,受整个民族认可的软件企业,梦想对于创业的成功,对于我们克服创业过程中的艰难险阻起了决定性的作用,谢谢大家!
他们两位都比我年长,我看上去比他们年轻很多,实际上也不年轻,我觉得这是他们讲创业成功的最关键的三点。加上一点,对在座的提一些建议,所以我觉得没什么可说的,要不就从我的角度讲一讲我的历程。
我自己的第一轮创业就是大学生创业,我在很多场合讲过,我是不支持大学生创业,除非你认为你优秀尤如比尔盖茨你可以试试,我们一般的大学生就不要试了,为什么呢?我是很多年以后看到柳传志对大学生的讲话,讲得非常出色,他说他还是建议大学生去一家优秀的公司,先工作一段时间,等你有了能力你再去做。为什么?因为我自己在大学创业的时候就是个失败的案例,为什么会失败呢?因为实际上我们这个社会不是从幼儿园、小学、中学、大学就教你怎么做创业,当你不具备社会资源,没有资金,然后只有满腔的热血和冲动去创业的话,结果是什么呢?在生存的压力下你一定会干坑蒙拐骗的事情,对吗?为什么我们中关村叫“骗子一条街”呢?我说的是实话。所以柳传志讲这种创业的压力会逼着大家发挥“农民式的狡猾”,这话不是我讲的,因为我爷爷是农民,我对农民没有任何偏见,只是说农民用那种小的狡猾来对付税务和客户,坑蒙拐骗。今天时间有限,我没有时间讲我自己怎么创业失败的,但我想在我那个年代的时候,我自己还是基础实力非常好的工程师,也写了不少好的东西,看起来好像具备了创业条件,实际上进去趟水的时候才发现能力不足。
所以有个别的同学条件具备了,我非常支持,但是我还是希望绝大部分同学大学能毕业,然后有一份好的工作,经过三、五年的酝酿,做好准备再去创业。我也常开志东讲的玩笑,我说为什么你们没成功呢?是因为你大学毕业了,书念的越多越反复,在你脑海中限制了你的创新,限制了你的想象力。所以创业成功的前提是要有梦想,我一直说我最喜欢的名言是“我有一个梦想”!
在我上大学的时候,有时候出去帮别人兼职打工,躺在地板上的时候,因为忙了一通宵累的要死,就想我什么时候能办成世界一流的企业?因为我这一代也叫年轻人,是被硅谷之火这样的书籍所激励的,我们梦想的都是办世界一流的企业,我就在想,如果为这样的目标付出十年、二十年甚至一辈子都是值得的,我也坚信中国能产生世界一流的企业。就是怀抱这样的梦想,我在91年底进入了金山,然后当时我认为金山也许三、五年就能干出这样的事情,时间过得真的很快,我在金山已经干了十五年半,有时候看到一张张年轻的脸,就想如果金山是个人的话,他现在已经念大学二年级了,因为它十九岁了。所以大家说我很年轻的时候,我就开玩笑说,我是年轻的老革命,因为从事革命已经十八、九年了,这个时间不算短。
在创业的过程中我们有这个想法,想把金山办成世界一流的企业,这个想法对我们起了什么作用?1994年微软在中国推出了微软Office,不到两年时间我们金山公司就快关门了,眼看着发不出工资。在这个时候摆在我们的面前有非常多的选择,就是1996年,10年,我们真的快发不出工资了,账上只有几十万人民币,能干的人都跑光了,剩下的十来个人七、八条枪,这个革命也快坚持不下去了。我在金山最低潮的时候也动摇过,也犹豫过,在我面前的路太多了,我可以去IBM,微软,或者去XX公司,第三,实在不行,可以干房地产,干保健品,再不行,真的到街上擦鞋都比在金山好。你面临很多选择,这时候犹豫的是什么东西支撑你?我今天想起来,我觉得是两条。第一个是兴趣,因为我喜欢做软件,我在上大学的时候,开始学软件的时候就觉得我自己非常喜欢干这个工作,我觉得兴趣比什么都重要,我说市场上能赚钱的事情很多很多,有什么东西是你喜欢干,你愿意为它付出的东西,这一点我很庆幸,我找到了我喜欢的工作,就是写软件。第二个,我就在想产业报国,我们自己到底能为我们民族和国家做什么呢?能不能做出能让整个民族骄傲的事业呢?难道我们就这么认输了吗?这就是在1996年我想的问题。后来沿着这种思路我们提出了阵地战,游击战,以少胜多,然后鸡蛋撞石头,总而言之这十年下来金山就是在微软和盗版的围追堵截下,金山已经一步一步在跟微软和盗版产品练就了铮铮铁骨,过去我们的付出很难用语言表达,但是我们今天不管是木桶理论,还是优势理论,我相信金山至少夯实了基础。
所以在我内心深处非常佩服任政飞,因为任政飞说一个公司只有死过三次还能再活过来,才是一家真正的企业,烧不死的鸟是凤凰!我至少认为金山在过去的接近二十年的历程里面是死过去,又活过来的企业,我们更知道创业的艰辛。在五、六年前我们盼星星,盼月亮,终于盼来了党和政府支持正版,保护知识产权一系列的政策,结果当这样大好春天来临的时候,我们的产品说实话,大家在用,说垃圾,我在参加政府任何组织的扶持软件产业会议,怎么说呢?这个体会很难讲,我就讲一个细节,我主持一个会衣服都汗湿了,我觉得我辜负了大家对我的期望。所以在五、六年前我们下定决心,做出了一个非商业的决定,就是扔掉原来的WPS,重做一组。这个至少我认为做到了跟微软看起来一样,用起来一样,文件格式一样,只花了三千多万人民币,我觉得中国人有戏,因为我们只要三千万!
这样的产品,我们现在在网上针对所有个人用户是免费的,我说反正大家现在还没有买正版的习惯,何不我们就大方一点算了!为什么能这么大方呢?做企业一定要赚钱的,不赚钱怎么活下去?非常简单,我想的就是我们中国人有没有可能在世界上去挣钱?自从有这样的梦想,十年前,我就去过很多个国家和区域,去看怎么做,一步一步的,第一次出去灰溜溜的回来了,发现我们产品卖不出去。04年9月份,也就是三年多前,金山开始了第二轮正经的国际化,我很骄傲地说,我们两年前设立了日本的子公司,把中国人写的软件产品推到了日本市场,当时去的时候我们自己心里没底,因为我去过日本,我在日本卖过产品,觉得日本人很挑剔,能用我们的产品吗?第一次我们去日本做新闻发布会,我也在很多场合讲,日本媒体对我们的欢迎程度超出了想像,因为他们很惊讶中国的崛起,因为软件代表了高科技的文化。两年下来,我跟大家汇报一下成绩,金山毒霸,日本市场是什么市场呢?是全球12家所有做杀毒软件的都在那里有公司,都在下很大工夫去推,我很骄傲地说,我们只用了一年多时间,在日本占到了4%的市场份额,进入了行业第五位,只用了一年时间!
我们在日本投了三亿日元,一年多后我们日本子公司的价钱涨了八倍多,我跟他们讲我的目的是什么?是咱们在中国辛辛苦苦挣了点血汗钱在日本市场是不经杀的,我们要用日本人的钱打南美市场。
金山一千人以上的研发团队极具竞争力,我相信中小企业会很快选择WPS。第二个,整个是以日本试验田为核心,先在亚洲拉开,下周礼拜二毒霸正式宣布进入越南市场,他们所面临的就是现在越南的媒体铺天盖地的宣传金山,因为听说中国有个金山,十分之一的价钱就要进来,风雨欲来,越南也是我们金山的骄傲,因为金山的游戏在越南已经占到了80%份额,在座的年轻人如果有梦想的话去越南创业,越南是十五年前的中国。两年前的这个时候,苦大愁深的两个越南人来找我,我一个朋友去越南徒步旅行一个多月,就说越南人看起来很瘦,街上很黑,越南人就这样,但是越南在过去的十多年里GDP增长接近10%,经济增长的非常快,两年多前的年轻人怀揣30万美金来到中国进入我们的办公室,当时我就答应帮他,不瞒各位说,我还是觉得是社会主义兄弟,真的得帮一把!
你去越南的时候,一打开护照一看,国徽都一样,但是这个国家充满活力,这两个小伙子,二十七、八岁,差不多已经在越南接近首富了。我说世界上还有很多国家可以开疆拓土,中关村已经很挤了,我们有太多的机会,所以中国在一步一步强大的过程之中,我们获得了一个空间的发展机会,我在想金山的国际化到今天才刚刚起步,未来还有很长的路要走,也许我们还需要十年、二十年,甚至一辈子,但是我们将沿着华为的路一样,真的在世界上开疆拓土,这就是我的梦想。
所以说到这里,我认为支持我们金山这么多同事,有这么多凝聚力的核心是什么?是梦想,我们想把金山办成世界一流的技术公司,办成一家受员工尊重,受股东尊重,受整个民族认可的软件企业,梦想对于创业的成功,对于我们克服创业过程中的艰难险阻起了决定性的作用,谢谢大家!
鉴于经常看到很多傻傻的问题,比如xx语言干什么用的,xxx语言是不是落伍了?(不过说实在的,这些问题初学者都会有.)
我在这里说说开发人员应该知道的一些东西。但是这些只是我在平日里看到和想到的。难免有所偏差,请见谅.
软件开发,是一个综合性的活计。软件开发,并不仅仅是编写代码.学会了用c这些编程语言进行编程只是第一步,一个最最基本要求。
其他要的东西还多着呢。在我看来,程序员大致可以分为两类.当一个工作任务分配到程序员身上时,一种程序员知道为什么要这样去做.另外一种则知道怎么去做完这个工作.
而这个区别就大了.如果你知道为什么要这样去实现,这个至少说明你能把握住你的任务在软件工程里面的位置.如果你只是仅仅知道怎么去完成他.那只是说明你能做完这个工作而已.想做好就不一定能行了.而第一种程序员一定能做好.做的最优.看看下面的条条,希望对大家都有所帮助.
第一要说的,编程的关键是什么?
编程不是实现了代码就可以了.引用我的友人的一句话,“编程讲究是一个整体的平衡性。”
对于这个他是这样解释的。“平衡性,是软件的很重要的部分,从平衡性的角度去考虑编程,就会抑制你想要用最新技术,最新系统等等一些想法。因为从平衡性的角度考虑,只要你的软件有一个瓶颈出现,你的程序就是失败。你首先要考虑的是怎么消除程序中可能存在的一些瓶颈。在这个基础上你才有权利去考虑提高你程序的性能”.就算你拥有最新的技术,最好系统,如果你的代码不行。只要你的程序有性能瓶颈存在,等于什么都没有做。
在这里我想说的就是程序是人写的。如果你的水平不行,再好的现成的技术也是用不起来的。就算用起来了,你可能没有办法说清楚,为什么这样用?
第二要说的,怎么编程?
我想很多人看到这个问题,一定会在心里把我骂的体无完肤的。心想这小子活腻了。骂也无妨。暂且听我说。我说的怎么编程不是要说怎么写详细的代码,而是你的程序最终是怎么形成的。我想写到这里又有人把我给陵迟了一次了。但实际上编写代码是在软件的生产过程中占有时间比较少的一块。
我个人觉得要包含以下的几个部分:
1。市场潜力分析
分析你要写的软件能不能卖出去,或者说我要编写什么样的软件?
2。同类产品竞争分析
看看你的同类产品的优缺点,设计你的软件的卖点.(如果没有卖点,就没有必要继续了)
3。软件设计
写出详细的软件流程,数据流程。主要算法。软件架构等
4。编写代码
不用说了吧
5。bug测试和试运行
6。卖
这些事,有的是市场的事,有的是系统分析员的事,还有的是编程的事。但是在很多小公司,本着小公司事必亲恭的办事原则。大家多了解一点是不会有错的。
举个具体的例子来说。假如我要编写一个共享软件。我要怎么做呢?
1。要好好想想我要写的软件有没有“钱”途。时间在15天-30天左右。在这段时间里面一定要好好的做一下市场考察.这个可是最关键的一步.
2。好,我已经决定要写xxx软件了。
3。在网上找几个对xxx最有威胁的同类软件,分析它们优缺点。要它们的优点,不要他们的缺点。设计出自己软件的卖点.
4。根据前面分析的结果,大概的列出xxx软件应该具有的功能表
5。写出1.0版的基本功能表,写出1.x的功能表。不要一次就做完全部的功能,这样的话,你的软件永远都没有出世的机会 :)
6。选择编程语言 (看看,编程语言到这里才出来)
7。上网找类似的源代码,算法。RFC标准文档。吃透.软件代码和算法的良好重用,会让你事半功倍的.
8。根据你选定语言,算法,标准文档,写出xxx的详细设计文档。文档一定要用,不然你的计划性就不强.计划性不强,随意性就大.随意性大了,软件很容易失败的.
9。按照设计文档编写代码
10。测试和卖
第三,哪里有资料,标准文档
代码的世界是千变万化的, 在开始一个新的项目之前,完全可以找一个类似功能的代码来看看。这样可以更好的改进你的程序。有时还可以加快进度。还有当新的技术出来时,你要看看相关的文档。虽然不要完全了解它的功能,好处。但是你至少要知道新的技术能用在什么地方。怎么用。配合什么其他的技术用能更好的发挥它的作用。编写软件不是全部的东西都是自己写的。有很多的功能是一种标准,也许是标准算法。像图形的,多媒体的,加密解密的算法。有的是一个标准的文件格式,像各种图像文件,多媒体文件。还有的是一种标准的约定。像email,telnet等常见的网络工具。
所以你要知道你可以从哪里找你要的资料。我把我知道的都写在这里
源代码和技术资料站点
www.vchelp.net gb
www.csdn.net gb
www.codeguru.com en
www.codetools.com en
www.dexv.com en
msdn.microsoft.com en
www.programmerheaven.com en
www.freshmeat.net en
www.sourceforge.net en
www-900.ibm.com/developerWorks/ gb
论坛和标准,组织
www.linuxaid.com.cn gb
www.linuxbyte.com gb
www.aka.org.cn gb
www.rfc.org en gb
各种maillist,irc
第四,要掌握的工具和知识
工具,可以让你的工作更加的有效率和不易出错。
下面的工具也许你用过,也许你没有用过。不过没有关系的。同行的老鸟会教我们怎么用的。(我想到哪个就写哪个。没有顺序问题)
1。数据库工具
建数据库工具,代表 powerdesigner
数据库分析工具。很多大型的数据库都会带的。
2。流程图设计 代表 visio 2000 , smartdraw
3。case工具 代表 rose
4。代码分析工具
代表 bounderchecker(for vc delphi),smartcheck(for vb) ....
5。编辑器
代表 vi,vic,Ultra Edit
6。源代码管理
代表 vss ,cvs
7。编程工具,不要我多说了吧
8。其他的,我没有用过的,但是也许在某个行业用的很多的工具。(废话 :))
知识的话,因为每一个人的发展方向不一样,所以大部分人的知识结构都不一样。但是有几点应该是一样的。
1。英语能力
主要的新的技术,文档资料都是用英语来作为首发的。如果要学到更好更新的知识,技巧。不懂点英语也是不行的。也不要指望有人给你翻译出来。一般来说,这些资料,看的懂的人不需要翻译,看不懂的人没有办法翻译。半懂不懂的人翻译出来的文章我想你也不敢看。所以大部分的资料还是英语原文的。当然也有很多的人在翻译这些文章,但是对于这么多的资料来说,翻译过来的只是很小很小的一部分。求人不如求己。多学点英语没有错的。
2。设计能力
虽然一般来说,正规的公司有系统分析员做设计(我猜的)。但是70%-80%的小公司,可就不一定了。知道一点软件工程的知识,知道一些文档设计工具怎么用。或者知道应该有哪些设计文档。也是很有好处的。比较这些东西如果你学到了,就是你自己的了。而且这些可是加工资的好东西。很有钱途的。:)
3。语文写作能力
作为一个程序员,大部分时间是都是在写代码。但是代码的注释,各种文档,测试报告,说明文档,使用手册编写,这些都需要文字功底的。 还有用email,bbs,qq这些工具与人交流的时候,如果话都说不清楚,那交流就更谈不上了。水平提高进步也就有点问题了。
4。学习能力
没有几个人是全部学会了再去工作的。这个不是很现实。目前社会也不太允许这样做。一边工作一边学习是很常见的。也许很多人是在工作之中才学会做某些事情的。很多技能也是这样会的。此外,很多新的项目的到来。很新的技术的到来都要求我们能适应新的工作环境,新的工作要求。如果没有好好的学习是很容易被一个项目踢掉的。呵呵。
另外有一点,当上司让你做你不会的东西时,你要告诉他,你不会,但是会在XX天内把他搞定。不会没有关系,会学习也是会上进的一种好表现。
5。知道自己要做什么,要学什么,要发展什么。
世界上软件技术是多的像9个牛上的毛一样多,也许还要多很多。如果我们什么都要知道。哦,天哪,我不想活了。
作为一个软件人员也好,作为一个初学者也好。知道自己要往那个方向走是很重要的。不然很容易的就饿死在软件技术迷宫里的。最后只好不干这一行了。这个可不太好。
一般来说,作为一个软件人员,掌握一到两个语言的开发能力就可以了。另外除非你是想做软件技术的研发(这些工作最有钱,在大型的公司是最受欢迎)。如果不是做软件技术的研发,只是一般的应用程序编写的话,不用太关注今天出来什么新的技术,明天又出来什么新的技术。这些东西只要知道就行了。知道有这么回事就可以了。以后有用的到的地方再去认真的关注也是不迟的。自己选择一个发展的方向,努力的向前走。不要被各种各样的新技术诱惑过去。说句实话,很多的所谓新技术的怎么怎么好,怎么怎么优异,很多时候都是有商业行为在里面的。要自己会判断才行。如果不能判断怎么办,看下面的一条。
第六:知道的更多
很多初学者最麻烦的事是怎么在这么多的软件技术里面选择一种又好学,又有前途(钱途),又能做点什么伟大的事情的技术来开拓软件开发这个他们未知的领域。对于这个麻烦的问题,很少有解。如果你能遇到一个很好的老师,那就是你的福气,千万要抓住这个机会。如果你不得不一人做出这个决定,那只能是小心翼翼地来了。不过一般来说学c和c++都是一个不错的选择。
初学者的另外一个麻烦的问题是,当我选择之后,在学习过程中出现的很多这个和那个的新技术,新的变化。我该怎么办。这个也基本无解。只能是你自己慢慢慢慢积累。积累到你能理解这些新技术的出现是为了什么,这些新变化的发生是为了什么之后。你就会不怕这些的新的东西。
我一向坚持,如果我知道的更多,我的力量就会更大。我就更不会怕出现变化。如果因为你的信息不足,而无法对某件事情进行判断时,千万不要强行进行判断。对你没有好处的。
我在这里说说开发人员应该知道的一些东西。但是这些只是我在平日里看到和想到的。难免有所偏差,请见谅.
软件开发,是一个综合性的活计。软件开发,并不仅仅是编写代码.学会了用c这些编程语言进行编程只是第一步,一个最最基本要求。
其他要的东西还多着呢。在我看来,程序员大致可以分为两类.当一个工作任务分配到程序员身上时,一种程序员知道为什么要这样去做.另外一种则知道怎么去做完这个工作.
而这个区别就大了.如果你知道为什么要这样去实现,这个至少说明你能把握住你的任务在软件工程里面的位置.如果你只是仅仅知道怎么去完成他.那只是说明你能做完这个工作而已.想做好就不一定能行了.而第一种程序员一定能做好.做的最优.看看下面的条条,希望对大家都有所帮助.
第一要说的,编程的关键是什么?
编程不是实现了代码就可以了.引用我的友人的一句话,“编程讲究是一个整体的平衡性。”
对于这个他是这样解释的。“平衡性,是软件的很重要的部分,从平衡性的角度去考虑编程,就会抑制你想要用最新技术,最新系统等等一些想法。因为从平衡性的角度考虑,只要你的软件有一个瓶颈出现,你的程序就是失败。你首先要考虑的是怎么消除程序中可能存在的一些瓶颈。在这个基础上你才有权利去考虑提高你程序的性能”.就算你拥有最新的技术,最好系统,如果你的代码不行。只要你的程序有性能瓶颈存在,等于什么都没有做。
在这里我想说的就是程序是人写的。如果你的水平不行,再好的现成的技术也是用不起来的。就算用起来了,你可能没有办法说清楚,为什么这样用?
第二要说的,怎么编程?
我想很多人看到这个问题,一定会在心里把我骂的体无完肤的。心想这小子活腻了。骂也无妨。暂且听我说。我说的怎么编程不是要说怎么写详细的代码,而是你的程序最终是怎么形成的。我想写到这里又有人把我给陵迟了一次了。但实际上编写代码是在软件的生产过程中占有时间比较少的一块。
我个人觉得要包含以下的几个部分:
1。市场潜力分析
分析你要写的软件能不能卖出去,或者说我要编写什么样的软件?
2。同类产品竞争分析
看看你的同类产品的优缺点,设计你的软件的卖点.(如果没有卖点,就没有必要继续了)
3。软件设计
写出详细的软件流程,数据流程。主要算法。软件架构等
4。编写代码
不用说了吧
5。bug测试和试运行
6。卖
这些事,有的是市场的事,有的是系统分析员的事,还有的是编程的事。但是在很多小公司,本着小公司事必亲恭的办事原则。大家多了解一点是不会有错的。
举个具体的例子来说。假如我要编写一个共享软件。我要怎么做呢?
1。要好好想想我要写的软件有没有“钱”途。时间在15天-30天左右。在这段时间里面一定要好好的做一下市场考察.这个可是最关键的一步.
2。好,我已经决定要写xxx软件了。
3。在网上找几个对xxx最有威胁的同类软件,分析它们优缺点。要它们的优点,不要他们的缺点。设计出自己软件的卖点.
4。根据前面分析的结果,大概的列出xxx软件应该具有的功能表
5。写出1.0版的基本功能表,写出1.x的功能表。不要一次就做完全部的功能,这样的话,你的软件永远都没有出世的机会 :)
6。选择编程语言 (看看,编程语言到这里才出来)
7。上网找类似的源代码,算法。RFC标准文档。吃透.软件代码和算法的良好重用,会让你事半功倍的.
8。根据你选定语言,算法,标准文档,写出xxx的详细设计文档。文档一定要用,不然你的计划性就不强.计划性不强,随意性就大.随意性大了,软件很容易失败的.
9。按照设计文档编写代码
10。测试和卖
第三,哪里有资料,标准文档
代码的世界是千变万化的, 在开始一个新的项目之前,完全可以找一个类似功能的代码来看看。这样可以更好的改进你的程序。有时还可以加快进度。还有当新的技术出来时,你要看看相关的文档。虽然不要完全了解它的功能,好处。但是你至少要知道新的技术能用在什么地方。怎么用。配合什么其他的技术用能更好的发挥它的作用。编写软件不是全部的东西都是自己写的。有很多的功能是一种标准,也许是标准算法。像图形的,多媒体的,加密解密的算法。有的是一个标准的文件格式,像各种图像文件,多媒体文件。还有的是一种标准的约定。像email,telnet等常见的网络工具。
所以你要知道你可以从哪里找你要的资料。我把我知道的都写在这里
源代码和技术资料站点
www.vchelp.net gb
www.csdn.net gb
www.codeguru.com en
www.codetools.com en
www.dexv.com en
msdn.microsoft.com en
www.programmerheaven.com en
www.freshmeat.net en
www.sourceforge.net en
www-900.ibm.com/developerWorks/ gb
论坛和标准,组织
www.linuxaid.com.cn gb
www.linuxbyte.com gb
www.aka.org.cn gb
www.rfc.org en gb
各种maillist,irc
第四,要掌握的工具和知识
工具,可以让你的工作更加的有效率和不易出错。
下面的工具也许你用过,也许你没有用过。不过没有关系的。同行的老鸟会教我们怎么用的。(我想到哪个就写哪个。没有顺序问题)
1。数据库工具
建数据库工具,代表 powerdesigner
数据库分析工具。很多大型的数据库都会带的。
2。流程图设计 代表 visio 2000 , smartdraw
3。case工具 代表 rose
4。代码分析工具
代表 bounderchecker(for vc delphi),smartcheck(for vb) ....
5。编辑器
代表 vi,vic,Ultra Edit
6。源代码管理
代表 vss ,cvs
7。编程工具,不要我多说了吧
8。其他的,我没有用过的,但是也许在某个行业用的很多的工具。(废话 :))
知识的话,因为每一个人的发展方向不一样,所以大部分人的知识结构都不一样。但是有几点应该是一样的。
1。英语能力
主要的新的技术,文档资料都是用英语来作为首发的。如果要学到更好更新的知识,技巧。不懂点英语也是不行的。也不要指望有人给你翻译出来。一般来说,这些资料,看的懂的人不需要翻译,看不懂的人没有办法翻译。半懂不懂的人翻译出来的文章我想你也不敢看。所以大部分的资料还是英语原文的。当然也有很多的人在翻译这些文章,但是对于这么多的资料来说,翻译过来的只是很小很小的一部分。求人不如求己。多学点英语没有错的。
2。设计能力
虽然一般来说,正规的公司有系统分析员做设计(我猜的)。但是70%-80%的小公司,可就不一定了。知道一点软件工程的知识,知道一些文档设计工具怎么用。或者知道应该有哪些设计文档。也是很有好处的。比较这些东西如果你学到了,就是你自己的了。而且这些可是加工资的好东西。很有钱途的。:)
3。语文写作能力
作为一个程序员,大部分时间是都是在写代码。但是代码的注释,各种文档,测试报告,说明文档,使用手册编写,这些都需要文字功底的。 还有用email,bbs,qq这些工具与人交流的时候,如果话都说不清楚,那交流就更谈不上了。水平提高进步也就有点问题了。
4。学习能力
没有几个人是全部学会了再去工作的。这个不是很现实。目前社会也不太允许这样做。一边工作一边学习是很常见的。也许很多人是在工作之中才学会做某些事情的。很多技能也是这样会的。此外,很多新的项目的到来。很新的技术的到来都要求我们能适应新的工作环境,新的工作要求。如果没有好好的学习是很容易被一个项目踢掉的。呵呵。
另外有一点,当上司让你做你不会的东西时,你要告诉他,你不会,但是会在XX天内把他搞定。不会没有关系,会学习也是会上进的一种好表现。
5。知道自己要做什么,要学什么,要发展什么。
世界上软件技术是多的像9个牛上的毛一样多,也许还要多很多。如果我们什么都要知道。哦,天哪,我不想活了。
作为一个软件人员也好,作为一个初学者也好。知道自己要往那个方向走是很重要的。不然很容易的就饿死在软件技术迷宫里的。最后只好不干这一行了。这个可不太好。
一般来说,作为一个软件人员,掌握一到两个语言的开发能力就可以了。另外除非你是想做软件技术的研发(这些工作最有钱,在大型的公司是最受欢迎)。如果不是做软件技术的研发,只是一般的应用程序编写的话,不用太关注今天出来什么新的技术,明天又出来什么新的技术。这些东西只要知道就行了。知道有这么回事就可以了。以后有用的到的地方再去认真的关注也是不迟的。自己选择一个发展的方向,努力的向前走。不要被各种各样的新技术诱惑过去。说句实话,很多的所谓新技术的怎么怎么好,怎么怎么优异,很多时候都是有商业行为在里面的。要自己会判断才行。如果不能判断怎么办,看下面的一条。
第六:知道的更多
很多初学者最麻烦的事是怎么在这么多的软件技术里面选择一种又好学,又有前途(钱途),又能做点什么伟大的事情的技术来开拓软件开发这个他们未知的领域。对于这个麻烦的问题,很少有解。如果你能遇到一个很好的老师,那就是你的福气,千万要抓住这个机会。如果你不得不一人做出这个决定,那只能是小心翼翼地来了。不过一般来说学c和c++都是一个不错的选择。
初学者的另外一个麻烦的问题是,当我选择之后,在学习过程中出现的很多这个和那个的新技术,新的变化。我该怎么办。这个也基本无解。只能是你自己慢慢慢慢积累。积累到你能理解这些新技术的出现是为了什么,这些新变化的发生是为了什么之后。你就会不怕这些的新的东西。
我一向坚持,如果我知道的更多,我的力量就会更大。我就更不会怕出现变化。如果因为你的信息不足,而无法对某件事情进行判断时,千万不要强行进行判断。对你没有好处的。
很多人很困惑,pda这个东西的真正用途是什么?游戏?---那个是扯淡。估计没有一个人认为在那个上面玩游戏有什么意义?电话?--为何不用小巧灵活的?用这么大的东西?
说来说去。我发表自己的看法。我之所以看重这个东西是因为他和我们95%的系统是兼容的。可以说是无缝连接,微软从开始做操作系统的时候并没有想今天的成果,但是机遇就是那样的巧妙展现在比尔盖茨的面前,我们知道机遇只会给有准备的人享用,当他做出来一个比较简陋的windows3.1以后,为了推销他的这个操作系统。必然要投入大量的资金去做这个操作系统的应用软件,估计看我这篇文章的人很多都没有在92年接触过电脑,可以说在95年以后接触电脑的朋友都对dos6.22很陌生,当时我们这些从dos中滚爬出来的人,接触到windows时非常的兴奋,不仅仅是微软,而是整个世界在丰富这个平台
当操作系统可以日常应用的时候,这个平台上的一个非常主要的软件群出现了,任何别的软件都可以说是为了锦上添花,但是这个软件群确是微软的命脉!那就是office软件,很多人听到这个名字自然的想到word,但实际上office代表的是所有办公涉及到的日常使用软件,从文字输入到计算到绘图到展示,但是这个时候互联网普及了,当我们金山之类的公司在看到人家嘴里的东西馋得流口水,学人家的样子东施效颦的时候,其实微软已经在做一个非常划时代的事情,就是引入了协作办公的概念
我有的时候真的很讨厌中国人的智商,永久自行车造了10年,他妈的连个螺丝钉都没有更改个位置,我不知道我们的军工如何,但是这样的环境中,我想也好不到什么地方去,不说远了,当微软提出来协作这个概念的时候,硬件厂商就有目的的配合了这个大的行动,现在我们国家的80%的人在使用电脑的时候,把他们当做了打字机,游戏机,实际上只使用了电脑的5%的功能,太差了,如果说国民素质的话,任何粉饰都是苍白的,我们利用工具进行改造社会的能力是这样的无力,谈什么别的,发展?没有手段,保家卫国?用什么--片刀?其实有的时候我身处的这个社会真的没有什么文化底蕴,说起来头头是道,大哥,那是100年前的人的成绩,不是你的不是我的,扯什么淡,最现实的就是,办公司的人,生产劳动的人,种田的人,用的都是非常原始的手段,我不是危言耸听,我们社会很原始
这个时候微软的outlook非常醒目的凸现出来,大家知道不管是干什么,我们都要有个计划,不过中国人的计划性不是很强调,但是西方人日本人很重视这个计划,在outlook中很重要的就是安排任务,日历、周期等等,还有就是联系人,现在说到pda,pda的重要功能是,可以简单的用连线的方式(后来有了红外、蓝牙、wifi)将pc中的outlook的数据。体现在手中的简易电脑中,让我们在第一时间得到信息,可以规避风险和处理问题,及时反馈,以期得到下一步的任务,所以电话的功能只是一种语音联系手段,文字的计划的,都需要pda来完成,这个时候有了另一个新的office成员,就是表单填写,虽然不是很完善,但是已经有实际操作的可能了,非常清晰的可以看出来。
微软已经成功地主导了世界的发展方向,那么我们应该如何使用,和需要这个东西?那么就要看我们的工作到底有多少内容要和电脑发生关系?如果很少我建议你使用智能手机,那个东西可以满足基本的信息交流和娱乐功能
比如我的老婆就使用智能手机来听音乐,打电话,他是办公室职员,他的电话簿里面有全公司(总公司)只要部门的电话领导的电话项目经理的电话,可以说任何时候只要问道我爱人,公司的认何人都可以找到,不仅仅是手机。她疯狂的收集这方面的信息。主要领导的家里、办公室、手机、生日、配偶等等,只要可以填写在联系人的都放进去,虽然很多但是不乱,公司领导很依赖她的这个能力。这是她的工作方面,生活方面家庭中要买的东西、要看的电视节目小孩的生日喜欢的音乐,都逃不出去。办公室别的同事很奇怪她这个奇特的能力,其实很简单了
说说我了,我是做工程的,建筑,我自己搞了一个公司,在管理上我建设了一个网站,用来管理公司的项目,在project软件的帮助下进行工程管理,比如工程进度了,原材料购买了,乱七八糟的事情。但是我要求我的员工都必须用outlock来和我联系,要我做的事情可以给我下任务,需要我帮助给我email。我仅仅是在联网的时候得到信息进行信息的反馈了,我用的最多的就是任务和日历,有的时候用便签,不多,我不喜欢用桌面插件,那样容易浪费内存,我不喜欢用818界面太小,憋气,696这个很顺手,所以说pda中做的最成功的就是这个机器了,现在的价格很低,我希望再低一点的时候给公司的人一人配一个,这样他们就没有任何理由逃避工作了
现在你明白了为什么pda中也有WORD EXECL这些软件了吧,主要是用来看共享工作区的文档的,不是用来写文档的,你也就知道了为什么别的系统的PDA要淘汰掉,离开了微软WINDOWS这个平台,就没有资格谈生存。至少在办公管理领域,我也不会要求我的员工去学习别的什么操作系统,我也不会自己花时间去学习别的操作系统,AUTOCAD不是屈服了吗,从14开始界面和WORD很相似,我不明白WPS搞出来有什么意思,你可以代替操作系统吗,不能,你有什么资格说你的软件比人家自己出的好,荒唐,说到数据库,离开了微软支持的数据库,就是扯淡,数据库的接口不向微软看齐只有淘汰,大家估计要骂我说我崇洋媚外,我只想说,机枪是外国的,有本事你不用?你敢吗?飞机别人发明的。你敢不要?给你片刀你敢放屁?这个社会,如果你有微冲,可以砸场子了 所以别人发明了,就不要不服,好好用。
美国人用导弹打进别人家的窗户,我们干工作开公司必须要有这样的能力精确制导,确定目标。一次性解决,大量的收集数据进行逻辑分析做好筹划,攻其一点不及其余。喜欢PDA的朋友希望从这个方面多思考一下在自己工作中的应用,其实我们已经被武装到了牙齿,只是你没有察觉到而已,举个简单的例子,陶宝网上开店的朋友很多,有谁在这个方面充分利用了PDA的功能?发掘出来什么新的办法?别人下单你如何第一时间知道?不要告诉我你派了个操盘手,那样的话我一样要用山西人的笑话笑话你,你笨到家,你经常问候你的业务关系吗?用什么方法?我告诉你第一步就是收集你有用的一切信息,当然用PDA了
随想随写,没有组织好,希望见谅了,希望给大家有一些启迪,思考思考了,如果你对office很陌生,那么就去看看书了,你会发现你会巧妙的做很多事情,pda这个东西和微软捆绑很紧---他和windows系统捆绑的很紧密,----他和windows系统的OFFICE组件捆绑的很紧密,----我们的生活中什么时候都要书写和计算和管理和计划,所以我们需要PDA。可能将来有更好的机器但是就今天来说最好的机器和最好的软件的结合产物非PDA了
以上说的PDA仅仅指WM2002---WM2005系统的PDA.那个原来的PLMA系统的我只好说拜拜了,因为没有土壤,即使多么艳丽依然没有价值
看的好就喝声彩。不好就驳一下,重在交流,别不吭不响的--咋就那么腼腆?
说来说去。我发表自己的看法。我之所以看重这个东西是因为他和我们95%的系统是兼容的。可以说是无缝连接,微软从开始做操作系统的时候并没有想今天的成果,但是机遇就是那样的巧妙展现在比尔盖茨的面前,我们知道机遇只会给有准备的人享用,当他做出来一个比较简陋的windows3.1以后,为了推销他的这个操作系统。必然要投入大量的资金去做这个操作系统的应用软件,估计看我这篇文章的人很多都没有在92年接触过电脑,可以说在95年以后接触电脑的朋友都对dos6.22很陌生,当时我们这些从dos中滚爬出来的人,接触到windows时非常的兴奋,不仅仅是微软,而是整个世界在丰富这个平台
当操作系统可以日常应用的时候,这个平台上的一个非常主要的软件群出现了,任何别的软件都可以说是为了锦上添花,但是这个软件群确是微软的命脉!那就是office软件,很多人听到这个名字自然的想到word,但实际上office代表的是所有办公涉及到的日常使用软件,从文字输入到计算到绘图到展示,但是这个时候互联网普及了,当我们金山之类的公司在看到人家嘴里的东西馋得流口水,学人家的样子东施效颦的时候,其实微软已经在做一个非常划时代的事情,就是引入了协作办公的概念
我有的时候真的很讨厌中国人的智商,永久自行车造了10年,他妈的连个螺丝钉都没有更改个位置,我不知道我们的军工如何,但是这样的环境中,我想也好不到什么地方去,不说远了,当微软提出来协作这个概念的时候,硬件厂商就有目的的配合了这个大的行动,现在我们国家的80%的人在使用电脑的时候,把他们当做了打字机,游戏机,实际上只使用了电脑的5%的功能,太差了,如果说国民素质的话,任何粉饰都是苍白的,我们利用工具进行改造社会的能力是这样的无力,谈什么别的,发展?没有手段,保家卫国?用什么--片刀?其实有的时候我身处的这个社会真的没有什么文化底蕴,说起来头头是道,大哥,那是100年前的人的成绩,不是你的不是我的,扯什么淡,最现实的就是,办公司的人,生产劳动的人,种田的人,用的都是非常原始的手段,我不是危言耸听,我们社会很原始
这个时候微软的outlook非常醒目的凸现出来,大家知道不管是干什么,我们都要有个计划,不过中国人的计划性不是很强调,但是西方人日本人很重视这个计划,在outlook中很重要的就是安排任务,日历、周期等等,还有就是联系人,现在说到pda,pda的重要功能是,可以简单的用连线的方式(后来有了红外、蓝牙、wifi)将pc中的outlook的数据。体现在手中的简易电脑中,让我们在第一时间得到信息,可以规避风险和处理问题,及时反馈,以期得到下一步的任务,所以电话的功能只是一种语音联系手段,文字的计划的,都需要pda来完成,这个时候有了另一个新的office成员,就是表单填写,虽然不是很完善,但是已经有实际操作的可能了,非常清晰的可以看出来。
微软已经成功地主导了世界的发展方向,那么我们应该如何使用,和需要这个东西?那么就要看我们的工作到底有多少内容要和电脑发生关系?如果很少我建议你使用智能手机,那个东西可以满足基本的信息交流和娱乐功能
比如我的老婆就使用智能手机来听音乐,打电话,他是办公室职员,他的电话簿里面有全公司(总公司)只要部门的电话领导的电话项目经理的电话,可以说任何时候只要问道我爱人,公司的认何人都可以找到,不仅仅是手机。她疯狂的收集这方面的信息。主要领导的家里、办公室、手机、生日、配偶等等,只要可以填写在联系人的都放进去,虽然很多但是不乱,公司领导很依赖她的这个能力。这是她的工作方面,生活方面家庭中要买的东西、要看的电视节目小孩的生日喜欢的音乐,都逃不出去。办公室别的同事很奇怪她这个奇特的能力,其实很简单了
说说我了,我是做工程的,建筑,我自己搞了一个公司,在管理上我建设了一个网站,用来管理公司的项目,在project软件的帮助下进行工程管理,比如工程进度了,原材料购买了,乱七八糟的事情。但是我要求我的员工都必须用outlock来和我联系,要我做的事情可以给我下任务,需要我帮助给我email。我仅仅是在联网的时候得到信息进行信息的反馈了,我用的最多的就是任务和日历,有的时候用便签,不多,我不喜欢用桌面插件,那样容易浪费内存,我不喜欢用818界面太小,憋气,696这个很顺手,所以说pda中做的最成功的就是这个机器了,现在的价格很低,我希望再低一点的时候给公司的人一人配一个,这样他们就没有任何理由逃避工作了
现在你明白了为什么pda中也有WORD EXECL这些软件了吧,主要是用来看共享工作区的文档的,不是用来写文档的,你也就知道了为什么别的系统的PDA要淘汰掉,离开了微软WINDOWS这个平台,就没有资格谈生存。至少在办公管理领域,我也不会要求我的员工去学习别的什么操作系统,我也不会自己花时间去学习别的操作系统,AUTOCAD不是屈服了吗,从14开始界面和WORD很相似,我不明白WPS搞出来有什么意思,你可以代替操作系统吗,不能,你有什么资格说你的软件比人家自己出的好,荒唐,说到数据库,离开了微软支持的数据库,就是扯淡,数据库的接口不向微软看齐只有淘汰,大家估计要骂我说我崇洋媚外,我只想说,机枪是外国的,有本事你不用?你敢吗?飞机别人发明的。你敢不要?给你片刀你敢放屁?这个社会,如果你有微冲,可以砸场子了 所以别人发明了,就不要不服,好好用。
美国人用导弹打进别人家的窗户,我们干工作开公司必须要有这样的能力精确制导,确定目标。一次性解决,大量的收集数据进行逻辑分析做好筹划,攻其一点不及其余。喜欢PDA的朋友希望从这个方面多思考一下在自己工作中的应用,其实我们已经被武装到了牙齿,只是你没有察觉到而已,举个简单的例子,陶宝网上开店的朋友很多,有谁在这个方面充分利用了PDA的功能?发掘出来什么新的办法?别人下单你如何第一时间知道?不要告诉我你派了个操盘手,那样的话我一样要用山西人的笑话笑话你,你笨到家,你经常问候你的业务关系吗?用什么方法?我告诉你第一步就是收集你有用的一切信息,当然用PDA了
随想随写,没有组织好,希望见谅了,希望给大家有一些启迪,思考思考了,如果你对office很陌生,那么就去看看书了,你会发现你会巧妙的做很多事情,pda这个东西和微软捆绑很紧---他和windows系统捆绑的很紧密,----他和windows系统的OFFICE组件捆绑的很紧密,----我们的生活中什么时候都要书写和计算和管理和计划,所以我们需要PDA。可能将来有更好的机器但是就今天来说最好的机器和最好的软件的结合产物非PDA了
以上说的PDA仅仅指WM2002---WM2005系统的PDA.那个原来的PLMA系统的我只好说拜拜了,因为没有土壤,即使多么艳丽依然没有价值
看的好就喝声彩。不好就驳一下,重在交流,别不吭不响的--咋就那么腼腆?
复习一下文件的操作,很多时候都用的上。
fread函数和fwrite函数
1.函数功能
用来读写一个数据块。
2.一般调用形式
fread(buffer,size,count,fp);
fwrite(buffer,size,count,fp);
3.说明
(1)buffer:是一个指针,对fread来说,它是读入数据的存放地址。对fwrite来说,是要输出数据的地址。
(2)size:要读写的字节数;
(3)count:要进行读写多少个size字节的数据项;
(4)fp:文件型指针。
注意:1 完成次写操(fwrite())作后必须关闭流(fclose());
2 完成一次读操作(fread())后,如果没有关闭流(fclose()),则指针(FILE * fp)自动向后移动前一次读写的长度,不关闭流继续下一次读操作则接着上次的输出继续输出;
3 fprintf() : 按格式输入到流,其原型是int fprintf(FILE *stream, const char *format[, argument, ...]);其用法和printf()相同,不过不是写到控制台,而是写到流罢了。注意的是返回值为此次操作写入到文件的字节数。如int c = fprintf(fp, "%s %s %d %f", str1,str2, a, b) ;str1:10字节;str2: 10字节;a:2字节;b:8字节,c为33,因为写入时不同的数据间自动加入一个空格。
文件使用之后一定要关闭,否则将不能正确显示内容.fwrite:读入两个学生信息然后用fwrite存入文件
fread:用fread从文件中读出学生信息。
fwrite.c
#include <stdio.h>
#define SIZE 2
struct student_type
{
char name[10];
int num;
int age;
char addr[10];
}stud[SIZE];
void save()
{
FILE *fp;
int i;
if((fp=fopen("stu_list","wb"))==NULL)
{
printf("cant open the file");
exit(0);
}
for(i=0;i<SIZE;i++)
{
if(fwrite(&stud[i],sizeof(struct student_type),1,fp)!=1)
printf("file write error\n");
}
fclose(fp);
}
main()
{
int i;
for(i=0;i<SIZE;i++)
{
scanf("%s%d%d%s",&stud[i].name,&stud[i].num,&stud[i].age,&stud[i].addr);
save();
}
for(i=0;i<SIZE;i++)
{
printf("%s,%d,%d",stud[i].name,stud[i].num,stud[i].age,stud[i].addr);
}
}
fread.c
#include <stdio.h>
#define SIZE 2
struct student_type
{
char name[10];
int num;
int age;
char addr[10];
}stud[SIZE];
void read()
{
FILE *fp;
int i;
if((fp=fopen("stu_list","rb"))==NULL)
{
printf("cant open the file");
exit(0);
}
for(i=0;i<SIZE;i++)
{
if(fread(&stud[i],sizeof(struct student_type),1,fp)!=1)
printf("file write error\n");
}
fclose(fp);
}
main()
{
int i;
read();
for(i=0;i<SIZE;i++)
{
printf("%s,%d,%d,%s",stud[i].name,stud[i].num,stud[i].age,stud[i].addr);
printf("\n");
}
}
fread函数和fwrite函数
1.函数功能
用来读写一个数据块。
2.一般调用形式
fread(buffer,size,count,fp);
fwrite(buffer,size,count,fp);
3.说明
(1)buffer:是一个指针,对fread来说,它是读入数据的存放地址。对fwrite来说,是要输出数据的地址。
(2)size:要读写的字节数;
(3)count:要进行读写多少个size字节的数据项;
(4)fp:文件型指针。
注意:1 完成次写操(fwrite())作后必须关闭流(fclose());
2 完成一次读操作(fread())后,如果没有关闭流(fclose()),则指针(FILE * fp)自动向后移动前一次读写的长度,不关闭流继续下一次读操作则接着上次的输出继续输出;
3 fprintf() : 按格式输入到流,其原型是int fprintf(FILE *stream, const char *format[, argument, ...]);其用法和printf()相同,不过不是写到控制台,而是写到流罢了。注意的是返回值为此次操作写入到文件的字节数。如int c = fprintf(fp, "%s %s %d %f", str1,str2, a, b) ;str1:10字节;str2: 10字节;a:2字节;b:8字节,c为33,因为写入时不同的数据间自动加入一个空格。
文件使用之后一定要关闭,否则将不能正确显示内容.fwrite:读入两个学生信息然后用fwrite存入文件
fread:用fread从文件中读出学生信息。
fwrite.c
#include <stdio.h>
#define SIZE 2
struct student_type
{
char name[10];
int num;
int age;
char addr[10];
}stud[SIZE];
void save()
{
FILE *fp;
int i;
if((fp=fopen("stu_list","wb"))==NULL)
{
printf("cant open the file");
exit(0);
}
for(i=0;i<SIZE;i++)
{
if(fwrite(&stud[i],sizeof(struct student_type),1,fp)!=1)
printf("file write error\n");
}
fclose(fp);
}
main()
{
int i;
for(i=0;i<SIZE;i++)
{
scanf("%s%d%d%s",&stud[i].name,&stud[i].num,&stud[i].age,&stud[i].addr);
save();
}
for(i=0;i<SIZE;i++)
{
printf("%s,%d,%d",stud[i].name,stud[i].num,stud[i].age,stud[i].addr);
}
}
fread.c
#include <stdio.h>
#define SIZE 2
struct student_type
{
char name[10];
int num;
int age;
char addr[10];
}stud[SIZE];
void read()
{
FILE *fp;
int i;
if((fp=fopen("stu_list","rb"))==NULL)
{
printf("cant open the file");
exit(0);
}
for(i=0;i<SIZE;i++)
{
if(fread(&stud[i],sizeof(struct student_type),1,fp)!=1)
printf("file write error\n");
}
fclose(fp);
}
main()
{
int i;
read();
for(i=0;i<SIZE;i++)
{
printf("%s,%d,%d,%s",stud[i].name,stud[i].num,stud[i].age,stud[i].addr);
printf("\n");
}
}
2007年4月16日更新
增加了一个令牌的判断,可以抓新版本的QQ和老版本的QQ包了.
2007年4月16日更新
因为朋友们说这个程序已经无法捕捉到最新的QQ登陆包了,所以修改了一下.因为时间关系,我没有修改本文的其他部分,仅仅修改了代码.
作者:梅劲松
本文档和程序为MIT授权
说到嗅探必须要讲到所支持的环境,并非只能对本机或者HUB环境才能使用。对于交换机,你可以指定一个口为嗅探口,从这个口能拿到所有端口的数据。如果这个交换是核心交换,那么你所能取到数据将更多。(三层交换一般都可以指定嗅探口)
1、如果你已经是个python爱好者你机器一定有了python的运行环境,如果你没有可以到www.python.org去下载一个。我使用的还是python 2.3。
2、这段程序需要pcap模块支持,你可以到http://monkey.org/~dugsong/pypcap/去下载一个,它有unix和win两个版本,请注意,win下他需要winpcap支持,如果你没有这个,请再下载winpcap。同样,如果你是在unix下使用,请下载libpcap。
3、安装pcap没有太多说的,win下是个exe,直接运行。unix下直接make就可以了。
4、打开你的记事本,将以下代码保存在sniffer-QQ.py这个文件中。
# -*- coding: cp936 -*-
import pcap ,struct
pack=pcap.pcap()
pack.setfilter('udp')
key=''
for recv_time,recv_data in pack:
recv_len=len(recv_data)
if recv_len == 102 and recv_data[42]== chr(02) and recv_data[101] == chr(03):
print struct.unpack('>I',recv_data[49:53])[0]
print '登陆了'
elif recv_len == 55:
print struct.unpack('>I',recv_data[49:53])[0]
print '登陆了'
如果你在*nix下运行,请将# -*- coding: cp936 -*-更改为# -*- coding: utf-8 -*-
好了,你可以运行你的python程序了,试着登陆你的QQ。看你的QQ号码是否被抓下来了。
这里付上我的抓屏结果
利用的什么原理呢。
QQ使用udp协议来和服务器进行通讯,当数据包在传输的时候。udp报文被抓了下来。而登陆包是以0x02开头0x03为结尾的,我们先判断是否为正确的登陆包,当然,登陆包的长度都为102个字节。我们取出结构中特定的位置,就是你的QQ号码了。
如果还有什么疑问,请大家跟贴。
增加了一个令牌的判断,可以抓新版本的QQ和老版本的QQ包了.
2007年4月16日更新
因为朋友们说这个程序已经无法捕捉到最新的QQ登陆包了,所以修改了一下.因为时间关系,我没有修改本文的其他部分,仅仅修改了代码.
作者:梅劲松
本文档和程序为MIT授权
说到嗅探必须要讲到所支持的环境,并非只能对本机或者HUB环境才能使用。对于交换机,你可以指定一个口为嗅探口,从这个口能拿到所有端口的数据。如果这个交换是核心交换,那么你所能取到数据将更多。(三层交换一般都可以指定嗅探口)
1、如果你已经是个python爱好者你机器一定有了python的运行环境,如果你没有可以到www.python.org去下载一个。我使用的还是python 2.3。
2、这段程序需要pcap模块支持,你可以到http://monkey.org/~dugsong/pypcap/去下载一个,它有unix和win两个版本,请注意,win下他需要winpcap支持,如果你没有这个,请再下载winpcap。同样,如果你是在unix下使用,请下载libpcap。
3、安装pcap没有太多说的,win下是个exe,直接运行。unix下直接make就可以了。
4、打开你的记事本,将以下代码保存在sniffer-QQ.py这个文件中。
# -*- coding: cp936 -*-
import pcap ,struct
pack=pcap.pcap()
pack.setfilter('udp')
key=''
for recv_time,recv_data in pack:
recv_len=len(recv_data)
if recv_len == 102 and recv_data[42]== chr(02) and recv_data[101] == chr(03):
print struct.unpack('>I',recv_data[49:53])[0]
print '登陆了'
elif recv_len == 55:
print struct.unpack('>I',recv_data[49:53])[0]
print '登陆了'
如果你在*nix下运行,请将# -*- coding: cp936 -*-更改为# -*- coding: utf-8 -*-
好了,你可以运行你的python程序了,试着登陆你的QQ。看你的QQ号码是否被抓下来了。
这里付上我的抓屏结果
D:\socket-qq>;sniffer-QQ.py
278333853
12345
1234567890
1234567890
1234567890
278333853
1234567890
1234567890
278333853
278333853
278333853
12345
1234567890
1234567890
1234567890
278333853
1234567890
1234567890
278333853
278333853
利用的什么原理呢。
QQ使用udp协议来和服务器进行通讯,当数据包在传输的时候。udp报文被抓了下来。而登陆包是以0x02开头0x03为结尾的,我们先判断是否为正确的登陆包,当然,登陆包的长度都为102个字节。我们取出结构中特定的位置,就是你的QQ号码了。
如果还有什么疑问,请大家跟贴。
netstat -aon|findstr "端口号"查看端口是否被占用
C:\Users\Administrator>netstat -ano|findstr 80
TCP 0.0.0.0:7680 0.0.0.0:0 LISTENING 3668
TCP 0.0.0.0:8680 0.0.0.0:0 LISTENING 8552
TCP 10.10.0.94:61717 47.102.197.26:80 ESTABLISHED 13932
TCP 10.10.0.94:61720 116.128.171.192:8080 ESTABLISHED 8552
TCP 10.10.0.94:61838 47.102.253.179:80 ESTABLISHED 13932
TCP 10.10.0.94:62936 47.102.253.179:80 ESTABLISHED 13932
TCP 10.10.0.94:62974 223.166.152.100:80 TIME_WAIT 0
TCP 127.0.0.1:9080 0.0.0.0:0 LISTENING 13932
TCP [::]:7680 [::]:0 LISTENING 3668
UDP [fe80::a564:aba:9613:90ce%14]:2177 *:* 9484
UDP [fe80::a564:aba:9613:90ce%14]:5353 *:* 3400
UDP [fe80::a8aa:4679:3642:9444%18]:2177 *:* 9484
今天在安装Apache的时候,最后显示80端口被占用,安装无法完成,我机器上没有装IIS,仔细想想也没有什么其他占用80端口的软件,最后google了一下,用下面的方法居然查出是被迅雷占用,退出迅雷,成功安装Apache,只是不明白迅雷为什么要用80端口。
netstat --help
-a 显示所有连接和侦听端口。
-n 以数字形式显示地址和端口号。
-o 显示拥有的与每个连接关联的进程 ID。
————————————————————————————————————————————————
用这个命令,在DOS窗口执行:netstat -ano
看看占用0.0.0:80端口的PID是多少
然后在“任务管理器”中查到与该PID对应的程序。
如果任务管理器的进程页中看不到PID栏,则在任务管理器的菜单 查看--选择列中选择一下PID(进程标识符)就可以了。



————————————————————————————————————————————————
在windows命令行窗口下执行:
C:/>netstat -aon|findstr "4444"
TCP 127.0.0.1:4444 0.0.0.0:0 LISTENING 2434
由上面得知,端口被进程号为2434的进程占用,继续执行下面命令:
C:/>tasklist|findstr "2434"
javaw.exe 2434 Console 0 16,064 K
这样就可以很轻松的kill掉javaw.exe 来腾出4444端口
WINXP下杀死进程的一个DOS命令 ntsd
杀进程,关键是找到这个进程的启动方式,不然下次重启它又出来了。其实用Windows自带的工具就能杀大部分进程:
c:/>ntsd -c q -p PID
只有System、SMSS.EXE和CSRSS.EXE不能杀。前两个是纯内核态的,最后那个是Win32子系统,ntsd本身需要它。ntsd 从2000开始就是系统自带的用户态调试工具。被调试器附着(attach)的进程会随调试器一起退出,所以可以用来在命令行下终止进程。使用ntsd自动就获得了debug权限,从而能杀掉大部分的进程。ntsd会新开一个调试窗口,本来在纯命令行下无法控制,但如果只是简单的命令,比如退出(q),用 -c参数从命令行传递就行了。
开个cmd.exe窗口,输入:
ntsd -c q -p PID
把最后那个PID,改成你要终止的进程的ID。如果你不知道进程的ID,任务管理器->进程选项卡->查看->选择列->勾上"PID(进程标识符)",然后就能看见了。
来自:http://blog.csdn.net/kbeanwu/article/details/5779365
C:\Users\Administrator>netstat -ano|findstr 80
TCP 0.0.0.0:7680 0.0.0.0:0 LISTENING 3668
TCP 0.0.0.0:8680 0.0.0.0:0 LISTENING 8552
TCP 10.10.0.94:61717 47.102.197.26:80 ESTABLISHED 13932
TCP 10.10.0.94:61720 116.128.171.192:8080 ESTABLISHED 8552
TCP 10.10.0.94:61838 47.102.253.179:80 ESTABLISHED 13932
TCP 10.10.0.94:62936 47.102.253.179:80 ESTABLISHED 13932
TCP 10.10.0.94:62974 223.166.152.100:80 TIME_WAIT 0
TCP 127.0.0.1:9080 0.0.0.0:0 LISTENING 13932
TCP [::]:7680 [::]:0 LISTENING 3668
UDP [fe80::a564:aba:9613:90ce%14]:2177 *:* 9484
UDP [fe80::a564:aba:9613:90ce%14]:5353 *:* 3400
UDP [fe80::a8aa:4679:3642:9444%18]:2177 *:* 9484
今天在安装Apache的时候,最后显示80端口被占用,安装无法完成,我机器上没有装IIS,仔细想想也没有什么其他占用80端口的软件,最后google了一下,用下面的方法居然查出是被迅雷占用,退出迅雷,成功安装Apache,只是不明白迅雷为什么要用80端口。
netstat --help
-a 显示所有连接和侦听端口。
-n 以数字形式显示地址和端口号。
-o 显示拥有的与每个连接关联的进程 ID。
————————————————————————————————————————————————
用这个命令,在DOS窗口执行:netstat -ano
看看占用0.0.0:80端口的PID是多少
然后在“任务管理器”中查到与该PID对应的程序。
如果任务管理器的进程页中看不到PID栏,则在任务管理器的菜单 查看--选择列中选择一下PID(进程标识符)就可以了。



————————————————————————————————————————————————
在windows命令行窗口下执行:
C:/>netstat -aon|findstr "4444"
TCP 127.0.0.1:4444 0.0.0.0:0 LISTENING 2434
由上面得知,端口被进程号为2434的进程占用,继续执行下面命令:
C:/>tasklist|findstr "2434"
javaw.exe 2434 Console 0 16,064 K
这样就可以很轻松的kill掉javaw.exe 来腾出4444端口
WINXP下杀死进程的一个DOS命令 ntsd
杀进程,关键是找到这个进程的启动方式,不然下次重启它又出来了。其实用Windows自带的工具就能杀大部分进程:
c:/>ntsd -c q -p PID
只有System、SMSS.EXE和CSRSS.EXE不能杀。前两个是纯内核态的,最后那个是Win32子系统,ntsd本身需要它。ntsd 从2000开始就是系统自带的用户态调试工具。被调试器附着(attach)的进程会随调试器一起退出,所以可以用来在命令行下终止进程。使用ntsd自动就获得了debug权限,从而能杀掉大部分的进程。ntsd会新开一个调试窗口,本来在纯命令行下无法控制,但如果只是简单的命令,比如退出(q),用 -c参数从命令行传递就行了。
开个cmd.exe窗口,输入:
ntsd -c q -p PID
把最后那个PID,改成你要终止的进程的ID。如果你不知道进程的ID,任务管理器->进程选项卡->查看->选择列->勾上"PID(进程标识符)",然后就能看见了。
来自:http://blog.csdn.net/kbeanwu/article/details/5779365
PHP4.4.4, MySQL4.0.27 Apache/2.0.59
恋爱多次还是单身,你会不会沮丧?接不到男人的电话,你会不会抓狂?你是不是坚持不懈地把约会想象成婚姻前奏,认定跟你出双入对的他就是未来老公?姐妹们,这样子爱是不是负担太重?干吗不轻松点,让我们学学男人们,看看他们是如何对待感情的!
下次他再惹到你,就能以其之道,还诸彼身。
1、女人一恋爱就忍不住往终身大事上联想。男人恋爱首先不是为了谈婚论嫁,而是为了从中获得乐趣,婚姻和恋爱对他完全是两码事。
2、女人得到爱情后反而不安,因为怕失去。男性得不到的时候才不安,因为渴望身体的征服。
3、女人总想变着法地改造男人。男人最想改变的只有两件东西,自己宝贝的大小和与日本的关系,女人永远不在改变的考虑之列,要么全盘接受,要么分手。
4、女人的爱因崇拜而生。 男人的爱因性而生。
5、女人爱等男人的电话。男人不会守着电话等你打来。别信他说什么手机没电,千万记住:如果想你了,就算没手机,他也会爬上屋顶嚷嚷,让你知道。
6、女人往往终生只爱一种男人。男人的口味时时会变。
7、女人总是不断求证男人的爱情,追求托付终身的感觉。男人更在乎感官和理性的决定,一旦界定两人的关系,就懒得去反复考证这种关系的正确性,而仅仅满足于感官层次。
8、女人担忧年华老去,绝对寿命长,有效生命短。男人相信他们在任何年龄都可以性感,绝对寿命短,有效生命长。
9、女人相信:男人太多,好男人太少。男人相信:姑娘太多,时间太少。
10、女人常常为尚未发生的事情担忧。男人除了担忧生育能力的消失,其他未发生事件一概无所谓。11、女人喜欢谈论感情。男人不喜欢,他们更愿意谈论足球或时政。
12、女人对男人不满时,常会借题发挥,无限上纲。男人喜欢就事论事,绝不会因为你洗了两个小时的澡而联想到国家水资源紧张进而联想到自己被渴死的悲惨下场从而断定你有险恶用心。
13、女人迷恋被追求的滋味。男人喜欢在感情中采取主动,中国的男女比例已经严重失衡,不用抢的,哪天才轮到自己?
14、女人为情所困时,常会不断降低底线,委曲求全。男人会事先设定好底线,一旦触及,便毫不犹豫转身离开。
15、女人担心男人介意自己的恋爱史,其实男人很容易宽容女人的过去,因为与前任相比,他是胜利者。
16、女人会避免跟男性朋友上床,因为这样会把友谊和爱情搞混。男人不在乎这个。
17、分手了,女人很难接受与前男友再有肌肤之亲。对男人而言,同前女友****就像回家串门,没什么大不了。
18、女人犯错,愿意主动承认错误。男人死要面子,尤其当着自己女人的面,所以即使错了,也会想尽办法不承认。
19、女人喜欢把三姑六婆的事告诉男人。而在男人眼里,这么做只会显得你很三八。
20、女人爱把男友介绍给姐妹们。男人则不愿太快结识你的姊妹帮,理由很简单,你一个人看着他就够了,他可不想有更多人帮你看着他。
21、第一次约会后,女人即使看上对方也会矜持地等待。男人如果没来电话,一定不是因为矜持,只能说明他对你不感兴趣。
22、女人焦虑时用倾诉来缓解。男人为了不给人留下娘娘腔的印象,只好将所有不爽的事情埋在心里。
23、分手时,女人往往光顾着伤心而不知道怎么分得漂漂亮亮。男人理性的思维和雄辩的口才在这时总能派上大用场,他会让你觉得分开是件不可避免而且利国利民的事情。
下次他再惹到你,就能以其之道,还诸彼身。
1、女人一恋爱就忍不住往终身大事上联想。男人恋爱首先不是为了谈婚论嫁,而是为了从中获得乐趣,婚姻和恋爱对他完全是两码事。
2、女人得到爱情后反而不安,因为怕失去。男性得不到的时候才不安,因为渴望身体的征服。
3、女人总想变着法地改造男人。男人最想改变的只有两件东西,自己宝贝的大小和与日本的关系,女人永远不在改变的考虑之列,要么全盘接受,要么分手。
4、女人的爱因崇拜而生。 男人的爱因性而生。
5、女人爱等男人的电话。男人不会守着电话等你打来。别信他说什么手机没电,千万记住:如果想你了,就算没手机,他也会爬上屋顶嚷嚷,让你知道。
6、女人往往终生只爱一种男人。男人的口味时时会变。
7、女人总是不断求证男人的爱情,追求托付终身的感觉。男人更在乎感官和理性的决定,一旦界定两人的关系,就懒得去反复考证这种关系的正确性,而仅仅满足于感官层次。
8、女人担忧年华老去,绝对寿命长,有效生命短。男人相信他们在任何年龄都可以性感,绝对寿命短,有效生命长。
9、女人相信:男人太多,好男人太少。男人相信:姑娘太多,时间太少。
10、女人常常为尚未发生的事情担忧。男人除了担忧生育能力的消失,其他未发生事件一概无所谓。11、女人喜欢谈论感情。男人不喜欢,他们更愿意谈论足球或时政。
12、女人对男人不满时,常会借题发挥,无限上纲。男人喜欢就事论事,绝不会因为你洗了两个小时的澡而联想到国家水资源紧张进而联想到自己被渴死的悲惨下场从而断定你有险恶用心。
13、女人迷恋被追求的滋味。男人喜欢在感情中采取主动,中国的男女比例已经严重失衡,不用抢的,哪天才轮到自己?
14、女人为情所困时,常会不断降低底线,委曲求全。男人会事先设定好底线,一旦触及,便毫不犹豫转身离开。
15、女人担心男人介意自己的恋爱史,其实男人很容易宽容女人的过去,因为与前任相比,他是胜利者。
16、女人会避免跟男性朋友上床,因为这样会把友谊和爱情搞混。男人不在乎这个。
17、分手了,女人很难接受与前男友再有肌肤之亲。对男人而言,同前女友****就像回家串门,没什么大不了。
18、女人犯错,愿意主动承认错误。男人死要面子,尤其当着自己女人的面,所以即使错了,也会想尽办法不承认。
19、女人喜欢把三姑六婆的事告诉男人。而在男人眼里,这么做只会显得你很三八。
20、女人爱把男友介绍给姐妹们。男人则不愿太快结识你的姊妹帮,理由很简单,你一个人看着他就够了,他可不想有更多人帮你看着他。
21、第一次约会后,女人即使看上对方也会矜持地等待。男人如果没来电话,一定不是因为矜持,只能说明他对你不感兴趣。
22、女人焦虑时用倾诉来缓解。男人为了不给人留下娘娘腔的印象,只好将所有不爽的事情埋在心里。
23、分手时,女人往往光顾着伤心而不知道怎么分得漂漂亮亮。男人理性的思维和雄辩的口才在这时总能派上大用场,他会让你觉得分开是件不可避免而且利国利民的事情。
很多男人总是认为现代女人的功利心,在挑选伴侣的问题上表现得最为明显。他们大多认为女人只看重男人的钱袋和“面皮”,既不帅又没钱的男人难以讨得女人的欢心。其实这样的看法是存在很大错误的。女性择偶指标其实是因人其异,通常因为年龄、学历、性格和背景的不同而有所转变,不过,在择偶时,还是普遍存在着一些“潜规则”:
首先,一个女人在挑选婚姻伴侣时,最看重的既不是财,也不是貌,而是对方的人品和性格。一个人品有问题的男人,即使能够得到女人的爱情,但这种爱情也是很难长久的,而想步入婚姻也是十分困难的,毕竟,现在每个女人都知道:跟一个人品有问题的男人一起生活,无疑是在枕边放了一枚定时炸弹,没准儿哪天就会让自己灰飞烟灭,跟这样的男人结婚,哪个女人都得心惊肉跳外加心理失态!另外,80%的离婚夫妻的离婚理由都是性格不合,可以说情投意合是两个人婚姻的最大保障!
其次,女人选择结婚对象时看重他的家庭条件。这也就是说,为什么出身名门的男人总是女人心目中的理想丈夫人选。很多男人的素质优劣是从小养成的生活习惯的总合,而且与家庭条件好的男人结婚就算不是想得到来自于婆家的帮助,也至少是可以减少一些不必要的负担,这也是很多城市女孩儿排斥“城乡结合式婚姻”最充分的理由。另外,男方家长的受教育程度以及工作条件也是要考虑的重点因素,毕竟婆媳关系是个千古难题,能遇上个通情达理的婆婆是每个女孩子的愿望,而在学历以及工作等方面高素质的婆婆更能与年轻人进行交流,这也是大部分的事实情况。
再其次,是对方的职业状况。钱固然很重要,但跟劫匪毒贩一起过日子也是蛮恐怖的事情,钱是有了,安全全无,自然也就别谈幸福了!有一份稳固而且收入颇丰的职业,这样的男人女人最看重。
再再其次,是男人的朋友圈子,俗话说“近朱者赤,近墨者黑”,尤其是在现如今这样的“关系社会”中,一个人的成功绝对不是单枪匹马的成就,而是周围人努力的总合,周围全是狐朋狗友的男人,很难让女人看到成功的希望,自然也就很难让女人把他们当成理想伴侣人选。
最后,才是男人的相貌,女人总是对外貌条件优裕的男人投以青睐的目光,但真正要结婚时却往往更愿意挑选一位相貌不是那么太英俊的的男人做丈夫,毕竟帅男面临的诱惑太多,没准儿哪天,自己这位原配夫人就会成为下堂妻,生活在不安全的婚姻里,女人通常难以获得幸福!
当然,不论这些规则是明是潜,能够获得幸福,就是最重要的目的!
首先,一个女人在挑选婚姻伴侣时,最看重的既不是财,也不是貌,而是对方的人品和性格。一个人品有问题的男人,即使能够得到女人的爱情,但这种爱情也是很难长久的,而想步入婚姻也是十分困难的,毕竟,现在每个女人都知道:跟一个人品有问题的男人一起生活,无疑是在枕边放了一枚定时炸弹,没准儿哪天就会让自己灰飞烟灭,跟这样的男人结婚,哪个女人都得心惊肉跳外加心理失态!另外,80%的离婚夫妻的离婚理由都是性格不合,可以说情投意合是两个人婚姻的最大保障!
其次,女人选择结婚对象时看重他的家庭条件。这也就是说,为什么出身名门的男人总是女人心目中的理想丈夫人选。很多男人的素质优劣是从小养成的生活习惯的总合,而且与家庭条件好的男人结婚就算不是想得到来自于婆家的帮助,也至少是可以减少一些不必要的负担,这也是很多城市女孩儿排斥“城乡结合式婚姻”最充分的理由。另外,男方家长的受教育程度以及工作条件也是要考虑的重点因素,毕竟婆媳关系是个千古难题,能遇上个通情达理的婆婆是每个女孩子的愿望,而在学历以及工作等方面高素质的婆婆更能与年轻人进行交流,这也是大部分的事实情况。
再其次,是对方的职业状况。钱固然很重要,但跟劫匪毒贩一起过日子也是蛮恐怖的事情,钱是有了,安全全无,自然也就别谈幸福了!有一份稳固而且收入颇丰的职业,这样的男人女人最看重。
再再其次,是男人的朋友圈子,俗话说“近朱者赤,近墨者黑”,尤其是在现如今这样的“关系社会”中,一个人的成功绝对不是单枪匹马的成就,而是周围人努力的总合,周围全是狐朋狗友的男人,很难让女人看到成功的希望,自然也就很难让女人把他们当成理想伴侣人选。
最后,才是男人的相貌,女人总是对外貌条件优裕的男人投以青睐的目光,但真正要结婚时却往往更愿意挑选一位相貌不是那么太英俊的的男人做丈夫,毕竟帅男面临的诱惑太多,没准儿哪天,自己这位原配夫人就会成为下堂妻,生活在不安全的婚姻里,女人通常难以获得幸福!
当然,不论这些规则是明是潜,能够获得幸福,就是最重要的目的!
* Demonstrates structures that contain other structures. */
/* Receives input for corner coordinates of a rectangle and
calculates the area. Assumes that the y coordinate of the
lower-right corner is greater than the y coordinate of the
upper-left corner, that the x coordinate of the lower-
right corner is greater than the x coordinate of the upper-
left corner, and that all coordinates are positive. */
#include <stdio.h>
int length, width;
long area;
struct coord{
int x;
int y;
};
struct rectangle{
struct coord topleft;
struct coord bottomrt;
} mybox;
int main( void )
{
/* Input the coordinates */
printf("\nEnter the top left x coordinate: ");
scanf("%d", &mybox.topleft.x);
printf("\nEnter the top left y coordinate: ");
scanf("%d", &mybox.topleft.y);
printf("\nEnter the bottom right x coordinate: ");
scanf("%d", &mybox.bottomrt.x);
printf("\nEnter the bottom right y coordinate: ");
scanf("%d", &mybox.bottomrt.y);
/* Calculate the length and width */
width = mybox.bottomrt.x - mybox.topleft.x;
length = mybox.bottomrt.y - mybox.topleft.y;
/* Calculate and display the area */
area = width * length;
printf("\nThe area is %ld units.\n", area);
return 0;
}
/* Receives input for corner coordinates of a rectangle and
calculates the area. Assumes that the y coordinate of the
lower-right corner is greater than the y coordinate of the
upper-left corner, that the x coordinate of the lower-
right corner is greater than the x coordinate of the upper-
left corner, and that all coordinates are positive. */
#include <stdio.h>
int length, width;
long area;
struct coord{
int x;
int y;
};
struct rectangle{
struct coord topleft;
struct coord bottomrt;
} mybox;
int main( void )
{
/* Input the coordinates */
printf("\nEnter the top left x coordinate: ");
scanf("%d", &mybox.topleft.x);
printf("\nEnter the top left y coordinate: ");
scanf("%d", &mybox.topleft.y);
printf("\nEnter the bottom right x coordinate: ");
scanf("%d", &mybox.bottomrt.x);
printf("\nEnter the bottom right y coordinate: ");
scanf("%d", &mybox.bottomrt.y);
/* Calculate the length and width */
width = mybox.bottomrt.x - mybox.topleft.x;
length = mybox.bottomrt.y - mybox.topleft.y;
/* Calculate and display the area */
area = width * length;
printf("\nThe area is %ld units.\n", area);
return 0;
}
比尔~盖茨最聪明的地方不是他做了什么,而是他没做什么。他可以做许许多多的事情,却只专注在自己的操作系统,软件研发二不被市场中别的诱惑吸引。
做人要谦卑,做事要学会不断找问题--比尔·盖茨
在真理面前的谦卑,是比尔·盖茨一种内心态度,远比外面的风光无限、备受世人崇敬更重要。
有了在真理面前的谦卑,就可以在这个浮躁的世界中保持一颗安静的心灵,有更大的创造力和影响力
做人要谦卑,做事要学会不断找问题--比尔·盖茨
在真理面前的谦卑,是比尔·盖茨一种内心态度,远比外面的风光无限、备受世人崇敬更重要。
有了在真理面前的谦卑,就可以在这个浮躁的世界中保持一颗安静的心灵,有更大的创造力和影响力
从大学毕业,现在工作了,钱也稍稍宽松点,为此特定此计划,四年必学的科目:
1。必须学好c语言Bc++,计算机基础和windows用户界面开发。
2.必须深入学习php的构架和他的php模块开发,这个作为前台开发的利器,通读mambo系统的代码和核心设计思想。
3.必须学会freebsd的优化
3.必须学会Apache模块的编写和相关代码的阅读
4.必须学会python,这将为几年后的后台开发打下基础
5.必须学会lucene这个开源搜索引擎的使用,以及它的中文本土化的处理,这个是直接关系到海量数据的存储打下基础
6.必须学好mysql的优化和rpc调用socket深入编程。
7.必须学好英语。
8.寻找一帮牛人朋友,打下打天下的基础,包括关注互联网的需求和销售市场随时把握。
最后,有空安排好先后顺序。。。。(千里之行始于足下)
渐渐发现这些东西太多:
去掉如下内容:
去掉python的学习。
1。必须学好c语言Bc++,计算机基础和windows用户界面开发。
2.必须深入学习php的构架和他的php模块开发,这个作为前台开发的利器,通读mambo系统的代码和核心设计思想。
3.必须学会freebsd的优化
3.必须学会Apache模块的编写和相关代码的阅读
4.必须学会python,这将为几年后的后台开发打下基础
5.必须学会lucene这个开源搜索引擎的使用,以及它的中文本土化的处理,这个是直接关系到海量数据的存储打下基础
6.必须学好mysql的优化和rpc调用socket深入编程。
7.必须学好英语。
8.寻找一帮牛人朋友,打下打天下的基础,包括关注互联网的需求和销售市场随时把握。
最后,有空安排好先后顺序。。。。(千里之行始于足下)
渐渐发现这些东西太多:
去掉如下内容:
去掉python的学习。
FreeBSD套接字模型
BSD套接字构建在基本的UNIX®模型上: 一切都是文件。那么,在我们的例子中,套接字将使我接收一个HTTP文件,就这么说。然后我们要负责将 PNG文件从中提取出来。
由于联网的复杂性,我们不能只使用 open系统调用,或open() C 函数。而是我们需要分几步 “打开”一个套接字。
一旦我们做了这些,我们就能以处理任何文件描述符 的方式处理套接字。我们从它读取 (read),向它写入(write),建立管道(pipe), 必定还要关闭(close)它。
重要的套接字函数
FreeBSD提供了与套接字相关的不同函数, “打开”一个套接字我们只需要四个函数。 有时我们只需要两个。
1 客户端-服务器差异
典型情况中,以套接字为基础的数据通信一端是一个 服务器,另一端是一个客户端。
1.1 通用元素
1.1.1 socket
这一个函数在客户端和服务器都要使用:socket(2)。它是这样被声明的:
int socket(int domain, int type, int protocol);
返回值的类型与open的相同,一个整数。 FreeBSD从和文件句柄相同的池中分配它的值。这就是允许套接字被以对文件相同的方式处理的原因。
参数domain告诉系统你需要使用什么协议族。有许多种协议族存在,有些是某些厂商专有的,其它的都非常通用。协议族的声明在 sys/socket.h中
使用PF_INET是对于 UDP, TCP 和其它网间协议(IPv4)的情况。
对于参数type有五个定义好的值,也在 sys/socket.h中。这些值都以 “SOCK_”开头。 其中最通用的是SOCK_STREAM, 它告诉系统你正需要一个可靠的流传送服务 (和PF_INET一起使用时是指 TCP)。
如果指定SOCK_DGRAM, 你是在请求无连接报文传送服务 (在我们的情形中是UDP)。
如何你需要处理基层协议 (例如IP),或者甚至是网络接口 (例如,以太网),你就需要指定 SOCK_RAW。
最后,参数protocol取决于前两个参数,并非总是有意义。在以上情形中,使用取值0。
未连接的套接字: 对于函数socket 我们还没有指定我们要连往什么其它(主机)系统。 我们新建的套接字还是未连接的。这是有意的:拿电话类比,我们刚把调制解调器接在电话线上。我们既没有告诉调制解调器发起一个呼叫,也不会应答电话振铃。
1.1.2 sockaddr
各种各样的套接字函数需要指定地址,那是一小块内存空间 (用C语言术语是指向一小块内存空间的指针)。在 sys/socket.h中有各种各样如 struct sockaddr的声明。 这个结构是这样被声明的:
/*
* 内核用来存储大多数种类地址的结构
*/
struct sockaddr {
unsigned char sa_len; /* 总长度 */
sa_family_t sa_family; /* 地址族 */
char sa_data[14]; /* 地址值,实际可能更长 */
};
#define SOCK_MAXADDRLEN 255 /* 可能的最长的地址长度 */
注意对于sa_data域的定义具有不确定性。 那只是被定义为14字节的数组, 注释暗示内容可能超过14字节
这种不确定性是经过深思熟虑的。套接字是个非常强大的接口。多数人可能认为比Internet接口强不到哪里 ──大多数应用现在很可能都用它 ──套接字可被用于几乎任何种类的进程间通信, Internet(更精确的说是IP)只是其中的一种。
sys/socket.h提到的各种类型的协议 将被按照地址族对待,并把它们就列在 sockaddr定义的前面:
/*
* 地址族
*/
#define AF_UNSPEC 0 /* 未指定 */
#define AF_LOCAL 1 /* 本机 (管道,portal) */
#define AF_UNIX AF_LOCAL /* 为了向前兼容 */
#define AF_INET 2 /* 网间协议: UDP, TCP, 等等 */
#define AF_IMPLINK 3 /* arpanet imp 地址 */
#define AF_PUP 4 /* pup 协议: 例如BSP */
#define AF_CHAOS 5 /* MIT CHAOS 协议 */
#define AF_NS 6 /* 施乐(XEROX) NS 协议 */
#define AF_ISO 7 /* ISO 协议 */
#define AF_OSI AF_ISO
#define AF_ECMA 8 /* 欧洲计算机制造商协会 */
#define AF_DATAKIT 9 /* datakit 协议 */
#define AF_CCITT 10 /* CCITT 协议, X.25 等 */
#define AF_SNA 11 /* IBM SNA */
#define AF_DECnet 12 /* DECnet */
#define AF_DLI 13 /* DEC 直接数据链路接口 */
#define AF_LAT 14 /* LAT */
#define AF_HYLINK 15 /* NSC Hyperchannel */
#define AF_APPLETALK 16 /* Apple Talk */
#define AF_ROUTE 17 /* 内部路由协议 */
#define AF_LINK 18 /* 协路层接口 */
#define pseudo_AF_XTP 19 /* eXpress Transfer Protocol (no AF) */
#define AF_COIP 20 /* 面向连接的IP, 又名 ST II */
#define AF_CNT 21 /* Computer Network Technology */
#define pseudo_AF_RTIP 22 /* 用于识别RTIP包 */
#define AF_IPX 23 /* Novell 网间协议 */
#define AF_SIP 24 /* Simple 网间协议 */
#define pseudo_AF_PIP 25 /* 用于识别PIP包 */
#define AF_ISDN 26 /* 综合业务数字网(Integrated Services Digital Network) */
#define AF_E164 AF_ISDN /* CCITT E.164 推荐 */
#define pseudo_AF_KEY 27 /* 内部密钥管理功能 */
#define AF_INET6 28 /* IPv6 */
#define AF_NATM 29 /* 本征ATM访问 */
#define AF_ATM 30 /* ATM */
#define pseudo_AF_HDRCMPLT 31 /* 由BPF使用,就不必在接口输出例程
* 中重写头文件了
*/
#define AF_NETGRAPH 32 /* Netgraph 套接字 */
#define AF_SLOW 33 /* 802.3ad 慢速协议 */
#define AF_SCLUSTER 34 /* Sitara 集群协议 */
#define AF_ARP 35
#define AF_BLUETOOTH 36 /* 蓝牙套接字 */
#define AF_MAX 37
用于指定IP的是 AF_INET。这个符号对应着常量 2。
在sockaddr中的域 sa_family指定地址族, 从而决定预先只确定下大致字节数的 sa_data的实际大小。
特别是当地址族 是AF_INET时,我们可以使用 struct sockaddr_in,这可在 netinet/in.h中找到,任何需要 sockaddr的地方都以此作为实际替代。
/*
* 套接字地址,Internet风格
*/
struct sockaddr_in {
uint8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
我们可这样描绘它的结构:
三个重要的域是: sin_family,结构体的字节1; sin_port,16位值,在字节2和3; sin_addr,一个32位整数,表示 IP地址,存储在字节4-7。
现在,让我们尝试填满它。让我们假设我们正在写一个 daytime协议的客户端,这个协议只是简单的规定服务器写出一个代表当前日期和时间文本字符串到端口13。 我们需要使用 TCP/IP,所以我们需要指定在地址族域指定 AF_INET。 AF_INET被定义为 2。让我们使用 IP地址192.43.244.18,这指向 美国联邦政府(time.nist.gov)的服务器。
顺便说一下,域sin_addr被声明为类型 struct in_addr,这个类型定义在 netinet/in.h之中:
/*
* Internet 地址 (由历史原因而形成的结构)
*/
struct in_addr {
in_addr_t s_addr;
};
而in_addr_t是一个32位整数。
192.43.244.18 只是为了表示32位整数的方便写法,按每个八位字节列出, 以最高位的字节开始。
到目前为止,我已经看见了sockaddr。我们的计算机并不将短整数存储为一个16位实体,而是一个2字节序列。同样的,计算机将32位整数存储为4字节序列。
想象我们这样写程序:
sa.sin_family = AF_INET;
sa.sin_port = 13;
sa.sin_addr.s_addr = (((((192 << 8) | 43) << 8) | 244) << 8) | 18;
结果会是什么样的呢?
好,那当然是要依赖于其它因素的。在Pentium®或其它x86 为基础的计算机上,它会像这样:
在另一个不同的系统上,它可能会是:
在一台PDP计算机上,它可能又是另一个样子。 不过上面两种情况是今天最常用的了。
译者注: PDP的字节顺序在英语中称为middle-endian或mixed-endian。例如,原数0x44332211会被PDP存储为0x33441122。 VAX也采用这种字节顺序。
通常,要书写可移植的代码,程序员假设不存在那些差异。他们回避这种差异(除了他们使用汇编语言写代码的时候)。唉,可你不能在为套接字写代码时那样轻易的回避这种差异。
为什么?
因为当与另一台计算机通信时, 你通常不知道对方存储数据时是先存放最高位字节 (MSB)还是最低位字节 (LSB)。
你可能会有问题,“那么,套接字可以为我把握这种差异吗?”
它不能。
这个回答可能先是让你感到惊讶, 请记住通用的套接字接口只明白结构体sockaddr 中的域sa_len和sa_family。 你不必担心那里的字节顺序(当然, 在FreeBSD上sa_family只有一个字节, 但是许多其它的 UNIX® 系统没有 sa_len 并使用2字节给 sa_family,而且数据使用何种顺序都取决于计算机(译者注:此处英文原文的用词为“is native to”))。
其余的数据,也就只剩下sa_data[14]。 依照地址族,套接字只是将那些数据转发到目的地。
事实上,我们输入一个端口号, 是为了让其它计算机知道我们需要什么服务。并且,当我们提供服务时, 只有读取了端口号我们才知道其它计算机期望从我们这里获得什么服务。另一方面,套接字只将端口号作为数据转发,完全不去理会(译者注:此处英文原文用词为“interpret”)其中的内容。
同样的,我们输入IP地址,告诉途经的每台计算机要将我们的数据发送到哪里。 套接字依然只将其按数据转发。
那就是为什么我们(指程序员,而不是套接字)不得不把使用在我们的计算机上的字节顺序和发送给其它计算机时使用的传统字节顺序区分开来。
我们将把我们的计算机上使用的字节顺序称为 主机字节顺序, 或者就是主机顺序.
有一个在IP发送多字节数据的传统: 最高位字节(MSB)优先。 这,我们将用网络字节顺序提及, 或者简单的称为网络顺序。
现在,如果我们在Intel计算机上编译上面的代码, 我们的主机字节顺序将产生:
但是网络字节顺序 要求我们先存储数据的最高位字节(MSB):
不幸的是,我们的主机顺序 恰恰与网络顺序相反。
我们有几种方法解决这个问题。一种是在我们的代码中 倒置数值:
sa.sin_family = AF_INET;
sa.sin_port = 13 << 8;
sa.sin_addr.s_addr = (((((18 << 8) | 244) << 8) | 43) << 8) | 192;
这将欺骗我们的编译器把数据按网络字节顺序存储。在一些情形中,这的确是个有效的办法 (例如,用汇编语言编程)。然而,在多数情形中,这会导致一个问题。
想象一下,你用C语言写了一个套接字程序。 你知道它将运行在一台Pentium计算机上, 于是你倒着输入你的所有常量,并且把它们强置为 网络字节顺序。 它工作正常。
然而,有一台,你所信任的旧 Pentium 变成一台生了锈的旧 Pentium。你把它更换为一个 主机顺序与 网络顺序相同的系统。 你需要重新编译你的所有软件。你的所有软件中除了你写的那个程序,都继续工作正常。
你早已经忘记你将全部常量强置为与 主机顺序相反。你花费宝贵时间拽头发,呼唤你曾经听到过的(有些是你编造的)所有上帝的名字, 用击球棍敲打你的显示器,还上演所有其它的传统仪式 试图找到一个原本好端端的程序突然完成不能工作的原因。
最终,你找到了原因,发了一通誓言, 开始重写你的代码。
幸运的是,你不是第一个面对这个问题的人。 其它人已经创建 htons(3) 和 htonl(3) C 语言函数分别将 short and long 从主机字节顺序转换为 网络字节顺序, 并且还有 ntohs(3) 和 ntohl(3) C 语言函数进行着另外的转换。
在最高位字节(MSB)-最前 的系统上,这些函数什么都不做。在 最低位字节(LSB)-最前的系统上它们将值转换为正确的顺序。
这样一来,无论你的软件在什么系统上编译, 如果你使用这些函数,你的数据最终都将是正确的顺序。
1.2 客户端函数
典型情况中,客户端初始化到服务器的连接。 客户端知道要呼叫哪台服务器:它知道服务器的IP地址,并且知道服务器驻守的 端口。这就好比你拿起电话拨号码 (地址),然后,有人应答, 呼叫负责狂欢的人 (端口)。
1.2.1 connect
一旦一个客户端已经建立了一个套接字,就需要把它连接到一个远方系统的一个端口上。这使用 connect(2):
int connect(int s, const struct sockaddr *name, socklen_t namelen);
参数 s 是套接字, 那是由函数socket返回的值。 name 是一个指向 sockaddr的指针,这个结构体我们已经展开讨论过了。 最后,namelen通知系统 在我们的sockaddr结构体中有多少字节。
如果 connect 成功, 返回 0。否则返回 -1 并将错误码存放于 errno之中。
有许多种connect可能失败的原因。例如,试图发起一个Internet连接时, IP 地址可能不存在,或可能停机, 或者就是太忙,或者可能没有在指定端口上有服务器监听。或者直接拒绝任何特定代码的请求。
1.2.2 我们的第一个客户端
现在我们知道足够多去写一个非常简单的客户端, 一个从192.43.244.18获取当前时间并打印到 stdout的程序。
/*
* daytime.c
*
* G. Adam Stanislav 编程
*/
#include
#include
#include
#include
int main() {
register int s;
register int bytes;
struct sockaddr_in sa;
char buffer[BUFSIZ+1];
if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
return 1;
}
bzero(&sa, sizeof sa);
sa.sin_family = AF_INET;
sa.sin_port = htons(13);
sa.sin_addr.s_addr = htonl((((((192 << 8) | 43) << 8) | 244) << 8) | 18);
if (connect(s, (struct sockaddr *)&sa, sizeof sa) < 0) {
perror("connect");
close(s);
return 2;
}
while ((bytes = read(s, buffer, BUFSIZ)) > 0)
write(1, buffer, bytes);
close(s);
return 0;
}
继续,把它输入到你的编辑器中,保存为 daytime.c,然后编译并运行:
% cc -O3 -o daytime daytime.c
% ./daytime
52079 01-06-19 02:29:25 50 0 1 543.9 UTC(NIST) *
%
在这一情形中,日期是2001年6月19日,时间是 02:29:25 UTC。你的结果会很自然的变化。
1.3 服务器函数
典型的服务器不初始化连接。 相反,服务器等待客户端呼叫并请求服务。服务器不知道客户端什么时候会呼叫, 也不知道有多少客户端会呼叫。服务器就是这样静坐在那儿,耐心等待,一会儿,又一会儿, 它突然发觉自身被从许多客户端来的请求围困,所有的呼叫都同时来到。
套接字接口提供三个基本的函数处理这种情况。
1.3.1 bind
端口像是电话线分机:在你拨一个号码后, 你拨分机到一个特定的人或部门。
有65535个 IP 端口,但是一台服务器通常只处理从其中一个端口进入的请求。这就像告诉电话室操作员我们处于工作状态并在一个特定分机应答电话。 我们使用 bind(2) 告诉套接字我们要服务的端口。
int bind(int s, const struct sockaddr *addr, socklen_t addrlen);
除了在 addr 中指定端口, 服务器还可以包含其自身的 IP 地址。不过,也可以就使用符号常量 INADDR_ANY,指示服务于无论哪个 IP上的指定端口上的请求。 这个符号和几个相同的常量,声明在 netinet/in.h之中。
#define INADDR_ANY (u_int32_t)0x00000000
想象我们正在为 daytime协议在 TCP/IP的基础上写一个服务器。 回想起使用端口13。我们的sockaddr_in 结构应当像这样:
1.3.2 listen
继续我们的办公室电话类比, 在你告诉电话中心操作员你会在哪个分机后,现在你走进你的办公室,确认你自己的电话已插上并且振铃已被打开。还有,你确认呼叫等待功能开启,这样即使你正在与其它人通话, 也可听见电话振铃。
服务器执守所有经过函数 listen(2) 操作的套接字。
int listen(int s, int backlog);
在这里,变量backlog 告诉套接字在忙于处理上一个请求时还可以接受多少个进入的请求。换句话说,这决定了挂起连接的队列的最大大小。
7.5.1.3.3 accept
在你听见电话铃响后,你应答呼叫接起电话。 现在你已经建立起一个与你的客户的连接。这个连接保持到你或你的客户挂线。
服务器通过使用函数 accept(2) 接受连接。
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
注意,这次 addrlen 是一个指针。这是必要的,因为在此情形中套接字要 填上 addr,这是一个 sockaddr_in 结构体。
返回值是一个整数。其实, accept 返回一个 新套接字。你将使用这个新套接字与客户通信。
老套接字会发生什么呢?它继续监听更多的请求 (想起我们传给listen的变量 backlog了吗?),直到我们 close(关闭) 它。
现在,新套接字仅对通信有意义,是完全接通的。 我们不能再把它传给 listen接受更多的连接。
1.3.4 我们的第一个服务器
我们的第一个服务器会比我们的第一个客户端复杂一些:我们不仅用到了更多的套接字函数, 还需要把程序写成一个守护程序。
这最好写成:在绑定端口后建立一个子进程。 主进程随后退出,将控制权交回给 shell (或者任何调用主进程的程序)。
子进程调用 listen,然后启动一个无休止循环。这个循环接受连接,提供服务, 最后关闭连接的套接字。
/*
* daytimed - 端口 13 的服务器
*
* G. Adam Stanislav 编程
* 2001年6月19日
*/
#include
#include
#include
#include
#include
#include
#define BACKLOG 4
int main() {
register int s, c;
int b;
struct sockaddr_in sa;
time_t t;
struct tm *tm;
FILE *client;
if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
return 1;
}
bzero(&sa, sizeof sa);
sa.sin_family = AF_INET;
sa.sin_port = htons(13);
if (INADDR_ANY)
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(s, (struct sockaddr *)&sa, sizeof sa) < 0) {
perror("bind");
return 2;
}
switch (fork()) {
case -1:
perror("fork");
return 3;
break;
default:
close(s);
return 0;
break;
case 0:
break;
}
listen(s, BACKLOG);
for (;;) {
b = sizeof sa;
if ((c = accept(s, (struct sockaddr *)&sa, &b)) < 0) {
perror("daytimed accept");
return 4;
}
if ((client = fdopen(c, "w")) == NULL) {
perror("daytimed fdopen");
return 5;
}
if ((t = time(NULL)) < 0) {
perror("daytimed time");
return 6;
}
tm = gmtime(&t);
fprintf(client, "%.4i-%.2i-%.2iT%.2i:%.2i:%.2iZ\n",
tm->tm_year + 1900,
tm->tm_mon + 1,
tm->tm_mday,
tm->tm_hour,
tm->tm_min,
tm->tm_sec);
fclose(client);
}
}
我们开始于建立一个套接字。然后我们填好 sockaddr_in 类型的结构体 sa。注意, INADDR_ANY的特定使用方法:
if (INADDR_ANY)
sa.sin_addr.s_addr = htonl(INADDR_ANY);
这个常量的值是0。由于我们已经使用 bzero于整个结构体, 再把成员设为0将是冗余。 但是如果我们把代码移植到其它一些 INADDR_ANY可能不是0的系统上, 我们就需要把实际值指定给 sa.sin_addr.s_addr。多数现在C语言 编译器已足够智能,会注意到 INADDR_ANY是一个常量。由于它是0,他们将会优化那段代码外的整个条件语句。
在我们成功调用bind后, 我们已经准备好成为一个 守护进程:我们使用 fork建立一个子进程。 同在父进程和子进程里,变量s都是套接字。 父进程不再需要它,于是调用了close, 然后返回0通知父进程的父进程成功终止。
此时,子进程继续在后台工作。 它调用listen并设置 backlog 为 4。这里并不需要设置一个很大的值, 因为 daytime 不是个总有许多客户请求的协议,并且总可以立即处理每个请求。
最后,守护进程开始无休止循环,按照如下步骤:
调用accept。 在这里等待直到一个客户端与之联系。在这里,接收一个新套接字,c, 用来与其特定的客户通信。
使用 C 语言函数 fdopen 把套接字从一个 低级 文件描述符 转变成一个 C语言风格的 FILE 指针。 这使得后面可以使用 fprintf。
检查时间,按 ISO 8601格式打印到 “文件” client。 然后使用 fclose 关闭文件。这会把套接字一同自动关闭。
我们可把这些步骤 概括 起来,作为模型用于许多其它服务器:
这个流程图很好的描述了顺序服务器, 那是在某一时刻只能服务一个客户的服务器,就像我们的daytime服务器能做的那样。这只能存在于客户端与服务器没有真正的“对话”的时候:服务器一检测到一个与客户的连接,就送出一些数据并关闭连接。整个操作只花费若干纳秒就完成了。
这张流程图的好处是,除了在父进程 fork之后和父进程退出前的短暂时间内, 一直只有一个进程活跃:我们的服务器不占用许多内存和其它系统资源。
注意我们已经将初始化守护进程 加入到我们的流程图中。我们不需要初始化我们自己的守护进程 (译者注:这里仅指上面的示例程序。一般写程序时都是需要的。), 但这是在程序流程中设置signal 处理程序、 打开我们可能需要的文件等操作的好地方。
几乎流程图中的所有部分都可以用于描述许多不同的服务器。 条目 serve 是个例外,我们考虑为一个 “黑盒子”,那是你要为你自己的服务器专门设计的东西, 并且 “接到其余部分上”。
并非所有协议都那么简单。许多协议收到一个来自客户的请求,回复请求,然后接收下一个来自同一客户的请求。 因此,那些协议不知道将要服务客户多长时间。这些服务器通常为每个客户启动一个新进程 当新进程服务它的客户时,守护进程可以继续监听更多的连接。
现在,继续,保存上面的源代码为 daytimed.c (用字母d 结束守护程序名是个风俗)。在你编译好后,尝试运行:
% ./daytimed
bind: Permission denied
%
这里发生了什么?正如你将回想起的, daytime协议使用端口13。 但是所有1024以下的端口保留给超级用户 (否则,任何人都可以启动一个守护进程伪装一个常用端口的服务, 这就导致了一个安全漏洞)。
再试一次,这次以超级用户的身份:
# ./daytimed
#
怎么……什么都没有?让我们再试一次:
# ./daytimed
bind: Address already in use
#
在一个时刻,每个端口只能被一个程序绑定。我们的第一个尝试真的成功了:启动了守护子进程并安静的返回。守护子进程仍然在运行,并且继续运行到你关闭它,或是它使用的系统调用失败,或是你重启计算机时。
好,我们知道它正在后台运行着。 但是它正在正常工作吗?我们如何知道它是个正常的 daytime 服务器?只需简单的:
% telnet localhost 13
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
2001-06-19T21:04:42Z
Connection closed by foreign host.
%
telnet 尝试新协议 IPv6,失败了。又重新尝试 IPv4,而后成功了。守护进程工作正常。
如果你可以通过telnet 访问另一个 UNIX 系统,你可以用测试远程访问服务器。 我们计算机没有静态 IP 地址, 所以我这样做:
% who
whizkid ttyp0 Jun 19 16:59 (216.127.220.143)
xxx ttyp1 Jun 19 16:06 (xx.xx.xx.xx)
% telnet 216.127.220.143 13
Trying 216.127.220.143...
Connected to r47.bfm.org.
Escape character is '^]'.
2001-06-19T21:31:11Z
Connection closed by foreign host.
%
又工作正常了。使用域名还会工作正常吗?
% telnet r47.bfm.org 13
Trying 216.127.220.143...
Connected to r47.bfm.org.
Escape character is '^]'.
2001-06-19T21:31:40Z
Connection closed by foreign host.
%
顺序说一句,telnet 在我们的守护进程关闭套接字之后打印消息 Connection closed by foreign host (连接被外部主机关闭)。这告诉我们,实际上,在我们的代码中使用 fclose(client); 的工作情况就像前面说的一样。
BSD套接字构建在基本的UNIX®模型上: 一切都是文件。那么,在我们的例子中,套接字将使我接收一个HTTP文件,就这么说。然后我们要负责将 PNG文件从中提取出来。
由于联网的复杂性,我们不能只使用 open系统调用,或open() C 函数。而是我们需要分几步 “打开”一个套接字。
一旦我们做了这些,我们就能以处理任何文件描述符 的方式处理套接字。我们从它读取 (read),向它写入(write),建立管道(pipe), 必定还要关闭(close)它。
重要的套接字函数
FreeBSD提供了与套接字相关的不同函数, “打开”一个套接字我们只需要四个函数。 有时我们只需要两个。
1 客户端-服务器差异
典型情况中,以套接字为基础的数据通信一端是一个 服务器,另一端是一个客户端。
1.1 通用元素
1.1.1 socket
这一个函数在客户端和服务器都要使用:socket(2)。它是这样被声明的:
int socket(int domain, int type, int protocol);
返回值的类型与open的相同,一个整数。 FreeBSD从和文件句柄相同的池中分配它的值。这就是允许套接字被以对文件相同的方式处理的原因。
参数domain告诉系统你需要使用什么协议族。有许多种协议族存在,有些是某些厂商专有的,其它的都非常通用。协议族的声明在 sys/socket.h中
使用PF_INET是对于 UDP, TCP 和其它网间协议(IPv4)的情况。
对于参数type有五个定义好的值,也在 sys/socket.h中。这些值都以 “SOCK_”开头。 其中最通用的是SOCK_STREAM, 它告诉系统你正需要一个可靠的流传送服务 (和PF_INET一起使用时是指 TCP)。
如果指定SOCK_DGRAM, 你是在请求无连接报文传送服务 (在我们的情形中是UDP)。
如何你需要处理基层协议 (例如IP),或者甚至是网络接口 (例如,以太网),你就需要指定 SOCK_RAW。
最后,参数protocol取决于前两个参数,并非总是有意义。在以上情形中,使用取值0。
未连接的套接字: 对于函数socket 我们还没有指定我们要连往什么其它(主机)系统。 我们新建的套接字还是未连接的。这是有意的:拿电话类比,我们刚把调制解调器接在电话线上。我们既没有告诉调制解调器发起一个呼叫,也不会应答电话振铃。
1.1.2 sockaddr
各种各样的套接字函数需要指定地址,那是一小块内存空间 (用C语言术语是指向一小块内存空间的指针)。在 sys/socket.h中有各种各样如 struct sockaddr的声明。 这个结构是这样被声明的:
/*
* 内核用来存储大多数种类地址的结构
*/
struct sockaddr {
unsigned char sa_len; /* 总长度 */
sa_family_t sa_family; /* 地址族 */
char sa_data[14]; /* 地址值,实际可能更长 */
};
#define SOCK_MAXADDRLEN 255 /* 可能的最长的地址长度 */
注意对于sa_data域的定义具有不确定性。 那只是被定义为14字节的数组, 注释暗示内容可能超过14字节
这种不确定性是经过深思熟虑的。套接字是个非常强大的接口。多数人可能认为比Internet接口强不到哪里 ──大多数应用现在很可能都用它 ──套接字可被用于几乎任何种类的进程间通信, Internet(更精确的说是IP)只是其中的一种。
sys/socket.h提到的各种类型的协议 将被按照地址族对待,并把它们就列在 sockaddr定义的前面:
/*
* 地址族
*/
#define AF_UNSPEC 0 /* 未指定 */
#define AF_LOCAL 1 /* 本机 (管道,portal) */
#define AF_UNIX AF_LOCAL /* 为了向前兼容 */
#define AF_INET 2 /* 网间协议: UDP, TCP, 等等 */
#define AF_IMPLINK 3 /* arpanet imp 地址 */
#define AF_PUP 4 /* pup 协议: 例如BSP */
#define AF_CHAOS 5 /* MIT CHAOS 协议 */
#define AF_NS 6 /* 施乐(XEROX) NS 协议 */
#define AF_ISO 7 /* ISO 协议 */
#define AF_OSI AF_ISO
#define AF_ECMA 8 /* 欧洲计算机制造商协会 */
#define AF_DATAKIT 9 /* datakit 协议 */
#define AF_CCITT 10 /* CCITT 协议, X.25 等 */
#define AF_SNA 11 /* IBM SNA */
#define AF_DECnet 12 /* DECnet */
#define AF_DLI 13 /* DEC 直接数据链路接口 */
#define AF_LAT 14 /* LAT */
#define AF_HYLINK 15 /* NSC Hyperchannel */
#define AF_APPLETALK 16 /* Apple Talk */
#define AF_ROUTE 17 /* 内部路由协议 */
#define AF_LINK 18 /* 协路层接口 */
#define pseudo_AF_XTP 19 /* eXpress Transfer Protocol (no AF) */
#define AF_COIP 20 /* 面向连接的IP, 又名 ST II */
#define AF_CNT 21 /* Computer Network Technology */
#define pseudo_AF_RTIP 22 /* 用于识别RTIP包 */
#define AF_IPX 23 /* Novell 网间协议 */
#define AF_SIP 24 /* Simple 网间协议 */
#define pseudo_AF_PIP 25 /* 用于识别PIP包 */
#define AF_ISDN 26 /* 综合业务数字网(Integrated Services Digital Network) */
#define AF_E164 AF_ISDN /* CCITT E.164 推荐 */
#define pseudo_AF_KEY 27 /* 内部密钥管理功能 */
#define AF_INET6 28 /* IPv6 */
#define AF_NATM 29 /* 本征ATM访问 */
#define AF_ATM 30 /* ATM */
#define pseudo_AF_HDRCMPLT 31 /* 由BPF使用,就不必在接口输出例程
* 中重写头文件了
*/
#define AF_NETGRAPH 32 /* Netgraph 套接字 */
#define AF_SLOW 33 /* 802.3ad 慢速协议 */
#define AF_SCLUSTER 34 /* Sitara 集群协议 */
#define AF_ARP 35
#define AF_BLUETOOTH 36 /* 蓝牙套接字 */
#define AF_MAX 37
用于指定IP的是 AF_INET。这个符号对应着常量 2。
在sockaddr中的域 sa_family指定地址族, 从而决定预先只确定下大致字节数的 sa_data的实际大小。
特别是当地址族 是AF_INET时,我们可以使用 struct sockaddr_in,这可在 netinet/in.h中找到,任何需要 sockaddr的地方都以此作为实际替代。
/*
* 套接字地址,Internet风格
*/
struct sockaddr_in {
uint8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
我们可这样描绘它的结构:
三个重要的域是: sin_family,结构体的字节1; sin_port,16位值,在字节2和3; sin_addr,一个32位整数,表示 IP地址,存储在字节4-7。
现在,让我们尝试填满它。让我们假设我们正在写一个 daytime协议的客户端,这个协议只是简单的规定服务器写出一个代表当前日期和时间文本字符串到端口13。 我们需要使用 TCP/IP,所以我们需要指定在地址族域指定 AF_INET。 AF_INET被定义为 2。让我们使用 IP地址192.43.244.18,这指向 美国联邦政府(time.nist.gov)的服务器。
顺便说一下,域sin_addr被声明为类型 struct in_addr,这个类型定义在 netinet/in.h之中:
/*
* Internet 地址 (由历史原因而形成的结构)
*/
struct in_addr {
in_addr_t s_addr;
};
而in_addr_t是一个32位整数。
192.43.244.18 只是为了表示32位整数的方便写法,按每个八位字节列出, 以最高位的字节开始。
到目前为止,我已经看见了sockaddr。我们的计算机并不将短整数存储为一个16位实体,而是一个2字节序列。同样的,计算机将32位整数存储为4字节序列。
想象我们这样写程序:
sa.sin_family = AF_INET;
sa.sin_port = 13;
sa.sin_addr.s_addr = (((((192 << 8) | 43) << 8) | 244) << 8) | 18;
结果会是什么样的呢?
好,那当然是要依赖于其它因素的。在Pentium®或其它x86 为基础的计算机上,它会像这样:
在另一个不同的系统上,它可能会是:
在一台PDP计算机上,它可能又是另一个样子。 不过上面两种情况是今天最常用的了。
译者注: PDP的字节顺序在英语中称为middle-endian或mixed-endian。例如,原数0x44332211会被PDP存储为0x33441122。 VAX也采用这种字节顺序。
通常,要书写可移植的代码,程序员假设不存在那些差异。他们回避这种差异(除了他们使用汇编语言写代码的时候)。唉,可你不能在为套接字写代码时那样轻易的回避这种差异。
为什么?
因为当与另一台计算机通信时, 你通常不知道对方存储数据时是先存放最高位字节 (MSB)还是最低位字节 (LSB)。
你可能会有问题,“那么,套接字可以为我把握这种差异吗?”
它不能。
这个回答可能先是让你感到惊讶, 请记住通用的套接字接口只明白结构体sockaddr 中的域sa_len和sa_family。 你不必担心那里的字节顺序(当然, 在FreeBSD上sa_family只有一个字节, 但是许多其它的 UNIX® 系统没有 sa_len 并使用2字节给 sa_family,而且数据使用何种顺序都取决于计算机(译者注:此处英文原文的用词为“is native to”))。
其余的数据,也就只剩下sa_data[14]。 依照地址族,套接字只是将那些数据转发到目的地。
事实上,我们输入一个端口号, 是为了让其它计算机知道我们需要什么服务。并且,当我们提供服务时, 只有读取了端口号我们才知道其它计算机期望从我们这里获得什么服务。另一方面,套接字只将端口号作为数据转发,完全不去理会(译者注:此处英文原文用词为“interpret”)其中的内容。
同样的,我们输入IP地址,告诉途经的每台计算机要将我们的数据发送到哪里。 套接字依然只将其按数据转发。
那就是为什么我们(指程序员,而不是套接字)不得不把使用在我们的计算机上的字节顺序和发送给其它计算机时使用的传统字节顺序区分开来。
我们将把我们的计算机上使用的字节顺序称为 主机字节顺序, 或者就是主机顺序.
有一个在IP发送多字节数据的传统: 最高位字节(MSB)优先。 这,我们将用网络字节顺序提及, 或者简单的称为网络顺序。
现在,如果我们在Intel计算机上编译上面的代码, 我们的主机字节顺序将产生:
但是网络字节顺序 要求我们先存储数据的最高位字节(MSB):
不幸的是,我们的主机顺序 恰恰与网络顺序相反。
我们有几种方法解决这个问题。一种是在我们的代码中 倒置数值:
sa.sin_family = AF_INET;
sa.sin_port = 13 << 8;
sa.sin_addr.s_addr = (((((18 << 8) | 244) << 8) | 43) << 8) | 192;
这将欺骗我们的编译器把数据按网络字节顺序存储。在一些情形中,这的确是个有效的办法 (例如,用汇编语言编程)。然而,在多数情形中,这会导致一个问题。
想象一下,你用C语言写了一个套接字程序。 你知道它将运行在一台Pentium计算机上, 于是你倒着输入你的所有常量,并且把它们强置为 网络字节顺序。 它工作正常。
然而,有一台,你所信任的旧 Pentium 变成一台生了锈的旧 Pentium。你把它更换为一个 主机顺序与 网络顺序相同的系统。 你需要重新编译你的所有软件。你的所有软件中除了你写的那个程序,都继续工作正常。
你早已经忘记你将全部常量强置为与 主机顺序相反。你花费宝贵时间拽头发,呼唤你曾经听到过的(有些是你编造的)所有上帝的名字, 用击球棍敲打你的显示器,还上演所有其它的传统仪式 试图找到一个原本好端端的程序突然完成不能工作的原因。
最终,你找到了原因,发了一通誓言, 开始重写你的代码。
幸运的是,你不是第一个面对这个问题的人。 其它人已经创建 htons(3) 和 htonl(3) C 语言函数分别将 short and long 从主机字节顺序转换为 网络字节顺序, 并且还有 ntohs(3) 和 ntohl(3) C 语言函数进行着另外的转换。
在最高位字节(MSB)-最前 的系统上,这些函数什么都不做。在 最低位字节(LSB)-最前的系统上它们将值转换为正确的顺序。
这样一来,无论你的软件在什么系统上编译, 如果你使用这些函数,你的数据最终都将是正确的顺序。
1.2 客户端函数
典型情况中,客户端初始化到服务器的连接。 客户端知道要呼叫哪台服务器:它知道服务器的IP地址,并且知道服务器驻守的 端口。这就好比你拿起电话拨号码 (地址),然后,有人应答, 呼叫负责狂欢的人 (端口)。
1.2.1 connect
一旦一个客户端已经建立了一个套接字,就需要把它连接到一个远方系统的一个端口上。这使用 connect(2):
int connect(int s, const struct sockaddr *name, socklen_t namelen);
参数 s 是套接字, 那是由函数socket返回的值。 name 是一个指向 sockaddr的指针,这个结构体我们已经展开讨论过了。 最后,namelen通知系统 在我们的sockaddr结构体中有多少字节。
如果 connect 成功, 返回 0。否则返回 -1 并将错误码存放于 errno之中。
有许多种connect可能失败的原因。例如,试图发起一个Internet连接时, IP 地址可能不存在,或可能停机, 或者就是太忙,或者可能没有在指定端口上有服务器监听。或者直接拒绝任何特定代码的请求。
1.2.2 我们的第一个客户端
现在我们知道足够多去写一个非常简单的客户端, 一个从192.43.244.18获取当前时间并打印到 stdout的程序。
/*
* daytime.c
*
* G. Adam Stanislav 编程
*/
#include
#include
#include
#include
int main() {
register int s;
register int bytes;
struct sockaddr_in sa;
char buffer[BUFSIZ+1];
if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
return 1;
}
bzero(&sa, sizeof sa);
sa.sin_family = AF_INET;
sa.sin_port = htons(13);
sa.sin_addr.s_addr = htonl((((((192 << 8) | 43) << 8) | 244) << 8) | 18);
if (connect(s, (struct sockaddr *)&sa, sizeof sa) < 0) {
perror("connect");
close(s);
return 2;
}
while ((bytes = read(s, buffer, BUFSIZ)) > 0)
write(1, buffer, bytes);
close(s);
return 0;
}
继续,把它输入到你的编辑器中,保存为 daytime.c,然后编译并运行:
% cc -O3 -o daytime daytime.c
% ./daytime
52079 01-06-19 02:29:25 50 0 1 543.9 UTC(NIST) *
%
在这一情形中,日期是2001年6月19日,时间是 02:29:25 UTC。你的结果会很自然的变化。
1.3 服务器函数
典型的服务器不初始化连接。 相反,服务器等待客户端呼叫并请求服务。服务器不知道客户端什么时候会呼叫, 也不知道有多少客户端会呼叫。服务器就是这样静坐在那儿,耐心等待,一会儿,又一会儿, 它突然发觉自身被从许多客户端来的请求围困,所有的呼叫都同时来到。
套接字接口提供三个基本的函数处理这种情况。
1.3.1 bind
端口像是电话线分机:在你拨一个号码后, 你拨分机到一个特定的人或部门。
有65535个 IP 端口,但是一台服务器通常只处理从其中一个端口进入的请求。这就像告诉电话室操作员我们处于工作状态并在一个特定分机应答电话。 我们使用 bind(2) 告诉套接字我们要服务的端口。
int bind(int s, const struct sockaddr *addr, socklen_t addrlen);
除了在 addr 中指定端口, 服务器还可以包含其自身的 IP 地址。不过,也可以就使用符号常量 INADDR_ANY,指示服务于无论哪个 IP上的指定端口上的请求。 这个符号和几个相同的常量,声明在 netinet/in.h之中。
#define INADDR_ANY (u_int32_t)0x00000000
想象我们正在为 daytime协议在 TCP/IP的基础上写一个服务器。 回想起使用端口13。我们的sockaddr_in 结构应当像这样:
1.3.2 listen
继续我们的办公室电话类比, 在你告诉电话中心操作员你会在哪个分机后,现在你走进你的办公室,确认你自己的电话已插上并且振铃已被打开。还有,你确认呼叫等待功能开启,这样即使你正在与其它人通话, 也可听见电话振铃。
服务器执守所有经过函数 listen(2) 操作的套接字。
int listen(int s, int backlog);
在这里,变量backlog 告诉套接字在忙于处理上一个请求时还可以接受多少个进入的请求。换句话说,这决定了挂起连接的队列的最大大小。
7.5.1.3.3 accept
在你听见电话铃响后,你应答呼叫接起电话。 现在你已经建立起一个与你的客户的连接。这个连接保持到你或你的客户挂线。
服务器通过使用函数 accept(2) 接受连接。
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
注意,这次 addrlen 是一个指针。这是必要的,因为在此情形中套接字要 填上 addr,这是一个 sockaddr_in 结构体。
返回值是一个整数。其实, accept 返回一个 新套接字。你将使用这个新套接字与客户通信。
老套接字会发生什么呢?它继续监听更多的请求 (想起我们传给listen的变量 backlog了吗?),直到我们 close(关闭) 它。
现在,新套接字仅对通信有意义,是完全接通的。 我们不能再把它传给 listen接受更多的连接。
1.3.4 我们的第一个服务器
我们的第一个服务器会比我们的第一个客户端复杂一些:我们不仅用到了更多的套接字函数, 还需要把程序写成一个守护程序。
这最好写成:在绑定端口后建立一个子进程。 主进程随后退出,将控制权交回给 shell (或者任何调用主进程的程序)。
子进程调用 listen,然后启动一个无休止循环。这个循环接受连接,提供服务, 最后关闭连接的套接字。
/*
* daytimed - 端口 13 的服务器
*
* G. Adam Stanislav 编程
* 2001年6月19日
*/
#include
#include
#include
#include
#include
#include
#define BACKLOG 4
int main() {
register int s, c;
int b;
struct sockaddr_in sa;
time_t t;
struct tm *tm;
FILE *client;
if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
return 1;
}
bzero(&sa, sizeof sa);
sa.sin_family = AF_INET;
sa.sin_port = htons(13);
if (INADDR_ANY)
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(s, (struct sockaddr *)&sa, sizeof sa) < 0) {
perror("bind");
return 2;
}
switch (fork()) {
case -1:
perror("fork");
return 3;
break;
default:
close(s);
return 0;
break;
case 0:
break;
}
listen(s, BACKLOG);
for (;;) {
b = sizeof sa;
if ((c = accept(s, (struct sockaddr *)&sa, &b)) < 0) {
perror("daytimed accept");
return 4;
}
if ((client = fdopen(c, "w")) == NULL) {
perror("daytimed fdopen");
return 5;
}
if ((t = time(NULL)) < 0) {
perror("daytimed time");
return 6;
}
tm = gmtime(&t);
fprintf(client, "%.4i-%.2i-%.2iT%.2i:%.2i:%.2iZ\n",
tm->tm_year + 1900,
tm->tm_mon + 1,
tm->tm_mday,
tm->tm_hour,
tm->tm_min,
tm->tm_sec);
fclose(client);
}
}
我们开始于建立一个套接字。然后我们填好 sockaddr_in 类型的结构体 sa。注意, INADDR_ANY的特定使用方法:
if (INADDR_ANY)
sa.sin_addr.s_addr = htonl(INADDR_ANY);
这个常量的值是0。由于我们已经使用 bzero于整个结构体, 再把成员设为0将是冗余。 但是如果我们把代码移植到其它一些 INADDR_ANY可能不是0的系统上, 我们就需要把实际值指定给 sa.sin_addr.s_addr。多数现在C语言 编译器已足够智能,会注意到 INADDR_ANY是一个常量。由于它是0,他们将会优化那段代码外的整个条件语句。
在我们成功调用bind后, 我们已经准备好成为一个 守护进程:我们使用 fork建立一个子进程。 同在父进程和子进程里,变量s都是套接字。 父进程不再需要它,于是调用了close, 然后返回0通知父进程的父进程成功终止。
此时,子进程继续在后台工作。 它调用listen并设置 backlog 为 4。这里并不需要设置一个很大的值, 因为 daytime 不是个总有许多客户请求的协议,并且总可以立即处理每个请求。
最后,守护进程开始无休止循环,按照如下步骤:
调用accept。 在这里等待直到一个客户端与之联系。在这里,接收一个新套接字,c, 用来与其特定的客户通信。
使用 C 语言函数 fdopen 把套接字从一个 低级 文件描述符 转变成一个 C语言风格的 FILE 指针。 这使得后面可以使用 fprintf。
检查时间,按 ISO 8601格式打印到 “文件” client。 然后使用 fclose 关闭文件。这会把套接字一同自动关闭。
我们可把这些步骤 概括 起来,作为模型用于许多其它服务器:
这个流程图很好的描述了顺序服务器, 那是在某一时刻只能服务一个客户的服务器,就像我们的daytime服务器能做的那样。这只能存在于客户端与服务器没有真正的“对话”的时候:服务器一检测到一个与客户的连接,就送出一些数据并关闭连接。整个操作只花费若干纳秒就完成了。
这张流程图的好处是,除了在父进程 fork之后和父进程退出前的短暂时间内, 一直只有一个进程活跃:我们的服务器不占用许多内存和其它系统资源。
注意我们已经将初始化守护进程 加入到我们的流程图中。我们不需要初始化我们自己的守护进程 (译者注:这里仅指上面的示例程序。一般写程序时都是需要的。), 但这是在程序流程中设置signal 处理程序、 打开我们可能需要的文件等操作的好地方。
几乎流程图中的所有部分都可以用于描述许多不同的服务器。 条目 serve 是个例外,我们考虑为一个 “黑盒子”,那是你要为你自己的服务器专门设计的东西, 并且 “接到其余部分上”。
并非所有协议都那么简单。许多协议收到一个来自客户的请求,回复请求,然后接收下一个来自同一客户的请求。 因此,那些协议不知道将要服务客户多长时间。这些服务器通常为每个客户启动一个新进程 当新进程服务它的客户时,守护进程可以继续监听更多的连接。
现在,继续,保存上面的源代码为 daytimed.c (用字母d 结束守护程序名是个风俗)。在你编译好后,尝试运行:
% ./daytimed
bind: Permission denied
%
这里发生了什么?正如你将回想起的, daytime协议使用端口13。 但是所有1024以下的端口保留给超级用户 (否则,任何人都可以启动一个守护进程伪装一个常用端口的服务, 这就导致了一个安全漏洞)。
再试一次,这次以超级用户的身份:
# ./daytimed
#
怎么……什么都没有?让我们再试一次:
# ./daytimed
bind: Address already in use
#
在一个时刻,每个端口只能被一个程序绑定。我们的第一个尝试真的成功了:启动了守护子进程并安静的返回。守护子进程仍然在运行,并且继续运行到你关闭它,或是它使用的系统调用失败,或是你重启计算机时。
好,我们知道它正在后台运行着。 但是它正在正常工作吗?我们如何知道它是个正常的 daytime 服务器?只需简单的:
% telnet localhost 13
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
2001-06-19T21:04:42Z
Connection closed by foreign host.
%
telnet 尝试新协议 IPv6,失败了。又重新尝试 IPv4,而后成功了。守护进程工作正常。
如果你可以通过telnet 访问另一个 UNIX 系统,你可以用测试远程访问服务器。 我们计算机没有静态 IP 地址, 所以我这样做:
% who
whizkid ttyp0 Jun 19 16:59 (216.127.220.143)
xxx ttyp1 Jun 19 16:06 (xx.xx.xx.xx)
% telnet 216.127.220.143 13
Trying 216.127.220.143...
Connected to r47.bfm.org.
Escape character is '^]'.
2001-06-19T21:31:11Z
Connection closed by foreign host.
%
又工作正常了。使用域名还会工作正常吗?
% telnet r47.bfm.org 13
Trying 216.127.220.143...
Connected to r47.bfm.org.
Escape character is '^]'.
2001-06-19T21:31:40Z
Connection closed by foreign host.
%
顺序说一句,telnet 在我们的守护进程关闭套接字之后打印消息 Connection closed by foreign host (连接被外部主机关闭)。这告诉我们,实际上,在我们的代码中使用 fclose(client); 的工作情况就像前面说的一样。
socket编程原理
2.1 问题的引入
Unix系统的I/O命令集,是从Maltics和早期系统中的命令演变出来的,其模式为打开一读/写一关闭(open-write-read-close)。在一个用户进程进行I/O操作时,它首先调用“打开”获得对指定文件或设备的使用权,并返回称为文件描述符的整型数,以描述用户在打开的文件或设备上进行I/O操作的进程。然后这个用户进程多次调用“读/写”以传输数据。当所有的传输操作完成后,用户进程关闭调用,通知操作系统已经完成了对某对象的使用。
TCP/IP协议被集成到UNIX内核中时,相当于在UNIX系统引入了一种新型的I/O操作。UNIX用户进程与网络协议的交互作用比用户进程与传统的I/O设备相互作用复杂得多。首先,进行网络操作的两个进程在不同机器上,如何建立它们之间的联系?其次,网络协议存在多种,如何建立一种通用机制以支持多种协议?这些都是网络应用编程界面所要解决的问题。
在UNIX系统中,网络应用编程界面有两类:UNIX BSD的套接字(socket)和UNIX System V的TLI。由于Sun公司采用了支持TCP/IP的UNIX BSD操作系统,使TCP/IP的应用有更大的发展,其网络应用编程界面──套接字(socket)在网络软件中被广泛应用,至今已引进微机操作系统Dos和Windows系统中,成为开发网络应用软件的强有力工具,本章将要详细讨论这个问题。
2.2 套接字编程基本概念
在开始使用套接字编程之前,首先必须建立以下概念。
2.2.1 网间进程通信
进程通信的概念最初来源于单机系统。由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进程之间既互不干扰又协调一致工作,操作系统为进程通信提供了相应设施,如UNIX BSD中的管道(pipe)、命名管道(named pipe)和软中断信号(signal),UNIX system V的消息(message)、共享存储区(shared memory)和信号量(semaphore)等,但都仅限于用在本机进程之间通信。网间进程通信要解决的是不同主机进程间的相互通信问题(可把同机进程通信看作是其中的特例)。为此,首先要解决的是网间进程标识问题。同一主机上,不同进程可用进程号(process ID)唯一标识。但在网络环境下,各主机独立分配的进程号不能唯一标识该进程。例如,主机A赋于某进程号5,在B机中也可以存在5号进程,因此,“5号进程”这句话就没有意义了。
其次,操作系统支持的网络协议众多,不同协议的工作方式不同,地址格式也不同。因此,网间进程通信还要解决多重协议的识别问题。
为了解决上述问题,TCP/IP协议引入了下列几个概念。
端口
网络中可以被命名和寻址的通信端口,是操作系统可分配的一种资源。
按照OSI七层协议的描述,传输层与网络层在功能上的最大区别是传输层提供进程通信能力。从这个意义上讲,网络通信的最终地址就不仅仅是主机地址了,还包括可以描述进程的某种标识符。为此,TCP/IP协议提出了协议端口(protocol port,简称端口)的概念,用于标识通信的进程。
端口是一种抽象的软件结构(包括一些数据结构和I/O缓冲区)。应用程序(即进程)通过系统调用与某端口建立连接(binding)后,传输层传给该端口的数据都被相应进程所接收,相应进程发给传输层的数据都通过该端口输出。在TCP/IP协议的实现中,端口操作类似于一般的I/O操作,进程获取一个端口,相当于获取本地唯一的I/O文件,可以用一般的读写原语访问之。
类似于文件描述符,每个端口都拥有一个叫端口号(port number)的整数型标识符,用于区别不同端口。由于TCP/IP传输层的两个协议TCP和UDP是完全独立的两个软件模块,因此各自的端口号也相互独立,如TCP有一个255号端口,UDP也可以有一个255号端口,二者并不冲突。
端口号的分配是一个重要问题。有两种基本分配方式:第一种叫全局分配,这是一种集中控制方式,由一个公认的中央机构根据用户需要进行统一分配,并将结果公布于众。第二种是本地分配,又称动态连接,即进程需要访问传输层服务时,向本地操作系统提出申请,操作系统返回一个本地唯一的端口号,进程再通过合适的系统调用将自己与该端口号联系起来(绑扎)。TCP/IP端口号的分配中综合了上述两种方式。TCP/IP将端口号分为两部分,少量的作为保留端口,以全局方式分配给服务进程。因此,每一个标准服务器都拥有一个全局公认的端口(即周知口,well-known port),即使在不同机器上,其端口号也相同。剩余的为自由端口,以本地方式进行分配。TCP和UDP均规定,小于256的端口号才能作保留端口。
地址
网络通信中通信的两个进程分别在不同的机器上。在互连网络中,两台机器可能位于不同的网络,这些网络通过网络互连设备(网关,网桥,路由器等)连接。因此需要三级寻址:
1. 某一主机可与多个网络相连,必须指定一特定网络地址;
2. 网络上每一台主机应有其唯一的地址;
3. 每一主机上的每一进程应有在该主机上的唯一标识符。
通常主机地址由网络ID和主机ID组成,在TCP/IP协议中用32位整数值表示;TCP和UDP均使用16位端口号标识用户进程。
网络字节顺序
不同的计算机存放多字节值的顺序不同,有的机器在起始地址存放低位字节(低价先存),有的存高位字节(高价先存)。为保证数据的正确性,在网络协议中须指定网络字节顺序。TCP/IP协议使用16位整数和32位整数的高价先存格式,它们均含在协议头文件中。
连接
两个进程间的通信链路称为连接。连接在内部表现为一些缓冲区和一组协议机制,在外部表现出比无连接高的可靠性。
半相关
综上所述,网络中用一个三元组可以在全局唯一标志一个进程:
(协议,本地地址,本地端口号)
这样一个三元组,叫做一个半相关(half-association),它指定连接的每半部分。
全相关
一个完整的网间进程通信需要由两个进程组成,并且只能使用同一种高层协议。也就是说,不可能通信的一端用TCP协议,而另一端用UDP协议。因此一个完整的网间通信需要一个五元组来标识:
(协议,本地地址,本地端口号,远地地址,远地端口号)
这样一个五元组,叫做一个相关(association),即两个协议相同的半相关才能组合成一个合适的相关,或完全指定组成一连接。
2.2.2 服务方式 www.it55.com在线教程
在网络分层结构中,各层之间是严格单向依赖的,各层次的分工和协作集中体现在相邻层之间的界面上。“服务”是描述相邻层之间关系的抽象概念,即网络中各层向紧邻上层提供的一组操作。下层是服务提供者,上层是请求服务的用户。服务的表现形式是原语(primitive),如系统调用或库函数。系统调用是操作系统内核向网络应用程序或高层协议提供的服务原语。网络中的n层总要向n+1层提供比n-1层更完备的服务,否则n层就没有存在的价值。
在OSI的术语中,网络层及其以下各层又称为通信子网,只提供点到点通信,没有程序或进程的概念。而传输层实现的是“端到端”通信,引进网间进程通信概念,同时也要解决差错控制,流量控制,数据排序(报文排序),连接管理等问题,为此提供不同的服务方式:
面向连接(虚电路)或无连接
面向连接服务是电话系统服务模式的抽象,即每一次完整的数据传输都要经过建立连接,使用连接,终止连接的过程。在数据传输过程中,各数据分组不携带目的地址,而使用连接号(connect ID)。本质上,连接是一个管道,收发数据不但顺序一致,而且内容相同。TCP协议提供面向连接的虚电路。
无连接服务是邮政系统服务的抽象,每个分组都携带完整的目的地址,各分组在系统中独立传送。无连接服务不能保证分组的先后顺序,不进行分组出错的恢复与重传,不保证传输的可靠性。UDP协议提供无连接的数据报服务。
下面给出这两种服务的类型及应用中的例子:
服务类型 www.it55.com在线教程
服 务
例 子
面向连接
可靠的报文流
可靠的字节流
不可靠的连接
文件传输(FTP)
远程登录(Telnet)
数字话音
无连接
不可靠的数据报
有确认的数据报
请求-应答
电子邮件(E-mail)
电子邮件中的挂号信
网络数据库查询
顺序
在网络传输中,两个连续报文在端-端通信中可能经过不同路径,这样到达目的地时的顺序可能会与发送时不同。“顺序”是指接收数据顺序与发送数据顺序相同。TCP协议提供这项服务。
差错控制
保证应用程序接收的数据无差错的一种机制。检查差错的方法一般是采用检验“检查和(Checksum)”的方法。而保证传送无差错的方法是双方采用确认应答技术。TCP协议提供这项服务。
流控制
在数据传输过程中控制数据传输速率的一种机制,以保证数据不被丢失。TCP协议提供这项服务。
字节流
字节流方式指的是仅把传输中的报文看作是一个字节序列,不提供数据流的任何边界。TCP协议提供字节流服务。
报文
接收方要保存发送方的报文边界。UDP协议提供报文服务。
全双工/半双工
端-端间数据同时以两个方向/一个方向传送。
缓存/带外数据
在字节流服务中,由于没有报文边界,用户进程在某一时刻可以读或写任意数量的字节。为保证传输正确或采用有流控制的协议时,都要进行缓存。但对某些特殊的需求,如交互式应用程序,又会要求取消这种缓存。
在数据传送过程中,希望不通过常规传输方式传送给用户以便及时处理的某一类信息,如UNIX系统的中断键(Delete或Control-c)、终端流控制符(Control-s和Control-q),称为带外数据。逻辑上看,好象用户进程使用了一个独立的通道传输这些数据。该通道与每对连接的流相联系。由于Berkeley Software Distribution中对带外数据的实现与RFC 1122中规定的Host Agreement不一致,为了将互操作中的问题减到最小,应用程序编写者除非与现有服务互操作时要求带外数据外,最好不使用它。
2.2.3 客户/服务器模式
IT资讯之家 www.it55.com
在TCP/IP网络应用中,通信的两个进程间相互作用的主要模式是客户/服务器模式(ClIEnt/Server model),即客户向服务器发出服务请求,服务器接收到请求后,提供相应的服务。客户/服务器模式的建立基于以下两点:首先,建立网络的起因是网络中软硬件资源、运算能力和信息不均等,需要共享,从而造就拥有众多资源的主机提供服务,资源较少的客户请求服务这一非对等作用。其次,网间进程通信完全是异步的,相互通信的进程间既不存在父子关系,又不共享内存缓冲区,因此需要一种机制为希望通信的进程间建立联系,为二者的数据交换提供同步,这就是基于客户/服务器模式的TCP/IP。
客户/服务器模式在操作过程中采取的是主动请求方式:
首先服务器方要先启动,并根据请求提供相应服务:
1. 打开一通信通道并告知本地主机,它愿意在某一公认地址上(周知口,如FTP为21)接收客户请求;
2. 等待客户请求到达该端口;
3. 接收到重复服务请求,处理该请求并发送应答信号。接收到并发服务请求,要激活一新进程来处理这个客户请求(如UNIX系统中用fork、exec)。新进程处理此客户请求,并不需要对其它请求作出应答。服务完成后,关闭此新进程与客户的通信链路,并终止。
4. 返回第二步,等待另一客户请求。
5. 关闭服务器
客户方:
1. 打开一通信通道,并连接到服务器所在主机的特定端口;
2. 向服务器发服务请求报文,等待并接收应答;继续提出请求......
3. 请求结束后关闭通信通道并终止。
从上面所描述过程可知:
1. 客户与服务器进程的作用是非对称的,因此编码不同。
2. 服务进程一般是先于客户请求而启动的。只要系统运行,该服务进程一直存在,直到正常或强迫终止。
2.2.4 套接字类型 it55.com
TCP/IP的socket提供下列三种类型套接字。
流式套接字(SOCK_STREAM)
提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复地发送,且按发送顺序接收。内设流量控制,避免数据流超限;数据被看作是字节流,无长度限制。文件传送协议(FTP)即使用流式套接字。
数据报式套接字(SOCK_DGRAM)
提供了一个无连接服务。数据包以独立包形式被发送,不提供无错保证,数据可能丢失或重复,并且接收顺序混乱。网络文件系统(NFS)使用数据报式套接字。
原始式套接字(SOCK_RAW)
该接口允许对较低层协议,如IP、ICMP直接访问。常用于检验新的协议实现或访问现有服务中配置的新设备。
2.3 基本套接字系统调用
为了更好地说明套接字编程原理,下面给出几个基本套接字系统调用说明。
2.3.1 创建套接字──socket()
应用程序在使用套接字前,首先必须拥有一个套接字,系统调用socket()向应用程序提供创建套接字的手段,其调用格式如下:
SOCKET PASCAL FAR socket(int af, int type, int protocol);
该调用要接收三个参数:af、type、protocol。参数af指定通信发生的区域,UNIX系统支持的地址族有:AF_UNIX、AF_INET、AF_NS等,而DOS、WINDOWS中仅支持AF_INET,它是网际网区域。因此,地址族与协议族相同。参数type 描述要建立的套接字的类型。参数protocol说明该套接字使用的特定协议,如果调用者不希望特别指定使用的协议,则置为0,使用默认的连接模式。根据这三个参数建立一个套接字,并将相应的资源分配给它,同时返回一个整型套接字号。因此,socket()系统调用实际上指定了相关五元组中的“协议”这一元。
有关socket()的详细描述参看5.2.23。
2.3.2 指定本地地址──bind()
http://www.it55.com/
当一个套接字用socket()创建后,存在一个名字空间(地址族),但它没有被命名。bind()将套接字地址(包括本地主机地址和本地端口地址)与所创建的套接字号联系起来,即将名字赋予套接字,以指定本地半相关。其调用格式如下:
int PASCAL FAR bind(SOCKET s, const struct sockaddr FAR * name, int namelen);
参数s是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。参数name 是赋给套接字s的本地地址(名字),其长度可变,结构随通信域的不同而不同。namelen表明了name的长度。
如果没有错误发生,bind()返回0。否则返回值SOCKET_ERROR。
地址在建立套接字通信过程中起着重要作用,作为一个网络应用程序设计者对套接字地址结构必须有明确认识。例如,UNIX BSD有一组描述套接字地址的数据结构,其中使用TCP/IP协议的地址结构为:
struct sockaddr_in{
short sin_family; /*AF_INET*/
u_short sin_port; /*16位端口号,网络字节顺序*/
struct in_addr sin_addr; /*32位IP地址,网络字节顺序*/
char sin_zero[8]; /*保留*/
}
有关bind()的详细描述参看5.2.2。
2.3.3 建立套接字连接──connect()与accept()
www.it55.com在线教程
这两个系统调用用于完成一个完整相关的建立,其中connect()用于建立连接。无连接的套接字进程也可以调用connect(),但这时在进程之间没有实际的报文交换,调用将从本地操作系统直接返回。这样做的优点是程序员不必为每一数据指定目的地址,而且如果收到的一个数据报,其目的端口未与任何套接字建立“连接”,便能判断该端口不可操作。而accept()用于使服务器等待来自某客户进程的实际连接。
connect()的调用格式如下:
int PASCAL FAR connect(SOCKET s, const struct sockaddr FAR * name, int namelen);
参数s是欲建立连接的本地套接字描述符。参数name指出说明对方套接字地址结构的指针。对方套接字地址长度由namelen说明。
如果没有错误发生,connect()返回0。否则返回值SOCKET_ERROR。在面向连接的协议中,该调用导致本地系统和外部系统之间连接实际建立。
由于地址族总被包含在套接字地址结构的前两个字节中,并通过socket()调用与某个协议族相关。因此bind()和connect()无须协议作为参数。
有关connect()的详细描述参看5.2.4。
accept()的调用格式如下:
SOCKET PASCAL FAR accept(SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen);
参数s为本地套接字描述符,在用做accept()调用的参数前应该先调用过listen()。addr 指向客户方套接字地址结构的指针,用来接收连接实体的地址。addr的确切格式由套接字创建时建立的地址族决定。addrlen 为客户方套接字地址的长度(字节数)。如果没有错误发生,accept()返回一个SOCKET类型的值,表示接收到的套接字的描述符。否则返回值INVALID_SOCKET。
accept()用于面向连接服务器。参数addr和addrlen存放客户方的地址信息。调用前,参数addr 指向一个初始值为空的地址结构,而addrlen 的初始值为0;调用accept()后,服务器等待从编号为s的套接字上接受客户连接请求,而连接请求是由客户方的connect()调用发出的。当有连接请求到达时,accept()调用将请求连接队列上的第一个客户方套接字地址及长度放入addr 和addrlen,并创建一个与s有相同特性的新套接字号。新的套接字可用于处理服务器并发请求。
有关accept()的详细描述参看5.2.1。
四个套接字系统调用,socket()、bind()、connect()、accept(),可以完成一个完全五元相关的建立。socket()指定五元组中的协议元,它的用法与是否为客户或服务器、是否面向连接无关。bind()指定五元组中的本地二元,即本地主机地址和端口号,其用法与是否面向连接有关:在服务器方,无论是否面向连接,均要调用bind();在客户方,若采用面向连接,则可以不调用bind(),而通过connect()自动完成。若采用无连接,客户方必须使用bind()以获得一个唯一的地址。
以上讨论仅对客户/服务器模式而言,实际上套接字的使用是非常灵活的,唯一需遵循的原则是进程通信之前,必须建立完整的相关。
2.3.4 监听连接──listen()
IT资讯之家 www.it55.com
此调用用于面向连接服务器,表明它愿意接收连接。listen()需在accept()之前调用,其调用格式如下:
int PASCAL FAR listen(SOCKET s, int backlog);
参数s标识一个本地已建立、尚未连接的套接字号,服务器愿意从它上面接收请求。backlog表示请求连接队列的最大长度,用于限制排队请求的个数,目前允许的最大值为5。如果没有错误发生,listen()返回0。否则它返回SOCKET_ERROR。
listen()在执行调用过程中可为没有调用过bind()的套接字s完成所必须的连接,并建立长度为backlog的请求连接队列。
调用listen()是服务器接收一个连接请求的四个步骤中的第三步。它在调用socket()分配一个流套接字,且调用bind()给s赋于一个名字之后调用,而且一定要在accept()之前调用。
有关listen()的详细描述参看5.2.13。
2.2.3节中提到在客户/服务器模式中,有两种类型的服务:重复服务和并发服务。accept()调用为实现并发服务提供了极大方便,因为它要返回一个新的套接字号,其典型结构为:
int initsockid, newsockid;
if ((initsockid = socket(....)) < 0)
error(“can't create socket”);
if (bind(initsockid,....) < 0)
error(“bind error”);
if (listen(initsockid , 5) < 0)
error(“listen error”);
for (;;) {
newsockid = accept(initsockid, ...) /* 阻塞 */
if (newsockid < 0)
error(“accept error“);
if (fork() == 0){ /* 子进程 */
closesocket(initsockid);
do(newsockid); /* 处理请求 */
exit(0);
}
closesocket(newsockid); /* 父进程 */
}
这段程序执行的结果是newsockid与客户的套接字建立相关,子进程启动后,关闭继承下来的主服务器的initsockid,并利用新的newsockid与客户通信。主服务器的initsockid可继续等待新的客户连接请求。由于在Unix等抢先多任务系统中,在系统调度下,多个进程可以同时进行。因此,使用并发服务器可以使服务器进程在同一时间可以有多个子进程和不同的客户程序连接、通信。在客户程序看来,服务器可以同时并发地处理多个客户的请求,这就是并发服务器名称的来由。
面向连接服务器也可以是重复服务器,其结构如下:
int initsockid, newsockid;
if ((initsockid = socket(....))<0)
error(“can't create socket”);
if (bind(initsockid,....)<0)
error(“bind error”);
if (listen(initsockid,5)<0)
error(“listen error”);
for (;;) {
newsockid = accept(initsockid, ...) /* 阻塞 */
if (newsockid < 0)
error(“accept error“);
do(newsockid); /* 处理请求 */
closesocket(newsockid);
}
重复服务器在一个时间只能和一个客户程序建立连接,它对多个客户程序的处理是采用循环的方式重复进行,因此叫重复服务器。并发服务器和重复服务器各有利弊:并发服务器可以改善客户程序的响应速度,但它增加了系统调度的开销;重复服务器正好与其相反,因此用户在决定是使用并发服务器还是重复服务器时,要根据应用的实际情况来定。
2.3.5 数据传输──send()与recv() http://www.it55.com/
当一个连接建立以后,就可以传输数据了。常用的系统调用有send()和recv()。
send()调用用于在参数s指定的已连接的数据报或流套接字上发送输出数据,格式如下:
int PASCAL FAR send(SOCKET s, const char FAR *buf, int len, int flags);
参数s为已连接的本地套接字描述符。buf 指向存有发送数据的缓冲区的指针,其长度由len 指定。flags 指定传输控制方式,如是否发送带外数据等。如果没有错误发生,send()返回总共发送的字节数。否则它返回SOCKET_ERROR。
有关send()的详细描述参看5.2.19。
recv()调用用于在参数s指定的已连接的数据报或流套接字上接收输入数据,格式如下:
int PASCAL FAR recv(SOCKET s, char FAR *buf, int len, int flags);
参数s 为已连接的套接字描述符。buf指向接收输入数据缓冲区的指针,其长度由len 指定。flags 指定传输控制方式,如是否接收带外数据等。如果没有错误发生,recv()返回总共接收的字节数。如果连接被关闭,返回0。否则它返回SOCKET_ERROR。
有关recv()的详细描述参看5.2.16。
2.3.6 输入/输出多路复用──select() 免费资源www.it55.com
select()调用用来检测一个或多个套接字的状态。对每一个套接字来说,这个调用可以请求读、写或错误状态方面的信息。请求给定状态的套接字集合由一个fd_set结构指示。在返回时,此结构被更新,以反映那些满足特定条件的套接字的子集,同时, select()调用返回满足条件的套接字的数目,其调用格式如下:
int PASCAL FAR select(int nfds, fd_set FAR * readfds, fd_set FAR * writefds, fd_set FAR * exceptfds, const struct timeval FAR * timeout);
参数nfds指明被检查的套接字描述符的值域,此变量一般被忽略。
参数readfds指向要做读检测的套接字描述符集合的指针,调用者希望从中读取数据。参数writefds 指向要做写检测的套接字描述符集合的指针。exceptfds指向要检测是否出错的套接字描述符集合的指针。timeout指向select()函数等待的最大时间,如果设为NULL则为阻塞操作。select()返回包含在fd_set结构中已准备好的套接字描述符的总数目,或者是发生错误则返回SOCKET_ERROR。
有关select()的详细描述参看5.2.18。
2.3.7 关闭套接字──closesocket()
closesocket()关闭套接字s,并释放分配给该套接字的资源;如果s涉及一个打开的TCP连接,则该连接被释放。closesocket()的调用格式如下:
BOOL PASCAL FAR closesocket(SOCKET s);
参数s待关闭的套接字描述符。如果没有错误发生,closesocket()返回0。否则返回值SOCKET_ERROR。
有关closesocket()的详细描述参看5.2.3。 45398 www.it55.com it55学习IT知识,享受IT生活 4dfkjn
2.4 典型套接字调用过程举例
如前所述,TCP/IP协议的应用一般采用客户/服务器模式,因此在实际应用中,必须有客户和服务器两个进程,并且首先启动服务器,其系统调用时序图如下。
it55.com
面向连接的协议(如TCP)的套接字系统调用如图2.1所示:
服务器必须首先启动,直到它执行完accept()调用,进入等待状态后,方能接收客户请求。假如客户在此前启动,则connect()将返回出错代码,连接不成功。
图2.1 面向连接的套接字系统调用时序图
无连接协议的套接字调用如图2.2所示:
图2.2 无连接协议的套接字调用时序图
无连接服务器也必须先启动,否则客户请求传不到服务进程。无连接客户不调用connect()。因此在数据发送之前,客户与服务器之间尚未建立完全相关,但各自通过socket()和bind()建立了半相关。发送数据时,发送方除指定本地套接字号外,还需指定接收方套接字号,从而在数据收发过程中动态地建立了全相关。
实例
本实例使用面向连接协议的客户/服务器模式,其流程如图2.3所示:
图2.3 面向连接的应用程序流程图
服务器方程序:
/* File Name: streams.c */
#include
#include
#define TRUE 1
/* 这个程序建立一个套接字,然后开始无限循环;每当它通过循环接收到一个连接,则打印出一个信息。当连接断开,或接收到终止信息,则此连接结束,程序再接收一个新的连接。命令行的格式是:streams */
main( )
{
int sock, length;
struct sockaddr_in server;
struct sockaddr tcpaddr;
int msgsock;
char buf[1024];
int rval, len;
/* 建立套接字 */
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror(“opening stream socket”);
exit(1);
}
/* 使用任意端口命名套接字 */
server.sin_family = AF_INET;
server.sin_port = INADDR_ANY;
if (bind(sock, (struct sockaddr *)&server, sizeof(server)) < 0) {
perror(“binding stream socket”);
exit(1);
}
/* 找出指定的端口号并打印出来 */
length = sizeof(server);
if (getsockname(sock, (struct sockaddr *)&server, &length) < 0) {
perror(“getting socket name”);
exit(1);
}
printf(“socket port #%d/n”, ntohs(server.sin_port));
/* 开始接收连接 */
listen(sock, 5);
len = sizeof(struct sockaddr);
do {
msgsock = accept(sock, (struct sockaddr *)&tcpaddr, (int *)&len);
if (msgsock == -1)
perror(“accept”);
else do{
memset(buf, 0, sizeof(buf));
if ((rval = recv(msgsock, buf, 1024)) < 0)
perror(“reading stream message”);
if (rval == 0)
printf(“ending connection /n”);
else
printf(“-->%s/n”, buf);
}while (rval != 0);
closesocket(msgsock);
} while (TRUE);
/* 因为这个程序已经有了一个无限循环,所以套接字“sock”从来不显式关闭。然而,当进程被杀死或正常终止时,所有套接字都将自动地被关闭。*/
exit(0);
}
客户方程序:
/* File Name: streamc.c */
#include
#include
#define DATA “half a league, half a league ...”
/* 这个程序建立套接字,然后与命令行给出的套接字连接;连接结束时,在连接上发送
一个消息,然后关闭套接字。命令行的格式是:streamc 主机名 端口号
端口号要与服务器程序的端口号相同 */
main(argc, argv)
int argc;
char *argv[ ];
{
int sock;
struct sockaddr_in server;
struct hostent *hp, *gethostbyname( );
char buf[1024];
/* 建立套接字 */
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror(“opening stream socket”);
exit(1);
}
/* 使用命令行中指定的名字连接套接字 */
server.sin_family = AF_INET;
hp = gethostbyname(argv[1]);
if (hp == 0) {
fprintf(stderr, “%s: unknown host /n”, argv[1]);
exit(2);
}
memcpy((char*)&server.sin_addr, (char*)hp->h_addr, hp->h_length);
sever.sin_port = htons(atoi(argv[2]));
if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0) {
perror(“connecting stream socket”);
exit(3);
}
if (send(sock, DATA, sizeof(DATA)) < 0)
perror(“sending on stream socket”);
closesocket(sock);
exit(0);
} sflj www.it55.com kg^&fgd
2.5 一个通用的实例程序
在上一节中,我们介绍了一个简单的socket程序实例。从这个例子我们可以看出,使用socket编程几乎有一个模式,即所有的程序几乎毫无例外地按相同的顺序调用相同的函数。因此我们可以设想,设计一个中间层,它向上提供几个简单的函数,程序只要调用这几个函数就可以实现普通情况下的数据传输,程序设计者不必太多地关心socket程序设计的细节。
本节我们将介绍一个通用的网络程序接口,它向上层提供几个简单的函数,程序设计者只要使用这几个函数就可以完成绝大多数情况下的网络数据传输。这些函数将socket编程和上层隔离开来,它使用面向连接的流式套接字,采用非阻塞的工作机制,程序只要调用这些函数查询网络消息并作出相应的响应即可。这些函数包括:
l InitSocketsStruct:初始化socket结构,获取服务端口号。客户程序使用。
l InitPassiveSock:初始化socket结构,获取服务端口号,建立主套接字。服务器程序使用。
l CloseMainSock:关闭主套接字。服务器程序使用。
l CreateConnection:建立连接。客户程序使用。
l AcceptConnection:接收连接。服务器程序使用。
l CloseConnection:关闭连接。
l QuerySocketsMsg:查询套接字消息。
l SendPacket:发送数据。
l RecvPacket:接收数据。
2.5.1 头文件 www.it55.com在线教程
/* File Name: tcpsock.h */
/* 头文件包括socket程序经常用到的系统头文件(本例中给出的是SCO Unix下的头文件,其它版本的Unix的头文件可能略有不同),并定义了我们自己的两个数据结构及其实例变量,以及我们提供的函数说明。*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef struct SocketsMsg{ /* 套接字消息结构 */
int AcceptNum; /* 指示是否有外来连接等待接收 */
int ReadNum; /* 有外来数据等待读取的连接数 */
int ReadQueue[32]; /* 有外来数据等待读取的连接队列 */
int WriteNum; /* 可以发送数据的连接数 */
int WriteQueue[32]; /* 可以发送数据的连接队列 */
int ExceptNum; /* 有例外的连接数 */
int ExceptQueue[32]; /* 有例外的连接队列 */
} SocketsMsg;
typedef struct Sockets { /* 套接字结构 */
int DaemonSock; /* 主套接字 */
int SockNum; /* 数据套接字数目 */
int Sockets[64]; /* 数据套接字数组 */
fd_set readfds, writefds, exceptfds; /* 要被检测的可读、可写、例外的套接字集合 */
int Port; /* 端口号 */
} Sockets;
Sockets Mysock; /* 全局变量 */
SocketsMsg SockMsg;
int InitSocketsStruct(char * servicename) ;
int InitPassiveSock(char * servicename) ;
void CloseMainSock();
int CreateConnection(struct in_addr *sin_addr);
int AcceptConnection(struct in_addr *IPaddr);
int CloseConnection(int Sockno);
int QuerySocketsMsg();
int SendPacket(int Sockno, void *buf, int len);
int RecvPacket(int Sockno, void *buf, int size);
2.5.2 函数源文件
it55.com
/* File Name: tcpsock.c */
/* 本文件给出九个函数的源代码,其中部分地方给出中文注释 */
#include "tcpsock.h"
int InitSocketsStruct(char * servicename)
/* Initialize Sockets structure. If succeed then return 1, else return error code (<0) */
/* 此函数用于只需要主动套接字的客户程序,它用来获取服务信息。服务的定义
在/etc/services文件中 */
{
struct servent *servrec;
struct sockaddr_in serv_addr;
if ((servrec = getservbyname(servicename, "tcp")) == NULL) {
return(-1);
}
bzero((char *)&Mysock, sizeof(Sockets));
Mysock.Port = servrec->s_port; /* Service Port in Network Byte Order */
return(1);
}
int InitPassiveSock(char * servicename)
/* Initialize Passive Socket. If succeed then return 1, else return error code (<0) */
/* 此函数用于需要被动套接字的服务器程序,它除了获取服务信息外,还建立
一个被动套接字。*/
{
int mainsock, flag=1;
struct servent *servrec;
struct sockaddr_in serv_addr;
if ((servrec = getservbyname(servicename, "tcp")) == NULL) {
return(-1);
}
bzero((char *)&Mysock, sizeof(Sockets));
Mysock.Port = servrec->s_port; /* Service Port in Network Byte Order */
if((mainsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
return(-2);
}
bzero((char *)&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); /* 任意网络接口 */
serv_addr.sin_port = servrec->s_port;
if (bind(mainsock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
close(mainsock);
return(-3);
}
if (listen(mainsock, 5) == -1) { /* 将主动套接字变为被动套接字,准备好接收连接 */
close(mainsock);
return(-4);
}
/* Set this socket as a Non-blocking socket. */
if (ioctl(mainsock, FIONBIO, &flag) == -1) {
close(mainsock);
return(-5);
}
Mysock.DaemonSock = mainsock;
FD_SET(mainsock, &Mysock.readfds); /* 申明对主套接字“可读”感兴趣 */
FD_SET(mainsock, &Mysock.exceptfds); /* 申明对主套接字上例外事件感兴趣 */
return(1);
}
void CloseMainSock()
/* 关闭主套接字,并清除对它上面事件的申明。在程序结束前关闭主套接字是一个好习惯 */
{
close(Mysock.DaemonSock);
FD_CLR(Mysock.DaemonSock, &Mysock.readfds);
FD_CLR(Mysock.DaemonSock, &Mysock.exceptfds);
}
int CreateConnection(struct in_addr *sin_addr)
/* Create a Connection to remote host which IP address is in sin_addr.
Param: sin_addr indicates the IP address in Network Byte Order.
if succeed return the socket number which indicates this connection,
else return error code (<0) */
{
struct sockaddr_in server; /* server address */
int tmpsock, flag=1, i;
if ((tmpsock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return(-1);
server.sin_family = AF_INET;
server.sin_port = Mysock.Port;
server.sin_addr.s_addr = sin_addr->s_addr;
/* Set this socket as a Non-blocking socket. */
if (ioctl(tmpsock, FIONBIO, &flag) == -1) {
close(tmpsock);
return(-2);
}
/* Connect to the server. */
if (connect(tmpsock, (struct sockaddr *)&server, sizeof(server)) < 0) {
if ((errno != EWOULDBLOCK) && (errno != EINPROGRESS)) {
/* 如果错误代码是EWOULDBLOCK和EINPROGRESS,则不用关闭套接字,因为系统将在之后继续为套接字建立连接,连接是否建立成功可用select()函数来检测套接字是否“可写”来确定。*/
close(tmpsock);
return(-3); /* Connect error. */
}
}
FD_SET(tmpsock, &Mysock.readfds);
FD_SET(tmpsock, &Mysock.writefds);
FD_SET(tmpsock, &Mysock.exceptfds);
i = 0;
while (Mysock.Sockets[i] != 0) i++; /* look for a blank sockets position */
if (i >= 64) {
close(tmpsock);
return(-4); /* too many connections */
}
Mysock.Sockets[i] = tmpsock;
Mysock.SockNum++;
return(i);
}
int AcceptConnection(struct in_addr *IPaddr)
/* Accept a connection. If succeed, return the data sockets number, else return -1. */
{
int newsock, len, flag=1, i;
struct sockaddr_in addr;
len = sizeof(addr);
bzero((char *)&addr, len);
if ((newsock = accept(Mysock.DaemonSock, &addr, &len)) == -1)
return(-1); /* Accept error. */
/* Set this socket as a Non-blocking socket. */
ioctl(newsock, FIONBIO, &flag);
FD_SET(newsock, &Mysock.readfds);
FD_SET(newsock, &Mysock.writefds);
FD_SET(newsock, &Mysock.exceptfds);
/* Return IP address in the Parameter. */
IPaddr->s_addr = addr.sin_addr.s_addr;
i = 0;
while (Mysock.Sockets[i] != 0) i++; /* look for a blank sockets position */
if (i >= 64) {
close(newsock);
return(-4); /* too many connections */
}
Mysock.Sockets[i] = newsock;
Mysock.SockNum++;
return(i);
}
int CloseConnection(int Sockno)
/* Close a connection indicated by Sockno. */
{
int retcode;
if ((Sockno >= 64) || (Sockno < 0) || (Mysock.Sockets[Sockno] == 0))
return(0);
retcode = close(Mysock.Sockets[Sockno]);
FD_CLR(Mysock.Sockets[Sockno], &Mysock.readfds);
FD_CLR(Mysock.Sockets[Sockno], &Mysock.writefds);
FD_CLR(Mysock.Sockets[Sockno], &Mysock.exceptfds);
Mysock.Sockets[Sockno] = 0;
Mysock.SockNum--;
return(retcode);
}
int QuerySocketsMsg()
/* Query Sockets Message. If succeed return message number, else return -1.
The message information stored in struct SockMsg. */
{
fd_set rfds, wfds, efds;
int retcode, i;
struct timeval TimeOut;
rfds = Mysock.readfds;
wfds = Mysock.writefds;
efds = Mysock.exceptfds;
TimeOut.tv_sec = 0; /* 立即返回,不阻塞。*/
TimeOut.tv_usec = 0;
bzero((char *)&SockMsg, sizeof(SockMsg));
if ((retcode = select(64, &rfds, &wfds, &efds, &TimeOut)) == 0)
return(0);
if (FD_ISSET(Mysock.DaemonSock, &rfds))
SockMsg.AcceptNum = 1; /* some client call server. */
for (i=0; i<64; i++) /* Data in message */
{
if ((Mysock.Sockets[i] > 0) && (FD_ISSET(Mysock.Sockets[i], &rfds)))
SockMsg.ReadQueue[SockMsg.ReadNum++] = i;
}
for (i=0; i<64; i++) /* Data out ready message */
{
if ((Mysock.Sockets[i] > 0) && (FD_ISSET(Mysock.Sockets[i], &wfds)))
SockMsg.WriteQueue[SockMsg.WriteNum++] = i;
}
if (FD_ISSET(Mysock.DaemonSock, &efds))
SockMsg.AcceptNum = -1; /* server socket error. */
for (i=0; i<64; i++) /* Error message */
{
if ((Mysock.Sockets[i] > 0) && (FD_ISSET(Mysock.Sockets[i], &efds)))
SockMsg.ExceptQueue[SockMsg.ExceptNum++] = i;
}
return(retcode);
}
int SendPacket(int Sockno, void *buf, int len)
/* Send a packet. If succeed return the number of send data, else return -1 */
{
int actlen;
if ((Sockno >= 64) || (Sockno < 0) || (Mysock.Sockets[Sockno] == 0))
return(0);
if ((actlen = send(Mysock.Sockets[Sockno], buf, len, 0)) < 0)
return(-1);
return(actlen);
}
int RecvPacket(int Sockno, void *buf, int size)
/* Receive a packet. If succeed return the number of receive data, else if the connection
is shutdown by peer then return 0, otherwise return 0-errno */
{
int actlen;
if ((Sockno >= 64) || (Sockno < 0) || (Mysock.Sockets[Sockno] == 0))
return(0);
if ((actlen = recv(Mysock.Sockets[Sockno], buf, size, 0)) < 0)
return(0-errno);
return(actlen); /* actlen是接收的数据长度,如果为零,指示连接被对方关闭。*/
}
2.5.3 简单服务器程序示例 www.it55.com
/* File Name: server.c */
/* 这是一个很简单的重复服务器程序,它初始化好被动套接字后,循环等待接收连接。如果接收到连接,它显示数据套接字序号和客户端的IP地址;如果数据套接字上有数据到来,它接收数据并显示该连接的数据套接字序号和接收到的字符串。*/
#include "tcpsock.h"
main(argc, argv)
int argc;
char **argv;
{
struct in_addr sin_addr;
int retcode, i;
char buf[32];
/* 对于服务器程序,它经常是处于无限循环状态,只有在用户主动kill该进程或系统关机时,它才结束。对于使用kill强行终止的服务器程序,由于主套接字没有关闭,资源没有主动释放,可能会给随后的服务器程序重新启动产生影响。因此,主动关闭主套接字是一个良好的变成习惯。下面的语句使程序在接收到SIGINT、SIGQUIT和SIGTERM等信号时先执行CloseMainSock()函数关闭主套接字,然后再结束程序。因此,在使用kill强行终止服务器进程时,应该先使用kill -2 PID给服务器程序一个消息使其关闭主套接字,然后在用kill -9 PID强行结束该进程。*/
(void) signal(SIGINT, CloseMainSock);
(void) signal(SIGQUIT, CloseMainSock);
(void) signal(SIGTERM, CloseMainSock);
if ((retcode = InitPassiveSock("TestService")) < 0) {
printf("InitPassiveSock: error code = %d/n", retcode);
exit(-1);
}
while (1) {
retcode = QuerySocketsMsg(); /* 查询网络消息 */
if (SockMsg.AcceptNum == 1) { /* 有外来连接等待接收?*/
retcode = AcceptConnection(&sin_addr);
printf("retcode = %d, IP = %s /n", retcode, inet_ntoa(sin_addr.s_addr));
}
else if (SockMsg.AcceptNum == -1) /* 主套接字错误?*/
printf("Daemon Sockets error./n");
for (i=0; i
if ((retcode = RecvPacket(SockMsg.ReadQueue[i], buf, 32)) > 0)
printf("sockno %d Recv string = %s /n", SockMsg.ReadQueue[i], buf);
else /* 返回数据长度为零,指示连接中断,关闭套接字。*/
CloseConnection(SockMsg.ReadQueue[i]);
}
} /* end while */
}
2.5.4 简单客户程序示例 45398 www.it55.com it55学习IT知识,享受IT生活 4dfkjn
/* File Name: client.c */
/* 客户程序在执行时,先初始化数据结构,然后等待用户输入命令。它识别四个命令:
conn(ect): 和服务器建立连接;
send: 给指定连接发送数据;
clos(e): 关闭指定连接;
quit: 退出客户程序。
*/
#include "tcpsock.h"
main(argc, argv)
int argc;
char **argv;
{
char cmd_buf[16];
struct in_addr sin_addr;
int sockno1, retcode;
char *buf = "This is a string for test.";
sin_addr.s_addr = inet_addr("166.111.5.249"); /* 运行服务器程序的主机的IP地址 */
if ((retcode = InitSocketsStruct("TestService")) < 0) { /* 初始化数据结构 */
printf("InitSocketsStruct: error code = %d/n", retcode);
exit(1);
}
while (1) {
printf(">");
gets(cmd_buf);
if (!strncmp(cmd_buf, "conn", 4)) {
retcode = CreateConnection(&sin_addr); /* 建立连接 */
printf("return code: %d/n", retcode);
}
else if(!strncmp(cmd_buf, "send", 4)) {
printf("Sockets Number:");
scanf("%d", &sockno1);
retcode = SendPacket(sockno1, buf, 26); /* 发送数据 */
printf("return code: %d/n", retcode, sizeof(buf));
}
else if (!strncmp(cmd_buf, "close", 4)) {
printf("Sockets Number:");
scanf("%d", &sockno1);
retcode = CloseConnection(sockno1); /* 关闭连接 */
printf("return code: %d/n", retcode);
}
else if (!strncmp(cmd_buf, "quit", 4))
exit(0);
else
putchar('/007');
} /* end while */
}
2.1 问题的引入
Unix系统的I/O命令集,是从Maltics和早期系统中的命令演变出来的,其模式为打开一读/写一关闭(open-write-read-close)。在一个用户进程进行I/O操作时,它首先调用“打开”获得对指定文件或设备的使用权,并返回称为文件描述符的整型数,以描述用户在打开的文件或设备上进行I/O操作的进程。然后这个用户进程多次调用“读/写”以传输数据。当所有的传输操作完成后,用户进程关闭调用,通知操作系统已经完成了对某对象的使用。
TCP/IP协议被集成到UNIX内核中时,相当于在UNIX系统引入了一种新型的I/O操作。UNIX用户进程与网络协议的交互作用比用户进程与传统的I/O设备相互作用复杂得多。首先,进行网络操作的两个进程在不同机器上,如何建立它们之间的联系?其次,网络协议存在多种,如何建立一种通用机制以支持多种协议?这些都是网络应用编程界面所要解决的问题。
在UNIX系统中,网络应用编程界面有两类:UNIX BSD的套接字(socket)和UNIX System V的TLI。由于Sun公司采用了支持TCP/IP的UNIX BSD操作系统,使TCP/IP的应用有更大的发展,其网络应用编程界面──套接字(socket)在网络软件中被广泛应用,至今已引进微机操作系统Dos和Windows系统中,成为开发网络应用软件的强有力工具,本章将要详细讨论这个问题。
2.2 套接字编程基本概念
在开始使用套接字编程之前,首先必须建立以下概念。
2.2.1 网间进程通信
进程通信的概念最初来源于单机系统。由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进程之间既互不干扰又协调一致工作,操作系统为进程通信提供了相应设施,如UNIX BSD中的管道(pipe)、命名管道(named pipe)和软中断信号(signal),UNIX system V的消息(message)、共享存储区(shared memory)和信号量(semaphore)等,但都仅限于用在本机进程之间通信。网间进程通信要解决的是不同主机进程间的相互通信问题(可把同机进程通信看作是其中的特例)。为此,首先要解决的是网间进程标识问题。同一主机上,不同进程可用进程号(process ID)唯一标识。但在网络环境下,各主机独立分配的进程号不能唯一标识该进程。例如,主机A赋于某进程号5,在B机中也可以存在5号进程,因此,“5号进程”这句话就没有意义了。
其次,操作系统支持的网络协议众多,不同协议的工作方式不同,地址格式也不同。因此,网间进程通信还要解决多重协议的识别问题。
为了解决上述问题,TCP/IP协议引入了下列几个概念。
端口
网络中可以被命名和寻址的通信端口,是操作系统可分配的一种资源。
按照OSI七层协议的描述,传输层与网络层在功能上的最大区别是传输层提供进程通信能力。从这个意义上讲,网络通信的最终地址就不仅仅是主机地址了,还包括可以描述进程的某种标识符。为此,TCP/IP协议提出了协议端口(protocol port,简称端口)的概念,用于标识通信的进程。
端口是一种抽象的软件结构(包括一些数据结构和I/O缓冲区)。应用程序(即进程)通过系统调用与某端口建立连接(binding)后,传输层传给该端口的数据都被相应进程所接收,相应进程发给传输层的数据都通过该端口输出。在TCP/IP协议的实现中,端口操作类似于一般的I/O操作,进程获取一个端口,相当于获取本地唯一的I/O文件,可以用一般的读写原语访问之。
类似于文件描述符,每个端口都拥有一个叫端口号(port number)的整数型标识符,用于区别不同端口。由于TCP/IP传输层的两个协议TCP和UDP是完全独立的两个软件模块,因此各自的端口号也相互独立,如TCP有一个255号端口,UDP也可以有一个255号端口,二者并不冲突。
端口号的分配是一个重要问题。有两种基本分配方式:第一种叫全局分配,这是一种集中控制方式,由一个公认的中央机构根据用户需要进行统一分配,并将结果公布于众。第二种是本地分配,又称动态连接,即进程需要访问传输层服务时,向本地操作系统提出申请,操作系统返回一个本地唯一的端口号,进程再通过合适的系统调用将自己与该端口号联系起来(绑扎)。TCP/IP端口号的分配中综合了上述两种方式。TCP/IP将端口号分为两部分,少量的作为保留端口,以全局方式分配给服务进程。因此,每一个标准服务器都拥有一个全局公认的端口(即周知口,well-known port),即使在不同机器上,其端口号也相同。剩余的为自由端口,以本地方式进行分配。TCP和UDP均规定,小于256的端口号才能作保留端口。
地址
网络通信中通信的两个进程分别在不同的机器上。在互连网络中,两台机器可能位于不同的网络,这些网络通过网络互连设备(网关,网桥,路由器等)连接。因此需要三级寻址:
1. 某一主机可与多个网络相连,必须指定一特定网络地址;
2. 网络上每一台主机应有其唯一的地址;
3. 每一主机上的每一进程应有在该主机上的唯一标识符。
通常主机地址由网络ID和主机ID组成,在TCP/IP协议中用32位整数值表示;TCP和UDP均使用16位端口号标识用户进程。
网络字节顺序
不同的计算机存放多字节值的顺序不同,有的机器在起始地址存放低位字节(低价先存),有的存高位字节(高价先存)。为保证数据的正确性,在网络协议中须指定网络字节顺序。TCP/IP协议使用16位整数和32位整数的高价先存格式,它们均含在协议头文件中。
连接
两个进程间的通信链路称为连接。连接在内部表现为一些缓冲区和一组协议机制,在外部表现出比无连接高的可靠性。
半相关
综上所述,网络中用一个三元组可以在全局唯一标志一个进程:
(协议,本地地址,本地端口号)
这样一个三元组,叫做一个半相关(half-association),它指定连接的每半部分。
全相关
一个完整的网间进程通信需要由两个进程组成,并且只能使用同一种高层协议。也就是说,不可能通信的一端用TCP协议,而另一端用UDP协议。因此一个完整的网间通信需要一个五元组来标识:
(协议,本地地址,本地端口号,远地地址,远地端口号)
这样一个五元组,叫做一个相关(association),即两个协议相同的半相关才能组合成一个合适的相关,或完全指定组成一连接。
2.2.2 服务方式 www.it55.com在线教程
在网络分层结构中,各层之间是严格单向依赖的,各层次的分工和协作集中体现在相邻层之间的界面上。“服务”是描述相邻层之间关系的抽象概念,即网络中各层向紧邻上层提供的一组操作。下层是服务提供者,上层是请求服务的用户。服务的表现形式是原语(primitive),如系统调用或库函数。系统调用是操作系统内核向网络应用程序或高层协议提供的服务原语。网络中的n层总要向n+1层提供比n-1层更完备的服务,否则n层就没有存在的价值。
在OSI的术语中,网络层及其以下各层又称为通信子网,只提供点到点通信,没有程序或进程的概念。而传输层实现的是“端到端”通信,引进网间进程通信概念,同时也要解决差错控制,流量控制,数据排序(报文排序),连接管理等问题,为此提供不同的服务方式:
面向连接(虚电路)或无连接
面向连接服务是电话系统服务模式的抽象,即每一次完整的数据传输都要经过建立连接,使用连接,终止连接的过程。在数据传输过程中,各数据分组不携带目的地址,而使用连接号(connect ID)。本质上,连接是一个管道,收发数据不但顺序一致,而且内容相同。TCP协议提供面向连接的虚电路。
无连接服务是邮政系统服务的抽象,每个分组都携带完整的目的地址,各分组在系统中独立传送。无连接服务不能保证分组的先后顺序,不进行分组出错的恢复与重传,不保证传输的可靠性。UDP协议提供无连接的数据报服务。
下面给出这两种服务的类型及应用中的例子:
服务类型 www.it55.com在线教程
服 务
例 子
面向连接
可靠的报文流
可靠的字节流
不可靠的连接
文件传输(FTP)
远程登录(Telnet)
数字话音
无连接
不可靠的数据报
有确认的数据报
请求-应答
电子邮件(E-mail)
电子邮件中的挂号信
网络数据库查询
顺序
在网络传输中,两个连续报文在端-端通信中可能经过不同路径,这样到达目的地时的顺序可能会与发送时不同。“顺序”是指接收数据顺序与发送数据顺序相同。TCP协议提供这项服务。
差错控制
保证应用程序接收的数据无差错的一种机制。检查差错的方法一般是采用检验“检查和(Checksum)”的方法。而保证传送无差错的方法是双方采用确认应答技术。TCP协议提供这项服务。
流控制
在数据传输过程中控制数据传输速率的一种机制,以保证数据不被丢失。TCP协议提供这项服务。
字节流
字节流方式指的是仅把传输中的报文看作是一个字节序列,不提供数据流的任何边界。TCP协议提供字节流服务。
报文
接收方要保存发送方的报文边界。UDP协议提供报文服务。
全双工/半双工
端-端间数据同时以两个方向/一个方向传送。
缓存/带外数据
在字节流服务中,由于没有报文边界,用户进程在某一时刻可以读或写任意数量的字节。为保证传输正确或采用有流控制的协议时,都要进行缓存。但对某些特殊的需求,如交互式应用程序,又会要求取消这种缓存。
在数据传送过程中,希望不通过常规传输方式传送给用户以便及时处理的某一类信息,如UNIX系统的中断键(Delete或Control-c)、终端流控制符(Control-s和Control-q),称为带外数据。逻辑上看,好象用户进程使用了一个独立的通道传输这些数据。该通道与每对连接的流相联系。由于Berkeley Software Distribution中对带外数据的实现与RFC 1122中规定的Host Agreement不一致,为了将互操作中的问题减到最小,应用程序编写者除非与现有服务互操作时要求带外数据外,最好不使用它。
2.2.3 客户/服务器模式
IT资讯之家 www.it55.com
在TCP/IP网络应用中,通信的两个进程间相互作用的主要模式是客户/服务器模式(ClIEnt/Server model),即客户向服务器发出服务请求,服务器接收到请求后,提供相应的服务。客户/服务器模式的建立基于以下两点:首先,建立网络的起因是网络中软硬件资源、运算能力和信息不均等,需要共享,从而造就拥有众多资源的主机提供服务,资源较少的客户请求服务这一非对等作用。其次,网间进程通信完全是异步的,相互通信的进程间既不存在父子关系,又不共享内存缓冲区,因此需要一种机制为希望通信的进程间建立联系,为二者的数据交换提供同步,这就是基于客户/服务器模式的TCP/IP。
客户/服务器模式在操作过程中采取的是主动请求方式:
首先服务器方要先启动,并根据请求提供相应服务:
1. 打开一通信通道并告知本地主机,它愿意在某一公认地址上(周知口,如FTP为21)接收客户请求;
2. 等待客户请求到达该端口;
3. 接收到重复服务请求,处理该请求并发送应答信号。接收到并发服务请求,要激活一新进程来处理这个客户请求(如UNIX系统中用fork、exec)。新进程处理此客户请求,并不需要对其它请求作出应答。服务完成后,关闭此新进程与客户的通信链路,并终止。
4. 返回第二步,等待另一客户请求。
5. 关闭服务器
客户方:
1. 打开一通信通道,并连接到服务器所在主机的特定端口;
2. 向服务器发服务请求报文,等待并接收应答;继续提出请求......
3. 请求结束后关闭通信通道并终止。
从上面所描述过程可知:
1. 客户与服务器进程的作用是非对称的,因此编码不同。
2. 服务进程一般是先于客户请求而启动的。只要系统运行,该服务进程一直存在,直到正常或强迫终止。
2.2.4 套接字类型 it55.com
TCP/IP的socket提供下列三种类型套接字。
流式套接字(SOCK_STREAM)
提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复地发送,且按发送顺序接收。内设流量控制,避免数据流超限;数据被看作是字节流,无长度限制。文件传送协议(FTP)即使用流式套接字。
数据报式套接字(SOCK_DGRAM)
提供了一个无连接服务。数据包以独立包形式被发送,不提供无错保证,数据可能丢失或重复,并且接收顺序混乱。网络文件系统(NFS)使用数据报式套接字。
原始式套接字(SOCK_RAW)
该接口允许对较低层协议,如IP、ICMP直接访问。常用于检验新的协议实现或访问现有服务中配置的新设备。
2.3 基本套接字系统调用
为了更好地说明套接字编程原理,下面给出几个基本套接字系统调用说明。
2.3.1 创建套接字──socket()
应用程序在使用套接字前,首先必须拥有一个套接字,系统调用socket()向应用程序提供创建套接字的手段,其调用格式如下:
SOCKET PASCAL FAR socket(int af, int type, int protocol);
该调用要接收三个参数:af、type、protocol。参数af指定通信发生的区域,UNIX系统支持的地址族有:AF_UNIX、AF_INET、AF_NS等,而DOS、WINDOWS中仅支持AF_INET,它是网际网区域。因此,地址族与协议族相同。参数type 描述要建立的套接字的类型。参数protocol说明该套接字使用的特定协议,如果调用者不希望特别指定使用的协议,则置为0,使用默认的连接模式。根据这三个参数建立一个套接字,并将相应的资源分配给它,同时返回一个整型套接字号。因此,socket()系统调用实际上指定了相关五元组中的“协议”这一元。
有关socket()的详细描述参看5.2.23。
2.3.2 指定本地地址──bind()
http://www.it55.com/
当一个套接字用socket()创建后,存在一个名字空间(地址族),但它没有被命名。bind()将套接字地址(包括本地主机地址和本地端口地址)与所创建的套接字号联系起来,即将名字赋予套接字,以指定本地半相关。其调用格式如下:
int PASCAL FAR bind(SOCKET s, const struct sockaddr FAR * name, int namelen);
参数s是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。参数name 是赋给套接字s的本地地址(名字),其长度可变,结构随通信域的不同而不同。namelen表明了name的长度。
如果没有错误发生,bind()返回0。否则返回值SOCKET_ERROR。
地址在建立套接字通信过程中起着重要作用,作为一个网络应用程序设计者对套接字地址结构必须有明确认识。例如,UNIX BSD有一组描述套接字地址的数据结构,其中使用TCP/IP协议的地址结构为:
struct sockaddr_in{
short sin_family; /*AF_INET*/
u_short sin_port; /*16位端口号,网络字节顺序*/
struct in_addr sin_addr; /*32位IP地址,网络字节顺序*/
char sin_zero[8]; /*保留*/
}
有关bind()的详细描述参看5.2.2。
2.3.3 建立套接字连接──connect()与accept()
www.it55.com在线教程
这两个系统调用用于完成一个完整相关的建立,其中connect()用于建立连接。无连接的套接字进程也可以调用connect(),但这时在进程之间没有实际的报文交换,调用将从本地操作系统直接返回。这样做的优点是程序员不必为每一数据指定目的地址,而且如果收到的一个数据报,其目的端口未与任何套接字建立“连接”,便能判断该端口不可操作。而accept()用于使服务器等待来自某客户进程的实际连接。
connect()的调用格式如下:
int PASCAL FAR connect(SOCKET s, const struct sockaddr FAR * name, int namelen);
参数s是欲建立连接的本地套接字描述符。参数name指出说明对方套接字地址结构的指针。对方套接字地址长度由namelen说明。
如果没有错误发生,connect()返回0。否则返回值SOCKET_ERROR。在面向连接的协议中,该调用导致本地系统和外部系统之间连接实际建立。
由于地址族总被包含在套接字地址结构的前两个字节中,并通过socket()调用与某个协议族相关。因此bind()和connect()无须协议作为参数。
有关connect()的详细描述参看5.2.4。
accept()的调用格式如下:
SOCKET PASCAL FAR accept(SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen);
参数s为本地套接字描述符,在用做accept()调用的参数前应该先调用过listen()。addr 指向客户方套接字地址结构的指针,用来接收连接实体的地址。addr的确切格式由套接字创建时建立的地址族决定。addrlen 为客户方套接字地址的长度(字节数)。如果没有错误发生,accept()返回一个SOCKET类型的值,表示接收到的套接字的描述符。否则返回值INVALID_SOCKET。
accept()用于面向连接服务器。参数addr和addrlen存放客户方的地址信息。调用前,参数addr 指向一个初始值为空的地址结构,而addrlen 的初始值为0;调用accept()后,服务器等待从编号为s的套接字上接受客户连接请求,而连接请求是由客户方的connect()调用发出的。当有连接请求到达时,accept()调用将请求连接队列上的第一个客户方套接字地址及长度放入addr 和addrlen,并创建一个与s有相同特性的新套接字号。新的套接字可用于处理服务器并发请求。
有关accept()的详细描述参看5.2.1。
四个套接字系统调用,socket()、bind()、connect()、accept(),可以完成一个完全五元相关的建立。socket()指定五元组中的协议元,它的用法与是否为客户或服务器、是否面向连接无关。bind()指定五元组中的本地二元,即本地主机地址和端口号,其用法与是否面向连接有关:在服务器方,无论是否面向连接,均要调用bind();在客户方,若采用面向连接,则可以不调用bind(),而通过connect()自动完成。若采用无连接,客户方必须使用bind()以获得一个唯一的地址。
以上讨论仅对客户/服务器模式而言,实际上套接字的使用是非常灵活的,唯一需遵循的原则是进程通信之前,必须建立完整的相关。
2.3.4 监听连接──listen()
IT资讯之家 www.it55.com
此调用用于面向连接服务器,表明它愿意接收连接。listen()需在accept()之前调用,其调用格式如下:
int PASCAL FAR listen(SOCKET s, int backlog);
参数s标识一个本地已建立、尚未连接的套接字号,服务器愿意从它上面接收请求。backlog表示请求连接队列的最大长度,用于限制排队请求的个数,目前允许的最大值为5。如果没有错误发生,listen()返回0。否则它返回SOCKET_ERROR。
listen()在执行调用过程中可为没有调用过bind()的套接字s完成所必须的连接,并建立长度为backlog的请求连接队列。
调用listen()是服务器接收一个连接请求的四个步骤中的第三步。它在调用socket()分配一个流套接字,且调用bind()给s赋于一个名字之后调用,而且一定要在accept()之前调用。
有关listen()的详细描述参看5.2.13。
2.2.3节中提到在客户/服务器模式中,有两种类型的服务:重复服务和并发服务。accept()调用为实现并发服务提供了极大方便,因为它要返回一个新的套接字号,其典型结构为:
int initsockid, newsockid;
if ((initsockid = socket(....)) < 0)
error(“can't create socket”);
if (bind(initsockid,....) < 0)
error(“bind error”);
if (listen(initsockid , 5) < 0)
error(“listen error”);
for (;;) {
newsockid = accept(initsockid, ...) /* 阻塞 */
if (newsockid < 0)
error(“accept error“);
if (fork() == 0){ /* 子进程 */
closesocket(initsockid);
do(newsockid); /* 处理请求 */
exit(0);
}
closesocket(newsockid); /* 父进程 */
}
这段程序执行的结果是newsockid与客户的套接字建立相关,子进程启动后,关闭继承下来的主服务器的initsockid,并利用新的newsockid与客户通信。主服务器的initsockid可继续等待新的客户连接请求。由于在Unix等抢先多任务系统中,在系统调度下,多个进程可以同时进行。因此,使用并发服务器可以使服务器进程在同一时间可以有多个子进程和不同的客户程序连接、通信。在客户程序看来,服务器可以同时并发地处理多个客户的请求,这就是并发服务器名称的来由。
面向连接服务器也可以是重复服务器,其结构如下:
int initsockid, newsockid;
if ((initsockid = socket(....))<0)
error(“can't create socket”);
if (bind(initsockid,....)<0)
error(“bind error”);
if (listen(initsockid,5)<0)
error(“listen error”);
for (;;) {
newsockid = accept(initsockid, ...) /* 阻塞 */
if (newsockid < 0)
error(“accept error“);
do(newsockid); /* 处理请求 */
closesocket(newsockid);
}
重复服务器在一个时间只能和一个客户程序建立连接,它对多个客户程序的处理是采用循环的方式重复进行,因此叫重复服务器。并发服务器和重复服务器各有利弊:并发服务器可以改善客户程序的响应速度,但它增加了系统调度的开销;重复服务器正好与其相反,因此用户在决定是使用并发服务器还是重复服务器时,要根据应用的实际情况来定。
2.3.5 数据传输──send()与recv() http://www.it55.com/
当一个连接建立以后,就可以传输数据了。常用的系统调用有send()和recv()。
send()调用用于在参数s指定的已连接的数据报或流套接字上发送输出数据,格式如下:
int PASCAL FAR send(SOCKET s, const char FAR *buf, int len, int flags);
参数s为已连接的本地套接字描述符。buf 指向存有发送数据的缓冲区的指针,其长度由len 指定。flags 指定传输控制方式,如是否发送带外数据等。如果没有错误发生,send()返回总共发送的字节数。否则它返回SOCKET_ERROR。
有关send()的详细描述参看5.2.19。
recv()调用用于在参数s指定的已连接的数据报或流套接字上接收输入数据,格式如下:
int PASCAL FAR recv(SOCKET s, char FAR *buf, int len, int flags);
参数s 为已连接的套接字描述符。buf指向接收输入数据缓冲区的指针,其长度由len 指定。flags 指定传输控制方式,如是否接收带外数据等。如果没有错误发生,recv()返回总共接收的字节数。如果连接被关闭,返回0。否则它返回SOCKET_ERROR。
有关recv()的详细描述参看5.2.16。
2.3.6 输入/输出多路复用──select() 免费资源www.it55.com
select()调用用来检测一个或多个套接字的状态。对每一个套接字来说,这个调用可以请求读、写或错误状态方面的信息。请求给定状态的套接字集合由一个fd_set结构指示。在返回时,此结构被更新,以反映那些满足特定条件的套接字的子集,同时, select()调用返回满足条件的套接字的数目,其调用格式如下:
int PASCAL FAR select(int nfds, fd_set FAR * readfds, fd_set FAR * writefds, fd_set FAR * exceptfds, const struct timeval FAR * timeout);
参数nfds指明被检查的套接字描述符的值域,此变量一般被忽略。
参数readfds指向要做读检测的套接字描述符集合的指针,调用者希望从中读取数据。参数writefds 指向要做写检测的套接字描述符集合的指针。exceptfds指向要检测是否出错的套接字描述符集合的指针。timeout指向select()函数等待的最大时间,如果设为NULL则为阻塞操作。select()返回包含在fd_set结构中已准备好的套接字描述符的总数目,或者是发生错误则返回SOCKET_ERROR。
有关select()的详细描述参看5.2.18。
2.3.7 关闭套接字──closesocket()
closesocket()关闭套接字s,并释放分配给该套接字的资源;如果s涉及一个打开的TCP连接,则该连接被释放。closesocket()的调用格式如下:
BOOL PASCAL FAR closesocket(SOCKET s);
参数s待关闭的套接字描述符。如果没有错误发生,closesocket()返回0。否则返回值SOCKET_ERROR。
有关closesocket()的详细描述参看5.2.3。 45398 www.it55.com it55学习IT知识,享受IT生活 4dfkjn
2.4 典型套接字调用过程举例
如前所述,TCP/IP协议的应用一般采用客户/服务器模式,因此在实际应用中,必须有客户和服务器两个进程,并且首先启动服务器,其系统调用时序图如下。
it55.com
面向连接的协议(如TCP)的套接字系统调用如图2.1所示:
服务器必须首先启动,直到它执行完accept()调用,进入等待状态后,方能接收客户请求。假如客户在此前启动,则connect()将返回出错代码,连接不成功。
图2.1 面向连接的套接字系统调用时序图
无连接协议的套接字调用如图2.2所示:
图2.2 无连接协议的套接字调用时序图
无连接服务器也必须先启动,否则客户请求传不到服务进程。无连接客户不调用connect()。因此在数据发送之前,客户与服务器之间尚未建立完全相关,但各自通过socket()和bind()建立了半相关。发送数据时,发送方除指定本地套接字号外,还需指定接收方套接字号,从而在数据收发过程中动态地建立了全相关。
实例
本实例使用面向连接协议的客户/服务器模式,其流程如图2.3所示:
图2.3 面向连接的应用程序流程图
服务器方程序:
/* File Name: streams.c */
#include
#include
#define TRUE 1
/* 这个程序建立一个套接字,然后开始无限循环;每当它通过循环接收到一个连接,则打印出一个信息。当连接断开,或接收到终止信息,则此连接结束,程序再接收一个新的连接。命令行的格式是:streams */
main( )
{
int sock, length;
struct sockaddr_in server;
struct sockaddr tcpaddr;
int msgsock;
char buf[1024];
int rval, len;
/* 建立套接字 */
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror(“opening stream socket”);
exit(1);
}
/* 使用任意端口命名套接字 */
server.sin_family = AF_INET;
server.sin_port = INADDR_ANY;
if (bind(sock, (struct sockaddr *)&server, sizeof(server)) < 0) {
perror(“binding stream socket”);
exit(1);
}
/* 找出指定的端口号并打印出来 */
length = sizeof(server);
if (getsockname(sock, (struct sockaddr *)&server, &length) < 0) {
perror(“getting socket name”);
exit(1);
}
printf(“socket port #%d/n”, ntohs(server.sin_port));
/* 开始接收连接 */
listen(sock, 5);
len = sizeof(struct sockaddr);
do {
msgsock = accept(sock, (struct sockaddr *)&tcpaddr, (int *)&len);
if (msgsock == -1)
perror(“accept”);
else do{
memset(buf, 0, sizeof(buf));
if ((rval = recv(msgsock, buf, 1024)) < 0)
perror(“reading stream message”);
if (rval == 0)
printf(“ending connection /n”);
else
printf(“-->%s/n”, buf);
}while (rval != 0);
closesocket(msgsock);
} while (TRUE);
/* 因为这个程序已经有了一个无限循环,所以套接字“sock”从来不显式关闭。然而,当进程被杀死或正常终止时,所有套接字都将自动地被关闭。*/
exit(0);
}
客户方程序:
/* File Name: streamc.c */
#include
#include
#define DATA “half a league, half a league ...”
/* 这个程序建立套接字,然后与命令行给出的套接字连接;连接结束时,在连接上发送
一个消息,然后关闭套接字。命令行的格式是:streamc 主机名 端口号
端口号要与服务器程序的端口号相同 */
main(argc, argv)
int argc;
char *argv[ ];
{
int sock;
struct sockaddr_in server;
struct hostent *hp, *gethostbyname( );
char buf[1024];
/* 建立套接字 */
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror(“opening stream socket”);
exit(1);
}
/* 使用命令行中指定的名字连接套接字 */
server.sin_family = AF_INET;
hp = gethostbyname(argv[1]);
if (hp == 0) {
fprintf(stderr, “%s: unknown host /n”, argv[1]);
exit(2);
}
memcpy((char*)&server.sin_addr, (char*)hp->h_addr, hp->h_length);
sever.sin_port = htons(atoi(argv[2]));
if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0) {
perror(“connecting stream socket”);
exit(3);
}
if (send(sock, DATA, sizeof(DATA)) < 0)
perror(“sending on stream socket”);
closesocket(sock);
exit(0);
} sflj www.it55.com kg^&fgd
2.5 一个通用的实例程序
在上一节中,我们介绍了一个简单的socket程序实例。从这个例子我们可以看出,使用socket编程几乎有一个模式,即所有的程序几乎毫无例外地按相同的顺序调用相同的函数。因此我们可以设想,设计一个中间层,它向上提供几个简单的函数,程序只要调用这几个函数就可以实现普通情况下的数据传输,程序设计者不必太多地关心socket程序设计的细节。
本节我们将介绍一个通用的网络程序接口,它向上层提供几个简单的函数,程序设计者只要使用这几个函数就可以完成绝大多数情况下的网络数据传输。这些函数将socket编程和上层隔离开来,它使用面向连接的流式套接字,采用非阻塞的工作机制,程序只要调用这些函数查询网络消息并作出相应的响应即可。这些函数包括:
l InitSocketsStruct:初始化socket结构,获取服务端口号。客户程序使用。
l InitPassiveSock:初始化socket结构,获取服务端口号,建立主套接字。服务器程序使用。
l CloseMainSock:关闭主套接字。服务器程序使用。
l CreateConnection:建立连接。客户程序使用。
l AcceptConnection:接收连接。服务器程序使用。
l CloseConnection:关闭连接。
l QuerySocketsMsg:查询套接字消息。
l SendPacket:发送数据。
l RecvPacket:接收数据。
2.5.1 头文件 www.it55.com在线教程
/* File Name: tcpsock.h */
/* 头文件包括socket程序经常用到的系统头文件(本例中给出的是SCO Unix下的头文件,其它版本的Unix的头文件可能略有不同),并定义了我们自己的两个数据结构及其实例变量,以及我们提供的函数说明。*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef struct SocketsMsg{ /* 套接字消息结构 */
int AcceptNum; /* 指示是否有外来连接等待接收 */
int ReadNum; /* 有外来数据等待读取的连接数 */
int ReadQueue[32]; /* 有外来数据等待读取的连接队列 */
int WriteNum; /* 可以发送数据的连接数 */
int WriteQueue[32]; /* 可以发送数据的连接队列 */
int ExceptNum; /* 有例外的连接数 */
int ExceptQueue[32]; /* 有例外的连接队列 */
} SocketsMsg;
typedef struct Sockets { /* 套接字结构 */
int DaemonSock; /* 主套接字 */
int SockNum; /* 数据套接字数目 */
int Sockets[64]; /* 数据套接字数组 */
fd_set readfds, writefds, exceptfds; /* 要被检测的可读、可写、例外的套接字集合 */
int Port; /* 端口号 */
} Sockets;
Sockets Mysock; /* 全局变量 */
SocketsMsg SockMsg;
int InitSocketsStruct(char * servicename) ;
int InitPassiveSock(char * servicename) ;
void CloseMainSock();
int CreateConnection(struct in_addr *sin_addr);
int AcceptConnection(struct in_addr *IPaddr);
int CloseConnection(int Sockno);
int QuerySocketsMsg();
int SendPacket(int Sockno, void *buf, int len);
int RecvPacket(int Sockno, void *buf, int size);
2.5.2 函数源文件
it55.com
/* File Name: tcpsock.c */
/* 本文件给出九个函数的源代码,其中部分地方给出中文注释 */
#include "tcpsock.h"
int InitSocketsStruct(char * servicename)
/* Initialize Sockets structure. If succeed then return 1, else return error code (<0) */
/* 此函数用于只需要主动套接字的客户程序,它用来获取服务信息。服务的定义
在/etc/services文件中 */
{
struct servent *servrec;
struct sockaddr_in serv_addr;
if ((servrec = getservbyname(servicename, "tcp")) == NULL) {
return(-1);
}
bzero((char *)&Mysock, sizeof(Sockets));
Mysock.Port = servrec->s_port; /* Service Port in Network Byte Order */
return(1);
}
int InitPassiveSock(char * servicename)
/* Initialize Passive Socket. If succeed then return 1, else return error code (<0) */
/* 此函数用于需要被动套接字的服务器程序,它除了获取服务信息外,还建立
一个被动套接字。*/
{
int mainsock, flag=1;
struct servent *servrec;
struct sockaddr_in serv_addr;
if ((servrec = getservbyname(servicename, "tcp")) == NULL) {
return(-1);
}
bzero((char *)&Mysock, sizeof(Sockets));
Mysock.Port = servrec->s_port; /* Service Port in Network Byte Order */
if((mainsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
return(-2);
}
bzero((char *)&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); /* 任意网络接口 */
serv_addr.sin_port = servrec->s_port;
if (bind(mainsock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
close(mainsock);
return(-3);
}
if (listen(mainsock, 5) == -1) { /* 将主动套接字变为被动套接字,准备好接收连接 */
close(mainsock);
return(-4);
}
/* Set this socket as a Non-blocking socket. */
if (ioctl(mainsock, FIONBIO, &flag) == -1) {
close(mainsock);
return(-5);
}
Mysock.DaemonSock = mainsock;
FD_SET(mainsock, &Mysock.readfds); /* 申明对主套接字“可读”感兴趣 */
FD_SET(mainsock, &Mysock.exceptfds); /* 申明对主套接字上例外事件感兴趣 */
return(1);
}
void CloseMainSock()
/* 关闭主套接字,并清除对它上面事件的申明。在程序结束前关闭主套接字是一个好习惯 */
{
close(Mysock.DaemonSock);
FD_CLR(Mysock.DaemonSock, &Mysock.readfds);
FD_CLR(Mysock.DaemonSock, &Mysock.exceptfds);
}
int CreateConnection(struct in_addr *sin_addr)
/* Create a Connection to remote host which IP address is in sin_addr.
Param: sin_addr indicates the IP address in Network Byte Order.
if succeed return the socket number which indicates this connection,
else return error code (<0) */
{
struct sockaddr_in server; /* server address */
int tmpsock, flag=1, i;
if ((tmpsock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return(-1);
server.sin_family = AF_INET;
server.sin_port = Mysock.Port;
server.sin_addr.s_addr = sin_addr->s_addr;
/* Set this socket as a Non-blocking socket. */
if (ioctl(tmpsock, FIONBIO, &flag) == -1) {
close(tmpsock);
return(-2);
}
/* Connect to the server. */
if (connect(tmpsock, (struct sockaddr *)&server, sizeof(server)) < 0) {
if ((errno != EWOULDBLOCK) && (errno != EINPROGRESS)) {
/* 如果错误代码是EWOULDBLOCK和EINPROGRESS,则不用关闭套接字,因为系统将在之后继续为套接字建立连接,连接是否建立成功可用select()函数来检测套接字是否“可写”来确定。*/
close(tmpsock);
return(-3); /* Connect error. */
}
}
FD_SET(tmpsock, &Mysock.readfds);
FD_SET(tmpsock, &Mysock.writefds);
FD_SET(tmpsock, &Mysock.exceptfds);
i = 0;
while (Mysock.Sockets[i] != 0) i++; /* look for a blank sockets position */
if (i >= 64) {
close(tmpsock);
return(-4); /* too many connections */
}
Mysock.Sockets[i] = tmpsock;
Mysock.SockNum++;
return(i);
}
int AcceptConnection(struct in_addr *IPaddr)
/* Accept a connection. If succeed, return the data sockets number, else return -1. */
{
int newsock, len, flag=1, i;
struct sockaddr_in addr;
len = sizeof(addr);
bzero((char *)&addr, len);
if ((newsock = accept(Mysock.DaemonSock, &addr, &len)) == -1)
return(-1); /* Accept error. */
/* Set this socket as a Non-blocking socket. */
ioctl(newsock, FIONBIO, &flag);
FD_SET(newsock, &Mysock.readfds);
FD_SET(newsock, &Mysock.writefds);
FD_SET(newsock, &Mysock.exceptfds);
/* Return IP address in the Parameter. */
IPaddr->s_addr = addr.sin_addr.s_addr;
i = 0;
while (Mysock.Sockets[i] != 0) i++; /* look for a blank sockets position */
if (i >= 64) {
close(newsock);
return(-4); /* too many connections */
}
Mysock.Sockets[i] = newsock;
Mysock.SockNum++;
return(i);
}
int CloseConnection(int Sockno)
/* Close a connection indicated by Sockno. */
{
int retcode;
if ((Sockno >= 64) || (Sockno < 0) || (Mysock.Sockets[Sockno] == 0))
return(0);
retcode = close(Mysock.Sockets[Sockno]);
FD_CLR(Mysock.Sockets[Sockno], &Mysock.readfds);
FD_CLR(Mysock.Sockets[Sockno], &Mysock.writefds);
FD_CLR(Mysock.Sockets[Sockno], &Mysock.exceptfds);
Mysock.Sockets[Sockno] = 0;
Mysock.SockNum--;
return(retcode);
}
int QuerySocketsMsg()
/* Query Sockets Message. If succeed return message number, else return -1.
The message information stored in struct SockMsg. */
{
fd_set rfds, wfds, efds;
int retcode, i;
struct timeval TimeOut;
rfds = Mysock.readfds;
wfds = Mysock.writefds;
efds = Mysock.exceptfds;
TimeOut.tv_sec = 0; /* 立即返回,不阻塞。*/
TimeOut.tv_usec = 0;
bzero((char *)&SockMsg, sizeof(SockMsg));
if ((retcode = select(64, &rfds, &wfds, &efds, &TimeOut)) == 0)
return(0);
if (FD_ISSET(Mysock.DaemonSock, &rfds))
SockMsg.AcceptNum = 1; /* some client call server. */
for (i=0; i<64; i++) /* Data in message */
{
if ((Mysock.Sockets[i] > 0) && (FD_ISSET(Mysock.Sockets[i], &rfds)))
SockMsg.ReadQueue[SockMsg.ReadNum++] = i;
}
for (i=0; i<64; i++) /* Data out ready message */
{
if ((Mysock.Sockets[i] > 0) && (FD_ISSET(Mysock.Sockets[i], &wfds)))
SockMsg.WriteQueue[SockMsg.WriteNum++] = i;
}
if (FD_ISSET(Mysock.DaemonSock, &efds))
SockMsg.AcceptNum = -1; /* server socket error. */
for (i=0; i<64; i++) /* Error message */
{
if ((Mysock.Sockets[i] > 0) && (FD_ISSET(Mysock.Sockets[i], &efds)))
SockMsg.ExceptQueue[SockMsg.ExceptNum++] = i;
}
return(retcode);
}
int SendPacket(int Sockno, void *buf, int len)
/* Send a packet. If succeed return the number of send data, else return -1 */
{
int actlen;
if ((Sockno >= 64) || (Sockno < 0) || (Mysock.Sockets[Sockno] == 0))
return(0);
if ((actlen = send(Mysock.Sockets[Sockno], buf, len, 0)) < 0)
return(-1);
return(actlen);
}
int RecvPacket(int Sockno, void *buf, int size)
/* Receive a packet. If succeed return the number of receive data, else if the connection
is shutdown by peer then return 0, otherwise return 0-errno */
{
int actlen;
if ((Sockno >= 64) || (Sockno < 0) || (Mysock.Sockets[Sockno] == 0))
return(0);
if ((actlen = recv(Mysock.Sockets[Sockno], buf, size, 0)) < 0)
return(0-errno);
return(actlen); /* actlen是接收的数据长度,如果为零,指示连接被对方关闭。*/
}
2.5.3 简单服务器程序示例 www.it55.com
/* File Name: server.c */
/* 这是一个很简单的重复服务器程序,它初始化好被动套接字后,循环等待接收连接。如果接收到连接,它显示数据套接字序号和客户端的IP地址;如果数据套接字上有数据到来,它接收数据并显示该连接的数据套接字序号和接收到的字符串。*/
#include "tcpsock.h"
main(argc, argv)
int argc;
char **argv;
{
struct in_addr sin_addr;
int retcode, i;
char buf[32];
/* 对于服务器程序,它经常是处于无限循环状态,只有在用户主动kill该进程或系统关机时,它才结束。对于使用kill强行终止的服务器程序,由于主套接字没有关闭,资源没有主动释放,可能会给随后的服务器程序重新启动产生影响。因此,主动关闭主套接字是一个良好的变成习惯。下面的语句使程序在接收到SIGINT、SIGQUIT和SIGTERM等信号时先执行CloseMainSock()函数关闭主套接字,然后再结束程序。因此,在使用kill强行终止服务器进程时,应该先使用kill -2 PID给服务器程序一个消息使其关闭主套接字,然后在用kill -9 PID强行结束该进程。*/
(void) signal(SIGINT, CloseMainSock);
(void) signal(SIGQUIT, CloseMainSock);
(void) signal(SIGTERM, CloseMainSock);
if ((retcode = InitPassiveSock("TestService")) < 0) {
printf("InitPassiveSock: error code = %d/n", retcode);
exit(-1);
}
while (1) {
retcode = QuerySocketsMsg(); /* 查询网络消息 */
if (SockMsg.AcceptNum == 1) { /* 有外来连接等待接收?*/
retcode = AcceptConnection(&sin_addr);
printf("retcode = %d, IP = %s /n", retcode, inet_ntoa(sin_addr.s_addr));
}
else if (SockMsg.AcceptNum == -1) /* 主套接字错误?*/
printf("Daemon Sockets error./n");
for (i=0; i
if ((retcode = RecvPacket(SockMsg.ReadQueue[i], buf, 32)) > 0)
printf("sockno %d Recv string = %s /n", SockMsg.ReadQueue[i], buf);
else /* 返回数据长度为零,指示连接中断,关闭套接字。*/
CloseConnection(SockMsg.ReadQueue[i]);
}
} /* end while */
}
2.5.4 简单客户程序示例 45398 www.it55.com it55学习IT知识,享受IT生活 4dfkjn
/* File Name: client.c */
/* 客户程序在执行时,先初始化数据结构,然后等待用户输入命令。它识别四个命令:
conn(ect): 和服务器建立连接;
send: 给指定连接发送数据;
clos(e): 关闭指定连接;
quit: 退出客户程序。
*/
#include "tcpsock.h"
main(argc, argv)
int argc;
char **argv;
{
char cmd_buf[16];
struct in_addr sin_addr;
int sockno1, retcode;
char *buf = "This is a string for test.";
sin_addr.s_addr = inet_addr("166.111.5.249"); /* 运行服务器程序的主机的IP地址 */
if ((retcode = InitSocketsStruct("TestService")) < 0) { /* 初始化数据结构 */
printf("InitSocketsStruct: error code = %d/n", retcode);
exit(1);
}
while (1) {
printf(">");
gets(cmd_buf);
if (!strncmp(cmd_buf, "conn", 4)) {
retcode = CreateConnection(&sin_addr); /* 建立连接 */
printf("return code: %d/n", retcode);
}
else if(!strncmp(cmd_buf, "send", 4)) {
printf("Sockets Number:");
scanf("%d", &sockno1);
retcode = SendPacket(sockno1, buf, 26); /* 发送数据 */
printf("return code: %d/n", retcode, sizeof(buf));
}
else if (!strncmp(cmd_buf, "close", 4)) {
printf("Sockets Number:");
scanf("%d", &sockno1);
retcode = CloseConnection(sockno1); /* 关闭连接 */
printf("return code: %d/n", retcode);
}
else if (!strncmp(cmd_buf, "quit", 4))
exit(0);
else
putchar('/007');
} /* end while */
}