2006年10月10日

好久没有写blog了。今天看到了豆腐生活的blog,又燃起了一丝写点的欲望,于是有了这一片东东。
前些日子,本本坏了。因为我刷了万恶的1.07版的BIOS了,黑屏,不启动。于是返厂一个月。这次返修,顺带解决了半年前出现的上表面外壳轴部出现裂纹的问题。不过由此也损失了逼人200现大洋——主板还好,外壳已经过保了。算来这个本已经买了块一年半了。质保果然是个很重要的东西。
9月29号,本本维修完毕,返回三门峡。下午销售商就打电话到我的手机,叫我领本去。我还在上班,就叫我父亲去了。紧接着十一同学婚礼,马不停蹄的赶到开封。见到了阔别多日的同学们,也见到了朝思暮想的alan——果然我是个邪恶的人,人家已经有男朋友了,我却还是念念不忘。同学见面,相谈甚欢。彼此之间有说不完的亲密。alan大呼:“这是我今年最快乐的两天之一”。
小强带着女朋友去了,居然和楠的妹妹同名同姓,很漂亮的一个女孩。大家的感情事业都发展的很快么。酒席上大家被灌的很惨,开封的服务员很能劝酒,从不粘酒的大家最终解决了一瓶53度的五星茅台。小强有些把持不住了,小猪一样的拱进女朋友的怀里。新娘的鲜花交到了小强的手里,大家便嚷着“让小强三个月后赶紧解决婚礼”——其实是我嚷得很厉害。
婚礼后,做新郎得林请客,大家去K歌。如同众所周知的,房间都满了。不过还有一间最大的,里面的设备稍微有点问题。需要修理一下,大家不愿再跑了,就定下了这间。房间很大,沙发很软。大家逮着没有伴奏的麦克风show着,工作人员修着机器——貌似其实是电脑坏了。大家都沉浸在婚礼的感动中。我昨晚熬夜,缺觉,找了个角落的沙发睡去了。迷迷糊糊间听到alan讲自己的感情故事,我顿时睡意全无,树着耳朵听,alan说她很小的时候喜欢上一个男孩,但终于有缘无分。前些日子见面,却还能做朋友。突然金导大叫我的名字,我迷迷糊糊装刚睡醒。电脑彻底修不好了,已经下午5点了。空房很多,大家就换了一间小包。两间包间的路上,老赵一把拍住我,低声说道:“你真是个HanZi”。
在小包中,我感觉自己的喉咙哽咽着。楠一如既往的想做麦霸,遭抗议,知趣的把机会多留给了别人。alan和金导快乐的唱着,林和新娘一会也来了,show了两首幸福的对唱。强和他的女友躲在里间,继续做小猪——以致于后来几天金导对他很大意见。我终于还是被感染了,咳清沙哑的嗓子。一起加入了幸福的歌声。
人生得意需尽欢,莫使金尊空对月。貌似所谓幸福,就是一群相投的人聚在一起,如此而已。幸福无需奢求太多的。
离别的时刻还是到来了,新郎用车把我们送到车站附近,就被大家赶了回去。大家稍微光了一下传说中的开封夜市,吃了几笼灌汤包作为晚餐,便定下了块10点的火车打道回郑州。没有定到座位,大家有些担心。尤其是我和郭杰,在来的路上深深体味到了站着的滋味。
火车终于进站了,接着传来惊呼声。一部分人开始飞跑起来,其中也包括我们——十一期间的不可思议出现了——空车。车上实在空的可以,只要愿意的话,一个人躺一个位置都绰绰有余。路上,我和alan、老赵用手机上的蓝牙相互传着铃声。楠和金导、郭杰在谈论国庆后的上班安排。就这样,带着一路欢笑到了郑州。
到了郑州站,大家简单话别之后就分开了。我没地方住就去了楠家。又可以和楠睡一张床了^_^(为避免误会,这里解释一下,楠是男的。而我,也没有什么特别的嗜好)。楠show了他单位发的IBM X30,其轻薄(指本)震撼了我。下载买本非12英寸不选,神舟什么的,就无视了吧。
随后的几天,和郭杰逛了一下郑州的电脑卖场。为我的SONY T11配了个1G内存。然后就返程了。
=================我是分隔线=======================
原本是想写本本内存的检测心得,没想到写着写着就变成了记十一的一次同学聚会。看来幸福的事更容易流注于笔端。就这样吧,为了幸福,作文以记之。至于内存和DocMem,就下回再写吧。

2006年03月04日

1. man

man,即 manunal,是 UNIX 系统手册的电子版本。根据习惯,UNIX 系统手册通常分为
不同的部分(或小节,即 section),每个小节阐述不同的系统内容。目前的小节划分如下:

  1. 命令:普通用户命令
  2. 系统调用:内核接口
  3. 函数库调用:普通函数库中的函数
  4. 特殊文件:/dev 目录中的特殊文件
  5. 文件格式和约定:/etc/passwd 等文件的格式
  6. 游戏。
  7. 杂项和约定:标准文件系统布局、手册页结构等杂项内容
  8. 系统管理命令。
  9. 内核例程:非标准的手册小节。便于 Linux 内核的开发而包含
2005年04月03日

打开KDE的“控制中心”。
点开“区域于辅助功能”->“KHotKeys”
点击“新建动作”按钮。

然后设置这个“新建动作”
  在“常规”标签下
    “动作名”:随便填。比如“LumaQQ弹出消息”
    “动作类型”:“键盘快捷方式->键盘输入(简单)”
    “禁用”:前面的勾可不能勾上。
    “注释”:随便填填就行。
  在“键盘快捷键”标签下
    点击“选择键盘快捷键”下面的按钮,在弹出“配置快捷键”窗口后,按键盘上的Crtl+Alt+Z(也可以按其它的键,或其它的组合键)。
  在“键盘输入设置”标签下
    点击“窗口”框里的“新建”按钮,在菜单里选择“简单窗口……”
    在弹出的“窗口细节”窗口中
        “注释”:随便填。比如LumaQQ。
        “窗口标题”:选“包含”,填“SWT”

打开KDE的“控制中心”。
点开“区域于辅助功能”->“KHotKeys”
点击“新建动作”按钮。

然后设置这个“新建动作”
  在“常规”标签下
    “动作名”:随便填。比如“LumaQQ弹出消息”
    “动作类型”:“键盘快捷方式->键盘输入(简单)”
    “禁用”:前面的勾可不能勾上。
    “注释”:随便填填就行。
  在“键盘快捷键”标签下
    点击“选择键盘快捷键”下面的按钮,在弹出“配置快捷键”窗口后,按键盘上的Crtl+Alt+Z(也可以按其它的键,或其它的组合键)。
  在“键盘输入设置”标签下
    点击“窗口”框里的“新建”按钮,在菜单里选择“简单窗口……”
    在弹出的“窗口细节”窗口中
        “注释”:随便填。比如LumaQQ。
        “窗口标题”:选“包含”,填“SWT”

2004年08月12日

在blog中发现了一些讨论“处女”的文章。
不可否认,“处女情节”是客观存在的,
不少人都有。
为什么呢?
为什么男人那么在乎处女呢?

偶然发现了一片文章,转载于下,应该说明了一些问题。

处女的意义     我曾经对女生是否是处女的问题不很关心,不过最近一两年来随着身边的朋友一个一个的谈论这个话题,我也情不自禁的陷入其中.  其实很多男人都弄不明白到底是否有处女情节,如果爱上一个非处女怎么办.  爱,就要爱一个处女. 

女人总是喜欢问男人\"你到底喜欢我什么\",很简单,就是喜欢女人是处女. 

其实处女已经不单单的是处女了,处女已经变成了一种情节了. 

这个世界上到底有几个男人作为处男而不在乎自己的女友、未来的妻子是不是处女? 

男人爱处女,这是事实,男人到底爱处女什么,处女对于男人来说有十足非凡的意义. 

男人到底爱女人什么?

 女人到底有什么地方值得男人去珍惜. 

男人爱女人,爱女人的可爱,爱女人的美丽.女人什么最吸引男人,女人第一次收到情书时的  亢奋,第一次和男生约会时左顾又看怕被人发现的紧张,女人第一次拉男人手时的害羞,女  人第一次靠在男人肩膀上时的那种含蓄,女人第一次接吻时的忐忑,女人第一次和男人爱抚  时的半推半就,女人第一次做新娘时的渴望与不安,女人第一次和男人相拥而卧时的含情脉  脉和欲说还休,女人第一次第一次和心爱的人长相厮守时的浪漫情怀.女人和男友在一起脸  红的害羞,女人为了心上人打吃醋高翘的小嘴,男人为女人制造惊喜时女人的微笑,这一切的  一切,都是女人吸引男人的地方,爱情,为什么伟大,爱情就因为这些而伟大,要是没有这  些,男人凭什么去爱女人啊.

 男人爱女人的含蓄.爱女人的如水的温柔,爱女人的娇羞.这些无数无数女人值得男人爱的第  一次,只有处女才能给予,有的女人总是说男人爱处女不知爱处女什么,不就是是爱那张膜吗  ,男人是爱这张膜吗,如果一个女人总是和别的男人发生边缘性行为,除了性交什么都做过  了,那这张膜对男人来讲又有什么意义呢?  男人爱这张膜,爱的更是这张膜下面掩藏的无数多个第一次,爱的是无数多个第一次下的女  人千姿百媚.

 都说做新娘时的女人最美丽,为什么,因为做新娘时女人把自己无数的含蓄,无数的娇羞,无  数的忐?无数的渴望,无数的激动都给了男人,让自己在这无数的第一次中变的无限美丽  ,在新婚之夜让一个男人去把自己征服,和自己的爱人一起去探索人生一片为开拓的疆域,  这些都会让男人终生难忘.

 如果女人少了这些,无疑是在一点一点减少的美丽,在拉了N个男生的手之后,把紧张丢掉了  ,初吻没了以后,又卸去了娇羞,靠了很多肩膀后,把幸福洋溢的感觉又丢掉了,女人一个  一个的把自己的美丽都渐渐丢掉,男人还是能够接受,娇羞没了,还有含蓄,可爱没了,还有  美丽,男人还可以爱女人的忐忑,爱女人把第一次献给自己的伟大.  不过,一项一项的丢,价值也就慢慢的减少了,爱情也会慢慢的变淡了,甚至变的爱情也从  无穷的价值变到可以到具体衡量的时候,爱情,于是就有了期限.

 有的女人总是在抱怨,自己年轻的时候如花似玉,嫁给了一个穷小子,可是到了四十岁,却栓  不住他了.这很简单,女人如果只用20岁的美丽,30岁的风韵去吸引男人,那么也只能到40岁  ,美丽是可以用金钱去衡量的,如果有了钱,做做美容,丑女也会变成天鹅.那么既然你用有  价的美丽去维系男人,那你的爱情必然是有期限的.

 男人心里也有一杆称,女人为自己付出了多少自己完全知道,当女人不在美丽的时候,男人  会想\"当我一无所有的时候,我的女人把自己最美丽的东西毫无保留的献给了我,我不能做对  不起她的事\".当女人不再容光焕发的时候,男人会回忆起以前和女人的光辉岁月,当女人已  不再含蓄的时候,男人会想到曾经的这个女人也是娇羞万分,当女人不再有美丽的时候,男  人会回想起和女人曾经一起走过的艰难岁月,曾经一起闯荡的奋斗历程.  男人会回想起曾经两个人吃一碗面时的相濡以沫,曾经在无家可归时,共住天桥时那种浪迹  天涯的浪漫,曾经为了结合而以世俗对抗到底的义无返顾,男人这时会欣然的想到,自己心爱  的女人啊,把她最宝贵的东西献给了自己,现在她已经不再美丽了,但是男人会一样的爱  女人,一样的怜惜她,这就是爱情的无限价值了.

 一分付出,总有一分收获,所以当20岁的美女还在算着如何安排和不同的男生约会的日期  的时候,当30岁的风韵少妇手里拿着大款给的无数钞票的时候,她们得到了风流,金钱,让  人羡慕的目光,让人体面的身份,但是同时上天也很公平,你既然得到了这么多,美丽,金  钱,地位,那么当然不能得到你所期待的那种真真切切的爱情,那种刻骨铭心的沧海桑田. 

