#
引言
PowerDesigner支持UML1.3的所有圖包括用例圖、序列圖和類圖、活動圖表和組件圖表等,并全面支持UML2.0。改進了面向對象分析與設計(OOAD)分析方法并增強了與開發過程的集成。
PowerDesigner 能夠幫助您構建適應現代 IT 發展的傳統商務和電子商務系統,使用
Java 等面向對象的語言以及 XML 等新技術,以物理或虛擬的方式與我們的數據庫技術合并。我們的目標是根據您的需求,提供隨時隨地訪問信息、控制業務流程的能力,并通過計算機和最新技術賦予企業在當今任何市場上先拔頭籌的競爭優勢。
我們的分析方法和設計技術將會是多種多樣的,從業務流程建模,到 UML 面向對象分析和設計,以及傳統的關系建模等。本文將幫助您深入了解 UML 這項強大的技術,它可以幫助您的企業創建出高效的傳統商務和電子商務系統。
面向對象的分析
在您準備為企業作出系統和軟件投資前,必須首先了解企業的實際需求,明確所部署的技術將如何幫助您的企業獲取更大的成功。您可以使用 UML,借助用例圖、序列圖和活動圖來進行分析。這些圖表將幫助您規劃系統的范圍、動態性能、以及表現方式等。不必考慮實施細節,您希望獲得的只是按照您的需求而表現的系統性能。
用例圖(The Use Case Diagram)
UML 用例圖提供了一個系統環境的建模方式。它能夠幫助您確定系統/應用程序的外部和內部元素以及系統范圍。作為圖形建模模式,它在您需要與所收集的系統需求進行對話時也將有所幫助,對于研制成品的開發團隊來說,更是有著舉足輕重的重要性。對于企業的所有者,或第一次接觸該軟件產品的用戶也有很大的幫助作用。用例圖能夠以可視化的方式,表達系統如何滿足所收集的業務規則,以及特定的用戶需求等信息。
在項目后期,也能夠用到 UML 用例圖。您可以通過用例圖中定義的需求來協助測試項目的相關功能。您不僅可以驗證系統性能是否無錯誤(無崩潰或明顯的非邏輯響應),還可以驗證系統運行時是否按照要求,執行了指定命令。這樣,您可以測試系統是否完全滿足了要求,以確信成品可以投入生產——也就是說,它已完全滿足了用戶的需求。
只有確保滿足了合理、實用的各項需求,才能確保 IT 項目的更大成功。
點擊查看大圖
序列圖(The Sequence Diagram)
您可以使用 UML 序列圖細化需求并對設計元素進行鏈接。序列圖允許高層和低層對象間的交互文檔。該交互在角色(與用例圖中的角色相同)和類實例(運行于計算機內存中的技術對象和細節對象)之間顯示。
通過序列圖,您可以按照系統特定方案中事件(消息)的精確順序來描述隨時間變化的系統行為。使用序列圖進行用例分析并引導設計:您可以決定將對用例圖所定義的管理任務負責的系統對象類型,并決定哪種對象將管理系統內外的“會話”或通信。由于消息已從序列圖中抽出,您可以描述類和接口(我們最后要編譯和部署的代碼元素)所需的某些關鍵操作(方法)。
點擊查看大圖
活動圖(The Activity Diagram)
UML 活動圖設計用于幫助您了解系統中對象的動態變化。用于描述某一特定類或一組類如何協同工作。與序列圖有所不同,活動圖不是一系列與時間相關的通信,而是從一個任務到另一任務的控制轉移,同時指定誰(哪個對象)對發生的任務負責。
UML 活動圖也是業務流程的技術視圖。可對業務工作流進行分析或在“業務流程建模”工作后可獲得活動圖。
活動圖還可幫助構造系統內元素的詳細動態視圖(EJB 如何互操作等)。
點擊查看大圖
通過分析推動設計
通過分析模型可捕獲獨立于實施細節之外的系統意向和預期行為,與使用的語言、部署的應用程序服務器或使用的體系結構都沒有關系。但是,設計階段開始后,一切都發生了變化。您必須進入生產環境的細節并將軟件構建至特定的體系結構。設計是對系統的實施。
如果設計是由分析得到的,您可以更加確信所編寫的系統行為的正確性,確認所開發的成果將是一個按需求構建的系統。您將獲得高度成功——讓用戶得到所需要的系統。您還可以直接利用分析得出的信息而無需在設計過程中重新生成,從而縮減開發時間,由于不必“重新復制”任何工作,因此減少了人為錯誤。
通過分析,我們可獲得什么呢?通過用例圖可以發現對象并促進類和接口的創建。一個或更多類和接口可以實現一個角色,您可以在角色定義中直接創建類和接口。您還可以將角色鏈接到現有的類和接口,顯示如何使用一條代碼來滿足所分析的多個元素。
通過序列圖可以發現方法并促進類操作的創建。如果您需要向類發送消息,您可以調用該類的方法。序列圖中的消息可以用來自動創建操作或鏈接到現有操作。您可以通過鏈接跟蹤方法的功能,包括將哪些作為輸入內容和必須返回哪些內容等等。
設計所包含的內容
您已經知道要構建的內容,現在您需要表述如何構建。您需要確定業務邏輯所在的位置:可以置于應用程序服務器的 EJB 等組件中,也可以置于使用 VB 或 PowerBuilder 等語言、作為客戶端應用程序一部分的類或組件中,或者做為觸發器和過程內置于關系數據庫中。您需要根據需求做出一些選擇,包括擴展性、安全、性能和可訪問性等方面。
UML 類圖和組件圖將用于定義詳細的技術系統靜態結構。
類圖 (The Class Diagram)
UML 類圖、業務邏輯和所有支持結構一同被用于定義全部的代碼結構。既然類圖用來模擬開發中所維護的實際代碼,顯然它是 Java 或 PowerBuilder 等對象語言的概括性表述。您還可以使用 UML 類圖來概括 XML 中的復雜結構,令其更易于開發和理解。
可以從 UML 類圖上生成代碼。還可以在開發過程中編輯該代碼以完善、測試和部署最終運行的應用程序。由于 PowerDesigner 在對象語言和 UML 類圖之間具有 1:1 的映射功能,您還可以實施反向工程代碼,讀取源文件并創建新的類圖。您可以更深入地理解現有系統并簡化集成和維護工作。
點擊查看大圖
組件圖(The Component Diagram)
UML 組件圖將被用于在更大的黑匣視圖(Black Box View)中描述高級對象的定義和相關性。它仍然是一個設計模型,并且是代碼的直接概括。例如,一個 EJB 的組件標識直接鏈接到實施所必需的一系列類和接口,并將生成所需代碼來推動最終 bean 的開發。

組件圖比組件體系結構的代碼層視圖更容易理解和管理。還可以通過編寫組件接口的文檔來實現代碼的共享和反復使用,用戶無需(或很少)了解組件的實施細節即可在其他項目和系統中使用這些代碼。
右擊Customer EntityBean_CMP,選擇Create/Update Class Diagram,生成如下class diagram:
點擊查看大圖
循環疊代工程
世界不是一成不變的,您的 IT 項目也如此。在您了解需求,通過分析進行了設計,并構建了系統的某些元素后,必然還會遇到新的變化,如要更新定義,又或者現有用例圖中存在某些需要改正的錯誤,代碼在 IDE 和文本編輯器中被編輯以及數據庫被DBA 優化等。必須管理和掌握所有需要更改的細節,以確保所構建的系統能夠與業務需求保持一致。
往返工程的一個方案是當代碼在開發過程中被更改時,需要在類圖中反映出來。具體細節如下:
1. 創建類圖并將業務邏輯元素添加到模型中
2. 生成文件系統的應用程序代碼
3. 在 IDE 或文本編輯器中編輯代碼
4. 編輯設計,此時忽略在生成的代碼中所發生的更改
5. 對編輯內容實施反向工程,直到與現有類圖一致
6. 將設計過程中完成的工作與開發時編輯的內容同步(合并)
7. 生成新代碼,該代碼是設計代碼和開發人員更改代碼的總和
當對類圖進行了修改以反映新的設計內容時,應該使用同步/合并技術防止丟失開發人員的工作成果,同時允許設計人員接受或拒絕開發過程中所做的更改。這樣,PowerDesigner 令 IT 能夠完全控制體系結構,這正是制勝的關鍵。
PowerDesigner 的功能并不是僅限于此!現在設計模型已被更新,您可以將這些更改鏈接到分析中。有可能您在分析中發現了新的需求,可以將這一更改反映到設計中并編寫代碼。使用 PowerDesigner 中領先的 Compare/Merge 技術(在 September Blueprint 中討論過),您可以在開發周期的所有模型和階段中獲得真正的往返同步。
對象圖(Object Diagram)
與類圖一樣,對象圖也是一個 UML 靜態結構圖;它定義了系統在給定時刻具有的物理元素,而沒有具體考慮系統的動態活動。它與代碼一一對應,但與類圖不同,我們現在討論的是具體的分類器,而不是分類器定義。將對象圖描述為類實例圖可能最為合適。
對象圖的主要用途是進行分析。類圖中無法表示的類之間存在不確定的約束。我們將使用對象圖來記錄這些約束。而且,在我們查看所管理的具體類實例示例以闡明這些元素之間的交互作用關系時,對象圖還允許我們定義具體的“What if”場景。
以下內容適用于 OO 建模的初學者:分類器是抽象的對象結構定義。分類器可以告訴我們所管理的是什么類型的數據(屬性/成員表示數據元素)以及該分類器具有什么能力(操作/方法表示對象的行為)。實例是具體的分類器示例。假定定義一個名為 Customer 的類,該類具有 Name 屬性。類 Customer 的實例“Jane Doe”是姓名恰為“Jane Doe”的客戶。實例通常具有比分類器更豐富的含義,這是因為分類器表示某種級別的概述。收集某個分類器的若干個實例或示例可能有助于您理解其用途并更好地使用它。
因此,對象圖是類圖的具體形式,表示類實例樣本,并且顯示了鍵值和關系。例如,CustomerBean 類具有以下客戶實例:該客戶的 ID 為 52271,姓名為“John Doe”。該客戶實例與三個訂單實例(三份訂單)相關,訂單編號分別為122047、122103 和 122399。

