黑客技术(一)

<script language="javascript" type="text/javascript"> </script>

黑客技术(一)

转自INTERNET

前言

  1999年7月6日《参考消息》:

  【英国〈星期日电讯报〉7月4日文章】题:克林顿下令通过“电脑破坏行动”来推翻塞尔维亚领导人(记者 菲力普·舍卫尔 萨沙·尼科利茨 朱利叶斯·斯特劳斯)

  克林顿总统已经命令美国政府的电脑黑客冲破障碍,查到米洛舍维奇在外国银行里的存款,并抽走他隐藏的财富,这是美国中央情报局旨在推翻南斯拉夫总统秘密计划的一部分。

  这一有争议的行动计划是克林顿上周批准的共有6点内容的一揽子秘密计划的部分内容。华盛顿政界和情报界一些高级人士反对这一行动计划。   虽说2日晚上有5000多人在诺维萨德举行反米洛舍维奇的最新一次集会,但是,贝尔格莱德的反对派内部四分五裂促使华盛顿亲自出马,发起旨在推翻米洛舍维奇的“电脑破坏”

行动。去年,五角大楼为海、陆、空增添了电脑空间,作为第四战区,并建立了一个主管情报战事务的机构。

  美国中央情报局认为,米洛舍维奇在其执政的10年期间向希腊、塞普路斯和俄罗斯银行转移了数以百万计英镑的钱财,因此得在这些银行进行调查。但是,一些情报官员担心,对塞尔维亚领导人米洛舍维奇数以百万计钱财采取这样的行动会对美国产生不利的后果,一些独立的黑客会向华盛顿的敌人出售他们的技术,从而使华盛顿的计算机系统成为他们进行破坏和非法抽取钱款的目标。

  另外,此举在政治上也引起人们的关切和担心,这会影响到希腊和俄罗斯的主权,美国的外交官们在美俄两国就科索沃问题发生争执后刚刚在两国之间重新架起桥梁。

  单是这条新闻,或许并没有给你太多的感想。但如果同时,把中国驻南斯拉夫大使馆被轰炸的事件联系在一起,或许会促使你更深层次地来分析这条新闻。

  南斯拉夫无奈地从科索沃撤军,是因为军事力量,特别是当代高科技军事力量的薄弱,所以受到了欺负。同样,由于信息技术的落后,南斯拉夫又受到了欺负。随着信息时代的到来,信息技术越来越显得重要,信息技术也能影响一个国家的安全。入侵总统银行帐号只是信息战争的小手段,如果利用各种技术,破坏一个国家的各种计算机网络,绝对会使这个国家的商业经济,国家安全受到影响。信息技术为军事战争开辟了另外一个战场。如果一个国家的信息技术落后,同样会受到侵略。

  我国驻南斯拉夫大使馆被轰炸,里面或多或少有点我国的军事力量不是很强大的因素在里面。那么如果,信息技术也大大落后与别国,是不是也存在国家安全问题。

  回想一下5-60年代,我国自行研制两弹一星时的情形。这些巨大成就为当时我国立足于世界提供了什么样的支持。从那时侯起,没有哪一个国家敢小看我们中国的力量。

  民族信息产业应该放到一个很重要的战略地位。过去是原子弹,氢弹,现在应该是高速计算能力,应该是现代化信息生产能力。

  如果我们的各种计算机系统用的是进口的CPU,进口的操作系统,进口的办公软件,是不是到时候会被别人牵着鼻子走呢。

  我们要象那些为两弹一星做出贡献的老科学家们一样,努力创造,发展民族信息产业。也希望民族信息产业能实现现代化,面向世界,大踏步走出去,屹立于世界。   (作者以前工作在一个高科技军事单位,为有幸能和这些为祖国争光的老前辈们同处一室而感到高兴。这些前辈的默默无闻的奉献精神,关心爱护年轻人的成长的胸怀,一直深深地印在我的脑海里。)

  另外一个结论就是,我们应该正视黑客技术。

  由于媒体的炒作,有关黑客的新闻给大众造成了一种对黑客技术不屑一顾,认为黑客是一类卑鄙下流的人的情况。有的商业公司甚至抓住大多数人不懂计算机技术这一特点,歪曲这些技术,误导大众,从而大赚其钱。

  其实,黑客技术并不下流,也并不深奥。相反,黑客技术是网络安全技术的一部分。或者就可以认为就是网络安全技术。从反面说就是黑客技术,从正面说就是网络安全技术。   这种技术被不法之人用了,当然要遭到谴责。但如果因为这种技术会引来犯罪而不准研究和介绍,也是不正确的。我们应该推广介绍这些技术,这样才能使我们对网络安全有更深的理解,从更深层次地提高网络安全。

  我们要善用这种技术,加强这些技术的研究。

  本书试图对各种网络安全技术(黑客技术)进行介绍,分析原理和提供基本实现方法。

  要想对网络安全技术有一个很深的研究,必须具备一些必要的知识。本书的前三章提供了一些操作系统,编程方法,网络协议和网络编程等基本概念。为以后各章打下良好的基础。在操作系统一章中介绍了Linux上的编程。因为Linux操作系统对网络通信做了很好的支持,而且带了gcc编译器和gdb调试器,是最佳选择。在Linux上编写的C程序可以很短小,代码执行效率也很高。第二章介绍了TCP/IP协议。对IP和TCP的数据包格式作了简单的介绍。另外将了TCP连接的三次握手。许多威胁网络安全的技术都是对协议的弱点的攻击。第三章介绍了网络编程。因为,在测试一个网络是否安全的时候,通常需要编个程序来完成一个特殊的测试工作。

  接下来是介绍根据TCP/IP协议进行的攻击。IP地址欺骗和TCP/IP协议攻击都是根据协议的弱点进行。

  接下来的几章介绍了进行攻击的方法。Sniffer一章介绍了Sniffer的工作原理,通过利用sniffer,能收集到许多有用的信息。端口扫描一章除了介绍一些常用的网络命令外,还介绍了端口扫描的几种技术。通过端口扫描也能收集到相当丰富和有用的信息。口令破解一章讲解了口令破解器的工作机理。口令破解是侵入一个系统的比较常用的方法。特洛伊木马是侵入系统后留下的后门,为以后能再进入目标系统做准备。随后,介绍了缓冲区溢出攻击方法。这通常也很常用,很重要的攻击方法。书中对它的原理作了较为详细地介绍。

  再下来对攻击步骤作了一个总结,并介绍了怎样入侵Windows NT。对前面介绍的方法的综合利用做了介绍。

  最后,介绍了计算机病毒的原理和防范以及Perl语言。

  在每章,为了对某个原理进行介绍,在原理介绍后,基本上还提供一些简单的源代码程序。这里的程序大多数是由C写的Linux程序。

  由于作者不是专业人士,水平有限,同时成书仓促,书中错误相当多。希望能得到批评指正,以便将来整理得更好。书中所有内容都来自Internet,只是略微加工整理。本书的最终目的是想让大家正确看待黑客技术,说明黑客技术并不是象许多媒体描述的那样高深莫测。

  此书仅作抛砖引玉之用。

作者电子邮件地址:jxcai@iname.com ICQ#:27771117

第一章

操作系统简介

本章主要介绍几个目前常见的操作系统。首先介绍Linux系统,一个自由软件。Linux对网络通信有很好的支持,在介绍网络安全技术时,对网络技术进行实例时,没有Linux是不可能。

随后对Windows 9x的Msdos.sys的设置以及Windows NT中的注册表作了介绍。在理解安全技术时,这些也是最基本的。

第一节  Linux

一  Linux下的C++编程

ELF和a.out

  在Linux下,有两种可执行文件:ELF和a.out。有可能你的Linux只支持一种,有可能两种都支持。运行一下命令file,如果命令输出包含ELF,则支持ELF,如果包含Linux/i386,则支持a.out。

GCC版本

  使用下面命令,可以知道它的版本:

gcc -v

GCC安装后目录结构

  /usr/lib/gcc-lib/target/version/ (及子目录) 编译器就在这个目录下。

  /usr/bin/gcc可以从命令行执行的二进制程序在这个目录下。

  /usr/target/(bin|lib|include)/ 库和头文件在这个目录下。

  /lib/,/usr/lib和其他目录,系统的库在这些目录下。

符号定义

  使用-V开关,就能看到GCC定义的符号。参见下列实例:

  $ echo 'main(){printf("hello world");}' | gcc -E -v -

   Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs

   gcc version 2.7.2

  /usr/lib/gcc-lib/i486-box-linux/2.7.2/cpp -lang-c -v -undef

   -D__GNUC__=2 -D__GNUC_MINOR__=7 -D__ELF__ -Dunix -Di386 -Dlinux

  -D__ELF__ -D__unix__ -D__i386__ -D__linux__ -D__unix -D__i386

  -D__linux -Asystem(unix) -Asystem(posix) -Acpu(i386)

  -Amachine(i386) -D__i486__ -

GCC编译器使用简介

  通常后跟一些选项和文件名来使用 GCC 编译器。gcc 命令的基本用法如下:

  gcc [options] [filenames]

  选项指定编译器怎样进行编译。

GCC选项

  GCC 有100个编译选项。这些选项中的许多可能永远都不会用到,但一些主要的选项会经常遇到。很多的 GCC

选项包括一个以上的字符,因此必须为每个选项指定各自的连字符。例如, 下面的两个命令是不同的:

  gcc -p -g test.c

  gcc -pg test.c

  第一条命令告诉 GCC 编译 test.c 时为 prof 命令建立剖析(profile)信息并且把调试信息加入到可执行的文件里。 第二条命令只告诉 GCC

为 gprof 命令建立剖析信息。

  没有选项时,GCC 会生成一个名为 a.out 的可执行文件。

  用 -o 编译选项来为将产生的可执行文件用指定的文件名来命名。例如, 将一个叫 count.c 的 C 程序编译为名叫 count 的可执行文件,