男人得到了天下还不是为了女人,这就是为什么有人不爱江山爱美人,如果一个男人连女人  这些宝贵的东西都没得到,那么这个男人是不是一个失败的男人呢,如果一个男人这一辈子  连什么是处女,什么是处女膜都永远不知道,他会不会永远死不幂目呢.
 
对于女人来说,初吻的唇就像珍珠,再吻最多就是花生,第一次把手给人拉是玉,再拉就是  布了,没摸过的胸部就是金,摸过了就成了草,处女把最宝贵的东西献出后再来也是可以用  钱来衡量的了,如果你们把珍珠,宝玉献给所爱的人,那么上天会很公平的赐给你伟大的爱  情,如果你付出的只是花生,棉布,那么获得的就只能是令你失望的所得. 

这不是我一个人的想法,只不过我今天是把处女情节的意思稍微的诠释清楚了,对于男人,

 处女的价值也就在这.


刻录各种启动光盘的经验小结(转载)
作者:未知

Climbing 注:本帖的第一个版本写成于2002年7月22日(我在无忧启动论坛注册的时间是2003年7月17日),现在已经是2003年10月6日了,这一年多来 光盘启动技术及相关软件的发展只能用突飞猛进来形容,相形之下这篇帖子也显得有些过时了,帖子中介绍的很多东西也发生了变化。虽然如此,本帖作为学习光盘 启动的入门教程还是没有问题的,希望有志于学习光盘启动的新手们从这篇帖子开始你的光盘启动学习之旅。为了方便大家学习和参考,我于2003年10月6日 又对本帖进行了整理,修正了一些链接,并增加了一些相关软件的下载链接,但由于工程浩大,没有对帖子的内容进行大的改动,否则还不如发一篇新帖呢。

修改后检查了一下,发现下面帖子中很多链接(包括链接到本论坛的一些链接)都已经不能使用了,不过这些文章大部分都还是有的,具体链接请参考本论坛“光盘启动区”的置顶帖:本站所有的教学文章及下载资源清单列表。

(2003年10月6日于无忧启动论坛)

================================================================
原帖内容如下(重新修改于2003年10月6日,就算是第2版吧):
================================================================

研究了一周的时间,对于刻录各种启动光盘的原理与方法终有小成,在此期间,我主要参考了http://www.nu2.nu(英 文网站,网站主人是一个 32岁的荷兰人,名字叫Bart,喜欢搞自由软件,他的网站上很多是他写的免费软件,他使用的也全是免费软件。这个人写的软件十分厉害,可以说国内论坛上 那些制做启动光盘的技术基本上来源于这个网站)、http://bbs.wwwfox.net的“无忧社区”的“光盘启动区”(也就是本论坛了:)中的部 分帖子。闲话少说,咱们言归正传。

一、启动光盘的形式(从原理上来说)共有三种。

1、从光盘启动时模拟软驱(或者硬驱)的一种启动光盘,最常见的是模拟软驱(A:),这种光盘的引导区存储的一般为一张软盘的映象文件(可以使用 Winimage软件制做,类似于HD-COPY的制做方法,但hd-copy制做的映象不能用于制做光盘启动映象文件)。基本制做方法(以Nero Burning-Rom为例):软盘映象 + CD-ROM(Boot)即可。

2、直接光盘启动形式的光盘:引导区存储的是一小段程序,这段程序直接运行光盘上的其它程序。例如Windows NT/2000/XP的安装光盘均为这种形式。

3、比第一种方法更进一步:直接从光盘启动,但是有多个引导映象文件(Multiple boot images),这种规范名为El Torito multiple boot image。前面的两种实际上属于同一种规范格式:El Torito single boot image。这里的这一段文字是我从http://www.nu2.nu/bootcd这一页上抄来的,里面有更详细的内容,可以自己参考。但是这种格式 需要主板BIOS支持,事实上是很多主板不支持这种启动光盘(现象就是只能从第一个启动映象启动)。

2003.10.06新增:Bart’s way to create bootable CD-Roms(用Bart的方法制做各种启动光盘,由Climbing翻译)

该页面的英文版原始链接:http://www.nu2.nu/bootcd。

二、具体的启动光盘刻录方法

了解多少规范都是没有用处的,关键是如何刻出一张符合要求的启动光盘,在这里我们使用一个叫mkisofs的软件(原来运行在unix系统下用来制做 iso文件的工具),有了iso,我们就可以直接使用刻录软件将iso文件刻录到光盘上(例如:Nero的Burn Image功能)。mkisofs的软件下载位置:http://climbing.minidns.net/software/mkisofs- 1.15a40.rar(这里提供的下载版本是 wwashington 在水木清华的 dfbb 以及COMMAN 等网友的工作基础上,深入探索、不断完善的一个版本。现在最新版的 mkisofs 可以通过加参数的方式来支持 dos 和 win 下面的 GBK 中文代码,而且它可以做基于MD5 技术的文件识别,实现 ISO 文件的空间优化,即节省存储空间。)在具体刻录过程中,建议使用Bart在http://www.nu2.nu/bootcd页面中提供的cdrpack + 相应光盘的addon中build.mak方式+最新版支持中文及优化的mkisofs(替换掉Bart提供的即可)方式来制做iso文件(注:现在 Bart提供了更新的bcd 1.1.1来代替cdrpack)。不推荐使用网上提供的Make_ISO等软件(操作起来太复杂,而且没有说明),实际上Bart已经提供了大部分的 mkisofs的参数,自己只需要少量修改部分参数即可(请参考Bart刻录光盘软件包中的bin\build.mak,bin\global.mak, cds\diskemu\build.mak 等文件)。在下面的具体刻录过程中,我会将相应的命令参数写出来(仅供参考)。这一段写的比较乱,实际上就是请你参考http: //www.nu2.nu/bootcd那一页的内容将需要下载的东西下载下来,然后按照页面上的说明将一张张Bart推荐的启动光盘实例刻录出来(最好 将刻录机关掉,只是用他提供的软件包中mkisofs及配置工具制做出光盘的iso映象),通过研究他的说明,你可以明白mkisofs这个命令的用法, 从而进一步通过定制其参数来制做出适合自己需要的启动光盘。

在继续刻录操作之前,需要推荐几个软件:第一个是一个软件组合:VMware workstation(在2000/xp环境下虚拟机器) + Daemon Tools(虚拟光驱)  + RAMDiskNT(虚拟软驱),这些软件可以到华军软件园搜索或者到www.superdown.com上找,这个软件组合可以在一个相当真实的虚拟环 境中测试你制做的iso镜像文件及软件映象文件是否可用(并进一步配置等),请参考文章:http: //www.wwwfox.net/testiso.htm。这篇文章中没有提及RAMDiskNT,但Daemon Tools虚拟的光驱可以作为虚拟机器中的物理光驱使用,受此启发,那么RAMDiskNT虚拟的软驱也可以作为虚拟机器中的物理软驱使用。另外需要用到 的软件为:WinISO(iso镜象文件编辑软件)、Winimage(软盘及光盘映像制做软件)、UltraEdit(16进制及文本文件编辑器)、 isobuster(支持很多光盘镜像格式)、Nero Burning-Rom(功能强大的刻录软件)等,这些软件在网上都比较好找,同样可以在上面提供的两个站点找。至于这些软件的注册或者破解,请搜索 http://astalavista.box.sk。

作者注:上面提到的主要软件在本帖末尾都将提供可用的下载链接(2003年10月6日)

