2006年02月20日

  最近,微软对其授权协议作出更改,它表示,电脑更换了新主板,就等于一台新电脑,因此用户需要重新购买一个新的授权。

  以下是微软的说法:“升级主板将产生一台‘新的个人电脑’,而微软的操作系统并不能转移到这台新电脑上。如果不是由于主板损坏而升级或更换了主板,这样的话就会产生了一台新的电脑,因此需要另外一个新的操作系统授权。”

  微软对这个条款的解析是:“微软需要一个能定义原来PC的合理的基本条款,由于主板包含了CPU,并且它是电脑的‘心脏和灵魂’,当更换主板后(除损坏以外的原因)一台新电脑就被创造出来了。”

  微软已经向其OEM合作伙伴发送了备忘录,每次他们为客户升级电脑后都要严格执行这个政策。

2005年11月17日

  下面是Live服务的一些测试签到地址及已经推出的测试网址汇总:

  申请 Windows Live Mail beta
  申请 Windows Live Messenger beta
  申请 Windows OneCare live beta
  申请 Windows Live Mail – mobile beta
  申请 Windows Live Search beta-mobile
  申请 Windows Live Local Beta
  申请 Office Live Beta

  尝试 Live.com beta
  尝试 Windows Live Safety Center beta
  尝试 Windows Live Favorites beta

2005年09月09日

  Google终于为中国用户推出本地搜索了!Google 本地搜索是一种 Google 搜索服务,其功能是帮助您查找位于某个特定地理位置的公司。
  通过 Google,你可以搜索万维网;而通过 Google 本地搜索,你可以找到一家步行就可以到达的商店。
  有趣的是,该搜索使用的域名为http://bendi.google.com,从bendi这个拼音来看,Google已经找到了一种使于中国人记忆的好方法,笔预计Google以后的域名也有可能再次使用拼音。
  现在Google本地搜索包含了100多个中国城市搜索资料,其中超过70个城市提供地图资料。 
  来看看截图

版本历史
日期 版本 简介
2002年05月25日 V1.8e 第一个英语发布版
2002年06月12日 V1.9e 第一次反馈(感谢Dnaiel Schmidberger!)
2002年07月19日 V1.10e 添加iostat章节
2002年09月04日 V1.11e Matt Ruetz的修订版
2002年12月05日 V1.12e 经过修正和增加的版本
2003年01月17日 V1.13e 被Halvard Halvorsen(www.sundot.com)和Sven Fink修正和附加的版本
2003年04月11日 V1.14e Sven和Andreas Fatum关于shell编译器和环境变量章节更多地说明
简介
这篇文档包含了你用solaris和Sun平台工作时所需要的最重要的20%的技术。它能处理你所遇到的80%的问题。
这篇文档并不打算覆盖Solaris处理环境的细节,也不会包含常见问题解答(相关的书和链接将在附录中给出)。这本书将用来为刚成为Solari

s系统管理员的新手或没有每天管理Solaris系统基础的人提供一种怎样收集这种信息的服务。这篇文章关注的方向是用来做技术开发的图形工

作站,也将对希望为建立一套用来演示或作为基准的Sun系统的人有所帮助。
两点提醒:
? 永远记住在记录日志中记录下你对系统的每一次改动
? 清楚的在头脑中记住这篇文档包含能让系统不可用的系统级的程序。除非你对它的破坏性做了仔细的分析,否则不要在正在使用的系统上执

行这些程序。

1 系统启动和测试
1.1 OK提示符
每套SUN都有一个Boot-PROM模式,可以在启动操作系统之前访问,也可以在运行的时候访问,可以用STOP+A进入,如果没有STOP键(PC),可

以用ctrl+break.。一旦进入Boot-PROM模式,会出现OK提示符。如果操作系统运行时候出现,可以用go继续。
1.2 控制台测试
Boot-PROM模式可以用直接连接的键盘和显示器,也可以用终端。
如果系统启动时候没有检测到键盘,输入输出自动转到第1个串口(/dev/ttya,A),可以连接一个终端或者另一计算机(通过交叉串口线)到

这个端口。
如果另外的计算机是SUN工作站,可以使用tip hardwire来查看启动控制台。Tip命令输入之后,所有的启动信息会出现在相应的终端窗口里面

