版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
9.1嵌入式以太網(wǎng)基礎(chǔ)知識9.2以太網(wǎng)接口設(shè)計(jì)9.3Linux網(wǎng)絡(luò)編程實(shí)現(xiàn)練習(xí)題第9章嵌入式網(wǎng)絡(luò)程序設(shè)計(jì)9.1嵌入式以太網(wǎng)基礎(chǔ)知識
9.1.1以太網(wǎng)介紹及其嵌入式應(yīng)用
1973年,施樂(Xerox)公司設(shè)計(jì)了第一個(gè)局域網(wǎng)系統(tǒng),命名為Ethernet,其帶寬為2.97Mb/s。1982年,DEC、Intel和Xerox聯(lián)合發(fā)表了EthernetVersion2規(guī)范,將帶寬提高到了10Mb/s,并正式投入商業(yè)市場。1983年,IEEE(國際電氣和電子工程師學(xué)會)通過802.3CSMA/CD規(guī)范。IEEE802標(biāo)準(zhǔn)是由IEEE制定的局域網(wǎng)標(biāo)準(zhǔn),IEEE802委員會有10多個(gè)分委員會。IEEE802標(biāo)準(zhǔn)分類如下:
(1)?802.1A,概述體系結(jié)構(gòu)和網(wǎng)絡(luò)互連、網(wǎng)絡(luò)管理;
(2)?802.1B,尋址、網(wǎng)絡(luò)管理、網(wǎng)間互連及高層接口;
(3)?802.2,邏輯鏈路控制LLC;
(4)?802.3,CSMA/CD共享總線網(wǎng),即Ethernet;
(5)?802.5,令牌環(huán)網(wǎng)(Token-Ring);
(6)?802.11,無線局域網(wǎng)。
IEEE802.3的命名規(guī)則是IEEE802.3XTYPE-YNAME。其中:X表示傳輸介質(zhì),5指粗同軸電纜,2指細(xì)同軸電纜,T指雙絞線,F(xiàn)指光纖;TYPE代表傳輸方式,Base指基帶傳輸,Broad指寬帶傳輸;Y與X一樣表示傳輸介質(zhì);NAME表示局域網(wǎng)的名稱,Ethernet為以太網(wǎng),F(xiàn)astEthernet為快速以太網(wǎng),GigaEthernet為千兆以太網(wǎng)。
以太網(wǎng)的數(shù)據(jù)鏈路層由以下兩部分組成:
(1)邏輯鏈路控制子層(LogicalLinkControl,即LLC子層),為網(wǎng)絡(luò)層定義了各種服務(wù)及接口;
(2)介質(zhì)訪問控制子層(MediaAccessControl,即MAC子層),定義了對各種物理傳輸介質(zhì)的訪問及控制技術(shù)。
IEEE802標(biāo)準(zhǔn)為各種局域網(wǎng)技術(shù)定義了統(tǒng)一的LLC子層,而各種局域網(wǎng)技術(shù)的MAC子層不盡相同,所以以太網(wǎng)的數(shù)據(jù)鏈路層主要是指以太網(wǎng)的MAC子層。
CSMA/CD(CarrierSenseMultipleAccess/CollisionDetect)協(xié)議的全稱為帶沖突檢測的載波監(jiān)聽多路訪問技術(shù),是以太網(wǎng)中所使用的介質(zhì)訪問控制技術(shù)。這種介質(zhì)訪問控制技術(shù)是基于共享介質(zhì)的,采用共享總線的拓?fù)浣Y(jié)構(gòu),以廣播的形式進(jìn)行數(shù)據(jù)傳送。在某一時(shí)刻,連接在共享總線上的所有站點(diǎn)中,只能有一個(gè)站點(diǎn)可以發(fā)送數(shù)據(jù)。
CSMA協(xié)議是指:
(1)總線有兩個(gè)狀態(tài),即“空閑”和“忙”;
(2)每個(gè)站點(diǎn)在使用總線發(fā)送數(shù)據(jù)幀之前首先監(jiān)聽總線,查看總線是否處于“空閑”狀態(tài),如果總線“忙”,就繼續(xù)等待,繼續(xù)監(jiān)聽,一直到總線“空閑”;
(3)要發(fā)送數(shù)據(jù)幀的站點(diǎn)在監(jiān)聽到總線“空閑”時(shí),開始發(fā)送數(shù)據(jù)幀。
CD協(xié)議是指:
(1)在使用CSMA協(xié)議時(shí),有可能會出現(xiàn)兩個(gè)或兩個(gè)以上的站點(diǎn)同時(shí)監(jiān)聽到總線“空閑”的情況,此時(shí)這些站點(diǎn)將同時(shí)開始發(fā)送數(shù)據(jù)幀,出現(xiàn)這種情況時(shí),總線會發(fā)生沖突,導(dǎo)致所有站點(diǎn)的發(fā)送全部失敗;
(2)每個(gè)站點(diǎn)在發(fā)送數(shù)據(jù)后必須檢測是否發(fā)生了沖突;
(3)在發(fā)生沖突的情況下,站點(diǎn)使用二進(jìn)制指數(shù)退避算法和重發(fā)數(shù)據(jù)幀。
MAC地址是指每張網(wǎng)卡中包含一個(gè)獨(dú)一無二的物理地址,由48位二進(jìn)制構(gòu)成,前24位代表設(shè)備生產(chǎn)商,由IEEE管理分配,后24位為各生產(chǎn)商內(nèi)部的編號。由于CSMA/CD協(xié)議中,幀以廣播的形式進(jìn)行傳輸,因此總線上的每個(gè)站點(diǎn)要根據(jù)幀中包含的目的MAC和自己網(wǎng)卡中的MAC地址是否一致來決定是否接收該幀。以太網(wǎng)接口控制器主要包括MAC和PHY兩部分,其中MAC層控制器作為邏輯控制比較容易集成在處理器內(nèi)部。很多針對網(wǎng)絡(luò)控制應(yīng)用的嵌入式處理器都集成了MAC層控制器。以ARM處理器核為例,通常,這種在處理器內(nèi)部集成片內(nèi)MAC層控制器的芯片結(jié)構(gòu)如圖9-1所示。ASB(AdvancedSystemBus,高級系統(tǒng)總線)和APB(AdvancedPeripheralBus,高級外設(shè)總線)都是AMBA(AdvancedMicroprocessorBusArchitecture,高級微處理器總線結(jié)構(gòu))總線定義的類型。ASB用作處理器與高速外設(shè)之間的互連,APB則為系統(tǒng)的低速外部設(shè)備提供低功耗的簡易互連。在圖9-1所示的集成在ARM片內(nèi)的MAC層控制器體系結(jié)構(gòu)中,ARM核可通過APB總線訪問寄存器接口,而MAC層控制器可通過DMA與內(nèi)存交換數(shù)據(jù)。圖9-1集成了MAC層控制器的ARM和PHY的連接
MAC層控制器和PHY的連接是通過MII(MediaIndependentInterface,媒體獨(dú)立接口)、RMII(ReducedMII,精簡MII)等接口實(shí)現(xiàn)的。MAC層負(fù)責(zé)完成數(shù)據(jù)幀的封裝、解封、發(fā)送和接收功能。物理層PHY的結(jié)構(gòu)隨著傳輸速率的不同而有一定差異。MII是連接數(shù)據(jù)鏈路層和物理層的接口。根據(jù)協(xié)議,要求MII接口具有的功能有:數(shù)據(jù)和幀分隔符的讀/寫時(shí)鐘同步;提供獨(dú)立的讀/寫數(shù)據(jù)通道;為MAC層和PHY層提供相應(yīng)的管理信號以及支持全雙工模式。
對于10Base-T等網(wǎng)絡(luò),從以太網(wǎng)PHY芯片輸出的就是傳輸所需的差分信號,但是還需要一個(gè)網(wǎng)絡(luò)隔離變壓器組成如圖9-2所示的結(jié)構(gòu)。網(wǎng)絡(luò)隔離變壓器可起到抑制共模干擾、隔離線路以及阻抗匹配等作用。圖9-210Base-T網(wǎng)絡(luò)的網(wǎng)絡(luò)接口對于沒有集成MAC控制器的嵌入式處理器,更為通用的方法就是使用以太網(wǎng)芯片。在嵌入式系統(tǒng)中通常需要的是HostBus接口的以太網(wǎng)芯片。其中,常見的有:
(1)?RTL8019—Realtek公司的全雙工10Mb/s以太網(wǎng)芯片。它最初是為了ISA接口的網(wǎng)卡設(shè)計(jì)的,支持即插即用ISA模式,兼容NE2000寄存器,支持8位和16位總線模式。其特點(diǎn)是:成本低廉,使用廣泛,驅(qū)動(dòng)程序支持好;支持跳線模式,在嵌入式處理器平臺上,很容易使用固有的配置;但是,其使用的是5V邏輯電平,與3.3V邏輯連接,需要考慮總線電平的問題。
(2)?CS8900—CirrusLogic公司的全雙工10Mb/s以太網(wǎng)芯片,也是為ISA總線接口設(shè)計(jì)的以太網(wǎng)芯片。其特點(diǎn)是:16位總線寬度;成本較低,Linux等多數(shù)操作系統(tǒng)下都有驅(qū)動(dòng)程序支持;有I/O和存儲兩種訪問模式;3.3V供電和I/O接口很容易與多數(shù)嵌入式處理器直接連接。
(3)?AX88796—ASIX公司10/100Mb/s自適應(yīng)以太網(wǎng)芯片,支持HostBus和Motorola的68000系列處理器讀/寫時(shí)序。其特點(diǎn)是:8位或16位數(shù)據(jù)總線;兼容NE2000寄存器,驅(qū)動(dòng)程序支持廣泛;3.3V接口電平,容易使用;還可使用MII接口與片外的PHY芯片連接。
(4)?DM9000—DAVICOM公司的10/100Mb/s自適應(yīng)以太網(wǎng)芯片。其特點(diǎn)是:支持8位、16位、32位數(shù)據(jù)總線寬度;寄存器操作簡單有效,有成熟的Linux驅(qū)動(dòng)程序支持;3.3V接口電平,容易使用;成本低廉;還可使用MII接口與片外的PHY芯片連接。
(5)?LAN91C111—SMSC公司的10/100Mb/s自適應(yīng)以太網(wǎng)芯片。其特點(diǎn)是:支持8位、16位、32位數(shù)據(jù)總線寬度;支持異步和同步總線傳輸;有成熟的Linux驅(qū)動(dòng)程序支持,但寄存器操作比較復(fù)雜;3.3V接口電平;成本偏高;也可使用MII接口與片外的PHY芯片連接。多數(shù)以太網(wǎng)芯片都可通過一片串行的EEPROM進(jìn)行配置,主要包括網(wǎng)卡的工作模式、總線的地址、中斷和以太網(wǎng)的MAC地址等信息。但是,通常對于嵌入式系統(tǒng)應(yīng)用來說,設(shè)計(jì)者希望節(jié)省這個(gè)用于配置的存儲器,而通過驅(qū)動(dòng)程序來配置以太網(wǎng)芯片。關(guān)于芯片和處理器的連接原理圖等,這里不作詳細(xì)討論,請根據(jù)需要自行設(shè)計(jì)。需要注意的問題如下:
(1)總線寬度讀/寫等待周期、時(shí)序是否匹配等;
(2)如果不使用配置存儲器,芯片復(fù)位以后,在總線上的默認(rèn)地址如何配置與保存;
(3)默認(rèn)的中斷號及中斷觸發(fā)模式,如上升沿(或高電平)、下降沿(或低電平)如何設(shè)置。9.1.2嵌入式系統(tǒng)中主要處理的網(wǎng)絡(luò)協(xié)議
在互聯(lián)網(wǎng)發(fā)展的歷史中,最成功的莫過于TCP/IP協(xié)議。TCP/IP協(xié)議(TransmissionControlProtocol/InternetProtocol,傳輸控制協(xié)議/互聯(lián)網(wǎng)絡(luò)協(xié)議)是Internet最基本的協(xié)議,簡單地說,它就是由網(wǎng)絡(luò)層的IP協(xié)議和傳輸層的TCP協(xié)議組成的。廣義的TCP/IP協(xié)議族主要可劃分為如表9-1所示的幾個(gè)層次。
TCP/IP主要定義的是網(wǎng)絡(luò)層及網(wǎng)絡(luò)層之上的協(xié)議標(biāo)準(zhǔn)。所有這些協(xié)議都在相應(yīng)的RFC(RequestForComments,請求注解)文檔中討論及標(biāo)準(zhǔn)化。重要的協(xié)議在對應(yīng)的RFC文檔中均標(biāo)記了狀態(tài),比如必需(required)、推薦(recommended)、可選(elective)。其他的協(xié)議還可能有試驗(yàn)(experimental)或歷史(historic)的狀態(tài)。下面對在嵌入式系統(tǒng)應(yīng)用中幾個(gè)主要協(xié)議作簡要介紹。
1.ARP
ARP(AddressResolutionProtocol,地址解析協(xié)議)的功能是實(shí)現(xiàn)從IP地址到對應(yīng)物理地址的轉(zhuǎn)換。網(wǎng)絡(luò)層用32位的IP地址來標(biāo)識不同的主機(jī),而鏈路層使用48位的MAC地址來標(biāo)識不同的以太網(wǎng)接口。只知道目的主機(jī)的IP地址并不能發(fā)送數(shù)據(jù)幀給它,必須要知道目的主機(jī)網(wǎng)絡(luò)接口的MAC地址才能發(fā)送數(shù)據(jù)幀。其工作流程可總結(jié)為:源主機(jī)發(fā)送一份包含目的主機(jī)IP地址的ARP請求數(shù)據(jù)幀給網(wǎng)上的每個(gè)主機(jī)(稱做ARP廣播),目的主機(jī)收到這份ARP廣播報(bào)文后,識別出這是發(fā)送端在詢問它的IP地址,于是發(fā)送一個(gè)包含目的主機(jī)IP地址及對應(yīng)的MAC地址的ARP應(yīng)答給源主機(jī)。每臺主機(jī)上都有一個(gè)ARP緩存,存放最近的IP地址到MAC地址之間的映射記錄。通常每一項(xiàng)的生存時(shí)間為20min。提示:在Windows或Linux中通過arp命令可查看記錄在系統(tǒng)中的arp列表。例如Linux下:
2.ICMP
ICMP(InternetControlMessagesProtocol,網(wǎng)絡(luò)控制報(bào)文協(xié)議)被封裝于IP層數(shù)據(jù)包中,是IP層的附屬協(xié)議。IP層用它來與其他主機(jī)或路由器交換錯(cuò)誤報(bào)文及其他重要控制信息。兩個(gè)實(shí)用的網(wǎng)絡(luò)診斷工具Ping和Traceroute(Windows下是Tracert)都是利用該協(xié)議工作的。
3.IP
IP協(xié)議工作在網(wǎng)絡(luò)層,它是TCP/IP協(xié)議族中最為核心的協(xié)議。所有的TCP、UDP、ICMP以及IGMP數(shù)據(jù)都以IP數(shù)據(jù)報(bào)格式傳輸。IP數(shù)據(jù)報(bào)最長可達(dá)65535字節(jié),其中報(bào)頭占20字節(jié),包含各32位的源IP地址和目的IP地址。有時(shí),在嵌入式應(yīng)用中為了簡化設(shè)計(jì)、節(jié)約資源,沒有必要支持如此大(64KB)的IP數(shù)據(jù)報(bào)。例如,讓IP數(shù)據(jù)報(bào)長度小于或等于數(shù)據(jù)鏈路層的數(shù)據(jù)長度(對于以太網(wǎng)來說就是1500字節(jié)),大多數(shù)IP層以上的協(xié)議仍然工作得很好。
4.TCP
TCP協(xié)議為兩臺主機(jī)提供基于連接的高可靠性的端到端數(shù)據(jù)通信。
發(fā)送方把應(yīng)用程序交給它的數(shù)據(jù)分成合適的小塊并附加信息(TCP頭),包括順序號、源、目的端口、控制、糾錯(cuò)信息等字段,稱為TCP數(shù)據(jù)報(bào),并將TCP數(shù)據(jù)報(bào)交給下面的網(wǎng)絡(luò)層處理。
接收方確認(rèn)接收到的TCP數(shù)據(jù)報(bào),重組并將數(shù)據(jù)送往高層。
5.UDP
UDP(UserDatagramProtocol,用戶數(shù)據(jù)報(bào)協(xié)議)是一種無連接、不可靠的傳輸層協(xié)議。其實(shí)現(xiàn)過程很簡單,僅把應(yīng)用程序傳來的數(shù)據(jù)加上UDP頭(包括端口號、段長等字段)作為UDP數(shù)據(jù)報(bào)發(fā)送出去,但并不保證它們能到達(dá)目的地。數(shù)據(jù)傳輸?shù)目煽啃杂蓱?yīng)用層來提供。
6.TFTP
TFTP(TrivialFileTransferProtocol,簡單文件傳送協(xié)議)基于UDP協(xié)議而實(shí)現(xiàn)。此協(xié)議設(shè)計(jì)時(shí)是進(jìn)行小文件傳輸?shù)?,因此它不具備通常的FTP的許多功能,而只能從文件服務(wù)器上獲得或?qū)懭胛募?,沒有目錄的概念,不能進(jìn)行認(rèn)證和權(quán)限管理。TFTP協(xié)議設(shè)計(jì)的初衷是用于引導(dǎo)無盤系統(tǒng),因?yàn)門FTP設(shè)計(jì)得簡單實(shí)用,實(shí)現(xiàn)也很容易。在嵌入式系統(tǒng)中,引導(dǎo)程序(BootLoader)常使用TFTP協(xié)議通過以太網(wǎng)下載系統(tǒng)內(nèi)核等。
9.2以太網(wǎng)接口設(shè)計(jì)
9.2.1網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序基本結(jié)構(gòu)及功能
Linux網(wǎng)絡(luò)驅(qū)動(dòng)程序位于TCP/IP網(wǎng)絡(luò)體系結(jié)構(gòu)的最底層,它主要實(shí)現(xiàn)兩個(gè)功能:
(1)接收上層協(xié)議棧發(fā)送來的數(shù)據(jù),將其發(fā)送給網(wǎng)絡(luò)設(shè)備;
(2)從網(wǎng)絡(luò)設(shè)備接收數(shù)據(jù),后交于上層協(xié)議處理。
Linux的網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)是Linux網(wǎng)絡(luò)子系統(tǒng)的一部分,它在Linux網(wǎng)絡(luò)子系統(tǒng)中的層次結(jié)構(gòu)如圖9-3所示。網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)在硬件設(shè)備與上層協(xié)議間建立起數(shù)據(jù)通信的橋梁。圖9-3Linux網(wǎng)絡(luò)驅(qū)動(dòng)體系結(jié)構(gòu)上層協(xié)議棧產(chǎn)生的數(shù)據(jù)需由網(wǎng)絡(luò)協(xié)議接口層的dev_queue_xmit()函數(shù)發(fā)送。dev_queue_xmit()函數(shù)通過網(wǎng)絡(luò)設(shè)備接口net_device的hard_start_xmit()函數(shù)指針,調(diào)用驅(qū)動(dòng)程序的數(shù)據(jù)發(fā)送函數(shù),將上層協(xié)議產(chǎn)生的數(shù)據(jù)包發(fā)送給硬件設(shè)備。
當(dāng)網(wǎng)絡(luò)數(shù)據(jù)到達(dá)設(shè)備后會觸發(fā)設(shè)備中斷,設(shè)備驅(qū)動(dòng)程序響應(yīng)中斷,從網(wǎng)絡(luò)設(shè)備接收數(shù)據(jù),并通過網(wǎng)絡(luò)協(xié)議接口層的netif_rx()函數(shù)將數(shù)據(jù)發(fā)送給上層協(xié)議棧。
在Linux網(wǎng)絡(luò)子系統(tǒng)中,驅(qū)動(dòng)程序設(shè)計(jì)要做的工作就是根據(jù)底層具體的硬件特性定義網(wǎng)絡(luò)設(shè)備接口structnet_device類型的結(jié)構(gòu)體變量,并實(shí)現(xiàn)相應(yīng)的操作接口函數(shù)以及中斷處理,完成設(shè)備驅(qū)動(dòng)功能。網(wǎng)絡(luò)驅(qū)動(dòng)程序主要完成系統(tǒng)的初始化、數(shù)據(jù)包的發(fā)送和接收等功能。網(wǎng)絡(luò)設(shè)備的初始化主要由net_device數(shù)據(jù)結(jié)構(gòu)中的init()函數(shù)指針?biāo)赶虻某跏蓟瘮?shù)來完成,當(dāng)內(nèi)核啟動(dòng)或加載網(wǎng)絡(luò)驅(qū)動(dòng)模塊的時(shí)候,就會調(diào)用這個(gè)初始化函數(shù)。在初始化函數(shù)中通過檢測物理設(shè)備的硬件特征來偵測網(wǎng)絡(luò)物理設(shè)備是否存在,然后再對設(shè)備進(jìn)行資源配置,接下來構(gòu)造設(shè)備的net_device數(shù)據(jù)結(jié)構(gòu),并用檢測到的數(shù)據(jù)對net_device中的變量初始化,最后向Linux內(nèi)核注冊該設(shè)備并申請內(nèi)存空間。數(shù)據(jù)包的發(fā)送和接收是實(shí)現(xiàn)Linux網(wǎng)絡(luò)驅(qū)動(dòng)程序中兩個(gè)最關(guān)鍵的過程,對這兩個(gè)過程處理的好壞將直接影響到驅(qū)動(dòng)程序的整體運(yùn)行質(zhì)量。在網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)加載時(shí),通過net_device域中的init()函數(shù)指針調(diào)用網(wǎng)絡(luò)設(shè)備的初始化函數(shù)對設(shè)備進(jìn)行初始化,如果操作成功再通過net_device域中的open()函數(shù)指針調(diào)用網(wǎng)絡(luò)設(shè)備的打開函數(shù)打開設(shè)備,并通過net_device域中建立硬件包頭函數(shù)指針hard_header來建立硬件包頭信息,最后通過協(xié)議接口層函數(shù)dev_queue_xmit()(參見net/core/dev.c文件)來調(diào)用net_device域中的hard_start_xmit()函數(shù)指針完成數(shù)據(jù)包的發(fā)送。該函數(shù)把存放在套接字緩沖區(qū)中的數(shù)據(jù)發(fā)送到物理設(shè)備,該緩沖區(qū)是由數(shù)據(jù)結(jié)構(gòu)sk_buff來表示的。數(shù)據(jù)包的接收是通過中斷機(jī)制來完成的。當(dāng)有數(shù)據(jù)到達(dá)時(shí)產(chǎn)生中斷信號,網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)功能層調(diào)用中斷處理程序(即數(shù)據(jù)包接收程序)來處理數(shù)據(jù)包的接收,隨后網(wǎng)絡(luò)協(xié)議接口層調(diào)用netif_rx()(參見net/core/dev.c文件)函數(shù),把接收到的數(shù)據(jù)包傳輸?shù)骄W(wǎng)絡(luò)協(xié)議的上層進(jìn)行處理。9.2.2以太網(wǎng)控制器CS8900A
1.?CS8900A介紹
CS8900A芯片是CIRRUSLOGIC公司的一款16位的以太網(wǎng)控制器,芯片內(nèi)嵌片內(nèi)RAM、10Base-T收/發(fā)濾波器、直接ISA總線接口。該芯片的突出特點(diǎn)是使用靈活,其物理層接口、數(shù)據(jù)傳輸模式和工作模式等都能根據(jù)需要而動(dòng)態(tài)調(diào)整,并可通過內(nèi)部寄存器的設(shè)置來適應(yīng)不同的應(yīng)用環(huán)境。1)特性
(1)最大工作電流55mA;
(2)支持廣泛的軟件驅(qū)動(dòng);
(3)?3V供電電壓;
(4)工業(yè)級溫度范圍;
(5)全雙工通信方式;
(6)可編程發(fā)送功能;
(7)數(shù)據(jù)碰撞自動(dòng)重發(fā);
(8)自動(dòng)打包及生成CRC校驗(yàn)碼;
(9)可編程接收功能;
(10)數(shù)據(jù)流降低CPU消耗;
(11)自動(dòng)切換于DMA和片內(nèi)RAM;(12)提前產(chǎn)生中斷,便于數(shù)據(jù)幀預(yù)處理;
(13)自動(dòng)阻斷錯(cuò)誤包;
(14)可跳線控制EEPROM功能;
(15)啟動(dòng)編程支持無盤系統(tǒng);
(16)邊沿掃描和回環(huán)測試;
(17)?LED驅(qū)動(dòng)器用于指示連接狀態(tài)和網(wǎng)絡(luò)活動(dòng)情況;
(18)待機(jī)和睡眠模式;
(19)?TQFP-100封裝。
CS8900的功能框圖如圖9-4所示。圖9-4CS8900的功能框圖
2)工作原理
CS8900A收到由主機(jī)發(fā)來的數(shù)據(jù)報(bào)(從目的地址域到數(shù)據(jù)域)后,偵聽網(wǎng)絡(luò)線路。如果線路忙,它就等到線路空閑為止,否則立即發(fā)送該數(shù)據(jù)幀。發(fā)送過程中,首先添加以太網(wǎng)幀頭(包括先導(dǎo)字段和幀開始標(biāo)志),然后生成CRC校驗(yàn)碼,最后將此數(shù)據(jù)幀發(fā)送到以太網(wǎng)上。接收時(shí),它將從以太網(wǎng)收到的數(shù)據(jù)幀在經(jīng)過解碼、去掉幀頭和地址檢驗(yàn)等步驟后緩存在片內(nèi)。通過CRC校驗(yàn)后,它會根據(jù)初始化配置情況,通知主機(jī)CS8900A收到數(shù)據(jù)幀,最后用上面介紹的某種傳輸模式傳到主機(jī)的存儲區(qū)中。
3)引腳定義
CS8900A采用TQFP-100封裝,引腳多達(dá)100個(gè),但在實(shí)際使用中并不需要把它們?nèi)恳鰜?。CS8900A中一些比較重要的引腳定義和說明如表9-2所示。對于表9-2中沒有提到的引腳,有一些具有很明確的定義。例如:XTALl、XTAL2,它們是用來連接外部20MHz的晶振;AVDD、DVDD分別是用來連接模擬和數(shù)字的電源;NC代表引腳是不連接的。
2.?CS8900A的操作方法
基于對功耗和布板的要求,嵌入式系統(tǒng)大都采用比較簡單的I/O模式。本節(jié)所介紹的CS8900A就是工作在I/O模式下,其操作方法也是基于I/O模式的。
1)?CS8900A的初始化
CS8900A在上電以后,默認(rèn)的操作模式是I/O模式,因此需要以I/O模式的操作方法進(jìn)行初始化。
CS8900A的初始化主要由SBHE(SystemBusHighEnable)引腳參與。初始化時(shí)需在SBHE引腳上產(chǎn)生一個(gè)由高到低,然后由低到高的電平。完成這個(gè)操作之后,才可以對該芯片進(jìn)行讀/寫操作。由于在正常工作時(shí)CS8900A是以字(16位)為單位尋址的,故處理器的地址線A0引腳不用于尋址。在以太網(wǎng)電路設(shè)計(jì)時(shí),可把A0引腳連接到CS8900A的SBHE引腳,讓處理器通過訪問A0完成初始化任務(wù)。這樣,在對CS8900A進(jìn)行初始化時(shí),實(shí)際上只要首先進(jìn)行從奇數(shù)地址到偶數(shù)地址尋址,然后再進(jìn)行一次從偶數(shù)地址到奇數(shù)地址尋址,就可以在SBHE引腳產(chǎn)生一個(gè)從高到低,然后由低到高的電平變化,從而實(shí)現(xiàn)該芯片的初始化。
2)?CS8900A的I/O模式寄存器
在I/O模式下,CS8900A只有6個(gè)寄存器可以直接尋址訪問,它們叫做I/O模式寄存器,如表9-3所示。對這些寄存器的訪問采用直接尋址的方式。按照本章所介紹的電路圖,CS8900A連接片選1的地址空間,因而地址計(jì)算公式為:地址?=?片選1的地址+I/O基址+偏移地址。
下面以計(jì)算片內(nèi)寄存器指針(PacketPagePointer)寄存器地址為例,說明寄存器地址的計(jì)算。PXA255處理器的片選1的地址為0x04000000;CS8900A默認(rèn)的I/O基址為0x300;根據(jù)表9-3,片內(nèi)寄存器指針寄存器的偏移地址為0x00a,因此,片內(nèi)寄存器指針寄存器的訪問地址為:0x04000000+0x300+0x00a
=0x0400030a。
3)?CS8900A片內(nèi)寄存器的讀/寫
CS8900A片內(nèi)寄存器就存在它的片內(nèi)RAM中。這些片內(nèi)寄存器在I/O模式下不能被直接訪問,對它們的讀/寫須通過前述的I/O模式寄存器進(jìn)行。
片內(nèi)寄存器的操作與片內(nèi)寄存器指針寄存器和數(shù)據(jù)端口寄存器有關(guān),參見表9-3。
假設(shè)需要讀片內(nèi)寄存器A,則必須先往片內(nèi)寄存器指針寄存器寫入A寄存器的片內(nèi)相對地址;然后寄存器就會被映射到數(shù)據(jù)端口(PacketPageData(Port0))寄存器上,也就是說,此時(shí)對數(shù)據(jù)端口寄存器的讀/寫就相當(dāng)于對A寄存器的讀/寫。下面以讀取產(chǎn)品ID寄存器為例,說明讀取寄存器的方法。產(chǎn)品ID寄存器是個(gè)只讀寄存器,片內(nèi)相對地址為0x0000,該寄存器中保存芯片的產(chǎn)品信息。若要讀取該寄存器的值,則需先向片內(nèi)寄存器指針寄存器寫入產(chǎn)品ID寄存器的片內(nèi)相對地址0x0000,接著向數(shù)據(jù)端口寄存器(PacketPageData(Por0))連續(xù)讀取兩次數(shù)據(jù),就可以讀到CS8900A的ID信息0x0a00630e。
片內(nèi)寄存器的寫操作與讀操作類似,也必須先往片內(nèi)寄存器指針寄存器寫入須訪問寄存器的片內(nèi)相對地址;然后再往數(shù)據(jù)端口寄存器寫入指定的值。
對于介紹的CS8900A的片內(nèi)寄存器的讀/寫操作,都須使用這種方法進(jìn)行。這種方法的實(shí)質(zhì)就是通過“寫”來讀寄存器,同樣也通過“寫”來寫寄存器。9.2.3基于CS8900A的網(wǎng)絡(luò)驅(qū)動(dòng)程序?qū)嵗?/p>
CS8900A適配器驅(qū)動(dòng)程序在Linux內(nèi)核中已經(jīng)提供,下面以內(nèi)核2.6.10版本中的代碼CS8900A適配器驅(qū)動(dòng)程序?yàn)榉治鰧ο?,講述網(wǎng)絡(luò)驅(qū)動(dòng)程序的開發(fā)過程。首先分析CS8900A模塊的初始化。
1.初始化
CS8900A適配器的初始化主要由net_device數(shù)據(jù)結(jié)構(gòu)中的init函數(shù)指針?biāo)赶虻某跏蓟瘮?shù)來完成,當(dāng)內(nèi)核啟動(dòng)或加載網(wǎng)絡(luò)驅(qū)動(dòng)模塊時(shí),就會調(diào)用這個(gè)初始化函數(shù)。在初始化函數(shù)中通過檢測物理設(shè)備的硬件特征來偵測網(wǎng)絡(luò)物理設(shè)備是否存在,然后再對設(shè)備進(jìn)行資源配置,接下來構(gòu)造device數(shù)據(jù)結(jié)構(gòu),并用檢測到的數(shù)據(jù)對net_device的變量進(jìn)行初始化,最后向Linux內(nèi)核注冊設(shè)備并申請內(nèi)存空間。首先,定義一個(gè)全局cs8900_dev,它為net_device結(jié)構(gòu)的對象,并且定義cs8900_dev對象的初始化函數(shù)為cs8900_probe(),代碼如下:
staticstructnet_devicecs8900_dev=
{
init:cs8900_probe
}接下來,分析cs8900_probe()函數(shù)的具體實(shí)現(xiàn),它主要用于對網(wǎng)卡的檢測,并初始化系統(tǒng)中的網(wǎng)絡(luò)設(shè)備信息,用于后面的網(wǎng)絡(luò)數(shù)據(jù)的發(fā)送和接收。它的完整代碼實(shí)現(xiàn)如下:接下來對上述代碼進(jìn)行逐行分析,第3行cs8900_t結(jié)構(gòu)是專門定義的一個(gè)用于描述網(wǎng)絡(luò)設(shè)備私有信息的結(jié)構(gòu),其結(jié)構(gòu)包含的成員有網(wǎng)絡(luò)設(shè)備的統(tǒng)計(jì)信息結(jié)構(gòu)成員(stats)、傳輸數(shù)據(jù)長度成員(txlen)、該網(wǎng)絡(luò)設(shè)備所包含的字符設(shè)備個(gè)數(shù)(char_devnum)成員以及自旋鎖(lock)成員。第8行用ether_setup()函數(shù)給網(wǎng)絡(luò)設(shè)備填充以太網(wǎng)相關(guān)的值,如MTU值、地址長度、以太網(wǎng)類型等信息。第9~14行用來初始化網(wǎng)絡(luò)設(shè)備的方法,包括open、stop、hard_start_xmit等。由于我們的程序是基于SMDK2410開發(fā)板的,因此內(nèi)核中會配置CONFIG_ARCH_SMDK2410這個(gè)選項(xiàng),在以下關(guān)于這個(gè)選項(xiàng)的條件編譯代碼中,都會執(zhí)行到它所包含的代碼。第17~22行用來定義網(wǎng)絡(luò)設(shè)備的MAC地址,也就是硬件地址(48位長度)。第31行定義網(wǎng)絡(luò)設(shè)備的接口介質(zhì)類型為10Base-T。第32行將cs8900_t結(jié)構(gòu)對象的地址賦給網(wǎng)絡(luò)設(shè)備的私有數(shù)據(jù)成員priv。第33行以cs8900_t結(jié)構(gòu)對象的自選鎖成員lock為參數(shù),傳遞給宏spin_lock_init,用于動(dòng)態(tài)初始化自選鎖。第35和36行分別定義以太網(wǎng)設(shè)備的I/O地址和中斷號。第38~40行用來檢測網(wǎng)絡(luò)設(shè)備I/O地址空間是否可用。第41行表示如果該網(wǎng)絡(luò)設(shè)備IYO地址可用,則注冊這塊區(qū)域。第42~44行用于檢查EISA是否正確。第45~48行用于驗(yàn)證CS8900A芯片的版本號是否正確。第49行用來設(shè)置網(wǎng)絡(luò)設(shè)備的中斷號。第50~64行用來確定網(wǎng)絡(luò)設(shè)備是否有EEPROM;由于我們的系統(tǒng)中沒有EEPROM設(shè)備和其他的配置塊,因此需要手動(dòng)配置MAC地址。第66~70行用來讀取MAC地址。第72行表示在一切都正確的情況下返回0,即網(wǎng)絡(luò)設(shè)備接口初始化完成。到這里為止,已經(jīng)完整講述了網(wǎng)絡(luò)設(shè)備接口的初始化函數(shù)cs8900_probe(),那么有人會問誰會調(diào)用cs8900_probe()函數(shù)呢?其實(shí)前面已經(jīng)說過,當(dāng)在內(nèi)核啟動(dòng)或加載網(wǎng)絡(luò)驅(qū)動(dòng)模塊的時(shí)候,就會調(diào)用這個(gè)初始化函數(shù),這里我們是以模塊加載形式來調(diào)用的,它的加載函數(shù)為cs8900_init(),具體實(shí)現(xiàn)代碼如下:
staticint__intcs8900_init(void)
{
strcpy(cs8900_,“eth%d”);
return(register_netdev(&cs8900_dev));
}該加載函數(shù)實(shí)現(xiàn)的功能是:首先給網(wǎng)絡(luò)設(shè)備取名,然后傳遞參數(shù)cs8900_dev的地址到網(wǎng)絡(luò)設(shè)備注冊函數(shù)register_netdev(),從而完成注冊網(wǎng)絡(luò)設(shè)備的任務(wù)。網(wǎng)絡(luò)設(shè)備的卸載函數(shù)的作用與加載函數(shù)相反,其實(shí)現(xiàn)代碼如下:該卸載函數(shù)實(shí)現(xiàn)的功能是:首先判斷網(wǎng)絡(luò)設(shè)備中是否定義了字符設(shè)備EEPROM,如果定義了,則首先卸載它,實(shí)際在我們的系統(tǒng)中沒有使用EEPROM,所以就不用執(zhí)行注銷字符設(shè)備了;然后釋放網(wǎng)絡(luò)設(shè)備的I/O地址空間;最后,注銷網(wǎng)絡(luò)設(shè)備cs8900_dev。
2.?open和stop方法
1)?open方法
網(wǎng)絡(luò)設(shè)備的open方法就是激活網(wǎng)絡(luò)接口,使它能接收來自網(wǎng)絡(luò)的數(shù)據(jù)并且將其傳遞到網(wǎng)絡(luò)協(xié)議棧的上層,也可以將數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)上。cs8900_dev設(shè)備的open方法實(shí)現(xiàn)代碼如下:上述代碼中的第5行用于設(shè)定網(wǎng)絡(luò)設(shè)備的中斷觸發(fā)類型為上升沿觸發(fā)。第7~16行用于配置以太網(wǎng)控制寄存器,包括接收總線配置寄存器PP_RxCFG、接收控制寄存器PP_RxCTL、發(fā)送配置寄存器PP_TxCFG、緩沖配置寄存器PP_BufCFG、線控制寄存器PP_LineCTL、總線控制寄存器PP_BusCTL和測試控制寄存器PP_TestCTL。這幾個(gè)寄存器的作用如下:
(1)?PP_RxCFG用來確定如何傳輸幀到主機(jī)和哪種類型幀被觸發(fā)中斷;
(2)?PP_RxCTL用來表示位8、C、D和E定義接收什么樣的幀,位6、7、9、A和B配置目的地址過濾器;
(3)?PP_TxCFG用來配置發(fā)送數(shù)據(jù)相關(guān)的中斷是否可用;
(4)?PP_BufCFG用來配置總線緩沖相關(guān)的中斷是否可用;
(5)?PP_LineCTL用來配置MAC引擎和物理接口;
(6)?PP_BusCTL用來配置ISA總線接口操作;
(7)?PP_TestCTL用來配置CS8900A的診斷測試模式。
上述代碼中的第20~23行用于注冊網(wǎng)絡(luò)設(shè)備的中斷服務(wù)程序cs8900_interrupt(后面會詳細(xì)講述這個(gè)中斷服務(wù)程序)。如果中斷服務(wù)程序注冊成功,則將執(zhí)行第45行,即用來啟動(dòng)一個(gè)網(wǎng)絡(luò)接口隊(duì)列,最后返回一個(gè)0,代表網(wǎng)絡(luò)設(shè)備被正確打開。
2)?stop方法
stop方法即停止網(wǎng)絡(luò)設(shè)備,它的作用與open方法相反,具體實(shí)現(xiàn)代碼如下:上述代碼中的第4~11行用來禁止以太網(wǎng)控制器各相應(yīng)的寄存器。第13行用于釋放網(wǎng)絡(luò)設(shè)備的中斷服務(wù)程序。第15行用于停止網(wǎng)絡(luò)設(shè)備接口隊(duì)列。最后返回0,表示完成停止設(shè)備。
3.數(shù)據(jù)發(fā)送
網(wǎng)絡(luò)設(shè)備數(shù)據(jù)發(fā)送是由net_device結(jié)構(gòu)中的hard_start_xmit方法實(shí)現(xiàn)的,所有的網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序都必須有這個(gè)發(fā)送方法。在驅(qū)動(dòng)程序?qū)哟沃?,發(fā)送和接收數(shù)據(jù)都是通過系統(tǒng)低層驅(qū)動(dòng)對硬件的讀/寫來完成的。根據(jù)初始化函數(shù)cs8900_probe()定義網(wǎng)絡(luò)設(shè)備的發(fā)送實(shí)現(xiàn)函數(shù)為cs8900_send_start(),其實(shí)現(xiàn)代碼如下:上述代碼中,在系統(tǒng)調(diào)用驅(qū)動(dòng)程序的hard_start_xmit方法時(shí),發(fā)送的數(shù)據(jù)放在一個(gè)sk_buff結(jié)構(gòu)中,也就是前面講過的套接字緩沖結(jié)構(gòu)。第5行用來獲得一個(gè)禁止中斷的自旋鎖。第6行為關(guān)閉網(wǎng)絡(luò)接口隊(duì)列。第7行和第8行為主機(jī)在發(fā)送數(shù)據(jù)前寫寄存器PP_TxCMD,利用這個(gè)命令告訴CS8900A主機(jī)有一個(gè)數(shù)據(jù)幀要傳輸,并且告訴如何傳輸這個(gè)幀。此外,寫PP_TxLength寄存器用來告訴將要傳輸數(shù)據(jù)幀的長度。第9行用來讀取PP_BusST寄存器來獲得當(dāng)前傳輸操作的狀態(tài)。第10~16行用于因數(shù)據(jù)幀的大小不符合要求而導(dǎo)致數(shù)據(jù)傳輸失敗(比如數(shù)據(jù)幀大于1518字節(jié))時(shí)釋放自旋鎖,設(shè)置數(shù)據(jù)傳輸長度為0,返回1。第17~22行用于因傳輸緩沖空間沒有準(zhǔn)備好而導(dǎo)致數(shù)據(jù)傳輸失敗時(shí)釋放自旋鎖,設(shè)置數(shù)據(jù)傳輸長度為0,返回1。第23~28行用于當(dāng)傳輸數(shù)據(jù)的狀態(tài)都正常時(shí),將skb指向的數(shù)據(jù)幀發(fā)送給CS8900A,然后釋放自旋鎖并且釋放skb指向的內(nèi)存空間,最后返回0,表示傳輸數(shù)據(jù)正確。
4.數(shù)據(jù)接收
數(shù)據(jù)包的接收實(shí)現(xiàn)方式不同于數(shù)據(jù)發(fā)送,可利用hard_start_xmit方法實(shí)現(xiàn),它是通過中斷機(jī)制來完成的。當(dāng)有數(shù)據(jù)到達(dá)時(shí)產(chǎn)生中斷信號,網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)功能層調(diào)用中斷處理程序(即數(shù)據(jù)包接收程序)來處理數(shù)據(jù)包的接收,隨后網(wǎng)絡(luò)協(xié)議接口層調(diào)用netif_rx()函數(shù)把接收到的數(shù)據(jù)包傳輸?shù)骄W(wǎng)絡(luò)協(xié)議的上層進(jìn)行處理。CS8900A的中斷服務(wù)程序在講述open方法時(shí)已經(jīng)提到,它是利用cs8900_interrupt()函數(shù)來實(shí)現(xiàn)的,其具體實(shí)現(xiàn)代碼如下:上述代碼中,當(dāng)觸發(fā)一個(gè)中斷時(shí),首先通過讀取PP_ISQ寄存器來判斷產(chǎn)生中斷的類型,這是在第13行代碼實(shí)現(xiàn)的。第15~54行根據(jù)獲得的中斷類型來執(zhí)行相應(yīng)的處理,其中有5種中斷類型,按優(yōu)先級順序分別為RxEvent、TxEvent、BufEvent、RxMISS、TxCOL。每次主控制器讀ISQ(InterruptStatusQueue)寄存器,相應(yīng)寄存器中產(chǎn)生中斷的位將清零,下一個(gè)中斷報(bào)告將會自動(dòng)移到中斷隊(duì)列最前面。
數(shù)據(jù)接收的核心功能是在RxEvent中斷類型的處理程序中由cs8900_receive()函數(shù)來完成的。cs8900_receive()函數(shù)實(shí)現(xiàn)了網(wǎng)絡(luò)數(shù)據(jù)接收的核心功能。cs8900_receive()函數(shù)的實(shí)現(xiàn)代碼如下:上述代碼中的第4行定義了一個(gè)sk_buff(套接字)結(jié)構(gòu)的指針skb,用來存放要傳遞的數(shù)據(jù)信息。第8~13行表示首先讀取接收數(shù)據(jù)寄存器的狀態(tài)是否正常,如果讀取的狀態(tài)不正確,則將這些錯(cuò)誤信息賦給相應(yīng)的統(tǒng)計(jì)變量,最后返回。第20行用于將CS8900A的數(shù)據(jù)幀讀取到skb指向的內(nèi)存中。第24行用于確定傳輸數(shù)據(jù)協(xié)議類型,比如802.3、802.2協(xié)議等。第25行用于調(diào)用netif_rx()把skb中存放的數(shù)據(jù)傳送給協(xié)議層,它是網(wǎng)絡(luò)數(shù)據(jù)接收中必須使用的函數(shù)。
9.3Linux網(wǎng)絡(luò)編程實(shí)現(xiàn)
9.3.1socket基本函數(shù)
本節(jié)介紹編寫網(wǎng)絡(luò)程序時(shí)使用的基本套接字函數(shù)。因?yàn)槲覀兙帉懙木W(wǎng)絡(luò)程序主要是使用TCP套接字,所以這一節(jié)主要討論TCP套接字使用這些函數(shù)的情況。
圖9-5描述了TCP客戶機(jī)和服務(wù)器之間的交互過程,這是使用TCP協(xié)議進(jìn)行網(wǎng)絡(luò)通信的程序的基本模型。圖9-5TCP客戶機(jī)和服務(wù)器通信模型服務(wù)器程序首先進(jìn)行初始化操作:調(diào)用函數(shù)socket創(chuàng)建一個(gè)套接字,函數(shù)bind將這個(gè)套接字與服務(wù)器的公認(rèn)地址綁定在一起,函數(shù)listen將這個(gè)套接字轉(zhuǎn)換成傾聽套接字(1isteningsocket),然后調(diào)用函數(shù)accept來等待接收客戶機(jī)的請求。過了一段時(shí)間之后,客戶機(jī)啟動(dòng),調(diào)用函數(shù)socket創(chuàng)建一個(gè)套接字,然后調(diào)用函數(shù)connect來與服務(wù)器建立連接。
連接建立之后,客戶機(jī)和服務(wù)器通過讀、寫套接字來進(jìn)行通信。
下面詳細(xì)討論這個(gè)通信過程所使用的套接字函數(shù)。
1.函數(shù)socket
函數(shù)socket用于創(chuàng)建一個(gè)套接字描述符。其定義如下:
#include<sys/types.h>
#include<sys/socket.h>
Intsocket(intdomain,inttype,intprotocol)
參數(shù)domain指定要?jiǎng)?chuàng)建的套接字的協(xié)議簇;參數(shù)type指定套接字類型;參數(shù)protocol指定使用哪種協(xié)議。函數(shù)socket成功執(zhí)行時(shí),返回一個(gè)正整數(shù),稱為套接字描述符,標(biāo)識這個(gè)套接字;否則,返回-1,并設(shè)置全局變量errno為相應(yīng)的錯(cuò)誤類型。
Linux系統(tǒng)的套接字編程接口(API)是一個(gè)通用的網(wǎng)絡(luò)編程接口,它可以訪問多種通信協(xié)議,如TCP/IP協(xié)議和Unix域協(xié)議等。在創(chuàng)建一個(gè)套接字時(shí)需要在參數(shù)domain中指定使用哪種協(xié)議簇。參數(shù)domain可以是如下值:
(1)?AF_Unix:Unix域協(xié)議簇,本機(jī)的進(jìn)程間通信時(shí)使用。
(2)?AF_INET:Internet協(xié)議簇(TCP/IP)。
(3)?AF_ISO:ISO協(xié)議簇。參數(shù)type指定套接字類型,可以是如下值:
(1)?SOCK_STREAM:流套接字,面向連接的和可靠的通信類型。
(2)?SOCK_DGRAM:數(shù)據(jù)報(bào)套接字,非面向連接的和不可靠的通信類型。
(3)?SOCK_RAW:原始套接字,只對Internet協(xié)議有效,可以用來直接訪問端口的協(xié)議。
參數(shù)protocol通常設(shè)置為0,表示使用默認(rèn)協(xié)議,如Internet協(xié)議簇的流套接字使用TCP協(xié)議,而數(shù)據(jù)報(bào)套接字使用UDP協(xié)議。當(dāng)套接字是原始套接字類型時(shí),需要指定參數(shù)protocol,因?yàn)樵继捉幼謱Χ喾N協(xié)議有效,如ICMP和IGMP等。創(chuàng)建一個(gè)TCP套接字的操作一般如下:
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0){
fprintf(stderr,“socketerror:%s\n,”,strerror(errno));
exit(1);
}
Linux系統(tǒng)中創(chuàng)建一個(gè)套接字的操作主要是:在內(nèi)核中創(chuàng)建一個(gè)套接字?jǐn)?shù)據(jù)結(jié)構(gòu),然后返回一個(gè)套接字描述符標(biāo)識這個(gè)套接字?jǐn)?shù)據(jù)結(jié)構(gòu)。這個(gè)套接字?jǐn)?shù)據(jù)結(jié)構(gòu)包含連接的各種信息,如對方地址、TCP狀態(tài)以及發(fā)送和接收緩沖區(qū)等。TCP協(xié)議根據(jù)這個(gè)套接字?jǐn)?shù)據(jù)結(jié)構(gòu)的內(nèi)容來控制這條連接。
2.函數(shù)connect
函數(shù)connect與服務(wù)器建立一個(gè)連接。其定義如下:
#include<sys/types.h>
#include<sys/socket.h>
intconnect(intsockfd,structsockaddr*servaddr,intaddrlen);
參數(shù)sockfd是函數(shù)socket返回的套接字描述符;參數(shù)servaddr指定遠(yuǎn)程服務(wù)器的套接字地址,包括服務(wù)器的IP地址和端口號;參數(shù)addrlen指定這個(gè)套接字地址的長度。函數(shù)connect成功執(zhí)行時(shí),返回0;否則返回1,并設(shè)置全局變量errno為以下任何一種錯(cuò)誤類型:EIIMEOUT、ECONNREFUSED、EHOSTUNRFACH或ENETUNREACH。在調(diào)用函數(shù)connect之前,客戶機(jī)需要指定服務(wù)器進(jìn)程的套接字地址。建立一個(gè)TCP連接的操作一般如下:客戶機(jī)一般不用指定自己的套接字地址(IP地址和端口號),系統(tǒng)會自動(dòng)從1024至5000的端口號范圍內(nèi)為它選擇一個(gè)未用的端口號,然后以這個(gè)端口號和本機(jī)的IP地址填充這個(gè)套接字地址。
客戶機(jī)調(diào)用函數(shù)connect來主動(dòng)建立連接。這個(gè)函數(shù)將啟動(dòng)TCP協(xié)議的3次握手過程。在連接建立之后或發(fā)生錯(cuò)誤時(shí),函數(shù)返回。連接過程中可能有如下幾種錯(cuò)誤情況:
(1)如果客戶機(jī)TCP協(xié)議沒有接收到對它的SYN數(shù)據(jù)段的確認(rèn),則函數(shù)以錯(cuò)誤返回,錯(cuò)誤類型為ETIMEOUT。通常TCP協(xié)議在發(fā)送SYN數(shù)據(jù)段失敗之后,會多次發(fā)送SYN數(shù)據(jù)段,在所有的發(fā)送都告失敗之后,函數(shù)以錯(cuò)誤返回。
(2)如果遠(yuǎn)程TCP協(xié)議返回一個(gè)RST數(shù)據(jù)段,則函數(shù)立即以錯(cuò)誤返回,錯(cuò)誤類型為ECONNREFUSED。當(dāng)遠(yuǎn)程機(jī)器在SYN數(shù)據(jù)段指定的目的端口號處沒有服務(wù)器進(jìn)程在等待連接時(shí),遠(yuǎn)程機(jī)器的TCP協(xié)議將發(fā)送一個(gè)RST數(shù)據(jù)段,向客戶機(jī)報(bào)告這個(gè)錯(cuò)誤??蛻魴C(jī)的TCP協(xié)議在接收到RST數(shù)據(jù)段之后,不再繼續(xù)發(fā)送SYN數(shù)據(jù)段,函數(shù)立即以錯(cuò)誤返回。
(3)如果客戶機(jī)的SYN數(shù)據(jù)段導(dǎo)致某個(gè)路由器產(chǎn)生“目的地不可到達(dá)”類型的ICMP消息,則函數(shù)以錯(cuò)誤返回,錯(cuò)誤類型為EHOSTUNREACH或ENETUNREACH。通常TCP協(xié)議在接收到ICMP消息之后,記錄這個(gè)消息,然后繼續(xù)幾次發(fā)送SYN數(shù)據(jù)段,在所有的發(fā)送都宣告失敗之后,TCP協(xié)議檢查這個(gè)ICMP消息,函數(shù)以錯(cuò)誤返回。在調(diào)用函數(shù)connect的過程中,當(dāng)客戶機(jī)TCP協(xié)議發(fā)送了SYN數(shù)據(jù)段之后,客戶機(jī)的TCP狀態(tài)由CLOSED狀態(tài)轉(zhuǎn)換成SYN_SENT狀態(tài),在接收到對SYN數(shù)據(jù)段的確認(rèn)之后,TCP狀態(tài)轉(zhuǎn)換成ESTABLISHED狀態(tài),函數(shù)成功返回。如果調(diào)用函數(shù)connect失敗,應(yīng)該用函數(shù)close關(guān)閉這個(gè)套接字描述符,不能再次用這個(gè)套接字描述符來調(diào)用函數(shù)connect。
3.函數(shù)bind
函數(shù)bind將本地地址與套接字綁定在一起。其定義如下:
#include<sys/types.h>
#include<sys/socket.h>
intbind(intsockfd,structsockaddr*myaddr,intaddrlen);
參數(shù)sockfd是函數(shù)socket返回的套接字描述符;參數(shù)myaddr是本地地址;參數(shù)addrlen是套接字地址結(jié)構(gòu)的長度。函數(shù)bind成功執(zhí)行時(shí),返回0;否則,返回-1,并設(shè)置全局變量errno為錯(cuò)誤類型EADDRINUSER。服務(wù)器和客戶機(jī)都可以通過調(diào)用函數(shù)bind來綁定套接字地址,但一般是服務(wù)器調(diào)用函數(shù)bind來綁定自己的公認(rèn)端口號。綁定操作一般如下:
bzero(&myaddr,sizeof(myaddr));
myaddr.sin_family=AF_INET;
myaddr.sin_port=htons(PORT);
myaddr.sin_addr.s_addr=htonl(INADDR_ANY);
if(bind(sockfd,(structsockaddr*)&myaddr,sizeof(myaddr))<0){
fprintf(stderr,“Bindtoport%derror\n”,PORT);
exit(l)
}
綁定操作一般有如下幾種組合方式,見表9-4??蛻魴C(jī)和服務(wù)器的其他口地址與端口號組合方式?jīng)]有什么意義,我們不予討論。
下面詳細(xì)說明表9-4中列出的5種方式。
(1)服務(wù)器指定套接字地址的公認(rèn)端口號,不指定IP地址。服務(wù)器調(diào)用函數(shù)bind時(shí),如果設(shè)置套接字的IP地址為特殊的INADDR_ANY,則表示它愿意接收來自任何網(wǎng)絡(luò)設(shè)備接口的客戶機(jī)連接。這是服務(wù)器經(jīng)常使用的綁定方式。
(2)服務(wù)器指定套接字地址的公認(rèn)端口號和IP地址。服務(wù)器調(diào)用函數(shù)bind時(shí),如果設(shè)置套接字的IP地址為某個(gè)本地IP地址,則表示服務(wù)器只接收來自對應(yīng)于這個(gè)IP地址的特定網(wǎng)絡(luò)設(shè)備接口的客戶機(jī)連接。當(dāng)這臺機(jī)器只有一個(gè)網(wǎng)絡(luò)設(shè)備接口時(shí),這和第一種情況是沒有區(qū)別的,但當(dāng)這臺機(jī)器有多個(gè)網(wǎng)絡(luò)設(shè)備接口時(shí),我們可以用這種方式來限制服務(wù)器的接收范圍。
(3)客戶機(jī)指定套接字地址的連接端口號。在一般情況下,客戶機(jī)不用指定自己的套接字地址的端口號,當(dāng)客戶機(jī)調(diào)用函數(shù)connect進(jìn)行TCP連接時(shí),系統(tǒng)會自動(dòng)為它選擇一個(gè)未用的端口號,并且用本地的IP地址來填充套接字地址中的相應(yīng)項(xiàng)。但在有的情況下,客戶機(jī)需要使用特定端口號,如Linux系統(tǒng)中的rlogin命令,因?yàn)閞login命令需要使用保留端口號,而系統(tǒng)不會為客戶機(jī)自動(dòng)分配一個(gè)保留端口號,所以需要調(diào)用函數(shù)bind來和一個(gè)未用的保留端口號綁定。
(4)指定客戶機(jī)的IP地址和連接端口號。在一般情況下,客戶機(jī)使用指定的網(wǎng)絡(luò)設(shè)備接口和端口號進(jìn)行通信。
(5)指定客戶機(jī)的IP地址??蛻魴C(jī)使用指定的網(wǎng)絡(luò)設(shè)備接口進(jìn)行通信,系統(tǒng)自動(dòng)為客戶機(jī)選擇一個(gè)未用的端口號。一般只有在主機(jī)有多個(gè)網(wǎng)絡(luò)設(shè)備接口時(shí)使用。
我們在編寫客戶機(jī)程序時(shí),一般不使用固定的客戶機(jī)端口號,除非是在必須使用特定端口號的情況下。固定客戶機(jī)端口號時(shí)會帶來一些不便,需要考慮如下兩種情況:①服務(wù)器執(zhí)行主動(dòng)關(guān)閉操作(如HTTP服務(wù)器)。服務(wù)器最后進(jìn)入TIME_WAIT狀態(tài)。當(dāng)客戶機(jī)再次與這個(gè)服務(wù)器進(jìn)行連接時(shí),仍使用相同的客戶機(jī)端口號,于是這個(gè)連接與前次連接的套接字對完全一樣,但是因?yàn)榍按芜B接處于TIME_WAIT狀態(tài),并未消失,所以這次連接請求被拒絕,函數(shù)connect以錯(cuò)誤返回,錯(cuò)誤類型為ECONNREFUSED。
②客戶機(jī)執(zhí)行主動(dòng)關(guān)閉操作(如FFP客戶機(jī))??蛻魴C(jī)最后進(jìn)入TIME_WAIT狀態(tài)。當(dāng)馬上再次執(zhí)行這個(gè)客戶機(jī)程序時(shí),客戶機(jī)將繼續(xù)與這個(gè)固定客戶機(jī)端口號綁定,但因?yàn)榍按芜B接處于TIME_WAlT狀態(tài),并未消失,系統(tǒng)會發(fā)現(xiàn)這個(gè)端口號仍被占用,所以這次綁定操作失敗,函數(shù)bind以錯(cuò)誤返回,錯(cuò)誤類型為EADDRINUSE。
4.函數(shù)listen
函數(shù)listen將一個(gè)套接字轉(zhuǎn)換為傾聽套接字(1isteningsocket)。其定義如下:
#include<sys/socket.h>
intlisten(intsockfd,intbacklog);
其中:參數(shù)sockfd指定要轉(zhuǎn)換的套接字描述符;參數(shù)backlog表示設(shè)置請求隊(duì)列的最大長度。函數(shù)listen成功執(zhí)行時(shí),返回0;否則返回?-1。服務(wù)器需要調(diào)用函數(shù)listen將套接字轉(zhuǎn)換成傾聽套接字,以便接收客戶機(jī)請求。函數(shù)listen的功能有兩個(gè):
(1)函數(shù)socket創(chuàng)建的套接字是主動(dòng)套接字,可以用它來進(jìn)行主動(dòng)連接(調(diào)用函數(shù)connect),但是不能接收連接請求,而服務(wù)器的套接字必須能夠接收客戶機(jī)的請求。函數(shù)listen將一個(gè)尚未連接的主動(dòng)套接字轉(zhuǎn)換成為一個(gè)被動(dòng)套接字:告訴TCP協(xié)議,這個(gè)套接字可以接收連接請求。執(zhí)行函數(shù)listen之后,服務(wù)器的TCP狀態(tài)由CLOSED狀態(tài)轉(zhuǎn)換成LISTEN狀態(tài)。
(2)?TCP協(xié)議將到達(dá)的連接請求排隊(duì),函數(shù)listen的第二個(gè)參數(shù)指定這個(gè)隊(duì)列的最大長度。
要?jiǎng)?chuàng)建一個(gè)傾聽套接字,必須首先調(diào)用函數(shù)socket創(chuàng)建一個(gè)主動(dòng)套接字,然后調(diào)用函數(shù)bind將它與服務(wù)器套接字地址綁定在一起,最后調(diào)用函數(shù)listen進(jìn)行轉(zhuǎn)換。這3步操作是所有TCP服務(wù)器所必需的操作。下面討論參數(shù)backlog的作用,這對于理解套接字建立連接的過程非常重要。TCP協(xié)議為每個(gè)傾聽套接字維護(hù)兩個(gè)隊(duì)列,即未完成連接隊(duì)列和完成連接隊(duì)列。
(1)未完成連接隊(duì)列。每個(gè)尚未完成3次握手操作的TCP連接在這個(gè)隊(duì)列中占有一項(xiàng)。TCP協(xié)議在接收到一個(gè)客戶機(jī)SYN數(shù)據(jù)段之后,在這個(gè)隊(duì)列中創(chuàng)建一個(gè)新條目,然后發(fā)送對客戶機(jī)SYN數(shù)據(jù)段的確認(rèn)和自己的SYN數(shù)據(jù)段(ACK+SYN數(shù)據(jù)段),并等待客戶機(jī)對自己的SYN數(shù)據(jù)段的確認(rèn)。此時(shí),套接字處于SYN_RCVD狀態(tài)。這個(gè)條目將保存在這個(gè)隊(duì)列中,直到客戶機(jī)返回對SYN數(shù)據(jù)段的確認(rèn),或者連接超時(shí)。
(2)完成連接隊(duì)列。每個(gè)已經(jīng)完成3次握手操作,但尚未被應(yīng)用程序接收(調(diào)用函數(shù)accept)的TCP連接在這個(gè)隊(duì)列中占有一項(xiàng)。當(dāng)一個(gè)在未完成連接隊(duì)列中的連接接收到對SYN數(shù)據(jù)段的確認(rèn)之后,完成3次握手操作,TCP協(xié)議將它從未完成連接隊(duì)列移到完成連接隊(duì)列中。此時(shí),套接字處于ESTABLISHED狀態(tài)。這個(gè)條目將保存在這個(gè)隊(duì)列中,直到應(yīng)用程序調(diào)用函數(shù)accept來接收它。圖9-6描述的是一個(gè)傾聽套接字的這兩個(gè)隊(duì)列在某一時(shí)刻的狀態(tài)。圖9-6傾聽套接字連接隊(duì)列此時(shí),這個(gè)傾聽套接字未完成連接隊(duì)列中有4個(gè)未完成的連接,完成連接隊(duì)列中有2個(gè)已經(jīng)建立的連接。為了理解這兩個(gè)隊(duì)列的工作過程,我們假定新接收到一個(gè)客戶機(jī)連接請求,圖9-7描述了這個(gè)客戶機(jī)連接3次握手的過程。圖9-7客戶機(jī)連接3次握手的過程服務(wù)器TCP協(xié)議在接收到客戶機(jī)的SYN數(shù)據(jù)段之后,在傾聽套接字的未完成隊(duì)列中創(chuàng)建一個(gè)新的條目,然后發(fā)送對客戶機(jī)SYN數(shù)據(jù)段的確認(rèn)和自己的SYN數(shù)據(jù)段。經(jīng)過大約一個(gè)往返時(shí)間之后,服務(wù)器接收到客戶機(jī)對它的SYN數(shù)據(jù)段的確認(rèn),表示連接已經(jīng)建立,于是服務(wù)器的TCP協(xié)議將這個(gè)連接從未完成隊(duì)列移到完成連接隊(duì)列中。當(dāng)服務(wù)器程序調(diào)用函數(shù)accept接收連接時(shí),返回完成連接隊(duì)列中的一個(gè)條目。參數(shù)backlog指定這個(gè)傾聽套接字的完成連接隊(duì)列的最大長度,表示這個(gè)套接字能夠接收的最大數(shù)目的未接收(unaccepted)連接。如果當(dāng)一個(gè)客戶機(jī)的SYN數(shù)據(jù)段到達(dá)時(shí),傾聽套接字的完成連接隊(duì)列已經(jīng)滿了,那么TCP協(xié)議將忽略這個(gè)SYN數(shù)據(jù)段。對于不能接收的SYN數(shù)據(jù)段,TCP協(xié)議不發(fā)送RST數(shù)據(jù)段,原因有以下兩個(gè):
①假設(shè)TCP協(xié)議在未完成隊(duì)列滿時(shí)返回RST數(shù)據(jù)段,那么客戶機(jī)的函數(shù)connect將馬上以錯(cuò)誤返回,不再繼續(xù)發(fā)送連接請求。根據(jù)這個(gè)RST數(shù)據(jù)段,客戶機(jī)無法知道,究竟是這個(gè)端口上沒有服務(wù)器進(jìn)程在等待連接,還是在這個(gè)端口上等待的服務(wù)器的未完成連接隊(duì)列暫時(shí)沒有空間。②完成隊(duì)列滿的情況是暫時(shí)的。經(jīng)過一段時(shí)間之后,應(yīng)用程序可能調(diào)用函數(shù)accept從這個(gè)完成隊(duì)列中接收已經(jīng)建立的連接,于是完成隊(duì)列中出現(xiàn)新的空間??蛻魴C(jī)TCP協(xié)議在超時(shí)之后,繼續(xù)幾次發(fā)送SYN數(shù)據(jù)段。如果在這幾次發(fā)送過程中,完成連接隊(duì)列中出現(xiàn)新的空間,那么TCP協(xié)議將接收這個(gè)連接請求,繼續(xù)正常的3次握手操作。如果在這幾次發(fā)送過程中,完成連接隊(duì)列中都沒有空間,則客戶機(jī)將放棄發(fā)送,以錯(cuò)誤ETIMEOUT返回。下面的這段程序顯示了這個(gè)問題,我們可以用命令telnet來做客戶機(jī)測試:我們執(zhí)行如下命令來測試:
bash$telnetlocalhost8080&
bash$telnetlocalhost8080&
bash$telnetlocalhost8080然后用命令netstat查看,結(jié)果如下:顯示結(jié)果的第2行是傾聽套接字的狀態(tài),第3行和第4行標(biāo)識一條已經(jīng)建立的連接,第5行和第6行標(biāo)識另一條已經(jīng)建立的連接,最后一行對應(yīng)于第三個(gè)telnet的操作,表示客戶機(jī)已經(jīng)發(fā)送了SYN數(shù)據(jù)段,正在等待確認(rèn)。經(jīng)過一段時(shí)間之后,第三個(gè)telnet命令將返回錯(cuò)誤,錯(cuò)誤信息是連接超時(shí)。
5.函數(shù)accept
函數(shù)accept從傾聽套接字的完成連接隊(duì)列中接收一個(gè)連接。如果完成連接隊(duì)列為空,那么這個(gè)進(jìn)程睡眠。其定義如下:
#include<sys/socket.h>
intaccept(intsockfd,structsockaddr*addr,int*addrlen);其中:參數(shù)sockfd用于指定傾聽套接字描述符;參數(shù)addr為指向一個(gè)Internet套接字地址結(jié)構(gòu)的指針;參數(shù)addrlen為指向一個(gè)整型變量的指針。函數(shù)accept成功執(zhí)行時(shí),返回3個(gè)結(jié)果:函數(shù)返回值為一個(gè)新的套接字描述符,標(biāo)識這個(gè)接收的連接;參數(shù)addr指向的結(jié)構(gòu)變量中存儲客戶機(jī)的地址;參數(shù)addrlen指向的整型變量中存儲客戶機(jī)地址的長度。如果我們對客戶機(jī)的地址和長度不感興趣,則可以將參數(shù)addr和addrlen設(shè)置為NULL。函數(shù)accept執(zhí)行失敗時(shí),返回?-1。函數(shù)accept從傾聽套接字的完成連接隊(duì)列中接收一個(gè)已經(jīng)建立起來的TCP連接,因?yàn)閮A聽套接字是專為接收客戶機(jī)連接請求,完成3次握手操作而用的,所以TCP協(xié)議不能使用傾聽套接字描述符來標(biāo)識這個(gè)連接,于是TCP協(xié)議創(chuàng)建一個(gè)新的套接字來標(biāo)識這個(gè)要接收的連接,并將它的描述符返回給應(yīng)用程序?,F(xiàn)在有兩個(gè)套接字:一個(gè)是調(diào)用函數(shù)accept時(shí)使用的傾聽套接字;另一個(gè)是函數(shù)accept返回的連接套接字(connectedsocket)。這兩個(gè)套接字的作用是完全不同的:一個(gè)服務(wù)器進(jìn)程通常只需創(chuàng)建一個(gè)傾聽套接字,在服務(wù)器進(jìn)程的整個(gè)活動(dòng)期間,用它來接收所有客戶機(jī)的連接請求,在服務(wù)器進(jìn)程終止前關(guān)閉這個(gè)傾聽套接字;而對于每個(gè)接收(accepted)的連接,TCP協(xié)議都創(chuàng)建一個(gè)新的連接套接字來標(biāo)識這個(gè)連接,服務(wù)器使用這個(gè)連接套接字與客戶機(jī)進(jìn)行通信操作,當(dāng)服務(wù)器處理完這個(gè)客戶機(jī)請求時(shí),關(guān)閉這個(gè)連接套接字。
當(dāng)函數(shù)accept阻塞等待已經(jīng)建立的連接時(shí),如果進(jìn)程捕獲到信號,那么函數(shù)將以錯(cuò)誤返回,錯(cuò)誤類型為EINTR。對于這種錯(cuò)誤,一般重新調(diào)用函數(shù)accept來接收連接。
6.函數(shù)close
函數(shù)close用于關(guān)閉一個(gè)套接字描述符。套接字描述符的close操作與文件描述符的close操作類似。其定義如下:
#include<unistd.h>
intclose(intsockfd);
其中,參數(shù)sockfd指定要關(guān)閉的套接字描述符。函數(shù)close成功執(zhí)行時(shí),返回0;否則,返回?-1。套接字描述符的close操作和文件描述符的close操作一樣:函數(shù)close將套接字描述符的引用計(jì)數(shù)減1,如果描述符的引用計(jì)數(shù)大于0,則表示還有進(jìn)程引用這個(gè)描述符,函數(shù)close正常返回;如果描述符的引用計(jì)數(shù)變?yōu)?,則表示再?zèng)]有進(jìn)程引用這個(gè)描述符,于是啟動(dòng)清除套接字描述符的操作,函數(shù)close立即正常返回。清除套接字描述符的操作是:將這個(gè)套接字描述符標(biāo)記為關(guān)閉狀態(tài)(不同于TCP協(xié)議狀態(tài)轉(zhuǎn)換圖中的CLDSED狀態(tài)),然后立即返回進(jìn)程。調(diào)用了函數(shù)close之后,進(jìn)程將不再訪問這個(gè)套接字,但是這不表示TCP協(xié)議刪除了這個(gè)套接字。TCP協(xié)議將繼續(xù)使用這個(gè)套接字,將尚未發(fā)送的數(shù)據(jù)傳遞到對方,然后發(fā)送FIN數(shù)據(jù)段,執(zhí)行關(guān)閉操作,一直等到這個(gè)TCP連接完全關(guān)閉之后,TCP協(xié)議才刪除這個(gè)套接字。
7.函數(shù)read和write
函數(shù)read和write用于從套接字讀和寫數(shù)據(jù)。其定義如下:
intread(intfd,char*buf,intlen);
intwrite(intfd,char*buf,intlen);
其中:參數(shù)fd用于指定讀/寫操作的套接字描述符;函數(shù)read的參數(shù)buf用于指定接收數(shù)據(jù)緩沖區(qū),函數(shù)write的參數(shù)buf用于指定發(fā)送數(shù)據(jù)緩沖區(qū);參數(shù)len用于指定接收或發(fā)送的數(shù)據(jù)量大小。函數(shù)read成功執(zhí)行時(shí),返回讀到的數(shù)據(jù)量大??;否則,返回-1。函數(shù)write成功執(zhí)行時(shí),返回寫入的數(shù)據(jù)量大??;否則,返回-1。每個(gè)TCP套接字都有兩個(gè)緩沖區(qū),分別用于處理發(fā)送和接收任務(wù),其中用于發(fā)送的緩沖區(qū)被稱為套接字發(fā)送緩沖區(qū),用于接收的緩沖區(qū)被稱為套接字接收緩沖區(qū)。從網(wǎng)絡(luò)讀、寫數(shù)據(jù)的操作是由TCP協(xié)議在內(nèi)核中完成的:TCP協(xié)議將從網(wǎng)絡(luò)上接收到的數(shù)據(jù)保存在相應(yīng)套接字的接收緩沖區(qū)中,等待用戶調(diào)用函數(shù)將它們從接收緩沖區(qū)拷貝到用戶緩沖區(qū);用戶將要發(fā)送的數(shù)據(jù)拷貝到相應(yīng)套接字的發(fā)送緩沖區(qū)中,然后由TCP協(xié)議按照一定的算法處理這些數(shù)據(jù)。讀、寫連接套接字的操作與讀、寫文件的操作類似,也可以使用函數(shù)read和write。函數(shù)read完成將數(shù)據(jù)從套接字接收緩沖區(qū)拷貝到用戶緩沖區(qū)的任務(wù):當(dāng)套接字接收緩沖區(qū)有數(shù)據(jù)可讀時(shí),函數(shù)read讀取這些數(shù)據(jù);如果緩沖區(qū)的可讀數(shù)據(jù)量大于函數(shù)read指定的值,則返回函數(shù)read參數(shù)len指定的數(shù)據(jù)量;如果緩沖區(qū)的可讀數(shù)據(jù)量小于函數(shù)read指定的值,則函數(shù)read不等待請求的所有數(shù)據(jù)都到達(dá),而是立即返回緩沖區(qū)中的所有數(shù)據(jù),這時(shí)的返回值小于參數(shù)len的值;當(dāng)套接字接收緩沖區(qū)中沒有數(shù)據(jù)可讀之時(shí),函數(shù)read將阻塞不返回,等待數(shù)據(jù)到達(dá)。函數(shù)write完成將數(shù)據(jù)從用戶緩沖區(qū)拷貝到套接字發(fā)送緩沖區(qū)的任務(wù):當(dāng)套接字發(fā)送緩沖區(qū)有足夠拷貝所有用戶數(shù)據(jù)的空間時(shí),函數(shù)write將數(shù)據(jù)拷貝到這個(gè)緩沖區(qū)中;如果可用空間小于函數(shù)write參數(shù)len的值,則函數(shù)write將阻塞不返回,等待緩沖區(qū)有足夠空間。注意,函數(shù)write返回之后,只表示用戶數(shù)據(jù)已經(jīng)拷貝到套接字的發(fā)送緩沖區(qū)中,并不表示數(shù)據(jù)已經(jīng)發(fā)送到遠(yuǎn)方主機(jī)。當(dāng)程序從一個(gè)套接字讀數(shù)據(jù)時(shí),有以下幾種情況:
(1)套接字接收緩沖區(qū)接收到數(shù)據(jù)。函數(shù)read讀取這些數(shù)據(jù),返回讀取的數(shù)據(jù)量大小,此時(shí)的返回值大于0。
(2)?TCP協(xié)議接收到FIN數(shù)據(jù)段。FIN數(shù)據(jù)段表示對方已經(jīng)發(fā)送完所有數(shù)據(jù),函數(shù)read返回0,并且以后所有在這個(gè)套接字上的讀操作均返回0。這和讀普通文件中遇到文件結(jié)束符的情況一樣,所以我們也將這種情況稱為讀到文件結(jié)束符。
(3)?TCP協(xié)議接收到RST數(shù)據(jù)段。RST數(shù)據(jù)段表示連接出現(xiàn)了某種錯(cuò)誤,函數(shù)read將以錯(cuò)誤返回,錯(cuò)誤類型為ECONNRESET。并且以后所有在這個(gè)套接字上的讀操作均返回錯(cuò)誤。
(4)進(jìn)程阻塞過程中接收到信號。如果進(jìn)程捕獲這個(gè)信號,那么函數(shù)read將以錯(cuò)誤返回,錯(cuò)誤類型為EINTR。以后可以繼續(xù)在這個(gè)套接字上讀數(shù)據(jù)。
所以在一個(gè)套接字上的讀操作一般有3種返回結(jié)果:大于0、等于0和小于0,分別對應(yīng)正常返回、讀到文件結(jié)束符和讀錯(cuò)誤。處理讀返回值的程序片斷如下:當(dāng)函數(shù)read返回值大于0時(shí),表示讀到新數(shù)據(jù),程序?qū)⑦@些數(shù)據(jù)寫入一個(gè)文件中;當(dāng)函數(shù)read返回值等于0時(shí),表示讀通道被關(guān)閉了,即讀到文件結(jié)束符,程序?qū)⑽募枋龇吞捉幼置枋龇P(guān)閉;當(dāng)函數(shù)read返回值小于0時(shí),表示讀操作發(fā)生錯(cuò)誤,程序需要根據(jù)錯(cuò)誤類型進(jìn)行不同的處理。如果錯(cuò)誤類型為EINTR,則表示讀函數(shù)被信號中斷,套接字上并未發(fā)生錯(cuò)誤,所以一般重新調(diào)用函數(shù)read讀數(shù)據(jù);如果錯(cuò)誤類型為ECONNRESET,則表示套接字接收到RST數(shù)據(jù)段,對方已經(jīng)重置了套接字,程序不能再在這個(gè)套接字上讀數(shù)據(jù),所以程序輸出錯(cuò)誤信息,然后終止執(zhí)行或者繼續(xù)其他操作。當(dāng)一個(gè)程序向套接字寫數(shù)據(jù)時(shí),有以下幾種情況:
(1)套接字發(fā)送緩沖區(qū)有足夠空間。函數(shù)write將數(shù)據(jù)拷貝到發(fā)送緩沖區(qū)中,返回拷貝的數(shù)據(jù)量大小。
(2)?TCP協(xié)議接收到RST數(shù)據(jù)段。當(dāng)對方TCP已經(jīng)關(guān)閉了這條連接之后,繼續(xù)向這個(gè)套接字上發(fā)送數(shù)據(jù),將導(dǎo)致對方TCP協(xié)議返回RST、數(shù)據(jù)段,表示這條連接已經(jīng)被關(guān)閉了。TCP協(xié)議在接收到這個(gè)RST數(shù)據(jù)段之后,函數(shù)write將以錯(cuò)誤返回,錯(cuò)誤類型為EPIPE。并且所有以后在這個(gè)套接字上的寫操作均以錯(cuò)誤返回。
(3)進(jìn)程阻塞過程中接收到信號。如果進(jìn)程捕獲這個(gè)信號,那么函數(shù)write將以錯(cuò)誤返回,錯(cuò)誤類型為EINTR。以后可以繼續(xù)在這個(gè)套接字上寫數(shù)據(jù)。
所以在一個(gè)套接字上讀操作一般有兩種返回結(jié)果:大于0和小于0,分別對應(yīng)正常返回和寫錯(cuò)誤。處理寫返回值的程序片斷如下:當(dāng)函數(shù)write返回值大于0時(shí),表示成功寫了部分或全部數(shù)據(jù),程序繼續(xù)其他操作;當(dāng)函數(shù)write返回值小于0時(shí),表示寫操作出現(xiàn)錯(cuò)誤,需要根據(jù)錯(cuò)誤類型進(jìn)行相應(yīng)處理。如果錯(cuò)誤類型為EINTR,則表示程序繼續(xù)執(zhí)行寫操作;如果錯(cuò)誤類型為EPIPE,則表示對方已經(jīng)關(guān)閉了套接字,程序輸出錯(cuò)誤信息,然后終止執(zhí)行。從前面的討論知道,如果接收緩沖區(qū)中沒有指定數(shù)據(jù)量的數(shù)據(jù)或者捕獲到信號,讀操作將不返回我們指定要讀的數(shù)據(jù)量;如果寫操作阻塞過程中捕獲到信號,寫操作也將不發(fā)送我們指定的數(shù)據(jù)量。但是這兩種情況并不表示讀或?qū)懖僮靼l(fā)生了錯(cuò)誤,如果每次編寫程序時(shí)均處理這兩種情況,比較麻煩,所以我們編寫兩個(gè)函數(shù):一個(gè)完成讀取指定數(shù)據(jù)量的讀操作,當(dāng)讀操作被信號中斷時(shí),函數(shù)自動(dòng)繼續(xù)讀數(shù)據(jù);另一個(gè)完成發(fā)送指定數(shù)據(jù)量的寫操作,當(dāng)寫操作被信號中斷時(shí),函數(shù)自動(dòng)繼續(xù)寫數(shù)據(jù)。其定義如下:
intread_all(intfd,void*buf,intnbytes);
intwrite_all(intfd,void*buf,intnbytes);其中:函數(shù)read_all用于從參數(shù)fd指定的描述符中讀取nbytes字節(jié)數(shù)據(jù)至緩沖區(qū)buf中;函數(shù)write_all用于從參數(shù)fd指定的描述符中寫入?yún)?shù)buf的前nbytes字節(jié)數(shù)據(jù)。這兩個(gè)函數(shù)的源代碼如下:函數(shù)read_all調(diào)用函數(shù)read從套接字中讀數(shù)據(jù),忽略錯(cuò)誤EINTR,如果已經(jīng)讀取的數(shù)據(jù)量小于指定值,則函數(shù)繼續(xù)讀,直到讀取了指定的數(shù)據(jù)量為止。如果發(fā)生除EINTR之外的其他讀錯(cuò)誤,則函數(shù)返回?-1。函數(shù)write_all調(diào)用函數(shù)write向套接字寫數(shù)據(jù),忽略錯(cuò)誤EINTR,如果已經(jīng)發(fā)送的數(shù)據(jù)量小于指定值,則函數(shù)繼續(xù)寫,直到發(fā)送了指定的數(shù)據(jù)量為止。如果發(fā)生除EINTR之外的其他寫錯(cuò)誤,則函數(shù)返回?-1。
8.函數(shù)getsockname和getpeername
函數(shù)getsockname返回套接字的本地地址;函數(shù)getpeername返回套接字對應(yīng)的遠(yuǎn)程地址。其定義如下:
#include<sys/socket.h>
intgetsockname(intsockfd,structsockaddr*localaddr,int*addrlen);
intgetpeername(intsockfd,structsockaddr*peeraddr,int*addrlen);其中:參數(shù)sockfd用于指定一個(gè)套接字描述符;參數(shù)localaddr和peeraddr為指向一個(gè)Internet套接字地址結(jié)構(gòu)的指針;參數(shù)addrlen為指向一個(gè)整型變量的指針。函數(shù)getsockname成功執(zhí)行時(shí),返回0,并在后兩個(gè)參數(shù)中返回結(jié)果,參數(shù)localaddr指向的結(jié)構(gòu)中存儲本地套接字地址,參數(shù)addrlen指向的整型變量中存儲返回的套接字地址的長度。函數(shù)getpeername成功執(zhí)行時(shí),返回0,并在后兩個(gè)參數(shù)中返回結(jié)果,參數(shù)peeraddr指向的結(jié)構(gòu)中存儲套接字對應(yīng)的遠(yuǎn)程地址,參數(shù)addrlen指向的整型變量中存儲返回的套接字地址的長度。兩函數(shù)執(zhí)行失敗時(shí),均返回?-l。下面這段程序顯示了如何使用這兩個(gè)函數(shù):一般在以下3種情況下使用這兩個(gè)函數(shù):
(1)客戶機(jī)在成功調(diào)用函數(shù)connect之后,可以用函數(shù)getsockname來獲得系統(tǒng)在套接字地址中填充的本地IP地址和自動(dòng)選擇的本地端口號。
(2)
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 工程安全分包合同樣本
- 小學(xué)五年級科技小制作教學(xué)計(jì)劃
- 客運(yùn)站服務(wù)質(zhì)量提升方案
- 安全生產(chǎn)月建筑工程總結(jié)報(bào)告
- 培智班主任工作計(jì)劃范本
- 2025年地?cái)偨?jīng)濟(jì)食品安全監(jiān)管十年報(bào)告
- 苜蓿種植基地可行性報(bào)告范文
- 軟件項(xiàng)目可行性分析完整報(bào)告
- 中學(xué)語文經(jīng)典古詩文教學(xué)設(shè)計(jì)
- 小學(xué)一年級語文教學(xué)計(jì)劃詳解
- 2026年陜西省森林資源管理局局屬企業(yè)公開招聘工作人員備考題庫帶答案詳解
- 規(guī)范園區(qū)環(huán)保工作制度
- 2026廣東深圳市龍崗中心醫(yī)院招聘聘員124人筆試備考試題及答案解析
- 2025年同工同酬臨夏市筆試及答案
- 2026年孝昌縣供水有限公司公開招聘正式員工備考題庫及答案詳解(考點(diǎn)梳理)
- 2026屆新高考語文熱點(diǎn)沖刺復(fù)習(xí) 賞析小說語言-理解重要語句含意
- 集資入股協(xié)議書范本
- 天津市部分區(qū)2024-2025學(xué)年九年級上學(xué)期期末練習(xí)道德與法治試卷(含答案)
- 統(tǒng)編版六年級語文上冊:閱讀理解知識點(diǎn)+答題技巧+練習(xí)題(含答案)
- JJG 521-2024 環(huán)境監(jiān)測用X、γ輻射空氣比釋動(dòng)能率儀檢定規(guī)程
- 采購部管理評審總結(jié)
評論
0/150
提交評論