協作圖(Collaboration Diagram)
協作圖和序列圖非常相似。實際上,序列圖和協作圖可以有效地交替使用,并可以簡便的相互轉換。其區別在于用戶閱讀和理解的方式不同。序列圖具有很好的層次性,并且圍繞時間構造。協作圖則主要是圍繞對象結構構造。通過在圖中對消息進行編號可以表示消息的順序。采用這種方式時,即使圖的結構不是基于時間的,也將保持定時關系。
協作圖借助于系統中元素或對象之間的交互作用,表示系統的動態方面,即在一段時間內的表現方式。它通過表示系統的靜態結構來對類圖和對象圖進行補充,但不是借助于基于結構的關系,而是在系統對象之間傳遞交互作用“消息”。
構造協作圖時還可以在概念級測試靜態模型。在類圖中定義了類實例,這些類實例之間的交互作用定義了一個具體的使用方案以及將在這些元素之間發生的內部通訊。我們還可以使用其他角色來表示系統的外部作用者和內部使用者,如用例圖。
例如,我們可以建立一個訂單輸入系統,以供客戶和銷售代表使用。客戶通過創建新訂單與該系統交互作用。訂單對象與銷售對象之間進行對話,該對話由鏈接消息表示,在此情況下,只有兩個消息:一個是來自 Orders 類的訂單請求,一個是來自 Sales 類的訂單確認。對一個鏈接上的消息數量沒有限制。我們在此討論的對話以一個訂單請求開始,然后是對該訂單的確認。

適用性
協作圖對于設計人員尤其重要,因為它闡明了對象的作用。您可以在序列圖之前構造協作圖(如果您計劃構造這兩個圖),但通常是在完成類圖之后構造協作圖以說明從類中導出的對象之間的交互作用。可以使用一個或多個協作圖來實現一個用例,或者將復雜行為分割成多個邏輯子行為。
狀態圖(Statechart Diagram)
狀態圖(也稱為狀態機)描述了特定類或組件在其整個生命周期中不斷變化時的行為。該圖顯示是什么觸發了從一種狀態向另一種狀態的轉換,以及在該類上調用哪些操作以提供該狀態的行為或觸發這種轉換。例如,訂單在被創建時處于初始狀態。在客戶確認訂單正確后,訂單將進入確認狀態。在發貨以后,訂單需要從確認狀態進入發貨狀態。

因此,每當一個類在其生命周期的不同階段具有不同的可用選項(不同的有效行為)時,您都可以使用狀態圖來將這些規則和條件建模。生命周期中的每個階段都是該對象的一種狀態,而每個改變狀態的觸發器都代表從一種狀態到另一種狀態的轉換。可以根據需要從某個狀態轉換到任意多個其它狀態,也可以從其它多個狀態進入某個狀態。
子狀態圖
若要保持狀態圖簡單和易讀,您可能發現所定義的一個或多個狀態實際上涉及到更為復雜的行為,以至于它本身就可以定義為一個狀態圖。此時,與向主圖中添加大量復雜細節的做法相比,更好的做法是將這個單獨的狀態分解為多個子狀態,進而組成一個輔助圖,以定義父狀態的更為復雜的內部行為。
部署圖(Deployment Diagram)
部署圖可以幫助我們確定所有代碼元素在服務器、工作站和數據庫中的存放位置。有的節點需要依賴硬件或軟件框來運行部分業務邏輯。這些節點交互作用以演示我們開發的多個計算機和系統是如何交互作用和集成的。節點中包含將部署到數據庫、應用程序或 Web 服務器中的組件實例。
部署圖用于將組件實際部署到服務器中。通過定義希望組件運行的位置,我們可以快捷的映射、部署和管理分布在客戶端應用程序和應用程序服務器端組件之間的業務邏輯或數據庫端服務器邏輯。以下是要管理的物理體系結構的 1:1 模型。
例如,假定我們已決定實現兩個 Enterprise Java Beans,并且在應用程序服務器上運行它們。下圖顯示了單個節點以及該節點內的兩個組件(每個 EJB 一個組件)。我們可以看出 EmployeeBean 依賴于同一應用程序服務器內的 CustomerBean。

結論
在我們借助用例圖、序列圖、活動圖、類圖和組件圖完成基本 UML 建模時,我們將需要其它一些工具來定義有關系統中某些特定元素的詳細信息。我們可能希望在對象圖中使用精確的示例來表示對象的結構,或者借助于狀態圖來更多地了解在其內部具有多個復雜狀態的類的行為。我們需要使用協作圖從結構角度而不是從時間角度來考察系統組件之間的交互作用。最后,還需要使用部署圖來顯示所有系統組件在運行環境中的物理硬件或服務器中所處的位置,從而更詳盡的了解分布式體系結構的使用方式。
我們期待自己成為一個優秀的軟件模型設計者,但是,要怎樣做,又從哪里開始呢?
將下列原則應用到你的軟件工程中,你會獲得立桿見影的成果。
1. 人遠比技術重要
你開發軟件是為了供別人使用,沒有人使用的軟件只是沒有意義的數據的集合而已。許多在軟件方面很有成就的行家在他們事業的初期卻表現平平,因為他們那時侯將主要精力都集中在技術上。顯然,構件(components),EJB(Enterprise Java Beans)和代理(agent)是很有趣的東西。但是對于用戶來說,如果你設計的軟件很難使用或者不能滿足他們的需求,后臺用再好的技術也于事無補。多花點時間到軟件需求和設計一個使用戶能很容易理解的界面上。
2. 理解你要實現的東西
好的軟件設計人員把大多數時間花費在建立系統模型上,偶爾寫一些源代碼,但那只不過是為了驗證設計過程中所遇到的問題。這將使他們的設計方案更加可行。
3. 謙虛是必須的品格
你不可能知道一切,你甚至要很努力才能獲得足夠用的知識。軟件開發是一項復雜而艱巨的工作,因為軟件開發所用到的工具和技術是在不斷更新的。而且,一個人也不可能了解軟件開發的所有過程。在日常生活中你每天接觸到的新鮮事物可能不會太多。但是對于從事軟件開發的人來說,每天可以學習很多新東西(如果愿意的話)。
4. 需求就是需求
如果你沒有任何需求,你就不要動手開發任何軟件。成功的軟件取決于時間(在用戶要求的時間內完成)、預算和是否滿足用戶的需求。如果你不能確切知道用戶需要的是什么,或者軟件的需求定義,那么你的工程注定會失敗。
5. 需求其實很少改變,改變的是你對需求的理解
Object ToolSmiths公司(www.objecttoolsmiths.com)的Doug Smith常喜歡說:“分析是一門科學,設計是一門藝術”。他的意思是說在眾多的“正確”分析模型中只存在一個最“正確”分析模型可以完全滿足解決某個具體問題的需要(我理解的意思是需求分析需要一絲不茍、精確的完成,而設計的時候反而可以發揮創造力和想象力 - 譯者注)。
如果需求經常改動,很可能是你沒有作好需求分析,并不是需求真的改變了。
你可以抱怨用戶不能告訴你他們想得到什么,但是不要忘記,收集需求信息是你工作。
你可以說是新來的開發人員把事情搞得一團糟,但是,你應該確定在工程的第一天就告訴他們應該做什么和怎樣去做。
如果你覺得公司不讓你與用戶充分接觸,那只能說明公司的管理層并不是真正支持你的項目。
你可以抱怨公司有關軟件工程的管理制度不合理,但你必須了解大多同行公司是怎么做的。
你可以借口說你們的競爭對手的成功是因為他們有了一個新的理念,但是為什么你沒先想到呢?
需求真正改變的情況很少,但是沒有做好需求分析工作的理由卻很多。
6. 經常閱讀
在這個每日都在發生變化的產業中,你不可能在已取得的成就上陶醉太久。
每個月至少讀2、3本專業雜志或者1本專業書籍。保持不落伍需要付出很多的時間和金錢,但會使你成為一個很有實力的競爭者。
7. 降低軟件模塊間的耦合度
高耦合度的系統是很難維護的。一處的修改引起另一處甚至更多處的變動。
你可以通過以下方法降低程序的耦合度:隱藏實現細節,強制構件接口定義,不使用公用數據結構,不讓應用程序直接操作數據庫(我的經驗法則是:當應用程序員在寫SQL代碼的時候,你的程序的耦合度就已經很高了)。
耦合度低的軟件可以很容易被重用、維護和擴充。
8. 提高軟件的內聚性
如果一個軟件的模塊只實現一個功能,那么該模塊具有高內聚性。高內聚性的軟件更容易維護和改進。
判斷一個模塊是否有高的內聚性,看一看你是否能夠用一個簡單的句子描述它的功能就行了。如果你用了一段話或者你需要使用類似“和”、“或”等連詞,則說明你需要將該模塊細化。
只有高內聚性的模塊才可能被重用。
9. 考慮軟件的移植性
移植是軟件開發中一項具體而又實際的工作,不要相信某些軟件工具的廣告宣傳(比如java 的宣傳口號write once run many ? 譯者注)。
即使僅僅對軟件進行常規升級,也要把這看得和向另一個操作系統或數據庫移植一樣重要。
記得從16位Windows移植到32位windows的“樂趣”嗎 ?當你使用了某個操作系統的特性,如它的進程間通信(IPC)策略,或用某數據庫專有語言寫了存儲過程。你的軟件和那個特定的產品結合度就已經很高了。
好的軟件設計者把那些特有的實現細節打包隱藏起來,所以,當那些特性該變的時候,你的僅僅需要更新那個包就可以了。
10. 接受變化
這是一句老話了:唯一不變的只有變化。
你應該將所有系統將可能發生的變化以及潛在需求記錄下來,以便將來能夠實現(參見“Architecting for Change”,Thinking Objectively, May 1999)
通過在建模期間考慮這些假設的情況,你就有可能開發出足夠強壯且容易維護的軟件。設計強壯的軟件是你最基本的目標。
11. 不要低估對軟件規模的需求
Internet 帶給我們的最大的教訓是你必須在軟件開發的最初階段就考慮軟件規模的可擴充性。
今天只有100人的部門使用的應用程序,明天可能會被有好幾萬人的組織使用,下月,通過因特網可能會有幾百萬人使用它。
在軟件設計的初期,根據在用例模型中定義的必須支持的基本事務處理,確定軟件的基本功能。然后,在建造系統的時候再逐步加入比較常用的功能。
在設計的開始考慮軟件的規模需求,避免在用戶群突然增大的情況下,重寫軟件。
12. 性能僅僅是很多設計因素之一
關注軟件設計中的一個重要因素--性能,這好象也是用戶最關心的事情。一個性能不佳的軟件將不可避免被重寫。
但是你的設計還必須具有可靠性,可用性,便攜性和可擴展性。你應該在工程開始就應該定義并區分好這些因素,以便在工作中恰當使用。性能可以是,也可以不是優先級最高的因素,我的觀點是,給每個設計因素應有的考慮。
13. 管理接口
“UML User Guide”(Grady Booch,Ivar Jacobson和Jim Rumbaugh ,Addison Wesley, 1999)中指出,你應該在開發階段的早期就定義軟件模塊之間的接口。
這有助于你的開發人員全面理解軟件的設計結構并取得一致意見,讓各模塊開發小組相對獨立的工作。一旦模塊的接口確定之后,模塊怎樣實現就不是很重要了。
從根本上說,如果你不能夠定義你的模塊“從外部看上去會是什么樣子”,你肯定也不清楚模塊內要實現什么。
14. 走近路需要更長的時間
在軟件開發中沒有捷徑可以走。
縮短你的在需求分析上花的時間,結果只能是開發出來的軟件不能滿足用戶的需求,必須被重寫。
在軟件建模上每節省一周,在將來的編碼階段可能會多花幾周時間,因為你在全面思考之前就動手寫程序。
你為了節省一天的測試時間而漏掉了一個bug,在將來的維護階段,可能需要花幾周甚至幾個月的時間去修復。與其如此,還不如重新安排一下項目計劃。
避免走捷徑,只做一次但要做對(do it once by doing it right)。
15. 別信賴任何人
產品和服務銷售公司不是你的朋友,你的大部分員工和高層管理人員也不是。
大部分產品供應商希望把你牢牢綁在他們的產品上,可能是操作系統,數據庫或者某個開發工具。
大部分的顧問和承包商只關心你的錢并不是你的工程(停止向他們付款,看一看他們會在周圍呆多長時間)。
大部分程序員認為他們自己比其他人更優秀,他們可能拋棄你設計的模型而用自己認為更好的。
只有良好的溝通才能解決這些問題。
要明確的是,不要只依靠一家產品或服務提供商,即使你的公司(或組織)已經在建模、文檔和過程等方面向那個公司投入了很多錢。
16. 證明你的設計在實踐中可行
在設計的時候應當先建立一個技術原型, 或者稱為“端到端”原型。以證明你的設計是能夠工作的。
你應該在開發工作的早期做這些事情,因為,如果軟件的設計方案是不可行的,在編碼實現階段無論采取什么措施都于事無補。技術原型將證明你的設計的可行性,從而,你的設計將更容易獲得支持。
17. 應用已知的模式
目前,我們有大量現成的分析和設計模式以及問題的解決方案可以使用。
一般來說,好的模型設計和開發人員,都會避免重新設計已經成熟的并被廣泛應用的東西。http://www.ambysoft.com/processPatternsPage.html收藏了許多開發模式的信息。
18. 研究每個模型的長處和弱點
目前有很多種類的模型可以使用,如下圖所示。用例捕獲的是系統行為需求,數據模型則描述支持一個系統運行所需要的數據構成。你可能會試圖在用例中加入實際數據描述,但是,這對開發者不是非常有用。同樣,數據模型對描述軟件需求來說是無用的。每個模型在你建模過程中有其相應的位置,但是,你需要明白在什么地方,什么時候使用它們。