1、第一种光盘的制做(假设我们要将Win98的启动盘做入光盘,光盘启动时就好象使用Win98启动盘一样),两种方法:
(1) 使用Nero直接制做:先准备一张Win98启动盘,然后启动Nero,选择光盘的格式为CD-ROM(Boot),将98启动盘放入A:,然后在 “Boot”标签选择使用“A:\”(或者将98启动盘使用Winimage制做成ima的镜像格式(不要使用IMZ),然后在“Image file”处选择那个映象文件即可->New,后面的操作与刻录正常的数据光盘相同。

(2) 使用mkisofs命令来制做启动镜像iso文件,先用Winimage将98的启动盘制做成的映象文件名为w98se.ima。假设我们工作在D:\ cdr目录(要求该盘空闲空间至少在1.4G以上),在cdr目录下bin目录中存放mkisofs程序包,在cdr\iso目录中存放要制做的iso文 件(命名为mycd.iso),在cdr\disk1目录中存放需要刻录到光盘上的所有文件,那么把w98se.ima文件放到disk1目录下,同样, 你要把disk1目录当做你的光盘的根目录来组织你要刻录到光盘(即制做的iso文件)中的数据。组织好后,在cdr目录下运行如下命令:
bin\mkisofs.exe -J -N -l -no-iso-translate -relaxed-filenames -gbk4dos-filenames -gbk4win-filenames -optimize -v -volid “W98SEBOOT” -p “Climbing” -P
“Little Stone Corp.” -A “Created ‘Barts way’ Bootable CD-ROM using MKISOFS” -eltorito-boot w98se.ima -hide w98se.ima -hide-joliet w98se.ima -hide boot.catalog -hide-joliet boot.catalog  -o iso/mycd.iso disk1

命令中各参数解释如下(我不明白什么意思的就使用英文原来的解释):
-J制做Joliet文件系统,好象比iso9660的优点就是支持长文件名。
-N Omit version numbers from ISO9660 file names. This violates the ISO9660 standard, but no  one  really uses the version numbers anyway.
-l(-full-iso9660-filenames) Allow full 31 character filenames for ISO9660 names,好象就是允许文件名有31个字符
-no-iso-translateDo not translate illegal ISO characters ‘~’, ‘-’ and ‘#’ (violates ISO9660),不转换文件名中的~、-、#等符号
-relaxed-filenames允许文件名中使用小写字符(不符合ISO9660规范)
-gbk4dos-filenames允许dos下的中文文件名(不符合ISO9660规范)
-gbk4win-filenames允许windows下的中文长文件名(不符合ISO9660规范)
-optimize优化iso文件存储(即所有目录中相同的文件只存储一份儿到iso文件中,节省存储空间,对于制做win2000三合一光盘特别有用)
-v(-verbose),在命令执行时显示详细的信息
-volid “W98SEBOOT”设置光盘短卷标,本例中设置为W98SEBOOT(即将光盘放入光驱后在光驱盘符上显示的卷标)
-p “Climbing”光盘制做人(自由设置)
-P “Little Stone Corp.”光盘制做公司(自由设置)
-A “Created ‘Barts way’ Bootable CD-ROM using MKISOFS”设置应用ID(Application ID),自由设置
-eltorito-boot w98se.ima(-b)启动映象文件名(这一项设置十分关键,光盘能否启动,全在这一步)
-hide w98se.ima ISO9660隐藏文件,在dos下不可以看到,但在Windows下可以看到
-hide-joliet w98se.imaJoliet隐藏文件,无论在什么系统下均无法看到这个文件(在isobuster中可以看到)。
-hide boot.catalog -hide-joliet boot.catalog隐藏boot.catalog文件,这个文件好象在光盘上就是一个2K长的空白文件,由mkisofs命令自动生成。
-o iso/mycd.iso要生成的iso文件的存放路径及文件名(即destination)。
disk1 [other path\filename ...]指定用来放入iso文件中的内容,可以有多个(即Source)。

mkisofs命令英文版的man page链接为:http: //www.fokus.gmd.de/research/cc/glone/employees/joerg.schilling/private/man/mkisofs -1.13.html,注意,这个英文版的使用说明中不包括-gbk4dos-filenames、-gkb4win-filenames、- optimize这三个参数,因为这三个参数是由国内高手wwashington在英文原版的mkisofs源码的基础上修改而来的,也就是说,Bart 提供的刻录光盘制做包中包括的mkisofs并不支持这三个参数,在实际制做光盘iso文件时,务必使用wwashington的修改版本(即: http://climbing.minidns.net/software/mkisofs-1.15a40.rar)。在后面提供的mkisofs命 令行参数中,与上面重复的我不再解释,只解释那些不同的。

2、第二种光盘的制做:以Windows XP的启动光盘为例,目录设置同例1。制做XP启动光盘时(2000、NT4的启动光盘与XP的制做方法完全相同)需要一个关键的文件为 w2ksect.dat文件,这个文件就是光盘的引导区文件,这个文件可以从Bart在http://www.nu2.nu/bootcd页面上提供的 wxp_addon.zip(http://www.nu2.nu/nu2files/wxp_addon.zip)文件中取得,这个文件在无忧启动论坛 上也叫w2ksect.bin,我们在下面的命令行中将其取名为wxpsect.bin,另外在i386目录下有一个bootfix.bin,这个文件的 作用就是如果xp的安装光盘发现C盘是可引导的,那么用光盘启动时就会提示Press any key to boot from CD,然后等待五秒,如果用户不按键,就会启动C盘上的系统。将wxpsect.bin文件放在disk1目录下,然后将xp光盘上的所有内容也放到 disk1目录下,尤其是根目录下有bootfont.bin,WIN51IP,win51等,关于在disk1目录下放哪些文件,请参考无忧启动论坛上 的这些技术文章(http://www.wwwfox.net/win2k.htm,http: //www.wwwfox.net/win2k1.htm,http://www.wwwfox.net/win2k2.htm,http: //www.wwwfox.net/winxp.htm),这些文章中都是用CDIMAGE命令来制做iso文件,我只能说mkisofs是比 cdimage功能更强的制做iso文件的工具,但其用法要比cdimage复杂上百倍。当然,你也可以在disk1目录下放置你自己定义的内容,例如 XP的所有补丁程序等(估量着不要刻不下就行了)。文件组织好后(怎么算组织好,请参考前面的文章或者Bart的介绍)。在cdr目录下执行下面的命令:
bin\mkisofs.exe -J -N -l -no-iso-translate -relaxed-filenames -gbk4dos-filenames -gbk4win-filenames -optimize -v -volid “WINXPPROCHS” -p “Climbing” -P “Little Stone Corp.” -A “Created ‘Barts way’ ISO using mkisofs” -no-emul-boot -eltorito-boot wxpsect.bin -hide wxpsect.bin  -hide boot.catalog -hide-joliet wxpsect.bin -hide-joliet boot.catalog  -o iso/myxpcd.iso disk1

这一次多了一个参数就是:
-no-emul-boot非模仿软驱或者硬盘启动方式,即区别于使用软驱映像启动方式,光盘启动时不再模仿从软驱启动,而是直接执行wxpsect.bin文件来进入安装程序。

3、第三种光盘的制做,即多映像文件启动方式。在实际使用中我并没有试验制做这种光盘,但Bart提供了这种光盘的制做方法,大家可以参照他的说明制做一张来试试,在mkisofs中与启动映象相关的参数如下(其它参数略,因为我没有试验,大体应该与上面的设置相同):
-hide boot.catalog -hide-joliet boot.catalog\
-eltorito-boot cdrom.img -hide cdrom.img -hide-joliet cdrom.img \
-eltorito-alt-boot \
-eltorito-boot msnet.img -hide msnet.img -hide-joliet msnet.img \
-eltorito-alt-boot \
-eltorito-boot scsitool.img -hide scsitool.img -hide-joliet scsitool.img \
-eltorito-alt-boot \
-eltorito-boot cpuid.img -hide cpuid.img -hide-joliet cpuid.img

上面命令中的“\”表示命令行连接符的意思,也就是说这些命令应该都在一行上,在win2000的dos提示符下使用时请去掉这些字符,因为win2000并不支持这种写法。

这些参数中一个关键的参数即为:-eltorito-alt-boot,而cdrom.img, msnet.img, scsitool.img, cpuid.img是多个启动软盘的映像文件。

与这种制做方法类似的另一种制做方法请参考无忧启动论坛的这篇文章:http://www.wwwfox.net/nin1.htm。

需要注意的是:这种多个启动映象文件的格式可能不会被某些主板支持,并不推荐这种做法。

4、集大成者,也是Bart给我们贡献最大的多映象启动光盘制做方法(此种方法绝对不会产生主板bios不支持的问题):loader.bin + diskemu1x.bin + diskemu.cmd方式。这种方式是我所知的最先进的方式,也是无忧启动论坛及国内启动光盘制做论坛目前使用最多的技术,与此类似的技术还有 isolinux/memdisk技术(注:现在这种多映像光盘启动程序是太多了,例如BootScript、BCDW等,2003.10.06),其原 理相同,Bart在网站上有详细介绍,请自行参考。

先说原理:所有的主板都支持一个启动文件(Single Boot image)的启动方法,那么可以做一个小的系统供启动程序调用(由这个系统来模拟一个BIOS),而这个模拟的BIOS系统可以接着调用其它的映象文件 来启动系统,你可以同时将很多启动映象文件刻入光盘(包括模拟软驱的、不模拟软驱直接启动的),然后在光盘启动时,由这个模拟的BIOS系统进一步调用各 个启动映象文件来启动系统。

Bart提供的loader.bin就是那个光盘启动程序,而loader.bin的目的就是将那个模拟的BIOS系统diskemu1x.bin由光盘 调入并运行,而diskemu.cmd文件则用来配置开机启动时有哪里启动映象文件可以供diskemu1x.bin文件来进一步调用。光盘启动时类似的 画面如下:

这种方法经过国内高手们的改进,启动菜单可以支持中文(并且可以通过上、下键选择相应的菜单条),请参考帖子:http: //www.wwwfox.net/cgi-bin/topic.cgi?forum=2&topic=4550&show=0 及 http://www.wwwfox.net/cgi-bin/topic.cgi?forum=2&topic=2464,事实上有很多版本的 中文菜单制做程序(有的甚至可以做子菜单),方法较复杂,咱们还是先以Bart的方法为主,搞清楚状况后再进行美化。

使用mkisofs制做Bart方式的多启动光盘的方法:首先将loader.bin、diskemu1x.bin 及diskemu.cmd文件(这些文件可以从Bart的网站上获得:http: //www.nu2.nu/nu2files/single_diskemu_addon.zip)放到disk1目录下,然后可以在disk1\ BootImg目录下存放各个需要加入启动菜单的映象文件,然后再用文本编辑器编辑diskemu.cmd文件,定义出适合于自己光盘的启动菜单供 diskemu.bin使用。下面是我的制做实例:
bin\mkisofs.exe -J -N -l -no-iso-translate -relaxed-filenames -gbk4dos-filenames -gbk4win-filenames -optimize -v -volid “WinXpProCHS” -p “Climbing” -P “Little Stone Corp.” -A “Created ‘Barts way’ Boot CD-ROM using MKISOFS” -no-emul-boot -eltorito-boot loader.bin -hide loader.bin  -hide boot.catalog -hide-joliet loader.bin -hide-joliet boot.catalog  -o iso/diskemucd.iso disk1

diskemu.cmd文件的内容如下:
;
; diskemu.cmd sample file v0.8
; Created by Bart Lagerweij
;
; Some notes:
; – do not use tabs in this file! Use spaces!
; – the key “tags” like “esc”, “entr”, … must be lowercase!
;
; [*** Added by customer users ***]
; diskemu.com for Windows XP Pro Setup Disc
; Modified by Little Stone on 2002.07.19
; E-mail: littlestone@netease.com
:start
cls
print
print ——————————————————————————
print Bootable CD main menu                                http://www.nu2.nu/diskemu
print ——————————————————————————
print F1=Help
print
print
print                     0)   Windows XP Pro Setup
print                     1)   Windows 98SE Boot Disk
print                     2)   Bart Network Boot Disk
print                     3)   Bart CD-ROM  Boot Disk
print                     4)   NT Password Editor
print                     5)   Bart SCSI Utilities
print
print                     a)   Boot floppy [A:]
print                     c)   Boot first harddisk [C:]
print                     q)   Quit to command prompt
print                     r)   Reboot
print
print Hit the key of choice(Timout=20sec, Default=0):
:mainkey
; timeout is 20 seconds, default key is key 0
getkey 20 0
onkey 0 goto winxp
onkey 1 goto w98se
onkey 2 goto net
onkey 3 goto cdrom
onkey 4 goto editpasswd
onkey 5 goto scsi
onkey a boot 0
onkey c boot 80
onkey q quit
onkey r reboot
onkey esc goto winxp
onkey spc goto winxp
onkey entr goto winxp
onkey f1 goto help
onkey 0×1C0D goto winxp
; When no key found…
goto winxp
;*** HELP ***
:help
cls
print HELP
print —-
print
print Here you can type help for your customers
print
print Press any key to return to main menu
getkey
goto start

;***Start Windows XP Boot Installation***
:winxp
print Start to install the Windows Xp in Normal Mode
cd BootImg
run wxpsect.bin
getkey
goto start
;*** Boot the Bart DOS network Boot Image ***
:net
print Starting Bart Network on DOS Boot
cd BootImg
run net.ima
getkey
goto start
;*** Boot Win98SE DOS Image ***
:w98se
print Boot Windows 98 SE dos prompt
cd BootImg
run w98se.ima
getkey
goto start
;*** Boot Bart CD-ROM Boot Disk ***
:cdrom
print Boot Bart CD-ROM Boot Disk
cd BootImg
run cdrom.ima
getkey
goto start
;*** Boot Bart SCSI-Tool Boot Disk ***
:scsi
print Boot Bart SCSI-Tool Boot Disk
cd BootImg
run scsi.ima
getkey
goto start
;*** Starting Edit Windows NT/2000 Adminitrator Password Tools
:editpasswd
print Edit NT or Windows 2000 Administrator Password
cd BootImg
run ntpass.ima
getkey
goto start
;
; EOF

这种光盘的启动效果如何,自己做一张就知道了,因为我不方便贴图片,这里就不演示了,有一个详细的说明帖子(来自www.2hacks.org): http://www.2hackers.org/classroom/cdr/2hackers.cd.htm,这个帖子里讲的比较详细,自己参照着来 就好了。

唉,写到这里发觉自己真是白痴,费了这么大的力气,不知道有没有把问题说清楚。在这篇总结文章里引用了很多高手的著作,无法一一注明的,请各位高手原谅, 特别感谢Bart高手、wwashington大虾(没有他的中文版mkisofs,一切都是白扯)、无忧社区CEO wwwfox大虾、萧十 and 一郎两位黑客、xhwfq(中文启动菜单高手)等大虾的帮助与指导,文章写的很仓促,是在UltraEdit中完成的,因此无法插入可以直接访问的链接与 图片,只是给出了URL地址。而且定有很多错误之外,望各位高手批评指正,再次感谢!

附1:diskemu.cmd文件中可以使用的命令请参考:http://www.nu2.nu/diskemu网页上的介绍,这些命令同样可以用在diskemu1x.bin提供的命令提示符下。
附2:mkisofs(wwashington高手制做版)命令行各参数的简要说明
Usage: mkisofs [options] file…
Options:
-nobak                      Do not include backup files
-no-bak                     Do not include backup files
-abstract FILE              Set Abstract filename
-A ID, -appid ID            Set Application ID
-biblio FILE                Set Bibliographic filename
-cache-inodes               Cache inodes (needed to detect hard links)
-no-cache-inodes            Do not cache inodes (if filesystem has no unique unides)
-check-oldnames             Check all imported ISO9660 names from old session
-check-session FILE         Check all ISO9660 names from previous session
-copyright FILE             Set Copyright filename
-debug                      Set debug flag
-b FILE, -eltorito-boot FILE
                             Set El Torito boot image name