要这样输入命令:

  gcc -o count count.c

  -c 选项告诉 GCC 仅把源代码编译为目标代码。缺省时 GCC 建立的目标代码文件有一个 .o 的扩展名。

  -S 编译选项告诉 GCC 在为 C 代码产生了汇编语言文件后停止编译。 GCC 产生的汇编语言文件的缺省扩展名是 .s 。

  -E 选项指示编译器仅对输入文件进行预处理。当这个选项被使用时, 预处理器的输出被送到标准输出而不是储存在文件里.

  用 GCC 编译 C 代码时, 它会试着用最少的时间完成编译并且使编译后的代码易于调试。

易于调试意味着编译后的代码没有经过优化。必要时,需要让编译器对代码进行优化。

  -O 选项告诉 GCC 对源代码进行基本优化。这些优化在大多数情况下都会使程序执行的更快。 -O2 选项告诉 GCC 产生尽可能小和尽可能快的代码。 -O2

选项将使编译的速度比使用 -O 时慢, 但通常产生的代码执行速度会更快。

  GCC 支持数种调试和剖析选项,常用到的是 -g 和 -pg 。

  -g 选项告诉 GCC 产生能被 GNU 调试器使用的调试信息以便调试你的程序。GCC 提供了一个很多其他 C 编译器里没有的特性, 在 GCC 里你能使

-g 和 -O (产生优化代码)联用。

  -pg 选项告诉 GCC 在编译好的程序里加入额外的代码。运行程序时, 产生 gprof 用的剖析信息以显示你的程序的耗时情况。

用 gdb 调试 GCC 程序

  Linux 包含了一个叫 gdb 的 GNU 调试程序。在程序运行时能观察程序的内部结构和内存的使用情况。 以下是 gdb 所提供的一些功能:

  监视程序中变量的值

  设置断点,使程序在指定的代码行上停止执行。

  一行行的执行代码

  为了用GDB调试程序,在编译是必须指定调试选项。在命令行上键入 gdb 并按回车键就可以运行 gdb 了。如果一切正常的话, gdb

将被启动并在屏幕上显示:

  GDB is free software and you are welcome to distribute copies of it under

certain conditions; type "show copying" to see the conditions.

  There is absolutely no warranty for GDB; type "show warranty" for details.

  GDB 4.14 (i486-slakware-linux), Copyright 1995 Free Software Foundation, Inc.

  (gdb)

  可以在启动GDB时,加入许多选项。也可以在这个命令后面直接指定要调试的程序。

gdb < fname>

gdb 基本命令

gdb 支持很多的命令,这些命令从简单的文件装入到允许检查所调用的堆栈内容的复杂命令。下表列出了你在用 gdb 调试时会用到的一些命令。

命令 描 述

file 装入想要调试的可执行文件

kill 终止正在调试的程序

list 列出产生执行文件的源代码的一部分

next 执行一行源代码但不进入函数内部

step 执行一行源代码而且进入函数内部

run 执行当前被调试的程序

quit 终止 gdb

watch 使你能监视一个变量的值而不管它何时被改变

break 在代码里设置断点, 这将使程序执行到这里时被挂起

make 使你能不退出 gdb 就可以重新产生可执行文件

shell 使你能不离开 gdb 就执行 UNIX shell 命令

gdb 应用举例

  下面列出了将被调试的程序,这个程序被称为 greeting ,显示一个简单的问候, 再用反序将它列出。

#include < stdio.h>

main ()

{

char my_string[] = "hello there";

my_print (my_string);

my_print2 (my_string);

}

void my_print (char *string)

{

printf ("The string is %s", string);

}

void my_print2 (char *string)

{

char *string2;

int size, i;

size = strlen (string);

string2 = (char *) malloc (size + 1);

for (i = 0; i < size; i++)

string2[size - i] = string[i];

string2[size+1] = `0';

printf ("The string printed backward is %s", string2);

}

  用下面的命令编译这个程序:

    gcc -o -g test test.c

  运行编译好的程序,显示如下:

    The string is hello there

    The string printed backward is

  输出的第一行是正确的, 但第二行打印出的东西并不是我们所期望的。我们所设想的输出应该是:

    The string printed backward is ereht olleh

  由于某些原因, my_print2 函数没有正常工作。用 gdb 看看问题究竟出在哪儿, 先键入如下命令:

    gdb greeting

  如果在输入命令时忘了把要调试的程序作为参数传给 gdb ,可以在 gdb 提示符下用 file 命令来载入它:

    (gdb) file greeting

  这个命令载入 greeting 可执行文件,就象在 gdb 命令行里指定启动gdb装入它一样。

  这时就能用 gdb 的 run 命令来运行 greeting 了。 当它在 gdb 里被运行后结果大约会象这样:

  (gdb) run

  Starting program: /root/greeting

  The string is hello there

  The string printed backward is

  Program exited with code 041

  

  这个输出和在 gdb 外面运行的结果一样。问题是, 为什么反序打印没有工作? 为了找出症结所在, 我们可以在 my_print2 函数的 for

语句后设一个断点, 具体的做法是在 gdb 提示符下键入 list 命令三次, 列出源代码:

  (gdb) list

  (gdb) list

  (gdb) list

  第一次键入 list 命令的输出如下:   1 #include < stdio.h>

  2

  3 main ()

  4 {

  5 char my_string[] = "hello there";

  6

  7 my_print (my_string);

  8 my_print2 (my_string);

  9 }

  10

  如果按下回车, gdb 将再执行一次 list 命令, 给出下列输出:

  11 my_print (char *string)

  12 {

  13 printf ("The string is %s", string);

  14 }

  15

  16 my_print2 (char *string)

  17 {

  18 char *string2;

  19 int size, i;

  20

  再按一次回车将列出 greeting 程序的剩余部分:

  21 size = strlen (string);

  22 string2 = (char *) malloc (size + 1);

  23 for (i = 0; i < size; i++)

  24 string2[size - i] = string[i];

  25 string2[size+1] = `0';

  26 printf ("The string printed backward is %s", string2);

  27 }

  根据列出的源程序, 你能看到要设断点的地方在第24行, 在 gdb 命令行提示符下键入如下命令设置断点:

    (gdb) break 24

  该命令的执行结果如下:

    Breakpoint 1 at 0x139: file greeting.c, line 24

    (gdb)

  现在再键入 run 命令, 将产生如下的输出:

    Starting program: /root/greeting

    The string is hello there

    Breakpoint 1, my_print2 (string = 0xbfffdc4 "hello there") at greeting.c :24

24 string2[size-i]=string[i]

  你能通过设置一个观察 string2[size - i] 变量的值的观察点来看出错误是怎样产生的, 做法是键入:

    (gdb) watch string2[size - i]

  执行结果如下:

    Watchpoint 2: string2[size - i]

  现在可以用 next 命令来一步步的执行 for 循环了:

    (gdb) next

  经过第一次循环后, gdb 告诉我们 string2[size - i] 的值是 `h`。这是执行next命令后的结果:

  Watchpoint 2, string2[size - i]

  Old value = 0 `000'

  New value = 104 `h'

  my_print2(string = 0xbfffdc4 "hello there") at greeting.c:23

  23 for (i=0; i< size; i++)

  这个值正是期望的。后来的数次循环的结果都是正确的。当 i=10 时, 表达式 string2[size - i] 的值等于 `e`, size - i

的值等于 1, 最后一个字符已经拷到新串里了。

  如果再把循环执行下去,会看到已经没有值分配给 string2[0] 了, 而它是新串的第一个字符, 因为 malloc

函数在分配内存时把它们初始化为空(null)字符。所以 string2 的第一个字符是空字符。于是就发现了为什么在打印 string2 时没有任何输出了.

  找出了问题出在哪里后, 修正这个错误是很容易的。把代码里写入 string2 的第一个字符的的偏移量改为 size - 1 而不是 size。这是因为

string2 的大小为 12, 但起始偏移量是 0, 串内的字符从偏移量 0 到 偏移量 10, 偏移量 11 为空字符保留。

  为了使代码正常工作有很多种修改办法. 。一种是另设一个比串的实际大小小 1 的变量,下面是这种办法的程序。

#include < stdio.h>

main ()

{

char my_string[] = "hello there";

my_print (my_string);

my_print2 (my_string);

}

my_print (char *string)

{

printf ("The string is %s", string);

}

my_print2 (char *string)