,如果被观察的机器用了STOP+A,必须使用Strg-]把tip改成telnet模式,然后输入一个BREAK:telnet>send brk
1.3 重新启动SUN工作站
重新启动之前,工作站需要进入一个合适的状态,可以使用下面及格命令退出:
# init 5
# init 6
# init 0
SUN工作站在重新启动之前,会以一定的方式关闭系统,下面是几种可选择的方法:
最好、最干净的方法:
- # init 5 关闭所有进程,然后系统自动关掉电源。
- #init 6,关闭所有进程,然后系统自动从新启动。
- #init 0,关闭所有进程,然后系统自动回到OK状态。
在这3种情况下,在系统关闭之前执行/etc/rc0.d下面的K*脚本,这可以用于关闭数据库,或者关闭WEB服务器的进程ID文件等。
如果没有合适的shell窗口,可以使用远程登录来执行上面的命令。远程也不可以,可以使用下面的方法:
- STOP-A
- OK sync 同步文件系统,中断OS,然后重启。
注意:需要察看随之产生的保存在/var/crash/<hostname>下面的内核转储文件。
如果碰到STOP-A也不能使用的情况,必须关闭计算机电源,在打开电源。
1.4 系统的测试
启动系统,在执行到内存测试的时候,按下STOP-A。
下面的段落只描述很重要的命令。
详细的描述可以在下面的书中《Hardware Diagnostics for Sun TM Systems: A Toolkit for System Adminis-trators》(Infodoc Number

23476 on http://sunsolve.sun.com)。
1.4.1 Banner(标识)
Banner显示下列信息:
. CPU的数量、类型和频率
. 内存
. MAC地址和主机地址(主机号)
. 控制台显示卡类型
. Sun-Globe: Creator 3D
. Sun with face: Elite 3D
. Self-describing Logo: PGX, Raptor GFX (=PGX32), Expert3D(lite) XVR-500, XVR-1000 etc.
1.4.2检测
probe-scsi-all列出内置和外置的所有SCSI设备。对于IDE硬盘,使用:probe-ide-all.
1.4.3 测试所有
test-all.运行所有的自检,可以检测象网线是否正常等情况。
1.4.4 printenv and devalias
devalias列出所有别名,printenv列出所有变量。例如boot-device变量,定义了计算机启动的路径,一般情况下是disk,disk是一个指向特定

设备的别名。
1.4.5禁止网卡错误信息
当网卡在没连接网线启动的时候,"Interface: No link"会出现多次,可以在OK下面设置
setenv tpe_link test false 来禁止这个报错信息。

2 管理硬盘
2.1 逻辑设备名
所有的硬盘都有一个共同的命名方案,该方案包括一下四个部分:
c 控制器号
t 目标号,对于SCSU设备,就是对应的SCSI地址。
d 驱动号或者是RAID设备的LUN 号(一个地址,多个硬盘)
S 分区号(见下一段)
这些逻辑设备的以连接方式放在/dev/dsk和/dev/rdsk目录里面,并指向物理设备名。但这些物理设备仅在改变启动设备时用到,一般的命令都

以逻辑设备为参数。
例如:
c0t0d0s0 通常是工作站的第1硬盘,c0t1d0s0 是第2个 c0t6d0s0 是内部光驱(SCSI地址是6)
在OK提示下输入 boot ?Cr可以更新/dev/dsk and /dev/rdsk目录里面的内容,devfsadm也可以。
Solaris8以前的需要用drvconfig;disks;devlinks3个命令来完成,刷新之后,probe-scsi-all所看到的设备的逻辑连接也就能看见了。
2.2 分区
下面是一个两个硬盘的工作站用format命令看到的内容
c0t0d0
c0t1d0
这里显示的是硬盘,不是分区或扇区,format的目的就是产生和管理分区,一个硬盘最多可以优个分区,用format-print可以查看。如果使用缺

省安装,Solaris安装工具就会把系统盘分成slices: / (root) /usr /var 和 /export/home。Solaris 8操作系统缺省安装产生3个分区,大约

1G的根区,SWAP区(根据内存容量确定大小),剩余空间给/export/home.
对于工作站来说,这个缺省分区不是最优化的,分区的容量不能在数据不丢失的情况下扩充。在分区空间用尽的情况下,必须使用符号连接。

对于服务器来说,得保留一个分区给/var目录,
这样的话,保存在/var/tmp里面的记录和大文件就不会影响到根区,避免系统产生问题。
建议工作站这样分区
slice 0: / root-Partition, incl. /usr, /var, /opt plus
/export/home
slice 1: swap 交换区,和物理内存一样大
slice 2: backup 不要改变这个分区!!代表整个硬盘
slice 7: sds 用于Solstice DiskSuite(TM) metadata的小分区
为了使用Solstice Disksuite logical volume manager (SDS),建议保留30M,用于拷贝"state replica databases".可以参阅后面的使用SDS

镜像启动盘
从Solaris 8 02/02开始,会自动安装Live Upgrade。使用Live Upgrade,可以在系统正在运行的时候拷贝当前根的环境。例如可以测试新的操

作系统而不用改变正在运行的系统。为了产生这个预备启动环境(ABS),必须留出足够的空间,现在的硬盘空间足够大,可以使用下面分区:
slice 0: / root-partition, incl. /usr, /var, /opt
slice 1: swap swap-Partition, as large as physical memory
slice 2: backup Do not change this partition!
It is always the size of the whole disk
slice 3: /export OS-independent data, home-directories
slice 4: <not mountet>, to be used as ABE with Live Upgrade
( about 150% of the expected size of / )
slice 7: sds A small space reserved for Disksuite metadata
2.3 Crashdumps
从Solaris 7开始,缺省情况下crashdump写到交换区上,这也是为什么交换区为什么和内存一样大的原因之一。下次启动的时候,从交换区中读

取这个文件,压缩之后写到var/crash/<hostname>里面,用于以后的分析。因此/var(如果没有单独分出/var或者是/区)不能太小。
如果一个系统用了几个G的空间作为dump设备,启动时会由于上面提到的拷贝操作,花上很长时间。这时候,最好利用dumpadm定义一个dump分

区,从这个指定分区拷贝的操作会在后台运行。注意经常检查和清除/var/crash/<hostname>,保证有足够的空间用于其他的crashdumps.
2.4 Example
假如有一台E450系统并且有一个已经配置好的启动盘,但你不知道两件事情:(1)盘插哪里(系统又20个槽,必须装到安装系统时硬盘所在的

槽里才能启动),(2)忘了超级用户口令。把 盘插到一个任意槽里,按下面的步骤:
第1步 插入一个启动光盘(SOLARIS 安装盘)STOP+A,然后 boot cdrom

?Cs。如果用外置光驱,就需要probe-scsi-all列出光驱位置,然后boot <光驱路径> -s。系统起来之后,就有了一个不需要口令的超级用户窗

口。
第2步 开始mount硬盘。首先用devfsadm重新建立设备,在/dev/dsk下面会有8个新文件。接下来使用format-print-partition确认盘已经装好

,并找出来哪个分区是root分区,通常都是0区 。假设盘插到2槽,format会识别到c1t2d0(SCSI ID是2,第1个控制器),c1t2d0s0就是root分

区,因此应该ount这个盘,fsck ?CY /dev/rdsk/c1t2d0s0;mldir /tmp/disk;mount /dev/dsk/c1t2d0s0 /tmp/disk
第3步

首先清楚/tmp/etc/shadow里面的root后面的加密内容清空,然后查看/tmp/etc/vfstab里面看看该盘是不是第2槽的,如果是,/应该mount在/d

ev/dsk/c1t2d0s0,如果不是,就把硬盘插到相应的槽里面。
第4步

假设在/tmp/etc/vfstab里面看到的是/dev/dsk/c1t2d0s0,这说明在原来的系统里面启动盘就是第2槽,一般,E450会从第1槽启动(c1t0d0s0

)。在/dev/dsk里面找到并记下来c1t0d0s0指向的物理设备文件名。
第5步 reboot,STOP+A,输入:boot <物理设备文件名>。
第6步 定义别名, nvalias my disk <物理设备文件名>,这样就可以输入boot mydisk就可以启动了。定义setenv boot-device mydisk,系统

就会自动从mydisk启动了。
2.5 Removable Devices
2.5.1 Volume Management(卷管理)
可移动设备又卷管理器来负责,卷管理器检测CD-ROM并自动挂接到/cdrom/cdrom0下面。一些手工操作会造成卷管理器错误,比如使用曲别针打

开光驱,而不是使用eject cdrom命令。只这时候需要手工重起卷管理器.
/etc/init.d/volmgt stop
/etc/init.d/volmgt start
2.5.2 软驱
插入软盘(UNIX或DOS格式)必须使用volcheck才能使用卷管理器管理软驱。如果volcheck成功,软驱会出现在/floppy/floppy0下面,使用eje

ct floppy而不手工取出软盘。
2.5.3 CD-ROM
CDROM不需要volcheck,开机就会被检测到,并挂接在/cdrom/cdrom0下面,没有shell或命令使用/cdrom路径时可以使用eject cdrom弹出光驱,

fuser ?Cc /cdrom/cdrom列出所有和光驱有关的进程ID或命令,fuser ?Ck可以快速杀掉相关进程。
如果没有卷管理器,使用下面的方法管理CDROM(假设SCSI地址是6):
- mkdir /tmp/cdrom
- mount -F hsfs /dev/dsk/c0t6d0s0 /tmp/cdrom
- (Work with CD mounted on /tmp/cdrom)
- umount /tmp/cdrom
- eject /dev/dsk/c0t6d0s0
2.5.4 CD-R 和 CD-RW
CDRW,是一个与solaris 8

捆绑的工具,用来烧制CD-R或者CD-RW介质。这个工具也可以烧制用命令mkisofs从solaris文件系统和目录中创建的ISO镜象文件。
看下面带精确参数的例子:

#! /bin/sh
# ./nfscd <PARAMETER1> <PARAMETER2>
# PARAMETER1 = Application ID
# PARAMETER2 = from where
#
# ./nfscd SunNetManager /net/center2000/export/pkg/SUNWsnm
# set Umask
umask 022
# Remove old image
rm /data/disk.img
# Create image
mkisofs -A $1 -d -l -L -o /data/disk.img -P "Sun Microsystems" -p
"Solaris SPARC" -r -R -J -V "$1" -v $2
# Burn
cdrw -i /data/disk.img

2.5.5 磁带
磁带设备不是用卷管理器来控制的,它们必须用命令tar来处理。典型的磁带设备被作为一个外部SCSI设备连接到系统。当solaris OE 运行时

它们的连接和配置就已经被完成了,在运行以前版本的solaris命令devfsadm(solaris 8 OE) 或者drvconfig; tapes;devlinks,你能找到一些

逻辑的连接在/dev/rmt 目录。0描述的是第一个磁带设备,1是第二个磁带设备。
通过以上,你就可以访问这个磁带,用命令 tar tvf /dev/rmt/0.
注意:SGI系统写入的磁带通常只能用特定的块因子(blocksize)来读
tar tvfb 512 /dev/rmt/0
在某些情况下,需要进行字节交换操作,才能正确读取
dd if=/dev/rmt/0 conv=swab | tar tvfb 512 –
2.6 克隆磁盘
通过这部分的学习,你可以把一个系统克隆到另一个和它的硬件属于同一系列的系统中。例如克隆Ultra(TM) 10工作站到Ultra 10工作站是可

以的,而克隆到Ultra 60工作站就是行不通的。
如果监测到目标系统的硬件结构和源系统有所不同的话(不同的结构缓冲,网卡等),目标系统在克隆以后需要用boot ?Cr命令来重新配置。
2.6.1 使用内部磁盘来克隆
这种方式适合用于易于改变的内部磁盘:
?Ultra 30, Ultra 60, Ultra 80或者Sun Blade(TM) 100工作站或者E220R/E420R/E250R/E450
?Sun Enterprise(TM)220R, 420R, 250, 或者450服务器
内部的FC-AL磁盘(如Sun Blade 2000工作站和Sun Fire(TM) 280R或V880服务器)可能导致一些问题。
接下来是用Ultra 60工作站做的一个范例:
1 将目标盘插入主板上的第二个硬盘口
3 启动进入但用户模式(STOP-A; boot ?Cs)
4 用devfsadm重新配置所有的磁盘
5 键入format察看硬盘的名称。在Ultra 60工作站上,你会看到c0t0d0和c0t1d0。C0t1d0是内部系统中最顶端的磁盘。
6 决定哪个盘是源盘,哪个盘是目标盘(不要忘记备份你的数据!)。在我们的例子中c0t0d0(底部的盘)作为源盘,c0t1d0(上面的盘)是目

标盘。
7 dd if=/dev/rdsk/c0t0d0s2 of=/dev/dsk/c0t1d0s2

bs=128k(这条命令为整个盘,包括分区标的内容作了一个1:1的副本。如果源盘是一个9GB的盘,而目标盘是一个18GB的盘,那么将会有9GB的

容量被浪费)。警告:仔细检查这一步,避免对你源盘的意外损坏。
8 fsck ?CY of=/dev/dsk/c0t1d0s0(检查启动分区的文件系统,一般来说是分区0)
9 移除源盘,将克隆好的盘放进先前源盘所在的位置。重新启动你新做好的克隆的系统

2.6.2 通过转接线来克隆
这种方式比上面描述的通过内部磁盘克隆要慢,但是它适合用于一些内部的磁盘不容易改变的系统(如Ultra 10或Ultra 5工作站)。
警告:记住文件“/.rhosts”仅仅在这个任务中使用,在完成这个任务之后删除它来避免系统的安全漏洞。
目标系统
启动
在注册屏幕上使用命令行注册(command line login)
尽可能地停止引起磁盘输入输出的进程
TERM=vt100vi /etc/inet/hosts
->添加目标和源
ifconfig hme0 plumb
ifconfig hme0 <target> netmask 255…up
->测试rsh源
rsh source “dd if=/dev/rdsk/c0t0d0s2 ibs=128k obs=8k” | dd of=dev/rdsk/cotodos2 bis=128k obs=128k
(9GB的盘需要等待大约40分钟)
STOP-Aboot cdrom ?Cs(用可引导的光盘引导)
fsck ?CY /dev/rdsk/c0t0d0s0
源系统
在文件/.rhosts中添加目标IP
sync; sync;

2.7 用SDS制作磁盘镜像
Solaris逻辑卷面管理(SVM),一般是指Solstice Disksuite(SDS)软件,它可以把磁盘系统做成RAID 0, 1, 0+1或5。工作站最感兴趣的方式

是RAID 1:为磁盘错误准备好了一个包含启动文件系统的磁盘镜像。
SVM是Solaris 9系统环境的一部分。在Solaris 8平台上,你可以在第二章光盘的“EA”目录下找到SDS软件。而在Solaris 7或者2.6系统环境

中,服务器版,你可以查找一下光盘“Easy Access Server”。
我们假定一个工作站有两块内部的磁盘,c0t0d0和c0t1d0。这两块盘都以相同的方式被正确的分区:分区0被分配给/(root),分区1被分配给交

换区(swap),一个比较小的分区7被分配用来记录复制数据库(State Replica Databases)。分区2被定义成一个备份区(参见“分区”一章)。
把第一块盘的分区信息复制到第二块盘,可以用如下的命令:
# prtvtoc /dev/rdsk/c0t0d0s2 | fmthard -s – /dev/rdsk/c0t1d0s2
记录复制数据库是一个包含了镜像信息纪录的小文件。镜像中的每个磁盘都必须知道有三个记录复制数据库文件:
# metadb -a -f -c 3 c0t0d0s3 c0t1d0s3
下一步,在第一块磁盘上创建第一个子镜像,它包含了启动文件系统:
# metainit -f d50 1 1 c0t0d0s0
然后在第二块磁盘上创建第二个子镜像:
# metainit d52 1 1 c0t1d0s0
使用第一个子镜像创建镜像的第一个部分:
# metainit d54 -m d50
用metaroot修改/etc/vfstab和/etc/system,让系统可以从元设备(metadevice)d54上启动:
# metaroot d54
重新启动系统,然后将添加第二个子镜像d54。这就强制了“镜像同步”,将第一块盘的内容复制到了第二块盘。这将在一小段时间内影响系统

的性能。
# metattach d54 d52
为了避免在从第二块盘上紧急启动时发生错误信息,交换分区必须做成相同样式的镜像。否则,交换分区在损坏的磁盘上,/etc/vfstab仍然会

定位到c0t0d0s1。
现在系统已经可以从第一块盘(boot disk),也可以从第二块盘(boot disk1)上启动了。
如果有一块磁盘损坏,Solstice DiskSuite会停止启动程序。这是因为如果要在一次错误以后成功地启动系统,纪录复制数据库的大部分必须

依然“幸存”。在我们的两块镜像盘上仅仅需要六个复制中的三个依然存活。
超过两块盘的服务需要有第三块盘来用记录复制数据库来配置。两块盘的工作站必须手动地修复,下面的例子描述了这个过程:
?记录数据库复制在c0t0d0s7和c0t1d0s7上配置,c0t0d0盘发生故障。
?启动程序被Solstice DiskSuite停止,并转换进入到单用户模式。
?现在记录数据库复制希望发生错误的盘没有被配置:
metadb -d -f c0t0d0s7
?在下一次重启之后,对重置的磁盘进行分区,使它和仍然活动的磁盘的分区一致。
?做完这些之后,在重置的硬盘上创建记录数据库复制文件:
metadb -a -c 3 c0t0d0s7
?在下次的重启之后,镜像又可以重新同步了。
注意:
如果你有一个两块盘的系统,在一块盘上创建三个记录数据库复制,在另一块上面创建四个。在理论上有50%的机会可以在一次错误后重新启动

系统而不需要手动地去使用元数据。
如果你能接受在2盘系统下发生错误后无人职守的重启,在两块盘上创建相同数量的记录数据库复制并且创建/etc/system条目:
set md:mirrored_root_flag=1
关于这方面内容更多的讨论可以在Sun的蓝皮书“Configuring Boot Disks With Solaris[tm] Volume Manager

Software"(http://www.sun.com/blueprints/1002/817-0407-10.pdf)上找到。

3 SWAP交换空间
3.1 固定配置文件 /etc/vfstab
#device device mount FS fsck mount mount
#to mount to fsck point type pass at boot options
#
#/dev/dsk/c1d0s2 /dev/rdsk/c1d0s2 /usr ufs 1 yes ?C
/dev/dsk/c0t0d0s1 – - swap – no ?C
/usr/local/swapfile.1 – - swap – no ?C

swap – /tmp tmpfs – yes ?C
交换文件系统可以使用像/dev/dsk/c0t0d0s1这样的裸分区或者象/usr/local/swapfil.1的空文件。
你可以定义多个交换文件系统,所有这些都在逻辑上绑定并挂接到/tmp挂接点。这就意味着只要物理内存是可用的,向/tmp中写文件将写入内

存并耗尽内存(not very sure)
例如:
/dev/dsk/c0t0d0s1 – - swap – no ?C
/dev/dsk/c0t1d0s1 – - swap – no ?C
/dev/dsk/c0t2d0s1 – - swap – no ?C

swap – /tmp tmpfs – yes ?C
上面创建了另外的6GB的虚拟内存(假设所有的分区大小为2G).Solaris将条带访问对所有的磁盘。
3.2 "裸分区”和空文件的比较
最快速的交换就是不进行交换,假如物理交换不可避免,使用在多可磁盘上条带后的分区。如果实在需要内存或者没有可用的分区或磁盘,则

使用空文件系统。
3.3 临时配置
在标准的SOLARIS操作中,空文件可以加入到交换空间。
# mkfile 100m /usr/local/swapfile.1
# swap -a /usr/local/swapfile.1
# swap -l
在系统重启之前,这些额外的空间是临时的。可以使用命令swap -d /usr/local/swapfile.1去掉这些额外的交换空间

4 系统安装和维护
4.1 CD安装
在安装过程中,你尽可以轻松地按照屏幕的提示进行,但也许会有一些小的缺陷;
- Name Service:选择 NONE.如果你这时候选择象NIS这样的内容,因为网络还没有安装好,所以没有NIS服务可用,这时系统可能会“挂起”

等待NIS服务器响应,在基本的网络安装完成后再配置NIS或DNS会更好些。
- 安装群集:对于工作站而言,应该始终选择"Entire Plus OEM Software"项来确保安装所有的内容。对于专用服务器,去掉不必要的服务包

可以最大降低安全风险。参考 SUN 蓝皮书《Minimizing the Solaris[tm] Operating Environment for Security: Updated for Solaris 9

Operating Environment>>
(http://www.sun.com/blueprints/1102/816-5241.pdf) 可获得更多内容
- 磁盘分区:根据再“分区”这一章所提供的建议,为了能够对磁盘进行分区,当提示安装方式时,选择“定制”方式而不是“默认”方式。
从 Solaris 7 OE版本开始,安装CD内含有预打包的Solaris映像.如果需要升级以前的季度升级包到该映像文件的版本,参考“维护升级”
例如:安装“维护升级 7”(同Solaris 8 02/02 OE发布)到Solaris 8 07/01 OE版本需要升级Solaris 补丁包(只这些,没有功能的增强)

到在02/02版本上的状态。升级安装在/etc/release文件中写入如下标记:"Solaris 8 07/01 Maintenance Update 7" “推荐补丁集”采用另

外的方式,用户每月在“推荐补丁集”上可以看到不同补丁包和补丁版本。 这些可以从http://www.sun.com/bigadmin下载并且在Solaris安装

后安装。
安装完毕后,root用户只能从system控制台登录,这种终端方式直接连接到系统或键盘。为了允许通过TELNET,RSH等从远程登录,必须修改一

个文件:注释该行:CONSOLE=/dev/console!in!/etc/default/login.注意:这是一项安全分险。

4.2 包的安装
包是通过tar/compress/zip等工具压缩的一些目录文件,其命名规则名字如下:前面的四个大写字母代表发行该包公司的US股票代码,后面的

小写字母代表其功能。例如SUNWspci2这个软件包,就是SUN-pci卡的驱动程序包,前面的SUNW是SUN的股票代码,后面的spci2是其功能(PCI卡

驱动)。
安装命令如下(后面的目录里必须包含这个包)
pkgadd -d /cdrom/cdrom0
pkginfo 打印该包的信息,pkgrm <Package-Name>删除(卸载)已经安装的包。
例如:你的机器原来安装的显示卡是Creator3D(ffb)换成了Elite3D(afb),这样开机的时候会提示"no console framebuffer"(没有显示卡

),说明没安装Elite3D驱动程序。使用
pkginfo | grep -i afb
命令检查,没有输出,说明安装系统的时候没有安装Elite3D显示卡的驱动,而Elite3D的驱动程序在安装盘的s0/Solaris/Product. 进入这个

目录,使用下面命令安装即可:
pkgadd -d . *afb*
重新启动机器,新安装的显示卡就可以正常显示了。
4.3补丁安装
补丁的数字代码包含了两个部分,6位数字的补丁号,后面是两位数字的版本号,人员123456-78。
Solaris8使用的是*.zip格式的补丁压缩,以前使用的是*.tar.Z的格式的包。安装补丁的时候,先拷贝到/tmp下面,然后接压缩,安装之后要

删除这些临时文件。解压缩办法:
zcat 123456-78.tar.Z | tar xvf –
unzip 123456-78.zip
然后使用patchadd 123456-78这样的命令安装这个补丁。使用patchadd 123456-78安装的补丁可以使用pkgrm删除(卸载),/var/sadm/patch

纪录了这些安装纪录,这个目录没有的话,pkgrm就不能正常删除补丁了。
showrev -p | grep <patch number>可以查找到已经安装的补丁包。

5 X-Server和CDE用户接口
5.1 常见的需要注意的问题
X-Server或者CDE总是按照以下顺序搜索配置文件:
1. $HOME/.dt/…
2. /etc/dt/…
3. /usr/dt/…
注意,永远不要改动在整个系统范围内都生效的/usr/dt/中的内容。如果系统的X-Server或CDE出现 问题,总是使用/etc/dt/下的文件去维护

或恢复设置。用户自己的配置如扩展按钮或面板的改变等,配置总是保存在$HOME/.dt/目录下。在本节以下的内容中,将用<DT>来代替这些目

录($HOME/.dt, /etc/dt,/usr/dt)。
5.2 定制登陆界面
文件:<DT>/config/$LANG/Xresources
Logo(象素图或位图,24位色或以下,广泛装载时间):
Dtlogin*logo*bitmapFile: /usr/local/lib/X11/dt/bitmaps/Mylogo.bm
通用欢迎登陆语句:
Dtlogin*greeting*labelString: Heres %LcoalHost%!
输入用户名后的欢迎:
Dtlogin*greeting*persLabelString: Hello %s
5.3 从多屏幕登陆,Xinerama,和24位色
首先查看 /dev/fbs,找出有几个显示卡可用,它们是如何命名的. 确认有 /dev/fbs/ifb0和/dev/fbs/ifb1,两个 Expert 3D显示卡。
然后,把X-Server文件从/usr/dt/config复制到/etc/dt/config,编辑该文件,确认最后一行与以下内容相同:
:0 Local local_uid@console root /usr/openwin/bin/Xsun :0 -nobanner
5.3.1 多屏幕
多屏幕是指CDE在每个屏幕中运行一个完整的用户面板。鼠标可以在两个屏幕间移动,而各屏幕上的窗口不能在两个屏幕间移动。ifb0的的显示

屏幕被称为: :0.0, ifb1的显示屏幕被称为: :0.1
:0 Local local_uid@console root /usr/openwin/bin/Xsun :0 -dev /dev/fbs/ifb0 -dev /dev/fbs/ifb1 -nobanner
5.3.2 Xinerama
Xinerama(Solaris 7 11/99 OE以后的版本有效)是一个覆盖多个显示屏幕的的虚拟显示屏幕。鼠标和程序窗口可以在多个显示屏幕之间移动

, CDE只生成一个用户前端面板。只有一个显示号码: 0:0. Xinerama 只在使用相同的显示卡时才可用。
:0 Local local_uid@console root /usr/openwin/bin/Xsun :0 +xinerama -dev /dev/fbs/ifb0 -dev/fbs/ifb1 -nobanner
Xinerama 允许定义一X和Y方向的重叠,这种设置使显示画面边扩展到多个显示器上,使两个显示器显示一个图形窗口(Xoverlap=<水平解析象

素>),下面是一个在1280像素的的水平分辨率,20%边缘重叠显示的配置例子:
:0 Local local_uid@console root /usr/openwin/bin/Xsun :0 = xinerama -xoverlap 256 -dev /dev/fbs/ifb0 -dev /dev/fbs/ifb1

-nobaaner
5.3.3 使用24位色深
CDE本身默认使用8位色深(256色). 不过这没有问题,因为每个窗口可以使用单独的颜色位深设置。某些显示卡只有一个颜色表,这可能导致在

某些应用中出现程序窗口颜色闪烁的现象。只需要将CDE修改成使用24位色深。如果使用PGX显卡,需要使用命令
fbconfig -degth 24
将框架缓冲区切换到24位。
注意:由于某些老的应用只支持8位色深,不是24位色深,可能会出现问题。
:0 Local local_uid@concole root /usr/openwin/bin/Xsun :0 -dev /dev/fbs/ifb0 defdepth 24 -dev /dev/fbs/ifb1 defdepth 24

?Cnobanner
5.4 用户定制CDE面板
CDE中的应用启动机制叫做“活动”。当用户将鼠标点到一个CDE图标是,“活动”发生预定义的行为,一旦定义以后,“活动”的关联到环境

变量中:Menus, file manager, MIME-types 和定义文件
5.4.1 活动(Actions)
CDE 按照以下顺序搜索*.dt-files配置文件:
$HOME/.dt/types
/etc/dt/types/$LANG/
/usr/dt/types/$LANG/
例如,StarOffice52.dt(这是在$HOME/.dt/types下安装完StarOffice之后)
ACTION StarOffice52
{
LABEL StarOffice 5.2
TYPE COMMAND
EXEC_STRING "/opt/Office52/program/soffice" "%(File)Args%"
ICON So52
WINDOW_TYPE NO_STDIO
DESCRIPTION StarOffice
}
5.4.2 图标
一个活动的的定义还包含一个图标名。图标名按照以下顺序解析:
$HOME/.dt/icons
/etc/dt/icons/$LANG/
/usr/dt/icons/$LANG/
naming scheme: <name>.<size=m|t|l>.<format=bm|pm>
例如StarOffice52的图标文件存在以下位置: $HOME/.dt/icons/So52.m.bm
5.4.3 用户前面板
通过添加*.fp-files文件到$HOME/.dt/types目录下,可以方便简单的扩展用户前面板。默认定义存储在:
. /etc/dt/types/$LANG/
. /usr/dt/types/$LANG/
例如($HOME/.dt/types/tools.fp)
CONTROL Tools (Always define the main control before sub panel)
{
TYPE icon
CONTAINER_TYPE BOX
CONTAINER_NAME Top
POSITION_HINTS first
ICON Dtagen (This is a folder icon, Icon must exist!)
LABEL Tools
}
SUBPANEL Toolspanel
{
CONTAINER_NAME Tools
TITLE My Tools
}
CONTROL StarOffice52
{
TYPE icon
CONTAINER_TYPE SUBPANEL
CONTAINER_NAME Toolpanel
ICON So52
LABEL Star Office 5.2
PUSH_ACTION StarOffice52 (compare ACTION StarOffice52)
DROP_ACTION StarOffice52
}
你可以通过在子面板(SUBPANEL)里添加额外的控制(CONTROLs),来建立整个菜单按钮

6 显示卡和OpenGL

6.1 Fbconfig
在Solaris 8 系统环境中,fbconfig是配置显示卡的唯一的程序接口,每个显示卡有自己的专用配置工具。在Solaris 8 软件中,通过有规则

的命名方式来与/dev/fbs下的设备名对应。
下面是一些显示卡配置命令及对应的显示卡类型
. ffbconfig Creator, Creator3D
. afbconfig Elite3D
. ifbconfig Expert3D, Expert3Dlite
. m64config PGX24 (onboard U5, U10), PGX64
. gfxconfig PGX32
在下面的内容中,只提及fbconfig,其他命令的参数都是一样的。如果没有在配置中把某个设备指定给某个显示卡,fbconfig工作在默认的显

示卡(接主屏幕的显示卡)。
可以使用 ls /dev/fbs命令查找系统中安装了哪些显示卡。另外,可以使用下列命令通过设备名直接列出系统中安装了的显示卡。
fbconfig -res /dev/fbs/afb1 -propt -prconf
这个命令可以列出第二个Elite3D显示卡的配置信息,例如显示卡的类型、分辨率等。第一个Elite3D显示卡使用的设备名是 afb0。
fbconfig -res \?
这条命令将列出该显示卡支持的分辨率,指定的分辨率可以"剪切&拷贝" 到下面命令里面:
fbconfig -res "1280×1024x86" -try -now
-try和-now都是选项。
-try将只会改变10秒钟的分辨率。可以使用这个方法测试显示器和改变之后的视频信号是否同步。-now参数使分辨率改变立即生效,不需要重

启X-Server。然而,有些时候使用-now选项改变分辨率后,屏幕显示会有一点几何失真,可以用退出用户再重新登陆来重新启动X-Server的方

法来解决。
fbconfig -g 1.7
命令将gamma因子设定到1.7,默认值是2.2,通常看起来可能太亮了。
新的gamma因子设定,不用重启X-Server就可以立刻生效,而且重启后依然有效。
6.2 ogl_install_check
尽管在Solaris 2.5.1以后的版本的CD中都有OpenGL软件包,但在Solaris系统安装过程中,不会自动安装。 使用以下命令检查该系统上是否正

确安装了OpenGL,以及OpenGl软件的版本:
/usr/openwin/demo/GL/ogl_install_chech
注意:在正确的设置了gamma因子后,OPENGL的测试环的设置也很简单了。
推荐使用最新版本的OpenGL。 例如Xinerama只能支持OpenGL1.2.1以后的版本。OpenGL的下载地址
http://www.sun.com/solaris/opengl

6.3 和图形卡相关的补丁
下面是和显示卡有关的补丁列表,它和Solaris版本、OpenGL版本和显示卡类型有关系。
下表只列出来补丁号。请到http://sunsolve.sun.com下载最新版本的合适的布丁。补丁的安装顺序如下
1.显示卡补丁
2.X-Server补丁
3.OpenGl
4.OpenGL补丁

显示卡补丁列表:(详细列表参见原版21页)
显示卡类型 Solaris 9 OE Solaris 8 OE Solaris 7 OE
Expert3D (lite) 112540 108576 108787
XVR-500 112540 108576 不支持
XVR-1000 112565 112564 不支持
Elite3D 111620 108604 106144
- and XFB 106148
- and VIS 109872 106147
Creator3D 112621 108605 106145
- and XFB 106148
- and VIS 109872 106147
OpenGL 1.2.2 32 Bit: 111993 32 Bit: 111993 32 Bit: 111993
64 Bit: 111994 64 Bit: 111994 64 Bit: 111994

OpenGL 1.2.3 32 Bit: 112628 32 Bit: 112628 32 Bit: 112628
64 Bit: 112629 64 Bit: 112629 64 Bit: 112629
XSun 112785 108652 108376
PGX32 /64 112622 109154 107716

7 管理
7.1 主机
你可以用admintool或者smc创建主机列表文件hosts,直接编辑/etc/inet/hosts文件更简单。提示:/etc/hosts文件是/etc/inet/hosts文件的

连接。
使用复制命令cp /etc/host.old /etc/hosts不会覆盖正确的文件。Solaris默认安装方式启动sendmail守护进程。如果主机名不包含点号(.)

的话,sendmail守护进程会输出错误信息。为了抑制错误信息,在/etc/inet/hosts文件里应该像下面这样定义主机信息: 192.168.0.2

myhost myhost.domain.com loghost
7.2 串口
同样,常用的最佳工具是admintool或者是Solaris 9环境下的“Solaris 管理控制台(Solaris Management Console)”smc。
7.3 打印机
同样,常用的最佳工具是admintool。Solaris 9环境下,有系统自带的“打印机设置向导”/usr/sadm/admin/bin/printmgr。
7.4 创建用户
创建用户常用的最佳工具是admintool。从Solaris 9平台开始,使用“Solaris 管理控制台”smc。当创建新用户时不要指定/home/<username>

作为用户的起始目录(须禁止autofs)。用下面的更好: /export/home/<username>。
/home/<username>目录默认作为Solaris自动装载器(automounter)的目标目录,因此不可写。一些第三方软件试图使用/home/xyz目录。为避

免出错,应该关闭自动装载器或者改变它的默认设置。
7.5 Shell和环境变量
正确的设置环境变量是系统设置中最有效的减少错误的方法之一。不幸的是,在使用不同的shell的情况下,如何设置变量以及针对特定用户的

性能优化所用的语法各不相同。
最常用的命令解释器是sh,ksh和csh。从Solaris 8开始,流行的bash也可以使用了。sh是安装完成后root用户默认的命令解释器。一般情况下

,用户的默认命令解释器在/etc/passwd文件中定义,例如:demo:x:2003:10:Demo User:/export/home/demo:/bin/csh 下表描述如何在不同的

命令解释器中设置变量:表1(见附件)
所有命令解释器在启动时读取一系列文件。登录命令解释器(login shell,通常在用户使用telnet,ssh等登录系统后启动)与子命令解释器

(subshell,即sh,ksh,csh或者bash的统称)读取的文件不同。下表描述了不同命令解释器所读取的文件:表2(见附件)
表格在原文P23页。

8 网络连接
8.1 /etc目录下的文件
Solaris

软件在/etc目录下存放网络配置文件。所有的配置数据比如ip地址、网关等可以被定义或者在运行层进行修改,而只有那些存放在/etc目录文

件中的定义可以在系统重启后可以保存下来,永久生效。
可以在这些文件中找到主机名(hostname):
/etc/nodename
/etc/hostname.<interface-name>
/etc/inet/hosts
/etc/net/{ticlts,ticots,ticotsord}/hosts
/etc/nodename 这个文件定义了系统名,从网络的观点看,一个有多块网卡和多个ip地址的系统可以有多个名字。每个都在/etc/inet/hosts中

有定义。 这些名字中的任何一个都等价于系统名( nodename),但是他们不是必须的 。
有些文件产生了ip地址跟网卡接口名之间的连接。他们是/etc/hostname.<interface-name><number>。
最常见interface name是:
le 老式sparc系统上的以太网网卡名
hme Ultra sparc系统上的快速以太网网卡名
eri UltraSPARC-III 系统上的快速以太网网卡名
qfe 基于四口扩展以太网卡上的快速以太网网卡名
举例: 一台有QFE (QuadFastEthernet)的Ultra 60 工作站有五个网络接口: hme0 (on board),qfe0,qfe1,qfe2 and qfe3 (on the

QFE-card)。主机名是 hermione, 则 /etc/inet/hosts文件的内容如下:
127.0.0.1 localhost
157.168.34.12 hermione hermione.domain.com loghost
191.133.23.10 dumbledore
在公司的局域网上地址是157.168.34.12的系统就叫hermione,相应的,文件 hostname.hme0 必须包含 hermione 这个名字。
Dumbledore(191.133.23.10)是网络服务提供商分配的地址。这根缆线连接到QFE的第一个端口 qfe0 ,所以文件 hostname.qfe0 包含

dumbledore 这个字符串。
qfe1-3 现在没有使用。为了避免solaris出现“没有缆线连接”这种错误信息,对于没有使用的接口不要生成hostname.* 这种文件。
一个具有多个网络接口的系统会在各个子网之间自动路由,例如在我们例子中的157.168.*.* 和 191.133.*.* 子网之间。在系统启动的时候,

会显示出来"machine is a router"这条信息。 如果不希望启动路由服务,手工生成一个空文件 /etc/notrouter 接着重新启动系统就可以了


Ip地址的子网掩码定义在文件/etc/netmasks中。在X-Server运行时不要更改nodename,最好更改nodename或hostname之前,从当前的会话中退

出来然后使用命令行界面重新登陆。
8.2 虚拟网络接口
有时候,对于只有一块网卡的系统定义2个ip地址是很有用的。例如,商业演示用的demo工作站了为了与其它的主机通信而需要添加另一个地址

的同时也需要保留自己公司的ip地址,这个需求可以通过虚拟网络接口得以实现。实现方法就和上一章节描述2块物理网卡的情形一样, 仅有

的不同是我们这次不使用 hme0 和 qfe0 ,而是使用 hme0 和 hme0:1,所以如果你写dumbledore 字符串到/etc/hostname.hme0:1 文件,在商

业演示时ISP提供的网线就可以接到 hme0, QFE网卡就不再需要了。
8.3 ifconfig工具
ifconfig -a 列出所有物理和虚拟的接口定义,包括ip地址和子网掩码。 另外在系统运行期间我们还可以用ifconfig去定义ip地址和子网掩码

(在hpux系统中这样的定义当系统重新启动后就不存在了,不知solaris是不是这样)。 让我们再来看Ultra 60 工作站的例子,这次首先假定

dumbledore在/etc/inet/hosts中没有定义,那么 qfe0 在系统重新启动后就不再被使用。
现在第一步是去激活qfe0(加载驱动):ifconfig qfe0 plumb
开始配置这种网络接口:
ifconfig qfe0 191.133.23.10 netmask 255.255.255.0 up
ifconfig -a 可以看到新的配置
对于虚拟网络接口这个步骤是相同的:
ifconfig hme0:1 plumb
ifconfig hme0:1 191.133.23.10 netmask 255.255.255.0 up
8.4路由
可以将IP地址加入/etc/defaultrouter文件来设定默认路由(或者是标准网关)。如果在此文件中加入的是主机名,要确认/etc/inet/hosts文

件中已经定义了此主机名。 等价的命令是:
*删除所有当前路由:route flush
*将1.2.3.4定义为默认路由:route add default 1.2.3.4
*用netstat -r命令可以查看更改结果。
如果机器上有两个以上可用的网络接口,Solaris会自动启用IP转发。
要检查IP转发是否已经启用:ndd -get /dev/ip_forwarding
启用IP转发:ndd -set /dev/ip_forwarding 1
关闭IP转发:ndd -set /dev/ip_forwarding 0
另一种关闭IP转发的方法是创建/etc/norouter文件,例如touch /etc/norouter
8.5NIS客户机
-首先将NIS域名加入/etc/defaultdomain文件(区分大小写)
-然后在/etc/inet/hosts文件中加入NIS服务器
-运行ypinit -c,输入NIS服务器名称 -运行/usr/lib/netsvc/yp/ypstart或者重启。
现在要告诉Solaris操作环境,对于哪些项目(主机名,用户等等)使用NIS。/etc/nsswitch.conf文件对此进行控制。要使Solaris软件在/etc

/inet/hosts文件中找不到主机名时查询NIS服务器,可以在hosts开头的行中在files后面加上nis。
在/etc目录中有名为nsswitch.nis的预设文件,对于所有可用项目都带有nis定义。
8.6 DNS客户机
1. 在/etc/resolv.conf文件中输入DNS服务器地址:
nameserver 192.168.0.1
现在可以用nslookup www.sun.com测试DNS服务。
2. 告知Solaris解析主机名时使用DNS。编辑/etc/nsswitch.conf,在hosts开头的行的files后面加上dns。现在不只是可以使用nslookup

www.sun.com,也可以ping www.sun.com
8.7 DHCP客户机
如果创建两个空文件/etc/hostname.<网络接口名称>和/etc/dhcp.<网络接口名称>,此接口就配置为使用DHCP。Solaris会用30秒等待DHCP服务

器应答。可以在/etc/dhcp.<网络接口名称>修改此时间: WAIT <以秒计的时间>
可以在网络接口运行时启用DHCP(在此例中是对接口qfe0):
ifconfig qfe0 plumb
ifconfig qfe0 dhcp start
8.8 sys-unconfig
如果要更改网络配置,但是对相关的文件不确定,有一个简单的方法:sys-unconfig,关闭机器。在下一次重启(在OK提示符下输入boot)时所

有Solaris安装时网络相关的问题都会再次问到。(IP地址,网关,掩码等等)。
8.9 10Mb还是100Mb,全双工还是半双工?
可以用ndd /dev/<interface-driver>命令查询网络接口状态。<interface-driver>一般是le, hme, eri或者是qfe。
-ndd /dev/eri link_status: 0=down, 1=up
-ndd /dev/eri link_speed: 0=10Mb, 1=100Mb
Solaris对于所有网络接口的参数默认为“自适应”。如果添加的网络设备不支持自适应,就要手动设置参数。将多余的参数设为0。
10Mb半双工的例子:
ndd -set /dev/eri adv_100fdx_cap 0
ndd -set /dev/eri adv_100hdx_cap 0
ndd -set /dev/eri adv_10fdx_cap 0
ndd -set /dev/eri adv_10hdx_cap 1
ndd -set /dev/eri adv_autoneg_cap 0
用ifconfig unplumb关闭网络接口然后用ifconfig plumb重启接口。也可以在/etc/system文件中定义这些设置。(set eri:

eri_adv_10hdx_cap=1)

9.网络文件系统
9.1 NFS服务器
Solaris将目录信息存储在/etc/dfs/dfstab 中,以便于在网络上的其他系统安装。该文件是一个被share命令使用的脚本。
如果这个文件没有包含任何share命令,Solaris OE(OE,操作系统引擎??)在重起时不会自动开始共享网络文件系统,

即使明确的调用执行了/etc/init.d/nfs.server start命令,dfstab也是空的。
例如:共享/export/home
1、 添加share /export/home 到/etc/dfs/dfstab中
2、 如果这是第一次添加该文件中的内容,执行/etc/init.d/nfs.server start命令
3、 输入shareall
4、 如果/export/home被正确共享的话,使用share命令检查
9.2NFS客户端
一个NFS客户端不需要制定任何域,但是,NFS服务器名是重要的。一个IP地址 (mount 192.168.0.1:/export/home)是不够的,所有的NFS服务

器必须在/etc/inet/hosts,NIS或者DNS中定义。
你能够安装(mount)一个网络目录,使用命令 mount server:/export/home /tmp/home。如果这个安装点需要在下次系统重起的时候自动的安装

的话,在文件/etc/vfstab中插入该安装点的相关内容。
注意:当使用root用户在网络目录中拷贝文件的时候,注意,本地的root和网络目录上的root是不同的,都是作为"nobody"(除非在服务器上使

用了anon=0)。网络目录下的文件没有"other"用户的读权限是禁止拷贝的,为了使用root用户从网络文件目录中拷贝所有的文件,不要使用下

列命令:
root# cd server:/verz;tar cvf – | (cd verz;tar xvf -)
最好使用 rsh来在该服务器上获得 root用户的读权限:
root# rsh server "cd /verz; tar cvf -"|(cd verz;tar xvf -)
9.3 与其他的流行的UNIX系统的区别
在Solaris上的NFS系统与其他流行的UNIX版本中(Linux,AIX等)的实现不完全相同:
1)文件:Solaris 系统中文件/etc/dfs/dfstab,由/etc/exports取代;
2) 命令:Solaris 系统中的命令shareall由exportfs -a取代。
9.4 自动加载(AutoMounter)
自动加载功能可以实现在用户访问相关目录的时候自动的将文件系统加载(mount)。在大型网络环
境中,如果大量主机和用户的具有如下目录路径/net/<hostname>或/home/<username>是,将会非常有帮助。
两个文件定义自动加载的基本操作:/etc/auto_master和/etc/auto_home.
auto_master文件预定义了上述自动加载的行为。
目录/net将会在访问相关的主机是自动加载。
/home目录的自动加载功能在/etc/auto_home文件中定义。
/net -hosts -nosuid,nobrowse
/home auto_home -nobrowse
如果/etc/auto_home文件包含以下行
/export/home/&
当有人访问/home/joe目录时,自动加载功能将起作用,把/export/home/joe 加载到这个加载点
(/home/joe)