-eltorito-alt-boot          Start specifying alternative El Torito boot parameters
-B FILES, -sparc-boot FILES Set sparc boot image names
-G FILE, -generic-boot FILE Set generic boot image name
-sparc-label label text     Set sparc boot disk label
-c FILE, -eltorito-catalog FILE
                             Set El Torito boot catalog name
-C PARAMS, -cdrecord-params PARAMS
                             Magic paramters from cdrecord
-d, -omit-period            Omit trailing periods from filenames (violates ISO9660)
-dir-mode mode              Make the mode of all directories this mode.
-D, -disable-deep-relocation
                             Disable deep directory relocation (violates ISO9660)
-file-mode mode             Make the mode of all plain files this mode.
-f, -follow-links           Follow symbolic links
-gid gid                    Make the group owner of all files this gid.
-graft-points               Allow to use graft points for filenames
-help                       Print option help
-hide GLOBFILE              Hide ISO9660/RR file
-hide-list FILE             File with list of ISO9660/RR files to hide
-hidden GLOBFILE            Set hidden attribute on ISO9660 file
-hidden-list FILE           File with list of ISO9660 files with hidden attribute
-hide-joliet GLOBFILE       Hide Joliet file
-hide-joliet-list FILE      File with list of Joliet files to hide
-hide-joliet-trans-tbl      Hide TRANS.TBL from Joliet tree
-hide-rr-moved              Rename RR_MOVED to .rr_moved in Rock Ridge tree
-gui                        Switch behaviour for GUI
-i ADD_FILES                No longer supported
-input-charset CHARSET      Local input charset for file name conversion
-output-charset CHARSET     Output charset for file name conversion
-iso-level LEVEL            Set ISO9660 conformance level (1..3)
-J, -joliet                 Generate Joliet directory information
-jcharset CHARSET           Local charset for Joliet directory information
-l, -full-iso9660-filenames Allow full 31 character filenames for ISO9660 names
-max-iso9660-filenames      Allow 37 character filenames for ISO9660 names (violates ISO9660)
-L, -allow-leading-dots     Allow ISO9660 filenames to start with ‘.’ (violates ISO9660)
-log-file LOG_FILE          Re-direct messages to LOG_FILE
-m GLOBFILE, -exclude GLOBFILE
                             Exclude file name
-exclude-list FILE          File with list of file names to exclude
-pad                        Pad outout to a multiple of 32k (default)
-no-pad                     Do not pad output to a multiple of 32k
-M FILE, -prev-session FILE Set path to previous session to merge
-N, -omit-version-number    Omit version number from ISO9660 filename (violates ISO9660)
-new-dir-mode mode          Mode used when creating new directories.
-force-rr                   Inhibit automatic Rock Ridge detection for previous session
-no-rr                      Inhibit reading of Rock Ridge attributes from previous session
-no-split-symlink-components
                             Inhibit splitting symlink components
-no-split-symlink-fields    Inhibit splitting symlink fields
-o FILE, -output FILE       Set output file name
-O, -optimize               Optimize for iso space(md5 technology)
-path-list FILE             File with list of pathnames to process
-p PREP, -preparer PREP     Set Volume preparer
-print-size                 Print estimated filesystem size and exit
-P PUB, -publisher PUB      Set Volume publisher
-quiet                      Run quietly
-r, -rational-rock          Generate rationalized Rock Ridge directory information
-R, -rock                   Generate Rock Ridge directory information
-sort FILE                  Sort file content locations according to rules inFILE
-split-output               Split output into files of approx. 1GB size
-sysid ID                   Set System ID
-T, -translation-table      Generate translation tables for systems that don’t understand long filenames
-table-name TABLE_NAME      Translation table file name
-ucs-level LEVEL            Set Joliet UCS level (1..3)
-udf                        Generate UDF file system
-uid uid                    Make the owner of all files this uid.
-U, -untranslated-filenames Allow Untranslated filenames (for HPUX & AIX – violates ISO9660). Forces -l, -d, -L, -N, -relaxed-filenames, -allow-lowercase, -allow-multidot
-relaxed-filenames          Allow 7 bit ASCII except lower case characters (violates ISO9660)
-gbk4dos-filenames          Allow 8 bit ASCII GBK standard code characters (violates ISO9660)
-gbk4win-filenames          Allow 8 bit ASCII GBK standard code characters (violates ISO9660)
-no-iso-translate           Do not translate illegal ISO characters ‘~’, ‘-’ and ‘#’ (violates ISO9660)
-allow-lowercase            Allow lower case characters in addition to the current character set (violates ISO9660)
-allow-multidot             Allow more than one dot in filenames (e.g. .tar.gz) (violates ISO9660)
-use-fileversion LEVEL      Use file version # from filesystem
-v, -verbose                Verbose
-version                    Print the current version
-V ID, -volid ID            Set Volume ID
-volset ID                  Set Volume set ID
-volset-size #              Set Volume set size
-volset-seqno #             Set Volume set sequence number
-x FILE, -old-exclude FILE  Exclude file name(depreciated)
-hard-disk-boot             Boot image is a hard disk image
-no-emul-boot               Boot image is ‘no emulation’ image
-no-boot                    Boot image is not bootable
-boot-load-seg #            Set load segment for boot image
-boot-load-size #           Set numbers of load sectors
-boot-info-table            Patch boot image with info table
-z, -transparent-compression
                             Enable transparent compression of files
-hfs-type TYPE              Set HFS default TYPE
-hfs-creator CREATOR        Set HFS default CREATOR
-g, -apple                  Add Apple ISO9660 extensions
-h, -hfs                    Create ISO9660/HFS hybrid
-H MAPPING_FILE, -map MAPPING_FILE
                             Map file extensions to HFS TYPE/CREATOR
-magic FILE                 Magic file for HFS TYPE/CREATOR
-probe                      Probe all files for Apple/Unix file types
-mac-name                   Use Macintosh name for ISO9660/Joliet/RockRidge file name
-no-mac-files               Do not look for Unix/Mac files (depreciated)
-boot-hfs-file FILE         Set HFS boot image name
-part                       Generate HFS partition table
-cluster-size SIZE          Cluster size for PC Exchange Macintosh files
-auto FILE                  Set HFS AutoStart file name
-no-desktop                 Do not create the HFS (empty) Desktop files
-hide-hfs GLOBFILE          Hide HFS file
-hide-hfs-list FILE         List of HFS files to hide
-hfs-volid HFS_VOLID        Volume name for the HFS partition
-icon-position              Keep HFS icon position
-root-info FILE             finderinfo for root folder
-input-hfs-charset CHARSET  Local input charset for HFS file name conversion
-output-hfs-charset CHARSET Output charset for HFS file name conversion
-hfs-unlock                 Leave HFS Volume unlocked
-hfs-bless FOLDER_NAME      Name of Folder to be blessed
-prep-boot FILE             PReP boot image file — up to 4 are allowed
–cap                       Look for AUFS CAP Macintosh files
–netatalk                  Look for NETATALK Macintosh files
–double                    Look for AppleDouble Macintosh files
–ethershare                Look for Helios EtherShare Macintosh files
–exchange                  Look for PC Exchange Macintosh files
–sgi                       Look for SGI Macintosh files
–macbin                    Look for MacBinary Macintosh files
–single                    Look for AppleSingle Macintosh files
–ushare                    Look for IPT UShare Macintosh files
–xinet                     Look for XINET Macintosh files
–dave                      Look for DAVE Macintosh files
–sfm                       Look for SFM Macintosh files

相关软件下载:

1、请访问下面的链接(链接到我的论坛上):
http://climbing.minidns.net/dvbbs/dispbbs.asp?boardID=6&ID=181

2、由大侠Wwashington修改并出品的支持中文以及基于md5技术的空间优化等功能的mkisofs 1.15版:841KB。

3、Bart的bcd 1.1.1经过我修改后的版本:4259KB,包含上面的新版mkisofs,同时包含cdimage,详细使用方法请参考Bart的网站:http://www.nu2.nu/bootcd

2004年07月31日

Beej网络socket编程指南

[Version 1.5.5]

[ http://www.ecst.csuchico.edu/~beej/guide/net/]


介绍
  Socket 编程让你沮丧吗?从man pages中很难得到有用的信息吗?你想跟上时代去编Internet相关的程序,但是为你在调用 connect() 前的bind() 的结构而不知所措?等等…
    好在我已经将这些事完成了,我将和所有人共享我的知识了。如果你了解 C 语言并想穿过网络编程的沼泽,那么你来对地方了。


读者对象
  这个文档是一个指南,而不是参考书。如果你刚开始 socket 编程并想找一本入门书,那么你是我的读者。但这不是一本完全的 socket 编程书。


平台和编译器
  这篇文档中的大多数代码都在 Linux 平台PC 上用 GNU 的 gcc 成功编译过。而且它们在 HPUX平台 上用 gcc 也成功编译过。但是注意,并不是每个代码片段都独立测试过。


目录:
1) 什么是套接字?
2) Internet 套接字的两种类型
3) 网络理论
4) 结构体
5) 本机转换
6) IP 地址和如何处理它们
7) socket()函数
8) bind()函数
9) connect()函数
10) listen()函数
11) accept()函数
12) send()和recv()函数
13) sendto()和recvfrom()函数
14) close()和shutdown()函数
15) getpeername()函数
16) gethostname()函数
17) 域名服务(DNS)
18) 客户-服务器背景知识
19) 简单的服务器
20) 简单的客户端
21) 数据报套接字Socket
22) 阻塞
23) select()–多路同步I/O
24) 参考资料


什么是 socket?
  你经常听到人们谈论着 “socket”,或许你还不知道它的确切含义。现在让我告诉你:它是使用 标准Unix 文件描述符 (file descriptor) 和其它程序通讯的方式。
什么?
你也许听到一些Unix高手(hacker)这样说过:“呀,Unix中的一切就是文件!”那个家伙也许正在说到一个事实:Unix 程序在执行任何形式的 I/O 的时候,程序是在读或者写一个文件描述符。一个文件描述符只是一个和打开的文件相关联的整数。但是(注意后面的话),这个文件可能是一个网络连接,FIFO,管道,终端,磁盘上的文件或者什么其它的东西。Unix 中所有的东西就是文件!所以,你想和Internet上别的程序通讯的时候,你将要使用到文件描述符。你必须理解刚才的话。现在你脑海中或许冒出这样的念头:“那么我从哪里得到网络通讯的文件描述符呢?”,这个问题无论如何我都要回答:你利用系统调用 socket(),它返回套接字描述符 (socket descriptor),然后你再通过它来进行send() 和 recv()调用。
“但是…”,你可能有很大的疑惑,“如果它是个文件描述符,那么为什 么不用一般调用read()和write()来进行套接字通讯?”简单的答案是:“你可以使用!”。详细的答案是:“你可以,但是使用send()和recv()让你更好的控制数据传输。”
存在这样一个情况:在我们的世界上,有很多种套接字。有DARPA Internet 地址 (Internet 套接字),本地节点的路径名 (Unix套接字),CCITT X.25地址 (你可以将X.25 套接字完全忽略)。也许在你的Unix 机器上还有其它的。我们在这里只讲第一种:Internet 套接字。


Internet 套接字的两种类型
  什么意思?有两种类型的Internet 套接字?是的。不,我在撒谎。其实还有很多,但是我可不想吓着你。我们这里只讲两种。除了这些, 我打算另外介绍的 “Raw Sockets” 也是非常强大的,很值得查阅。
