2004年09月26日

    近几年,随着计算机及其周边设备技术的日益成熟和价位的一路走低,网络技术得到了充分的发展。企事业单位、工厂、学校等纷纷组建自己的局域网,有网络就有网站,有个人主页。在局域网中有很多的朋友都想将自己搜集来的影音佳作拿出来与大家一起分享,往往都是通过电影网站的形式对外发布,在实际的应用中,存在着建站复杂、维护管理困难、缺乏灵活性等等弊端,使许多的新手朋友望而却步。今天笔者就教大家一个快速搭建自己的流媒体点播系统的方法,注意是点播,也就是说具有很强的交互性哟。OK,我们开工吧!
    首先请出我们今天的主角—-”远古WebVOD视频点播系统”,下载地址:http://www.viewgood.com,大小:13.1MB。WebVOD(Web Video On Demand)即网络交互式视频点播,是随着计算机技术和网络通讯技术的发展,综合了计算机、通讯技术、电视技术而迅速新兴的一门综合性技术,集动态影视图像、静态图变、声音、文字等信息为一体,为用户提供实时、交互、按需点播服务的系统。用户还可以在播出过程中留言、发表评论等,从而加强交互性和增加用户与节目之间的交流。整个软件只需要在服务器上安装即可提供服务,在网络上的任何有权限访问该服务系统的用户,均可通过Web浏览器进行点播访问。

   视频点播服务器的安装

  本模块讲述了专门针对文件服务器安全模板的配置。本模块假定已将成员服务器基准应用于服务器。除安全模板定义的安全配置设置外,本模块还考虑了必须应用的其他安全配置设置。为了创建完全强化的文件服务器,您需要进行这些设置。

  目标

  使用本模块可以:

  1.强化基于 Microsoft? Windows Server? 2003 操作系统的文件服务器。
  2.检查文件服务器合适的安全配置。

  适用范围

  本模块适用于下列产品和技术:

  Windows Server 2003

  如何使用本模块

  使用本模块您可以了解应用于基于 Windows Server 2003 的文件服务器的安全设置。本模块使用多个角色特定安全模板和一个基准安全模板。安全模板来自“Windows Server 2003 Security Guide”。

  为了更好地理解本模块的内容,请:

  阅读模块 Windows Server 2003 安全性简介。该模块描述了“Windows Server 2003 Security Guide”的目的和内容。
  阅读模块创建 Windows Server 2003 服务器的成员服务器基准。该模块演示了使用组织单位和组策略将成员服务器基准应用于多个服务器的过程。

  概述

  由于文件服务器提供的大多数重要服务都需要 Microsoft? Windows? 网络基本输入/输出系统 (NetBIOS) 相关协议的支持,因此在进一步强化文件服务器上存在一些挑战。服务器消息块 (SMB) 协议和通用 Internet 文件系统 (CIFS) 协议可以为没有经过身份验证的用户提供丰富的信息。因此,常常建议在高安全性的 Windows 环境中禁止文件服务器使用这些协议。但是,禁用这些协议可能给您的环境中的管理员和用户访问文件服务器造成一定的困难。

  本模块后面的部分将详细描述文件服务器可从安全设置中受益的内容,这些安全设置不是通过成员服务器基准策略 (MSBP) 得到应用的。有关 MSBP 的详细信息,请参阅模块创建 Windows Server 2003 服务器的成员服务器基准。

  审核策略设置

  在本指南定义的三种环境下,文件服务器的审核策略设置都是通过 MSBP 进行配置。有关 MSBP 的详细信息,请参阅模块创建 Windows Server 2003 服务器的成员服务器基准。MSBP 设置确保了在全部的文件服务器上记录所有相关的安全性审核信息。

  用户权限分配

  在本指南定义的三种环境下,文件服务器的用户权限分配都是通过 MSBP 进行配置。有关 MSBP 的详细信息,请参阅模块创建 Windows Server 2003 服务器的成员服务器基准。MSBP 设置确保所有适当的用户权限可以跨越所有文件服务器实现统一配置。

安全选项

  在本指南定义的三种环境下,文件服务器的安全性选项设置都是通过 MSBP 进行配置。有关 MSBP 的详细信息,请参阅模块创建 Windows Server 2003 服务器的成员服务器基准。MSBP 设置确保所有相关的安全性选项设置可以跨越所有文件服务器实现统一配置。

  事件日志设置

  在本指南定义的三种环境下,文件服务器的事件日志设置都是通过 MSBP 进行配置。有关 MSBP 的详细信息,请参阅模块创建 Windows Server 2003 服务器的成员服务器基准。

  系统服务

  任何服务或应用程序都是一个潜在的攻击点,因此,应该禁用或删除所有不需要的服务或可执行文件。在 MSBP 中,这些可选服务以及所有不必要的服务都禁用。

  在运行 Microsoft Windows Server 2003 的文件服务器上,经常还有其他一些服务被启用,但是,这些服务不是必需的。这些服务的使用及其安全性一直是人们争论的主题。为此,本指南所建议的文件服务器配置可能不适用于您的环境。可以根据需要调整我们建议的文件服务器组策略以满足您组织机构的需求。

  Distributed File System

  表 1:设置

  Distributed File System (DFS) 服务管理分布在局域网 (LAN) 或广域网 (WAN) 的逻辑卷,而且是 Microsoft Active Directory? 目录服务 SYSVOL 共享所必需的。DFS 是将完全不同的文件共享集成为一个逻辑命名空间的分布式服务。

  命名空间是网络存储资源的一种逻辑表示方法,这些网络存储资源对网络中的用户都可用。禁用 DFS 服务可以防止用户通过逻辑命名空间访问网络数据,要求用户必须知道环境中所有服务器和共享资源的名称才可以访问网络数据。

  文件服务器增量式组策略禁用了 DFS 服务,以便将环境中文件服务器遭到的攻击面降到最小。为此,在本指南所定义的所有安全环境中,应该将 Distributed File System 设置配置为“禁用”。

  注意:通过在文件服务器上使用 DFS 简化分布式资源访问方式的组织机构必须修改文件服务器的增量式组策略,或者创建一个新的 GPO 来启用该服务。

  File Replication Service

  表 6.2:设置

  File Replication Service (FRS) 可以自动复制文件并在多个服务器上同时进行保存。FRS 是 Microsoft? Windows? 2000 操作系统和 Windows Server 2003 家族中的一种自动文件复制服务。这种服务复制所有域控制器中的系统卷 (Sysvol)。另外,您还可以对该服务进行配置,使其复制与容错 DFS 关联的备用目标中的文件。若禁用这种服务,文件复制将不再发生而服务器上的数据也不再进行同步。

  文件服务器增量式组策略禁用了 FRS 服务,以便将您所在环境中文件服务器遭到的攻击表面积降到最小。为此,在本指南定义的所有安全环境中,应该将 File Replication Service 设置配置为“禁用”。

  注意:通过在文件服务器上使用 FRS 复制多个服务器上的数据的组织必须修改文件服务器的增量式组策略,或者创建一个新的 GPO 来启用该服务。
其他安全性设置

  MSBP 中应用的安全设置为文件服务器提供了大量的增强安全性。不过,您也需要考虑其他一些注意事项。这些步骤不能通过组策略来实施,而要在所有文件服务器上手动执行操作。

  保护众所周知帐户的安全

  Microsoft Windows Server 2003 中具有大量的内置用户帐户,不能将其删除,但可以重命名。Windows 2003 中最常用的两个内置帐户是“来宾”帐户和“管理员”帐户。

  默认情况下,“来宾”账户在成员服务器和域控制器上为禁用状态。不应该将此设置更改。您应该对内置的“管理员”账户重命名并改变其描述,以阻止攻击者利用一个众所周知的帐户危及远程服务器的安全。

  最初,许多恶意代码的变种使用内置的管理员帐号,企图破坏服务器。近几年来,进行上述重命名配置的意义已经降低了,因为出现了很多新的攻击工具,这些工具企图通过指定内置 “管理员”账户的安全标识符 (SID) 确定该帐户的真实姓名,从而侵入服务器。SID 是识别每个用户、组、计算机帐户和网络上登录会话的唯一值。不可能更改内置帐户的 SID。将本地管理员帐户重命名为唯一的名称,操作部门就可以轻松监控攻击该帐户的企图。

  要保护文件服务器上的常用账户,您应当:

  1. 重新命名“管理员”帐户和“来宾”账户,然后在每个域和服务器上将其密码更改为长且复杂的值。
  2. 在每个服务器上使用不同的名称和密码。如果所有的域和服务器使用同一个帐户名和密码,则取得其中一台成员服务器访问权的攻击者就可以用相同的帐户名和密码取得其他域和服务器的访问权。
  3. 改变默认的帐户描述,以防止帐户被轻易识别。
  4. 在安全的位置记录这些更改。

  注意:可通过组策略重命名内置的“管理员”帐户。由于应该为您的环境选择一个唯一的名称,因此本指南所提供的所有安全模板中都没有对此设置进行配置。在本指南定义的三种环境中,都可以将“账户:重命名管理员帐户”设置配置为重命名管理员帐户。此设置是组策略安全选项设置的一部分。

  保护服务账户

  除非绝对必要,否则不要配置在域帐号安全性背景之下运行的服务。如果服务器的物理安全受到破坏,域账户密码可以很容易通过转储本地安全性机构 (LSA) 秘文而获得。

  用 IPSec 过滤器阻断端口

  Internet 协议安全 (IPSec) 过滤器能为提高服务器的安全级别提供一条有效途径。本指南推荐在其定义的高安全性环境中使用该选项,以便进一步减少服务器的攻击表面积。

  有关 IPSec 过滤器使用的详细信息,请参阅模块其他成员服务器强化过程。

  下表列出了可在本指南定义的高安全性环境中的文件服务器上创建的所有 IPSec 过滤器。

  表 3:文件服务器 IPSec 网络流量图

  在执行上表列出的所有规则时都应该进行镜像处理。这可以确保进入服务器的所有网络流量也可以返回到源服务器。

  上表描述了为服务器执行特别任务功能所需打开的基本端口。如果服务器使用静态 IP 地址,这些端口已经足够使用了。如果需要提供其他功能,可能需要打开其他端口。打开其他端口可使您环境中的文件服务器更容易管理,不过,它们可能大大降低这些服务器的安全性。
