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

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

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

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

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

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

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

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

2.3 面向連接的應(yīng)用程序流程圖

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

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

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