那么这两种类型是什么呢?一种是”Stream Sockets”(流格式),另外一种是”Datagram Sockets”(数据包格式)。我们以后谈到它们的时候也会用到 “SOCK_STREAM” 和 “SOCK_DGRAM”。数据报套接字有时也叫“无连接套接字”(如果你确实要连接的时候可以用connect()。) 流式套接字是可靠的双向通讯的数据流。如果你向套接字按顺序输出“1,2”,那么它们将按顺序“1,2”到达另一边。它们是无错误的传递的,有自己的错误控制,在此不讨论。
    有什么在使用流式套接字?你可能听说过 telnet,不是吗?它就使用流式套接字。你需要你所输入的字符按顺序到达,不是吗?同样,WWW浏览器使用的 HTTP 协议也使用它们来下载页面。实际上,当你通过端口80 telnet 到一个 WWW 站点,然后输入 “GET pagename” 的时候,你也可以得到 HTML 的内容。为什么流式套接字可以达到高质量的数据传输?这是因为它使用了“传输控制协议 (The Transmission Control Protocol)”,也叫 “TCP” (请参考 RFC-793 获得详细资料。)TCP 控制你的数据按顺序到达并且没有错
误。你也许听到 “TCP” 是因为听到过 “TCP/IP”。这里的 IP 是指“Internet 协议”(请参考 RFC-791。) IP 只是处理 Internet 路由而已。
    那么数据报套接字呢?为什么它叫无连接呢?为什么它是不可靠的呢?有这样的一些事实:如果你发送一个数据报,它可能会到达,它可能次序颠倒了。如果它到达,那么在这个包的内部是无错误的。数据报也使用 IP 作路由,但是它不使用 TCP。它使用“用户数据报协议 (User Datagram Protocol)”,也叫 “UDP” (请参考 RFC-768。)
    为什么它们是无连接的呢?主要是因为它并不象流式套接字那样维持一个连接。你只要建立一个包,构造一个 有目标信息的IP 头,然后发出去。无需连接。它们通常使用于传输包-包信息。简单的应用程序有:tftp, bootp等等。
    你也许会想:“假如数据丢失了这些程序如何正常工作?”我的朋友,每个程序在 UDP 上有自己的协议。例如,tftp 协议每发出的一个被接受到包,收到者必须发回一个包来说“我收到了!” (一个“命令正确应答”也叫“ACK” 包)。如果在一定时间内(例如5秒),发送方没有收到应答,它将重新发送,直到得到 ACK。这一ACK过程在实现 SOCK_DGRAM 应用程序的时候非常重要。



网络理论

  既然我刚才提到了协议层,那么现在是讨论网络究竟如何工作和一些 关于 SOCK_DGRAM 包是如何建立的例子。当然,你也可以跳过这一段, 如果你认为已经熟悉的话。
    现在是学习数据封装 (Data Encapsulation) 的时候了!它非常非常重 要。它重要性重要到你在网络课程学(图1:数据封装)
习中无论如何也得也得掌握它。主要 的内容是:一个包,先是被第一个协议(在这里是TFTP )在它的报头(也许 是报尾)包装(“封装”),然后,整个数据(包括 TFTP 头)被另外一个协议 (在这里是 UDP )封装,然后下一个( IP ),一直重复下去,直到硬件(物理) 层( 这里是以太网 )。
当另外一台机器接收到包,硬件先剥去以太网头,内核剥去IP和UDP 头,TFTP程序再剥去TFTP头,最后得到数据。
现在我们终于讲到声名狼藉的网络分层模型 (Layered Network Model)。这种网络模型在描述网络系统上相对其它模型有很多优点。例如, 你可以写一个套接字程序而不用关心数据的物理传输(串行口,以太网,连 接单元接口 (AUI) 还是其它介质),因为底层的程序会为你处理它们。实际 的网络硬件和拓扑对于程序员来说是透明的。
不说其它废话了,我现在列出整个层次模型。如果你要参加网络考试, 可一定要记住:
应用层 (Application)
   表示层 (Presentation)
会话层 (Session)

  传输层(Transport)
  网络层(Network)
  数据链路层(Data Link)
  物理层(Physical)
物理层是硬件(串口,以太网等等)。应用层是和硬件层相隔最远的–它 是用户和网络交互的地方。
这个模型如此通用,如果你想,你可以把它作为修车指南。把它对应 到 Unix,结果是:
应用层(Application Layer) (telnet, ftp,等等)
  传输层(Host-to-Host Transport Layer) (TCP, UDP)
  Internet层(Internet Layer) (IP和路由)
  网络访问层 (Network Access Layer) (网络层,数据链路层和物理层)
现在,你可能看到这些层次如何协调来封装原始的数据了。
看看建立一个简单的数据包有多少工作?哎呀,你将不得不使用 “cat” 来建立数据包头!这仅仅是个玩笑。对于流式套接字你要作的是 send() 发 送数据。对于数据报式套接字,你按照你选择的方式封装数据然后使用 sendto()。内核将为你建立传输层和 Internet 层,硬件完成网络访问层。 这就是现代科技。
现在结束我们的网络理论速成班。哦,忘记告诉你关于路由的事情了。 但是我不准备谈它,如果你真的关心,那么参考 IP RFC。



结构体

  终于谈到编程了。在这章,我将谈到被套接字用到的各种数据类型。 因为它们中的一些内容很重要了。
首先是简单的一个:socket描述符。它是下面的类型:
int
仅仅是一个常见的 int。
从现在起,事情变得不可思议了,而你所需做的就是继续看下去。注 意这样的事实:有两种字节排列顺序:重要的字节 (有时叫 “octet”,即八 位位组) 在前面,或者不重要的字节在前面。前一种叫“网络字节顺序 (Network Byte Order)”。有些机器在内部是按照这个顺序储存数据,而另外 一些则不然。当我说某数据必须按照 NBO 顺序,那么你要调用函数(例如 htons() )来将它从本机字节顺序 (Host Byte Order) 转换过来。如果我没有 提到 NBO, 那么就让它保持本机字节顺序。
我的第一个结构(在这个技术手册TM中)–struct sockaddr.。这个结构 为许多类型的套接字储存套接字地址信息:
struct sockaddr {
   unsigned short sa_family; /* 地址家族, AF_xxx */
   char sa_data[14]; /*14字节协议地址*/
   };
sa_family 能够是各种各样的类型,但是在这篇文章中都是 “AF_INET”。 sa_data包含套接字中的目标地址和端口信息。这好像有点 不明智。
为了处理struct sockaddr,程序员创造了一个并列的结构: struct sockaddr_in (“in” 代表 “Internet”。)
struct sockaddr_in {
   short int sin_family; /* 通信类型 */
   unsigned short int sin_port; /* 端口 */
   struct in_addr sin_addr; /* Internet 地址 */
   unsigned char sin_zero[8]; /* 与sockaddr结构的长度相同*/
   };
用这个数据结构可以轻松处理套接字地址的基本元素。注意 sin_zero (它被加入到这个结构,并且长度和 struct sockaddr 一样) 应该使用函数 bzero() 或 memset() 来全部置零。 同时,这一重要的字节,一个指向 sockaddr_in结构体的指针也可以被指向结构体sockaddr并且代替它。这 样的话即使 socket() 想要的是 struct sockaddr *,你仍然可以使用 struct sockaddr_in,并且在最后转换。同时,注意 sin_family 和 struct sockaddr 中的 sa_family 一致并能够设置为 “AF_INET”。最后,sin_port和 sin_addr 必须是网络字节顺序 (Network Byte Order)!
你也许会反对道:”但是,怎么让整个数据结构 struct in_addr sin_addr 按照网络字节顺序呢?” 要知道这个问题的答案,我们就要仔细的看一看这 个数据结构: struct in_addr, 有这样一个联合 (unions):
/* Internet 地址 (一个与历史有关的结构) */
   struct in_addr {
   unsigned long s_addr;
   };