10 工具
10.1 编译器
c, c++和fortran编译器通常安装在/opt/SUNWspro/bin目录, 通用工具象make安装在/usr/ccs/bin下.
10.2 GNU工具包
几乎所有GNU工具包都有基于solaris平台的预编译包. 它们可以从http://www.sunfreeware.com下载.从solaris 8 OE开始,

一些重要工具象perl,gzip或apache是自动安装的, 剩下的绝大多数GNU工具包都作为预编译包存放于solaris companion CD上.
10.3 SUN PCi和SUNPCi-II
很多工作站都装备有SUNPCi卡. 这是一个运行WINDOWS(98,ME,NT,2000,XP)系统的完整PC.这里有一些关于操作SUNPCi卡的提示.
*封装/opt/SUNWspci2/bin/sunpci进一个小脚本以设置一些环境变量:
setenv NVL_INTERFACE=hme0 (如果SUNPCi要用hme0网络界面)
setenv LANG=C
setenv KBCP=850 (例如:德国键盘/代码页)
setenv KBTYPE=GR (还记得MS-DOS吗?)
*不但让root可写c:映像, 而且这个工作站上的每一个用户都要有666权限.当sunpci由一个用户启动时, 一个叫pc的子目录在该用户的HOME下产

生.为让该用户同全局c:映像协同工作, 中断随后的特定用户映像的创建, 并且编辑sunpci.ini文件以指向那个全局映像(例如:

/pc/C.diskimage).
10.4 staroffice 软件
如果staroffice软件由root执行setup /net安装, 所有用户能在他们的个别安装期间选择”工作站安装”选项. 工作站安装仅仅拷贝1MB数据进

