轉自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,只是略微加工整理。本書的最終目的是想讓大家正確看待黑客技術,說明黑客技術並不是象許多媒體描述的那樣高深莫測。

  此書僅作拋磚引玉之用。

作者電子郵件地址:[email protected] 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 適合於對字符串的通信,具體選用何種方法則取決於具體應用的需求。

 

By tony

自由軟體愛好者~喜歡不斷的思考各種問題,有新的事物都會想去學習嘗試 做實驗並熱衷研究 沒有所謂頂天的技術 只有謙虛及不斷的學習 精進專業,本站主要以分享系統及網路相關知識、資源而建立。 Github http://stnet253.github.io

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料