它曾经是个最坏的联合,但是现在那些日子过去了。如果你声明 “ina” 是数据结构 struct sockaddr_in 的实例,那么 “ina.sin_addr.s_addr” 就储 存4字节的 IP 地址(使用网络字节顺序)。如果你不幸的系统使用的还是恐 怖的联合 struct in_addr ,你还是可以放心4字节的 IP 地址并且和上面 我说的一样(这是因为使用了“#define”。)



本机转换

  我们现在到了新的章节。我们曾经讲了很多网络到本机字节顺序的转 换,现在可以实践了!
你能够转换两种类型: short (两个字节)和 long (四个字节)。这个函 数对于变量类型 unsigned 也适用。假设你想将 short 从本机字节顺序转 换为网络字节顺序。用 “h” 表示 “本机 (host)”,接着是 “to”,然后用 “n” 表 示 “网络 (network)”,最后用 “s” 表示 “short”: h-to-n-s, 或者 htons() (“Host to Network Short”)。
太简单了…
如果不是太傻的话,你一定想到了由”n”,”h”,”s”,和 “l”形成的正确 组合,例如这里肯定没有stolh() (“Short to Long Host”) 函数,不仅在这里 没有,所有场合都没有。但是这里有:
htons()–”Host to Network Short”
  htonl()–”Host to Network Long”
  ntohs()–”Network to Host Short”
  ntohl()–”Network to Host Long”
现在,你可能想你已经知道它们了。你也可能想:“如果我想改变 char 的顺序要怎么办呢?” 但是你也许马上就想到,“用不着考虑的”。你也许 会想到:我的 68000 机器已经使用了网络字节顺序,我没有必要去调用 htonl() 转换 IP 地址。你可能是对的,但是当你移植你的程序到别的机器 上的时候,你的程序将失败。可移植性!这里是 Unix 世界!记住:在你 将数据放到网络上的时候,确信它们是网络字节顺序的。
最后一点:为什么在数据结构 struct sockaddr_in 中, sin_addr 和 sin_port 需要转换为网络字节顺序,而sin_family 需不需要呢? 答案是: sin_addr 和 sin_port 分别封装在包的 IP 和 UDP 层。因此,它们必须要 是网络字节顺序。但是 sin_family 域只是被内核 (kernel) 使用来决定在数 据结构中包含什么类型的地址,所以它必须是本机字节顺序。同时, sin_family 没有发送到网络上,它们可以是本机字节顺序。



IP 地址和如何处理它们

现在我们很幸运,因为我们有很多的函数来方便地操作 IP 地址。没有 必要用手工计算它们,也没有必要用”<<”操作来储存成长整字型。 首先,假设你已经有了一个sockaddr_in结构体ina,你有一个IP地 址”132.241.5.10″要储存在其中,你就要用到函数inet_addr(),将IP地址从 点数格式转换成无符号长整型。使用方法如下:
ina.sin_addr.s_addr = inet_addr(“132.241.5.10″);
注意,inet_addr()返回的地址已经是网络字节格式,所以你无需再调用 函数htonl()。
我们现在发现上面的代码片断不是十分完整的,因为它没有错误检查。 显而易见,当inet_addr()发生错误时返回-1。记住这些二进制数字?(无符 号数)-1仅仅和IP地址255.255.255.255相符合!这可是广播地址!大错特 错!记住要先进行错误检查。
好了,现在你可以将IP地址转换成长整型了。有没有其相反的方法呢? 它可以将一个in_addr结构体输出成点数格式?这样的话,你就要用到函数 inet_ntoa()(“ntoa”的含义是”network to ascii”),就像这样:
printf(“%s”,inet_ntoa(ina.sin_addr));
它将输出IP地址。需要注意的是inet_ntoa()将结构体in-addr作为一 个参数,不是长整形。同样需要注意的是它返回的是一个指向一个字符的 指针。它是一个由inet_ntoa()控制的静态的固定的指针,所以每次调用 inet_ntoa(),它就将覆盖上次调用时所得的IP地址。例如:
char *a1, *a2;
.
.
a1 = inet_ntoa(ina1.sin_addr); /* 这是198.92.129.1 */
a2 = inet_ntoa(ina2.sin_addr); /* 这是132.241.5.10 */
printf(“address 1: %s\n”,a1);
printf(“address 2: %s\n”,a2);
输出如下:
address 1: 132.241.5.10
address 2: 132.241.5.10
假如你需要保存这个IP地址,使用strcopy()函数来指向你自己的字符 指针。
上面就是关于这个主题的介绍。稍后,你将学习将一个类 似”wintehouse.gov”的字符串转换成它所对应的IP地址(查阅域名服务,稍 后)。



socket()函数

我想我不能再不提这个了-下面我将讨论一下socket()系统调用。
下面是详细介绍:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
但是它们的参数是什么? 首先,domain 应该设置成 “AF_INET”,就 象上面的数据结构struct sockaddr_in 中一样。然后,参数 type 告诉内核 是 SOCK_STREAM 类型还是 SOCK_DGRAM 类型。最后,把 protocol 设置为 “0″。(注意:有很多种 domain、type,我不可能一一列出了,请看 socket() 的 man帮助。当然,还有一个”更好”的方式去得到 protocol。同 时请查阅 getprotobyname() 的 man 帮助。)
socket() 只是返回你以后在系统调用种可能用到的 socket 描述符,或 者在错误的时候返回-1。全局变量 errno 中将储存返回的错误值。(请参考 perror() 的 man 帮助。)


bind()函数
  一旦你有一个套接字,你可能要将套接字和机器上的一定的端口关联 起来。(如果你想用listen()来侦听一定端口的数据,这是必要一步–MUD 告 诉你说用命令 “telnet x.y.z 6969″。)如果你只想用 connect(),那么这个步 骤没有必要。但是无论如何,请继续读下去。
这里是系统调用 bind() 的大概:
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
sockfd 是调用 socket 返回的文件描述符。my_addr 是指向数据结构 struct sockaddr 的指针,它保存你的地址(即端口和 IP 地址) 信息。 addrlen 设置为 sizeof(struct sockaddr)。
简单得很不是吗? 再看看例子:
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#define MYPORT 3490
main()
   {
   int sockfd;
   struct sockaddr_in my_addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0); /*需要错误检查 */
my_addr.sin_family = AF_INET; /* host byte order */
   my_addr.sin_port = htons(MYPORT); /* short, network byte order */
   my_addr.sin_addr.s_addr = inet_addr(“132.241.5.10″);
   bzero(&(my_addr.sin_zero),; /* zero the rest of the struct */
/* don’t forget your error checking for bind(): */
   bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
   .
   .
   .
这里也有要注意的几件事情。my_addr.sin_port 是网络字节顺序, my_addr.sin_addr.s_addr 也是的。另外要注意到的事情是因系统的不同, 包含的头文件也不尽相同,请查阅本地的 man 帮助文件。
在 bind() 主题中最后要说的话是,在处理自己的 IP 地址和/或端口的 时候,有些工作是可以自动处理的。
my_addr.sin_port = 0; /* 随机选择一个没有使用的端口 */
  my_addr.sin_addr.s_addr = INADDR_ANY; /* 使用自己的IP地址 */
通过将0赋给 my_addr.sin_port,你告诉 bind() 自己选择合适的端 口。同样,将 my_addr.sin_addr.s_addr 设置为 INADDR_ANY,你告诉 它自动填上它所运行的机器的 IP 地址。
如果你一向小心谨慎,那么你可能注意到我没有将 INADDR_ANY 转 换为网络字节顺序!这是因为我知道内部的东西:INADDR_ANY 实际上就 是 0!即使你改变字节的顺序,0依然是0。但是完美主义者说应该处处一 致,INADDR_ANY或许是12呢?你的代码就不能工作了,那么就看下面 的代码:
my_addr.sin_port = htons(0); /* 随机选择一个没有使用的端口 */
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);/* 使用自己的IP地址 */
你或许不相信,上面的代码将可以随便移植。我只是想指出,既然你 所遇到的程序不会都运行使用htonl的INADDR_ANY。
bind() 在错误的时候依然是返回-1,并且设置全局错误变量errno。
在你调用 bind() 的时候,你要小心的另一件事情是:不要采用小于 1024的端口号。所有小于1024的端口号都被系统保留!你可以选择从1024 到65535的端口(如果它们没有被别的程序使用的话)。
你要注意的另外一件小事是:有时候你根本不需要调用它。如果你使 用 connect() 来和远程机器进行通讯,你不需要关心你的本地端口号(就象 你在使用 telnet 的时候),你只要简单的调用 connect() 就可以了,它会检 查套接字是否绑定端口,如果没有,它会自己绑定一个没有使用的本地端 口。



connect()程序

  现在我们假设你是个 telnet 程序。你的用户命令你得到套接字的文件 描述符。你听从命令调用了socket()。下一步,你的用户告诉你通过端口 23(标准 telnet 端口)连接到”132.241.5.10″。你该怎么做呢? 幸运的是,你正在阅读 connect()–如何连接到远程主机这一章。你可 不想让你的用户失望。
connect() 系统调用是这样的:
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
sockfd 是系统调用 socket() 返回的套接字文件描述符。serv_addr 是 保存着目的地端口和 IP 地址的数据结构 struct sockaddr。addrlen 设置 为 sizeof(struct sockaddr)。
想知道得更多吗?让我们来看个例子:
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#define DEST_IP “132.241.5.10″
  #define DEST_PORT 23
main()
   {
int sockfd;
struct sockaddr_in dest_addr; /* 目的地址*/
sockfd = socket(AF_INET, SOCK_STREAM, 0); /* 错误检查 */
dest_addr.sin_family = AF_INET; /* host byte order */
dest_addr.sin_port = htons(DEST_PORT); /* short, network byte order */
dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);
bzero(&(dest_addr.sin_zero),; /* zero the rest of the struct */
/* don’t forget to error check the connect()! */
connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr));
   .
   .
   .
  再一次,你应该检查 connect() 的返回值–它在错误的时候返回-1,并 设置全局错误变量 errno。
同时,你可能看到,我没有调用 bind()。因为我不在乎本地的端口号。 我只关心我要去那。内核将为我选择一个合适的端口号,而我们所连接的 地方也自动地获得这些信息。一切都不用担心。



listen()函数

  是换换内容得时候了。假如你不希望与远程的一个地址相连,或者说, 仅仅是将它踢开,那你就需要等待接入请求并且用各种方法处理它们。处 理过程分两步:首先,你听–listen(),然后,你接受–accept() (请看下面的 内容)。
除了要一点解释外,系统调用 listen 也相当简单。
int listen(int sockfd, int backlog);
sockfd 是调用 socket() 返回的套接字文件描述符。backlog 是在进入 队列中允许的连接数目。什么意思呢? 进入的连接是在队列中一直等待直 到你接受 (accept() 请看下面的文章)连接。它们的数目限制于队列的允许。 大多数系统的允许数目是20,你也可以设置为5到10。
和别的函数一样,在发生错误的时候返回-1,并设置全局错误变量 errno。
你可能想象到了,在你调用 listen() 前你或者要调用 bind() 或者让内 核随便选择一个端口。如果你想侦听进入的连接,那么系统调用的顺序可 能是这样的:
socket();
  bind();
listen();
  /* accept() 应该在这 */
因为它相当的明了,我将在这里不给出例子了。(在 accept() 那一章的 代码将更加完全。)真正麻烦的部分在 accept()。



accept()函数

  准备好了,系统调用 accept() 会有点古怪的地方的!你可以想象发生 这样的事情:有人从很远的地方通过一个你在侦听 (listen()) 的端口连接 (connect()) 到你的机器。它的连接将加入到等待接受 (accept()) 的队列 中。你调用 accept() 告诉它你有空闲的连接。它将返回一个新的套接字文 件描述符!这样你就有两个套接字了,原来的一个还在侦听你的那个端口, 新的在准备发送 (send()) 和接收 ( recv()) 数据。这就是这个过程!