该用户的home目录, 而其他的情况使用集中式安装. 如果staroffice软件安装在/opt/Office60, 用户能用/opt/Office60/program/soffice来

启动他的工作站安装.
10.5 netscape navigator浏览器
从solaris 8开始, netscape navigator作为标准浏览器. 它被安装在/usr/dt/bin/netscape.
10.6 杂项
提示: 当启动桌面时, 那个注册提醒器能用”more information”中的“never register”选项禁止.
要禁止所有用户中出现的的警告信息, 在/etc/default/solregis文件中加入一行:DISABLE=1

11.提示集
11.1 3个首要提示
l c-shell: 在~/.cshrc中定义 set filec和set history=100 savehist=50, 一旦你按下esc键, shell将完成文件名和目录名. History列出最

后的100条命令, 用!13用让编号13的命令重新出现.!$用于列出先前命令的最后一个参数, 象: mkdir /export/home/demo/test后键入cd !$.

从solaris 8开始, tcsh有效了, 它有用光标键编辑命令行和浏览历史命令的功能.
l 进程控制 : 从solaris 8 O以后, 可以用pkill <搜索文本> 去替代由ps ?Cef|grep <搜索文本>, 然后kill <搜索文本的PID>所能达到的功

能. Kill -9 -1中断属于当前用户的所有进程.
l 文件编辑: 不喜欢vi的人可用CDE的编辑器dtpad.
11.2 硬件配置分析
11.2.1 prtdiag
/usr/platform/`uname ?Ci`/sbin/prtdiag ?Cv命令显示CPU的数量及类型, RAM, 扩展卡等硬件配置信息.
11.2.2 prtcon/path_to_inst
prtconf 以阶层的形式列出设备目录. 用逻辑设备(驱动程序在/dev)映射物理设备(在/devices下指定)的说明在/etc/path_to_inst文件中. 当

执行boot ?Cr或devfsadm时, 这个文件被重写. 在把一张卡从一个PCI槽移到另一个槽后, 必须编辑这个文件. 在该文件中, /dev/hme1被连接