19. 在現有任務中應用多個模型
當你收集需求的時候,考慮使用用例模型,用戶界面模型和領域級的類模型。
當你設計軟件的時候,應該考慮制作類模型,順序圖、狀態圖、協作圖和最終的軟件實際物理模型。
程序設計人員應該慢慢意識到,僅僅使用一個模型而實現的軟件要么不能夠很好地滿足用戶的需求,要么很難擴展。
20. 教育你的聽眾
你花了很大力氣建立一個很成熟的系統模型,而你的聽眾卻不能理解它們,甚至更糟-連為什么要先建立模型都不知道。那么你的工作是毫無意義的。
教給你開發人員基本的建模知識;否則,他們會只看看你畫的漂亮圖表,然后繼續編寫不規范的程序。
另外, 你還需要告訴你的用戶一些需求建模的基礎知識。給他們解釋你的用例(uses case)和用戶界面模型,以使他們能夠明白你要表達地東西。當每個人都能使用一個通用的設計語言的時候(比如UML-譯者注),你的團隊才能實現真正的合作。
21. 帶工具的傻瓜還是傻瓜
你給我CAD/CAM工具,請我設計一座橋。但是,如果那座橋建成的話,我肯定不想當第一個從橋上過的人,因為我對建筑一竅不通。
使用一個很優秀的CASE工具并不能使你成為一個建模專家,只能使你成為一個優秀CASE工具的使用者。成為一個優秀的建模專家需要多年的積累,不會是一周針對某個價值幾千美元工具的培訓。一個優秀的CASE工具是很重要,但你必須學習使用它,并能夠使用它設計它支持的模型。
22. 理解完整的過程
好的設計人員應該理解整個軟件過程,盡管他們可能不是精通全部實現細節。
軟件開發是一個很復雜的過程,還記得《object-oriented software process》第36頁的內容嗎?除了編程、建模、測試等你擅長工作外,還有很多工作要做。
好的設計者需要考慮全局。必須從長遠考慮如何使軟件滿足用戶需要,如何提供維護和技術支持等。
23. 常做測試,早做測試
如果測試對你的軟件來說是無所謂的,那么你的軟件多半也沒什么必要被開發出來。
建立一個技術原型供技術評審使用,以檢驗你的軟件模型。
在軟件生命周期中,越晚發現的錯誤越難修改,修改成本越昂貴。盡可能早的做測試是很值得的。
24. 把你的工作歸檔
不值得歸檔的工作往往也不值得做。歸檔你的設想,以及根據設想做出的決定;歸檔軟件模型中很重要但不很明顯的部分。 給每個模型一些概要描述以使別人很快明白模型所表達的內容。
25. 技術會變,基本原理不會
如果有人說“使用某種開發語言、某個工具或某某技術,我們就不需要再做需求分析,建模,編碼或測試”。不要相信,這只說明他還缺乏經驗。拋開技術和人的因素,實際上軟件開發的基本原理自20世紀70年代以來就沒有改變過。你必須還定義需求,建模,編碼,測試,配置,面對風險,發布產品,管理工作人員等等。
軟件建模技術是需要多年的實際工作才能完全掌握的。好在你可以從我的建議開始,完善你們自己的軟件開發經驗。
以雞湯開始,加入自己的蔬菜。然后,開始享受你自己的豐盛晚餐吧。
最近同事給我介紹Screen 命令,真是不錯。以前為了讓程序在脫離終端的情況下運行,要么讓它在后臺運行,要么使用nohup運行,但是如果需要交互的程序就麻煩了。例如,你需要使用scp拷貝,需要輸入密碼,而且數據量很大,需要很長時間。遇到過的人就知道痛苦了。
有了screen,一切都簡單了。這里把一篇介紹的文章轉貼過來,使用還是很方便的。
前言
screen 是什么
根據其man介紹,screen是個多元化多功能的全屏窗口管理器,每個虛擬終端都可以為你提供DEC VT100 terminal的功能, 也許你會問:DEC VT100 terminal又是什么?如果你登陸過某些字符界面的BBS,或許你會記得在注冊時,其要求你輸入你的終端機型別,而一般預設就是我們剛剛提到的DEC VT100 termina了.另外screen還附加提供了比如SO 6429 (ECMA 48, ANSI X3.64) and ISO 2022 standards的操作功能.
screen 可以做些什么
如果在以前或許screen 是你登陸 bbs 站的好伴侶,但是相信現在大家都是直接登陸圖形界面的也就是WEB界面的BBS.當你正在登陸多個BBS而又不想在多個窗口之間切換.那么screen就可以幫你的忙了。
當然screen可不是專為BBS服務, 它可以讓你只需要打開一個終端窗口就可以地處理很多的(進程)事情,舉個例子:你正在shell上編寫某個程序,碰巧你又需要重新啟動某個服務,同時還要 FTP上傳個大文件,這個時候就可以使用調用screen,只需要按下3個鍵就可以無須用鼠標在3個窗口間切換.又或者你使用PUTTY等工具登陸到服務器,不想在退出時關閉當前的進程,比如你正在復制文件等.這個時候就可以利用screen讓你復制文件這個前臺進程享受后臺進程的"待遇"。
正是因為screen的種種實用功能 ,已經成為不少*unix玩家的必備利器,讓*unix的日常操作管理更加方便。
screen使用
使用screen非常簡易.只需在SHELL鍵入screen,便可打開一個screen session。
而在每個screen session 下,所有命令都以 ctrl+a(C-a) 開始。
現在讓我來簡單介紹基本的命令
C-a c -> Create,開啟新的 window
C-a n -> Next,切換到下個 window
C-a p -> Previous,前一個 window
C-a C-a -> Other,在兩個 window 間切換
C-a w -> Windows,列出已開啟的 windows 有那些
C-a 0 -> 切換到第 0 個 window
C-a 1..9 -> 切換到第 1..9 個window
C-a a -> 發出 C-a,在 emacs, ve, bash, tcsh 下可移到行首
C-a t -> Time,顯示當前時間,和系統的 load
C-a K(大寫) -> kill window,強行關閉當前的 window
C-a [ -> 進入 copy mode,在 copy mode 下可以回滾、搜索、
復制就像用使用 vi 一樣
C-b Backward,PageUp
C-f Forward,PageDown
H(大寫) High,將光標移至左上角
L Low,將光標移至左下角
0 移到行首
$ 行末
w forward one word,以字為單位往前移
b backward one word,以字為單位往后移
Space 第一次按為標記區起點,第二次按為終點
Esc 結束 copy mode
C-a ] -> Paste,把剛剛在 copy mode 選定的內容貼上
C-a ? -> Help,顯示簡單說明
C-a d -> detach,將目前的 screen session (可能含有多個 windows)
丟到后臺執行 當按了 C-a d 把 screen session detach 掉后,會回到還沒進 screen 時的狀態,此時在 screen session 里每個 window 內運行的 process (無論是前臺/后臺)都在繼續執行,即使 logout 也不影響。
下次 login 進來時:
screen -ls -> 顯示所有的 screen sessions
screen -r [keyword] -> 選擇一個screen session 恢復對話
若 screen -ls 里有 Attached sessions:
screen -d [keyword] -> 強制 detach,以便「接手」過來
實例
說明看了那么多,讓我們用一個實際例子來結束我們今天的學習。
在我們開啟一個screen后,然后使用joe編輯一個文件,之后因為臨時需要離開這時就可以運行Ctrl+a d,顯示如下:
[becks@ec-base becks]$ screen
[detached]
這個時候當我們運行ps -e 可以看到pts/2這個我剛剛運行的screen正在運行joe
6264 pts/2 00:00:00 bash
6354 pts/2 00:00:00 joe
而當我們回來后想恢復這個session,只需要鍵入screen -r,而當你有多個session時候,系統將提示你選擇一個,如下:
[becks@ec-base becks]$ screen -r
There are several suitable screens on:
6263.pts-1.ec-base (Detached)
6382.pts-1.ec-base (Detached)
Type "screen [-d] -r [pid.]tty.host" to resume one of them.
輸入該session的pid進行恢復
[becks@becks becks]$ screen -r 6263
想退出screen的session,和退出shell一樣,只需要鍵入exit命令,成功退出后將有以下提示
[screen is terminating]
screen的簡單用法就介紹到這里,更多的功能和應有請讀者參考MAN自行研究.
我本想把發送和接收分開作為兩部分,但是最后我決定只略微解釋一下 FD_READ ,留下更多的時間來說明更復雜的 FD_WRITE , FD_READ 事件非常容易掌握. 當有數據發送過來時, WinSock 會以 FD_READ 事件通知你, 對于每一個 FD_READ 事件, 你需要像下面這樣調用 recv() :
int bytes_recv = recv(wParam, &data, sizeof(data), 0);
基本上就是這樣, 別忘了修改上面的 wParam. 還有, 不一定每一次調用 recv() 都會接收到一個完整的數據包, 因為數據可能不會一次性全部發送過來. 所以在開始處理接收到的數據之前, 最好對接收到的字節數 ( 即 recv() 的返回值) 進行判斷, 看看是否收到的是一個完整的數據包.
FD_WRITE 相對來說就麻煩一些. 首先, 當你建立了一個連接時, 會產生一個 FD_WRITE 事件. 但是如果你認為在收到 FD_WRITE 時調用 send() 就萬事大吉, 那就錯了. FD_WRITE 事件只在發送緩沖區有多出的空位, 可以容納需要發送的數據時才會觸發.
上面所謂的發送緩沖區,是指系統底層提供的緩沖區. send() 先將數據寫入到發送緩沖區中, 然后通過網絡發送到接收端. 你或許會想, 只要不把發送緩沖區填滿, 讓發送緩沖區保持足夠多的空位容納需要發送的數據, 那么你就會源源不斷地收到 FD_WRITE 事件了. 嘿嘿, 錯了.上面只是說 FD_WRITE 事件在發送緩沖區有多出的空位時會觸發, 但不是在有足夠的空位時觸發, 就是說你得先把發送緩沖區填滿.
通常的辦法是在一個無限循環中不斷的發送數據, 直到把發送緩沖區填滿. 當發送緩沖區被填滿后, send() 將會返回 SOCKET_ERROR , WSAGetLastError() 會返回 WSAWOULDBLOCK . 如果當前這個 SOCKET 處于阻塞(同步)模式, 程序會一直等待直到發送緩沖區空出位置然后發送數據; 如果SOCKET是非阻塞(異步)的,那么你就會得到 WSAWOULDBLOCK 錯誤. 于是只要我們首先循環調用 send() 直到發送緩沖區被填滿, 然后當緩沖區空出位置來的時候, 系統就會發出FD_WRITE事件. 有沒有想過我能指出這一點來是多么不容易, 你可真走運. 下面是一個處理 FD_WRITE 事件的例子.
case FD_WRITE: // 可以發送數據了
{
// 進入無限循環
while(TRUE)
{
// 從文件中讀取數據, 保存到 packet.data 里面.
in.read((char*)&packet.data, MAX_PACKET_SIZE);
// 發送數據
if (send(wparam, (char*)(&packet), sizeof(PACKET), 0) == SOCKET_ERROR)
{
if (WSAGetLastError() == WSAEWOULDBLOCK)
{
// 發送緩沖區已經滿了, 退出循環.
break;
}
else // 其他錯誤
{
// 顯示出錯信息然后退出.
CleanUp();
return(0);
}
}
}
} break;
看到了吧, 實現其實一點也不困難. 你只是弄混了一些概念而已. 使用這樣的發送方式, 在發送緩沖區變滿的時候就可以退出循環. 然后, 當緩沖區空出位置來的時候, 系統會觸發另外一個 FD_WRITE 事件, 于是你就可以繼續發送數據了.
在你開始使用新學到的知識之前, 我還想說明一下 FD_WRITE 事件的使用時機. 如果你不是一次性發送大批量的數據的話, 就別想著使用 FD_WRITE 事件了, 原因很簡單 - 如果你寄期望于在收到 FD_WRITE 事件時發送數據, 但是卻又不能發送足夠的數據填滿發送緩沖區, 那么你就只能收到連接剛剛建立時觸發的那一次 FD_WRITE - 系統不會觸發更多的 FD_WRITE 了. 所以當你只是發送盡可能少的數據的時候, 就忘掉 FD_WRITE 機制吧, 在任何你想發送數據的時候直接調用 send() .
結論
這是我寫過的最長的一篇文章. 我也曾試圖盡可能把它寫短一些來吸引你的注意力, 但是有太多的內容要包括. 在剛剛使用異步SOCKET 時, 如果你沒有正確地理解它, 真的會把自己搞胡涂. 我希望我的文章教會了你如何使用它們. ___________________________________
這是我在 GOOGLE 上搜到的一篇文章中的一部分. 雖然原作者的部分觀點似乎并不正確, 但是文章寫得很易懂. 其實, 如果你想收到 FD_WRITE事件而你又無法先填滿發送緩沖區, 可以調用 WSAAsyncSelect( ..., FD_WRITE ). 如果當前發送緩沖區有空位, 系統會馬上給你發 FD_WRITE 事件.
FD_WRITE 消息, MFC 的 CAsyncSocket 類將其映射為 OnSend() 函數. FD_READ 消息, 被映射為 OnReceive() 函數.
Socket(套接字)
◆先看定義:
typedef unsigned int u_int;
typedef u_int SOCKET;
◆Socket相當于進行網絡通信兩端的插座,只要對方的Socket和自己的Socket有通信聯接,雙方就可以發送和接收數據了。其定義類似于文件句柄的定義。
◆Socket有五種不同的類型:
1、流式套接字(stream socket)
定義:
#define SOCK_STREAM 1?
流式套接字提供了雙向、有序的、無重復的以及無記錄邊界的數據流服務,適合處理大量數據。它是面向聯結的,必須建立數據傳輸鏈路,同時還必須對傳輸的數據進行驗證,確保數據的準確性。因此,系統開銷較大。
2、 數據報套接字(datagram socket)
定義:
#define SOCK_DGRAM 2?
數據報套接字也支持雙向的數據流,但不保證傳輸數據的準確性,但保留了記錄邊界。由于數據報套接字是無聯接的,例如廣播時的聯接,所以并不保證接收端是否正在偵聽。數據報套接字傳輸效率比較高。
3、原始套接字(raw-protocol interface)
定義:
#define SOCK_RAW 3?
原始套接字保存了數據包中的完整IP頭,前面兩種套接字只能收到用戶數據。因此可以通過原始套接字對數據進行分析。
其它兩種套接字不常用,這里就不介紹了。
◆Socket開發所必須需要的文件(以WinSock V2.0為例):
頭文件:Winsock2.h
庫文件:WS2_32.LIB
動態庫:W32_32.DLL
一些重要的定義
1、數據類型的基本定義:這個大家一看就懂。
typedef unsigned char u_char;
typedef unsigned short u_short;
typedef unsigned int u_int;
typedef unsigned long u_long;
2、 網絡地址的數據結構,有一個老的和一個新的的,請大家留意,如果想知道為什么,
請發郵件給Bill Gate。其實就是計算機的IP地址,不過一般不用用點分開的IP地
址,當然也提供一些轉換函數。
◆ 舊的網絡地址結構的定義,為一個4字節的聯合:
struct in_addr {
union {
struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { u_short s_w1,s_w2; } S_un_w;
u_long S_addr;
} S_un;
#define s_addr S_un.S_addr /* can be used for most tcp & ip code */
//下面幾行省略,反正沒什么用處。
};
其實完全不用這么麻煩,請看下面:
◆ 新的網絡地址結構的定義:
非常簡單,就是一個無符號長整數 unsigned long。舉個例子:IP地址為127.0.0.1的網絡地址是什么呢?請看定義:
#define INADDR_LOOPBACK 0x7f000001
3、 套接字地址結構
(1)、sockaddr結構:
struct sockaddr {
u_short sa_family; /* address family */
char sa_data[14]; /* up to 14 bytes of direct address */
};
sa_family為網絡地址類型,一般為AF_INET,表示該socket在Internet域中進行通信,該地址結構隨選擇的協議的不同而變化,因此一般情況下另一個與該地址結構大小相同的sockaddr_in結構更為常用,sockaddr_in結構用來標識TCP/IP協議下的地址。換句話說,這個結構是通用socket地址結構,而下面的sockaddr_in是專門針對Internet域的socket地址結構。
(2)、sockaddr_in結構
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
sin _family為網絡地址類型,必須設定為AF_INET。sin_port為服務端口,注意不要使用已固定的服務端口,如HTTP的端口80等。如果端口設置為0,則系統會自動分配一個唯一端口。sin_addr為一個unsigned long的IP地址。sin_zero為填充字段,純粹用來保證結構的大小。
◆ 將常用的用點分開的IP地址轉換為unsigned long類型的IP地址的函數:
unsigned long inet_addr(const char FAR * cp )
用法:
unsigned long addr=inet_addr("192.1.8.84")
◆ 如果將sin_addr設置為INADDR_ANY,則表示所有的IP地址,也即所有的計算機。
#define INADDR_ANY (u_long)0x00000000
4、 主機地址:
先看定義:
struct hostent {
char FAR * h_name; /* official name of host */
char FAR * FAR * h_aliases; /* alias list */
short h_addrtype; /* host address type */
short h_length; /* length of address */
char FAR * FAR * h_addr_list; /* list of addresses */
#define h_addr h_addr_list[0] /* address, for backward compat */
};
h_name為主機名字。
h_aliases為主機別名列表。
h_addrtype為地址類型。
h_length為地址類型。
h_addr_list為IP地址,如果該主機有多個網卡,就包括地址的列表。
另外還有幾個類似的結構,這里就不一一介紹了。
5、 常見TCP/IP協議的定義:
#define IPPROTO_IP 0?
#define IPPROTO_ICMP 1?
#define IPPROTO_IGMP 2?
#define IPPROTO_TCP 6?
#define IPPROTO_UDP 17?
#define IPPROTO_RAW 255?
具體是什么協議,大家一看就知道了。
套接字的屬性
為了靈活使用套接字,我們可以對它的屬性進行設定。
1、 屬性內容:
//允許調試輸出
#define SO_DEBUG 0x0001 /* turn on debugging info recording */
//是否監聽模式
#define SO_ACCEPTCONN 0x0002 /* socket has had listen() */
//套接字與其他套接字的地址綁定
#define SO_REUSEADDR 0x0004 /* allow local address reuse */
//保持連接
#define SO_KEEPALIVE 0x0008 /* keep connections alive */
//不要路由出去
#define SO_DONTROUTE 0x0010 /* just use interface addresses */
//設置為廣播
#define SO_BROADCAST 0x0020 /* permit sending of broadcast msgs */
//使用環回不通過硬件
#define SO_USELOOPBACK 0x0040 /* bypass hardware when possible */
//當前拖延值
#define SO_LINGER 0x0080 /* linger on close if data present */
//是否加入帶外數據
#define SO_OOBINLINE 0x0100 /* leave received OOB data in line */
//禁用LINGER選項
#define SO_DONTLINGER (int)(~SO_LINGER)
//發送緩沖區長度
#define SO_SNDBUF 0x1001 /* send buffer size */
//接收緩沖區長度
#define SO_RCVBUF 0x1002 /* receive buffer size */
//發送超時時間
#define SO_SNDTIMEO 0x1005 /* send timeout */
//接收超時時間
#define SO_RCVTIMEO 0x1006 /* receive timeout */
//錯誤狀態
#define SO_ERROR 0x1007 /* get error status and clear */
//套接字類型
#define SO_TYPE 0x1008 /* get socket type */
2、 讀取socket屬性:
int getsockopt(SOCKET s, int level, int optname, char FAR * optval, int FAR * optlen)
s為欲讀取屬性的套接字。level為套接字選項的級別,大多數是特定協議和套接字專有的。如IP協議應為 IPPROTO_IP。
optname為讀取選項的名稱
optval為存放選項值的緩沖區指針。
optlen為緩沖區的長度
用法:
int ttl=0; //讀取TTL值
int rc = getsockopt( s, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl));
//來自MS platform SDK 2003
3、 設置socket屬性:
int setsockopt(SOCKET s,int level, int optname,const char FAR * optval, int optlen)
s為欲設置屬性的套接字。
level為套接字選項的級別,用法同上。
optname為設置選項的名稱
optval為存放選項值的緩沖區指針。
optlen為緩沖區的長度
用法:
int ttl=32; //設置TTL值
int rc = setsockopt( s, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl));
套接字的使用步驟
1、啟動Winsock:對Winsock DLL進行初始化,協商Winsock的版本支持并分配必要的
資源。(服務器端和客戶端)
int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData )
wVersionRequested為打算加載Winsock的版本,一般如下設置:
wVersionRequested=MAKEWORD(2,0)
或者直接賦值:wVersionRequested=2
LPWSADATA為初始化Socket后加載的版本的信息,定義如下:
typedef struct WSAData {
WORD wVersion;
WORD wHighVersion;
char szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYS_STATUS_LEN+1];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpVendorInfo;
} WSADATA, FAR * LPWSADATA;
如果加載成功后數據為:
wVersion=2表示加載版本為2.0。
wHighVersion=514表示當前系統支持socket最高版本為2.2。
szDescription="WinSock 2.0"
szSystemStatus="Running"表示正在運行。
iMaxSockets=0表示同時打開的socket最大數,為0表示沒有限制。
iMaxUdpDg=0表示同時打開的數據報最大數,為0表示沒有限制。
lpVendorInfo沒有使用,為廠商指定信息預留。
該函數使用方法:
WORD wVersion=MAKEWORD(2,0);
WSADATA wsData;
int nResult= WSAStartup(wVersion,&wsData);
if(nResult !=0)
{
//錯誤處理
}
2、創建套接字:(服務器端和客戶端)
SOCKET socket( int af, int type, int protocol );
af為網絡地址類型,一般為AF_INET,表示在Internet域中使用。
type為套接字類型,前面已經介紹了。
protocol為指定網絡協議,一般為IPPROTO_IP。
用法:
SOCKET sock=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
if(sock==INVALID_SOCKET)
{
//錯誤處理
}
3、套接字的綁定:將本地地址綁定到所創建的套接字上。(服務器端和客戶端)
int bind( SOCKET s, const struct sockaddr FAR * name, int namelen )
s為已經創建的套接字。
name為socket地址結構,為sockaddr結構,如前面討論的,我們一般使用sockaddr_in
結構,在使用再強制轉換為sockaddr結構。
namelen為地址結構的長度。
用法:
sockaddr_in addr;
addr. sin_family=AF_INET;
addr. sin_port= htons(0); //保證字節順序
addr. sin_addr.s_addr= inet_addr("192.1.8.84")
int nResult=bind(s,(sockaddr*)&addr,sizeof(sockaddr));
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}
4、 套接字的監聽:(服務器端)
int listen(SOCKET s, int backlog )
s為一個已綁定但未聯接的套接字。
backlog為指定正在等待聯接的最大隊列長度,這個參數非常重要,因為服務器一般可
以提供多個連接。
用法:
int nResult=listen(s,5) //最多5個連接
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}
5、套接字等待連接::(服務器端)
SOCKET accept( SOCKET s, struct sockaddr FAR * addr, int FAR * addrlen )
s為處于監聽模式的套接字。
sockaddr為接收成功后返回客戶端的網絡地址。
addrlen為網絡地址的長度。
用法:
sockaddr_in addr;
SOCKET s_d=accept(s,(sockaddr*)&addr,sizeof(sockaddr));
if(s==INVALID_SOCKET)
{
//錯誤處理
}
6、套接字的連結:將兩個套接字連結起來準備通信。(客戶端)
int connect(SOCKET s, const struct sockaddr FAR * name, int namelen )
s為欲連結的已創建的套接字。
name為欲連結的socket地址。
namelen為socket地址的結構的長度。
用法:
sockaddr_in addr;
addr. sin_family=AF_INET;
addr. sin_port=htons(0); //保證字節順序
addr. sin_addr.s_addr= htonl(INADDR_ANY) //保證字節順序
int nResult=connect(s,(sockaddr*)&addr,sizeof(sockaddr));
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}
7、套接字發送數據:(服務器端和客戶端)
int send(SOCKET s, const char FAR * buf, int len, int flags )
s為服務器端監聽的套接字。
buf為欲發送數據緩沖區的指針。
len為發送數據緩沖區的長度。
flags為數據發送標記。
返回值為發送數據的字符數。
◆這里講一下這個發送標記,下面8中討論的接收標記也一樣:
flag取值必須為0或者如下定義的組合:0表示沒有特殊行為。
#define MSG_OOB 0x1 /* process out-of-band data */
#define MSG_PEEK 0x2 /* peek at incoming message */
#define MSG_DONTROUTE 0x4 /* send without using routing tables */
MSG_OOB表示數據應該帶外發送,所謂帶外數據就是TCP緊急數據。
MSG_PEEK表示使有用的數據復制到緩沖區內,但并不從系統緩沖區內刪除。
MSG_DONTROUTE表示不要將包路由出去。
用法:
char buf[]="xiaojin";
int nResult=send(s,buf,strlen(buf));
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}
8、 套接字的數據接收:(客戶端)
int recv( SOCKET s, char FAR * buf, int len, int flags )
s為準備接收數據的套接字。
buf為準備接收數據的緩沖區。
len為準備接收數據緩沖區的大小。
flags為數據接收標記。
返回值為接收的數據的字符數。
用法:
char mess[1000];
int nResult =recv(s,mess,1000,0);
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}
9、中斷套接字連接:通知服務器端或客戶端停止接收和發送數據。(服務器端和客戶端)
int shutdown(SOCKET s, int how)
s為欲中斷連接的套接字。
How為描述禁止哪些操作,取值為:SD_RECEIVE、SD_SEND、SD_BOTH。
#define SD_RECEIVE 0x00
#define SD_SEND 0x01
#define SD_BOTH 0x02
用法:
int nResult= shutdown(s,SD_BOTH);
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}
10、 關閉套接字:釋放所占有的資源。(服務器端和客戶端)
int closesocket( SOCKET s )
s為欲關閉的套接字。
用法:
int nResult=closesocket(s);
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}
與socket有關的一些函數介紹
1、讀取當前錯誤值:每次發生錯誤時,如果要對具體問題進行處理,那么就應該調用這個函數取得錯誤代碼。
int WSAGetLastError(void );
#define h_errno WSAGetLastError()
錯誤值請自己閱讀Winsock2.h。
2、將主機的unsigned long值轉換為網絡字節順序(32位):為什么要這樣做呢?因為不同的計算機使用不同的字節順序存儲數據。因此任何從Winsock函數對IP地址和端口號的引用和傳給Winsock函數的IP地址和端口號均時按照網絡順序組織的。
u_long htonl(u_long hostlong);
舉例:htonl(0)=0
htonl(80)= 1342177280
3、將unsigned long數從網絡字節順序轉換位主機字節順序,是上面函數的逆函數。
u_long ntohl(u_long netlong);
舉例:ntohl(0)=0
ntohl(1342177280)= 80
4、將主機的unsigned short值轉換為網絡字節順序(16位):原因同2:
u_short htons(u_short hostshort);
舉例:htonl(0)=0
htonl(80)= 20480
5、將unsigned short數從網絡字節順序轉換位主機字節順序,是上面函數的逆函數。
u_short ntohs(u_short netshort);
舉例:ntohs(0)=0
ntohsl(20480)= 80
6、將用點分割的IP地址轉換位一個in_addr結構的地址,這個結構的定義見筆記(一),實際上就是一個unsigned long值。計算機內部處理IP地址可是不認識如192.1.8.84之類的數據。
unsigned long inet_addr( const char FAR * cp );
舉例:inet_addr("192.1.8.84")=1409810880
inet_addr("127.0.0.1")= 16777343
如果發生錯誤,函數返回INADDR_NONE值。
7、將網絡地址轉換位用點分割的IP地址,是上面函數的逆函數。
char FAR * inet_ntoa( struct in_addr in );
舉例:char * ipaddr=NULL;
char addr[20];
in_addr inaddr;
inaddr. s_addr=16777343;
ipaddr= inet_ntoa(inaddr);
strcpy(addr,ipaddr);
這樣addr的值就變為127.0.0.1。
注意意不要修改返回值或者進行釋放動作。如果函數失敗就會返回NULL值。
8、獲取套接字的本地地址結構:
int getsockname(SOCKET s, struct sockaddr FAR * name, int FAR * namelen );
s為套接字
name為函數調用后獲得的地址值
namelen為緩沖區的大小。
9、獲取與套接字相連的端地址結構:
int getpeername(SOCKET s, struct sockaddr FAR * name, int FAR * namelen );
s為套接字
name為函數調用后獲得的端地址值
namelen為緩沖區的大小。
10、獲取計算機名:
int gethostname( char FAR * name, int namelen );
name是存放計算機名的緩沖區
namelen是緩沖區的大小
用法:
char szName[255];
memset(szName,0,255);
if(gethostname(szName,255)==SOCKET_ERROR)
{
//錯誤處理
}
返回值為:szNmae="xiaojin"
11、根據計算機名獲取主機地址:
struct hostent FAR * gethostbyname( const char FAR * name );
name為計算機名。
用法:
hostent * host;
char* ip;
host= gethostbyname("xiaojin");
if(host->h_addr_list[0])
{
struct in_addr addr;
memmove(&addr, host->h_addr_list[0],4);
//獲得標準IP地址
ip=inet_ ntoa (addr);
}
返回值為:hostent->h_name="xiaojin"
hostent->h_addrtype=2 //AF_INET
hostent->length=4
ip="127.0.0.1"
Winsock 的I/O操作:1、 兩種I/O模式
- 阻塞模式:執行I/O操作完成前會一直進行等待,不會將控制權交給程序。套接字 默認為阻塞模式。可以通過多線程技術進行處理。
- 非阻塞模式:執行I/O操作時,Winsock函數會返回并交出控制權。這種模式使用 起來比較復雜,因為函數在沒有運行完成就進行返回,會不斷地返回 WSAEWOULDBLOCK錯誤。但功能強大。
為了解決這個問題,提出了進行I/O操作的一些I/O模型,下面介紹最常見的三種:
2、select模型:
通過調用select函數可以確定一個或多個套接字的狀態,判斷套接字上是否有數據,或
者能否向一個套接字寫入數據。
int select( int nfds, fd_set FAR * readfds, fd_set FAR * writefds,?
fd_set FAR *exceptfds, const struct timeval FAR * timeout );
◆先來看看涉及到的結構的定義:
a、 d_set結構:
#define FD_SETSIZE 64?
typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */
} fd_set;
fd_count為已設定socket的數量
fd_array為socket列表,FD_SETSIZE為最大socket數量,建議不小于64。這是微軟建
議的。
B、timeval結構:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};
tv_sec為時間的秒值。
tv_usec為時間的毫秒值。
這個結構主要是設置select()函數的等待值,如果將該結構設置為(0,0),則select()函數
會立即返回。
◆再來看看select函數各參數的作用:
- nfds:沒有任何用處,主要用來進行系統兼容用,一般設置為0。
- readfds:等待可讀性檢查的套接字組。
- writefds;等待可寫性檢查的套接字組。
- exceptfds:等待錯誤檢查的套接字組。
- timeout:超時時間。
- 函數失敗的返回值:調用失敗返回SOCKET_ERROR,超時返回0。
readfds、writefds、exceptfds三個變量至少有一個不為空,同時這個不為空的套接字組
種至少有一個socket,道理很簡單,否則要select干什么呢。 舉例:測試一個套接字是否可讀:
fd_set fdread;
//FD_ZERO定義
// #define FD_ZERO(set) (((fd_set FAR *)(set))->fd_count=0)
FD_ZERO(&fdread);
FD_SET(s,&fdread); //加入套接字,詳細定義請看winsock2.h
if(select(0,%fdread,NULL,NULL,NULL)>0
{
//成功
if(FD_ISSET(s,&fread) //是否存在fread中,詳細定義請看winsock2.h
{
//是可讀的
}
}
◆I/O操作函數:主要用于獲取與套接字相關的操作參數。
int ioctlsocket(SOCKET s, long cmd, u_long FAR * argp );
s為I/O操作的套接字。
cmd為對套接字的操作命令。
argp為命令所帶參數的指針。
常見的命令:
//確定套接字自動讀入的數據量
#define FIONREAD _IOR(''''f'''', 127, u_long) /* get # bytes to read */
//允許或禁止套接字的非阻塞模式,允許為非0,禁止為0
#define FIONBIO _IOW(''''f'''', 126, u_long) /* set/clear non-blocking i/o */
//確定是否所有帶外數據都已被讀入
#define SIOCATMARK _IOR(''''s'''', 7, u_long) /* at oob mark? */
3、WSAAsynSelect模型:
WSAAsynSelect模型也是一個常用的異步I/O模型。應用程序可以在一個套接字上接收以
WINDOWS消息為基礎的網絡事件通知。該模型的實現方法是通過調用WSAAsynSelect函
數 自動將套接字設置為非阻塞模式,并向WINDOWS注冊一個或多個網絡時間,并提供一
個通知時使用的窗口句柄。當注冊的事件發生時,對應的窗口將收到一個基于消息的通知。
int WSAAsyncSelect( SOCKET s, HWND hWnd, u_int wMsg, long lEvent);
s為需要事件通知的套接字
hWnd為接收消息的窗口句柄
wMsg為要接收的消息
lEvent為掩碼,指定應用程序感興趣的網絡事件組合,主要如下:
#define FD_READ_BIT 0
#define FD_READ (1 << FD_READ_BIT)
#define FD_WRITE_BIT 1
#define FD_WRITE (1 << FD_WRITE_BIT)
#define FD_OOB_BIT 2
#define FD_OOB (1 << FD_OOB_BIT)
#define FD_ACCEPT_BIT 3
#define FD_ACCEPT (1 << FD_ACCEPT_BIT)
#define FD_CONNECT_BIT 4
#define FD_CONNECT (1 << FD_CONNECT_BIT)
#define FD_CLOSE_BIT 5
#define FD_CLOSE (1 << FD_CLOSE_BIT)
用法:要接收讀寫通知:
int nResult= WSAAsyncSelect(s,hWnd,wMsg,FD_READ|FD_WRITE);
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}
取消通知:
int nResult= WSAAsyncSelect(s,hWnd,0,0);
當應用程序窗口hWnd收到消息時,wMsg.wParam參數標識了套接字,lParam的低字標明
了網絡事件,高字則包含錯誤代碼。
4、WSAEventSelect模型
WSAEventSelect模型類似WSAAsynSelect模型,但最主要的區別是網絡事件發生時會被發
送到一個事件對象句柄,而不是發送到一個窗口。
使用步驟如下:
a、 創建事件對象來接收網絡事件:
#define WSAEVENT HANDLE
#define LPWSAEVENT LPHANDLE
WSAEVENT WSACreateEvent( void );
該函數的返回值為一個事件對象句柄,它具有兩種工作狀態:已傳信(signaled)和未傳信
(nonsignaled)以及兩種工作模式:人工重設(manual reset)和自動重設(auto reset)。默認未
未傳信的工作狀態和人工重設模式。
b、將事件對象與套接字關聯,同時注冊事件,使事件對象的工作狀態從未傳信轉變未
已傳信。
int WSAEventSelect( SOCKET s,WSAEVENT hEventObject,long lNetworkEvents );
s為套接字
hEventObject為剛才創建的事件對象句柄
lNetworkEvents為掩碼,定義如上面所述
c、I/O處理后,設置事件對象為未傳信
BOOL WSAResetEvent( WSAEVENT hEvent );
Hevent為事件對象
成功返回TRUE,失敗返回FALSE。
d、等待網絡事件來觸發事件句柄的工作狀態:
DWORD WSAWaitForMultipleEvents( DWORD cEvents,
const WSAEVENT FAR * lphEvents, BOOL fWaitAll,
DWORD dwTimeout, BOOL fAlertable );
lpEvent為事件句柄數組的指針
cEvent為為事件句柄的數目,其最大值為WSA_MAXIMUM_WAIT_EVENTS?
fWaitAll指定等待類型:TRUE:當lphEvent數組重所有事件對象同時有信號時返回;
FALSE:任一事件有信號就返回。
dwTimeout為等待超時(毫秒)
fAlertable為指定函數返回時是否執行完成例程
對事件數組中的事件進行引用時,應該用WSAWaitForMultipleEvents的返回值,減去
預聲明值WSA_WAIT_EVENT_0,得到具體的引用值。例如:
nIndex=WSAWaitForMultipleEvents(…);
MyEvent=EventArray[Index- WSA_WAIT_EVENT_0];
e、判斷網絡事件類型:
int WSAEnumNetworkEvents( SOCKET s,
WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents );
s為套接字
hEventObject為需要重設的事件對象
lpNetworkEvents為記錄網絡事件和錯誤代碼,其結構定義如下:
typedef struct _WSANETWORKEVENTS {
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;
f、關閉事件對象句柄:
BOOL WSACloseEvent(WSAEVENT hEvent);
調用成功返回TRUE,否則返回FALSE。
一、簡介
WINDOWS SOCKETS 是從 Berkeley Sockets 擴展而來的,其在繼承 Berkeley Sockets 的基礎上,又進行了新的擴充。這些擴充主要是提供了一些異步函數,并增加了符合WINDOWS消息驅動特性的網絡事件異步選擇機制。
WINDOWS SOCKETS由兩部分組成:開發組件和運行組件。
開發組件:WINDOWS SOCKETS 實現文檔、應用程序接口(API)引入庫和一些頭文件。
運行組件:WINDOWS SOCKETS 應用程序接口的動態鏈接庫(WINSOCK.DLL)。
二、主要擴充說明
1、異步選擇機制:
WINDOWS SOCKETS 的異步選擇函數提供了消息機制的網絡事件選擇,當使用它登記網絡事件發生時,應用程序相應窗口函數將收到一個消息,消息中指示了發生的網絡事件,以及與事件相關的一些信息。
WINDOWS SOCKETS 提供了一個異步選擇函數 WSAAsyncSelect(),用它來注冊應用程序感興趣的網絡事件,當這些事件發生時,應用程序相應的窗口函數將收到一個消息。
函數結構如下:
int PASCAL FAR WSAAsyncSelect(SOCKET s,HWND hWnd,unsigned int wMsg,long lEvent);
參數說明:
hWnd:窗口句柄
wMsg:需要發送的消息
lEvent:事件(以下為事件的內容)
值: 含義:
FD_READ 期望在套接字上收到數據(即讀準備好)時接到通知
FD_WRITE 期望在套接字上可發送數據(即寫準備好)時接到通知
FD_OOB 期望在套接字上有帶外數據到達時接到通知
FD_ACCEPT 期望在套接字上有外來連接時接到通知
FD_CONNECT 期望在套接字連接建立完成時接到通知
FD_CLOSE 期望在套接字關閉時接到通知
例如:我們要在套接字讀準備好或寫準備好時接到通知,語句如下:
rc=WSAAsyncSelect(s,hWnd,wMsg,FD_READ|FD_WRITE);
如果我們需要注銷對套接字網絡事件的消息發送,只要將 lEvent 設置為0
2、異步請求函數在 Berkeley Sockets 中請求服務是阻塞的,WINDOWS SICKETS 除了支持這一類函數外,還增加了相應的異步請求函數(WSAAsyncGetXByY();)。
3、阻塞處理方法
WINDOWS SOCKETS 為了實現當一個應用程序的套接字調用處于阻塞時,能夠放棄CPU讓其它應用程序運行,它在調用處于阻塞時便進入一個叫“HOOK”的例程,此例程負責接收和分配WINDOWS消息,使得其它應用程序仍然能夠接收到自己的消息并取得控制權。
WINDOWS 是非搶先的多任務環境,即若一個程序不主動放棄其控制權,別的程序就不能執行。因此在設計 WINDOWS SOCKETS 程序時,盡管系統支持阻塞操作,但還是反對程序員使用該操作。但由于 SUN 公司下的 Berkeley Sockets 的套接字默認操作是阻塞的,WINDOWS 作為移植的 SOCKETS 也不可避免對這個操作支持。
在 WINDOWS SOCKETS 實現中,對于不能立即完成的阻塞操作做如下處理:DLL初始化→循環操作。在循環中,它發送任何 WINDOWS 消息,并檢查這個 WINDOWS SOCKETS 調用是否完成,在必要時,它可以放棄CPU讓其它應用程序執行(當然使用超線程的CPU就不會有這個麻煩了^_^)。我們可以調用 WSACancelBlockingCall() 函數取消此阻塞操作。
在 WINDOWS SOCKETS 中,有一個默認的阻塞處理例程 BlockingHook() 簡單地獲取并發送 WINDOWS 消息。如果要對復雜程序進行處理,WINDOWS SOCKETS 中還有 WSASetBlockingHook() 提供用戶安裝自己的阻塞處理例程能力;與該函數相對應的則是 SWAUnhookBlockingHook(),它用于刪除先前安裝的任何阻塞處理例程,并重新安裝默認的處理例程。請注意,設計自己的阻塞處理例程時,除了函數 WSACancelBlockingHook() 之外,它不能使用其它的 WINDOWS SOCKETS API 函數。在處理例程中調用 WSACancelBlockingHook()函數將取消處于阻塞的操作,它將結束阻塞循環。
4、出錯處理
WINDOWS SOCKETS 為了和以后多線程環境(WINDOWS/UNIX)兼容,它提供了兩個出錯處理函數來獲取和設置當前線程的最近錯誤號。(WSAGetLastEror()和WSASetLastError())
5、啟動與終止
使用函數 WSAStartup() 和 WSACleanup() 啟動和終止套接字。
三、WINDOWS SOCKETS 網絡程序設計核心我們終于可以開始真正的 WINDOWS SOCKETS 網絡程序設計了。不過我們還是先看一看每個 WINDOWS SOCKETS 網絡程序都要涉及的內容。讓我們一步步慢慢走。
1、啟動與終止在所有 WINDOWS SOCKETS 函數中,只有啟動函數 WSAStartup() 和終止函數 WSACleanup() 是必須使用的。
啟動函數必須是第一個使用的函數,而且它允許指定 WINDOWS SOCKETS API 的版本,并獲得 SOCKETS的特定的一些技術細節。本結構如下:
int PASCAL FAR WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
其中 wVersionRequested 保證 SOCKETS 可正常運行的 DLL 版本,如果不支持,則返回錯誤信息。
我們看一下下面這段代碼,看一下如何進行 WSAStartup() 的調用
WORD wVersionRequested;// 定義版本信息變量
WSADATA wsaData;//定義數據信息變量
int err;//定義錯誤號變量
wVersionRequested = MAKEWORD(1,1);//給版本信息賦值
err = WSAStartup(wVersionRequested, &wsaData);//給錯誤信息賦值
if(err!=0)
{
return;//告訴用戶找不到合適的版本
}
//確認 WINDOWS SOCKETS DLL 支持 1.1 版本
//DLL 版本可以高于 1.1
//系統返回的版本號始終是最低要求的 1.1,即應用程序與DLL 中可支持的最低版本號
if(LOBYTE(wsaData.wVersion)!= 1|| HIBYTE(wsaData.wVersion)!=1)
{
WSACleanup();//告訴用戶找不到合適的版本
return;
}
//WINDOWS SOCKETS DLL 被進程接受,可以進入下一步操作
關閉函數使用時,任何打開并已連接的 SOCK_STREAM 套接字被復位,但那些已由 closesocket() 函數關閉的但仍有未發送數據的套接字不受影響,未發送的數據仍將被發送。程序運行時可能會多次調用 WSAStartuo() 函數,但必須保證每次調用時的 wVersionRequested 的值是相同的。
2、異步請求服務WINDOWS SOCKETS 除支持 Berkeley Sockets 中同步請求,還增加了了一類異步請求服務函數 WSAAsyncGerXByY()。該函數是阻塞請求函數的異步版本。應用程序調用它時,由 WINDOWS SOCKETS DLL 初始化這一操作并返回調用者,此函數返回一個異步句柄,用來標識這個操作。當結果存儲在調用者提供的緩沖區,并且發送一個消息到應用程序相應窗口。常用結構如下:
HANDLE taskHnd;
char hostname="rs6000";
taskHnd = WSAAsyncBetHostByName(hWnd,wMsg,hostname,buf,buflen);
需要注意的是,由于 Windows 的內存對像可以設置為可移動和可丟棄,因此在操作內存對象是,必須保證 WIindows Sockets DLL 對象是可用的。
3、異步數據傳輸
使用 send() 或 sendto() 函數來發送數據,使用 recv() 或recvfrom() 來接收數據。Windows Sockets 不鼓勵用戶使用阻塞方式傳輸數據,因為那樣可能會阻塞整個 Windows 環境。下面我們看一個異步數據傳輸實例:
假設套接字 s 在連接建立后,已經使用了函數 WSAAsyncSelect() 在其上注冊了網絡事件 FD_READ 和 FD_WRITE,并且 wMsg 值為 UM_SOCK,那么我們可以在 Windows 消息循環中增加如下的分支語句:
case UM_SOCK:
switch(lParam)
{
case FD_READ:
len = recv(wParam,lpBuffer,length,0);
break;
case FD_WRITE:
while(send(wParam,lpBuffer,len,0)!=SOCKET_ERROR)
break;
}
break;
4、出錯處理Windows 提供了一個函數來獲取最近的錯誤碼 WSAGetLastError(),推薦的編寫方式如下:
len = send (s,lpBuffer,len,0);
of((len==SOCKET_ERROR)&&(WSAGetLastError()==WSAWOULDBLOCK)){...}
一、客戶機/服務器模式
在TCP/IP網絡中兩個進程間的相互作用的主機模式是客戶機/服務器模式(Client/Server model)。該模式的建立基于以下兩點:1、非對等作用;2、通信完全是異步的。客戶機/服務器模式在操作過程中采取的是主動請示方式:
首先服務器方要先啟動,并根據請示提供相應服務:(過程如下)
1、打開一通信通道并告知本地主機,它愿意在某一個公認地址上接收客戶請求。
2、等待客戶請求到達該端口。
3、接收到重復服務請求,處理該請求并發送應答信號。
4、返回第二步,等待另一客戶請求
5、關閉服務器。
客戶方:
1、打開一通信通道,并連接到服務器所在主機的特定端口。
2、向服務器發送服務請求報文,等待并接收應答;繼續提出請求……
3、請求結束后關閉通信通道并終止。
二、基本套接字為了更好說明套接字編程原理,給出幾個基本的套接字,在以后的篇幅中會給出更詳細的使用說明。
1、創建套接字——socket()
功能:使用前創建一個新的套接字
格式:SOCKET PASCAL FAR socket(int af,int type,int procotol);
參數:af: 通信發生的區域
type: 要建立的套接字類型
procotol: 使用的特定協議
2、指定本地地址——bind()
功能:將套接字地址與所創建的套接字號聯系起來。
格式:int PASCAL FAR bind(SOCKET s,const struct sockaddr FAR * name,int namelen);
參數:s: 是由socket()調用返回的并且未作連接的套接字描述符(套接字號)。
其它:沒有錯誤,bind()返回0,否則SOCKET_ERROR
地址結構說明:
struct sockaddr_in
{
short sin_family;//AF_INET
u_short sin_port;//16位端口號,網絡字節順序
struct in_addr sin_addr;//32位IP地址,網絡字節順序
char sin_zero[8];//保留
}
3、建立套接字連接——connect()和accept()
功能:共同完成連接工作
格式:int PASCAL FAR connect(SOCKET s,const struct sockaddr FAR * name,int namelen);
SOCKET PASCAL FAR accept(SOCKET s,struct sockaddr FAR * name,int FAR * addrlen);
參數:同上
4、監聽連接——listen()
功能:用于面向連接服務器,表明它愿意接收連接。
格式:int PASCAL FAR listen(SOCKET s, int backlog);
5、數據傳輸——send()與recv()
功能:數據的發送與接收
格式:int PASCAL FAR send(SOCKET s,const char FAR * buf,int len,int flags);
int PASCAL FAR recv(SOCKET s,const char FAR * buf,int len,int flags);
參數:buf:指向存有傳輸數據的緩沖區的指針。
6、多路復用——select()
功能:用來檢測一個或多個套接字狀態。
格式:int PASCAL FAR select(int nfds,fd_set FAR * readfds,fd_set FAR * writefds,
fd_set FAR * exceptfds,const struct timeval FAR * timeout);
參數:readfds:指向要做讀檢測的指針
writefds:指向要做寫檢測的指針
exceptfds:指向要檢測是否出錯的指針
timeout:最大等待時間
7、關閉套接字——closesocket()
功能:關閉套接字s
格式:BOOL PASCAL FAR closesocket(SOCKET s);
三、典型過程圖2.1 面向連接的套接字的系統調用時序圖

2.2 無連接協議的套接字調用時序圖

2.3 面向連接的應用程序流程圖

?
一、TCP/IP 體系結構與特點
1、TCP/IP體系結構
TCP/IP協議實際上就是在物理網上的一組完整的網絡協議。其中TCP是提供傳輸層服務,而IP則是提供網絡層服務。TCP/IP包括以下協議:(結構如圖1.1)

(圖1.1)
IP: 網間協議(Internet Protocol) 負責主機間數據的路由和網絡上數據的存儲。同時為ICMP,TCP,UDP提供分組發送服務。用戶進程通常不需要涉及這一層。
ARP: 地址解析協議(Address Resolution Protocol)
此協議將網絡地址映射到硬件地址。
RARP: 反向地址解析協議(Reverse Address Resolution Protocol)
此協議將硬件地址映射到網絡地址
ICMP: 網間報文控制協議(Internet Control Message Protocol)
此協議處理信關和主機的差錯和傳送控制。
TCP: 傳送控制協議(Transmission Control Protocol)
這是一種提供給用戶進程的可靠的全雙工字節流面向連接的協議。它要為用戶進程提供虛電路服務,并為數據可靠傳輸建立檢查。(注:大多數網絡用戶程序使用TCP)
UDP: 用戶數據報協議(User Datagram Protocol)
這是提供給用戶進程的無連接協議,用于傳送數據而不執行正確性檢查。
FTP: 文件傳輸協議(File Transfer Protocol)
允許用戶以文件操作的方式(文件的增、刪、改、查、傳送等)與另一主機相互通信。
SMTP: 簡單郵件傳送協議(Simple Mail Transfer Protocol)
SMTP協議為系統之間傳送電子郵件。
TELNET:終端協議(Telnet Terminal Procotol)
允許用戶以虛終端方式訪問遠程主機
HTTP: 超文本傳輸協議(Hypertext Transfer Procotol)
TFTP: 簡單文件傳輸協議(Trivial File Transfer Protocol)
2、TCP/IP特點TCP/IP協議的核心部分是傳輸層協議(TCP、UDP),網絡層協議(IP)和物理接口層,這三層通常是在操作系統內核中實現。因此用戶一般不涉及。編程時,編程界面有兩種形式:一、是由內核心直接提供的系統調用;二、使用以庫函數方式提供的各種函數。前者為核內實現,后者為核外實現。用戶服務要通過核外的應用程序才能實現,所以要使用套接字(socket)來實現。
圖1.2是TCP/IP協議核心與應用程序關系圖。

(圖1.2)
二、專用術語1、套接字
它是網絡的基本構件。它是可以被命名和尋址的通信端點,使用中的每一個套接字都有其類型和一個與之相連聽進程。套接字存在通信區域(通信區域又稱地址簇)中。套接字只與同一區域中的套接字交換數據(跨區域時,需要執行某和轉換進程才能實現)。WINDOWS 中的套接字只支持一個域——網際域。套接字具有類型。
WINDOWS SOCKET 1.1 版本支持兩種套接字:流套接字(SOCK_STREAM)和數據報套接字(SOCK_DGRAM)
2、WINDOWS SOCKETS 實現
一個WINDOWS SOCKETS 實現是指實現了WINDOWS SOCKETS規范所描述的全部功能的一套軟件。一般通過DLL文件來實現
3、阻塞處理例程
阻塞處理例程(blocking hook,阻塞鉤子)是WINDOWS SOCKETS實現為了支持阻塞套接字函數調用而提供的一種機制。
4、多址廣播(multicast,多點傳送或組播)
是一種一對多的傳輸方式,傳輸發起者通過一次傳輸就將信息傳送到一組接收者,與單點傳送
(unicast)和廣播(Broadcast)相對應。