{

char *string2;

int size, size2, i;

size = strlen (string);

size2 = size -1;

string2 = (char *) malloc (size + 1);

for (i = 0; i < size; i++)

string2[size2 - i] = string[i];

string2[size] = `0';

printf ("The string printed backward is %s", string2);

}

二 Linux SHELL编程

  SHELL编程是指写一个包含一系列UNIX命令的程序,这个程序可以在命令行运行。用下面的命令何以执行一个SHELL程序:

方式一

$ sh cmd.file

方式二

$ . cmd.file;

方式三

$ chmod u+x cmd.file

$ cmd.file

怎样创建和运行一个SHELL脚本

  在一个编辑器里,写入一系列UNIX命令,举个例子:

    echo This is a shell program

    echo Today I am going to

    echo $1 $2 $3 $4 $5 $6 $7 $8 $9

  保存这个文件,命名为ex1。然后用下列命令“chmod 700

ex1”,将该文件变为可执行文件。做完上述个步骤之后,就好了。如果要看运行这个文件会出现什么结果,可以在命令行状态下键入:ex1 coffee bar in

hangzhou。

  上述程序中最后一行就是将ex1命令中的单词读入内存,同样将第二个等等。$1代表第一个单词,$2代表第二个。

  可见,SHELL程序的目的是能批量处理命令,从而完成一些比较复杂的工作。

  不同的SHELL有不同的启动文件,比如:

  bash: .profile

  sh: .profile

  csh: .cshrc

  tcsh: .cshrc

   zsh: $ZDOTDIR/.zprofile and/or $ZDOTDIR/.zshrc

  所有的这些启动文件都要读入.login和.logout文件。

SHELL程序设计

注释

操作符“#"引入注释。

if 操作符

语法

if [ 条件表达式 ]

then

命令序列

fi

if [ 条件表达式 ]

then

命令序列

else

命令序列

fi

数值操作符

= 等于

-n 不等于

-gt 大于

-lt 小于

-le 小于等于

exit 命令

用于结束SHELL脚本。可以带一个返回值。

expr 命令

以数值和算术运算符作为参数,计算结果,将其返回标准输出。

$ expr 4 + 5

9

$

合法算术运算符有+、-、*、/和%。在*和/之前必须冠以反斜线,已防被SHELL先行解释。

for 操作符

循环语句。

语法:

for $环境变量 in 字符串表

do

语句序列

done

while 操作符

循环语句。

语法:

while [ 条件表达式 ]

do

语句序列

done

case 操作符

条件控制语句。

语法:

case $环境变量 in

常量1)

语句序列1

;;

常量2)

语句序列2

;;

... ...

常量n)

语句系列n

;;

esac

命令行变元

$# 传入脚本的命令行变元数;

$* 所有命令行变元值;

位置变元

$0 命令本身

$1 第一个命令行变元;

$2 第二个命令行变元

SHELL函数

shell函数由以下形式定义

funcname () {

命令序列

}

调用时

funcname arg1 arg2

第二节 Windows 98

Windows 98 MSDOS.SYS的设置和编辑

  Windows 98 的安装程序会在根目录中建立一个叫MSDOS.SYS

的文件,并且设定其属性为只读,系统和隐藏。这个文件不像MS-DOS的开机文件MSDOS.SYS。这个文件只是一个普通文本文件。包含了两个段落——[Paths]和[Options]。可以修改这个文件,来改变系统的一些属性。    [Paths]段列出了Windows 95其它文件的位置(如注册文件等)。[Options]段则使你可以用来设定自己的喜欢的开机模式。

  简介如下:

1. [Paths] 段的设置

----------------------------------------------------------------------

HostWinBootDrv=< 开机驱动器>

预设值: C

目的:  指定所要开机的驱动器 

----------------------------------------------------------------------

WinBootDir=< Windows所在目录>

预设值: 安装时所指定的目录(例如C:)

目的:  列出开机时所需要的文件位置 

----------------------------------------------------------------------

WinDir=< Windows 所在目录>

预设值: 安装时所指定的目录(例如  C:)

目的:  列出安装时所指定的Windows 95目录位置 

2. [Options]段包含下列设置,必须手动加入 

----------------------------------------------------------------------

BootDelay=< 秒数>

预设值: 2

目的:  设定Windows 95开机前显示“Starting Windows 95"这个信息的时间长度 

----------------------------------------------------------------------

BootFailSafe=< Boolean>

预设值: 0

目的:  设为1时,使电脑开机时进入安全模式 

----------------------------------------------------------------------

BootGUI=< Boolean>

预设值: 1

目的:  设为1时,系统自动进入GUI界面(就是进Windows 95)

     设为0时,系统自动进入DOS界面(也就是进到C:>) 

----------------------------------------------------------------------

BootKeys=< Boolean>

预设值: 1

目的:  设为1时,开机时可使用功能键(如F4、F5、F6和F8) 

     设为0时,则禁止使用  注意:  当设BootKeys=0时,BootDelay=n无效 

----------------------------------------------------------------------

BootMenu=< Boolean>

预设值: 0

目的:  设为1时,开机时自动进入startup menu 

     设为0时,必须在开机显示“Starting Windows 95"时按F8,才能进入startup menu 

----------------------------------------------------------------------

BootMenuDefault=< Number>

预设值: 1  如果系统正常运作的话 

     4  如果系统在前一次运行时发生硬关机的话 

目的:  预设系统启动时进入startup menu时,默认要继续的那一项 

----------------------------------------------------------------------

BootMenuDelay=< Number>

预设值: 30

目的:  设定系统启动时,startup menu的等待秒数,如果这个值减到0,你没有

选择菜单时,系统按照 BootMenuDefault的值启动系统

----------------------------------------------------------------------

BootMulti=< Boolean>

预设值: 0

目的:  设为0时,关掉多重开机的功能(例如:设定为0时不能用前一个操作系统开机)

     设为1时,允许使用F4和F8来选择使用前一个操作系统开机 

注意:  预设值设为0,是为了避免文件错误。因为使用者会无意中使用MS-DOS开机,并使用不认识长文件名的DOS工具程序 

----------------------------------------------------------------------

BootWarn=< Boolean>

预设值: 1

目的:  设为0时,关掉安全模式开机时的警告讯息和startup menu 

----------------------------------------------------------------------

BootWin=< Boolean>

预设值: 1

目的:  设为1时,开机后马上执行Win95 

     设为0时,不会在开机后马上执行Win95。当然你的系统必须有MS-DOS 5.x 或 6.x 

注意:  当BootMulti=1时,按F4则会使这里的设定变成相反的作用。例如BootWin=0时,如果BootMulti=1,则按F4就会强迫开机后执行Win95 

----------------------------------------------------------------------

DoubleBuffer=< Boolean>

预设值: 0

目的:  设为1时,提供Double-buffer功能。如果你的controller需要的话,如SCSI Controller 

    设为2时,则是无条件使用double-buffer功能,不管你的controller 是否需要 

----------------------------------------------------------------------

DBLSpace=< Boolean>

预设值: 1

目的:  设为1时,自动载入DBLSPACE.BIN 

     设为0时,不会自动载入 

----------------------------------------------------------------------

DRVSpace=< Boolean>

预设值: 1

目的:  设为1时,自动载入DRVSPACE.BIN 

     设为0时,不会自动载入

----------------------------------------------------------------------

LoadTop=< Boolean>

预设值: 1

目的:  设为0时,要求Win95不要将COMMAND.COM、DRVSAPCE.BIN和DBLSPACE.BIN载入UMB。如果你使用的软件有兼容问题时,可以考虑将此设为0 

----------------------------------------------------------------------

Logo=< Boolean>

预设值: 1

目的:  设为1时,强迫Win95显示预设的启动画面 

     设为0时,避免显示Win95启动画面 

3. MSDOS.SYS的重要性 

  MSDOS.SYS文件包含一些表面上看起来没有用的信息。其实,这些信息对某些软件是必需的。这些软件认为这个文件大小至少需1024

bytes。例如,如果防毒软体检测到MSDOS.SYS这个文件小于1024 bytes,则

它会认为这个文件已经遭到破坏。因此在这个文件后面一段说明文字:“;The following lines are required for

compatibility with other programs. ;Do not remove them (MSDOS.SYS needs to be

>1024 bytes)."。“ ;"是说明的意思,系统不会读取这段文字。在这段文字下就出现一堆 "X"。 

4. 如何编辑MSDOS.SYS

  编辑MSDOS.SYS的步骤如下:

   (1)修改MSDOS.SYS的文件属性:attrib -s -h -r c:.sys。

   (2)用Notepad编辑这个文件,并保存。

   (3)再将文件属性改回去:attrib +s +h +r c:.sys。

第三节 Windows NT

Windows NT注册表

  注册表提供了一个安全,统一的数据库,用来以一个层次结构来保存配置信息。注册表中的每一个主键和一个.INI文件中的用方括号括住的条目一样。

  .INI文件的一个缺点就是不支持条目的嵌套,以及包含非纯文本信息。注册表的主键能包含嵌套的付键。这些付键为配置系统提供了进一步详细的信息。注册表的值可以包含可执行代码,并同时为在同一计算机上的多个用户提供配置。

  有两个版本的注册表编辑器,可以用来修改注册表。

  .:Regedt32.exe 包含了大多数菜单项和它们的选择。可以在注册表里查找主键和付键。   .:Regedit.exe 可以查找字符串,值,主键和付键。

  为了便于使用,注册表分成了五个独立的部分。这些都叫做主键。

HKEY_CURRENT_USER

  这里包含了当前登录的用户的一些配置信息。用户的文件夹,屏幕颜色,控制面板设置都保存在这里。这些都是用户相关信息。

HKEY_USERS

  在NT 3.5版本,用户相关信息存在systemroot目录。在NT

4.0,它们存在systemroot目录。用户特有信息和系统用户共有的信息都存在那里。

  这里的改变是为了和Windows

95处理用户信息方法保持并行。在新版本的NT里,单个用户的信息被分成几个部分,放在的不同的子目录下。这样做的一个原因是,Win95和NT操作系统使用地层的目录结构形成他们新用户界面。

  一个用户信息包含在NTUser.dat(和NTUser.dat.log)文件和下面的目录里:

* Application Data:保存这个用户的应用程序信息。

* Desktop:放一个文件的图标或快捷方式,使得这些东西能在用户桌面上显示。

* Favorites:提供给用户放置他个人的一些保存内容,如文件,快捷方式和其他信息。

* NetHood::保存个人的有关网络连接的信息。

* Personal:为一个指定用户保存个人文档的跟踪。 * PrintHood:保存的是打印机的使用。

* Recent:最近使用的东西

* SendTo:提供一个对输出设备的统一存储。

* Start Menu:用户菜单的设置。

* Templates:放置文档的模板。

HKEY_LOCAL_MACHINE

  这个主键包含有关计算机的特殊信息。这些信息存放在systemroot目录下,作为操作系统的永久文件,除了一些易变的硬件主键。

  应用程序,设备驱动程序和NT操作系统从这些配置文件里读入信息。操作系统用这些信息来决定系统的配置,而不管是哪个用户在使用这个系统。正是因为这个原因,对于系统管理员来讲,HKEY_LOCAL_MACHINE是相当重要的。

  HKEY_LOCAL_MACHINE包括五个付键:

* Hardware:描述计算机硬件,硬件驱动程序怎样使用硬件,映象和连接内核模式驱动程序和各种用户模式数据的数据库。所有这些数据在系统启动时重新建立。

* SAM:安全帐号管理。在NT 4服务器域,用户和组帐号的安全管理。

* Security:包括本地安全策略,比如特定用户的权限的数据库。

* Software:应用软件的安装和配置信息数据库。

* System:控制系统启动,驱动程序装入,NT服务和操作系统行为的数据库。

HKEY_LOCAL_MACHINE的有关信息

  这个子树包含了本地计算机的SAM数据库中的用户和组帐号。对于NT 4.0,还包含了域的安全信息。SAM注册键包含的信息是User Manager

工具里显示的信息,或者是你使用NT 4 的资源管理器的安全菜单命令所显示的用户和组。

KEY_LOCAL_MACHINE的有关信息

  这个子树包含本地计算机的安全信息。包括以下方面:分配用户权限,建立口令策略,本地组的成员,都是由User Manager 配置的。

HKEY_CLASSES_ROOT

  保存在这里的信息是在使用资源管理器或者对象联结和嵌入时,打开一个文件时能调用正确的应用程序。

HKEY_CURRENT_CONFIG

  这里保存的数据是用来进行配置,比如软件和驱动程序的载入或显示时使用的分辨率。这个主键有两个付键:software和system,他们保持对配置信息的跟踪。

理解Hives

  注册表分成叫做hives的部分。这些hives和一个单独的文件及严格lOG文件对应。这些文件在systemroot目录下。

Registry Hive File Name

=================================================================

HKEY_LOCAL_MACHINESAM 和SAM.LOG

HKEY_LOCAL_MACHINESecurity 和 Security.LOG

HKEY_LOCAL_MACHINESoftware 和 Software.LOG

HKEY_LOCAL_MACHINESystem 和 System.ALT

=================================================================

注释

  Ownership =

选择ownership菜单项会出现一个对话框,显示所选择的注册键的拥有者的名字。键的拥有者允许另一个用户拥有这个键。管理员可以指定一个用户拥有这个Ownership,或自己直接操作。

  REGINI.EXE = 这是一个文本的控制台应用程序,通过她,可以将一个注册表脚本中的键加入到注册表中。

  下面的表列出了注册表hives和付键及缺省的存取权限。

   / 表示一个主要hive

    表示一个付键

/HKEY_LOCAL_MACHINE

Admin-Full Control

Everyone-Read Access

System-Full Control

 

Admin-Full Control

Everyone-Read Access

System-Full Control

 

Admin-Full Control

Everyone-Read Access

System-Full Control

 

Admin-Special (Write DAC, Read Control)

System-Full Control

 

Admin-Full Control

Creator Owner-Full Control

Everyone-Special (Query, Set, Create, Enumerate, Notify, Delete, Read)

System-Full Control

 

Admin-Special (Query, Set, Create, Enumerate, Notify, Delete, Read)

Everyone-Read Access

System-Full Control

/HKEY_CURRENT_USER

Admin-Full Control

Current User-Full Control

System-Full Control

/HKEY_USERS

Admin-Full Control

Current User-Full Control

System-Full Control

/HKET_CLASSES_ROOT

Admin-Full Control

Creator Owner-Full Control

Everyone-Special (Query, Set, Create, Enumerate, Notify, Delete, Read)

System-Full Control

/HKEY_CURRENT CONFIG

Admin-Full Control

Creator Owner-Full Control

Everyone-Read Access

System-Full Control

第二章

 

TCP/IP协议介绍

第一节 

TCP/IP协议简介

什么是TCP/IP?

  TCP协议和IP协议指两个用在Internet上的网络协议(或数据传输的方法)。它们分别是传输控制协议和互连网协议。这两个协议属于众多的TCP/IP 协议

组中的一部分。

  TCP/IP协议组中的协议保证Internet上数据的传输,提供了几乎现在上网所用到的所有服务。这些服务包括:

  电子邮件的传输

  文件传输

  新闻组的发布

  访问万维网

在TCP/IP协议组分两种协议:

  网络层的协议

  应用层的协议

网络层协议   网络层协议管理离散的计算机间的数据传输。这些协议用户注意不到,是在系统表层以下工作的。比如,IP协议为用户和远程计算机提供了信息包的传输方法。它是在许多信息的基础上工作的,比如说是机器的IP地址。在机器IP地址和其它信息的基础上,IP确保信息包能正确地到达目的机器。通过这一过程,IP和其它网络层的协议共同用于数据传输。如果没有网络工具,用户就看不到在系统里工作的IP。

应用层协议

  相反地,应用层协议用户是可以看得到的。比如,文件传输协议(FTP)用户是看得到的。用户为了传输一个文件请求一个和其它计算机的连接,连接建立后,就开始传输文件。在传输时,用户和远程计算机的交换的一部分是能看到的。

  请记住这句总结性的话:TCP/IP协议是指一组使得Internet上的机器相互通信比较方便的协议。

TCP/IP是如何工作的?

TCP/IP通过使用协议栈工作。这个栈是所有用来在两台机器间完成一个传输的所有协议的几个集合。(这也就是一个通路,数据通过它从一台机器到另一台机器。)栈分成层,与这里有关的是五个层。学习下面的图可以对层有个概念。

  在数据通过图示的步骤后,它就从网络中的一台机器传到另一台机器了。在这个过程中,一个复杂的查错系统会在起始机器和目的机器中执行。

  栈的每一层都能从相邻的层中接收或发送数据。每一层都与许多协议相联系。在栈的每一层,这些协议都在起作用。本章的下一部分将分析这些服务,以及它们在栈中是如何联系的。同时也分析一下它们的功能,它们提供的服务和与安全性的关系。

协议简介

  已经知道数据是怎样使用TCP/IP协议栈来传输的了。现在仔细分析在栈中所用到的关键的协议。先从网络层的协议开始。

网络层协议

  网络层协议是那些使传输透明化的协议。除了使用一些监视系统进程的工具外,用户是看不见这些协议的。

  Sniffers是能看到这些步骤的装置。这个装置可以是软件,也可以是硬件,她能读取通过网络发送的每一个包。Sniffers广泛地用于隔离用户看不到的、网络性能下降的问题。sniffers能读取发生在网络层协议的任何活动。而且,正如你已经猜到的,sniffers会对安全问题造成威胁。参见Sniffers一章。

  重要的网络层协议包括:

   地址解析协议(ARP)

   Internet控制消息协议(ICMP)

   Internet协议(IP)

   传输控制协议(TCP)

  下面仅仅简单介绍一下。

地址解析协议ARP

  地址解析协议的目的是将IP地址映射成物理地址。这在使信息通过网络时特别重要。在一个消息(或其他数据)发送之前,被打包到IP包里,或适合于Internet传输的信息块。这包括两台计算机的IP地址。在这个包离开发送计算机之前,必须要找到目标的硬件地址。这就是ARP最初用到的地方。

  一个ARP请求消息在网上广播。请求由一个进程接收,它回复物理地址。这个回复消息由原先的那台发送广播消息计算机接收,从而传输过程就开始了。

  ARP的设计包括一个缓存。为了理解缓存的概念,考虑一下:许多现代的HTML浏览器(比如Netscape或Microsoft的Internet

Explorer)使用了一个缓存。缓存是磁盘的一部分,从Web网上经常访问的东西就存在里面(比如按钮,或通用的图形)。这是符合逻辑的,因为当你返回这些主页的时候,这些东西不必再从远程计算机上装载了。从缓存中装载的速度要比较快。

  相似的,ARP的实现包括一个缓存。以这种方式,网络或远程计算机的硬件地址就存着了,并为接着的ARP请求作准备。这样节省了时间和网络资源。

  但是,正是由于缓存,就引起了安全性。

  对于网络安全来将,这并不是最重要的安全性问题。然而,地址缓存(不仅仅是在ARP而且在其他例子中)确实会引起安全性问题。一旦这些地址保存,都会是让黑客伪造一个远程连接,它们对缓存的地址很欢迎。 Internet控制消息协议ICMP

  Internet控制消息协议是用来在两台计算机间传输时处理错误和控制消息的。它允许这些主机共享信息。在这一方面,ICMP是用来诊断网络问题的重要工具。通过ICMP收集诊断信息的例子如下:

  一台主机关机

  一个网关堵塞和工作不正常

  网络中其他的失败

  可能最著名的ICMP实现的网络工具是ping。ping通常用来判断是否一台远程机器正开着,数据包从用户的计算机发到远程计算机。这些包通常返回用户的计算机。如果没有返回数据包到用户计算机,ping程序就产生一个表示远程计算机关机的错误消息。

应用层协议

  应用层协议是专门为用户提供应用服务的。它是建立在网络层协议之上的。

Telnet

Telnet在RFC

854中有详细地描述,Telnet协议中说明:Telnet协议的目的就是提供一个相当通用的,双向的,面向八位字节的通信机制。它的最初目的是允许终端和面向终端的进程之间的交互。

Telnet不仅允许用户登录到一个远程主机,它允许用户在那台计算机上执行命令。这样,Los Angeles的一个人可以Telnet到New

York的一台机器,并在这台机器上运行程序,就跟在New York的用户一样。

  对于熟悉Telnet的用户来讲,他的操作与BBS的界面一样。Telnet是一个能提供建立在终端字体的访问数据库的一个应用程序。比如,多于80%的大学的图书馆的目录可以通过Telnet访问到。

  即使GUI应用程序被大大采用,Telnet这个建立在字符基础上的应用程序,仍相当的流行。这有许多原因。第一,Telnet允许你以很小的网络资源花费实现各种功能(如收发邮件)。实现安全的Telnet是件十分简单的事。有许多这样的程序,通用的是Secure

Shell。

  要使用Telnet,用户要指定启动Telnet客户的命令,并在后面指定目标主机的名字。在Linux中,可以这样:

  $telnet internic.net

  这个命令启动Telnet过程,连接到internic.net。这个连接可能被接受,或被拒绝,这与目标主机的配置有关。在UNIX,Telnet命令很久以前就是内置的。也就是说,Telnet已经包含在UNIX的发行版本中有十年了。但并不是所有操作系统都将Telnet作为内置的Telnet客户。

文件传输协议FTP

  文件传输协议是从一个系统向另一个系统传递文件的标准方法。它的目标在RFC 0765中写得很清楚。

  FTP的目标是1)促进文件和程序的共享,2)鼓励间接和含蓄的使用远程计算机,3)使用户不必面对主机间使用的不同的文件存储系统,4)有效和可靠地传输文件。FTP,尽管用户可以直接通过终端来使用,是设计成让别的程序使用的。

  约有二十年,研究者调查了相当广泛的文件传输方法。FTP经历了多次改变。1971年作了第一次定义,整个的说名参见RFC 114。

FTP是怎样工作的?

  FTP文件传输应用在客户/服务环境。请求机器启动一个FTP客户端软件。这就给目标文件服务器发出了一个请求。典型地,这个要求被送到端口21。一个连接建立起来后,目标文件服务器必须运行一个FTP服务软件。

  FTPD是标准的FTP服务daemon。它的功能很简单:回复inetd收到的连接请求,并满足这些要传输文件的请求。这个daemon在许多发行版的UNIX中是个标准。

  FTPD等待一个连接请求。当这样的一个请求到达时,FTPD请求用户登录。用户提供它的合法的登录名和口令或匿名登录。

  一旦登录成功,用户可以下载文件了。在某些情况下,如果服务器的安全允许,用户可以上载文件。

简单邮件传输协议SMTP

  简单邮件传输协议的目的是使得邮件传输可靠和高效。

  SMTP是一个相当小和有效的协议。用户给SMTP服务器发个请求。一个双向的连接随后就建立了。客户发一个MAIL指令,指示它想给Internet上的某处的一个收件人发个信。如果SMTP允许这个操作,一个肯定的确认发回客户机。随后,会话开始。客户可能告知收件人的名称和IP地址,以及要发送的消息。

  尽管SMTP相当简单,邮件服务是无穷的安全漏洞的源泉。

  SMTP服务在Linux内部是内置的。其它网络操作系统也提供某些形式的SMTP。

Gopher

  Gopher是一个分布式的文件获取系统。它最初是作为Campus Wide Information

System在Minnesota大学实现的。它的定义如下:

  Internet

Gopher协议最初是设计用来最为一个分布式文件发送系统的。文档放在许多服务器上,Gopher客户软件给客户提供一个层次项和目录,看上去象一个文件系统。事实上,Gopher的界面设计成类似一个文件系统,因为文件系统是查找文件和服务的最好模型。

  Gopher服务功能相当强大。能提供文本,声音,和其他媒体。主要用在文本模式,比通过用浏览器使用HTTP要来得快。毫无疑问,最流行的Gopher客户软件是为UNIX编写的。其他操作系统也有Gopher客户端软件。

  典型地,用户启动一个Gopher客户端软件,和一个Gopher服务器。随后,Gopher返回一个可以选择的菜单。可能包括查找菜单,预先设置的目标,或文件目录。

  注意,Gopher模式完全是一个客户服务器模式。用户每次登录,客户给Gopher服务器发送一个请求,要求所有能得到的文档。Gopher服务器对这个信息做出反应知道用户请求一个对象。

超联结传输协议HTTP

  由于它能让用户在网上冲浪,超联结传输协议可能是最有名的协议。HTTP是一个应用层协议,它很小也很有效,符合发布、合成和超媒体文本系统的的需要。是一个通用的,面向对象的协议,通过扩展请求命令,可以用来实现许多任务。HTTP的一个特点是数据表现的类型允许系统相对独立于数据的传输。

  HTTP的出现永久地改变了Internet的特点,主要是使Internet大众化。在某些程度上,他它的操作与Gopher相类似。比如,它的工作是请求/响应式的。这是相当重要的一点。其他应用程序,比如Telnet仍需要用户登录(当他们登录时,便消耗系统资源)。但Gopher和HTTP协议,消除了这一现象。用户(客户)仅仅在他们请求或接受数据时消耗资源。

  使用通用浏览器,象Netscape Navigator或Microsoft Internet

Explore,可以监视这一过程的发生。在WWW上的数据,你的浏览器会和服务器及时联系。这样,它首先获取文本,然后是图形,再后是声音,等等。在你的浏览器的状态栏的左下角。当它装载页面时,看着它几分钟。你会看到请求和服务活动的发生,通常速度很快。

  HTTP并不特别关注所需的是什么类型的数据。各种形式的媒体都能插进,以及远程的HTML主页。

网络新闻传输协议NNTP

  网络新闻传输协议是一个广泛使用的协议。它提供通常作为USENET新闻组的新闻服务。

  NNTP定义了一个协议,使用一个可靠的建立在流的基础上的在Internet上传输新闻的分发,询问,获取和发布的一个协议。NNTP被设计成新闻被存储在一个中心的数据库,允许订阅者选择他们希望读的主题。目录,交叉引用和过期的新闻都能找到。

  NNTP有许多特性和简单邮件传输协议以及TCP相似。与SMTP相似,它接受一般的英语命令。和TCP相似,它是建立在流的传输和分发的基础上的。NNTP通常在端口119运行。

下面详细地讲解一下以太网,IP协议和TCP协议。

第二节 Etherner

以太网的基本工作原理

  以太网上的所有设备都连在以太总线上,它们共享同一个通信通道。以太网采用的是广播方式的通信,即所有的设备都接收每一个信息包。网络上的设备通常将接收到的所有包都传给主机界面,在这儿选择计算机要接收的信息,并将其他的过滤掉。以太网是最有效传递的意思是,硬件并不给发送者提供有关信息已收到的信息。比如,即使目标计算机碰巧关机了,送给它的包自然就丢失,但发送者并不会知道这一点。

  以太网的控制是分布式的。以太网的存取方式叫做带有Collision的Carrier Sense Multipe

Access。因为多台计算机可以同时使用以太网,每台机器看看是否有载波信号出现判定总线是否空闲。如果主机接口有数据要传输,它就侦听,看看是否有信号正在传输。如果没有探测到,它就开始传输。每次传输都在一定的时间间隔内,即传输的包有固定的大小。而且,硬件还必须在两次传输之间,观察一个最小的空闲时间,也就是说,没有一对机器可以不给其他计算机通信的机会而使用总线。

冲突侦测和恢复

  当开始一个传输时,信号并不能同时到达网络的所有地方。传输速度实际上是光速的80%。这就有可能两个设备同时探测到网络是空闲的,并都开始传输。但当这两个电信号在网络上相遇时,它们都不再可用了。这种情况叫做冲突。

  以太网在处理这种情况时,很有技巧性。每台设备在它传输信号的时候都监视总线,看看它在传输的时候是否有别的信号的干扰。这种监视叫做冲突侦听。在探测到冲突后,设备就停止传输。有可能网络会因为所有的设备都忙于尝试传输数据而每次都产生冲突。

  为了避免这种情况,以太网使用一个2进制指数后退策略。发送者在第一次冲突后等待一个随机时间,如果第二次还是冲突,等待时间延长一倍。第三次则再延长一倍。通过这种策略,即使两台设备第二的等待时间会很接近,但由于后面的等待时间成指数倍增长,不就,他们就不会相互冲突了。

以太网的硬件地址

  每台连接到以太网上的计算机都有一个唯一的48位以太网地址。以太网卡厂商都从一个机构购得一段地址,在生产时,给每个卡一个唯一的地址。通常,这个地址是固化在卡上的。这个地址又叫做物理地址。

  当一个数据帧到达时,硬件会对这些数据进行过滤,根据帧结构中的目的地址,将属于发送到本设备的数据传输给操作系统,忽略其他任何数据。

  一个是地址位全为1的时表示这个数据是给所有总线上的设备的。

以太网的帧结构

  以太网的帧的长度是可变的,但都大于64字节,小于1518字节。在一个包交换网络中,每个以太网的帧包含一个指明目标地址的域。上图是以太网帧的格式,包含了目标和源的物理地址。为了识别目标和源,以太网帧的前面是一些前导字节,类型和数据域以及冗余校验。前导由64个0和1交替的位组成,用于接收同步。32位的CRC校验用来检测传输错误。在发送前,将数据用CRC进行运算,将结果放在CRC域。接收到数据后,将数据做CRC运算后,将结果和CRC域中的数据相比较。如果不一致,那么传输过程中有错误。

  帧类型域是一个16位的整数,用来指示传输的数据的类型。当一个帧到达台设备后,操作系统通过帧类型来决定使用哪个软件模块。从而允许在同一台计算机上同时运行多个协议。

第三节 Internet地址

  网络上的每一台计算机都有一个表明自己唯一身份的地址。TCP/IP协议对这个地址做了规定。一个IP地址由一个32位的整数表示。它的一个较为聪明的地方是很好的规定了地址的范围和格式,从而使地址寻址和路由选择都很方便。一个IP地址是对一个网络和它上面的主机的地址一块编码而形成的一个唯一的地址。

  在同一个物理网络上的主机的地址都有一个相同前缀,即IP地址分成两个部分:(netid,hostid)。其中netid代表网络地址,hostid代表这个网络上的主机地址,根据他们选择的位数的不同,可以分成以下五类基本IP地址。

 

  通过地址的前3位,就能区分出地址是属于A,B或C类。其中A类地址的主机容量有16777216台主机,B类地址可以有65536台主机,C类地址可以有256台主机。

  将地址分成网络和主机部分,在路由寻址时非常有用,大大提高了网络的速度。路由器就是通过IP地址的netid部分来决定是否发送和将一个数据包发送到什么地方。

  一个设备并不只能有一个地址。比如一个连到两个物理网络上的路由器,它就有两个IP地址。所以可以将IP地址看成是一个网络连接。

  为了便于记忆和使用32位的IP地址,可以将地址使用用小数点分开的四位整数来表示。下面举个例子:

IP地址: 10000000 00001010 00000010 00011110

记为: 128.10.2.30

第四节 IP协议和路由

IP协议

  IP协议定义了一种高效、不可靠和无连接的传输方式。由于传输没有得到确认,所以是不可靠的。一个包可能丢失了,或看不见了,或是延时了,或是传输顺序错了。但是传输设备并不检测这些情况,也不通知通信双方。无连接

因为每个包的传递与别的包是相互独立的。同一个机器上的包可能通过不同的路径到达另一台机器,或在别的机器上时已经丢失。由于传输设备都试图以最快的速度传输,所以是最高效的。

  IP协议定义了通过TCP/IP网络传输的数据的格式,定义了数据进行传递的路由功能。

IP数据包的格式如下:

  由一个头和数据部分组成。数据包的头部分包含诸如目的地址和源地址,数据的类型等信息。

数据包头格式:

  数据包是由软件处理的,它的内容和格式并不是由硬件所限定。

  比如,头4位是一个VERS,表示的是使用的IP协议的版本号。它表示发送者、接收者和路由器对该数据的处理都要按所示的版本进行处理。现在的版本号是4。软件通过版本来决定怎样进行处理。

  头长度(HLEN)也是用4位来表示以32位为计量单位的头的长度。

  TOTAL LENGTH表示这个数据包的长度(字节数)。从而包中的数据的长度就可以通过上面两个数据而计算出来了。

  一般来说,数据部分就是一个物理的帧。对于以太网来讲,就是将整个的一个以太网的帧数据作为一个IP数据包的数据来传输的。

  数据包的头里面还包含了一些其他的信息,请参见有关资料的具体介绍。 IP路由

  在一个网络上,连接两种基本设备,主机和路由器。路由器通常连接几个物理网络。对一台主机来讲,要将一个数据包发往别的网络,就需要知道这个数据包应该走什么路径,才能到达目的地。对于一台路由器来讲,将收到的数据包发往哪个物理网络。因此,无论主机还是路由器,在发送数据包是都要做路由选择。

  数据发送有两种方式:直接数据发送和间接数据发送。

  直接数据发送通常是在同一个物理网络里进行的。当一个主机或路由器要将数据包发送到同一物理网络上的主机上时,是采用这种方式的。首先判断IP数据包中的目的地址中的网络地址部分,如果是在同一个物理网络上,则通过地址分析,将该IP目的地址转换成物理地址,并将数据解开,和该地址合成一个物理传输帧,通过局域网将数据发出。

  间接数据发送是在不同物理网络里进行的。当一个主机或路由器发现要发送的数据包不在同一个物理网络上时,这台设备就先在路由表中查找路由,将数据发往路由中指定的下一个路由器。这样一直向外传送数据,到最后,肯定有一个路由器发现数据要发往同一个物理网络,于是,再用直接数据发送方式,将数据发到目的主机上。

  主机和路由器在决定数据怎样发送的时候,都要去查找路由。一般,都将路由组成一个路由表存在机器中。路由表一般采用Next-Hop格式,即(N,R)对。N是目标地址的网络地址,R是传输路径中的下一个路由。通常这个路由和这台机器在同一物理网络里。

第五节 TCP协议

TCP传输原理

  TCP协议在IP协议之上。与IP协议提供不可靠传输服务不同的是,TCP协议为其上的应用层提供了一种可靠传输服务。这种服务的特点是:可靠、全双工、流式和无结构传输。

  它是怎样实现可靠传输的呢?

  TCP协议使用了一个叫积极确认和重发送(positive acknowledgement with retransmission)的技术来实现这一点的。

  接收者在收到发送者发送的数据后,必须发送一个相应的确认(ACK)消息,表示它已经收到了数据。

  发送者保存发送的数据的记录,在发送下一个数据之前,等待这个数据的确认消息。在它发送这个数据的同时,还启动了一个记时器。如果在一定时间之内,没有接收到确认消息,就认为是这个数据在传送时丢失了,接着,就会重新发送这个数据。

  这种方法还产生了一个问题,就是包的重复。如果网络传输速度比较低,等到等待时间结束后,确认消息才返回到发送者,那么,由于发送者采用的发送方法,就会出现重复的数据了。解决的一个办法是给每个数据一个序列号,并需要发送者记住哪个序列号的数据已经确认了。为了防止由于延时或重复确认,规定确认消息里也要包含确认序列号。从而发送者就能知道哪个包已经确认了。   TCP协议中还有一个重要的概念:滑动窗口。这一方法的使用,使得传输更加高效。

  有前面的描述可见,发送者在发送完一个数据包之后,要等待确认。在它收到确认消息之前的这段时间是空闲的。如果网络延时比较长,这个问题会相当明显。

  滑动窗口方法是在它收到确认消息以前,发送多个数据包。可以想象成有一个窗口在一个序列上移动。

  如果一个包发送出去之后还没有确认,叫做未确认包。通常未确认的包的个数就是窗口的大小。

  此窗口的大小为8。发送者允许在接收到一个确认消息以前发送8个数据包。当发送者接到窗口中第一个包的确认消息时,它就将窗口下滑一个。

  在接收端,也有一个滑动窗口接收和确认一个包。

端口

使用TCP传输就是建立一个连接。在TCP传输中一个连接有两个端点组成。其实,一个连接代表的是发送和接收两端应用程序的之间的一个通信。可以把他们想象成建立了一个电路。通常一个连接用下面的公式表示:

(host,port)

host是主机,port是端口。TCP端口能被几个应用程序共享。对于程序员来讲,可以这样理解:一个应用程序可以为不同的连接提供服务。

TCP格式

  TCP传输的单位是段,在建立连接,传送数据,确认消息和告之窗口大小时均要进行段的交换。

  段的格式如下图:

  段的格式也分成两部分,头和数据。

  上面格式中的名称已经足够说明了他们的作用了。具体的含义请参见有关资料。

建立一个TCP连接

  TCP协议使用一个三次握手来建立一个TCP连接的。

 

  握手过程的第一个段的代码位设置为SYN,序列号为x,表示开始一次握手。接收方收到这个段后,向发送者回发一个段。代码位设置为SYN和ACK,序列号设置为y,确认序列号设置为x+1。发送者在受到这个段后,知道就可以进行TCP数据发送了,于是,它又向接收者发送一个ACK段,表示,双方的连接已经建立。

  在完成握手之后,就开始正式的数据传输了。

  上面握手段中的序列号都是随机产生的。

第三章 

网络编程

本章主要介绍一下网络编程的基本知识。由于书中后面章节都有一些简单的源程序实例来对各章的基本概念进行解释,因此必须具备必要的网络编程知识。

在平时工作中,为了查找安全漏洞,也需要编写一些短小精悍的程序来代替复杂的手工命令输入。 在操作系统一章中对Linux中的C语言编程和调试已经作了介绍。本章在前两章的基础上,首先对Linux中的网络编程作介绍,Linux对网络通信提供了很好的支持。由于Windows系统目前很流行,特别是开发环境Visual

C++,所以,本章也对Windows环境下的网络编程作了介绍。

第一节 Linux网络编程(Berkeley Sockets)

我们可以认为套接字是将Unix系统的文件操作推广到提供点对点的通信。如果要操作文件,应用程序会根据应用程序的需要为之创建一个套接字。操作系统返回一个整数。应用程序通过引用这个正数来使用这个套接字。文件描述符和套接字描述符的不同点在于,在程序调用open()时,操作系统将一个文件描述符绑定到一个文件或设备,但在创建一个套接字时,可以不将它绑定到一个目标地址。程序可以在任何想要用这个套接字的时候指定目标地址。

在点对点的通信程序中,我们将请求服务或数据的程序叫做客户端程序,提供数据或服务的软件叫做服务器程序。

图1是一个面向连接的服务器程序和客户端程序的流程图。

对于使用无连接协议的服务器程序和客户端程序的流程,请参见图2。图中,客户端程序并不和服务器程序建立连接,它是通过使用服务器地址作为参数的sendto()系统调用,发送一个数据报给服务器的。同样,服务器并不接受客户端的连接,而是用recvfrom()调用等待从客户端来的数据。

套接字系统调用

  下面解释一下几个基本的套接字系统调用函数。只要你将下面的函数与系统的输入输出函数调用加以对比,就能很快地掌握这些函数调用了。

socket()

------------------------------------------------------------

#include < sys/types.h>

#include < sys/socket.h>

int socket(int family, int type, int protocol);

------------------------------------------------------------

int family参数指定所要使用的通信协议,取以下几个值。

值 含义

AF_UNIX Unix内部协议

AF_INET Internet协议

AF_NS Xerox NS协议

AF_IMPLINK IMP 连接层

int type 指定套接字的类型,取以下几个值

值 含义

SOCK_STREAM 流套接字

SOCK_DGRAM 数据报套接字

SOCK_RAW 未加工套接字

SOCK_SEQPACKET 顺序包套接字

int protocol 参数通常设置为0。

  socket()系统调用返回一个整数值,叫做套接字描述字sockfd,它的原理与文件描述符一样。网络I/O的第一步通常就是调用这个函数。

socektpair()

------------------------------------------------------------

#include < sys/types.h>

#include < sys/socket.h>

int socketpair(int family, int type, int protocol, int sockvec[2]);

------------------------------------------------------------

  这个调用返回两个套接字描述符,

sockvec[0]和sockvec[1],它们没有名字,但是连着的。这个调用与管道系统调用类似。由这个调用创建的结构叫做一个流管道。

bind()

------------------------------------------------------------

#include < sys/types.h>

#include < sys/socket.h>

int bind(int sockfd, struct sockaddr *myaddr, int addrlen);

------------------------------------------------------------

  这个调用将一个名字命名给一个没有名字的套接字。第二个参数myaddr是指向一个特定协议地址的指针,第三个参数是这个地址结构的大小。

  bind()有三个作用:

   服务器在系统里登记它们的地址

   客户为它自己注册一个地址

   一个没有连接的客户确保系统固定分配给它一个唯一的地址

connect()

------------------------------------------------------------

#include < sys/types.h>

#include < sys/socket.h>

int connect(int sockfd, struct sockaddr *servaddr, int addrlen);

------------------------------------------------------------

  这个过程在socket()调用后,将一个套接字描述符和一个与服务器建立的连接的联系。sockfd是一个由socket()调用返回的套接字描述符。第二个参数是服务器套接字地址的指针,第三个参数是这个地址的长度。

 

listen()

------------------------------------------------------------

#include < sys/types.h>

#include < sys/socket.h>

int listen(int sockfd, int backlog)

------------------------------------------------------------

  面向连接的服务器使用这个系统调用,来表示它希望接受连接。

  这个系统调用通常在socket()和bind()之后,在accept()调用之前调用。参数backlog表示当它们等待执行accept()系统调用之前,系统能对多少个连接请求进行排队。

accept()

------------------------------------------------------------

#include < sys/types.h>

#include < sys/socket.h>

int accept(int sockfd, struct sockaddr *peer, int *addrlen);

------------------------------------------------------------

  在一个建立好连接的服务器执行了listen()系统调用之后,一个实际和客户的连接过程等待服务器调用accept()系统调用。

  accept()取出在队列里的第一个连接请求,并且创建另一个和sockfd有相同属性套接。如果队列中没有连接请求,这个调用就将调用者阻塞,知道有请求为止。

  peer和addrlen 参数用来返回连接的客户的地址。调用者在调用之前设置addrlen的值,系统调用通过它返回一个值。

send(), sendto(), recv(), recvfrom()

------------------------------------------------------------

#include < sys/types.h>

#include < sys/socket.h>

int send(int sockfd, char *buff, int nbytes, int flags);

int sendto(int sockfd, char *buff, int nbytes, int flags,

struct sockaddr *to, int addrlen);

int recv(int sockfd, char *buff, int nbytes, int flags);

int recvfrom(int sockfd, char *buff, int nbytes, int flags,

struct sockaddr *from, int addrlen);

------------------------------------------------------------

  这些调用与标准的系统调用read()和write()相似。

  这些调用需要附加的参数。Flag参数可以是0或者下列常数:

   MSG_OOB 接受或发送绑定外的数据

   MSG_PEEK 监视进入信息

   MSG_DONTROUTE 绕过路由

close()

------------------------------------------------------------

#include < sys/types.h>

#include < sys/socket.h>

int close(int sockfd);

------------------------------------------------------------

  关闭一个套接字。

编程实例

从一个描述符读n字节数据

/* 从一个描述符读n字节数据 */

int readn(register int fd, register char *ptr, register int nbytes)

{

int nleft, nread;

nleft=nbytes;

while (nleft > 0){

nread=read(fd,ptr,nleft);

if(nread < 0)

return(nread);

else if (nread==0)

break;

nleft-=nread;

ptr +=nread;

}

return(nbytes - nleft);

}

写n字节数据到一个描述符

/* 写n字节数据到一个描述符 */

int writen(register int fd, register char *ptr, register int nbytes)

{

int nleft, nwritten;

nleft=nbytes;

while(nleft>0){

nwritten=write(fd,ptr,nleft);

if(nwritten< =0)

return(nwritten);

nleft -= nwritten;

ptr += nwritten;

}

return(nbytes-nleft);}

TCP编程

/* inet.h

* 服务器和客户端程序的头文件。

*/

#include < stdio.h>

#include < sys/types.h>

#include < sys/socket.h>

#include < netinet/in.h>

#include < arpa/inet.h>

#define SERV_UDP_PORT 6000

#define SERV_TCP_PORT 6000

#define SERV_HOST_ADDR "192.43.235.6" /* host addr for server */

char *pname;

服务器程序如下:

/* TCP服务器程序 */

#include "inet.h"

main(int argc, char * argv)

{

int sockfd, newsockfd, clilen, childpid;

struct sockaddr_in cli_addr, serv_addr;

pname = argv[0];

/* 打开一个TCP套接字 (一个Internet流套接字) */

if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)

err_dump("server: can't open stream socket");

/* 绑定本地地址,这样,客户机就能访问到服务器。*/

bzero((char *) &serv_addr, sizeof(serv_addr));

serv_addr.sin_family = AF_INET;

serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

serv_addr.sin_port = htons(SERV_TCP_PORT);

if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)

err_dump("server: can't bind local address");

listen(sockfd, 5);

for ( ; ; ) {

/* 等待一个来自客户机的连接进程,这是一个并发的服务器。*/

clilen = sizeof(cli_addr);

newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);

if (newsockfd < 0)

err_dump("server: accept error");

if ( (childpid = fork()) < 0)

err_dump("server: fork error");

else if (childpid == 0) { /* 子进程 */

close(sockfd); /* 关闭原来的套接字 */

str_echo(newsockfd); /* 处理请求 */

exit(0);

}

close(newsockfd); /* 父进程 */

}

}

服务机代码:

/* 使用TCP协议客户机 */

#include "inet.h"

main(argc, argv)

int argc;

char *argv[];

{

int sockfd;

struct sockaddr_in serv_addr;

pname = argv[0];

/* 在结构"serv_addr"里填入想要连接的服务器的地址*/

bzero((char *) &serv_addr, sizeof(serv_addr));

serv_addr.sin_family = AF_INET;

serv_addr.sin_addr.s_addr = inet_addr(SERV_HOST_ADDR);

serv_addr.sin_port = htons(SERV_TCP_PORT);

/* 打开一个TCP套接字(一个Internet 流套接字) */

if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)

err_sys("client: can't open stream socket");

/* 连到服务器上*/

if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)

err_sys("client: can't connect to server");

str_cli(stdin, sockfd); /* 全部输出 */

close(sockfd);

exit(0);

}

套接字和信号量

  在使用一个套接字时,可以产生三个信号量。    (SIGIO) 这个信号量表示一个套接字已经准备好进行异步I/O了。这个信号量会发给这个套接字的所有进程。这些进程是通过用FIOSETOWN 或

SIOCSPGRP

调用ioctl而建立的。或者是用F_SETOWN调用fcntl建立的。这个信号量只在这个进程在这个套接字上,用FIOASYNC调用ioctl或用FASYNC调用fcntl后,可以进行异步I/O后发给这些进程的。

   (SIGURG)

这个信号量表示出现了一个紧急情形。一个紧急情形是任何一个在套接字上一个出现了一个超过带宽的数据的到达信息。超过带宽表示在用户进程到达的数据超出了I/O缓冲区了。

   (SIGPIPE) 这个信号量表明我们不再会向套接字,管道或FIFO写数据了。

异步I/O

  异步I/O允许进程通知操作系统内核,如果一个指定的描述符可以进行I/O时,内核通知该进程。这通常叫做信号量驱动I/O。内核通知进程的信号量是SIGIO。

  为了实现异步I/O,一个进程必须:

   建立一个处理SIGIO信号量的程序。

   将进程ID或进程组ID设置好,能接受SIGIO信号量。这是由fcntl命令实现的。

   进程必须用dcntl系统调用,激活异步I/O。

第二节 Windows网络编程(WinSock)

  这里介绍WinSock创建TCP流套接字程序。Winsock的编程和第一部分将的非常的相似。

创建TCP流套接字服务器程序

  用socket()函数打开一个流套接字。用AF_INET指定地址格式参数,SOCK_STREAM指定类型参数。

if ((WinSocket = socket (AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)

{

wsprintf (szError, TEXT("Allocating socket failed. Error: %d"),

WSAGetLastError ());

MessageBox (NULL, szError, TEXT("Error"), MB_OK);

return FALSE;

}

  使用SOCKADDR_IN结构作为地址参数,用bind()函数命名套接字。

  用socket()函数打开一个套接字时,这个套接字没有名字,仅仅在一个地址家族名字空间里分配了一个描述符。为了让客户端套接字区分开来,一个TCP流套接字服务器程序必须命名它的套接字。但不必用bind()函数命名客户端的套接字。

  一个套接字的名字在TCP/TP协议里由三部分组成:协议名称,主机地址和一个表征应用程序的端口数字。这些地址域sin_family, sin_addr,

sin_port都是SOCKADDR_IN结构的成员。必须在调用bind()之前初始化SOCKADDR_IN结构。

  下面的这段代码示范怎样初始化SOCKADDR_IN结构和调用bind()函数。

// 填写本地套接字地址数据

local_sin.sin_family = AF_INET;

local_sin.sin_port = htons (PORTNUM);

local_sin.sin_addr.s_addr = htonl (INADDR_ANY);

// 将本地地址和WinSocket相连

if (bind (WinSocket,

(struct sockaddr *) &local_sin,

sizeof (local_sin)) == SOCKET_ERROR)

{

wsprintf (szError, TEXT("Binding socket failed. Error: %d"),

WSAGetLastError ());

MessageBox (NULL, szError, TEXT("Error"), MB_OK);

closesocket (WinSocket);

return FALSE;

}

  使用listen()函数侦听。为了准备一个TCP流套接字服务器的一个名字连接,必须侦听从客户端来的连接。   下面这个例子说明了怎样使用listen()函数。

if (listen (WinSocket, MAX_PENDING_CONNECTS) == SOCKET_ERROR)

{

wsprintf (szError,

TEXT("Listening to the client failed. Error: %d"),

WSAGetLastError ());

MessageBox (NULL, szError, TEXT("Error"), MB_OK);

closesocket (WinSocket);

return FALSE;

}

  使用accept()接受客户端的连接。

  TCP流服务器套接字使用这个函数来完成服务器和客户端的名字连接过程。

  Accept()函数创建一个新的套接字。初始的由服务器打开的套接字继续侦听该端口,可以一直接受连接,知道关闭。服务器程序必须负责关闭侦听套接字以及在接受客户连接是创建的所有套接字。   下面的代码是accept()函数应用的示范。

accept_sin_len = sizeof (accept_sin);

// 接受一个试图在WinSocket上连接的请求

ClientSock = accept (WinSocket,

(struct sockaddr *) &accept_sin,

(int *) &accept_sin_len);

// 停止对客户连接的侦听

closesocket (WinSocket);

if (ClientSock == INVALID_SOCKET)

{

wsprintf (szError, TEXT("Accepting connection with client

failed.") TEXT(" Error: %d"), WSAGetLastError ());

MessageBox (NULL, szError, TEXT("Error"), MB_OK);

return FALSE;

}

  使用send() and recv()函数发送和接受客户的数据。

  一旦客户端和服务端的套接字连接上后,就能使用上述两个函数交换数据。

  Send()函数将数据输出到套接字上。Recv()函数从套接字中读取数据。

  下面的代码是上述两个函数的应用示范。

for (;;)

{

// 从客户端接受数据

iReturn = recv (ClientSock, szServerA, sizeof (szServerA), 0);

// 确认数据收到后,显示数据

if (iReturn == SOCKET_ERROR)

{

wsprintf (szError, TEXT("No data is received, receive failed.")

TEXT(" Error: %d"), WSAGetLastError ());

MessageBox (NULL, szError, TEXT("Server"), MB_OK);

break;

}

else if (iReturn == 0)

{

MessageBox (NULL, TEXT("Finished receiving data"),

TEXT("Server"), MB_OK);

break;

}

else

{

// 将ASCII字符串转换成Unicode字符串

for (index = 0; index < = sizeof (szServerA); index++)

szServerW[index] = szServerA[index];

// 显示从客户端接收到的数据

MessageBox (NULL, szServerW, TEXT("Received From Client"),

MB_OK);

}

}

// 从服务器给客户端发个数据

if (send (ClientSock, "To Client.", strlen ("To Client.") + 1, 0)

== SOCKET_ERROR)

{

wsprintf (szError,

TEXT("Sending data to the client failed. Error: %d"),

WSAGetLastError ());

MessageBox (NULL, szError, TEXT("Error"), MB_OK);

}

  成功地完成send()函数的调用并不能说明数据的发送是成功的。

  使用closesocket()函数来断开连接。当服务器和客户端数据交换结束后,使用这个函数关闭套接字。为了在一个TCP连接确认数据已经交换了,一个程序应该在调用这个函数之前调用shutdown()函数。

  一个程序应该在程序结束前,关闭所有打开的程序,以便将套接字资源返回给操作系统。对于TCP流套接字,当一个套接字连接结束后,服务器关闭了有accept()创建的套接字,但最先的侦听套接字还是打开的。在程序结束前要将侦听套接字也关闭。

创建TCP流套接字客户端程序

  用socket()函数打开一个流套接字。 调用这个函数时使用AF_INET作为地址格式参数,用SOCK_STREAM做类型参数。

  用SOCKADDR_IN结构作为名字参数调用connect()函数和服务器连接。TCP流套接字客户端通过这个函数将名字和服务器相连。

  在调用connect()函数之前要初始化SOCKADDR_IN 结构,这和bind()函数调用类似,但是sin_port

和sin_addr用远程的套接字名字,而不是本地的。

  下面这段代码显示怎样和服务器相连。

// 建立一个和服务器套接字的连接

if (connect (ServerSock,

(PSOCKADDR) &destination_sin,

sizeof (destination_sin)) == SOCKET_ERROR)

{

wsprintf (szError,

TEXT("Connecting to the server failed. Error: %d"),

WSAGetLastError ());

MessageBox (NULL, szError, TEXT("Error"), MB_OK);

closesocket (ServerSock);

return FALSE;

}

  用send()和recv*(函数和服务器交换数据。用closesocker()函数关闭连接。

 

第三节 MFC中的编程

  Visual C++的MFC提供了CSocket类用来实现网络通信。下图给出了CSocket 类的继承关系。

 

  下面介绍VC++在Windows 95中实现Socket的 CSocket 类相关成员函数(这些成员函数实际上是从CAsyncSocket

类继承来的)的使用。

(1) BOOL Create( UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, long

lEvent = FD_READ |FD_WRITE|FD_OOB|FD_ACCEPT|FD_CONNECT| FD_CLOSE,LPCTSTR

lpszSocketAddress = NULL )

  该函数用来建立Socket。 其中,nSocketPort 为所选择的Socket 端口,一般要大于 1023, 如果该参数为0,

则由系统选定一端口,默认值为0 ;nSocketType 为套接字类型:SOCK_STREAM 表示为流套接字,SOCK_DGRAM

表示为数据报套接字,默认值为SOCK_STREAM ;lEvent 标识该Socket 要完成哪种工作,默认值为FD_READ|FD_WRITE|FD_OOB|

FD_ACCEPT|FD_CONNECT|FD_CLOSE ;lpszSockAddress 为网络地址信息结构指针,包含网络地址, 默认值为NULL 。

(2)BOOL Bind( UINT nSocketPort, LPCTSTR lpszSocketAddress = NULL )

  该函数的作用是将Socket 端口与网络地址连接起来。参数含义同上 。

(3)BOOL Listen( int nConnectionBacklog = 5 )

  该函数的作用是等待Socket请求。其中,nConnec-tionBacklog 表示等待队列的长度,默认值为最大值5 。

(4)virtual BOOL Accept( CAsyncSocket& rConnectedSocket, SOCKADDR* lpSockAddr =

NULL, int* lpSockAddrLen = NULL )

  该函数的作用是取得队列上第一个连接请求并建立一个具有与Socket相同特性的套接字。其中,rConnectedSocket 表示一个新的Socket 。

(5)BOOL Connect( LPCTSTR lpszHostAddress, UINT nHostPort )

  该函数的作用是提出请求。其中,lpszHostAddress 和 nHostPort 为接受请求进程的网络地址和Socket 端口号。

(6)virtual void Close( )

  该函数的作用是关闭该Socket 。

  利用CSocket类直接进行数据通信有两种方式:一种是利用CSocketFile 类和Archive 类去实现,另一种是利用CSocket的成员函数

Receive、Send、ReceiveFrom、SendTo、Listen 和 Accept 等来实现(这些成员函数实际上也是从CAsyncSocket

类继承的)。

  两种方法的实现步骤如下 :

  Server : Construct-> Creat-> Bind -> Listen-> Accept-> Send->Close ;

  Cilent : Construct ->Creat-> Connect-> Receive-> Close。

   下面就用VC++的代码分别介绍如何运用上述两种方法来实现Socket 编程。

  1、 利用CSocketFile类和Archive 类实现

  (1)服务器程序流程

  // 创建一个套接字对象

  CSocket sockSrvr;

  //为上述套接字对象创建一个套接字

  sockSrvr.Create(nPort);

  //开始侦听

  sockSrvr.Listen( );

  //创建一个新的套接字对象

  CSocket sockRecv;

  //接受连接

  sockSrvr.Accept( sockRecv );

// 创建文件对象

CSocketFile file(&sockRecv);

  //创建一个archive对象

  CArchive arIn(&file, CArchive::load);

  /*or*/_CArchive arOut(&file, CArchive::store);

  //使用archive对象传输数据

  arIn >> dwValue;

  /*or*/ arOut < < dwValue;

  (2)客户端程序流程

  //创建一个套接字对象

  CSocket sockClient;

  //为这个对象创建一个套接字

  sockClient.Create( );

  //寻找一个连接

  sockClient.Connect(strAddr, nPort);

  //创建一个文件对象

  CSocketFile file(&sockClient);

  //创建一个archive对象

  CArchive arIn(&file, CArchive::load);

  /*or*/_CArchive arOut(&file, CArchive::store);

  //使用这个对象传输数据

  arOut < < dwValue;

  /*or*/ arIn >> dwValue;

  上述程序中, nPort 是Socket 的端口号,strAddr 是该机器的IP地址(如202.197.1.3 或

FTP://RedAlert.com等),这两个变量在Server和Client中要一致。当Server进程运行至Listen

后便处于睡眠状态直到Client进程执行Connect 时才被唤醒,而后两个进程便开始传输数据了。

  2、利用CSocket的成员函数实现

  (1)服务器流程

  //套接字初始化

  if(!AfxSocketInit()){

   MessageBox("WindowsSocket initial failed!","Send",MB_ICONSTOP);

   Return;

  }

  // 创建两个套接字对象

  CSocket ChatSend,server;

  // 创建一个套接字

  if(!ChatSend.Create(nPort)) // nPort=1025

   MessageBox("SendSocket create failed!", "Send",MB_ICONSTOP);

  else{

   // 把本地地址给套接字

ChatSend.Bind(nProt,strAddr);

  // strAddr="202.196.111.1"

   // 开始侦听

   ChatSend.Listen();

   // 创建一个新的套接字并和他相连

   ChatSend.Accept(Server);

  }

  //发送一个CString 对象

  Server.SendTo(csSendText,csCounts,nPort,strAddr);

  // 关闭这两个套接字

  Server.Close();

  ChatSend.Close();

  (2)客户端程序流程

  // 套接字初始化

  if(!AfxSocketInit()){

   MessageBox("WindowsSocket initial failed!", "Receive",MB_ICONSTOP);

   return;

  }

  // 创建一个套接字对象

  CSocket ChatRecieve;

  // 创建一个套接字

  if(!ChatReceive.Create()){

   MessageBox("ReceiveSocket create failed!","Receive",MB_ICONSTOP);

   return;

  }

  else{

   // 创建一个对等套接字

   ChatReceive.Connect(strAddr,nPort);

  }

  //接受一个CString 对象

  ChatReceive.ReceiveFrom(csReceiveText,csCounts,strAddr,nPort);

  // 关闭套接字

  ChatReceive.Close();

  上述两个进程完成的工作是:由Server 进程发送一字符串,Client 进程接收。 strAddr 和 nPort 的含义与方法1 中的相同

;csSendText 和 csReceiveText

为发送与接收的字符串;csCounts为字串长度,这一长度在两个进程中要求接收长度小于或等于发送长度,否则会导致数据传输错误。另外,在程序中要加入头文件afxsock.h,

CSocket 类的有关说明均在afxsock.h 中。

方法1 适合于对多个不同类型数据的通信,方法2 适合于对字符串的通信,具体选用何种方法则取决于具体应用的需求。




Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=434804


<script src="http://localhost:82/PromoteIcon.aspx?id=434804" type="text/javascript"></script>[ 收藏到我的网摘]   jemmy发表于 2005年07月25日 22:39:00
href="http://blog.csdn.net/jemmy/Services/Pingback.aspx" rel="pingback" /> <script type="text/javascript">function hide(){showComment();}</script>


特别推荐:

没有评论。
<script language="javascript" type="text/javascript"> ad_width=468; ad_height=60; adcss=2; unionuser=19; ad_type='j'; count=5; </script><script language="javascript" src="http://tagegg.csdn.net/showads.js" type="text/javascript"></script><script language="JavaScript1.1" src="http://tagegg.csdn.net/a.aspx?action=displayad&unionuser=19&unionurl=http%3A%2F%2Fblog.csdn.net%2Fjemmy%2Farchive%2F2005%2F07%2F25%2F434804.aspx&adcss=2&ad_type=j&width=468&height=60&ad_color=&ad_color_border=&count=5" type="text/javascript"></script> <script language="javascript" src="/js/showgm.js" type="text/javascript"></script>

发表评论

  
大名:
网址:
校验码: 看不清,换一张
评论 
   
当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
<script src="http://www.csdn.net/common/counter.js" type="text/javascript"></script> id="myframe" border="0" name="myframe" src="http://www.csdn.net/ggmm/dd333.htm" frameborder="no" width="0" scrolling="no" height="0"> <script type="text/javascript"> </script> <script type="text/javascript"> </script>
<script language="javascript" type="text/javascript"> </script> 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值