函数是这样定义的:
#include <sys/socket.h>
int accept(int sockfd, void *addr, int *addrlen);
sockfd 相当简单,是和 listen() 中一样的套接字描述符。addr 是个指 向局部的数据结构 sockaddr_in 的指针。这是要求接入的信息所要去的地 方(你可以测定那个地址在那个端口呼叫你)。在它的地址传递给 accept 之 前,addrlen 是个局部的整形变量,设置为 sizeof(struct sockaddr_in)。 accept 将不会将多余的字节给 addr。如果你放入的少些,那么它会通过改
变 addrlen 的值反映出来。
同样,在错误时返回-1,并设置全局错误变量 errno。
现在是你应该熟悉的代码片段。
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#define MYPORT 3490 /*用户接入端口*/
#define BACKLOG 10 /* 多少等待连接控制*/
main()
   {
  int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */
  struct sockaddr_in my_addr; /* 地址信息 */
  struct sockaddr_in their_addr; /* connector’s address information */
  int sin_size;
sockfd = socket(AF_INET, SOCK_STREAM, 0); /* 错误检查*/
my_addr.sin_family = AF_INET; /* host byte order */
  my_addr.sin_port = htons(MYPORT); /* short, network byte order */
  my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */
  bzero(&(my_addr.sin_zero),; /* zero the rest of the struct */
/* don’t forget your error checking for these calls: */
  bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
listen(sockfd, BACKLOG);
sin_size = sizeof(struct sockaddr_in);
  new_fd = accept(sockfd, &their_addr, &sin_size);
   .
   .
   .
注意,在系统调用 send() 和 recv() 中你应该使用新的套接字描述符 new_fd。如果你只想让一个连接进来,那么你可以使用 close() 去关闭原 来的文件描述符 sockfd 来避免同一个端口更多的连接。



send() and recv()函数

  这两个函数用于流式套接字或者数据报套接字的通讯。如果你喜欢使 用无连接的数据报套接字,你应该看一看下面关于sendto() 和 recvfrom() 的章节。
send() 是这样的:
int send(int sockfd, const void *msg, int len, int flags);
sockfd 是你想发送数据的套接字描述符(或者是调用 socket() 或者是 accept() 返回的。)msg 是指向你想发送的数据的指针。len 是数据的长度。 把 flags 设置为 0 就可以了。(详细的资料请看 send() 的 man page)。
这里是一些可能的例子:
char *msg = “Beej was here!”;
  int len, bytes_sent;
  .
  .
  len = strlen(msg);
  bytes_sent = send(sockfd, msg, len, 0);
  .
  .
  .
send() 返回实际发送的数据的字节数–它可能小于你要求发送的数 目! 注意,有时候你告诉它要发送一堆数据可是它不能处理成功。它只是 发送它可能发送的数据,然后希望你能够发送其它的数据。记住,如果 send() 返回的数据和 len 不匹配,你就应该发送其它的数据。但是这里也 有个好消息:如果你要发送的包很小(小于大约 1K),它可能处理让数据一 次发送完。最后要说得就是,它在错误的时候返回-1,并设置 errno。
recv() 函数很相似:
int recv(int sockfd, void *buf, int len, unsigned int flags);
sockfd 是要读的套接字描述符。buf 是要读的信息的缓冲。len 是缓 冲的最大长度。flags 可以设置为0。(请参考recv() 的 man page。) recv() 返回实际读入缓冲的数据的字节数。或者在错误的时候返回-1, 同时设置 errno。
很简单,不是吗? 你现在可以在流式套接字上发送数据和接收数据了。 你现在是 Unix 网络程序员了!



sendto() 和 recvfrom()函数

  “这很不错啊”,你说,“但是你还没有讲无连接数据报套接字呢?” 没问题,现在我们开始这个内容。
既然数据报套接字不是连接到远程主机的,那么在我们发送一个包之 前需要什么信息呢? 不错,是目标地址!看看下面的:
int sendto(int sockfd, const void *msg, int len, unsigned int flags,
  const struct sockaddr *to, int tolen);
你已经看到了,除了另外的两个信息外,其余的和函数 send() 是一样 的。 to 是个指向数据结构 struct sockaddr 的指针,它包含了目的地的 IP 地址和端口信息。tolen 可以简单地设置为 sizeof(struct sockaddr)。 和函数 send() 类似,sendto() 返回实际发送的字节数(它也可能小于 你想要发送的字节数!),或者在错误的时候返回 -1。
相似的还有函数 recv() 和 recvfrom()。recvfrom() 的定义是这样的:
int recvfrom(int sockfd, void *buf, int len, unsigned int flags,  struct sockaddr *from, int *fromlen);
又一次,除了两个增加的参数外,这个函数和 recv() 也是一样的。from 是一个指向局部数据结构 struct sockaddr 的指针,它的内容是源机器的 IP 地址和端口信息。fromlen 是个 int 型的局部指针,它的初始值为 sizeof(struct sockaddr)。函数调用返回后,fromlen 保存着实际储存在 from 中的地址的长度。
recvfrom() 返回收到的字节长度,或者在发生错误后返回 -1。
记住,如果你用 connect() 连接一个数据报套接字,你可以简单的调 用 send() 和 recv() 来满足你的要求。这个时候依然是数据报套接字,依 然使用 UDP,系统套接字接口会为你自动加上了目标和源的信息。



close()和shutdown()函数

  你已经整天都在发送 (send()) 和接收 (recv()) 数据了,现在你准备关 闭你的套接字描述符了。这很简单,你可以使用一般的 Unix 文件描述符 的 close() 函数:
  close(sockfd);
它将防止套接字上更多的数据的读写。任何在另一端读写套接字的企 图都将返回错误信息。
如果你想在如何关闭套接字上有多一点的控制,你可以使用函数 shutdown()。它允许你将一定方向上的通讯或者双向的通讯(就象close()一 样)关闭,你可以使用:
int shutdown(int sockfd, int how);
sockfd 是你想要关闭的套接字文件描述复。how 的值是下面的其中之 一:
  0 – 不允许接受
  1 – 不允许发送
  2 – 不允许发送和接受(和 close() 一样)
shutdown() 成功时返回 0,失败时返回 -1(同时设置 errno。) 如果在无连接的数据报套接字中使用shutdown(),那么只不过是让 send() 和 recv() 不能使用(记住你在数据报套接字中使用了 connect 后 是可以使用它们的)。



getpeername()函数

  这个函数太简单了。
它太简单了,以至我都不想单列一章。但是我还是这样做了。 函数 getpeername() 告诉你在连接的流式套接字上谁在另外一边。函 数是这样的:
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);
sockfd 是连接的流式套接字的描述符。addr 是一个指向结构 struct sockaddr (或者是 struct sockaddr_in) 的指针,它保存着连接的另一边的 信息。addrlen 是一个 int 型的指针,它初始化为 sizeof(struct sockaddr)。 函数在错误的时候返回 -1,设置相应的 errno。
一旦你获得它们的地址,你可以使用 inet_ntoa() 或者 gethostbyaddr() 来打印或者获得更多的信息。但是你不能得到它的帐号。(如果它运行着愚 蠢的守护进程,这是可能的,但是它的讨论已经超出了本文的范围,请参 考 RFC-1413 以获得更多的信息。)


gethostname()函数
  甚至比 getpeername() 还简单的函数是 gethostname()。它返回你程 序所运行的机器的主机名字。然后你可以使用 gethostbyname() 以获得你 的机器的 IP 地址。
  下面是定义:
  #include <unistd.h>
int gethostname(char *hostname, size_t size);
参数很简单:hostname 是一个字符数组指针,它将在函数返回时保存
主机名。size是hostname 数组的字节长度。
函数调用成功时返回 0,失败时返回 -1,并设置 errno。



域名服务(DNS)

  如果你不知道 DNS 的意思,那么我告诉你,它代表域名服务(Domain Name Service)。它主要的功能是:你给它一个容易记忆的某站点的地址, 它给你 IP 地址(然后你就可以使用 bind(), connect(), sendto() 或者其它 函数) 。当一个人输入:
   $ telnet whitehouse.gov
telnet 能知道它将连接 (connect()) 到 “198.137.240.100″。
但是这是如何工作的呢? 你可以调用函数 gethostbyname():
#include <netdb.h>
  struct hostent *gethostbyname(const char *name);
很明白的是,它返回一个指向 struct hostent 的指针。这个数据结构 是这样的:
   struct hostent {
   char *h_name;
   char **h_aliases;
   int h_addrtype;
   int h_length;
   char **h_addr_list;
   };
   #define h_addr h_addr_list[0]
这里是这个数据结构的详细资料:
struct hostent:
  h_name – 地址的正式名称。
  h_aliases – 空字节-地址的预备名称的指针。
  h_addrtype –地址类型; 通常是AF_INET。
  h_length – 地址的比特长度。
  h_addr_list – 零字节-主机网络地址指针。网络字节顺序。
  h_addr – h_addr_list中的第一地址。
gethostbyname() 成功时返回一个指向结构体 hostent 的指针,或者 是个空 (NULL) 指针。(但是和以前不同,不设置errno,h_errno 设置错 误信息。请看下面的 herror()。)
但是如何使用呢? 有时候(我们可以从电脑手册中发现),向读者灌输 信息是不够的。这个函数可不象它看上去那么难用。
这里是个例子:
#include <stdio.h>
  #include <stdlib.h>
  #include <errno.h>
  #include <netdb.h>
  #include <sys/types.h>
  #include <netinet/in.h>
int main(int argc, char *argv[])
   {
   struct hostent *h;
if (argc != 2) { /* 检查命令行 */
   fprintf(stderr,”usage: getip address\n”);
   exit(1);
   }
if ((h=gethostbyname(argv[1])) == NULL) { /* 取得地址信息 */
   herror(“gethostbyname”);
   exit(1);
   }
printf(“Host name : %s\n”, h->h_name);
  printf(“IP Address : %s\n”,inet_ntoa(*((struct in_addr *)h->h_addr)));
return 0;
   }
在使用 gethostbyname() 的时候,你不能用 perror() 打印错误信息 (因为 errno 没有使用),你应该调用 herror()。
相当简单,你只是传递一个保存机器名的字符串(例如 “whitehouse.gov”) 给 gethostbyname(),然后从返回的数据结构 struct hostent 中获取信息。
唯一也许让人不解的是输出 IP 地址信息。h->h_addr 是一个 char *, 但是 inet_ntoa() 需要的是 struct in_addr。因此,我转换 h->h_addr 成 struct in_addr *,然后得到数据。




客户-服务器背景知识

  这里是个客户–服务器的世界。在网络上的所有东西都是在处理客户进 程和服务器进程的交谈。举个telnet 的例子。当你用 telnet (客户)通过23 号端口登陆到主机,主机上运行的一个程序(一般叫 telnetd,服务器)激活。 它处理这个连接,显示登陆界面,等等。


图2:客户机和服务器的关系
图 2 说明了客户和服务器之间的信息交换。
注意,客户–服务器之间可以使用SOCK_STREAM、SOCK_DGRAM 或者其它(只要它们采用相同的)。一些很好的客户–服务器的例子有 telnet/telnetd、 ftp/ftpd 和 bootp/bootpd。每次你使用 ftp 的时候,在远 端都有一个 ftpd 为你服务。
一般,在服务端只有一个服务器,它采用 fork() 来处理多个客户的连 接。基本的程序是:服务器等待一个连接,接受 (accept()) 连接,然后 fork() 一个子进程处理它。这是下一章我们的例子中会讲到的。



简单的服务器

  这个服务器所做的全部工作是在流式连接上发送字符串 “Hello, World!\n”。你要测试这个程序的话,可以在一台机器上运行该程序,然后 在另外一机器上登陆:
   $ telnet remotehostname 3490
remotehostname 是该程序运行的机器的名字。
服务器代码:
#include <stdio.h>
  #include <stdlib.h>
  #include <errno.h>
  #include <string.h>
  #include <sys/types.h>
  #include <netinet/in.h>
  #include <sys/socket.h>
  #include <sys/wait.h>
#define MYPORT 3490 /*定义用户连接端口*/
#define BACKLOG 10 /*多少等待连接控制*/
main()
   {
   int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd
*/
   struct sockaddr_in my_addr; /* my address information */
   struct sockaddr_in their_addr; /* connector’s address information */
   int sin_size;

if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
   perror(“socket”);
   exit(1);
   }

my_addr.sin_family = AF_INET; /* host byte order */
   my_addr.sin_port = htons(MYPORT); /* short, network byte order */
   my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */
   bzero(&(my_addr.sin_zero),; /* zero the rest of the struct */

if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct
sockaddr))== -1) {
   perror(“bind”);
   exit(1);
   }
if (listen(sockfd, BACKLOG) == -1) {
   perror(“listen”);
   exit(1);
   }

while(1) { /* main accept() loop */
   sin_size = sizeof(struct sockaddr_in);
   if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, \
   &sin_size)) == -1) {
   perror(“accept”);
   continue;
   }
   printf(“server: got connection from %s\n”, \
   inet_ntoa(their_addr.sin_addr));
   if (!fork()) { /* this is the child process */
   if (send(new_fd, “Hello, world!\n”, 14, 0) == -1)
   perror(“send”);
   close(new_fd);
   exit(0);
   }
   close(new_fd); /* parent doesn’t need this */
while(waitpid(-1,NULL,WNOHANG) > 0); /* clean up child processes */
   }
   }
如果你很挑剔的话,一定不满意我所有的代码都在一个很大的main() 函数中。如果你不喜欢,可以划分得更细点。
你也可以用我们下一章中的程序得到服务器端发送的字符串。


简单的客户程序
  这个程序比服务器还简单。这个程序的所有工作是通过 3490 端口连 接到命令行中指定的主机,然后得到服务器发送的字符串。
客户代码:
#include <stdio.h>
  #include <stdlib.h>
  #include <errno.h>
  #include <string.h>
  #include <sys/types.h>
  #include <netinet/in.h>
  #include <sys/socket.h>
  #include <sys/wait.h>
#define PORT 3490 /* 客户机连接远程主机的端口 */
#define MAXDATASIZE 100 /* 每次可以接收的最大字节 */
int main(int argc, char *argv[])
   {
   int sockfd, numbytes;
   char buf[MAXDATASIZE];
   struct hostent *he;
   struct sockaddr_in their_addr; /* connector’s address information */

if (argc != 2) {
   fprintf(stderr,”usage: client hostname\n”);
   exit(1);
   }
if ((he=gethostbyname(argv[1])) == NULL) { /* get the host info */
   herror(“gethostbyname”);
   exit(1);
   }

if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
   perror(“socket”);
   exit(1);
   }

their_addr.sin_family = AF_INET; /* host byte order */
  their_addr.sin_port = htons(PORT); /* short, network byte order */
  their_addr.sin_addr = *((struct in_addr *)he->h_addr);
  bzero(&(their_addr.sin_zero),; /* zero the rest of the struct */
if (connect(sockfd, (struct sockaddr *)&their_addr,sizeof(struct
sockaddr)) == -1) {
   perror(“connect”);
   exit(1);
   }
if ((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {
   perror(“recv”);
   exit(1);
   }
buf[numbytes] = ‘\0′;
printf(“Received: %s”,buf);
close(sockfd);
return 0;
   }
注意,如果你在运行服务器之前运行客户程序,connect() 将返回 “Connection refused” 信息,这非常有用。


数据包 Sockets
  我不想讲更多了,所以我给出代码 talker.c 和 listener.c。
listener 在机器上等待在端口 4590 来的数据包。talker 发送数据包到 一定的机器,它包含用户在命令行输入的内容。
这里就是 listener.c:
#include <stdio.h>
  #include <stdlib.h>
  #include <errno.h>
  #include <string.h>
  #include <sys/types.h>
  #include <netinet/in.h>
  #include <sys/socket.h>
  #include <sys/wait.h>
#define MYPORT 4950 /* the port users will be sending to */
#define MAXBUFLEN 100
main()
   {
   int sockfd;
   struct sockaddr_in my_addr; /* my address information */
   struct sockaddr_in their_addr; /* connector’s address information */
   int addr_len, numbytes;
   char buf[MAXBUFLEN];
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
   perror(“socket”);
   exit(1);
   }
my_addr.sin_family = AF_INET; /* host byte order */
   my_addr.sin_port = htons(MYPORT); /* short, network byte order */
   my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */
   bzero(&(my_addr.sin_zero),; /* zero the rest of the struct */
if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr))
\
   == -1) {
   perror(“bind”);
   exit(1);
   }
