用来表示可被Linux理解的二进
由于Linux与windows在图形显示的内在原理上有着本质不同,Linux的内核不控制图形显示,它通过外挂一个称为X服务器的应用程序显示图形界面,属于用户态行为;而windows的微内核中一个叫GDI(图形设备接口)的子系统,由它在核心态控制图形界面的显示。所以用户在使用Linux时不要抱怨Linux的图形显示不能像windows那样随心所欲,虽然Linux的版本发布商已经在这方面尽了力。
用户要使用图形界面,必须正确配置一个叫XF86Config(redhat版本下叫XF86Config4)的配置文件,它一般位于etcX11目录下。实际过程中,X服务器使用XF86Config中的参数实现图形显示。XF86Config不是很容易理解,为此各Linux发行套件都附加了简单易用的配置工具(各种版本的名称稍有不同,用户可查看说有关说明),通过配置工具,用户可以很容易的配置XFConfig,从而正常使用图形界面。
但现在的问题是,通过配置工具配置后,经常出现这样那样的问题,或者图形界面不出现、或者是图形显示效果很差,字符过大过小,花屏重影,刷新太低等等。查看说明文档,重新配置几遍,问题照样,下狠心配的高档显卡、液晶显示器不能用,真头疼。
其实,我们可以做点更有效的工作,直接操作配置文件XFConfig,不用害怕,只修改一点点内容,不需要高深知识,更不会烧毁你的宝贝显示器。VI编辑器总会用吧。
第一步,你要知道你的显卡信息。你可以不知道具体的芯片类型(让我们强大的2.4内核或日益强大的X服务器探测去!),但最少你要知道显存(因为硬件探测过程中使用就低原则,探测的显存不是很准);还有,你要清楚你的显示器所能支持的最大分辩率和最大刷新率。记住了,一定看清楚哦,否则,烧毁显示器不赖我啊!
第二步,你要用你的Linux附带的配置工具配置一遍(具体配置过程不用我说吧!)。做这一步的目的是:如果能正常,显示效果如你所愿,就不用我废话了;如果不能正常显示图形界面,那说明我们得到的是一个不能正常使用的配置文件。虽然这个配置文件不能正常使用,但是它还是包含了显卡(device)、显示器(montior)、显示屏(screen)的全部信息,不能正常工作的原因是有些参数不能正确驱动显卡和显示器。
第三步,这一步你要做的工作就是修改导致不能正常显示一些参数。我们这里要修改的参数和windows差不多,主要有“桌面颜色(Depth)”、“桌面分辨率(Modes)”、“刷新率(Vertrefresh)”三个。关于上述三个参数的具体含义,这里我用不多讲。
用VI打开XFConfig,粗略一看,整整三屏不说,内容一点也看不懂。不用急,虽然内容挺多,真正起作用的不多(大部分行用#注释了),需要你修改的更少了,只有上面提到的三处。找到它的位置:Monitor字段下的VertRefresh;Screen字段下的DefaultDepth;Display子字段下的Depth。
简单说明,VertRefresh,刷新率,不过这儿给出的是一个范围,不像windows你可以设定一个具体的值;Modes,是分辨率;DefaultDepth是默认桌面颜色。 讲到这儿,大概你已经想,不用说了,我知道了。
对了,你将要做把DefaultDepth、Modes、VertRefresh、分别“换”成你想使用默认桌面颜色、分辨率、刷新率,就这么简单。
不过,有两点是需要提醒一下。
VertRefresh参数,X服务器工作时将根据分辨率、桌面颜色在设定的范围内自动使用其中的一个值,如果你有关于你的显示器分辨率、桌面颜色、刷新率三者的对应表,那更简单了,对照表上内容换上即可;如果没有,那只好用笨法子,挨个试,注意只需改动下限的值,不改变上限的值,这样做保证不会烧毁你的显示器。我的经验是最多改两次就可以成功显示图形界面。
Modes和Depth参数,注意如果配置文件的Screen字段中没有Depth和Depth所对应的Modes行,可以依照文件中已有Depth行和Modes行的格式,添加这两个参数行,数据当然是你想在图形界面中打算使用的了。
POSIX API 最重要的一个抽象概念就是文件。尽管几乎所有的操作系统都将文件用于永久性存储器,但所有 Unix 版本通过文件抽象概念提供对大多数系统资源的访问。
更具体地说,这意味着 Linux 使用相同的一组系统调用来提供对设备(例如软盘和磁带设备)、网络资源(最常见的是 TCPIP 连接)、系统终端,甚至内核状态信息的访问。感谢无所不在的系统调用,娴熟地使用与文件相关的调用对于每个 Linux 程序员来说都很重要。让我们仔细查看一下文件 API 背后的一些基本概念,并描述最重要的文件相关系统调用。
Linux 提供许多不同种类的文件。最常见的类型就简称为常规文件,它存储大量用于以后访问的信息。您所使用的绝大部分文件 例如可执行文件(如 binvi)、数据文件(如 etcpasswd)和系统二进制文件(如 liblibc.so.6) 都是常规文件。它们通常驻留在磁盘上的某处,但我们稍后会发现,并不一定都是这种情况。
另一种文件类型是目录,它包含了一个其它文件及其位置的列表。使用 ls 命令列出目录中的文件时,它打开该目录的文件,并打印出它所包含的所有文件的信息。
其它文件类型包括块设备(表示文件系统高速缓存的设备,例如硬盘驱动器)、字符设备(表示非高速缓存的设备,例如磁带驱动器、鼠标和系统终端)、管道和套接字(允许进程相互之间对话),以及符号链接(允许文件在目录层次结构中有多个名称)。
大多数文件都有一个或多个引用它们的符号名。这些符号名是一组由 字符定界的字符串,并向内核标识文件。它们是 Linux 用户所熟悉的路径名;例如,路径名 homeewtarticle 引用的是我手提电脑中包含这篇文章文本的文件。没有两个文件可以共享相同的名称(但单一文件可以有多个名称),因此路径名唯一地标识单一文件。
进程可以访问的每个文件都由一个小的非负整数标识,称为“文件描述符”。文件描述符由打开文件的系统调用创建,并由从当前进程创建的新子进程继承。就是说,当进程启动了一个新程序时,原始进程的打开文件通常是由新程序继承的。
按照约定,大多数程序保留前三个文件描述符(0、1 和 2)用于特殊目的 访问所谓的标准输出、标准输出和标准错误流。文件描述符 0 是标准输入,这里许多程序都将从外部世界接收输入。文件描述符 1 是标准输出。大多数程序在这里显示正常的输出。对于与错误情况相关的输出,使用文件描述符 2(标准错误)。
任何习惯使用 Linux shell 的人都曾看到过标准输入、输出和错误文件描述符的使用。通常,shell 运行命令时带文件描述符 0、1 和 2,都是指 shell 的终端。当使用 > 字符指示 shell 将一个程序的输出发送给另一个程序时,shell 在调用新程序之前打开该文件作为文件描述符 1。这将导致程序将它的输出发送给指定的文件而不是用户终端;其妙处是,对于程序本身,这是透明的!
与之类似,"<" 字符指示 shell 使用特定的文件作为文件描述符 0。这样就强迫程序从该文件中读取它的输入;这两种情况下,任何来自程序的错误仍将出现在终端上,如同它们在文件描述符 2 的情况下发送给标准错误一样。(在 "bash" shell 中,可以使用 2> 而不是 > 将标准错误重定向)。这种类型的文件重定向是 Linux 命令行最强大的特性之一。
使用任何与文件相关的系统调用之前,程序应该包括 和 ;它们为最普遍的文件例程提供了函数原型和常数。在下面的示例代码中,我们假设每个程序开始处都有
#i nclude #i nclude
首先,让我们了解如何读写文件。凭直觉就可以知道,read() 和 write() 系统调用是执行这些操作的最常用方法。这两种系统调用将有三个自变量:要访问的文件描述符、指向要读写的信息的指针以及应该读写的字符数。返回成功读写的字符数。清单 1 说明了一个简单的程序,它从标准输入(文件描述符 0)中读取一行,并将它写入标准输出(文件描述符 1):
清单 1:
void main(void) { char buf[100]; int num;
num = read(0, buf, sizeof(buf)); write(1, "I got: ", 7); * Length of "I got: " is 7! * write(1, buf, num); }
关于这个处理有两个值得注意的问题。首先,我们要求 read() 返回 100 个字符,但如果我们运行这个程序,只有在用户按下了 "enter" 键以后才能获得输入。许多文件操作都根据最佳效果工作:它们尝试返回程序要求的所有信息,但只有部分能够成功。缺省情况下,终端配置成一旦存在 "\n" 或新行符(通过按 "enter" 键产生)时,就从 read() 调用返回。这实际上非常方便,因为大多数用户都希望程序无论如何都是面向行的。但常规数据文件并非如此,如果依靠它就可能产生不可预料的结果。
另一个要注意的问题是我们不必在显示输出后写一个 \n。read() 调用给了我们来自用户的 \n,只将那个 \n 通过 write() 写回标准输出。如果您希望在没有新行符的情况下看到发生的事件,尝试将最后一行改为
write(1, buf, num 1);
有关这个简单示例的最后一点:buf 绝对不包含实际的 C 字符串。C 字符串由标记字符串结束的单一 \0 字符终止。因为 read() 不将 \0 添加到缓冲区的结尾,在 read() 上使用 strlen()(或任何其它 C 字符串函数)将可能铸成大错!这种行为可以让 read() 和 write() 对包括 \0 字符的数据处理,而这对于一般字符串函数来说是不可能的。
read() 和 write() 系统调用可以对绝大多数文件起作用。但它们不对目录起作用,目录应该通过特殊函数(例如 readdir())来访问。另外,read() 和 write() 对于某些类型的套接字也不起作用。
某些文件,例如常规文件和块设备文件,使用文件指针的概念。它指定在文件中,下一个 read() 调用从哪里读取,下一个 write() 调用从哪里写入。read() 或 write() 后,文件指针随着已处理的字符数(在内部,通过内核)增加。这样,使用单一循环就可以方便地读取文件中的所有数据。清单 2 就是示例:
清单 2:
char buffer[1024];
while ((num = read(0, buffer, 1024))) { printf("got some data\n"); }
这个循环将读取标准输入上的所有数据,自动在每次读取后增加内核的内部文件指针。当文件指针处于文件结尾时,read() 将返回 0 并退出循环。某些文件(例如字符设备 终端就是很好的一例)本身没有文件指针,所以对于这一点,该程序将继续运行,直到用户提供文件结束标记(通过按 "CtrlD")为止。
到现在为止,我们已经知道如何读写文件了,下一步要学习如何打开一个新文件。打开不同类型的文件有不同方法;我们将在这里讨论的方法是通过路径名打开在文件系统中表示的文件;包括常规文件、目录、设备文件和指定的管道。某些套接字文件有路径名,那些必须通过替代方法打开。
撇开放弃权利的,open() 系统调用可以让程序访问大多数系统文件。open() 是个不寻常的系统调用,因为它获取两个或者三个自变量:
int open(const char * pathname, int flags);
或者,
int open(const char * pathname, int flags, int perm);
第一种形式更普遍一些;它打开一个已存在的文件。第二种格式应该在需要创建文件时使用。第三个自变量指定应该给予新文件的访问权限。
open() 的第一个参数是以正常 C 字符串表示的全路径名(即以 \0 终止)。第二个参数指定文件应该如何打开,并包括逻辑“与”操作的一个或多个以下标志:
O_RDONLY:文件可以只读
O_RDWR:文件可以读写
O_APPEND:文件可以读或附加
O_CREAT:如果文件还不存在则应该创建
O_EXCL:如果文件已存在,失败而不是创建它(只应该使用 O_CREAT)
O_TRUNC:如果文件已存在,从中除去所有数据(与创建新文件类似)
open() 的第三个参数只在使用 O_CREAT 时需要;它指定了以数字表示的文件许可权(格式与 chown 命令的数值许可权自变量的格式相同。为 open() 指定的许可权受用户的 umask 影响,后者允许用户指定一系列新文件应该获得的缺省许可权。大多数创建文件的程序都使用第三个自变量 0666 调用 open(),可以让用户通过 umask 来控制程序的缺省许可权。(大多数 shell 的 umask 命令都可以更改它。)
例如,清单 3 显示了如何为进行读写打开文件、如果它不存在则创建,以及废弃其中的数据:
清单 3:
int fd; fd = open("myfile", O_RDWR | O_CREAT | O_TRUNC, 0666) if (fd < 0) { * Some error occurred * * ... * }
open() 返回引用文件的文件描述符。回忆一下,文件描述符总是 >= 0。如果 open() 返回了一个负值,就表示发生了错误,全局变量错误号包含了描述问题的 Unix 错误代码。open() 总尽量返回最小数,如果没有使用文件描述符 0,open() 将总返回 0。
进程带文件结束时,它应该通过 close() 系统调用关闭它,该系统调用的格式为:
int close(int fd);
close 的文件描述符是传递给 close() 的唯一自变量,在成功情况下返回 0。尽管 close() 失败的情况比较少见,但如果文件描述符引用的是远程服务器上的文件,系统无法正确清空它的高速缓存,close() 就可能真的失败。进程终止时,内核自动关闭所有还在打开的文件。
最后的一个常见文件操作是移动文件指针。这(自然)只对带文件指针的文件有意义,如果尝试在不恰当的文件上尝试该操作就会返回错误。lseek() 系统调用用于以下目的:
off_t lseek(int fd, off_t pos, int whence);
off_t 类型是表达 longint (long 就是 lseek 中 "l" 的来历)的一种别致方法。lseek() 返回相对于文件开始处文件指针的最终位置,如果有错误,则返回 1。这个系统调用希望被移动的文件指针所属的文件描述符作为第一个自变量,将它移动到文件中的位置作为第二个自变量。最后一个自变量描述文件指针的移动方式。
SEEK_SET 将它移动到从文件开始算起的 pos 字节。
SEEK_END 将它移动到从文件结尾算起的 pos 字节。
SEEK_CUR 从它当前位置开始向文件结尾移动 pos 字节。
open()、close()、write()、read() 和 lseek() 的组合为 Linux 提供了基本的文件访问 API。虽然还有许多其它操纵文件的函数,但这里描述的是最常用的。
大多数程序员都使用熟悉的 ANSI C 库文件函数,例如 fopen() 和 fread(),而不是在此描述的低级系统调用。可以预见到,fopen() 和 fread() 是在用户级别库中这些系统调用的基础上实现的。仍然会经常看到低级系统调用的使用,特别是在更复杂的程序中。通过熟悉这些例程和接口,您就可以成为一个真正的 Unix 黑客了。
因为Linux系统是在Internet上起源和发展的,它与生俱来拥有强大的网络功能和丰富的网络应用软件,尤其是TCPIP网络协议的实现尤为成熟。Linux的网络命令比较多,其中一些命令像ping、ftp、telnet、route、netstat等在其它操作系统上也能看到,但也有一些UnixLinux系统独有的命令,如ifconfig、finger、mail等。Linux网络操作命令的一个特点是,命令参数选项和功能很多,一个命令往往还可以实现其它命令的功能。 ifconfig
1.作用 ifconfig用于查看和更改网络接口的地址和参数,包括IP地址、网络掩码、广播地址,使用权限是超级用户。
2.格式 ifconfig interface [options] address
3.主要参数
interface:指定的网络接口名,如eth0和eth1。 up:激活指定的网络接口卡。 down:关闭指定的网络接口。 broadcast address:设置接口的广播地址。 pointopoint:启用点对点方式。 address:设置指定接口设备的IP地址。 netmask address:设置接口的子网掩码。
4.应用说明 ifconfig是用来设置和配置网卡的命令行工具。为了手工配置网络,这是一个必须掌握的命令。使用该命令的好处是无须重新启动机器。要赋给eth0接口IP地址207.164.186.2,并且马上激活它,使用下面命令: fconfig eth0 210.34.6.89 netmask 255.255.255.128 broadcast 210.34.6.127
该命令的作用是设置网卡eth0的IP地址、网络掩码和网络的本地广播地址。若运行不带任何参数的ifconfig命令,这个命令将显示机器所有激活接口的信息。带有“a”参数的命令则显示所有接口的信息,包括没有激活的接口。注意,用ifconfig命令配置的网络设备参数,机器重新启动以后将会丢失。 如果要暂停某个网络接口的工作,可以使用down参数: ifconfig eth0 down
ip
1.作用 ip是iproute2软件包里面的一个强大的网络配置工具,它能够替代一些传统的网络管理工具,例如ifconfig、route等,使用权限为超级用户。几乎所有的Linux发行版本都支持该命令。
2.格式 ip [OPTIONS] OBJECT [COMMAND [ARGUMENTS]]
3.主要参数 OPTIONS是修改ip行为或改变其输出的选项。所有的选项都是以字符开头,分为长、短两种形式。目前,ip支持如表1所示选项。
OBJECT是要管理者获取信息的对象。目前ip认识的对象见表2所示。
表1 ip支持的选项
V,Version 打印ip的版本并退出。 s,stats,statistics 输出更为详尽的信息。如果这个选项出现两次或多次,则输出的信息将更为详尽。 f,family 这个选项后面接协议种类,包括inet、inet6或link,强调使用的协议种类。如果没有足够的信息告诉ip使用的协议种类,ip就会使用默认值inet或any。link比较特殊,它表示不涉及任何网络协议。 4 是family inet的简写。 6 是family inet6的简写。 0 是family link的简写。 o,oneline 对每行记录都使用单行输出,回行用字符代替。如果需要使用wc、grep等工具处理ip的输出,则会用到这个选项。 r,resolve 查询域名解析系统,用获得的主机名代替主机IP地址
COMMAND设置针对指定对象执行的操作,它和对象的类型有关。一般情况下,ip支持对象的增加(add)、删除(delete)和展示(show或list)。有些对象不支持这些操作,或者有其它的一些命令。对于所有的对象,用户可以使用help命令获得帮助。这个命令会列出这个对象支持的命令和参数的语法。如果没有指定对象的操作命令,ip会使用默认的命令。一般情况下,默认命令是list,如果对象不能列出,就会执行help命令。
ARGUMENTS是命令的一些参数,它们倚赖于对象和命令。ip支持两种类型的参数:flag和parameter。flag由一个关键词组成;parameter由一个关键词加一个数值组成。为了方便,每个命令都有一个可以忽略的默认参数。例如,参数dev是ip link命令的默认参数,因此ip link ls eth0等于ip link ls dev eth0。我们将在后面的详细介绍每个命令的使用,命令的默认参数将使用default标出。
4.应用实例 添加IP地址192.168.2.224到eth0网卡上: ip addr add 192.168.1.124 dev eth0
丢弃源地址属于192.168.2.024网络的所有数据报: ip rule add from 192.168.2.024 prio 32777 reject
ping
1.作用 ping检测主机网络接口状态,使用权限是所有用户。
2.格式 ping [dfnqrRv][c][i][I][l][p][s][t] IP地址
3.主要参数
d:使用Socket的SO_DEBUG功能。 c:设置完成要求回应的次数。 f:极限检测。 i:指定收发信息的间隔秒数。 I:网络界面使用指定的网络界面送出数据包。 l:前置载入,设置在送出要求信息之前,先行发出的数据包。 n:只输出数值。 p:设置填满数据包的范本样式。 q:不显示指令执行过程,开头和结尾的相关信息除外。 r:忽略普通的Routing Table,直接将数据包送到远端主机上。 R:记录路由过程。 s:设置数据包的大小。 t:设置存活数值TTL的大小。 v:详细显示指令的执行过程。
ping命令是使用最多的网络指令,通常我们使用它检测网络是否连通,它使用ICMP协议。但是有时会有这样的情况,我们可以浏览器查看一个网页,但是却无法ping通,这是因为一些网站处于安全考虑安装了防火墙。另外,也可以在自己计算机上试一试,通过下面的方法使系统对ping没有反应: echo 1 > procsysnetipv4icmp_echo_ignore_all
初用Linux时可能有处处不方便的感觉,可是等使用一段时间掌握了一些技巧后就会感到越来越顺手了。以下就是我使用Linux以来积累的一些经验与技巧,相信对初学者会有所帮助的。
1. 使用虚拟控制台
登录后按Alt+F2键这时又可以看到"login:"提示符, 这 个就是第二个虚拟控制台。 一般新安装的Linux有四个虚拟控制台, 可以用Alt+F1~Alt+F4来访问。 虚拟控制台最有用的时 候是当一个程序出错锁住输入时可以切换到其他虚拟控制台 ?
2. 拷贝与粘贴
字符界面: 不管是Slackware 还是RedHat 安装后每次启 动时都会自动运行一个叫gpm的程序, 该程序运行后就可以用 鼠标来拷贝与粘贴了。
具体做法是按住鼠标左键拖动使要拷贝的地方反白, 这时反白的区域已经被拷贝, 再按鼠标右键拷贝的内容就会被粘贴在光标所在位置了. XWindow下: 拷贝的操作与字符界面下一样, 三键鼠标的话按中键粘贴, 两键鼠标的话同时按左右键粘贴(须在配置XF86 时在鼠标的设置里选择 Emulate 3 Button)。
3. 快速进入某些目录
键入 cd ~ 可进入用户的home目录。 键入 cd 可进入上一个进入的目录。
4. 软驱的使用
如果是Linux 的ext2文件系统用如下命令: mount t ext2 devfd0 mnt dos
格式的软盘则用命令:
mount t msdos devfd0 mnt
然后就可以在mnt里访问软盘的内容了, 注意在取出软盘之 前要先卸掉软盘上的文件系统, umount mnt 否则会导致信息丢失。在软盘上建立文件系统可用如下命令:
mke2fs devfd0 1440
FTP命令是Internet用户使用最频繁的命令之一,不论是在DOS还是UNIX操作系统下使用 FTP,都会遇到大量的FTP内部命令。熟悉并灵活应用FTP的内部命令,可以大大方便使用者,并收到事半功倍之效。
FTP的命令行格式为: ftp v d i n g [主机名] ,其中
v 显示远程服务器的所有响应信息; d 使用调试方式; i 限制ftp的自动登录,即不使用; n etrc文件; g 取消全局文件名。
ftp使用的内部命令如下(中括号表示可选项): 1. ![cmd[args]]:在本地机中执行交互shell,exit回到ftp环境,如:!ls*.zip。
2. macroame[args]:执行宏定义macroname。
3. account[password]:提供登录远程系统成功后访问系统资源所需的补充口令。
4.append localfile[remotefile]:将本地文件追加到远程系统主机,若未指定远程系统文件名,则使用本地文件名。
5. ascii:使用ascii类型传输方式。
6. bell:每个命令执行完毕后计算机响铃一次。
7. bin:使用二进制文件传输方式。
8. bye:退出ftp会话过程。
9. case:在使用mget时,将远程主机文件名中的大写转为小写字母。
10. cd remotedir:进入远程主机目录。
11. cdup:进入远程主机目录的父目录。
12. chmod mode filename:将远程主机文件filename的存取方式设置为mode,如: chmod 777 a.out 。
13. close:中断与远程服务器的ftp会话(与open对应)。
14. cr:使用asscii方式传输文件时,将回车换行转换为回行。
15. delete remotefile:删除远程主机文件。
16. debug[debugvalue]:设置调试方式, 显示发送至远程主机的每条命令,如: deb up 3,若设为0,表示取消debug。
17.dir[remotedir][localfile]:显示远程主机目录,并将结果存入本地文件localfile。
18. disconnection:同close。
19. form format:将文件传输方式设置为format,缺省为file方式。
20. get remotefile[localfile]: 将远程主机的文件remotefile传至本地硬盘的localfile。
21. glob:设置mdelete,mget,mput的文件名扩展,缺省时不扩展文件名,同命令行的g参数。
22. hash:每传输1024字节,显示一个hash符号()。
23. help[cmd]:显示ftp内部命令cmd的帮助信息,如:help get。
24. idle[seconds]:将远程服务器的休眠计时器设为[seconds]秒。
25. image:设置二进制传输方式(同binary)。
26. lcd[dir]:将本地工作目录切换至dir。
27. ls[remotedir][localfile]:显示远程目录remotedir, 并存入本地文件localfile。
28. macdef macroname:定义一个宏,遇到macdef下的空行时,宏定义结束。
29. mdelete[remotefile]:删除远程主机文件。
30. mdir remotefiles localfile:与dir类似,但可指定多个远程文件,如: mdir *.o.*.zipoutfile 。
31. mget remotefiles:传输多个远程文件。
32. mkdir dirname:在远程主机中建一目录。
33. mls remotefile localfile:同nlist,但可指定多个文件名。
34. mode[modename]:将文件传输方式设置为modename, 缺省为stream方式。
35. modtime filename:显示远程主机文件的最后修改时间。
36. mput localfile:将多个文件传输至远程主机。
37. newer filename: 如果远程机中filename的修改时间比本地硬盘同名文件的时间更近,则重传该文件。
38. nlist[remotedir][localfile]:显示远程主机目录的文件清单,并存入本地硬盘的localfile。
39. nmap[inpattern outpattern]:设置文件名映射机制, 使得文件传输时,文件中的某些字符相互转换,如:nmap 1.2.3[1,2].[2,3],则传输文件a1.a2.a3时,文件名变为a1,a2。该命令特别适用于远程主机为非UNIX机的情况。
40. ntrans[inchars[outchars]]:设置文件名字符的翻译机制,如ntrans1R,则文件名LLL将变为RRR。
41. open host[port]:建立指定ftp服务器连接,可指定连接端口。
42. passive:进入被动传输方式。
43. prompt:设置多个文件传输时的交互提示。
44. proxy ftpcmd:在次要控制连接中,执行一条ftp命令,该命令允许连接两个ftp服务器,以在两个服务器间传输文件。第一条ftp命令必须为open,以首先建立两个服务器间的连接。
45. put localfile[remotefile]:将本地文件localfile传送至远程主机。
46. pwd:显示远程主机的当前工作目录。
47. quit:同bye,退出ftp会话。
48. quote arg1,arg2...:将参数逐字发至远程ftp服务器,如:quote syst。
49. recv remotefile[localfile]:同get。
50. reget remotefile[localfile]:类似于get,但若localfile存在,则从上次传输中断处续传。
51. rhelp[cmdname]:请求获得远程主机的帮助。
52. rstatus[filename]:若未指定文件名,则显示远程主机的状态,否则显示文件状态。
53. rename[from][to]:更改远程主机文件名。
54. reset:清除回答队列。
55. restart marker:从指定的标志marker处,重新开始get或put,如:restart 130。
56. rmdir dirname:删除远程主机目录。
57. runique:设置文件名唯一性存储。
58. send localfile[remotefile]:同put。
59. sendport:设置PORT命令的使用。
60. site arg1,arg2...:将参数作为SITE命令逐字发送至远程ftp主机。
61. size filename:显示远程主机文件大小,如:site idle 7200。
62. status:显示当前ftp状态。
63. struct[structname]:将文件传输结构设置为structname,缺省时使用stream结构。
64. sunique:将远程主机文件名存储设置为唯一(与runique对应)。
65. system:显示远程主机的操作系统类型。
66. tenex:将文件传输类型设置为TENEX机的所需的类型。
67. tick:设置传输时的字节计数器。
68. trace:设置包跟踪。
69. type[typename]:设置文件传输类型为typename,缺省为ascii,如:type binary,设置二进制传输方式。
70. umask[newmask]:将远程服务器的缺省umask设置为newmask,如:umask 3。 71. user username[password][account]:向远程主机表明自己的身份,需要口令时,必须输入口令,如:user anonymous my@email。
72. verbose:同命令行的v参数,即设置详尽报告方式,ftp服务器的所有响应都将显示给用户,缺省为on。
73. ?[cmd]:同help。
不知道在什幺时候,Linux 出现了 module 这种东西,的确,它是 Linux 的一大革新。有了 module 之后,写 device driver 不再是一项恶梦,修改 kernel 也不再是一件痛苦的事了。因为你不需要每次要测试 driver 就重新 compile kernel 一次。那简直是会累死人。Module 可以允许我们动态的改变 kernel,加载 device driver,而且它也能缩短我们 driver development 的时间。在这篇文章里,我将要跟各位介绍一下 module 的原理,以及如何写一个 module。
module 翻译成中文就是模块,不过,事实上去翻译这个字一点都没意义。在讲模块之前,我先举一个例子。相信很多人都用过 RedHat。在 RedHat 里,我们可以执行 sndconfig,它可以帮我们 config 声卡。config 完之后如果捉得到你的声卡,那你的声卡马上就可以动了,而且还不用重新激活计算机。这是怎幺做的呢 ? 就是靠module。module 其实是一般的程序。但是它可以被动态载到 kernel 里成为 kernel的一部分。载到 kernel 里的 module 它具有跟 kernel 一样的权力。可以 access 任何 kernel 的 data structure。你听过 kdebug 吗 ? 它是用来 debug kernel 的。它就是先将它本身的一个 module 载到 kernel 里,而在 user space 的 gdb 就可以经由跟这个 module 沟通,得知 kernel 里的 data structure 的值,除此之外,还可以经由载到 kernel 的 module 去更改 kernel 里 data structure。
我们知道,在写 C 程序的时候,一个程序只能有一个 main。Kernel 本身其实也是一个程序,它本身也有个 main,叫 start_kernel()。当我们把一个 module 载到 kernel 里的时候,它会跟 kernel 整合在一起,成为 kernel 的一部分。请各位想想,那 module 可以有 main 吗 ? 答案很明显的,是 No。理由很简单。一个程序只能有一个 main。在使用 module 时,有一点要记住的是 module 是处于被动的角色。它是提供某些功能让别人去使用的。
Kernel 里有一个变量叫 module_list,每当 user 将一个 module 载到 kernel 里的时候,这个 module 就会被记录在 module_list 里面。当 kernel 要使用到这个 module 提供的 function 时,它就会去 search 这个 list,找到 module,然后再使用其提供的 function 或 variable。每一个 module 都可以 export 一些 function 或变量来让别人使用。除此之外,module 也可以使用已经载到 kernel 里的 module 提供的 function。这种情形叫做 module stack。比方说,module A 用到 module B 的东西,那在加载 module A 之前必须要先加载 module B。否则 module A 会无法加载。除了 module 会 export 东西之外,kernel 本身也会 export 一些 function 或 variable。同样的,module 也可以使用 kernel 所 export 出来的东西。由于大家平时都是撰写 user space 的程序,所以,当突然去写 module 的时候,会把平时写程序用的 function 拿到 module 里使用。像是 printf 之类的东西。我要告诉各位的是,module 所使用的 function 或 variable,要嘛就是自己写在 module 里,要嘛就是别的 module 提供的,再不就是 kernel 所提供的。你不能使用一般 libc 或 glibc所提供的 function。像 printf 之类的东西。这一点可能是各位要多小心的地方。(也许你可以先 link 好,再载到 kernel,我好象试过,但是忘了)
刚才我们说到 kernel 本身会 export 出一些 function 或 variable 来让 module 使用,但是,我们不是万能的,我们怎幺知道 kernel 有开放那里东西让我们使用呢 ? Linux 提供一个 command,叫 ksyms,你只要执行 ksyms a 就可以知道 kernel 或目前载到 kernel 里的 module 提供了那些 function 或 variable。
作用: ASP 无组件发送电子邮件 实现了直接套用式功能, 只需修改少许选项, 即可使用.
说明: 主要操作皆封装在两个函数里, 一个为发送邮件表单(名 fCdoForm), 一个为发送邮件操作(名 fCdoSendMail) 除主收件人以外, 支持 抄送, 密送. 动态选择使用 TEXTHTML 格式, 或者两者都选 动态输入SMTP 相关信息 支持是否使用 SSL 支持三种验证方法
附注: 暂不支持附件功能, 主要原因为服务端与客户端的限制. 服务端脚本不允许直接读取客户端文件.
目录: 1. 主内容: 两个主函数及完整应用代码 2. 预览:
shawl.qiu 20060911 http:blog.csdn.netbtbtd
1. 主内容: 两个主函数及完整应用代码
linenum <h1 style="textalign:center!important;color:red!important; ">ASP CDO.Message 发送邮件完整实例 By shawl.qiu<h1> <h2 style="float:right!important;margin:0px;padding:0px; "><a href=" http:blog.csdn.netbtbtd">shawl.qiu<a> code<h2> < dim mAction, mFrom, mTo, mCC, mBcc, mSubject, mBody dim mSmtp, mPort, mTimeout, mSsl, mUser, mPwd mAction="?id=cdo" mFrom="shawlqiu@21cn.com" mTo="shawl.qiu@gmail.com,btbtd@msn.com" mCc="btbtd@yahoo.com.cn" mBcc="shawl.qiu+2@gmail.com" mSubject="mail subject" mBody="test mail body"chr(13)"中文"chr(13)"<h2>html format<h2>" mSmtp="smtp.21cn.com" mPort=25 mTimeout=60 mUser="shawlqiu" mPwd="不告诉你" dim qId:qId=request.queryString("id") call fCdoSendMail(qId,request.form) if qId<>"cdo" then _ call fCdoForm(mAction,mFrom, mTo, mCc, mBcc, mSubject, mBody, mSmtp, mPort, mTimeout, mUser, mPwd) function fCdoSendMail(rQs, rForm) ASP CDO.Message 发送邮件完整实例 By shawl.qiu CDO 发送邮件操作函数 fCdoSendMail 参数说明: rQs = request.queryString(id) rForm = request.Form 集合 sample call: call fCdoSendMail(qId,request.form) if rQs="" or rQs<>"cdo" then exit function if isObject(rForm)=false then exit function dim temp for each temp in rForm select case temp case "mTextBody" case "mHtmlBody" case "mFile" case "mSsl" case else execute "dim "temp":"temp"=request.form(temp)" end select next dim mSsl, mTextBody, mHtmlBody mSsl=request.form("mSsl") mTextBody=request.form("mTextBody") mHtmlBody=request.form("mHtmlBody") mBody="<meta httpequiv=""ContentType"" content=""texthtml; charset=gb2312"" >"chr(13)mBody if mSsl="" then mSsl=false else mSsl=true dim cdo set cdo=createObject("cdo.message") configuration information for the remote SMTP Server with cdo.configuration.fields .Item(" http:schemas.microsoft.comcdoconfigurationsendusing") = mSend .item(" http:schemas.microsoft.comcdoconfigurationsmtpserver")= mSmtp SMTP 服务器地址 .item(" http:schemas.microsoft.comcdoconfigurationsmtpserverport")= mPort 端口 25 .item(" http:schemas.microsoft.comcdoconfigurationsendusername")= mUser 用户名 .item(" http:schemas.microsoft.comcdoconfigurationsendpassword")= mPwd 用户密码 .item(" http:schemas.microsoft.comcdoconfigurationsmtpauthenticate")= mAuth NONE, Basic (Base64 encoded), NTLM .item(" http:schemas.microsoft.comcdoconfigurationsmtpconnectiontimeout")= mTimeout 超时设置, 以秒为单位 .Item(" http:schemas.microsoft.comcdoconfigurationsmtpusessl") = mSsl 是否使用套接字 truefalse .Update end with with cdo .from=mFrom .to=mTo if mCc<>"" then .cc=mCc if mBcc<>"" then .bcc=mBcc .subject=mSubject if mTextBody<>"" then .textbody=mBody if mHtmlBody<>"" then .htmlbody=mBody if len(mFile)>0 then .Addattachment mFile CDO 发附件规则: 当前目录的文件可用变量, 不是当前的目录不能用变量 On Error Resume Next .send if err.number<>0 then response.write "邮件发送失败, 错误编号: "_ err.number"<br>错误描述: "err.description else response.write "邮件已发送" end with shawl.qiu code set cdo=nothing end function function fCdoForm(mAction,mFrom, mTo, mCc, mBcc, mSubject, mBody, mSmtp, mPort, mTimeout, mUser, mPwd) if mAction="" then exit function ASP CDO.Message 发送邮件完整实例 By shawl.qiu CDO 发送邮件表单函数 fCdoForm 参数说明: (除 mAction 外, 其余参数值不是必须) mAction= 表单提交 URL, 如: ?id=cdo mFrom= 发件人 Email mTo= 收件人 Email mCc= 收件人 Email mBcc= 收件人 Email mSubject= 邮件标题 mBody= 邮件内容 mSmtp= SMTP 服务器地址, 如: smtp.21cn.com mPort= SMTP 端口, 如: 25 mTimeout= 超时限制, 如: 60(单位为秒) mUser= 用户名 mPwd= 密码 sample call: dim mAction, mFrom, mTo, mCC, mBcc, mSubject, mBody dim mSmtp, mPort, mTimeout, mSsl, mUser, mPwd mAction="?id=cdo" mFrom="shawlqiu@21cn.com" mTo="shawl.qiu@gmail.com,btbtd@msn.com" mCc="btbtd@yahoo.com.cn" mBcc="shawl.qiu+2@gmail.com" mSubject="mail subject" mBody="test mail body"chr(13)"中文"chr(13)"<h2>html format<h2>" mSmtp="smtp.21cn.com" mPort=25 mTimeout=60 mUser="shawlqiu" mPwd="yourPassword" call fCdoForm(mAction,mFrom, mTo, mCc, mBcc, mSubject, mBody, mSmtp, mPort, mTimeout, mUser, mPwd) > <form action="< response.write mAction >" method="post" name="mCdo"> <p>From: <input name="mFrom" type="text" value="< response.write mFrom >" size="50"><br > T <input name="mTo" type="text" value="< response.write mTo >" size="50" > <br > Cc: <input name="mCc" type="text" value="< response.write mCc >" size="50" ><br > Bcc: <input name="mBcc" type="text" id="mBcc" value="< response.write mBcc >" size="50" > <br > Subject: <input name="mSubject" type="text" value="< response.write mSubject >" size="50" > <br > Email Format: text: <input type="checkbox" name="mTextBody" value="yes" checked="checked" > html: <input type="checkbox" name="mHtmlBody" value="yes" ><br > Text:<br > <textarea name="mBody" cols="77" rows="10">< response.write mBody ><textarea> <br > Remote SMTP: <input name="mSmtp" type="text" value="< response.write mSmtp >" > Port: <input name="mPort" type="text" value="< response.write mPort >" size="5" > Timeout: <input name="mTimeout" type="text" value="< response.write mTimeout >" size="5" > Sec SSL: <input type="checkbox" name="mSsl" value="ture" > <br > Username: <input name="mUser" type="text" value="< response.write mUser >" > <br > Password: <input name="mPwd" type="password" value="< response.write mPwd >" > <br > SMTP验证选项: <select name="mAuth"> <option value="0">匿名验证<option> <option value="1" selected="selected">普通验证<option> <option value="2">NTLM 验证<option> <select> 邮件发送选项: <select name="mSend"> <option value="1">Send Using Pickup<option> <option value="2" selected="selected">Send Using Port<option> <select> <br > <input type="submit" value="Submit" > <input type="reset" value="Reset" > <form><! shawl.qiu code > < end function ><br > <a href="?">back<a>
C、传统 C++
include <assert.h> 设定插入点 include <ctype.h> 字符处理 include <errno.h> 定义错误码 include <float.h> 浮点数处理 include <fstream.h> 文件输入/输出 include <iomanip.h> 参数化输入/输出 include <iostream.h> 数据流输入/输出 include <limits.h> 定义各种数据类型最值常量 include <locale.h> 定义本地化函数 include <math.h> 定义数学函数 include <stdio.h> 定义输入/输出函数 include <stdlib.h> 定义杂项函数及内存分配函数 include <string.h> 字符串处理 include <strstrea.h> 基于数组的输入/输出 include <time.h> 定义关于时间的函数 include <wchar.h> 宽字符处理及输入/输出 include <wctype.h> 宽字符分类
标准 C++ (同上的不再注释)
include <algorithm> STL 通用算法 include <bitset> STL 位集容器 include <cctype> include <cerrno> include <clocale> include <cmath> include <complex> 复数类 include <cstdio> include <cstdlib> include <cstring> include <ctime> include <deque> STL 双端队列容器 include <exception> 异常处理类 include <fstream> include <functional> STL 定义运算函数(代替运算符) include <limits> include <list> STL 线性列表容器 include <map> STL 映射容器 include <iomanip> include <ios> 基本输入/输出支持 include <iosfwd> 输入/输出系统使用的前置声明 include <iostream> include <istream> 基本输入流 include <ostream> 基本输出流 include <queue> STL 队列容器 include <set> STL 集合容器 include <sstream> 基于字符串的流 include <stack> STL 堆栈容器 include <stdexcept> 标准异常类 include <streambuf> 底层输入/输出支持 include <string> 字符串类 include <utility> STL 通用模板类 include <vector> STL 动态数组容器 include <cwchar> include <cwctype>
using namespace std;
C99 增加
include <complex.h> 复数处理 include <fenv.h> 浮点环境 include <inttypes.h> 整数格式转换 include <stdbool.h> 布尔环境 include <stdint.h> 整型环境 include <tgmath.h> 通用类型数学宏
一个好的用户界面(GUI)的设计通常可以在现实世界找到相应的表现。例如,如果在您的面前摆放着一个类似于电脑键盘按键的一个简单的按钮,然而就是这么简单的一个按钮,我们就可以看出一个GUI设计的规则,它由两个主要的部分构成,一部分使得它具有了按钮应该具有的动作特性,例如可以被按下。另外一部分则负责它的表现,例如这个按钮是代表了A还是B。
看清楚这两点你就发现了一个很强大的设计方法,这种方法鼓励重用reuse,而不是重新设计redesign。你发现按钮都有相同的机理,你只要在按钮的顶上喷上不同的字母便能制造出“不同”的按钮,而不用为了每个按钮而重新设计一份图纸。这大大减轻了设计工作的时间和难度。
如果您把上述设计思想应用到软件开发领域,那么取得相似的效果一点都不让人惊奇。一个在软件开发领域应用的非常广泛的技术ModelViewController(MVC)便是这种思想的一个实现。
这当然很不错,但是或许您又开始疑惑这和java基础类JFC(Java Foundation Class)中的用户界面设计部分(Swing)又有什么关系呢?好的,我来告诉你。
尽管MVC设计模式通常是用来设计整个用户界面(GUI)的,JFC的设计者们却独创性的把这种设计模式用来设计Swing中的单个的组件(Component),例如表格Jtable,树Jtree,组合下拉列表框JcomboBox等等等等。这些组件都有一个Model,一个View,一个Controller,而且,这些model,view,controller可以独立的改变,就是当组件正在被使用的时候也是如此。这种特性使得开发GUI界面的工具包显得非常的灵活。
MVC设计模式把一个软件组件区分为三个不同的部分,model,view,controller。
Model是代表组件状态和低级行为的部分,它管理着自己的状态并且处理所有对状态的操作,model自己本身并不知道使用自己的view和controller是谁,系统维护着它和view之间的关系,当model发生了改变系统还负责通知相应的view。
View代表了管理model所含有的数据的一个视觉上的呈现。一个Model可以有一个以上的View,但是Swing中却很少有这样的情况。
Controller管理着model和用户之间的交互的控制。它提供了一些方法去处理当model的状态发生了变化时的情况。
使用键盘上的按钮的例子来说明一下:Model就是按钮的整个机械装置,ViewController就是按钮的表面部分。
下面的图解释了如何把一个JFC开发的用户界面分为model,view,controller,注意,viewController被合并到了一起,这是MVC设计模式通常的用法,它们提供了组件的用户界面(UI)。
用Button的例子详细说明
为了更好的理解MVC设计模式和Swing用户界面组件之间的关系,让我们更加深入的进行分析。我将采用最常见的组件button来说明。
我们从model来开始。
Model
一个按钮的model所应该具备的行为由一个接口ButtonModel来完成。一个按钮model实例封装了其内部的状态,并且定义了按钮的行为。它的所有方法可以分为四类:
1、查询内部状态
2、操作内部状态
3、添加和删除事件监听器
4、发生事件
法。
程序员通常并不会直接和model以及viewcontroller打交道,他们通常隐藏于那些继承自java.awt.Component的组件里面了,这些组件就像胶水一样把MVC三者合三为一。也正是由于这些继承的组件对象,一个程序员可以很方便的混合使用Swing组件和AWT组件,然后,我们知道,Swing组件有很多都是直接继承自相应的AWT组件,它能提供比AWT组件更加方便易用的功能,所以通常情况下,我们没有必要混合使用两者。
一个实例
现在我们已经明白了Java类与MVC各个部分的对应关系,我们可以更加深入一点去分析问题了。下面我们将要讲述一个小型的使用MVC模式开发的例子。因为JFC十分的复杂,我只能把我的例子局限于一个用户界面组件里面(如果你猜是一个按钮的例子,那么你对了!)
让我们来看看这个例子的所有部分吧。
Button类
最显而易见的开始的地方就是代表了按钮组件本省的代码,因为这个类是大部分程序员会接触的。
就像我前面提到的,按钮用户界面组件类实际上就是model和viewcontroller的之间的黏合剂。每个按钮组件都和一个model以及一个controller关联,model定义了按钮的行为,而viewcontroller定义了按钮的表现。而应用程序可以在任何事件改变这些关联。让我们看看得以实现此功能的代码。
public void setModel(ButtonModel buttonmodel) { if (this.buttonmodel != null) { this.buttonmodel.removeChangeListener(buttonchangelistener); this.buttonmodel.removeActionListener(buttonactionlistener);
buttonchangelistener = null; buttonactionlistener = null; }
this.buttonmodel = buttonmodel;
if (this.buttonmodel != null) { buttonchangelistener = new ButtonChangeListener(); buttonactionlistener = new ButtonActionListener();
this.buttonmodel.addChangeListener(buttonchangelistener); this.buttonmodel.addActionListener(buttonactionlistener); }
updateButton(); }
public void setUI(ButtonUI buttonui) { if (this.buttonui != null) { this.buttonui.uninstallUI(this); }
this.buttonui = buttonui;
if (this.buttonui != null) { this.buttonui.installUI(this); }
updateButton(); }
public void updateButton() { invalidate(); }
在进入下一节之前,你应该多花一些时间来仔细阅读一下Button类的源代码。
ButtonModel类
ButtonModel维护着三种类型的状态信息:是否被按下(pressed),是否“武装上了”(armed),是否被选择(selected)。它们都是boolean类型的值。
一个按钮被按下(pressed)是指当鼠标在按钮上面的时候,按下鼠标但是还没有松开鼠标按钮的状态,及时用户此时把鼠标拖拽到按钮的外面也没有改变这种状态。
一个按钮是否“武装了”(armed)是指按钮被按下,并且鼠标还在按钮的上面。
一些按钮还可能被选择(selected),这种状态通过重复的点击按钮取得true或者false的值。
下面的代码是状态pressed的一个缺省的实现。状态armed以及selected实现的代码与之类似。ButtonModel类应该被继承,这样可以覆盖缺省的状态定义,实现有个性的按钮。
private boolean boolPressed = false;
public boolean isPressed() { return boolPressed; }
public void setPressed(boolean boolPressed) { this.boolPressed = boolPressed; fireChangeEvent(new ChangeEvent(button)); }
在进入下一节之前,你应该多花一些时间来仔细阅读一下ButtonModel类的源代码。
ButtonUI类
按钮的viewcontroller是负责构建表示层的。缺省情况下它仅仅是用背景色画一个矩形而已,他们的子类继承了他们并且覆盖了绘制的方法,使得按钮可以有许多不同的表现,例如MOTIF,Windows 95,Java样式等等。
public void update(Button button, Graphics graphics) { }
public void paint(Button button, Graphics graphics) { Dimension dimension = button.getSize(); Color color = button.getBackground(); graphics.setColor(color); graphics.fillRect(0, 0, dimension.width, dimension.height); }
ButtonUI类并不自己处理AWT事件,他们会使用一个定制的事件监听器把低级的AWT事件翻译为高级的Button模型期望的语义事件。下面就是安装卸载事件监听器的代码。
private static ButtonUIListener buttonuilistener = null;
public void installUI(Button button) { button.addMouseListener(buttonuilistener); button.addMouseMotionListener(buttonuilistener); button.addChangeListener(buttonuilistener); }
public void uninstallUI(Button button) { button.removeMouseListener(buttonuilistener); button.removeMouseMotionListener(buttonuilistener); button.removeChangeListener(buttonuilistener); }
ViewController实际上就是一些方法。他们不维护任何自己的状态信息。因此,许多按钮的实例可以共享一个ButtonUI实例。ButtonUI是通过在方面的参数列表里面加上按钮的引用来区分各个不同的按钮。
同样,希望你能多花一些时间来看看ButtonUI类,然后咱们进入下一节。
ButtonUIListener类
ButtonUIListener类可以帮助Button类去转变鼠标或者键盘的输入为对按钮模型的操作。这个监听器类实现了:MouseListener,MouseMotionListener,ChangeListener接口,并且处理一下事件:
public void mouseDragged(MouseEvent mouseevent) { Button button = (Button)mouseevent.getSource(); ButtonModel buttonmodel = button.getModel();
if (buttonmodel.isPressed()) { if (button.getUI().contains(button, mouseevent.getPoint())) { buttonmodel.setArmed(true); } else { buttonmodel.setArmed(false); } } }
public void mousePressed(MouseEvent mouseevent) { Button button = (Button)mouseevent.getSource(); ButtonModel buttonmodel = button.getModel(); buttonmodel.setPressed(true); buttonmodel.setArmed(true); }
public void mouseReleased(MouseEvent mouseevent) { Button button = (Button)mouseevent.getSource(); ButtonModel buttonmodel = button.getModel(); buttonmodel.setPressed(false); buttonmodel.setArmed(false); }
public void stateChanged(ChangeEvent changeevent) { Button button = (Button)changeevent.getSource(); button.repaint(); }
总结
我希望你能按照上面讲述的方法去做。如果不能,那么所有的努力都将白费。这个例子以及Swing用户界面组件的好处在于你不用去花时间去弄明白他们底层是如何设计实现的就可以很方便的使用他们了。他们都提供了缺省的model以及viewcontroller,然后,当你自己做组件的时候,你会发现上面的思想的强大之处。
由于本文旨在探讨Java\"异常机制\"的深层原理,因此关于"异常"的使用方法都不做详细说明。首先看一段非常熟悉的用于打开一个文件的C程序段:
FILE *fp;
fp=fopen(filename,"rw");
if(fp==NULL){
printf("cannot open file\n");
exit(0);
}
在这段程序中,if条件语句中的一段用来处理没有找到指定文件,或者其它原因无法正确打开指定文件。可是如果遇到一个责任心不强的程序员,他可能认为出现找不到文件的可能性很小,或者由于思路集中在程序功能的实现上而忘记了处理这种情况。这时程序同样可以正确编译,而且一般情况下也不会出现问题。但此时这段程序可以肯定说是不够健壮的,而且一旦这段程序发生了错误也会让程序员很难发现错误出在哪里。在C语言以及其它大多数高级语言中都可以举出很多这种例子。
也就是一个函数在使用的时候,可能会出现并没有达到这个函数的使用目的的情况,哪怕在这段程序的特定使用环境下发生这种异常情况的可能性只有万分之一。常用处理的方法就是,程序员在需要使用某个函数时必须充分了解可能会有什么原因导致该函数不能正确执行,然后加入相应的条件判断语句来进行处理。后面将有一个例子说明这个问题。
而Java的"异常机制"就是在处理上述问题中给了程序员非常简单而灵活的方式。一般来说,其它高级语言主要是让函数使用者来关注该函数可能会出现的异常情况,而java则是把这件事情交给方法(和函数对应的概念,在Java中称方法)的设计者来做。这对于方法的使用者来说带来的方便是不会因为责任心不强,或者办事丢三那四,会忘了在使用方法时处理可能发生的异常情况。而麻烦就是,在使用一个可能会发生异常的方法时,绝对不能视而不见,而必须做出相应的处理。也就是说象上述C程序段中,如果忘了if程序块,这个程序甚至还能蒙过一个外行上司,但当使用Java来完成这个功能时,只要用到的方法使用了"异常"机制,如果不对可能产生"异常"的方法进行相应处理,java编译器是不会让其通过的。
一、"异常类"的组织形式
Java系统类中的方法产生的异常都被组织成"异常类"(还有Error类,不在本文讨论范围),此方法和它相关的"异常类"通过throws关键字关联在一起,并且这些类都必须是Exception类的子类。任何一个自己开发的类的方法中如果可能会产生某种异常,也可以将这种异常组织成一个"异常类",但这个"异常类"同样必须是Exception的子类,或孙子类等等。
例1:
*isLegal于检查数据是否合法,当>0时视为合法,返回合法值,
*否则视为不合法,抛出"异常"。*
int isLegal(int dt) throws LowZeroException这种定义本文中均称为方法与"异常"通
{ 过throws建立了关联
if(dt>=0){
return data;
}
else
throw new LowZeroException();
}
*自已写的异常类,继承自Exception*
class LowZeroException extends Exception
{
public LowZeroException(){
super();
}
}
仔细观察方法isLegal(),它体现出的最值得注意的特色是,它有两种方式的函数出口,一种是通过return语句,返回的是方法本身定义的类型的实例,另一种是通过throw,返回的是"异常类"的对象实例,Java中称之为抛出"异常"。对比一下C中如何处理同样的问题的:
int isLegal(int dt) {
if(dt>=0){
return data;
}
else
return 1;通过一个特定值来表明出错
}
由于C只能通过return返回函数值,所以在处理异常情况时则可能通过以上方式来处理。当然这就要求isLegal()函数的使用者必须知道函数中使用返回值1来表明出现不合法数据的情况。
对比这两种处理方法,可以知道java的"异常机制"把处理异常事件的职能和方法本身的职能通过两个不同出口分离开来。
所有这些"异常类"独立于它具体服务的方法被统一组织成一个类树。"异常机制"就好比高校的后勤社会化一样,通过后勤社会化将学校的教学职能和学校的后勤保障分离开来,并且后勤集团的组织形式也是独立于学校主体的。事实证明,这种组织方式不仅提高了服务效率,也提高了服务质量。整个Java体系中的"异常类"组织形式如图1所示:
在例1中的isLegal()方法如果在调用过程中没有能正常返回整形数,而是在"异常"产生点产生了"异常"对象,那么这个"异常"对象由谁来接收,并处理它呢?以下就来解答这个问题。
二、"异常"的处理过程
Java中由try…catch语法来处理"异常",将关联有"异常类"的方法包含在try{}程序块中,catch(){}关键字可以使用形参,用于和方法产生的"异常"对象结合。当调用某个方法时,引起异常事件发生的条件成立,便会抛出"异常",原来的程序流程将会在此方法处中断,然后try模块后紧跟的catch中的"形参"和此异常对象完成了结合,继而进入了catch模块中运行。具体过程举例说明:
例2:
*将关联有异常的方法包含在try模块中*
int myMethod(int dt){
int data = 0;
try{
int data = isLegal(dt);
}catch(LowZeroException e){
System.out.println("发生数据错误!");
}
return data;
}
三、"异常"的处理方法
有两种方法处理"异常":第一种如例2,将含有"异常"出口的方法直接放到try块中,然后由紧随其后的catch块捕捉。第二种是不直接监听捕捉被引用方法的"异常",而是将这个"异常"关联传递给引用方法,同时监听捕捉工作也相应向上传递。
例3:
int myMethod2(int dt)
{
int data = 0;
try{
data = myMethod(dt)
}catch(LowZeroException e){
System.out.println("发生数据错误!");
e.printStackTrace();
}
return data;
}
int myMethod(int dt) throws LowZeroException
{
int data = isLegal(dt); 此处引用isLegal()方法,但并没有捕捉它的"异常"
return data;
}
从上例中可以看到方法myMethod()与它引用的方法isLegal()产生的"异常"LowZeroException建立了关联,也就是完成了将"异常"关联的向上传递,此时的myMethod()方法体中虽然只有一个return返回语句,但它事实上同样有两种方式的函数出口,一种是由return返回的整形值,另一种则是返回方法名中的throws关键字所指的"异常类"的实例对象。相应的,监听捕捉的工作交给了上一层方法myMethod2()。同样的道理,myMethod2()也可以将"异常"通过throws的关联继续向上传递。这样的话,一旦一个"异常"被捕捉到时,这个"异常"必有一个传递路径,而如果我们在捕捉点的catch程序块中加入printStackTrace()方法,便能清楚的看到这个"异常"是怎样传递过来的。例如在例3如果有"异常"被捕捉到,e.printStackTrace()打印出来的结果将是:
LowZeroException:
at Example.isLegal
at Example myMethod
at Example.myMethod2
at Example main
从上结果中我们可以看到,从LowZeroException"异常"产生点,即包含throw new LowZeroException();子句的方法开始,然后一直追溯到产生当前线程的方法(注意:printStackTrace()并不是追溯到捕捉点结束,而是到产生当前线程的方法结束)。"异常"产生点产生的LowZeroException"异常"对象,首先被赋给了isLegal()关联的LowZeroException类的无名引用,然后继续赋给myMethod()关联的LowZeroException类的无名引用,再继续赋给myMethod2()中的catch块中的形参e,最后在这里被处理掉,这个"异常"对象随即消失。可以说,catch(){}就是"异常"对象的生命终结点。
另外还要注意一点,方法与"异常"的关联可以一直向上传递,当传递到与main方法关联后,即在main()方法的定义中使用了throws Exception,这时除了虚拟机没有其它方法能够引用main()方法,且在程序中可能看不到try…catch程序块,但并不会产生错误,因为此时虚拟机会捕捉"异常",并且会默认的调用printStackTrace()方法打印出"异常"路径。总之只要一个方法关联了"异常",可以将这个"异常"关联向上传递,但是最终必须使用catch来终止"异常",或者一直传递到main()方法交给Java虚拟机来结束"异常"对象的生命,否则是通不过编译的。
四、使用"异常机制"的需要注意的几点
1.一个方法中可能会产生多种不同的异常,你可以设置多个"异常"抛出点来解决这个问题。
2."异常"对象从产生点产生后,到被捕捉后终止生命的全过程中,实际上是一个传值过程,所以你可以根据需要,来合理的控制检测到"异常"的粒度。例如在例3中,如果你并不需要知道具体产生的是LowZeroException"异常",那么你可以使用"异常"的公共父类Exception来结合"异常"对象,即catch(Exception e){…}。同样在"异常"与方法关联的传递过程中,也可以根据需要控制关联"异常"的粒度,即throws后面跟上异常对象的父类名。
3."异常机制"中还有一种特殊情况――RuntimeException"异常类"(见图1),这个"异常类"和它的所有子类都有一个特性,就是"异常"对象一产生就被Java虚拟机直接处理掉,即在方法中出现throw 子句的地方便被虚拟机捕捉了。因此凡是抛出这种"运行时异常"的方法在被引用时,不需要有try…catch语句来处理"异常"。
|