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

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

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

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

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

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

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

2.2 無連接協(xié)議的套接字調(diào)用時序圖

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

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

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

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