addr_len = sizeof(struct sockaddr);
   if ((numbytes=recvfrom(sockfd, buf, MAXBUFLEN, 0, \
   (struct sockaddr *)&their_addr, &addr_len)) == -1) {
   perror(“recvfrom”);
   exit(1);
   }
printf(“got packet from %s\n”,inet_ntoa(their_addr.sin_addr));
   printf(“packet is %d bytes long\n”,numbytes);
   buf[numbytes] = ‘\0′;
   printf(“packet contains \”%s\”\n”,buf);
close(sockfd);
   }
注意在我们的调用 socket(),我们最后使用了 SOCK_DGRAM。同时, 没有必要去使用 listen() 或者 accept()。我们在使用无连接的数据报套接 字!
下面是 talker.c:
#include <stdio.h>
  #include <stdlib.h>
  #include <errno.h>
  #include <string.h>
  #include <sys/types.h>
  #include <netinet/in.h>
  #include <sys/socket.h>
  #include <sys/wait.h>
#define MYPORT 4950 /* the port users will be sending to */
int main(int argc, char *argv[])
   {
   int sockfd;
   struct sockaddr_in their_addr; /* connector’s address information */
   struct hostent *he;
   int numbytes;

if (argc != 3) {
   fprintf(stderr,”usage: talker hostname message\n”);
   exit(1);
   }

if ((he=gethostbyname(argv[1])) == NULL) { /* get the host info */
   herror(“gethostbyname”);
   exit(1);
   }

if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
   perror(“socket”);
   exit(1);
   }

their_addr.sin_family = AF_INET; /* host byte order */
   their_addr.sin_port = htons(MYPORT); /* short, network byte order
*/
   their_addr.sin_addr = *((struct in_addr *)he->h_addr);
   bzero(&(their_addr.sin_zero),; /* zero the rest of the struct */
if ((numbytes=sendto(sockfd, argv[2], strlen(argv[2]), 0, \
   (struct sockaddr *)&their_addr, sizeof(struct sockaddr))) == -1) {
   perror(“sendto”);
   exit(1);
   }
printf(“sent %d bytes to
%s\n”,numbytes,inet_ntoa(their_addr.sin_addr));
close(sockfd);
return 0;
   }
这就是所有的了。在一台机器上运行 listener,然后在另外一台机器上 运行 talker。观察它们的通讯!
除了一些我在上面提到的数据套接字连接的小细节外,对于数据套接 字,我还得说一些,当一个讲话者呼叫connect()函数时并指定接受者的地 址时,从这点可以看出,讲话者只能向connect()函数指定的地址发送和接 受信息。因此,你不需要使用sendto()和recvfrom(),你完全可以用send() 和recv()代替。


阻塞
  阻塞,你也许早就听说了。”阻塞”是 “sleep” 的科技行话。你可能注意 到前面运行的 listener 程序,它在那里不停地运行,等待数据包的到来。 实际在运行的是它调用 recvfrom(),然后没有数据,因此 recvfrom() 说” 阻塞 (block)”,直到数据的到来。
很多函数都利用阻塞。accept() 阻塞,所有的 recv*() 函数阻塞。它 们之所以能这样做是因为它们被允许这样做。当你第一次调用 socket() 建 立套接字描述符的时候,内核就将它设置为阻塞。如果你不想套接字阻塞, 你就要调用函数 fcntl():
#include <unistd.h>
  #include <fontl.h>
   .
   .
   sockfd = socket(AF_INET, SOCK_STREAM, 0);
   fcntl(sockfd, F_SETFL, O_NONBLOCK);
   .
   .
  通过设置套接字为非阻塞,你能够有效地”询问”套接字以获得信息。如 果你尝试着从一个非阻塞的套接字读信息并且没有任何数据,它不允许阻 塞–它将返回 -1 并将 errno 设置为 EWOULDBLOCK。
但是一般说来,这种询问不是个好主意。如果你让你的程序在忙等状 态查询套接字的数据,你将浪费大量的 CPU 时间。更好的解决之道是用 下一章讲的 select() 去查询是否有数据要读进来。


select()–多路同步 I/O
  虽然这个函数有点奇怪,但是它很有用。假设这样的情况:你是个服 务器,你一边在不停地从连接上读数据,一边在侦听连接上的信息。 没问题,你可能会说,不就是一个 accept() 和两个 recv() 吗? 这么 容易吗,朋友? 如果你在调用 accept() 的时候阻塞呢? 你怎么能够同时接 受 recv() 数据? “用非阻塞的套接字啊!” 不行!你不想耗尽所有的 CPU 吧? 那么,该如何是好?
select() 让你可以同时监视多个套接字。如果你想知道的话,那么它就 会告诉你哪个套接字准备读,哪个又准备写,哪个套接字又发生了例外 (exception)。
闲话少说,下面是 select():
#include <sys/time.h>
  #include <sys/types.h>
  #include <unistd.h>
int select(int numfds, fd_set *readfds, fd_set *writefds,fd_set
*exceptfds, struct timeval *timeout);
这个函数监视一系列文件描述符,特别是 readfds、writefds 和 exceptfds。如果你想知道你是否能够从标准输入和套接字描述符 sockfd 读入数据,你只要将文件描述符 0 和 sockfd 加入到集合 readfds 中。参 数 numfds 应该等于最高的文件描述符的值加1。在这个例子中,你应该 设置该值为 sockfd+1。因为它一定大于标准输入的文件描述符 (0)。 当函数 select() 返回的时候,readfds 的值修改为反映你选择的哪个 文件描述符可以读。你可以用下面讲到的宏 FD_ISSET() 来测试。 在我们继续下去之前,让我来讲讲如何对这些集合进行操作。每个集 合类型都是 fd_set。下面有一些宏来对这个类型进行操作:
FD_ZERO(fd_set *set) – 清除一个文件描述符集合
  FD_SET(int fd, fd_set *set) – 添加fd到集合
  FD_CLR(int fd, fd_set *set) – 从集合中移去fd
  FD_ISSET(int fd, fd_set *set) – 测试fd是否在集合中
最后,是有点古怪的数据结构 struct timeval。有时你可不想永远等待 别人发送数据过来。也许什么事情都没有发生的时候你也想每隔96秒在终 端上打印字符串 “Still Going…”。这个数据结构允许你设定一个时间,如果 时间到了,而 select() 还没有找到一个准备好的文件描述符,它将返回让 你继续处理。
数据结构 struct timeval 是这样的:
struct timeval {
   int tv_sec; /* seconds */
   int tv_usec; /* microseconds */
   };
只要将 tv_sec 设置为你要等待的秒数,将 tv_usec 设置为你要等待 的微秒数就可以了。是的,是微秒而不是毫秒。1,000微秒等于1毫秒,1,000 毫秒等于1秒。也就是说,1秒等于1,000,000微秒。为什么用符号 “usec” 呢? 字母 “u” 很象希腊字母 Mu,而 Mu 表示 “微” 的意思。当然,函数 返回的时候 timeout 可能是剩余的时间,之所以是可能,是因为它依赖于 你的 Unix 操作系统。
哈!我们现在有一个微秒级的定时器!别计算了,标准的 Unix 系统 的时间片是100毫秒,所以无论你如何设置你的数据结构 struct timeval, 你都要等待那么长的时间。
还有一些有趣的事情:如果你设置数据结构 struct timeval 中的数据为 0,select() 将立即超时,这样就可以有效地轮询集合中的所有的文件描述 符。如果你将参数 timeout 赋值为 NULL,那么将永远不会发生超时,即 一直等到第一个文件描述符就绪。最后,如果你不是很关心等待多长时间, 那么就把它赋为 NULL 吧。
下面的代码演示了在标准输入上等待 2.5 秒:
#include <sys/time.h>
  #include <sys/types.h>
  #include <unistd.h>
#define STDIN 0 /* file descriptor for standard input */
main()
   {
  struct timeval tv;
  fd_set readfds;
tv.tv_sec = 2;
  tv.tv_usec = 500000;
FD_ZERO(&readfds);
  FD_SET(STDIN, &readfds);
/* don’t care about writefds and exceptfds: */
  select(STDIN+1, &readfds, NULL, NULL, &tv);
if (FD_ISSET(STDIN, &readfds))
  printf(“A key was pressed!\n”);
  else
  printf(“Timed out.\n”);
  }
如果你是在一个 line buffered 终端上,那么你敲的键应该是回车 (RETURN),否则无论如何它都会超时。
现在,你可能回认为这就是在数据报套接字上等待数据的方式–你是对 的:它可能是。有些 Unix 系统可以按这种方式,而另外一些则不能。你 在尝试以前可能要先看看本系统的 man page 了。
最后一件关于 select() 的事情:如果你有一个正在侦听 (listen()) 的套 接字,你可以通过将该套接字的文件描述符加入到 readfds 集合中来看是 否有新的连接。
这就是我关于函数select() 要讲的所有的东西。
  参考书目:
  Internetworking with TCP/IP, volumes I-III by Douglas E. Comer and
David L. Stevens. Published by Prentice Hall. Second edition ISBNs:
0-13-468505-9, 0-13-472242-6, 0-13-474222-2. There is a third edition of
this set which covers IPv6 and IP over ATM.
  Using C on the UNIX System by David A. Curry. Published by
O’Reilly & Associates, Inc. ISBN 0-937175-23-4.
  TCP/IP Network Administration by Craig Hunt. Published by O’Reilly
& Associates, Inc. ISBN 0-937175-82-X.
  TCP/IP Illustrated, volumes 1-3 by W. Richard Stevens and Gary R.
Wright. Published by Addison Wesley. ISBNs: 0-201-63346-9,
0-201-63354-X, 0-201-63495-3.
Unix Network Programming by W. Richard Stevens. Published by
Prentice Hall. ISBN 0-13-949876-1.
  On the web:
  BSD Sockets: A Quick And Dirty Primer
  (http://www.cs.umn.edu/~bentlema/unix/–has other great Unix
system programming info, too!)
Client-Server Computing
  (http://pandonia.canberra.edu.au/ClientServer/socket.html)
Intro to TCP/IP (gopher)

(gopher://gopher-chem.ucdavis.edu/11/Index/Internet_aw/Intro_the_Inter
net/intro.to.ip/)
Internet Protocol Frequently Asked Questions (France)
  (http://web.cnam.fr/Network/TCP-IP/)
The Unix Socket FAQ
  (http://www.ibrado.com/sock-faq/)
RFCs–the real dirt:
  RFC-768 — The User Datagram Protocol (UDP)
   (ftp://nic.ddn.mil/rfc/rfc768.txt)
RFC-791 — The Internet Protocol (IP)
  (ftp://nic.ddn.mil/rfc/rfc791.txt)
RFC-793 — The Transmission Control Protocol (TCP)
   (ftp://nic.ddn.mil/rfc/rfc793.txt)
RFC-854 — The Telnet Protocol
   (ftp://nic.ddn.mil/rfc/rfc854.txt)
RFC-951 — The Bootstrap Protocol (BOOTP)
 (ftp://nic.ddn.mil/rfc/rfc951.txt)
RFC-1350 — The Trivial File Transfer Protocol (TFTP)
   (ftp://nic.ddn.mil/rfc/rfc1350.txt)

1.开机

    在开机的一瞬间,系统完成了什么操作呢?当一台Intel x86计算机电源开关或复位按钮被按下的时候,冷启动就开始了。这时,CPU进入复位状态,将内存中的所有数据清零,并对内存进行效验。如果没有错误,则 CS寄存器中将置入FFFF[0],IP寄存器中将置入0000[0]。CS:IP指向BIOS(基本输入输出系统,Basic Input Output System)的入口,他将作为处理器运行的第一条指令启动BIOS。
    内存被自动清零时CPU无法获得指令,但是ROM中的FFFF:0000处,CPU可以从BIOS中获得启动所需要的指令集。BIOS的主要任务是提供CPU所需要的启动指令,完成

2.系统引导

3.系统初始化
4.关机
5.小结