到一个特定槽. Reboot不能重新定义这个关系.
警告: 手工编辑/etc/path_to_inst需要设备路径操作的高级经验. 编辑错误会导致系统不能引导!
11.2.3 explorer
该工具能从SunSolve(http://sunsolve.sun.com/pub-cgi/show.pl?target=explorer/explorer) 下载. 它搜集所有重要的系统信息. 它能用于

准备服务命令, 备份配置数据和快照配置等.

11.3 运行时数据分析
11.3.1 time/ptime
较简单的运行时分析工具有time(0.1sec解析)和ptime(1msec解析), 它们能加入命令行. time gzip j2sdk.tar.gz 的结果分成用户(程序)时间

, 系统时间和用去的时间.
11.3.2 top/prstat/ps
solaris下的prstat类似于其他平台下的top工具./usr/ucb/ps ?Caux显示相近的结果, 但它没有排序且不能自动更新.
ps ?CL ?Cp <process-ID>列出有线程的进程及其线程数.
11.3.3 vmstat/mpstat
vmstat <以秒计频率>列出系统负载明细. 下面行列出大多数有趣的:
free: 空闲内存(直到solaris 7此值接近零, 因为有用磁盘缓存. 从solaris8开始反映了的正确值.)
page pi/po: 页面调度行为. 系统不交换直到在交换设备上i/o流量被报告!
Cpu us: user-time. 被应用程序消耗的百分比.
Cpu sy: system-time.被solaris系统消耗的百分比.
Cpu id: idle-time.
Vmstat参数输出结果解释(原文没有,RACE注)
procs 报表下面三种状态的进程数:
r–在运行队列中等候运行
b–被资源阻塞(I/0,页面调度,等等.)
w–可运行但是被换出的
memory 报告虚拟内存和实存信息:
swap–以千字节为单位的当前可用交换空间的数量
free–以千字节为单位的页自由表大小
page 报告每秒页面调度活动数量的信息:
re-从自由表回收页
mf–次要的错误;地址空间或硬件地址转换错误
pi–页入的千字节数
po -页出的千字节数
fr- 释放的千字节数
de–以千字节为单位的可接受的短期内存不足数
sr–页由时钟算法扫描
disk 可以为四个磁盘报告每秒磁盘I/O的数量
faults 报告每秒系统软件中断和硬件中断的速率
in-设备中断,不包括系统时钟中断
sy-系统调用
cs-CPU任务(上下文)交换
cpu– CPU故障时间的百分比,在多处理器系统上,这是全部处理器的平均值:
us- 用户时间
sy– 系统时间
id– 闲置时间

mstat 打印类似信息, 每个处理器一行.
mpstat输出结果解释(原文没有,RACE注)
*CPU――处理器ID
*minf――一般故障
*mjf ――重大故障
*xcal――处事器间的交叉调用
*intr――中断
*ithr――线程中断,不计时钟中断
*csw――任务交换
*icsw――非主动任务交换
*migr――向另一处理器的线程转移
*smtx――互斥信号旋转(第一次时不要求锁定)
*srw――读/写锁定旋转(第一次时不要求锁定)
*syscl――系统调用
*usr ――用户时间百分比
*sys――系统时间百分比
*wt――等待时间百分比
*idd――空闲时间百分比

11.3.4 iostat
iostat ?CxtcP <以秒计频率>列出系统i/o负载.每一个分区或NFS装载打印一行. “kr/s”和”kw/s”行显示以千字节/秒为单位的读写吞吐量.

若”svc_t”大于100表明那磁盘用于磁头(?)分配的时间多过数据传送的时间.
iostat输出结果解释(原文没有,RACE注)
tin每秒输入的字符数
tout每秒输出的字符数
kps每秒传输的千字符数
tps每秒传输的操作次数
serv以毫秒计的的平均服务时间
最后一组报告了CPU使用率:
us用户状态所占百分比
sy系统状态所占百分比
wt等待状态所占百分比
id空闲时间所占百分比
us用户状态所占百分比
ni用于运行nice或renice的进程的时间所占百分比
sy系统状态所占百分比
id空闲时间所占百分比
r/s每秒的读传输操作
w/s每秒的写传输操作
kr/s每秒的千字节数
kw/s每秒写的千字节数
wait在设备队列中等待命令的平均数目
actv在处理中的命令的平均数目
svc_t服务时间(* 是指为一条命令服务的平均时间,这里包括为等待在处理队列中前面的命令所耗费的时间)
%w在队列等待时间的百分比
%b设备忙的时间的百分比

11.3.5 sdtperfmeter, sdtprocess
sdtperfmeter是一个图形工具, 它将vmstat的数据作为柱形或条形图. 该工具的最小化版本显示在CDE面板. Sdtprocess显示用某种方式排序的

所有进程. 可用于深入察看进程特性, 中断进程等.
11.3.6 32或64位?
Isainfo ?Ckv 显示系统内核是32还是64位. UltraSPACR-II系统自动启动64位内核 ; 在ok状态下, 用boot kernel/unix而不是boot

kernel/sparcv9/unix可以明确地装入32位内核 . 而UltraSPARC III只有64位内核.
11.4 调试
11.4.1 Truss,
命令truss <command> 列表应用程序执行过程中所有的系统调用,由于列表中还包含系统调用的参数和返回,因而我们可看到应用程序调用的

共享库和配置文件以及它们的文件位置。
11.4.2 pstack,pmap
"p*-command" 输出属于该进程的详细信息
pstack <PID> 显示进程的最后一个子程序名
pldd <PID> 显示共享连接库列表
pmap -x <PID> 显示该进程与它的装载模块的内存使用情况
11.4.3 snoop
snoop 输出通过网卡所有的包

11.5 常见问题

1. 启动进程过长
OBP 设置:ls diag-swith?=true ??
2. 不能用FTP登陆,但telnet、ssh可以
在/ect/ftpuser中设置不允许用户FTP登陆
3. VI 不能正确的显示
查看变量term 是否定义,如果没有,设置term=vt100


12 相关参考书籍和站点
12.1 参考书
A valuable collection of Hints&Tricks for UNIX:
Jerry Peek, Tim OReilly, Mike Loukides "UNIX Power Tools"
ISBN 1565922603
A detailed view on all UNIX-variants incl. Solaris software and Linux
Evi Nemeth, Garth Snyder, Scott Seebass, Trent R. Hein "UNIX System
Administration Handbook"
ISBN 0130206016
12.2 站点
http://www.sun.com Homepage of Sun Microsystems
http://www.sun.com/bigadmin Everything a sys admin needs
http://www.sun.com/blueprints Blueprints on various topics, monthly additions
http://docs.sun.com All documentation for Sun products online
http://sunsolve.sun.com Technical faqs, infodocs, symptom/resulution database,
patch download etc.
http://www.sunfreeware.com All Freeware-Tools for Solaris, precompiled
http://www.sunhelp.org FAQ portal, other information
http://bhami.com/rosetta.html A Sysadmins Unixersal Translator (ROSETTA STONE)
OR What do they call that (command) in this world (Unix)?

C++的获得硬盘id号的代码,在BC++下运行通过

#include <windows.h>
#include <stdio.h>
#include <string.h>

/**************************************/
//   web:itbaby.jss.cn

//   作者:javasuki(itbaby)
//   日期:2003/03/04
/**************************************/

//用于WinNT/Win2000,对Win9X无效
//通过MS的S.M.A.R.T.接口,直接从RING3调用
//API DeviceIoControl()来获取硬盘信息

typedef struct
{
ULONG HeaderLength;
char  Signature[8];
ULONG Timeout;
ULONG ControlCode;
ULONG ReturnCode;
ULONG Length;
} SRB_IO_CONTROL;

#if 0
typedef struct
{
BYTE bFeaturesReg;
BYTE bSectorCountReg;
BYTE bSectorNumberReg;
BYTE bCylLowReg;
BYTE bCylHighReg;
BYTE bDriveHeadReg;
BYTE bCommandReg;
BYTE bReserved;
} IDEREGS;

typedef struct
{
DWORD   cBufferSize;
IDEREGS irDriveRegs;
BYTE    bDriveNumber;
BYTE    bReserved[3];
DWORD   dwReserved[4];
BYTE    bBuffer[1];
} SENDCMDINPARAMS;


typedef struct
{
DWORD         cBufferSize;
DRIVERSTATUS  DriverStatus;
BYTE          bBuffer[1];
} SENDCMDOUTPARAMS;

#endif

typedef struct _IDSECTOR
{
   USHORT  wGenConfig;
   USHORT  wNumCyls;
   USHORT  wReserved;
   USHORT  wNumHeads;
   USHORT  wBytesPerTrack;
   USHORT  wBytesPerSector;
   USHORT  wSectorsPerTrack;
   USHORT  wVendorUnique[3];
   CHAR    sSerialNumber[20];
   USHORT  wBufferType;
   USHORT  wBufferSize;
   USHORT  wECCSize;
   CHAR    sFirmwareRev[8];
   CHAR    sModelNumber[40];
   USHORT  wMoreVendorUnique;
   USHORT  wDoubleWordIO;
   USHORT  wCapabilities;
   USHORT  wReserved1;
   USHORT  wPIOTiming;
   USHORT  wDMATiming;
   USHORT  wBS;
   USHORT  wNumCurrentCyls;
   USHORT  wNumCurrentHeads;
   USHORT  wNumCurrentSectorsPerTrack;
   ULONG   ulCurrentSectorCapacity;
   USHORT  wMultSectorStuff;
   ULONG   ulTotalAddressableSectors;
   USHORT  wSingleWordDMA;
   USHORT  wMultiWordDMA;
   BYTE    bReserved[128];
} IDSECTOR;

#define  IDE_ATAPI_IDENTIFY               0xA1
#define  IDE_ATA_IDENTIFY                 0xEC
#define IDENTIFY_BUFFER_SIZE              512
#define DFP_RECEIVE_DRIVE_DATA            0×0007c088
#define IOCTL_SCSI_MINIPORT               0×0004d008
#define IOCTL_SCSI_MINIPORT_IDENTIFY      0×001b0501
#define DATA_SIZE                         (sizeof(SENDCMDINPARAMS)-1+IDENTIFY_BUFFER_SIZE)
#define BUFFER_SIZE                       (sizeof(SRB_IO_CONTROL)+DATA_SIZE)
#define W9X_BUFFER_SIZE                   (IDENTIFY_BUFFER_SIZE+16)
#define SENDIDLENGTH                      (sizeof(SENDCMDOUTPARAMS)+IDENTIFY_BUFFER_SIZE)


#define PRINTING_TO_CONSOLE_ALLOWED

static char HardDriveSerialNumber [1024];
//—————————————————————————
char *ConvertToString (DWORD diskdata [256], int firstIndex, int lastIndex)
{
   static char string [1024];
   int index = 0;
   int position = 0;


   for (index = firstIndex; index <= lastIndex; index++)
   {
      string [position] = (char) (diskdata [index] / 256);
      position++;

      string [position] = (char) (diskdata [index] % 256);
      position++;
   }

   string [position] = ‘\0′;

   for (index = position – 1; index > 0 && ‘ ‘ == string [index]; index–)
      string [index] = ‘\0′;

   return string;
}
//—————————————————————————
void PrintIdeInfo (int drive, DWORD diskdata [256])
{
   strcpy (HardDriveSerialNumber, ConvertToString (diskdata, 10, 19));

   #ifdef PRINTING_TO_CONSOLE_ALLOWED

   switch (drive / 2)
   {
      case 0: //printf ("\nPrimary Controller – ");
              break;
      case 1: //printf ("\nSecondary Controller – ");
              break;
      case 2: //printf ("\nTertiary Controller – ");
              break;
      case 3: //printf ("\nQuaternary Controller – ");
              break;
   }

   switch (drive % 2)
   {
      case 0: //printf ("Master drive\n\n");
              break;
      case 1: //printf ("Slave drive\n\n");
              break;
   }

   //输出硬盘信息
   //printf ("Drive Model  Number: %s\n", ConvertToString (diskdata, 27, 46));
   //printf ("Drive Serial Number: %s\n", ConvertToString (diskdata, 10, 19));

   //便于PHP调用,所以注释上面的输出,改用适合与PHP的控制台输出
   printf ("%s<br>", ConvertToString (diskdata, 27, 46));
   printf ("%s",     ConvertToString (diskdata, 10, 19));


   /*
   该出的信息可以不显示
   printf ("Drive Controller Revision Number__: %s\n", ConvertToString (diskdata, 23, 26));
   printf ("Controller Buffer Size on Drive___: %u bytes\n", diskdata [21] * 512);

   printf ("Drive Type________________________: ");
   if (diskdata [0] & 0×0080)
      printf ("Removable\n");
   else if (diskdata [0] & 0×0040)
      printf ("Fixed\n");
   else printf ("Unknown\n");
          
   printf ("Physical Geometry:     "
           "%u Cylinders     %u Heads     %u Sectors per track\n",
           diskdata [1], diskdata [3], diskdata [6]);
   */

#else   //  PRINTING_TO_CONSOLE_ALLOWED
#endif  // PRINTING_TO_CONSOLE_ALLOWED

}
//—————————————————————————
int ReadIdeDriveAsScsiDriveInNT (void)
{
   int done = FALSE;
   int controller = 0;

   for (controller = 0; controller < 2; controller++)
   {
      HANDLE hScsiDriveIOCTL = 0;
      char   driveName [256];

      sprintf (driveName, "\\\\.\\Scsi%d:", controller);

      hScsiDriveIOCTL = CreateFile (driveName,
                               GENERIC_READ | GENERIC_WRITE,
                               FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
                               OPEN_EXISTING, 0, NULL);

      // if (hScsiDriveIOCTL == INVALID_HANDLE_VALUE)
      //    printf ("Unable to open SCSI controller %d, error code: 0x%lX\n",
      //            controller, GetLastError ());

      if (hScsiDriveIOCTL != INVALID_HANDLE_VALUE)
      {
         int drive = 0;

         for (drive = 0; drive < 2; drive++)
         {
            char buffer [sizeof (SRB_IO_CONTROL) + SENDIDLENGTH];
            SRB_IO_CONTROL *p = (SRB_IO_CONTROL *)buffer;
            SENDCMDINPARAMS *pin =(SENDCMDINPARAMS *)(buffer + sizeof (SRB_IO_CONTROL));
            DWORD dummy;
  
            memset (buffer, 0, sizeof (buffer));
            p -> HeaderLength = sizeof (SRB_IO_CONTROL);
            p -> Timeout = 10000;
            p -> Length = SENDIDLENGTH;
            p -> ControlCode = IOCTL_SCSI_MINIPORT_IDENTIFY;
            strncpy ((char *) p -> Signature, "SCSIDISK", 8);
 
            pin -> irDriveRegs.bCommandReg = IDE_ATA_IDENTIFY;
            pin -> bDriveNumber = drive;

            if (DeviceIoControl (hScsiDriveIOCTL, IOCTL_SCSI_MINIPORT,
                                 buffer,
                                 sizeof (SRB_IO_CONTROL) +
                                         sizeof (SENDCMDINPARAMS) – 1,
                                 buffer,
                                 sizeof (SRB_IO_CONTROL) + SENDIDLENGTH,
                                 &dummy, NULL))
            {
               SENDCMDOUTPARAMS *pOut =(SENDCMDOUTPARAMS *)(buffer + sizeof (SRB_IO_CONTROL));
               IDSECTOR *pId = (IDSECTOR *)(pOut -> bBuffer);
               if (pId -> sModelNumber [0])
               {
                  DWORD diskdata [256];
                  int ijk = 0;
                  USHORT *pIdSector = (USHORT *) pId;
         
                  for (ijk = 0; ijk < 256; ijk++)
                     diskdata [ijk] = pIdSector [ijk];

                  PrintIdeInfo (controller * 2 + drive, diskdata);

                  done = TRUE;
               }
            }
         }
         CloseHandle (hScsiDriveIOCTL);
      }
   }

   return done;
}
//—————————————————————————
long getHardDriveComputerID ()
{
   int done = FALSE;
   __int64 id = 0;

   strcpy (HardDriveSerialNumber, "");
   if ( ! done) done = ReadIdeDriveAsScsiDriveInNT ();

   if (done)
   {
      char *p = HardDriveSerialNumber;

      if ( ! strncmp (HardDriveSerialNumber, "WD-W", 4)) p += 5;
      for ( ; p && *p; p++)
      {
         if (‘-’ == *p) continue;
         id *= 10;
         switch (*p)
         {
            case ‘0′: id += 0; break;
            case ‘1′: id += 1; break;
            case ‘2′: id += 2; break;
            case ‘3′: id += 3; break;
            case ‘4′: id += 4; break;
            case ‘5′: id += 5; break;
            case ‘6′: id += 6; break;
            case ‘7′: id += 7; break;
            case ‘8′: id += 8; break;
            case ‘9′: id += 9; break;
            case ‘a’: case ‘A’: id += 10; break;
            case ‘b’: case ‘B’: id += 11; break;
            case ‘c’: case ‘C’: id += 12; break;
            case ‘d’: case ‘D’: id += 13; break;
            case ‘e’: case ‘E’: id += 14; break;
            case ‘f’: case ‘F’: id += 15; break;
            case ‘g’: case ‘G’: id += 16; break;
            case ‘h’: case ‘H’: id += 17; break;
            case ‘i’: case ‘I’: id += 18; break;
            case ‘j’: case ‘J’: id += 19; break;
            case ‘k’: case ‘K’: id += 20; break;
            case ‘l’: case ‘L’: id += 21; break;
            case ‘m’: case ‘M’: id += 22; break;
            case ‘n’: case ‘N’: id += 23; break;
            case ‘o’: case ‘O’: id += 24; break;
            case ‘p’: case ‘P’: id += 25; break;
            case ‘q’: case ‘Q’: id += 26; break;
            case ‘r’: case ‘R’: id += 27; break;
            case ’s’: case ‘S’: id += 28; break;
            case ‘t’: case ‘T’: id += 29; break;
            case ‘u’: case ‘U’: id += 30; break;
            case ‘v’: case ‘V’: id += 31; break;
            case ‘w’: case ‘W’: id += 32; break;
            case ‘x’: case ‘X’: id += 33; break;
            case ‘y’: case ‘Y’: id += 34; break;
            case ‘z’: case ‘Z’: id += 35; break;
         }                           
      }
   }
   if (id > 268435455) id %= 268435456;

#ifdef PRINTING_TO_CONSOLE_ALLOWED

   //printf ("\nComputer ID_______________________: %d\n", id);

#endif

   return (long) id;
}
//—————————————————————————
int main (int argc, char * argv [])
{
  OSVERSIONINFO ver;
  ver.dwOSVersionInfoSize=sizeof(OSVERSIONINFO);
  GetVersionEx(&ver);
  if(VER_PLATFORM_WIN32_NT==ver.dwPlatformId)
    getHardDriveComputerID ();
  else
    printf("不能在Win9X下运行!!!\n");
  return 0;
}


//—————————————————————————

2005年09月03日

  这是从一消息源获得的Google操作系统的截图。据消息源描述,该系统基于GNU/LINUX,采用HURD/GOOGLE技术内核。据称它将有三种版本:嵌入式、移动版、及商用版。该操作系统基于远程操作系统概念设计,其内核及引导组件将固化于芯片中或者一个2GB的USB KEY中,而所有的应用程序将从Google的服务器上安全下载。而其运行的环境将可能是下一代的主板,一种专门基于Google的集群节点式电脑。而USB设备将使用户能够从USB KEY上启动电脑。而对于第三种,即面向企业级用户来说,将在电脑中内建最小化的操作系统,只有文件管理器,电子表格,文本编辑器,以及Google 的电子邮件客户端,浏览器及即时通讯软件等。



Google系统引导过程



Google操作系统截图


  据称,该套系统已经在一台Pentium 4 2.0GHZ处理器,256MB内存的电脑上进行测试成功,系统启动非常快,电脑设备多数能够被自动识别出来。而且其基于XML的菜单也非常友好易用。



Google操作系统截图2


  看来,微软这次真的要警惕了,很可能将来千家万户的电脑开机时出现的不再是微软的“视窗”,而是彩色的Google啦。

2005年09月02日

         你想在没有DVD光驱的电脑上欣赏到和DVD质量不相上下的影片吗?什么,你说这不可能。当然,在以前这确实是不可能的,不过自从有了DVDrip这种新型流媒体格式之后,就完全可以办到这一点了。
  所谓DVDrip,就是用DivX压缩技术对DVD盘片的视频图像进行高质量压缩、用MP3或AC3技术对音频进行压缩,然后将视频、音频部分合并成一个.avi文件,最后再加上外挂的字幕文件而形成的新一代影音播放格式。其大小仅是原先DVD个头的数分之一。由于它用相对小的体积还原了最接近于DVD质量的画面与声音,因此一经推出就受到了广大影音发烧友的热烈追捧。
  随着宽带网的日益普及,DVDrip以其无以比拟的优势,已成为越来越多网友欣赏影片的首选,相信在不久的将来,DVDrip必将取代其他的视频格式成为网络影音播放的主流。
  解码器及其安装
  只有安装了DivX解码器,你才能欣赏到DVDrip影片。由于种种原因,目前DivX编码平台及解码器存在多种版本。
  1、DivX 3.11
  DivX 3.11是目前最稳定、使用最广泛的DVDrip编码解码平台,建议机器配置不是很高的朋友安装它。不过它有一个缺点,就是只能解译自己编码的DVDrip文件,对以XviD和DivX 4、DivX 5.02 Pro为编码核心的DivX无效。
  下载后运行安装程序,一路“Yes”即可安装。但要注意的DivX 3.11在安装后会在开始菜单的“程序”中自动生成一个名为“DivX MPEG-4 Video Codec”的组,找到其中的快捷方式“Run Me First”,只有在运行它后再重新启动电脑才算真正完成DivX 3.11的安装。如果程序组中没有这一项目,也可直接进入安装所在目录,执行其中的“Register_DivX.exe”后重启即可。
  2、DivX 5.02 Pro
  DivX 5.02功能更为强大,不但向下支持DivX 3.11格式的DVDrip媒体文件,而且在较高配置的电脑上还可以轻松实现Post-Processing画面优化功能。建议系统在安装好DivX3.11后,加装DivX 5.02。DivX3.11用于保证遇到问题后最基本的稳定播放,DivX 5.02则用于提供更好的解码画面。

  3、XviD
  一些精通视频编码的程序员(包括原DivX 3.11的开发者)设计开发出了XviD这种全新的编码平台,实际上与DivX 5是同一技术核心,可解码几乎所有的DVDrip,而以其自身编码压缩的DVDrip却只有它自己能解开,因此强烈建议一定要安装XviD。
  播放软件
  1、Windows Media Player
  Windows操作系统自带的媒体播放软件,以8.0版的播放效果最好。但从稳定方面考虑还是首推6.4版,新手如果在播放中遇到什么问题,最好先用它来测试播放一下。
  2、RealOne Player
  大名鼎鼎的RealPlayer相信大家都知道吧,RealOne Player就是由RealNetworks公司推出的一种新型视音频综合播放系统,全新的Web浏览、不一样的信息中心让你与互联网实现互动,丰富的媒体格式支持让您可以轻松播放绝大多数的影音格式。
  建议下载安装RealOne Player 2.0版。
  
  该版本具有多画面播放功能,新增DVD播放与刻录功能,可以在全屏幕的影院模式下收看DVD,画面效果更加逼真。同时为节约系统资源,增加了“Toolbar Mode”播放模式,启动速度也比以前快了许多,可说是看电影、听音乐的上上之选。
  3、专业DVD播放软件:PowerDVD、金山影霸、WinDVD等
  PowerDVD、金山影霸、WinDVD等专为播放DVD而设计的软件,对播放DVDrip影片自然是得心应手。它们共同的特点就是贴近用户、使用方便、功能强大,对AC3多声道的表现也接近于淋漓尽致。
  4、特别推荐:ZoomPlayer和n.Player
  有朋友会说了:上面那些软件一个比一个块头大,耗费的资源一定很大,我的电脑受不了啊!别急,以下推荐两款适合机器配置较低的朋友安装的DVDrip播放软件:

  ZoomPlayer是一款免费、小巧易用的媒体播放软件。它的界面十分简洁,然而功能却非常强大。可支持如DivXPG、ASF、AVI等MPEG4的媒体格式,支持全屏幕、PlayList(播放列表)等,并通过消除Overscan(超滚屏)来提升画面输出的品质。它有两种模式可以选择:Media模式可以供你播放任何被DirectShow支持的文件格式(也就是说在MediaPlayer中可以播放的文件它全部通吃);如果你预先安装了DirectShow DVD过滤器,还可以使用其DVD模式播放DVD影片。
  作为媒体播放软件的新星,名字怪怪的n.Player堪称多面手,不但系统资源占用极少,而且几乎支持所有格式的影音文件,其中包括DVD、CD、MP3、WMA、AVI、DivX等,还能优化缩放画面。其独特的换肤功能,时不时地会让你眼前一亮。
  辅助插件的安装与设置
  安装完以上的播放软件,也许你会松一口气:终于可以欣赏到高品质的电影了吧?等等,这是怎么回事,我的影片怎么光有画面没有声音呀?哎呀,连字幕也没有,画面还扭扭曲曲的……要解决这些问题,我们就要先搞定辅助播放插件的安装与设置。
  1、解码器参数的设置
  要想获得流畅理想的播放效果,首先就要对解码器进行合理设置。找到你的DivX安装目录(默认是C:\Program Files\DivX\DivX Pro Codec),双击名为“Config.exe”的文件,启动相应的设置程序,弹出Decoder属性窗口,选择“Postprocessing Settings”(后期设定)界面,根据你的需求对Quality Level(品质水准)和Film Effect(电影效果)进行设置;
  
  对DivX参数进行设置
  然后选择“Quality Settings”(画面质量设定)界面,对Brightness(亮度)、Contrast(对比度)、Saturation(饱和度)进行设置,反复调试,直到达到你满意的画面效果。
  2、音效外挂插件——AC3 Filter
  目前DVDrip在制作音频部分时普遍采用两种方式:MP3格式压缩音轨和保留DVD中原有的AC3音频文件。一般来说,Windows操作系统已经自带了MP3的解码器——Fhg Radium MP3 codec,因此采用MP3格式做的DVDrip音频我们都可以听到。但对于以AC3音频文件制作的DVDrip,我们就只能看到图像而听不到声音了。此时就应下载安装AC3音效的外挂插件——AC3 Filter。
  AC3 Filter的安装稍微有些复杂,用户应根据自己的操作系统,运行相应的批处理文件。具体执行方法是:找到安装目录,Windows 9x/Me用户运行其中的Registerfilters-98.bat文件,Windows 2000/XP用户运行Registerfilters-win2000.cmd文件后,即完成AC3-Filter的注册。

  3、字幕外挂软件——VobSub
  VobSub是目前功能最齐全的DVDrip字幕外挂软件,兼容性好,支持各种语言文字,支持多种字幕存放格式,同时可方便地管理DVDrip播放时所需的各种插件。只要你将.avi文件及相应的字幕文件(主文件名应相同)放在同一目录中,运行AVI文件时,播放器就会自动调用VobSub开始工作。
  左键双击VobSub在系统托盘中的工作图标,弹出主菜单界面。
  “Language”(语言)用于选择所要显示的字幕语言种类;“Override placement”(位置调整)用来设定字幕的显示位置;Text Settings(正文设置)是字体字库的选择。
  以上设置完毕后,请点击面板上的“Misc”,开始对画面及字幕输出进行调整。这里的“Flip picture vertically”表示画面垂直翻转;“Flip subtitle vertically”表示字幕垂直翻转;“Hide subtitle”表示隐藏字幕。这些重要选项用户可根据自身需要进行选择。
  点击“Timing”标签,即进入字幕时间及速度调整。
  
  调整字幕的显示速度
  该选项十分重要,如果你的DVDrip在播放时出现画音与字幕不同步现象,就要到这里进行设置了。调整“Delay”(字幕延时)以使声音与字幕同步。注意此处的单位为毫秒,因此要延迟5秒,就要输入数值5000;调整“Speed Ratio”(对比速率) 使字幕与画面同步。最后一个“Playback rate”为自定义字幕文件的播放速率。
  “Colors”标签为颜色调整,可对字幕颜色进行搭配调试,最终调出自己喜欢的字幕颜色。
  “Path”(路径)标签为字幕自动加载时的工作目录设定,在此可增加或删除VobSub自动加载字幕文件后的存放目录。你可通过点击“Add”按钮来添加更多的工作目录。
  好了,到此为止你终于可以欣赏到声色俱全的DVDrip了。虽然电脑的显示器比电视屏幕小了点,可毕竟是DVD品质嘛。

dvdrip研究


第一步:知道需要处理的模块
第二步:选择开源的模块代码(你如果要自己写我也完全没有意见)
第三步:组合代码


第一步:
关于dvd转码我们到底需要西什么模块来呐?
1.ifo and vob信息读取
这部分是dvd描述信息取得的关键部分.我们需要从ifo或者vob文件里面取得dvd的视频音频字幕等等数据具体大家可以研究一个VS开头的著名开源软件
2.video and audio and sub stream 分离,
也就是大家说的demux,mux的反向操作,这个操作可以参考很多著名软件,如著名的dvd2avi 的demux部分
3.视频解码 mpeg2decode
这个方面有很多开源代码,鄙人测试过很多,从开源&纯软件解码的角度来说,最快是一个lib开头的库.
4.音频解码
dvd的音频格式就很多,dts,ac3,pcm……..都在网上可以找到…..但这部分很不好做.
5.colorspace and resize
colorspace 简单的说就是把一种像素格式转变成其他的像素格式.
resize就不用解释了把Smile
这一部分可能是工作量比较大的部分,也不排除直接使用某些开源中的相关部分..
6.video encode and audio encode
先说video encode,这部分,如果大家准备转码成avi等等格式的话就比较简单,直接用vfw然后调用喜欢的code就完成.如果要弄成mpeg1 和mpeg2的话就比较麻烦,但是也有直接的代码可以使用
再说audio encode ,一般大家是把audio转成mp3来节约空间,如果片源的audio是ac3 and dts并且对音质要求较高,对空间要求不高的话可以直接把demux出来的音轨直接写入avi(前提是转成avi),如果是mpeg1 和mpeg2的话就只有再找代码了…….

第二步:选择开源的模块代码
其实每一个模块都有开源的代码可以使用.这一阶段组要就是需要大量的编写测试代码,以熟悉开源代码的接口跟性能.
如用dvd2avi的decode部分在鄙人的机器上面解mpeg2的流最多就只有50fps,而使用前面提到的lib开头的代码就可以在200fps左右….
至于选择就说这么多了,剩下的就是自己去找代码,然后剔出自己需要的部分,测试…………….然后重复……..哈哈哈


第三步:组合代码

前面已经熟悉了选择代码的接口了,剩下的就是粘接代码了……

2005年08月28日

  虽然现在获得一个Gmail已经不是什么难为的事情了,不过老是要经别人邀请才能得到,这也许会令一些相对较懒的朋友不太愉快。

  现在我们发现了一个便捷申请Gmail帐号的方法,通过一个网站,我们可以无限制地申请Gmail帐号,而无需别人的邀请,不过要注意的是大家千万不要申请过多,以留下一些资源给还没有Gmail帐号的朋友,笔者在这里先为广大的网友们谢谢啦!

  申请步骤:

  1. 登陆http://www.bytetest.com/网站,就会看到下图的页面,如果你运气好,会在"Enter the code above to get a gmail account"这句话上面看到几个英文代码(如果看不到可以刷新几次)。


刷新后得到代码

  2. 接下来你将得到的代码输入到下方"Get Gmail"前面的输入框中,然后点击"Get Gmail"。

  3. 到这里已经大功告成了,我们已经来到了创建 Google 帐户的页面。在这里填入你的个人资料即可成功申请Gmail了。


成功申请了!

  想知道Gmail如何使用吗?请点击以下文章观看。

  ·Gmail邮箱常规应用小技巧集 | ·邮箱可作硬盘!GMail Drive更新

  除了大容量的诱惑以外,接下来你想不想用Gmail来聊天?快去下载一个属于Google的即时聊天软件Google Talk吧!现在已经有了汉化版,可以让你轻松使用,体积只有2.17MB。[点击下载]

2005年08月18日

编写断点续传和多线程下载模块



概述

    在当今的网络时代,下载软件是使用最为频繁的软件之一。几年来,下载技术也在不停地发展。最原始的下载功能仅仅是个“下载”过程,即从WEB服务器上连续地读取文件。其最大的问题是,由于网络的不稳定性,一旦连接断开使得下载过程中断,就不得不全部从头再来一次。

    随后,“断点续传”的概念就出来了,顾名思义,就是如果下载中断,在重新建立连接后,跳过已经下载的部分,而只下载还没有下载的部分。
无论“多线程下载”技术是否洪以容先生的发明,洪以容使得这项技术得到前所未有的关注是不争的事实。在“网络蚂蚁”软件流行开后,许多下载软件也都纷纷效仿,是否具备多线程下载技术、甚至能支持多少个下载线程都成了人们评测下载软件的要素。多线程下载的基础是WEB服务器支持远程的随机读取,也即支持断点续传。这样,在下载时可以把文件分成若干部分,每一部分创建一个下载线程进行下载。

    现在,不要说编写专门的下载软件,在自己编写的软件中,加入下载功能有时也非常必要。如让自己的软件支持自动在线升级,或者在软件中自动下载新的数据进行数据更新,这都是很有用、而且很实用的功能。本文的主题即怎样编写一个支持断点续传和多线程的下载模块。当然,下载的过程非常复杂,在一篇文章中难以全部阐明,所以,与下载过程关系不直接的部分基本上都忽略了,如异常处理和网络错误处理等,敬请各位读者注意。我使用的开发环境是C++ Builder 5.0,使用其他开发环境或者编程语言的朋友请自行作适当修改。

HTTP协议简介

    下载文件是电脑与WEB服务器交互的过程,它们交互的语言的专业名称是协议。传送文件的协议有多种,最常用的是HTTP(超文本传输协议)和FTP(文件传送协议),我采用的是HTTP。

    HTTP协议最基本的命令只有三条:Get、Post和Head。Get从WEB服务器请求一个特定的对象,比如HTML页面或者一个文件,WEB服务器通过一个Socket连接发送此对象作为响应;Head命令使服务器给出此对象的基本描述,比如对象的类型、大小和更新时间。Post命令用于向WEB服务器发送数据,通常使把信息发送给一个单独的应用程序,经处理生成动态的结果返回给浏览器。下载即是通过Get命令实现。

基本的下载过程

    编写下载程序,可以直接使用Socket函数,但是这要求开发人员理解、熟悉TCP/IP协议。为了简化Internet客户端软件的开发,Windows提供了一套WinInet API,对常用的网络协议进行了封装,把开发Internet软件的门槛大大降低了。我们需要使用的WinInet API函数如图1所示,调用顺序基本上是从上到下,其具体的函数原型请参考MSDN。

图1

    在使用这些函数时,必须严格区分它们使用的句柄。这些句柄的类型是一样的,都是HINTERNET,但是作用不同,这一点非常让人迷惑。按照这些句柄的产生顺序和调用关系,可以分为三个级别,下一级的句柄由上一级的句柄得到。

    InternetOpen是最先调用的函数,它返回的HINTERNET句柄级别最高,我习惯定义为hSession,即会话句柄。

    InternetConnect使用hSession句柄,返回的是http连接句柄,我把它定义为hConnect。

    HttpOpenRequest使用hConnect句柄,返回的句柄是http请求句柄,定义为hRequest。

    HttpSendRequest、HttpQueryInfo、InternetSetFilePointer和InternetReadFile都使用HttpOpenRequest返回的句柄,即hRequest。

    当这几个句柄不再使用是,应该用函数InternetCloseHandle把它关闭,以释放其占用的资源。

    首先建立一个名为THttpGetThread、创建后自动挂起的线程模块,我希望线程在完成后自动销毁,所以在构造函数中设置:

FreeOnTerminate = True; // 自动删除

    并增加以下成员变量:

char Buffer[HTTPGET_BUFFER_MAX+4]; // 数据缓冲区
AnsiString FURL; // 下载对象的URL
AnsiString FOutFileName; // 保存的路径和名称
HINTERNET FhSession; // 会话句柄
HINTERNET FhConnect; // http连接句柄
HINTERNET FhRequest; // http请求句柄
bool FSuccess; // 下载是否成功
int iFileHandle; // 输出文件的句柄

1、建立连接

    按照功能划分,下载过程可以分为4部分,即建立连接、读取待下载文件的信息并分析、下载文件和释放占用的资源。建立连接的函数如下,其中ParseURL的作用是从下载URL地址中取得主机名称和下载的文件的WEB路径,DoOnStatusText用于输出当前的状态:

//初始化下载环境
void THttpGetThread::StartHttpGet(void)
{
   AnsiString HostName,FileName;
   ParseURL(HostName, FileName);
   try
   {
      // 1.建立会话
      FhSession = InternetOpen(http-get-demo,
            INTERNET_OPEN_TYPE_PRECONFIG,
            NULL,NULL,
            0); // 同步方式
      if( FhSession==NULL)throw(Exception(Error:InterOpen));
      DoOnStatusText(ok:InterOpen);
      // 2.建立连接
      FhConnect=InternetConnect(FhSession,
            HostName.c_str(),
            INTERNET_DEFAULT_HTTP_PORT,
            NULL,NULL,
            INTERNET_SERVICE_HTTP, 0, 0);
      if(FhConnect==NULL)throw(Exception(Error:InternetConnect));
      DoOnStatusText(ok:InternetConnect);
      // 3.初始化下载请求
      const char *FAcceptTypes = */*;
      FhRequest = HttpOpenRequest(FhConnect,
            GET, // 从服务器获取数据
            FileName.c_str(), // 想读取的文件的名称
            HTTP/1.1, // 使用的协议
            NULL,
            &FAcceptTypes,
            INTERNET_FLAG_RELOAD,
            0);
      if( FhRequest==NULL)throw(Exception(Error:HttpOpenRequest));
      DoOnStatusText(ok:HttpOpenRequest);
      // 4.发送下载请求
      HttpSendRequest(FhRequest, NULL, 0, NULL, 0);
      DoOnStatusText(ok:HttpSendRequest);
   }catch(Exception &exception)
   {
      EndHttpGet(); // 关闭连接,释放资源
      DoOnStatusText(exception.Message);
   }
}
// 从URL中提取主机名称和下载文件路径
void THttpGetThread::ParseURL(AnsiString &HostName,AnsiString &FileName)
{
   AnsiString URL=FURL;
   int i=URL.Pos(http://);
   if(i>0)
   {
      URL.Delete(1, 7);
   }
   i=URL.Pos(/);
   HostName = URL.SubString(1, i-1);
   FileName = URL.SubString(i, URL.Length());
}

    可以看到,程序按照图1中的顺序,依次调用InternetOpen、InternetConnect、HttpOpenRequest函数得到3个相关的句柄,然后通过HttpSendRequest函数把下载的请求发送给WEB服务器。

    InternetOpen的第一个参数是无关的,最后一个参数如果设置为INTERNET_FLAG_ASYNC,则将建立异步连接,这很有实际意义,考虑到本文的复杂程度,我没有采用。但是对于需要更高下载要求的读者,强烈建议采用异步方式。

    HttpOpenRequest打开一个请求句柄,命令是GET,表示下载文件,使用的协议是HTTP/1.1。

    另外一个需要注意的地方是HttpOpenRequest的参数FAcceptTypes,表示可以打开的文件类型,我设置为*/*表示可以打开所有文件类型,可以根据实际需要改变它的值。

2、读取待下载的文件的信息并分析

    在发送请求后,可以使用HttpQueryInfo函数获取文件的有关信息,或者取得服务器的信息以及服务器支持的相关操作。对于下载程序,最常用的是传递HTTP_QUERY_CONTENT_LENGTH参数取得文件的大小,即文件包含的字节数。模块如下所示:

// 取得待下载文件的大小
int __fastcall THttpGetThread::GetWEBFileSize(void)
{
   try
   {
      DWORD BufLen=HTTPGET_BUFFER_MAX;
            DWORD dwIndex=0;
            bool RetQueryInfo=HttpQueryInfo(FhRequest,
            HTTP_QUERY_CONTENT_LENGTH,
            Buffer, &BufLen,
            &dwIndex);
      if( RetQueryInfo==false) throw(Exception(Error:HttpQueryInfo));
      DoOnStatusText(ok:HttpQueryInfo);
      int FileSize=StrToInt(Buffer); // 文件大小
      DoOnGetFileSize(FileSize);
   }catch(Exception &exception)
   {
      DoOnStatusText(exception.Message);
   }
   return FileSize;
}

    模块中的DoOnGetFileSize是发出取得文件大小的事件。取得文件大小后,对于采用多线程的下载程序,可以按照这个值进行合适的文件分块,确定每个文件块的起点和大小。

3、下载文件的模块

    开始下载前,还应该先安排好怎样保存下载结果。方法很多,我直接采用了C++ Builder提供的文件函数打开一个文件句柄。当然,也可以采用Windows本身的API,对于小文件,全部缓冲到内存中也可以考虑。

// 打开输出文件,以保存下载的数据
DWORD THttpGetThread::OpenOutFile(void)
{
   try
   {
   if(FileExists(FOutFileName))
      DeleteFile(FOutFileName);
   iFileHandle=FileCreate(FOutFileName);
   if(iFileHandle==-1) throw(Exception(Error:FileCreate));
   DoOnStatusText(ok:CreateFile);
   }catch(Exception &exception)
   {
      DoOnStatusText(exception.Message);
   }
   return 0;
}
// 执行下载过程
void THttpGetThread::DoHttpGet(void)
{
   DWORD dwCount=OpenOutFile();
   try
   {
      // 发出开始下载事件
      DoOnStatusText(StartGet:InternetReadFile);
      // 读取数据
      DWORD dwRequest; // 请求下载的字节数
      DWORD dwRead; // 实际读出的字节数
      dwRequest=HTTPGET_BUFFER_MAX;
      while(true)
      {
         Application->ProcessMessages();
         bool ReadReturn = InternetReadFile(FhRequest,
              (LPVOID)Buffer,
              dwRequest,
              &dwRead);
         if(!ReadReturn)break;
         if(dwRead==0)break;
         // 保存数据
         Buffer[dwRead]=’\0′;
         FileWrite(iFileHandle, Buffer, dwRead);
         dwCount = dwCount + dwRead;
         // 发出下载进程事件
         DoOnProgress(dwCount);
      }
      Fsuccess=true;
   }catch(Exception &exception)
   {
      Fsuccess=false;
      DoOnStatusText(exception.Message);
   }
   FileClose(iFileHandle);
   DoOnStatusText(End:InternetReadFile);
}

    下载过程并不复杂,与读取本地文件一样,执行一个简单的循环。当然,如此方便的编程还是得益于微软对网络协议的封装。

4、释放占用的资源

    这个过程很简单,按照产生各个句柄的相反的顺序调用InternetCloseHandle函数即可。

void THttpGetThread::EndHttpGet(void)
{
   if(FConnected)
   {
      DoOnStatusText(Closing:InternetConnect);
      try
      {
         InternetCloseHandle(FhRequest);
         InternetCloseHandle(FhConnect);
         InternetCloseHandle(FhSession);
      }catch(…){}
      FhSession=NULL;
      FhConnect=NULL;
      FhRequest=NULL;
      FConnected=false;
      DoOnStatusText(Closed:InternetConnect);
   }
}

    我觉得,在释放句柄后,把变量设置为NULL是一种良好的编程习惯。在这个示例中,还出于如果下载失败,重新进行下载时需要再次利用这些句柄变量的考虑。

5、功能模块的调用

    这些模块的调用可以安排在线程对象的Execute方法中,如下所示:

void __fastcall THttpGetThread::Execute()
{
   FrepeatCount=5;
   for(int i=0;i<FRepeatCount;i++)
   {
      StartHttpGet();
      GetWEBFileSize();
      DoHttpGet();
      EndHttpGet();
      if(FSuccess)break;
   }
   // 发出下载完成事件
   if(FSuccess)DoOnComplete();
   else DoonError();
}

    这里执行了一个循环,即如果产生了错误自动重新进行下载,实际编程中,重复次数可以作为参数自行设置。

实现断点续传功能

    在基本下载的代码上实现断点续传功能并不是很复杂,主要的问题有两点:

1、 检查本地的下载信息,确定已经下载的字节数。所以应该对打开输出文件的函数作适当修改。我们可以建立一个辅助文件保存下载的信息,如已经下载的字节数等。我处理得较为简单,先检查输出文件是否存在,如果存在,再得到其大小,并以此作为已经下载的部分。由于Windows没有直接取得文件大小的API,我编写了GetFileSize函数用于取得文件大小。注意,与前面相同的代码被省略了。

DWORD THttpGetThread::OpenOutFile(void)
{
   ……
   if(FileExists(FOutFileName))
   {
      DWORD dwCount=GetFileSize(FOutFileName);
      if(dwCount>0)
      {
         iFileHandle=FileOpen(FOutFileName,fmOpenWrite);
         FileSeek(iFileHandle,0,2); // 移动文件指针到末尾
         if(iFileHandle==-1) throw(Exception(Error:FileCreate));
         DoOnStatusText(ok:OpenFile);
         return dwCount;
      }
      DeleteFile(FOutFileName);
   }
   ……
}

2、 在开始下载文件(即执行InternetReadFile函数)之前,先调整WEB上的文件指针。这就要求WEB服务器支持随机读取文件的操作,有些服务器对此作了限制,所以应该判断这种可能性。对DoHttpGet模块的修改如下,同样省略了相同的代码:

void THttpGetThread::DoHttpGet(void)
{
   DWORD dwCount=OpenOutFile();
   if(dwCount>0) // 调整文件指针
   {
      dwStart = dwStart + dwCount;
      if(!SetFilePointer()) // 服务器不支持操作
      {
         // 清除输出文件
         FileSeek(iFileHandle,0,0); // 移动文件指针到头部
      }
   }
   ……
}

多线程下载

    要实现多线程下载,最主要的问题是下载线程的创建和管理,已经下载完成后文件的各个部分的准确合并,同时,下载线程也要作必要的修改。

1、下载线程的修改

    为了适应多线程程序,我在下载线程加入如下成员变量:

int FIndex; // 在线程数组中的索引
DWORD dwStart; // 下载开始的位置
DWORD dwTotal; // 需要下载的字节数
DWORD FGetBytes; // 下载的总字节数

    并加入如下属性值:

__property AnsiString URL = { read=FURL, write=FURL };
__property AnsiString OutFileName = { read=FOutFileName, write=FOutFileName};
__property bool Successed = { read=FSuccess};
__property int Index = { read=FIndex, write=FIndex};
__property DWORD StartPostion = { read=dwStart, write=dwStart};
__property DWORD GetBytes = { read=dwTotal, write=dwTotal};
__property TOnHttpCompelete OnComplete = { read=FOnComplete, write=FOnComplete };

    同时,在下载过程DoHttpGet中增加如下处理,

void THttpGetThread::DoHttpGet(void)
{
   ……
   try
   {
      ……
      while(true)
      {
         Application->ProcessMessages();
         // 修正需要下载的字节数,使得dwRequest + dwCount <dwTotal;
         if(dwTotal>0) // dwTotal=0表示下载到文件结束
         {
            if(dwRequest+dwCount>dwTotal)
            dwRequest=dwTotal-dwCount;
         }
         ……
         if(dwTotal>0) // dwTotal <=0表示下载到文件结束
         {
            if(dwCount>=dwTotal)break;
         }
      }
   }
   ……
   if(dwCount==dwTotal)FSuccess=true;
}

2、建立多线程下载组件

    我先建立了以TComponent为基类、名为THttpGetEx的组件模块,并增加以下成员变量:

// 内部变量
THttpGetThread **HttpThreads; // 保存建立的线程
AnsiString *OutTmpFiles; // 保存结果文件各个部分的临时文件
bool *FSuccesss; // 保存各个线程的下载结果
// 以下是属性变量
int FHttpThreadCount; // 使用的线程个数
AnsiString FURL;
AnsiString FOutFileName;

    各个变量的用途都如代码注释,其中的FSuccess的作用比较特别,下文会再加以详细解释。因为线程的运行具有不可逆性,而组件可能会连续地下载不同的文件,所以下载线程只能动态创建,使用后随即销毁。创建线程的模块如下,其中GetSystemTemp函数取得系统的临时文件夹,OnThreadComplete是线程下载完成后的事件,其代码在其后介绍:

// 分配资源
void THttpGetEx::AssignResource(void)
{
   FSuccesss=new bool[FHttpThreadCount];
   for(int i=0;i<FHttpThreadCount;i++)
      FSuccesss[i]=false;
   OutTmpFiles = new AnsiString[FHttpThreadCount];
   AnsiString ShortName=ExtractFileName(FOutFileName);
   AnsiString Path=GetSystemTemp();
   for(int i=0;i<FHttpThreadCount;i++)
      OutTmpFiles[i]=Path+ShortName+-+IntToStr(i)+.hpt;
   HttpThreads = new THttpGetThread *[FHttpThreadCount];
}
// 创建一个下载线程
THttpGetThread * THttpGetEx::CreateHttpThread(void)
{
   THttpGetThread *HttpThread=new THttpGetThread(this);
   HttpThread->URL=FURL;
   …… // 初始化事件
   HttpThread->OnComplete=OnThreadComplete; // 线程下载完成事件
   return HttpThread;
}
// 创建下载线程数组
void THttpGetEx::CreateHttpThreads(void)
{
   AssignResource();
   // 取得文件大小,以决定各个线程下载的起始位置
   THttpGetThread *HttpThread=CreateHttpThread();
   HttpThreads[FHttpThreadCount-1]=HttpThread;
   int FileSize=HttpThread->GetWEBFileSize();
   // 把文件分成FHttpThreadCount块
   int AvgSize=FileSize/FHttpThreadCount;
   int *Starts= new int[FHttpThreadCount];
   int *Bytes = new int[FHttpThreadCount];
   for(int i=0;i<FHttpThreadCount;i++)
   {
      Starts[i]=i*AvgSize;
      Bytes[i] =AvgSize;
   }
   // 修正最后一块的大小
   Bytes[FHttpThreadCount-1]=AvgSize+(FileSize-AvgSize*FHttpThreadCount);
   // 检查服务器是否支持断点续传
   HttpThread->StartPostion=Starts[FHttpThreadCount-1];
   HttpThread->GetBytes=Bytes[FHttpThreadCount-1];
   bool CanMulti=HttpThread->SetFilePointer();
   if(CanMulti==false) // 不支持,直接下载
   {
      FHttpThreadCount=1;
      HttpThread->StartPostion=0;
      HttpThread->GetBytes=FileSize;
      HttpThread->Index=0;
      HttpThread->OutFileName=OutTmpFiles[0];
   }else
   {
      HttpThread->OutFileName=OutTmpFiles[FHttpThreadCount-1];
      HttpThread->Index=FHttpThreadCount-1;
      // 支持断点续传,建立多个线程
      for(int i=0;i<FHttpThreadCount-1;i++)
      {
         HttpThread=CreateHttpThread();
         HttpThread->StartPostion=Starts[i];
         HttpThread->GetBytes=Bytes[i];
         HttpThread->OutFileName=OutTmpFiles[i];
         HttpThread->Index=i;
         HttpThreads[i]=HttpThread;
      }
   }
   // 删除临时变量
   delete Starts;
   delete Bytes;
}

    下载文件的下载的函数如下:

void __fastcall THttpGetEx::DownLoadFile(void)
{
   CreateHttpThreads();
   THttpGetThread *HttpThread;
   for(int i=0;i<FHttpThreadCount;i++)
   {
      HttpThread=HttpThreads[i];
      HttpThread->Resume();
   }
}

    线程下载完成后,会发出OnThreadComplete事件,在这个事件中判断是否所有下载线程都已经完成,如果是,则合并文件的各个部分。应该注意,这里有一个线程同步的问题,否则几个线程同时产生这个事件时,会互相冲突,结果也会混乱。同步的方法很多,我的方法是创建线程互斥对象。

const char *MutexToThread=http-get-thread-mutex;
void __fastcall THttpGetEx::OnThreadComplete(TObject *Sender, int Index)
{
   // 创建互斥对象
   HANDLE hMutex= CreateMutex(NULL,FALSE,MutexToThread);
   DWORD Err=GetLastError();
   if(Err==ERROR_ALREADY_EXISTS) // 已经存在,等待
   {
      WaitForSingleObject(hMutex,INFINITE);//8000L);
      hMutex= CreateMutex(NULL,FALSE,MutexToThread);
   }
   // 当一个线程结束时,检查是否全部认为完成
   FSuccesss[Index]=true;
   bool S=true;
   for(int i=0;i<FHttpThreadCount;i++)
   {
      S = S && FSuccesss[i];
   }
   ReleaseMutex(hMutex);
   if(S)// 下载完成,合并文件的各个部分
   {
      // 1. 复制第一部分
      CopyFile(OutTmpFiles[0].c_str(),FOutFileName.c_str(),false);
      // 添加其他部分
      int hD=FileOpen(FOutFileName,fmOpenWrite);
      FileSeek(hD,0,2); // 移动文件指针到末尾
      if(hD==-1)
      {
         DoonError();
         return;
      }
      const int BufSize=1024*4;
      char Buf[BufSize+4];
      int Reads;
      for(int i=1;i<FHttpThreadCount;i++)
      {
         int hS=FileOpen(OutTmpFiles[i],fmOpenRead);
         // 复制数据
         Reads=FileRead(hS,(void *)Buf,BufSize);
         while(Reads>0)
         {
            FileWrite(hD,(void *)Buf,Reads);
            Reads=FileRead(hS,(void *)Buf,BufSize);
         }
         FileClose(hS);
      }
      FileClose(hD);
   }
}

结语

    到此,多线程下载的关键部分就介绍完了。但是在实际应用时,还有许多应该考虑的因素,如网络速度、断线等等都是必须考虑的。当然还有一些细节上的考虑,但是限于篇幅,就难以一一写明了。如果读者朋友能够参照本文编写出自己满意的下载程序,我也就非常欣慰了。我也非常希望读者能由此与我互相学习,共同进步。