由于域成员和域控制器之间具有大量的交互操作,因此在特殊的 RPC 和身份验证通信中,您需要允许文件服务器和所有域控制器之间的所有通信。还可以将通信进一步限制,但是大多数环境都需要为有效保护服务器而创建更多的过滤器。这使得 IPSec 策略的执行和管理更为困难。与一个文件服务器相关的所有域控制器都要创建相似的规则。为了提高文件服务器的可靠性和可用性,您需要为环境中的所有域控制器添加更多规则。

  如上所述,如果在环境中运行 Microsoft Operation Manager (MOM),则必须允许通过运行 IPSec 过滤器的服务器和 MOM 服务器之间的所有网络通信。这一点十分重要,因为 MOM 服务器和 OnePoint 客户 — 向 MOM 控制台提供报告的客户端应用程序 — 之间具有大量的交互行为。其他的管理软件可能也有相似的要求。如果需要更高级别的安全性,可以将 OnePoint 客户过滤操作配置为就 IPSec 同 MOM 服务器进行协商。

  IPSec 策略可以有效地阻止任意一个高端口的通信,因此,将无法进行远程过程调用 (RPC) 通信。这使得服务器的管理更加困难。由于已经有效关闭了如此之多的端口,因此可以启用终端服务。这将使管理员能够进行远程管理。

  上面的网络流量图假定环境中包括启用了 DNS 服务器的 Active Directory。如果使用独立的 DNS 服务器,可能还需要设定其他规则。

  执行 IPSec 策略不会对服务器的性能产生明显的影响。但是,在执行这些过滤器前应首先进行测试,以验证服务器的必要功能和性能是否得以维持。如果要支持其他应用软件,还可能需要添加其他规则。

  本指南包括一个 .cmd 文件,该文件简化了为文件服务器创建 IPSec 过滤器的过程。“PacketFilters-File.cmd”文件使用 NETSH 命令创建适当的过滤器。必须修改 .cmd 文件以使它包括环境中域控制器的 IP 地址。脚本为即将添加的域控制器提供了两个占位符。如果需要,还可以添加其他的域控制器。域控制器的 IP 地址列表必须是最新的。

  如果环境中有 MOM,那么相应的 MOM 服务器的 IP 地址也必须列入脚本。这个脚本不会创建永久性的过滤器。因此,除非 IPSec 策略代理开始运行,否则服务器是不受保护的。有关生成永久性的过滤器或创建更高级 IPSec 过滤脚本的详细信息,请参阅模块其他成员服务器强化过程。最后,将该脚本配置为不对其创建的 IPSec 策略进行分配。IP 安全策略管理单元可用于检查它创建的 IPSec 过滤器和分配 IPSec 策略以便使其生效。

  小结

  本模块讲述了在本指南所定义的三种环境中保护文件服务器安全所需采取的服务器强化设置。所论述的大多数设置都是使用组策略进行配置和应用的。您可以将能够对 MSBP 进行有益补充的组策略对象 (GPO) 链接到包含文件服务器的相应组织单位 (OU) 中,以便为这些服务器提供的服务赋予更多的安全性。

  本指南所论述的一些设置不能使用组策略进行应用。在这些情况下,本指南提供了有关手动配置这些设置的详细信息。此外,本指南还提供了创建和应用能够控制文件服务器间网络通信类型的 IPSec 过滤器的详细信息。

  文件系统是整个UNIX系统中与用户关系最密切,用户操作最频繁的部分,随着系统运行时间的延续,文件系统的使用效率也跟着下降,这主要表现为:硬盘空间的减少,垃圾信息的增加,寻址时间的增加等。本文将介绍几种提高文件系统使用效率的方法,和大家共享,欢迎更正和补充。

  首先,我们应该对UNIX文件系统的结构有一个了解。文件系统是UNIX系统中的文件,目录,以及对这些文件和目录进行管理的数据结构的总称。UNIX文件系统包括引导块、超级块、i节点区、文件存储区、进程对换区等几部分。

  引导块占用第0号物理块,不属于文件系统管辖,如果系统中有多个文件系统,只有根文件系统才有引导程序放在引导块中,其余文件系统都不使用引导块;

  超级块占用第1号物理块,是文件系统的控制块,超级块包括:文件系统的大小、空闲块数目、空闲块索引表、空闲i节点数目、空闲i节点索引表、封锁标记等。超级块是系统为文件分配存储空间、回收存储空间的依据。

  而i节点是对文件进行控制和管理的一种数据结构。一个文件对应一个i节点,每个i节点都有一个唯一的i节点号,i节点由64个字节组成,保存了文件的属性和类型、存放文件内容的物理块地址、最近一次的存取时间、最近一次的修改时间、创建此文件的时间。要注意哦:i节点中并不包括文件名,文件名和文件占用的i节点的i节点号放在目录文件的目录项中。

  文件存储区是存放文件内容的区域,文件存储区中各数据块的使用情况在超级块中由记录,系统利用超级块中的记录完成对数据块的分配和回收。

  在文件系统的末尾还可能有进程对换区,这里保留了对换到内存中的进程的映象,它不属于文件系统管辖。相信通过上面的这段文字,兄弟们应该对文件系统有一个大致的了解了。

  为提高UNIX系统的运行效率,文件系统应该保持一定的空闲空间,理论上,空闲空间至少应占文件系统总空间的15%,当空闲空间小于总空间的15%时,UNIX系统操作将明显变慢!因此系统管理员应该了解文件系统空闲时间和i节点的使用情况,保证系统的高效运行。系统管理员可以使用df命令了解空闲空间和i节点的使用情况。使用“df -v 文件系统”命令,可以显示文件系统的使用情况:该文件系统数据块的总数、已使用数据块的数量、空闲数据块的数量、使用的数据快占总数据块的百分比。如果想显示文件系统的i节点总数、空闲i节点的数目、已使用i节点数目以及已使用i节点占总i节点的百分比,可使用-i选项。比如在Solaris下,可执行:df -o i(注意哦:i前没有“-”,呵呵)

  当文件系统中的空闲空间减少,以至于使文件系统的效率降低时,可采用以下措施:

  1.删除临时目录下的文件

  临时目录下存放的都是临时文件,临时文件是在程序执行期间根据需要创建的,但没有被及时删除。系统中的临时目录一般有/var/tmp、/tmp和/usr/tmp。管理员可以手工删除他们,也可以建立一个作业调度,使清理工作自动进行。比如在/usr/spool/cron/crontabs/root中加入以下代码:

  30 2 * * * find /var/tmp –atime 7 –exec rm{} \ ; >/dev/null
  30 2 * * * find /usr/tmp –atime 7 –exec rm{} \ ; >/dev/null
  30 2 * * * find /tmp –atime 7 –exec rm{} \ ; >/dev/null

  2.删除core文件

  当系统中一些进程由于收到一些信号而非正常结束时,系统便建立一个core文件,记录进程当时的一些信息,包括进程状态、数据以及硬件寄存器的值等。在这些core文件没有用的时候,可以考虑删除它们。

  3.清除系统日志文件

  日志文件包含了有关系统运行情况的信息和用户访问系统的情况,系统在运行过程中,会不断地把新的信息添加到日志文件中,因此日志文件会急剧增多,系统中主要的日志文件有:

  /usr/adm/acct 记帐日志文件
  /usr/adm/messages 系统信息日志文件
  /usr/adm/sulog 命令使用日志文件
  /usr/adm/vold.log 卷管理日志文件
  /usr/spool/uucp/LOGFILE uucp的记录
  /usr/spool/uucp/.Log/.Old/* 旧的uucp日志文件
  /usr/spool/lp/logs/requests 打印请求记录
  /var/log/syslog 系统日志文件

  上述有的日志文件对分析系统出现的故障有帮助,因此在清除时要慎重。

  4.压缩不常使用的文件

  对平时不经常使用的文件可以进行压缩,以便节省空间,压缩与解压缩的命令有:compress和uncompress;gzip和gunzip等命令。

  5.减少小文件的使用

  文件系统中的i节点数目是一定的,系统中如果小文件太多,会浪费很多的i节点,这样可能会导致系统中虽然还有磁盘空间,但无法创建新文件的情况。可以把多个小文件合并成一个大文件以节省i节点,也可以删除系统中不再需要的文件。

  6.增加文件系统i节点的数目

  从文件系统的结构看,一个文件占用一个i节点和若干个数据块。当i节点用完时,可考虑在硬盘上重新建立文件系统,指定比较大的i节点数目。这时首先要备份原文件系统,然后卸载该文件系统,使用mkfs重新建立文件系统,指定更大的i节点数目,该命令的格式为:

  #mkfs 设备文件数目:新的i节点数目

  把新的文件系统安装到系统中,最后利用备份恢复文件系统的内容。

如果你的Linux服务器被非受权用户接触到(如服务器放在公用机房内、公用办公室内),那么它的安全就会存在严重的隐患。

使用单用户模式进入系统

Linux启动后出现boot:提示时,使用一个特殊的命令,如linuxsingle或linux 1,就能进入单用户模式(Single-User mode)。这个命令非常有用,比如忘记超级用户(root)密码。重启系统,在boot:提示下输入linux single(或linux 1),以超级用户进入系统后,编辑Passwd文件,去掉root一行中的x即可。

防范对策:

以超级用户(root)进入系统,编辑/etc/inittab文件,改变id:3:initdefault的设置,在其中额外加入一行(如下),让系统重新启动进入单用户模式的时候,提示输入超级用户密码:

~~:S:walt:/sbin/sulogin

然后执行命令:/sbin/init q,使这一设置起效。

天极IT资讯短信服务 电脑小技巧
资费:包月5元
手机:    
   介绍:细处着手,巧处用功。高手和菜鸟之间的差别就是:高手什么都知道,菜鸟知道一些。电脑小技巧收集最新奇招高招,让你轻松踏上高手之路。  

在系统启动时向核心传递危险参数

在Linux下最常用的引导装载(boot loader)工具是LILO,它负责管理启动系统(可以加入别的分区及操作系统)。但是一些非法用户可能随便启动Linux或者在系统启动时向核心传递危险参数,这也是相当危险的。

防范对策:

编辑文件/etc/lilo.conf,在其中加入restricted参数,这一参数必须同下面一个要讲的password参数一起使用,表明在boot:提示下,传递给Linux内核一些参数时,需要你输入密码。

password参数可以同restricted一起使用,也可以单独使用,下面将分别说明。

同restricted一起使用:只有在启动时需要传递给内核参数时,才会要求输入密码,而在正常(缺省)模式下,是不需要密码的,这一点一定要注意。

单独使用(没有同restricted一起使用):表示不管用什么启动模式,Linux总会要求输入密码;如果没有密码,就没有办法启动Linux,在这种情况下的安全程度更高,相当于外围又加入一层防御措施。当然也有坏处——你不能远程重启系统,除非你加上restricted参数。

由于密码是明文没有加密,所以/etc/lilo.conf文件一定要设置成只有超级用户可读,可使用下面的命令进行设置:

chmod 600 /ietc/lilo.conf

然后执行命令:/sbin/lilo -V,将其写入boot sector,并使这一改动生效。

为了加强/etc/liIo.conf文件的安全,你还可以设置这个文件为不可改变的属性,可使用命令:

chattr  十i/etc/lilo.conf

如果日后你要修改/etc/liIo.conf文件,用chattr -i/etc/lilo.conf命令去掉这个属性即可。

使用“Ctrl+Alt+Del”组合键重新启动

对于这一点,非常重要,也非常容易忽略,如果非法用户能接触到服务器的键盘,他就可以用组合键“Ctrl+AIt+Del”使你的服务器重启。

防范对策:

编辑/etc/inittab文件,给ca::ctrlaltdel:/sbin/shutdown-t3 -r now加上注释###ca::ctrlaltdeI:/sbin/shutdown-t3 -r now。

然后执行命令:/sbin/init q,使这一改动生效。v

为了更好的理解这一问题的解决方法,我们需要先介绍一下.NET Framework的安全机制。然后再结合我们的实际问题来讨论解决方案。

  为了解决安全问题,.NET Framework提供了一种称为代码访问安全性的安全机制。代码访问安全性允许根据代码的来源和代码的标识等属性将代码设置为不同级别的信任代码,同时还详细定义了不同级别的对代码的信任,从而可以详细的对代码设置各自的权限而不是将最大权限赋给所有的代码。使用代码访问安全性,可以减小恶意代码或各种错误的代码带来的严重的系统安全性问题的可能性。您可以设置允许代码执行的一组操作,同样可以设置永远不允许代码执行的一组操作。

  实现代码访问安全性的基础就是JIT(运行时编译)和IL(中间代码)。所以所有以公共语言运行库为目标的托管代码都会受益于代码访问安全性。非托管代码则无法完全使用代码访问安全性。
下面我们将介绍一下代码访问安全性实现的各种功能:

  代码访问安全性是控制代码对受保护资源和操作的访问权限的一种机制。在 .NET Framework中,代码访问安全性执行下列功能:

  · 定义权限和权限集,它们表示访问各种系统资源的权限。

  · 使管理员能够通过将权限集与代码组关联来配置安全策略。

  · 使代码能够请求运行所需权限以及其他一些有用的权限,以及指定代码绝对不能拥有哪些权限。

  · 根据代码请求的权限和安全策略允许的操作,向加载的每个程序集授予权限。

  · 使代码能够要求其调用方拥有特定的权限。

  · 使代码能够要求其调用方拥有数字签名,从而只允许特定组织或特定站点的调用方来调用受保护的代码。

  · 通过将调用堆栈上每个调用方所授予的权限与调用方必须拥有的权限相比较,加强运行时对代码的限制。

  为了确定是否已授予代码相应的权限,.NET运行库的安全系统将遍历整个调用堆栈,将每个调用方所授予的权限与目前要求的权限相比较。如果调用堆栈中的任何调用方没有要求的权限,则会引发安全性异常,并会拒绝访问和相应的操作。堆栈步旨在防止引诱攻击;在这种攻击中,受信程度较低的代码调用高度信任的代码,并使用高度信任的代码执行未经授权的操作。在运行时要求所有调用方都拥有权限将影响性能,但对防止代码遭受攻击至关重要。若要优化性能,可以使代码执行较少的堆栈步;但是,任何时候这样做时均必须确保不会暴露安全缺陷。

  还存在另外一种代码访问安全性的常见用途,即应用程序将控件从网络 Web 站点直接下载到客户端,这种方式的代码安全性也是可以在客户端进行设置的,根据签名等数据权限证书来确定是不是可以允许下载的控件运行。这种方法类似于ActiveX的安全性设置,但是比之在设置权限更加详细和强大。同JAVA APPLET的沙箱安全机制相比,.NET 的客户端控件可以在本地简单设置后访问客户端的各种资源。由于这一方面的用途不是我们的重点,所以我们在这里就不再更详细的讨论其用途及其实现原理了。

  下面我们就谈谈如何应用这一安全特性来解决ASP.NET中存在的系统安全漏洞。由于我们介绍的系统是共享主机,所以有其特殊性,即系统管理员无法事先给所有的代码赋予相应的权限,因为每个用户都可能有各种权限要求,并且这些要求特殊权力的代码在使用中都可能出现的,所以在权限管理上随时都有各种要求。
因此在权限设置方面,不仅仅是管理员设置,也包括了各个共享主机用户的权限请求,这也正是安全代码机制的一个重要部分。

  请求权限是您让运行库知道代码执行有哪些操作权限的方法。通过将属性(声明式语法)放到代码的程序级范围来为程序集请求权限。

  请求内置权限的代码示例:

//The attribute is placed on the assembly level.
using System.Security.Permissions;
[assembly:PermissionSetAttribute(SecurityAction.RequestMinimum, Name = "FullTrust")]

  将此段代码放在程序的开始部分(namespace声明之前),在编译时就会将请求的权限存储在程序集清单中。加载时,运行库检查权限请求,并应用安全策略规则来确定授予程序集哪些权限。

  虽然我们编写的大部分代码都没有请求权限,其实不管是共享主机形式还是独立服务器形式都应该请求权限,这是因为请求权限有助于确保只将代码需要的权限授予代码。如果没有授予代码额外权限,即使某些恶意代码想利用您的代码来进行安全性破坏,它也无法操作没有赋给您自己代码相应权限的额外系统资源。您只应该请求代码需要的那些权限,而不应请求更多权限。

  代码请求权限之后,系统管理员可以使用”权限查看”工具 (Permview.exe,位于您的.NET Framework的目录的bin目录下) 来检查您的程序集并根据其他条件来设置安全策略以决定是否给您的代码所请求的相应权限。如果您不显式地在代码中请求应用程序需要的权限,那么管理员将很难管理您的应用程序。在权限管理严格的主机上,将无法实现您的代码所要求的功能。

  请求权限会通知运行库应用程序正常运行需要哪些权限,或具体不需要哪些权限。在.NET Framework安装后的默认状态下,所有代码都是FullTrust(完全信任)的。这时是不需要申请任何权限的,但是管理员一旦修改了代码安全,我们使用的磁盘访问就要受到限制了,这是就需要申请相应的权限了。我们上边介绍的文件管理代码就需要具有本地硬盘读写操作的能力,则应用程序必须拥有 FileIOPermission。如果代码不请求 FileIOPermission,在本地安全设置不允许应用程序拥有此权限的主机上,在应用程序尝试磁盘操作时就会引发安全性异常。即使应用程序能够处理此异常,也不会允许它操作磁盘。当然,如果您的代码不访问受保护的资源或执行受保护的操作,则不必请求任何权限。例如,如果代码只根据向它传递的输入来计算结果而不使用任何资源,则不必请求权限。如果您的代码访问受保护的资源但未请求必要的权限,则仍可能允许它执行,但如果它尝试访问某种资源而它又没有必要的权限,则可能在执行过程中失败。

现在绝大多数的虚拟主机都禁用了 ASP 的标准组件:FileSystemObject,因为这个组件为 ASP 提供了强大的文件系统访问能力,可以对服务器硬盘上的任何文件进行读、写、复制、删除、改名等操作(当然,这是指在使用默认设置的 Windows NT / 2000 下才能做到)。但是禁止此组件后,引起的后果就是所有利用这个组件的 ASP 将无法运行,无法满足客户的需求。
  如何既允许 FileSystemObject 组件,又不影响服务器的安全性(即:不同虚拟主机用户之间不能使用该组件读写别人的文件)呢?这里介绍本人在实验中获得的一种方法,下文以 Windows 2000 Server 为例来说明。
  在服务器上打开资源管理器,用鼠标右键点击各个硬盘分区或卷的盘符,在弹出菜单中选择“属性”,选择“安全”选项卡,此时就可以看到有哪些帐号可以访问这个分区(卷)及访问权限。默认安装后,出现的是“Everyone”具有完全控制的权限。点“添加”,将“Administrators”、“Backup Operators”、“Power Users”、“Users”等几个组添加进去,并给予“完全控制”或相应的权限,注意,不要给“Guests”组、“IUSR_机器名”这几个帐号任何权限。然后将“Everyone”组从列表中删除,这样,就只有授权的组和用户才能访问此硬盘分区了,而 ASP 执行时,是以“IUSR_机器名”的身份访问硬盘的,这里没给该用户帐号权限,ASP 也就不能读写硬盘上的文件了。
  下面要做的就是给每个虚拟主机用户设置一个单独的用户帐号,然后再给每个帐号分配一个允许其完全控制的目录。
  如下图所示,打开“计算机管理”→“本地用户和组”→“用户”,在右栏中点击鼠标右键,在弹出的菜单中选择“新用户”:

  在弹出的“新用户”对话框中根据实际需要输入“用户名”、“全名”、“描述”、“密码”、“确认密码”,并将“用户下次登录时须更改密码”前的对号去掉,选中“用户不能更改密码”和“密码永不过期”。本例是给第一虚拟主机的用户建立一个匿名访问 Internet 信息服务的内置帐号“IUSR_VHOST1”,即:所有客户端使用 http://xxx.xxx.xxxx/ 访问此虚拟主机时,都是以这个身份来访问的。输入完成后点“创建”即可。可以根据实际需要,创建多个用户,创建完毕后点“关闭”:

现在新建立的用户已经出现在帐号列表中了,在列表中双击该帐号,以便进一步进行设置:

在弹出的“IUSR_VHOST1”(即刚才创建的新帐号)属性对话框中点“隶属于”选项卡:

刚建立的帐号默认是属于“Users”组,选中该组,点“删除”:

  现在出现的是如下图所示,此时再点“添加”:

在弹出的“选择 组”对话框中找到“Guests”,点“添加”,此组就会出现在下方的文本框中,然后点“确定”:

  出现的就是如下图所示的内容,点“确定”关闭此对话框:

  打开“Internet 信息服务”,开始对虚拟主机进行设置,本例中的以对“第一虚拟主机”设置为例进行说明,右击该主机名,在弹出的菜单中选择“属性”:

  弹出一个“第一虚拟主机 属性”的对话框,从对话框中可以看到该虚拟主机用户的使用的是“F:\VHOST1”这个文件夹:

  暂时先不管刚才的“第一虚拟主机 属性”对话框,切换到“资源管理器”,找到“F:\VHOST1”这个文件夹,右击,选“属性”→“安全”选项卡,此时可以看到该文件夹的默认安全设置是“Everyone”完全控制(视不同情况显示的内容不完全一样),首先将最将下的“允许将来自父系的可继承权限传播给该对象”前面的对号去掉:

此时会弹出如下图所示的“安全”警告,点“删除”:

 此时安全选项卡中的所有组和用户都将被清空(如果没有清空,请使用“删除”将其清空),然后点“添加”按钮。

  将如图中所示的“Administrator”及在前面所创建的新帐号“IUSR_VHOST1”添加进来,将给予完全控制的权限,还可以根据实际需要添加其他组或用户,但一定不要将“Guests”组、“IUSR_机器名”这些匿名访问的帐号添加上去!

  再切换到前面打开的“第一虚拟主机 属性”的对话框,打开“目录安全性”选项卡,点匿名访问和验证控制的“编辑”:

  在弹出的“验证方法”对方框(如下图所示),点“编辑”:

  弹出了“匿名用户帐号”,默认的就是“IUSR_机器名”,点“浏览”:

  在“选择 用户”对话框中找到前面创建的新帐号“IUSR_VHOST1”,双击:

 此时匿名用户名就改过来了,在密码框中输入前面创建时,为该帐号设置的密码:

  再确定一遍密码:

  OK,完成了,点确定关闭这些对话框。
  经此设置后,“第一虚拟主机”的用户,使用 ASP 的 FileSystemObject 组件也只能访问自己的目录:F:\VHOST1 下的内容,当试图访问其他内容时,会出现诸如“没有权限”、“硬盘未准备好”、“500 服务器内部错误”等出错提示了。
  另:如果该用户需要读取硬盘的分区容量及硬盘的序列号,那这样的设置将使其无法读取。如果要允许其读取这些和整个分区有关的内容,请右键点击该硬盘的分区(卷),选择“属性”→“安全”,将这个用户的帐号添加到列表中,并至少给予“读取”权限。由于该卷下的子目录都已经设置为“禁止将来自父系的可继承权限传播给该对象”,所以不会影响下面的子目录的权限设置。

此方法可以从根本上杜绝FSO的虚拟主机问题,昨天晚上我也试用了这一方法很方便的解决了FSO的问题。

在Microsoft .NET Framework Configuration中可以设置所有关于.NET Framework的属性。
  点击我的电脑,打开下拉菜单,我们可以看到程序集缓存、已配置程序集、远程处理服务、运行库安全策略、应用程序等五项。运行库安全策略设置是我们这篇文章的重点。
   
    我们可以先查看一下程序集缓存,在这里我们可以看到所有的全局程序集缓存,全局程序集缓存中存储了专门指定给由计算机中若干应用程序共享的程序集。在这里我们可以发现我们可以使用的所有的程序集,同时也可以添加和删除某些程序集。详细操作请参见.NET Framework SDK文档。
   
    我们在这里主要讨论的是运行库安全策略。在此策略中,按层次结构由高到低分为四个级别,即:企业、计算机、用户、应用程序。在计算权限授予时,运行库从该层次结构的顶部开始,然后向下进行计算。较低的策略级别不能对在较高级别上授予的权限进行增加,但是可以使权限减少。这就是说如果我们将计算机策略设置为较小的权限时,可以不必更改企业策略就可以使设置的权限生效,也就是说权限检查的顺序是从低级别到高级别,只有在低级别中不存在的设置才会检查上一级的设置。默认情况下,用户策略和应用程序域策略的限制性小于计算机策略和企业级策略。大部分默认策略存在于计算机级别。所以我们需要将默认安装的主机的权限在计算机级别上进行修改,修改的内容根据主机是不是共享主机,主机应用的其他不明代码的可能性来设置。如果是我们讨论的共享主机的话,在计算机级别上就尽量将权限设的小一些,为了避免我们讨论的文件系统安全问题,一定要注意权限中的本地磁盘访问权限。
   
    我们打开计算机策略设置可以发现几个默认的代码组、权限集和策略程序集。
   
    根据需要,我们可以添加代码组和自定义的权限集。
   
    在添加代码组的时候可以选择几种条件,主要的条件类型:默认为All Code、应用程序目录、哈希、强名称、作者、站点等。
   
    对于我们所要讨论的共享主机,我们需要将My_Computer_Zone下的All Code的权限更改为不能进行磁盘读写,在更改之前,我们需要先定义一个权限集。这一权限集的作用就是将我们需要点击权限集,右键快捷菜单中选择新建,会出现一个创建权限集的窗口,这里需要给我们新建的权限集命名。下一步就是将单个权限分配给权限集。如下图所示。

在这里我们可以给这个新建的权限集赋予一个的系统权限,如上图所示,可用的权限包括:目录服务、DNS、事件日志、环境变量、文件IO、OLEDB数据库操作、注册表等等。我们主要要说明的是文件IO操作,其他的权限操作可以根据自己的需求来设置。这里我们就不再说明了。
   
    在文件IO的权限设置中我们可以自定义针对每一个目录的权限,这里包括读、写、追加、路径盘等操作,在这里我们可以将我们需要的目录权限添加到列表中。因为我们是利用这一权限使所有没有配置权限的代码不可以进行文件IO操作,所以我们不强文件IO添加到分配的权限中。
   
    新建了这一权限集后,我们更改一下默认设置,即将All Code的权限设置为此新建的权限集,也就是说所有没有在此定义代码都不能访问文件IO系统。
   
    这里需要注意一件事情,因为Microsoft .NET Framework Configuration本身也需要文件IO权限,如果没有单独分配给Configuration一个文件IO操作权限的话,那么您就不能再次使用Configuration来设置权限了,只能重新安装.NET Framework了。所以我们需要将FullTrust权限分配给Configuration所使用的Dll,即mscorcfg.dll。在添加时,成员条件可以选择强名称,使用”导入”,到winnt/window .net/framework/versionnumber/下选择mscorcfg.dll。如果需要运行其他配置程序,还需要设置相应的权限,这些系统程序一般都在系统程序集缓存中。
   
    这样我们就完成了一个简单的设置,可以防止任何未经验证的代码访问文件IO系统。这样就从根本上防止了磁盘恶意操作。
   
    如果您今后需要利用这一功能或者有共享主机用户需要使用文件IO功能,那么您可以在Microsoft .NET Framework Configuration中将其加入代码,如果不能使其使用其他功能,可以仅仅设置一个只具有文件IO权限的权限集。如果是共享主机用户您还可以给他分配直接到其所使用的目录的全部读写权限,对于他的日志文档,您可以将读功能分配给用户。通过上边新建权限集时我们可以发现:权限集可以规定到每一个目录的读写权限,所以可以将用户锁定于其可以使用的目录中。当然对于共享主机提供商来说,最好的方法就是自己实现这些功能,然后配置权限系统使用户使用共享主机提供商的程序来实现他们的正常操作,而避免了恶意文件操作。
   
    需要注意的是如果分配给每一个单独的程序相应的权限时,我们最好使用强名称这一方式或者其他的可验证方式,强名称由程序集的标识–其简单文本名称、版本号和区域性信息(如果提供)–加上公钥和数字签名组成。这就需要我们使用Sn.exe 来设置密钥、签名和签名验证。强名称保证了程序是开发人员开发的并且没有被改动。
   
    在进行上面的设置之后,管理员可以根据用户的各种需求来设置不同的代码集和权限集。
   
    我们已经简单的介绍了一下ASP.NET中关于文件IO系统的漏洞的防治方法,这一方法有些繁琐,但是却可以从根本上杜绝一些漏洞,由于.NET的JIT(运行时编译)和IL(中间语言),.NET可以在程序编译时检查程序的安全性设置,所以能从根本上防止一些非法访问。.NET的代码安全性的内容很全面,我们讨论的只是很少的一部分,更多的功能需要大家共同来探索、学习。

   最新版的Serv-U3.0是一款不错的FTP服务器软件,可以应用于95/98/2000/me/NT上。使用Serv-U3.0,我们可以很快建立起强大的FTP服务器。
  Serv-U3.0由两部分组成:引擎(Engine)和用户接口(user interface)。Serv-U3.0 Engine(或称守护程序)是Serv-U3.0的核心部分,它执行FTP客户端发出的所有命令,实现文件的传输。Serv-U3.0 Administrator(Serv-U3.0管理员)是用户与engine交互的接口,主要用来配置Serv-U3.0和定义用户、指定访问权限等。
  Serv-U3.0为共享软件,标准版的注册费为49.95美元,专家版的注册费为299.95美元,主页地址是http://www.Serv-U.com。

Serv-U3.0具有以下主要特点:

标准Windows界面,设置维护非常方便;
众多安全设置。口令、基于目录和文件和读/写/添加/改变权限,可以分组管理用户并针对不同的用户提供不同的设置(包括匿名用户),可基于IP控制用户的访问;
快速稳定;
完全支持多主IP站点和虚拟FTP服务器;
支持实时多用户连接;
支持远程管理;
支持S/Key一次性口令;
可以做为”系统服务”后台运行于95/98/200/me和NT上;
支持临时账户,账户过期后会自动被删除;
OL/DL比率限制,磁盘配额限制,网络带宽限制;
完全UNC路径支持;
虚拟路径支持;
Links支持;
完全支持所有的”ls”目录列表选项;
支持用户与服务器间的消息通信;
文件上传下载的断点续传功能;
完全支持RFC959和RFC1123标准,包括被动传输模式;
详细的消息设置;
客户端超时自动挂断功能;
所有事务均有完整的记录(记录在文件中,显示在屏幕上),可以非常容易的被第三方软件读取。

2004年09月10日

    经常会碰到有人问如何保证程序只运行一个实例,原来我也零碎的给过两三个方法,今天干脆来个大总结,希望对大家在做程序设计的时候有所帮助。

    一个程序只运行一个实例(或限制实例数量)通常可以采用如下方法:

1)FindWindow 之<窗口标题>
    通过查找窗口标题来确定上一实例是否正在运行,不适合窗口标题动态变化的程序。

2)FindWindow 之<任务栏按纽标题>
    通过查找任务栏按纽标题来确定上一实例是否正在运行,不适合按纽标题动态变化的程序(如Winamp)。通常情况下,该方法还是优先考虑,因为按纽标题是一般是固定的。

3)Window Property
    将某个数据(可以是字符串或句柄)通过SetProp加入到指定窗口的property list,程序运行时枚举窗口并检查该数据是否存在来确定上一实例是否正在运行。

4)全局Atom
    将某个特定字符串通过GlobalAddAtom加入全局原子表(Global Atom Table),程序运行时检查该串是否存在来确定上一实例是否正在运行。该方法有个局限,就是程序终止前必须显式调用GlobalDeleteAtom来释放atom,否则该atom不会自动释放,如果程序运行时意外终结了,那么下一个实例就无法正常执行。早期版本的realplayer就存在这个现象,不知道是不是采用了该方法。

5)Mutex/Event/Semaphore
    通过互斥对象/信号量/事件等线程同步对象来确定实例是否存在,在NT下要注意权限问题(SID)。

6)DLL全局共享区域
    VC下的DLL工程可以通过下面代码来建立一个进程间共享数据段:
    #pragma data_seg(“.share”)
    //shared for all processes that attach to the dll
    DWORD dllgs_dwRunCount = 1; //一定要在这里对变量进行初始化,否则工夫白做!
    #pragma data_seg()
    #pragma comment(linker,”/section:.share,rws”)

    导出3个函数,分别为:
    DWORD IncRunCount(void); //运行计数器加1,返回计数器结果
    DWORD DecRunCount(void); //运行计数器减1,返回计数器结果
    DWORD GetRunCount(void); //取当前运行计数器

    由于DLL全局共享段在映射到各个进程地址空间时仅会被初始化一次,并且是在首次被windows加载时,所以利用该共享段数据就能对程序实例进行可靠计数。

7)内存映射文件(File Mapping)
    通过把程序实例信息(如窗口句柄、计数器等等)放置到跨进程的内存映射文件,同样可以控制程序实例运行的数量,道理与DLL全局共享区域类似。

8)其它
    曾经见过有人通过注册表、磁盘文件等途径来处理实例控制问题,但由于这些参考对象均为非易失性资源,在碰到程序非正常结束且没有清除实例标识时相当麻烦,真正使用起来具有很大的局限性。

    总结:前面三种方法适用于拥有窗体的程序,而后面几种则没有这个限制,但相对而言后者实现起来较复杂。不管采用哪种方法,参考对象均必须具有可共享、跨进程、易失性、重启自复位等必要性质。

    近日从网上发现了一个非常有趣的软件——私人磁盘(我经常会打成死人磁盘),当前版本为V3.70。该软件只有一个主程序(附带的那个Agent.dll没见有什么用,删了都能跑),使用相当简单,并且功能强大,起码从表面上看是这样,还号称加密强度一流!

    我当时就晕了,试用了一下,哎,还真吸引人。前段时间我也做了个加密虚拟磁盘来玩,但比起它差远了,首先我的虚拟磁盘不支持空间动态调整,而私人磁盘则完全实现了——巨轻松,它就象从源驱动器长出来似的,一切都是那么直截了当。还有就是,它那四百来K的超小SIZE直让我冒汗,我想俺算是密界最无知的人了。

    在强烈的好奇心驱动下,我耍起性子来了——俺就要剖了你!随便提一下,我分析(注意不是破解,我通常懒得去干)一个软件一般都不会去DEBUG来DEBUG去,费劲不讨好。先用eXeScope瞧瞧它的导入表吧,看看装了些什么东西。一瞧,狂晕……除了mpr.dll中的WNetGetConnectinA还有点发挥想象的余地外,其它都是三个代表。当然,我不排除写程序的兄台采用动态加载的方法,但这时我已对这没兴趣了。再CHECK CHECK资源吧,看看有没有在资源段内含了些SYS驱动什么的,RegMon、FileMon等就比较喜欢这套。结果是——NO!我更晕了,幸好今天没丢第N+1个钱包,否则就要打开窗户含泪跟大家道别了。

    连驱动都不用,那那那……是高手?唬我吧?我身躯本来就不庞大,给几大箩的问题一压,顿时更渺小了。在WINDOWS NT下,如果不通过驱动而直接在用户模式下建立加密虚拟盘是非常困难的,目前我只知道可以通过代理机制来实现,大家可以参考Galen C. Hunt写的Creating User-Mode Device Drivers with a Proxy,但该机制实现起来相当相当复杂,理智点的人都会优先考虑用DDK搭个驱动。开始的时候,那WNetGetConnectinA函数就在我的脑子里闪了一下,难道眼前这个就是传说中的Hunter?

    迅速打开RegSnap,配合私人磁盘跑了两遍,结果出来了:只在右键菜单扩展里动了点手脚,其它都没变。显然,Hunter还没诞生,如果采用代理机制,在注册表HKEY_LOCAL_MACHINE\SOFTWARE\URCS\ProxyDevices分枝下会有登记。我那本来就不是很好使的脑瓜进一步陷入昏迷状态,赶紧泡杯参茶喝下,总算及时抢救过来。到这时,我已肯定这兄台是蒙着俺的眼睛使巧了!巧巧巧,你巧巧巧巧,我看你往哪巧!再巧也不能不操作文件吧?说时迟那时快,FileMon已蹦出来,并依次记录了私人磁盘的四个动作:建立、打开、关闭、删除。一看FileMon记录,噢噢噢,亲爱的灰姑娘啊,我可揪到你啦!下面不正是吗?

    //在回收站里建立虚拟驱动器源目录
    C:\Recycled\UDrives.{25336920-03F9-11CF-8FD0-00AA00686F13}
    //构造一个虚拟驱动器回收站
    C:\Recycled\UDrives.{25336920-03F9-11CF-8FD0-00AA00686F13}\Recycled\
    //虚拟驱动器加载程序
    C:\WINNT\System32\subst.exe

    注意,subst.exe是Windows下自带的一个命令行小工具,用来将路径与驱动器关联,具体用法为:

    SUBST [drive1: [drive2:]path]
    SUBST drive1: /D

        drive1: 指定要指派路径的虚拟驱动器。
        [drive2:]path 指定物理驱动器和要指派给虚拟驱动器的路径。
        /D 删除被替换的 (虚拟) 驱动器。

    不加任何参数键入 SUBST,可以显示当前虚拟驱动器的清单。

    显然,私人磁盘整个工作机制是通过把回收站下一子目录指派给虚拟驱动器来实现的,由于回收站文件夹在Shell名字空间的特殊性,在里面自建立的文件或目录不会从资源管理器反映出来,从而达到隐藏源文件的目的。但是,在控制台模式下用dir /a就能让所有东西原形毕露。

    好,程序的工作机制已经搞清楚,那回到前面最重要的问题——加密。在私人磁盘里建立的文件是否经过加密并安全存储呢,现在是时候来找答案了。我在新建的虚拟盘根目录下建了个文本文件hello.txt,随便写了些东西,然后一顿关闭,连私人磁盘程序也结了。按程序上说的,现在的虚拟盘数据应该是“自动加密隐藏”了。对,藏是藏起来了,可还是能揪出来。用记事本程序顺利打开“C:\Recycled\UDrives.{25336920-03F9-11CF-8FD0-00AA00686F13}\hello.txt”,上面一个字也没变,害我咖啡喷了一屏幕。唉,失望啊。

    ——总结——

    在Windows下,单单做个虚拟磁盘并不是件困难的事,你甚至不用编写一行代码就可以用subst.exe来完成。但如果要支持加密,而且还是面向透明文件访问的加密,那就没那么简单的,区块解释、文件流加密、冲突控制、缓冲管理、设备管理等等都是非常头疼的问题,去分析一下StrongDisk就知道所有这些事情实现起来是多么的不容易。

    ——后话——

    刚拿到这个软件的时候,我是怀着十二分的敬意来看待的,因为自己也是个做技术的人,知道搞点东西很不容易,但知道来龙去脉后,就有了现在这篇文章。如果那兄台在描述这个软件的时候有一说一,就算没有加密功能,我也会叫好,因为其在巧上面做得的确有水平。诚然,不管是做人还是做事,灵巧是很重要的,但离开诚信和正直就一文不值了。这个私人磁盘软件是共享软件,叫价倒不高,也就¥30,用起来还算可以,就是在加密这个敏感问题上撒了谎,你买不买单就看着办吧。反正我是肯定不会了,至少对当前版本,我还要把钱省下来买瓶清洁剂——清洗我那无辜的笔记本屏幕和键盘。

写在前面:

  《C程序设计》可以说是一本再基础不过的编程书了,但每读一遍的感觉却都是不同的,可以说,每读一遍,都会有很多新的收获。真所谓老书再读,回味无穷啊!此笔记是《C程序设计》谭浩强编著,清华大学出版社出版。除了将书中的重点知识点记下来外,也加入了我对知识点的理解,我想这一点是读书笔记的重要性所在。

第一章 概述 第二章 数据类型、运算符与表达式
第三章 最简单的c程序设计 第四章 逻辑运算和判断选取控制
第五章 循环控制 第六章 数组
第七章 函数 第八章 预编译处理
第九章 指针 第十章 结构体与共用体
第十一章 位运算 第十二章 文件

第一章 概述

1. C语言的特点

①语言简洁、紧凑,使用方便、灵活。共有32个关键字,9种控制语句。
②运算符丰富,公有34种运算符。
③数据结构丰富,数据类型有:整型、实型、字符型、数组、指针、结构体、共用体等。
④具有结构化的控制语句(如if…else、while、do…while、switch、for)
⑤语法限制不太严格,程序设计自由度大。
⑥允许直接访问物理地址,能进行位(bit)操作,可以直接对硬件操作。
⑦生成目标代码质量高,程序执行效率高。
⑧可移植性好。

2. C语言的用途

C虽不擅长科学计算和管理领域,但对操作系统和系统实用程序以及对硬件进行操作方面,C有明显的优势。现在很多大型应用软件也用C编写。

Top of Page

第二章 数据类型、运算符与表达式

1. C的数据类型

C的数据类型包括:整型、字符型、实型或浮点型(单精度和双精度)、枚举类型、数组类型、结构体类型、共用体类型、指针类型和空类型。

2. 常量与变量

常量其值不可改变,符号常量名通常用大写。变量其值可以改变,变量名只能由字母、数字和下划线组成,且第一个字符必须为字母或下划线。否则为不合法的变量名。变量在编译时为其分配相应存储单元。

3. 整型数据

整型常量的表示方法:十进制不用说了,八进制以0开头,如0123,十六进制以0x开头,如0×1e。
整型变量分为:基本型(int)、短整型(short int)、长整型(long int)和无符号型。不同机器上各类数据所占内存字节数不同,一般int型为2个字节,long型为4个字节。

4. 实型数据

实型常量表示形式:十进制形式由数字和小数点组成(必须有小数点),如:0.12、.123、123.、0.0等。指数形式如123e3代表123×10的三次方。
实型变量分为单精度(float)和双精度(double)两类。在一般系统中float型占4字节,7位有效数字,double型占8字节,15~16位有效数字。

5. 字符型数据

字符变量用单引号括起来,如’a',’b'等。还有一些是特殊的字符常量,如’\n’,'\t’等。分别代表换行和横向跳格。
字符变量以char 来定义,一个变量只能存放一个字符常量。
字符串常量是由双引号括起来的字符序列。这里一定要注意’a'和”a”的不同,前者为字符常量,后者为字符串常量,c规定:每个字符串的结尾加一个结束标志’\0′,实际上”a”包含两个字符:’a'和’\0′。

6. 数值型数据间的混合运算

整型、字符型、实型数据间可以混合运算,运算时不同类型数据要转换成同一类型再运算,转换规则:
char,short -> int -> unsigned -> long -> double <- float

7. 运算符和表达式

c运算符包括:
算数运算符( + - * / % )
关系运算符( > < == >= <= != )
逻辑运算符( ! && || )
位运算符( << >> ~ | ^ & )
赋值运算符( = )
条件运算符( ? : )
逗号运算符( , )
指针运算符( * & )
求字节数( sizeof )
强制类型转换(类型)
分量运算符( . -> )
下标运算符( [ ] )
其它运算符( 如函数调用运算符( ) )
自增自减运算符( ++ — )注意:++i和i++的不同之处,++i使用i之前先使i加1,i++使用i之后,使i加1。
逗号表达式的求解过程:先求解表达式1,再求解表达式2,整个表达式的值是表达式2的值。

Top of Page

第三章 最简单的c程序设计

1.c的9种控制语句:

if() ~ else~
for()~
while()~
do~while()
continue
break
switch
goto
return
程序的三种基本结构:顺序结构,选择结构,循环结构

2.数据输出

c语言不提供输入输出语句,输入输出操作是由c的库函数完成。但要包含头文件stdio.h。
putchar( ) 向终端输出一个字符
printf( )的格式字符:
① d格式符 用来输出十进制整数
%d 按整型数据的实际长度输出
%md 使输出长度为m,如果数据长度小于m,则左补空格,如果大于m,则输出实际长度
%ld 输出长整型数据
② o格式符 以八进制形式输出整数
③ x格式符 以十六进制形式输出整数
④ u格式符 用来输出unsigned型数据,以十进制形式输出
⑤ c格式符 用来输出一个字符
⑥ s格式符 输出一个字符串
%s 输出实际长度字符串
%ms 输出的串占m列,如果串长度小于m,左补空格,如果大于m,实际输出
%-ms输出的串占m列,如果串长度小于m,右补空格,
%m.ns 输出占m列,但只取字符串中左端n个字符并靠右对齐
%-m.ns m、n含义同上,靠左对齐,如果n>m,则m自动取n值
⑦ f格式符 以小数形式输出实数
%f 整数部分全部输出,小数部分输出6位
%m.nf 输出数据共占m列,其中有n位小数。如果数值长度小于m,左补空格
%-m.nf 同上,右补空格
⑧ e格式符 以指数形式输出实数
%e 系统指定6位小数,5位指数(e+002 )
⑨ g格式符 输出实数,根据数值大小,自动选f格式或e格式

3.数据输入

getchar( ) 从终端输入一个字符
scanf( 格式控制,地址列表) 标准C scanf中不使用%u,对于unsigned型数据,以%d或%o或%x输入。%后的*,用来跳过它相应的数据。输入数据时不能规定精度如scanf( “%7.2f”, &a );是不合法的。

Top of Page

第四章 逻辑运算和判断选取控制

1. 关系运算符:

c提供6种关系运算符(> < <= >= == != )前四种优先级高于后两种。

2. If语句

C提供了三种形式的if语句
If(表达式) 语句
If(表达式) 语句1 else 语句2
If(表达式1) 语句1
Else if(表达式2) 语句2

else 语句n

3. 条件运算符

(a>b)?a:b 条件为真,表达式取值a,否则取值b

4. Switch语句

Switch(表达式)
{
case 常量表达式1:语句1; break;
case 常量表达式2:语句2; break;
   …
case 常量表达式n:语句n; break;
  default :语句n+1;
}

Top of Page

第五章 循环控制

1. 几种循环语句

goto语句(现已很少使用)
while语句 先判断表达式后执行语句
do-while语句 先执行语句后判断表达式
for语句

2. Break语句和continue语句

Break语句用于跳出循环,continue用于结束本次循环。

Top of Page

第六章 数组

1. 一维数组

c规定只有静态存储(static)和外部存储(extern)数组才能初始化。给数组初始化时可以不指定数组长度。

2. 二维数组

3. 字符数组

部分字符串处理函数
puts(字符数组) 将一个字符串输出到终端。
gets(字符数组) 从终端输入一个字符串到字符数组,并且得到一个函数值,为该字符数组的首地址
strcat(字符数组1,字符数组2) 连接两个字符数组中的字符串,数组1必须足够大。
Strcpy(字符数组1,字符串2)  将字符串2拷贝到字符数组1中。
Strcmp(字符串1,字符串2) 比较字符串,相等返回0,字符串1>字符串2,返回正数,小于返回负数。
Strlen(字符数组) 求字符串长度。
Strlwr( 字符串) 将字符串中的大写字母转换成小写
Strupr( 字符串)  将字符串中的小写字母转换成大写
以上是一些比较常用的字符串处理函数。

Top of Page

第七章 函数

1. 关于形参和实参的说明

① 在函数被调用之前,形参不占内存
② 实参可以是常量、变量或表达式
③ 必须指定形参的类型
④ 实参与形参类型应一致
⑤ 实参对形参的数据传递是”值传递”,即单向传递

2. 函数返回值

如果想让函数返回一个值,在函数中就要用return语句来获得,在定义函数时也要对函数值指定类型,如果不指定,默认返回整型。

3. 函数调用

1)注意在函数调用时实参和形参的个数、类型应一一对应。对实参表求值的顺序是不确定的,有的系统按自左至右,有的系统则按自右至左的顺序。这一点要注意。
2)函数调用的方式:函数语句,函数表达式,函数参数
3)如果主调函数和被调函数在同一文件中,并且主调函数在前,那么一般要在主调函数中对被调函数进行说明。除非:(1)被调函数的返回值类型为整型或字符型(2)被调函数出现在主调函数之前。
4)对函数的说明和定义是不同的,定义是指对函数功能的确立,包括指定函数名,函数值类型,形参及其类型、函数体等。说明则只是对已定义的函数返回值类型进行说明,只包括函数名、函数类型以及一个空的括弧,不包括形参和函数体。
5)c语言允许函数的递归调用(在调用一个函数的过程中又出现直接或间接的调用该函数本身)。

4. 数组作为函数参数

1)数组元素作为函数参数 和一般变量相同
2)数组名作参数应该在主调和被调函数分别定义数组,形参数组的大小可以不定义。注意:数组名作参数,不是单向传递。
3)多维数组作参数,在被调函数中对形参数组定义时可以省略第一维的大小说明,但不能省略第二维或更高维的说明。

5. 局部变量和全局变量

从变量作用域角度分,变量可分为局部变量和全局变量。
1)内部变量(局部变量)
在一个函数内定义,只在函数范围内有效的变量。
   2)外部变量(全局变量)
在函数外定义,可以为本文件其它函数所共用,有效范围从定义变量的位置开始
     到本文件结束。建议尽量少使用全局变量,因为它在程序全部执行过程中都占用
     资源,而且使函数的通用性降低了。如果在定义外部变量之前的函数要想使用该
     外部变量,则应在该函数中用extern作外部变量说明。

6. 动态存储变量与静态存储变量

从变量值存在的时间(生存期)角度来分,可分为静态存储变量和动态存储变量。静态存储指在程序运行期间给变量分配固定的存储空间,动态存储指程序运行期间根据需要动态的给变量分配存储空间。
C语言中,变量的存储方法分为两大类:静态存储类和动态存储类,具体包括:自动的(auto),静态的(static),寄存器的(register),外部的(extern)。
1) 局部变量的存储方式
函数中的局部变量如不作专门说明,都之auto的,即动态存储的,auto可以省略。局部变量也可以定义为static的,这时它在函数内值是不变的。静态局部变量如不赋初值,编译时系统自动赋值为0,动态局部变量如不赋初值,则它的值是个不确定的值。C规定,只有在定义全局变量和局部静态变量时才能对数组赋初值。为提高执行效率,c允许将局部变量值放在寄存器中,这种变量叫register变量,要用register说明。但只有局部动态变量和形式参数可以作为register变量,其它不行。
2) 全局变量的存储方式
全局变量在函数外部定义,编译时分配在静态存储区,可以在程序中各个函数所引用。多个文件的情况如何引用全局变量呢?假如在一个文件定义全局变量,在别的文件引用,就要在此文件中用extern对全局变量说明,但如果全局变量定义时用static的话,此全局变量就只能在本文件中引用了,而不能被其它文件引用。
3) 存储类别小结
从作用域角度分,有局部变量和全局变量
局部变量:自动变量,即动态局部变量(离开函数,值就消失)
     静态局部变量(离开函数,值仍保留)
 寄存器变量(离开函数,值就消失)
 (形参可定义为自动变量和寄存器变量)
全局变量:静态全局变量(只限本文件引用)
 全局变量(允许其它文件引用)
从存在的时间分,有静态存储和动态存储
动态存储:自动变量(本函数内有效)
 寄存器变量(本函数内有效)
 形参
静态存储:静态局部变量(函数内有效)
 静态全局变量(本文件内有效)
 全局变量(其它文件可引用)
从变量值存放的位置分
静态存储区:静态局部变量
  静态全局变量
全局变量
动态存储区:自动变量和形参
寄存器内:寄存器变量

7. 内部函数和外部函数

内部函数:只能被本文件中的其它函数调用,定义时前加static,内部函数又称静态函数。
外部函数:可以被其它文件调用,定义时前加extern,如果省略,则隐含为外部函数,在需要调用此函数的文件中,一般要用extern说明。

Top of Page

第八章 预编译处理

 

c编译系统在对程序进行通常的编译之前,先进行预处理。c提供的预处理功能主要有以下三种:1)宏定义 2)文件包含 3)条件编译

1. 宏定义

不带参数的宏定义
用一个指定的标识符来代表一个字符串,形式:#define 标识符 字符串
几点说明:
1) 宏名一般用大写
2) 宏定义不作语法检查,只有在编译被宏展开后的源程序时才会报错
3) 宏定义不是c语句,不在行末加分号
4) 宏名有效范围为定义到本源文件结束
5) 可以用#undef命令终止宏定义的作用域
6) 在宏定义时,可以引用已定义的宏名

带参数的宏定义
定义形式:#define 宏名(参数表) 字符串
这和函数有些类似,但他们是不同的:
1) 函数调用时,先求实参表达式值,再代入形参,而宏只是简单替换,并不求值
2) 函数调用是在程序运行时分配内存的,而宏展开时并不分配内存,也没有返回值的概念
3) 对函数中的实参和形参都要定义类型,而且要求一致,宏名无类型,其参数也没有类型。
4) 函数只有一个返回值,而宏可以得到几个结果
5) 宏替换不占运行时间,只占编译时间,而函数调用占运行时间

2. 文件包含处理

#include “文件1″ 就是将文件1的全部内容复制插入到#include位置,作为一个源文件进行编译。
在#include命令中,文件名可以用” “也可以用< >,假如现在file1.c中包含file2.h文件,” “表示系统先在file1.c所在目录中找file2.h,如果找不到,再按系统指定的标准方式检索目录,< >表示系统直接按指定的标准方式检索目录。所以用” “保险一点。

3. 条件编译

条件编译指不对整个程序都编译,而是编译满足条件的那部分。条件编译有以下几种形式:
1)#ifdef 标识符
  程序段1
  #else
程序段2
#endif
它的作用:当标识符在前面已经被定义过(一般用#define),则对程序段1编译,否则对程序段2编译。
2)#ifndef 标识符
程序段1
#else
程序段2
#endif
它的作用和#ifdef相反,当标识符没被定义过,对程序段1编译,否则对程序段2编译。
3)#if 表达式
程序段1
  #else
 程序段2
#endif
它的作用:当表达式值为真(非0)时,对程序段1编译,否则对程序段2编译。

Top of Page

第九章 指针

  指针说白了就是地址。指针变量就是用来存放指针(地址)的变量。

1. 变量的指针和指向变量的指针变量

读起来很拗口,说白了就是变量的地址和用来存放变量地址的地址变量。因为一个变量在编译的时候系统要为它分配一个地址,假如再用一个变量来存放这个地址,那么这个变量就叫做指向变量的指针变量,也就是用来存放变量地址的这么一个变量。所谓”指向”就是指存放××的地址,如指向变量的指针变量,”指向”就是指用来存放变量的地址,再如指向数组的指针变量,”指向”就是指存放数组的地址。只要理解了这个,指针也就不难了。另外,还有指向字符串的指针变量,指向函数的指针变量,指向指针的指针变量等。

1) 指针变量的定义
形式:类型标识符 *标识符 如:int *pointer;
要注意两点:*表示pointer是个指针变量,在用这个变量的时候不能写成*pointer, *pointer是pointer指向的变量。一个指针变量只能指向同一个类型的变量。如上面
pointer只能指向int型变量。

2)指针变量的引用
两个有关的运算符:
& 取地址运算符 &a 就代表变量a的地址
* 指针运算符  *a 就代表变量a的值

2. 数组的指针和指向数组的指针变量

数组的指针指数组的起始地址,数组元素的指针指数组元素的地址。
1)指向数组元素的指针变量的定义与赋值
定义和指向变量的指针变量定义相同,c规定数组名代表数组的首地址,即第一个数组元素地址。
2)通过指针引用数组元素
我们通常引用数组元素的形式是a[i],如果用指针可以这样引用,*(a+i),或定义一个指针变量p,将数组a的首地址赋给p,p=a;然后用*(p+i)引用。
注意:指针变量p指向数组a首地址,则p++指向数组a的下一元素地址,即a[1]的地址。
3)数组名作函数参数
形参数组和实参数组之间并不是值传递,而是共用同一段地址,所以在函数调用过程中如果形参的值发生变化,则实参的值也跟着变化。
4)指向多维数组的指针和指针变量
以二维数组为居多。假设定义了一个二维数组a[3][4],那么
a代表整个二维数组的首地址,也代表第0行的首地址,同时也是第0行第0列的元素的首地址。a +0和a[0]代表第0行首地址,a+1和a[1]代表第一行的首地址。
假设a是一个数组的首地址,那么如果a是一维的,a+I代表第I个元素的地址,如果a是二维的,则a+I代表第I行的首地址。
那么第一行第二列的元素地址如何表示呢?a[1]+2或&a[1][2]或*(a+1)+2。
我们只要记住:在二维数组中a代表整个数组的首地址,a[I]代表第I行的首地址,a[I]与*(a+I)等价就行了。只要运用熟练了就没什么复杂的了。
5)指向由m个整数组成的一维数组的指针变量
如:int (*p)[4],p是一个指向包含4个元素的一维数组,如果p先指向a[0],则p+1指向a[1],即p的增值是以一维数组的长度为单位的,这里是4,举个例子:
假设a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23},p先指向a[0]也就是数组a的首地址,那么p+1就是a[1]的首地址即元素9的地址,因为在定义p时int (*p)[4],定义一维数组长度为4,所以p+1就等于加了一个一维数组的长度4。

3. 字符串的指针和指向字符串的指针变量

1)字符串的表示形式
c中字符串有两种表示形式:一种是数组,一种是字符指针
char string[]=”I love c!”;
char *str=”I love c!”;
其实指针形式也是在内存中开辟了一个数组,只不过数组的首地址存放在字符指针变量str中,千万不要认为str是一个字符串变量。
2)字符串指针作函数参数
实际上字符串指针就是数组的首地址。
3)字符指针变量与字符数组的区别
① 字符数组由若干元素组成,每个元素存放一个字符,而字符指针变量只存放字符串的首地址,不是整个字符串
② 对数组初始化要用static,对指针变量不用。
③ 对字符数组赋值,只能对各个元素赋值,不能象下面这样:
char str[14];
str=”I love c!”;
对指针变量可以,
char *str;
str=”I love c!”;
注意:此时赋给str的不是字符,而是字符串首地址。
④ 数组在定义和编译时分配内存单元,而指针变量定义后最好将其初始化,否则指针变量的值会指向一个不确定的内存段,将会破坏程序。如:
char *a;
scanf( “%s”, a );这种方法是很危险的,应该这样:
char *a, str[10];
a = str;
scanf( “%s”, a );这样字符指针就指向了一个确定的内存段。
⑤ 指针变量的值是可以改变的,而字符数组名所代表的字符串首地址却是不能改变的。

4. 函数的指针和指向函数的指针变量

一个函数在编译时被分配一个入口地址,这个入口地址就称为函数的指针。函数名代表函数的入口地址,这一点和数组一样。我们可以用一个指针变量来存放这个入口地址,然后通过该指针变量调用函数。如:假设有一个求两者较大的函数如下:int max( int x, int y );
当我们调用这个函数时可以这样:
int c;
c=max( a, b );这是通常调用方法,其实我们可以定义一个函数指针,通过指针来调用,如:
int (*p)(); //注意指向函数指针变量的定义形式
p=max; //此句就是将函数的入口地址赋给函数指针变量p
c=(*p)( a, b );
有些朋友可能对(*p)()不大理解,其实它的意思就是定义一个指向函数的指针变量p,p不是固定指向哪个函数的,而是专门用来存放函数入口地址的变量。在程序中把哪个函数的入口地址赋给它,它就指向哪个函数。但要注意,p不能象指向变量的指针变量一样进行p++,p-等无意义的操作。
既然p是一个指针变量,那么就可以作为函数的参数进行传递。其实函数的指针变量最常用的用途之一就是作为函数参数传递到其它函数。这也是c语言中应用的比较深入的部分了。

5. 返回指针值的函数

我们知道,一个函数可以带回一个整型值、字符值、实型值等,函数还可以带回一个指针型的数据,即地址。这种函数的定义形式如下:
类型标识符 *函数名(参数表) 如:int *a(x,y)返回一个指向整型的指针
使用这种函数的时候要注意:在调用时要先定义一个适当的指针来接收函数的返回值。这个适当的指针其类型应为函数返回指针所指向的类型。
这样的函数比较难于理解,其实只要把它当做一般的函数来处理就容易了。当我们觉得指针难于理解的时候,就把它暂时当做整型来看,就好理解多了。

6. 指针数组

指针数组无疑就是数组元素为指针,定义形式为: 类型标识 *数组名[数组长度]
如:int *p[4],千万不要写成int (*p)[4],这是指向一维数组的指针变量。指针数组多用于存放若干个字符串的首地址,注意一点,在定义指针数组时初始化,如下:
static char *name[]={“Li jing”,”Wang mi”,”Xu shang”};
不要以为数组中存放的是字符串,它存放的是字符串首地址,这一点一定要注意。

7. 指向指针的指针

说的明白一点,将一个指针再用一个变量来存放,那么这个变量就是指向指针的指针。定义如:char * *p;

8. 指针数组作main()函数的参数

函数形式为
main( int argc, char *argv[] ){}
main函数的参数是从命令行得到的,argc指命令行参数个数,注意命令名也算一个参数,命令行参数都是字符串,他们的首地址构成一个指针数组argv。Main函数的形参用argc和argv只是一个习惯,也可以定义成别的名字。

9. 指针小结

1)有关指针的数据类型

定  义 含   义
Int I; 定义一个整型变量I
Int *p; P为指向整型数据的指针变量
Int a[n]; 定义整型数组a,它有n个元素
Int *p[n]; 定义指针数组p,它有n个指向整型的指针元素
Int (*p)[n]; P为指向含有n个元素的一维数组的指针变量
Int f(); F为返回整型值的函数
Int *p(); P为返回值为指针的函数,该指针指向整型数据
Int (*p)(); P为指向函数的指针,该函数返回一个整型值
Int **p; 定义一个指向指针的指针变量

 

2)ANSI新增了一种void *指针类型,即定义一个指针变量,但不指向任何数据类型,等用到的时候再强制转换类型。如:
char *p1;
void *p2;
p1 = (char *)p2;
也可以将一个函数定义成void *型,如:
void *fun( ch1, ch2 )
表示函数fun返回一个地址,它指向空类型,如果需要用到此地址,也要对其强制转换。如(假设p1为char型):
p1=(char *)fun( c1,c2 );

指针应该说是c语言中比较重要的概念,也是c语言的精华,它有很多优点,但用不好也会带来严重性的错误,这就需要我们多用,多练,慢慢的积累经验。

Top of Page

第十章 结构体与共用体

1. 定义

结构体定义的一般形式:
struct 结构体名{
成员列表
};
定义一个结构体变量可以这样定义:struct 结构体名 结构体变量名;

2. 结构体变量的引用

在引用结构体变量时应注意以下规则:
1)不能将结构体变量作为一个整体输入输出,只能对变量当中的各个成员输入输出。新标准C允许将一个结构体变量直接赋值给另一个具有相同结构的结构体变量。

3. 结构体变量的初始化

如:
struct student
{long int num;
char name[20];
char sex;
char addr[20];
}a={89031,”Li Lin”,’M',”123 Beijing Road” };

4. 结构体数组

struct student stu[4];
定义了一个数组stu,其元素为struct student类型,数组有4个元素。注意数组各元素在内存中是连续存放的。
在定义结构体数组时,数组元素个数可以不指定。编译时,系统会根据给出初值的结构体常量的个数来确定数组元素的个数。

5. 指向结构体变量的指针

因为结构体变量在内存中是连续存放各成员的,因此我们可以将结构体变量在内存中的起始地址存放到一个变量中,那么这个变量就是指向结构体变量的指针。
注意将结构体变量的首地址赋给指针变量的形式:
struct student stu_1;
struct student *p;
p=&stu_1; //要加取地址符 而指向函数和指向字符串的指针不用
在对引用结构体变量中的成员时,有三种方式:
以上面的结构体为例:设p为指向此结构体变量的指针,即p=&a;
1) a.num
2) (*p).num
3) p->num

6. 指向结构体数组的指针

struct student *p;
struct student stu[4];
p=stu;
则p为指向结构体数组的指针变量。这里应注意p++,p指向stu[0],p++则指向stu[1]。P指向的是数组中一个元素的首地址,而不能让p指向元素中的某一成员,如p=&stu[I].name是不对的。

7. 用指向结构体的指针作函数参数

虽然ANSI C允许用整个结构体作为函数参数,但要将全部成员值一个一个传递,开销大。所以用指针作参数,能提高运行效率。
Struct student stu;
用整个结构体作为参数调用形式:
fun( stu );
而且被调函数fun中也要定义成结构体变量,struct student stu;
用指针作参数调用形式:
fun( &stu );
被调函数fun中定义成指针变量,struct student *p;

8. 用指针处理链表

链表是一种重要的数据结构,原因就在于它可以动态的进行存储分配。链表都有一个头指针,用来存放整个链表的首地址。链表的定义形式如下:
struct node{
int num;

struct node *next;
};
next用来存放下一节点的地址。
如何进行动态的开辟和释放存储单元呢?c提供了以下有关函数:
1) malloc(size) 在内存的动态存储区开辟一个长度为size的连续空间。成功返回空间首地址,失败返回0;
2) calloc(n,size) 在内存的动态存储区开辟n个长度为size的连续空间。成功返回空间首地址,失败返回0;
3) free(ptr) 释放由ptr指向的内存区。Ptr是最近调用一次调用malloc和calloc时返回的值。
  上面函数中,n和size为整型,ptr为字符指针。

9. 共用体

定义形式:
union 共用体名
{
成员列表
}变量列表;
共用体和结构体类似,只是有一点不同,结构体中个成员的起始地址不同,结构体变量在内存中的长度为各成员长度之和;而共用体中个成员的起始地址相同,共用体变量所占的内存长度为最长的成员的长度。
共用体类型数据的特点:
1) 同一个内存段可以存放几种不同类型的成员
2) 共用体变量中起作用的成员是最后一次存放的成员
3) 不能对共用体变量名赋值,不能在定义时初始化。
4) 不能把共用体变量作为函数参数
5) 共用体类型可以出现在结构体定义中,反之也可,也可以定义共用体数组。
另外,结构体名可以作为参数,而共用体名不可以。
这两中数据结构在不同场合中各有所用。

10. 枚举类型

定义形式如下:举个例子
enum weekday{sun,mon,tue,wed,thu,fri,sat};
enum weekday workday,week_end; //定义枚举变量
workday和week_end被定义成枚举类型,他们的值只能为sun到sat之一。
也可以直接定义枚举变量,这一点与结构体相同
enum weekday{sun,mon,tue,wed,thu,fri,sat}wordday,week_end;
注意:枚举元素是作为常量存在的,他们是有值的,c在编译时使他们的值按顺序为0,1,2…
如:上面的定义中,sun的值为0,mon的值为1
另外:虽然枚举元素有值,但不能将一个整数直接赋给一个枚举变量。应进行强制类型转换,如:
workday=(enum weekday)2;它相当于把tue赋给了workday。

11. 用typedef定义类型

typedef的作用就是能够让你定义一个自己喜欢的数据类型名来代替已有的数据类型名。如:
typedef int INT;那么我就可以用INT来定义整型变量了。作用和int一样。
Typedef用于结构体定义,如:
Typedef struct{
Int day;
Int month;
Int year;
}DATE;
DATE birthday;
DATE *p;等等
用typedef有利于程序的通用与移植。

Top of Page

第十一章 位运算

1)概述

所谓位运算是指进行二进制位的运算。在系统软件中,常要处理二进制位的问题。
c提供的位运算符有:
& 按位与
| 按位或
^ 按位异或
~ 取反
<< 左移
>> 右移
&对于将一个单元清零、取一个数中的某些指定位以及保留指定位有很大用途。
|常被用来将一个数的某些位置1。
^判断两个位值,不同为1,相同为0。常用来使特定位翻转等。
~常用来配合其它位运算符使用的,常用来设置屏蔽字。
<<将一个数的各二进制位全部左移,高位左移后溢出,舍弃不起作用。左移一位相当于该数乘2,左移n位相当于乘2n。左移比乘法运算要快的多。
>>右移时,要注意符号问题。对无符号数,右移时左边高位移入0,对于有符号数,如果原来符号位为0(正数),则左边移入0;如果符号位为1(负数),则左边移入0还是1要取决于系统。移入0的称为”逻辑右移”,移入1的称为”算数右移”。

2)位段

将一个字节分为几段来存放几个信息。所谓位段是以位为单位定义长度的结构体类型中的成员。如:
struct packed-data{
unsigned a:2;
unsigned b:6;
unsigned c:4;
unsigned d:4;
int I;
}data;
其中a,b,c,d分别占2位,6位,4位,4位。I为整型,占4 个字节。
对于位段成员的引用如下:
data.a = 2;等,但要注意赋值时,不要超出位段定义的范围。如位段成员a定义为2位,最大值为3,即(11)2,所以data.a=5;就会取5的两个低位进行赋值,就得不到想要的值了。
关于位段的定义和引用,有几点重要说明:
①若某一个段要从另一个字开始存放,可以定义:
unsigned a:1;
unsigned b:2;
unsigned :0;
unsigned c:3; (另一单元)
使用长度为0的位段,作用就是使下一个位段从下一个存储单元开始存放。
②一个位段必须存放在用一个存储单元中,不能跨两个单元。
③可以定义无名位段。如:
unsigned a:1;
unsigned :2; (这两位空间不用)
unsigned b:3;
④位段的长度不能大于存储单元的长度,也不能定义位段数组。

Top of Page

第十二章 文件

1) 概述

c语言将文件看成一个字符的序列,分为ASCII文件(文本文件)和二进制文件。即一个c文件就是一个字节流或二进制流。
ASCII文件每一个字节放一个ASCII码,代表一个字符,输出与字符一一对应,便于逐个处理字符,但占用空间较多。二进制文件按内存中的存储形式原样输出到磁盘上,节省空间,由于输出与字符不对应,不能直接输出字符形式,一般用于保存中间结果。目前c对文件的处理只有缓冲文件系统一种方法,即无论是从程序到磁盘文件还是从磁盘文件到程序,数据都要先经过缓冲区,待缓冲区充满后,才集中发送。

2) 文件夹类型指针

在缓冲文件系统中,关键的概念是文件指针。因为每个被使用的文件都在内存中开辟一个缓冲区,来存放文件有关信息。这些信息保存在一个结构体变量中,该结构体类型是由系统定义的,取名为FILE,在stdio.h中定义。
FILE *fp;
定义了一个文件指针变量fp,以后对文件的操作都是通过fp进行的。

3) 文件的打开与关闭

在对文件读写之前,要先打开文件。
打开文件的函数为:fopen(),调用方式为:
FILE *fp;
fp=fopen( filename,使用文件方式 );
fopen()失败返回一个空指针NULL,成功则返回一个指向”filename”的文件指针,赋给fp,这样fp就和打开的文件联系在一起了。或者说,fp指向了”filename”。
文件使用方式:r,w,a,rb,wb,ab,r+,w+,a+,rb+,wb+,ab+,具体含义要记住。

4)文件的关闭

为了防止数据丢失,程序结束前,务必将打开的文件关闭,即将文件指针与文件脱钩。用fclose(文件指针)函数关闭文件,执行函数后,先将缓冲区中的数据送到磁盘文件,然后释放文件指针。成功返回0,失败返回非0。

5)文件的读写

文件打开后,就可以对其读写了,常用的文件读写函数有:
①fputc和fgetc
fputc将一个字符写到文件,形式为fputc( ch, fp );将字符ch写入fp所指向的文件。成功返回该字符,失败返回EOF,EOF在stdio.h中定义为符号常量-1。
fgetc从指定文件读入一个字符,该文件必须是以读或读写方式打开的。调用形式为ch=fgetc(fp);从fp指向的文件读入一个字符赋给ch,当文件结束时,fgetc返回一个EOF,我们可以用函数feof(fp)来判断是否已到文件尾,返回1表示已到文件尾,否则返回0。这个函数适用于文本文件和二进制文件。
②fread和fwrite函数
可以读写一组数据。调用形式如下:
fread( buffer, size, count, fp );
fwrite( buffer, size, count, fp );
buffer为一个指针,对fread来讲,是指从文件读出数据的存放地址,对fwrite来讲,是要写入文件的数据的地址。
size 要读写的字节数
count 要进行读写多少个size字节的数据项(书上这么说)其实就是读写的次数
fp 文件指针
这两个函数返回值成功为1,失败为非1,一般用于二进制文件的读写。
注意:有些c编译系统不具备这两个函数。
③fprintf()和fscanf()函数
格式化输出和输入函数,与printf()和scanf()作用相似,只有一点不同,fprintf()和fscanf()的读写对象不是终端而是磁盘文件。调用方式:
fprintf(文件指针,格式字符串,输出列表);
fscanf(文件指针,格式字符串,输出列表);
④fgets()和fputs()函数
作用是读写一个字符串,如:
fgets(str,n,fp);
意为从fp指向的文件读出n-1个字符,存放到str中,成功返回str的首地址。
fputs( “China”, fp );
把字符串China写入fp指向的文件。成功返回0,失败为非0。

6)文件的定位

文件中有一个位置指针,指向当前读写的位置,如果要强制改变位置指针的位置,可以用有关函数:
①rewind 使位置指针重新返回文件的开头
②fseek()
fseek()函数可以任意改变位置指针的位置,以实现随机读写文件。调用形式:
fseek( 文件指针类型,位移量,起始点 );
起始点有以下三个值:
SEEK_SET或0 文件开始
SEEK_CUR或1 文件当前位置
SEEK_END或2 文件末尾
位移量指以起始点为基点,移动的字节数(正数向文件尾移动,负数向文件头移动),一般位移量用long型数据,以避免大于64K的文件出错。Fseek()函数一般用于二进制文件,因为文本文件要进行字符转换,计算时会发生混乱。
Fseek( fp, 100L, 0 ); 将位置指针从文件头向文件尾移动100个字节处。
Fseek( fp, 50L, 1 ); 将指针从当前位置向文件尾移动50个字节处。
Fseek( fp, -10L, 2 ); 将指针从文件尾向文件头移动10个字节处。
③ftell()
得到流式文件位置指针的当前位置,成功返回相对于文件头的位移量,失败返回-1L。

Top of Page

  另外,由于ANSI C不使用非缓冲文件系统,而其它C系统还用到非缓冲文件系统,所以对于这一章节只是略微的看了一下,不至于以后见到这样的程序不认识,呵呵。这一节主要讲了几个文件读写的有关函数,看了也没做笔记。如果关心的话,自己看一下吧。
  至此,这本语言类的基础书又温习了一遍,由于工作太忙,花了我半个月的时间。不过总的来说,收获还是很大的,有很多以前没有发现的新东西,也有很多以前理解较浅显的东西,这次加深了理解。其实,看完了一章后,最好将书后的习题一一解答,因为这是对这章知识点的考查。同时,动手编一下小程序,也能提高自己的编程能力。

虚拟私用网络
V P N(虚拟私用网络)是远距离传输的网络,它从逻辑上定义了利用公用传输系统的一个组织的所有用户,使得该组织的传输业务与利用同一公用传输系统的其他用户隔离开来。它提供了利用现有的公用传输系统构建广域网的一种途径。例如,一个组织可以在因特网上构建一个私用的广域网来只为其自己的业务服务,同时保持数据的安全并与其他(公用)业务隔离。由于V P N并不需要租用全部的T 1线路或帧中继系统,它们提供了创建远距离广域网的一种并不是十分昂贵的解决方案。V P N采用特殊的协议和安全技术保证了数据只能在
广域网的节点处得到解析。所用的安全技术可能是纯软件的,或者包括了如防火墙这样的硬件。用来建立V P N的软件通常都不贵。有些情况下,这些软件还捆绑有其他用途广泛的软件,例如: Windows NT Server都带有一个叫作R A S的远程接入工具,它允许你创建一个简单的V P N。对于基于N o v e l l的网络,你可以利用B e r d e r M a n a g e r这种N e t Wa r e的附加产品来构建V P N。V P N的优点就在于它能根据用户对距离和带宽的需要量体裁衣,因此对每个用户都会有所不同。
注意:不要把V P N(虚拟私用网络)和V L A N(虚拟局域网)搞混淆了。V L A N是从逻辑上定义的局域网,它是在一个组织已存在的局域网或广域网基础上创建的,通常都服务于一组特定的用户。

建立远程连接
在一个拥有企业级网络的机构,使用广域网与使用局域网没有什么区别。你可能会登录到公司位于北京的网络,打开Windows Explorer,然后选择一台位上海的服务器并打开它上面的一个P o w e r P o i n t演示文稿来浏览。由于广域网连接使你的计算机看起来就像是一个大网络的一部分,所以你的计算机并不关心这个演示文稿的具体位置。然而,如果你在家里或者在旅途中,连接广域网或局域网就有些不同了。下面两节将讲述远程接入网络所采用的三种方法。
远程访问方法
作为一个远程用户,你可以有三种方法连接到局域网:直接拨号到局域网,直接拨号到一台工作站,或者使用一个带有We b接口的因特网连接。上述三种方法都各有如下所述的优缺点。要记住一点:真正限制远程连接的因素通常都是调制解调器、P S T N或I S D N线路的传输速率。
直接拨号到局域网:客户直接拨号到局域网的远程访问服务器上。远程访问服务器(也叫作拨号服务器)是一种软硬件结合的产品。它为拨号访问局域网或广域网的用户提供了一个中心接入点。局域网对待直接拨号访问的远程用户像局域网中的任何其他用户一样;也就是说,远程用户可以执行他或她在办公室里的同样功能。拨号访问局域网的计算机在接入后就成为网络的一个远程点。尽管这种远程接入方法配置起来是最复杂的,特别是在服务器端,但它能够提供最高的安全性。同时,当因特网发生拥塞时,直接拨号连接的传输速率会让人无法忍受。如果服务器硬件和软件使用适当,这种连接就可以支持多用户同时访问局域网。
直接拨号到工作站:远程用户拨号进入直接与局域网连接的工作站。软件(如S y m a n t e c公司的p c A N Y W H E R E)一旦运行在远程用户的计算机和局域网的计算机上时,就允许远程用户“接管”局域网的工作站,这就是所谓的远程控制。远程控制配置起来并不不困难,并且享受与直接拨号连接到远程访问服务器这种方法同样的安全性和吞吐量。另外,这种方法为像数据库这样的处理密集型应用提供了最佳的性能。这是因为这种数据处理在连接到局域网内的工作站上进行,而不用经过较慢的调制解调器连接到远程工作站上去处理。这种方案的缺点是在任何时候它都只能允许一个到局域网的连接。
I n t e r n e t / We b接口:通过浏览器,如Internet Explorer,用户在家里或在旅途中连接到一个局域网时,通过We b服务器软件,该局域网的文件对We b就可见了。这种方法需要在客户端和服务器端进行一些配置。但它通常都不像直接拨号那样难以配置。但是,由于远程用户采用该方法建立的连接不是专用的,就像直接拨号那样不能对它的安全性和吞吐量进行全面控制。然而, We b接口使用起来很简单并且用途广泛。另外,采用这种方法几乎可以同时访问局域网的资源。只要软硬件配置得当,远程连接几乎可以在任何运行了操作系统的工作站间建立起来。也许最简单的拨号访问服务器还是R A S(远程访问服务,),它是随Wi n d o w sN T一起发布的。由于R A S是一个很好的远程访问服务器的例子,你应该在运行Windows NT时研究一下R A S。搞清楚R A S将会对于你理解更复杂的远程访问技术有所帮助。除了M i c r o s o f t的远程访问方法之外,网络硬件制造商,如Bay Network、C i s c o和3 C o m都用它们自己的远程访问技术去占领市场。另外,还有大量的专门的软件公司都提供运行于Windows NT、I n t r a N e t w a r e或者U N I X服务器上的这类程序。选择哪种方法将取决于你对诸如安全性、吞吐量、连接数量、成本以及你的用户和
技术支持人员中的专家数量这些因素。为了使你的网络允许远程访问,你需要熟悉配置连接的用户的过程,并能够为用户提供支持。