IPv6套接字编程介绍
IPv6套接字編程
1.概述
由于互聯(lián)網(wǎng)用戶的日益增加,網(wǎng)絡(luò)需求日益擴(kuò)大,IPv4地址也日益緊張。人們?yōu)榱私鉀Q地址日趨耗盡的問題,采用了CIDR、NAT等技術(shù)來(lái)延緩地址耗盡的速度,但這并不能從根本上解決IPv4目前存在的問題,IPv4地址耗盡只是一個(gè)時(shí)間問題。隨著互聯(lián)網(wǎng)的發(fā)展,Internet骨干路由器的路由表也日益擴(kuò)大,這使得路由器必須維護(hù)大量路由表。
由于IPv6可以解決傳統(tǒng)的IP技術(shù)的瓶頸問題,因此,它會(huì)推動(dòng)整個(gè)信息產(chǎn)業(yè)的發(fā)展。目前,第三代移動(dòng)技術(shù)的基本協(xié)議就采用IPv6,這意味著下一代互聯(lián)網(wǎng)具有移動(dòng)性,將來(lái)手機(jī)或其他個(gè)人移動(dòng)終端都將具有全球唯一的IPv6地址,因而IPv6技術(shù)將會(huì)變得越來(lái)越重要。
1.1套接字與通信
1.1.1套接字的概念
套接字Socket,是指從應(yīng)用程序中接受計(jì)算機(jī)網(wǎng)絡(luò)通信服務(wù)時(shí)的應(yīng)用程序接口,簡(jiǎn)單的說(shuō)就是通信的兩方的一種約定,用套接字中的相關(guān)函數(shù)來(lái)完成通信過程。套接字是個(gè)抽象編程概念,它把用戶代碼與TCP/IP協(xié)議堆棧的底層實(shí)現(xiàn)隔離開了,TCP套接字可以使用戶快速地開發(fā)出自定義協(xié)議的客戶/服務(wù)器應(yīng)用程序。
套接字是通信的基石,是支持TCP/IP協(xié)議的網(wǎng)絡(luò)通信的基本操作單元??梢詫⑻捉幼挚醋鞑煌鳈C(jī)間的進(jìn)程進(jìn)行雙向通信的端點(diǎn),它構(gòu)成了單個(gè)主機(jī)內(nèi)及整個(gè)網(wǎng)絡(luò)間的編程界面。套接字存在于通信域中,通信域是為了處理一般的線程通過套接字通信而引進(jìn)的一種抽象概念。套接字通常和同一個(gè)域中的套接字交換數(shù)據(jù)(數(shù)據(jù)交換也可能穿越域的界限,但這時(shí)一定要執(zhí)行某種解釋程序)。各種進(jìn)程使用這個(gè)相同的域互相之間用Internet協(xié)議簇來(lái)進(jìn)行通信。
1.1.2套接字應(yīng)用程序編程接口
套接字應(yīng)用程序編程接口是網(wǎng)絡(luò)應(yīng)用程序通過網(wǎng)絡(luò)協(xié)議棧進(jìn)行通信時(shí)所使用的接口,即應(yīng)用程序與協(xié)議棧軟件之間的接口,簡(jiǎn)稱套接字編程接口(Socket API)。它定義了應(yīng)用程序與協(xié)議棧軟件進(jìn)行交互時(shí)可以使用的一組操作,決定了應(yīng)用程序使用協(xié)議棧的方式、應(yīng)用程序所能實(shí)現(xiàn)的功能、以及開發(fā)具有這些功能的程序的難度。圖1-1為應(yīng)用進(jìn)程通過套接字接入到網(wǎng)絡(luò)的示意圖。
?
套接口是對(duì)網(wǎng)絡(luò)中不同主機(jī)上應(yīng)用進(jìn)程之間進(jìn)行雙向通信的端點(diǎn)的抽象,一個(gè)套接口就是網(wǎng)絡(luò)上進(jìn)程通信的一端,提供了應(yīng)用層進(jìn)程利用網(wǎng)絡(luò)協(xié)議棧交換數(shù)據(jù)的機(jī)制。要想實(shí)現(xiàn)套接字編程接口,可以采用兩種實(shí)現(xiàn)方式:
? ? 一種是在操作系統(tǒng)的內(nèi)核中增加相應(yīng)的軟件來(lái)實(shí)現(xiàn);
? ? 一種是通過開發(fā)操作系統(tǒng)之外的函數(shù)庫(kù)來(lái)實(shí)現(xiàn)。
在這里我們采用的是第一種實(shí)現(xiàn)方式。
1.1.3套接字的分類
套接字具有三種類型:
(1)數(shù)據(jù)報(bào)套接字(Datagram? SOCKET)
數(shù)據(jù)報(bào)套接字提供無(wú)連接的不保證可靠的獨(dú)立的數(shù)據(jù)報(bào)傳輸服務(wù)。在Internet通信域中,數(shù)據(jù)報(bào)套接字使用UDP數(shù)據(jù)報(bào)協(xié)議形成的進(jìn)程間通路,具有UDP協(xié)議為上層所提供的服務(wù)的所有特點(diǎn)。圖1-2揭示了基于UDP協(xié)議的數(shù)據(jù)報(bào)套接字的工作模型。
(2)流式套接字(Stream SOCKET)
? ?? 流式套接字提供雙向的、有序的、無(wú)重復(fù)的、無(wú)記錄邊界的可靠的數(shù)據(jù)流傳輸服務(wù)。在Internet通信域中,流式套接字使用TCP協(xié)議形成的進(jìn)程間通路,具有TCP協(xié)議為上層所提供的服務(wù)的所有特點(diǎn),在使用流式套接字傳輸數(shù)據(jù)之前,必須在數(shù)據(jù)的發(fā)送端和接收端之間建立連接。 圖1-3揭示了基于TCP協(xié)議的流式套接字的工作模型
(3)原始式套接字(RAW SOCKET)
原始式套接字允許對(duì)較低層次的協(xié)議,如IP、ICMP直接訪問,用于檢驗(yàn)新的協(xié)議的實(shí)現(xiàn)。
每一個(gè)正被使用的套接字都有它確定的類型,只有相同類型的套接字才能相互通信。
1.1.4套接字的應(yīng)用場(chǎng)合
(1)不管是采用對(duì)等模式或者客戶機(jī)/服務(wù)器模式,通信雙方的應(yīng)用程序都需要開發(fā)。
(2)雙方所交換數(shù)據(jù)的結(jié)構(gòu)和交換數(shù)據(jù)的順序有特定的要求,不符合現(xiàn)在成熟的應(yīng)用層協(xié)議,甚至需要自己去開發(fā)應(yīng)用層協(xié)議,自己設(shè)計(jì)最適合的數(shù)據(jù)結(jié)構(gòu)和信息交換規(guī)程。
1.1.5套接字的一般通信過程
網(wǎng)絡(luò)通信一般為Client/Server模式
Server:運(yùn)行一個(gè)特定的程序,它申請(qǐng)一個(gè)Socket,該Socket在某一個(gè)Port監(jiān)聽客戶機(jī)的連接。
Client:申請(qǐng)一個(gè)Socket,將該Socket與服務(wù)器端的Port相聯(lián),服務(wù)器在接受該Client的連接后,新生成一個(gè)Port,在該新Port上與Client通信;原Port繼續(xù)監(jiān)聽,準(zhǔn)備接受新的Client的連接。
圖1-5 server通過另一個(gè)端口與client建立連接
套接字是網(wǎng)絡(luò)上與另一個(gè)應(yīng)用程序建立連接并通信的一個(gè)句柄。
?
1.2 IPv6
1.2.1 IPv6協(xié)議
IPv6是因特網(wǎng)協(xié)議第六版(Internet Protocol Version Six)的縮寫。目前,在Internet中廣泛使用的IP協(xié)議是被人們稱為IP第四版的IPv4協(xié)議。IPv4協(xié)議只使用了32位的IP地址,在迅速發(fā)展的Internet中,發(fā)生了地址的絕度數(shù)嚴(yán)重不足的問題。為了解決這個(gè)問題,人們對(duì)IP的第六版本進(jìn)行了標(biāo)準(zhǔn)化,并且目前已經(jīng)有一些操作系統(tǒng)對(duì)它進(jìn)行支持。在IPv6協(xié)議中,IP地址的長(zhǎng)度變?yōu)?span lang="en-us">128位,在Internet中能夠連接巨大數(shù)目的主機(jī)。
IPv6協(xié)議對(duì)IPv4的改進(jìn)表現(xiàn)在:
(1)擴(kuò)展地址空間。IP地址長(zhǎng)度由32位增加到128位。
(2)簡(jiǎn)化的首部格式,優(yōu)化路由選擇。IPv4首部的某些字段被取消或改為選項(xiàng),以減少報(bào)文分組處理過程中常用情況的處理開銷,并使得IPv6首部的帶寬開銷盡可能低。
(3)支持?jǐn)U展首部和選項(xiàng)。IPv6的選項(xiàng)放在單獨(dú)的擴(kuò)展首部中,位于報(bào)文分組中IPv6基本首部和傳送層首部之間。因?yàn)榇蠖鄶?shù)IPv6選項(xiàng)首部不會(huì)被報(bào)文分組投遞路徑上的任何路由器檢查和處理,直至其到達(dá)最終目的地,這種組織方式有利于改進(jìn)路由器在處理包含選項(xiàng)的報(bào)文分組時(shí)的性能。IPv6的另一改進(jìn),是其選項(xiàng)與IPv4不同,可具有任意長(zhǎng)度,不限于40字節(jié)。
(4)支持認(rèn)證和加密機(jī)制。IPv6定義了一種擴(kuò)展,可支持權(quán)限驗(yàn)證和數(shù)據(jù)完整性并支持保密性要求。
(5)支持自動(dòng)配置。IPv6支持多種形式的自動(dòng)配置,從孤立網(wǎng)絡(luò)結(jié)點(diǎn)地址的“即插即用”自動(dòng)配置,到DHCP提供的全功能的設(shè)施。
(6)服務(wù)質(zhì)量能力。IPv6增加了一種新的能力,如果某些報(bào)文分組屬于特定的工作流,發(fā)送者要求對(duì)其給予特殊處理,則可對(duì)這些報(bào)文分組加標(biāo)號(hào),例如非缺省服務(wù)質(zhì)量通信業(yè)務(wù)或“實(shí)時(shí)”服務(wù)。
?
?
?
?
?
1.2.2 IPv6數(shù)據(jù)報(bào)
圖1-6 為IPv6數(shù)據(jù)報(bào)報(bào)頭
0? ? ? ? 4? ? ? ? ? ? ? ? 12? ? ? 16? ? ? ? ? ? ? ? 24? ? ? ? ? ? ?? 31
| 版本號(hào) | 通信分類 | 流標(biāo)識(shí)符 | ||
| 有效負(fù)載長(zhǎng)度 | 下一首部 | 跳數(shù)限制 | ||
| 源地址(128位) | ||||
| 目的地址(128位) | ||||
| 有效負(fù)載 (0-多個(gè)擴(kuò)展首部+高層數(shù)據(jù)) | ||||
圖1 IPv6數(shù)據(jù)報(bào)報(bào)頭
Ipv6協(xié)議的結(jié)構(gòu)體定義如下:
Struct ip6_hdr{
? Union{
Struct ip6_hdrctl{
?? u_int32_t ip6_unl_flow;/*4位的版本,8位的傳輸與分類,20位的流標(biāo)識(shí)符*/
u_int16_t ip6_unl_plen;/*報(bào)頭長(zhǎng)度*/
u_int8_t ip6_unl_nxt;/*下一個(gè)報(bào)頭*/
u_int8_t ip6_unl_hlim;/*跨度限制*/
}ip6_unl ;
u_int8_t ip6_un2_vfc;/*4位的版本號(hào),跨度為4位的傳輸分類*/
}ip6_ctlun ;
struct in6_addr ip6_src;/*發(fā)送端地址*/
struct in6_addr ip6_dst;/*接收端地址*/
};
? #define ip6_vfc? ? ? ? ? ? ? ip6_ctlun.ip6_un2_vfc
? #define ip6_flow? ? ? ? ? ?? ip6_ctlun.ip6_unl.ip6_unl_flow
#define ip6_plen? ? ? ? ? ? ?ip6_ctlun.ip6_unl.ip6_unl_plen
#define ip6_nxt? ? ? ? ? ? ? ip6_ctlun.ip6_unl.ip6_unl_nxt
#define ip6_hlim? ? ? ? ? ?? ip6_ctlun.ip6_unl.ip6_unl_hlim
#define ip6_hops? ? ? ? ? ?? ip6_ctlun.ip6_unl.ip6_unl_hops
?
1.3? ?IPv6與IPv4的兼容性問題
由于IPv6與IPv4在地址長(zhǎng)度,數(shù)據(jù)報(bào)格式等方面存在許多不同點(diǎn),因此在IPv6套接字編程的時(shí)候,如何使程序既能適應(yīng)Ipv6的特點(diǎn),又能消除不同地址間的差異,使程序既能處理IPv4的地址,又能處理IPv6地址,實(shí)現(xiàn)IPv6與IPv4的兼容,對(duì)程序編程者來(lái)說(shuō)顯得非常重要。IPv6套接字編程將綜合考慮各種情況,解決IPv6與IPv4、IPv6與Ipv6節(jié)點(diǎn)間的通信問題。
?
?
2.從IPv4網(wǎng)絡(luò)向IPv6網(wǎng)絡(luò)過渡
目前,Internet中的絕大部分節(jié)點(diǎn)用的都是IPv4地址。為了解決IPv4地址的缺陷,Internet將逐步向IPv6過渡,在一段時(shí)間后,使Internet中所有的節(jié)點(diǎn)都具備處理IPv6地址的能力。
為了達(dá)到這個(gè)目標(biāo),因特網(wǎng)工程任務(wù)組(IETF)已經(jīng)設(shè)計(jì)出兩種解決方案。通過這兩種方案,可以使IPv6無(wú)縫地移植到IPv4中。這兩種方法就是:雙棧協(xié)議和隧道技術(shù)。
?
雙棧協(xié)議
? ?? 雙協(xié)議棧(dual stack)是指在完全過渡到 IPv6 之前,使一部分主機(jī)(或路由器)裝有兩個(gè)協(xié)議棧,一個(gè) IPv4 和一個(gè) IPv6。
雙棧網(wǎng)絡(luò)的建設(shè)有兩種模式:
? ? (1)完全雙棧網(wǎng)絡(luò),即所有網(wǎng)絡(luò)設(shè)備、用戶終端都支持IPv4、IPv6雙協(xié)議棧,用戶通信既可使用IPv4協(xié)議棧也可使用IPv6協(xié)議棧。
?? ?(2)有限雙棧網(wǎng)絡(luò),網(wǎng)絡(luò)中部分網(wǎng)絡(luò)設(shè)備、用戶終端采用雙協(xié)議棧,這些用戶可使用IPv4或IPv6與其它用戶互聯(lián)互通,但新增的網(wǎng)絡(luò)設(shè)備和用戶終端則僅使用IPv6協(xié)議棧,應(yīng)用基于IPv6協(xié)議棧。
雙協(xié)議棧的具體工作方式如下:
(1)若應(yīng)用程序使用的目的地址為IPv4地址,則使用IPv4協(xié)議;
(2)若應(yīng)用程序使用的目的地址為IPv4兼容的IPv6地址,則同樣使用IPv4協(xié)議,區(qū)別僅在于此時(shí)的IPv6封裝在IPv4中;
(3)若應(yīng)用程序使用的目的地址是一個(gè)非IPv4兼容的IPv6地址,則使用IPv6協(xié)議,而且很可能要采用隧道等機(jī)制來(lái)進(jìn)行路由傳送;
(4)若應(yīng)用程序使用域名作為目標(biāo)地址,則先從DNS服務(wù)器得到相應(yīng)的IPv4/IPv6地址,然后根據(jù)地址情況進(jìn)行相應(yīng)的處理。
2.2隧道技術(shù)
? ? 所謂隧道,就是在一方將IPv6的包封裝在IPv4包里,然后在目的地對(duì)其解析,得到IPv6包。通過隧道,IPv6分組被作為無(wú)結(jié)構(gòu)無(wú)意義的數(shù)據(jù),封裝在IPv4數(shù)據(jù)報(bào)中,被IPv4網(wǎng)絡(luò)傳輸。由于IPv4網(wǎng)絡(luò)把IPv6數(shù)據(jù)當(dāng)作無(wú)結(jié)構(gòu)無(wú)意義數(shù)據(jù)傳輸,因此不提供幀自標(biāo)示能力,所以只有在IPv4連接雙方都同意時(shí)才能交換IPv6分組,否則收方會(huì)將IPv6分組當(dāng)成IPv4分組而造成混亂。
?
在IPv6協(xié)議中,為了存儲(chǔ)通信所需要的IP地址和端口號(hào),定義了一個(gè)sockaddr_in6的結(jié)構(gòu)體。sockaddr_in6的結(jié)構(gòu)如下:
struct sockaddr_in6{
u_int8_t ? ? ? ? ? ? ?sin6_len;
u_int8_ ? ? ? ? ? ? ? sin6_family;
u_int16_t ? ? ? ? ? ? sin6_port;
u_int32_t ? ? ? ? ? ? sin6_flowinfo;
struct in6_addr ?? ? ?sin6_addr;
u_int32_t ? ? ? ? ? ? sin6_scope_id;
};
數(shù)據(jù)類型u_int8_t為8位無(wú)符號(hào)整數(shù),typedef unsigned char u_int8_t。u_int16_t 與u_int32_t亦類似。
在sin6_len域中,存儲(chǔ)有sockaddr_in6結(jié)構(gòu)體的長(zhǎng)度。在 sin6_family域中,存儲(chǔ)有表示IPv6地址系列的AF_INET6.在sin6_port域中,存儲(chǔ)有一個(gè)傳輸層所使用的端口號(hào)。在sin6_flowinfo域中,存儲(chǔ)有一個(gè)在QoS中所使用的流標(biāo)識(shí)符。在sin6_addr域中,存儲(chǔ)有IPv6協(xié)議的地址。在sin6_scope_id域中,存儲(chǔ)有表示范圍的ID.
IPv6協(xié)議的地址是由下面的int6_addr結(jié)構(gòu)體加以定義的:
struct int6_addr{
u_int8_t s6_addr[16];
};
in6_addr與IPv6協(xié)議的地址相同為16Byte,但為了在操作系統(tǒng)內(nèi)部處理方便起見,實(shí)際上是由聯(lián)合體來(lái)定義的。
在IPv4協(xié)議中,相對(duì)于sockaddr_in6結(jié)構(gòu)體的為sockaddr_in結(jié)構(gòu)體,其中缺少域sin6_flowinfo與sin6_scope_id。
?
3.2 addrinfo
?? addrinfo結(jié)構(gòu)體是為了消除IPv6協(xié)議與IPv4協(xié)議之間的差異,編制統(tǒng)一的程序而追加的。在各臺(tái)主機(jī)中,考慮能夠賦予多個(gè)IPv4地址或IPv6地址,將addrinfo結(jié)構(gòu)體設(shè)計(jì)為具有下面的列表結(jié)構(gòu):
? Struct addrinfo{
int? ? ?ai_flags;
int? ?? ai_family;
int? ?? ai_socktype;
int? ?? ai_protocol;
size_t? ai_addrlen;
char?? ?*ai_canonname;
struct sockaddr? *ai_addr;
struct addrinfo? *ai_next;
? };
在ai_flags域中,能夠設(shè)定3位的標(biāo)志位。它們分別是AI_PASSIVE、AI_CANONNAME、AI_NUMERICHOST。AI_PASSIVE在IPv4協(xié)議中指定INADDR_ANY的時(shí)候,不需要指定具體的主機(jī),而是利用任意的主機(jī)。AI_CANONNAME是在最初的列表結(jié)構(gòu)中存儲(chǔ)正式名稱的時(shí)候所設(shè)定的值。AI_NUMERICHOST不使用DNS進(jìn)行檢索,只使用IP地址,它在不想使用DNS查詢處理、需要等待一定時(shí)間的時(shí)候等情況下才使用。
ai_family域表示了地址系列。在地址系列中,具有表示IPv4協(xié)議的AF_INET、IPv6協(xié)議的AF_INET6等。
ai_socktype域表示了套接字的類型。在一個(gè)套接字的類型域中,具有下面的三種類型:表示流型的SOCK_STREAM,表示數(shù)據(jù)報(bào)型的SOCK_DGRAM,表示raw IP的SOCK_RAW。
ai_protocol域表示了傳輸層所使用的協(xié)議。在使用TCP協(xié)議時(shí),它為IPPROTO_TCP;在使用UDP協(xié)議時(shí),它為IPPROTO_UDP;在不使用傳輸層時(shí),在該域中存儲(chǔ)0.
ai_addrlen域表示ai_addr的長(zhǎng)度。ai_canonname域表示ai_addr的別名。
ai_addr域表示了訪問sockaddr_in或sockaddr_in6的指針。ai_next域表示列表的下一個(gè)地址。在列表結(jié)束時(shí),在該域中存儲(chǔ)NULL。
?
?
4.IPv6套接字編程中用到的函數(shù)
4.1 socket()函數(shù)
在使用套接字的時(shí)候,利用socket系統(tǒng)調(diào)用來(lái)打開一個(gè)套接字。socket系統(tǒng)調(diào)用的語(yǔ)法如下所示:
#include <sys/types.h>
#include <sys/socket.h>
?
int socket(int domain,int type,int protocol);
在domain域中,指定地址系列(協(xié)議系列)。地址系列表示所使用的地址體系。在TCP/IP協(xié)議中,將IP地址和端口號(hào)所形成的地址體系指定為AF_INET或AF_INET6,將表示TCP/IP協(xié)議的地址體系指定為PF_INET。
在type變量中,指定所使用的協(xié)議的類型,其中可以指定下面的值:
#define sock_STREAM? ? ? ?? /*流式套接字*/
#define sock_DGRAM? ? ? ?? /*數(shù)據(jù)報(bào)套接字*/
#define sock_RAW? ? ? ? ? ? /*原始式套接字*/
#define sock_RDM? ? ? ? ? ? /*可靠傳輸報(bào)文*/
#define sock_SEQPACKET? ?? /*序列包流*/
在使用TCP協(xié)議時(shí),指定為SOCK_STREAM;在使用UDP協(xié)議時(shí),指定為SOCK_DGRAM;在使用原始IP協(xié)議時(shí),指定為SOCK_RAW。
在protocol域中,指定所使用的協(xié)議類型。在使用TCP協(xié)議或UDP協(xié)議時(shí),由于指定type就可以確定方法,所以在protocol域中,缺省值為0。
如果成功地調(diào)用了socket系統(tǒng)調(diào)用,則打開一個(gè)套接字,并且返回一個(gè)可以利用該套接字的描述符。在發(fā)生錯(cuò)誤時(shí),則返回值為-1。
?
4.2 bind()函數(shù)
在利用自己的主機(jī)指定所使用的IP地址和端口號(hào)時(shí),一般都使用bind系統(tǒng)調(diào)用。關(guān)于bind系統(tǒng)調(diào)用語(yǔ)法,如下所示:
#include <sys/types.h>
#include <sys/socket.h>
?
int bind(int s,struct sockaddr *my_addr,socklen_t addrlen);
在變量s中,指定的是利用socket系統(tǒng)調(diào)用所打開的套接字的描述符。在my_addr指針中,指定自己的IP地址和端口號(hào)。在addrlen域中,指定結(jié)構(gòu)體my_addr的大小。
在利用bind系統(tǒng)調(diào)用將一個(gè)IP地址設(shè)置為INADDR_ANY的時(shí)候,該主機(jī)或路由器的所有IP地址都能夠接收到一個(gè)包。在主機(jī)中,除了NIC的IP地址之外,都帶有一個(gè)循環(huán)測(cè)試(loopback)的IP地址(127.0.0.1)。并且,在使用路由器等時(shí),由于準(zhǔn)備了多個(gè)接口,所以帶有多個(gè)IP地址。這時(shí),如果使用bind系統(tǒng)調(diào)用來(lái)指定一個(gè)IP地址,那么處理IP數(shù)據(jù)報(bào)的接收端地址和指定的IP地址之外,不能夠接收到通信。但是,在指定為INADDR_ANY時(shí),無(wú)論接收端的IP地址是什么,都能夠接收到包。
在服務(wù)器中,無(wú)論是TCP協(xié)議還是UDP協(xié)議,都必須使用bind系統(tǒng)調(diào)用來(lái)指定自己的端口號(hào)。在客戶機(jī)中,可以有操作系統(tǒng)來(lái)指定一個(gè)端口號(hào)。如果使用bind系統(tǒng)調(diào)用來(lái)指定的端口號(hào)時(shí),能夠自動(dòng)地分配一個(gè)端口號(hào)。另外,在TCP協(xié)議的客戶機(jī)中,可以省略執(zhí)行bind系統(tǒng)調(diào)用。
?
4.3 close()函數(shù)
在結(jié)束對(duì)套接字的使用的時(shí)候,使用close系統(tǒng)調(diào)用。關(guān)于close系統(tǒng)調(diào)用的語(yǔ)法,如下面的語(yǔ)句所示:
#include <unistd.h>
int close(int s);
在變量s中,存儲(chǔ)著利用socket系統(tǒng)調(diào)用所打開的套接字的描述符,或存儲(chǔ)著accept系統(tǒng)調(diào)用的返回值。
?
4.4 sendto()函數(shù)
? ? 套接字函數(shù)可以分為兩種:一種為無(wú)連接型的函數(shù),另一種為面向連接型的函數(shù)。
? ? #include <sys/types.h>
? ? #include <sys/socket.h>
?
? ? int sendto(int s,const void *msg,size_t len,int flags,const struct sockaddr *to,socklen_t tolen);
在raw IP協(xié)議中,必須使用無(wú)連接型的函數(shù)。在UDP協(xié)議中,通常都是使用無(wú)連接型的函數(shù),但是,也可以使用面向連接型的函數(shù)。在TCP協(xié)議中,必須使用面向連接型的函數(shù)。
在UDP協(xié)議中,如果沒有使用connect系統(tǒng)調(diào)用,則可以使用一個(gè)無(wú)連接型的函數(shù)。如果使用connect系統(tǒng)調(diào)用,也可以使用一個(gè)面向連接型的函數(shù)。
對(duì)于報(bào)文的發(fā)送與接收,可以利用sendto系統(tǒng)調(diào)用或recvfrom系統(tǒng)調(diào)用。在發(fā)送報(bào)文的時(shí)候,使用sendto系統(tǒng)調(diào)用;而在接收?qǐng)?bào)文的時(shí)候,則使用recvfrom系統(tǒng)調(diào)用。無(wú)論是哪一種系統(tǒng)調(diào)用,都必須在實(shí)際參數(shù)中指定訪問sockaddr結(jié)構(gòu)體的指針。
在變量s中,指定的是一個(gè)利用socket系統(tǒng)調(diào)用所打開的端口號(hào)描述符。在msg結(jié)構(gòu)體中,存儲(chǔ)著所發(fā)送報(bào)文的存儲(chǔ)器的初始地址,在len變量中,指定的是所發(fā)送報(bào)文的字節(jié)數(shù);在to結(jié)構(gòu)體中,指定的是接收端的IP地址和接收端的端口號(hào);在tolen變量中,指定結(jié)構(gòu)體to的大小;
在flags變量中,通常指定為0。
sendto系統(tǒng)調(diào)用的返回值是已經(jīng)發(fā)送報(bào)文的字節(jié)數(shù)。嚴(yán)格地來(lái)講,該返回值并不是在計(jì)算機(jī)網(wǎng)絡(luò)上所傳輸?shù)淖止?jié)數(shù),而是從應(yīng)用程序傳遞給套接字模塊的字節(jié)數(shù)。在發(fā)生錯(cuò)誤時(shí),返回值為-1。
?
4.5 recvfrom()函數(shù)
#include <sys/types.h>
#include <sys/socket.h>
int recvfrom(int s,void *hbuf,size_t len,int flags,struct sockaddr *from,socklen_t *fromlen);
在變量s中,指定的是利用socket系統(tǒng)調(diào)用所打開的一個(gè)套接字描述符。在buf指針中,存儲(chǔ)的是所接收到報(bào)文的緩沖區(qū)的起始地址;len變量中指定的是buf中所能夠存儲(chǔ)的最大字節(jié)數(shù)。在from結(jié)構(gòu)體中,存儲(chǔ)著所接收到的包的發(fā)送端IP地址和發(fā)送端端口號(hào);在fromlen變量中,存儲(chǔ)這結(jié)構(gòu)體from的大小;在flags變量中,通常指定為0。
recvfrom系統(tǒng)調(diào)用的返回值是所接收到的報(bào)文的字節(jié)數(shù)。在發(fā)生錯(cuò)誤時(shí),返回值為-1。
?
?
4.6 connect()函數(shù)
? ? 在指定通信對(duì)方的IP地址的時(shí)候,通常采用connect系統(tǒng)調(diào)用。在TCP協(xié)議中,需要傳輸建立連接請(qǐng)求的包。在UDP協(xié)議中,并不使用sendto系統(tǒng)調(diào)用或recvfrom系統(tǒng)調(diào)用,而是通過send系統(tǒng)調(diào)用或recv系統(tǒng)調(diào)用進(jìn)行通信。connect系統(tǒng)調(diào)用的語(yǔ)法格式如下:
? ? #include <sys/types.h>
#include <sys/socket.h>
int connect(int s,const struct sockaddr *addr,socklen_t addrlen);
在變量s中,指定的是利用socket系統(tǒng)調(diào)用所打開的一個(gè)套接字描述符。在addr結(jié)構(gòu)體中,指定的是通信對(duì)方的IP地址和端口號(hào)。在addrlen變量中,指定的是addr結(jié)構(gòu)體的大小。
?
4.7 listen()函數(shù)
當(dāng)服務(wù)器接收到TCP協(xié)議連接的時(shí)候,執(zhí)行一個(gè)listen系統(tǒng)調(diào)用。關(guān)于listen系統(tǒng)調(diào)用的語(yǔ)法格式如下:
#include <sys/socket.h>
int listen(int s,int backlog);
在變量s中,存儲(chǔ)著利用socket系統(tǒng)調(diào)用所打開的一個(gè)套接字描述符。在backlog變量中,指定的是隊(duì)列的長(zhǎng)度。如果listen系統(tǒng)調(diào)用正常,則返回值為0;否則發(fā)生錯(cuò)誤時(shí),返回值為-1。
?
4.8 getaddrinfo()函數(shù)
IPv4中使用gethostbyname()函數(shù)完成主機(jī)名到地址解析,但是該API不允許調(diào)用者指定所需地址類型的任何信息,返回的結(jié)構(gòu)只包含了用于存儲(chǔ)IPv4地址的空間。為了解決該問題,IPv6中引入了getaddrinfo()的新API,它是協(xié)議無(wú)關(guān)的,既可用于IPv4也可用于IPv6。getaddrinfo函數(shù)能夠處理名字到地址以及服務(wù)到端口這兩種轉(zhuǎn)換,調(diào)用該函數(shù)會(huì)獲得一個(gè)addrinfo結(jié)構(gòu)的列表,調(diào)用的返回值是addrinfo的結(jié)構(gòu)(列表)指針。
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char* nodename,const char* servname,
const struct addrinfo* hints,struct addrinfo** res);
nodename域指定了一個(gè)域名或IP地址。在這個(gè)域中,既能夠指定IPv6協(xié)議的地址,又能夠指定IPv4協(xié)議的地址。servname域能夠指定表示端口號(hào)的服務(wù)器名或表示端口號(hào)的數(shù)字。在指定一個(gè)域名或服務(wù)器名的時(shí)候,與使用C語(yǔ)言處理字符串的規(guī)則是相同的,在字符串的最后要追加“/0”。
在hints域中,指定想要獲得的信息。例如,在只想獲得與IPv6協(xié)議有關(guān)的信息時(shí),在addrinfo結(jié)構(gòu)體的ai_family中設(shè)定AF_INET6之后,再指定hints。在不特別指定時(shí),將hints域設(shè)置為NULL。
在res域中,使用一個(gè)addrinfo結(jié)構(gòu)體來(lái)保存;列表結(jié)構(gòu)的開始地址。
在IPv6協(xié)議中,在一個(gè)端口能夠指定多個(gè)IP地址。并且在過渡期中,也有將IPv6地址和IPv4地址這兩種地址都賦予一個(gè)NIC(網(wǎng)絡(luò)適配器、網(wǎng)卡)的情況存在。調(diào)用一次getaddrinfo函數(shù),即可檢索到所以的IP地址,并使用一個(gè)addrinfo結(jié)構(gòu)體存儲(chǔ)到該列表結(jié)構(gòu)中。
?
4.9 getnameinfo()函數(shù)
? ? getnameinfo()是getaddrinfo()的互補(bǔ)函數(shù)。它把一個(gè)套接字地址轉(zhuǎn)換為對(duì)應(yīng)的主機(jī)名和服務(wù)。它是一個(gè)“協(xié)議無(wú)關(guān)”的函數(shù),既能處理IPv4地址,又能處理IPv6地址。它集合了gethostbyaddrin()函數(shù)和getservbyport()函數(shù)的功能,但getnameinfo()消除了地址族依靠的特性。
#include <sys/socket.h>
#include <netdb.h>
int getnameinfo(const struct sockaddr *sa, socklen_t salen,
? ? ? ? ? ? ? ? ? ? ?? char *host, size_t hostlen,
? ? ? ? ? ? ? ? ? ? ?? char *serv, size_t servlen, int flags);
sa域是一個(gè)指向sockaddr結(jié)構(gòu)體的指針,其中指定了IP地址和端口號(hào)。
host域是存放主機(jī)名的字符型指針,而serv域是存放服務(wù)的指針,即端口號(hào)。
flag域用于控制getnameinfo()的操作,它允許的值如下面所列:
NI_DGRAM
當(dāng)知道處理的是數(shù)據(jù)報(bào)套接口的時(shí)候,調(diào)用者應(yīng)該設(shè)置NI_DGRAM標(biāo)志,因?yàn)樵谔捉涌诘刂方Y(jié)構(gòu)中給出的僅僅是IP地址和端口號(hào),getnameinfo無(wú)法就此確定所用協(xié)議是TCP還是UDP。比如端口514,在TCP端口上提供rsh服務(wù),而在UDP端口上則提供syslog服務(wù)。
NI_NOFQDN
該標(biāo)志導(dǎo)致返回的主機(jī)名稱被截去第一個(gè)點(diǎn)號(hào)之后的內(nèi)容。比如假設(shè)套接口結(jié)構(gòu)中的IP地址為91.168.42.2,那么不設(shè)置該標(biāo)志返回的主機(jī)名為sina.aiwen.com,那么如果設(shè)置了該標(biāo)志后返回的主機(jī)名則為sina。
NI_NUMERICHOST,NI_NUMERICSERV,NI_NUMERICSCOPE
NI_NUMERICHOST標(biāo)志通知getnameinfo不要調(diào)用DNS,而是以數(shù)值表達(dá)格式作為字符串返回IP地址;類似的,NI_NUMERICSERV標(biāo)志指定以十進(jìn)制數(shù)格式作為字符串返回端口號(hào),以代替查找服務(wù)名;NI_NUMERICSCOPE則指定以數(shù)值格式作為字符串返回范圍標(biāo)識(shí),以代替其名字。
NI_NAMEREQD
該標(biāo)志通知getnameinfo函數(shù)如果無(wú)法適用DNS反向解析出主機(jī)名,則直接返回一個(gè)錯(cuò)誤。需要把客戶的IP地址映射成主機(jī)名的那些服務(wù)器可以使用該特性。
?
?
4.10 inet_pton()函數(shù)和inet_ntop()函數(shù)
inet_pton函數(shù)是一個(gè)將域名或ASCII碼表示的IP地址變換為使用字節(jié)來(lái)表示的IP地址的函數(shù)。inet_ntop函數(shù)則是inet_pton函數(shù)的逆,即把使用字節(jié)來(lái)表示的IP地址轉(zhuǎn)為使用字符串所表示的IP地址。
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_pton(int af,const char *src,void *dst);
const char *inet_ntop(int af,const void *src,char *dst,size_t size);
在af域中,指定地址系列;在src、dst域中,分別指定變換前所存儲(chǔ)的地址信息以及返回后所存儲(chǔ)的地址信息。在inet_ntop的size域中,指定了從dst開始的緩沖區(qū)的大小。如果不指定充分大的緩沖區(qū),則不能夠進(jìn)行變換處理。
?
4.11 memset()函數(shù)
? ? void *memset(void *s,int c,size_t n)
memset函數(shù)用來(lái)對(duì)一段內(nèi)存空間全部設(shè)置為某個(gè)字符,常用于內(nèi)存空間初始化。將已開辟內(nèi)存空間s的首n個(gè)字節(jié)的值設(shè)為值c。
?
4.12 memcpy()函數(shù)
extern void *memcpy(void *dest, void *src, unsigned int count);
memcpy函數(shù)把src所指內(nèi)存區(qū)域復(fù)制count個(gè)字節(jié)到dest所指內(nèi)存區(qū)域,但src和dest所指內(nèi)存區(qū)域不能重疊,函數(shù)返回指向dest的指針。memcpy用來(lái)做內(nèi)存拷貝,你可以拿它拷貝任何數(shù)據(jù)類型的對(duì)象,而strcpy就只能拷貝字符串了,它遇到'/0'就結(jié)束拷貝。
?
5.程序的地址族無(wú)關(guān)性
5.1 地址族無(wú)關(guān)的概念
在套接字編程中,我們經(jīng)常說(shuō)到程序必須實(shí)現(xiàn)地址族無(wú)關(guān)(address-family independent)。什么是地址族無(wú)關(guān)呢?所謂的地址族無(wú)關(guān),就是要求程序在處理IP地址時(shí),能消除不同IP地址間的差異性,可對(duì)不同的IP地址進(jìn)行統(tǒng)一的無(wú)差別的處理。不需修改程序,即可對(duì)不同的IP地址進(jìn)行預(yù)定的處理,實(shí)現(xiàn)相應(yīng)功能。
?
5.2 為什么程序需要地址族無(wú)關(guān)
?? 程序具備地址族無(wú)關(guān)性可以使程序消除一系列不足,使程序更具靈活性:
(1)為了支持IPv4/v6雙棧環(huán)境,網(wǎng)絡(luò)程序必須能夠同時(shí)正確處理IPv4與IPv6。如果在程序中規(guī)定了地址族為AF_INET或AF_INET6,那么程序?qū)o(wú)法在IPv4/v6雙棧環(huán)境中正確運(yùn)行。
(2)當(dāng)一個(gè)新的協(xié)議投入使用后,我們總是希望以前的網(wǎng)絡(luò)程序能夠適應(yīng)新協(xié)議,而不需為了適應(yīng)新協(xié)議而對(duì)程序進(jìn)行重寫。這包括在IP層,雖然現(xiàn)在還沒有開發(fā)IPv7的計(jì)劃,但誰(shuí)也不能肯定未來(lái)會(huì)不會(huì)開發(fā)。在傳輸層亦如此。
(3)目前,已經(jīng)有足夠的工具支持網(wǎng)絡(luò)程序的地址族無(wú)關(guān),比如sockaddr_storage,getaddrinfo和getnameinfo。
(4)在一些操作系統(tǒng)中可能不支持地址族。如果在編程中引入了地址族,可能會(huì)導(dǎo)致程序不能正確執(zhí)行。而程序的地址族無(wú)關(guān)可以解決此類問題。
(5)程序的地址族無(wú)關(guān)可以使程序更簡(jiǎn)潔,提高程序的移植性。
(6)有些應(yīng)用程序接口(API)不支持IPv6,比如gethostbyname()。
?
5.3 以AF_INET6替代AF_INET,sockaddr_in6替代sockaddr_in的不足
在重寫IPv4依靠(IPv4 dependent)程序的時(shí)候,能不能只是簡(jiǎn)單地把AF_INET代替為AF_INET6,sockaddr_in替代為sockaddr_in6,而使程序地址族無(wú)關(guān)呢?
這樣做有幾個(gè)缺點(diǎn):
首先,用gethostbyname2(3),程序只能連接IPv6目的地址,而不能連接IPv4目的地址。在一個(gè)IPv4/v6雙棧環(huán)境中,FQDN(Fully Qualified Domain Name,完全合格域名/全稱域名,是指主機(jī)名加上全路徑,全路徑中列出了序列中所有域成員)可以被分解為多個(gè)IPv4地址和多個(gè)IPv6地址??蛻舳藨?yīng)該盡可能地連接分解出來(lái)的IP地址,而不只是連接IPv6地址。
第二,IPv6支持范圍IPv6地址(scoped IPv6 addresses),用gethostbyname2(3)并不能處理范圍IPv6地址,因?yàn)?/span>gethostbyname2(3)不返回范圍標(biāo)識(shí)符(scope? identification)。
第三,在程序中指定地址族為AF_INET6將使程序只能在支持IPv6的內(nèi)核中運(yùn)行,因?yàn)橐粋€(gè)不支持IPv6的內(nèi)核通常沒有AF_INET6套接字的支持。如果想讓一個(gè)單一雙態(tài)程序(既能處理IPv4地址,又能處理IPv6地址)能正確運(yùn)行在IPv4-only內(nèi)核、IPv6內(nèi)核和IPv4/v6雙棧內(nèi)核中,地址族無(wú)關(guān)是必須的。
第四,這樣的程序并不能適應(yīng)未來(lái)的需要。如果一些新協(xié)議投入使用,這樣的程序?qū)⒉豢杀苊庵貙憽?/span>IPv4到IPv6的過渡的花費(fèi)是巨大的,在此過渡過程中,把其他問題一起解決未嘗不是一件好事。
第五,用地址族無(wú)關(guān)方法進(jìn)行編程,可以使程序獲得更高的移植性和穩(wěn)定性。
?
5.4 套接字編程的地址族無(wú)關(guān)指南
(1)使用sockaddrs用于地址表示
為了處理IPv4和IPv6地址,建議使用sockaddrs,如sockaddr_in或sockaddr_in6。用sockaddrs,可以使數(shù)據(jù)包含地址族的標(biāo)識(shí),這樣的話我們就可以在傳遞地址數(shù)據(jù)時(shí)知道其地址族。
當(dāng)需要一個(gè)預(yù)留空間給一個(gè)sockaddr時(shí),可以使用結(jié)構(gòu)體sockaddr_storage。結(jié)構(gòu)體sockaddr_storage有足夠大的空間來(lái)存儲(chǔ)任何類型的sockaddr。
使用sockaddr的另一個(gè)重要原因是一個(gè)IPv6地址并不能唯一地確定一個(gè)端點(diǎn),還必須加上一個(gè)一個(gè)范圍標(biāo)識(shí)符,指定出口端(outgoing interface)。
(2)把文本表示轉(zhuǎn)換為sockaddrs。利用getaddrinfo(3)可以實(shí)現(xiàn)。
(3)把二進(jìn)制地址表示轉(zhuǎn)換為文本??梢岳?/span>getnameinfo(3)實(shí)現(xiàn)。
?
?
6.IPv6套接字編程
6.1? 編寫能處理IPv6地址的程序
為了使程序能夠處理IPv6地址,我們知道可以用基于socket的應(yīng)用程序接口,通過使用getaddrinfo和getnameinfo來(lái)使程序具備地址族無(wú)關(guān)的能力。
getaddrinfo()應(yīng)用舉例:
const struct sockaddr * foo(hostname,servname)
? ?? const char *hostname;
? ?? const char *servname;
{
? ?? struct addrinfo hints,*res;
? ?? static struct sockaddr_storage ss;
? ?? int error;
?
? ?? memset(&hints,0,sizeof(hints));
? ?? hints.ai_socktype=SOCK_STREAM;
? ?? error=getaddrinfo(hostname,servname,&hints,&res);
? ?? if(error){
? ? ? ? fprintf(stderr,”%s/%s:%s/n”,hostname,servname,gai_strerror(error));
? ? ? ? exit(1);
? ? }
?
? ? if(res->ai_addrlen sizeof(ss)){
? ? ? ? fprintf(stderr,”sockaddr too large/n”);
? ? ? ? exit(1);
? ? }
? ? memcpy(&ss,res->aiaddr,res-ai_addrlen);
? ? freeaddrinfo(res);
? ? return(const struct sockaddr *)&ss;
}
getaddrinfo(3)非常靈活,具有許多狀態(tài)操作。比如,如果你想避免DNS lookup,你可以在hints.ai_flags域中指定為AI_NUMERICHOST。通過AI_NUMERICHOST,getaddrinfo(3)將只接收數(shù)字表示的地址。
getaddrinfo(3)用范圍識(shí)別(scope identification)處理IPv6字符串地址,所以程序不需要對(duì)范圍識(shí)別做任何的特殊處理。
?
getnameinfo(3)也非常靈活,既支持?jǐn)?shù)字的地址,也支持FQDN(Fully Qualified Domain Name:完全合格域名/全稱域名,是指主機(jī)名加上全路徑,全路徑中列出了序列中所有域成員)表示的地址。getnameinfo(3)同時(shí)也可以把端口號(hào)轉(zhuǎn)換成字符串。所以getnameinfo(3)能同時(shí)支持IPv4和IPv6,并不需要區(qū)分是支持IPv4還是支持IPv6。最后一個(gè)參數(shù)可以控制getnameinfo(3)的行為。
struct sockaddr *sa;
char hbuf[NI_MAXHOST];
sbuf[NI_MAXSERV] ;
int error ;
?
error=getnameinfo(sa,salen,hbuf,sizeof(hbuf),
?? NI_NUMERICHOST|NI_NUMERICSERV);
if(error){
fprintf(stderr,”error:%s/n”,gai_strerror(error));
exit(1);
}
fprintf(“addr:%s port:%s/n”,hbuf,sbuf);
?
error=getnameinfo(sa,salen,hbuf,sizeof(hbuf),0);
?
if(error){
fprintf(stderr,”error:%s/n”,gai_strerror(error));
exit(1);
}
fprintf(“addr:%s port:%s/n”,hbuf,sbuf);
?
error=getnameinfo(sa,salen,hbuf,sizeof(hbuf),NULL,0,NI_NAMEREQD);
if(error){
fprintf(stderr,”error:%s/n”,gai_strerror(error));
exit(1);
}
printf(“FQDN:%s/n”,hbuf);
?
getnameinfo(3)需要生成IPv6地址字符串的范圍標(biāo)識(shí),但也沒必要擔(dān)心在sin6_scope_id域中的范圍標(biāo)識(shí)。
?
6.2移植可用的程序來(lái)支持IPv6
為了找到需要重寫的部分,我們需要找出IPv4依賴的功能調(diào)用和IPv4依賴的數(shù)據(jù)類型:
%grep gethostby *.c *.h
%grep inet_aton *.c *.h
%grep sockaddr_in *.c *.h
%grep in_addr *.c *.h
然而,如果程序編寫不正確并且在int或u_int32_t中傳遞32位的二進(jìn)制表示的IPv4地址的話,對(duì)in_addr將毫無(wú)用處,還需識(shí)別在哪個(gè)變量中存儲(chǔ)了IPv4地址。
如果套接字編程借口(socket API)是由單個(gè)*.c文件生成,那么將容易移植。否則,我們必須弄清IPv4依靠的數(shù)據(jù)是怎樣傳送的,然后把它們重寫成協(xié)議族無(wú)關(guān)的程序。有時(shí)候,IPv4依靠的數(shù)據(jù)類型用在結(jié)構(gòu)體定義和功能原型中。在這種情況下,我們需要辨別出地址族無(wú)關(guān)的代碼。
下面的例子是IPv4依賴的部分程序代碼:
struct foo{
struct sockaddr_in dst;
}
?
struct foo *
setaddr(in)
? ? ? struct in_addr in;
{
? ? ? struct foo *foo;
?
? ? ? foo=malloc(sizeof(*foo));
? ? ? if(!foo)
? ? ? ? ? return NULL;
? ? ? memset(foo,0,sizeof(*foo));
?
? ? ? ? ? foo->dst.sin_family=AF_INET;
? ? ? ? ? foo->dst.sin_addr=in;
? ? ? ? ? return foo;
}
改變結(jié)構(gòu)體的定義是比較簡(jiǎn)單的,要么把結(jié)構(gòu)體改為struct sockaddr_storage,要么定義一個(gè)struct addrinfo*。而改為功能原型則要難得多。有時(shí)候,傳遞struct sockaddr *會(huì)容易些,但如果你要處理多個(gè)地址的話,用struct addrinfo *會(huì)顯得更明智些。
下面是重寫后的程序,實(shí)現(xiàn)了地址族無(wú)關(guān),但不支持多地址:
struct foo{
? ?? struct sockaddr_storage dst;
};
struct foo *
setaddr(sa,salen)
? ? ?? struct sockaddr *sin;
? ? ?? socklen_t salen;
{
? ? ?? struct foo *foo;
? ? ? ? ?? if(salen>sizeof(foo->dst))
? ? ? ? ? ? ? ? return NULL;
?
? ? ? ? ?? foo=malloc(sizeof(*foo));
? ? ? ? ?? if(!foo)
? ? ? ? ? ? ? return NULL;
? ? ? ? ?? memset(foo,0,sizeof(*foo));
?
? ? ? ? ?? memcpy(&foo->dst,sa,salen);
? ? ? ? ?? return foo;
}
如果由于一些限制不能使用select(2)和poll(2),可以運(yùn)行兩個(gè)應(yīng)用程序的實(shí)例,一個(gè)用于AF_INET socket,另一個(gè)用于AF_INET6,這樣就可以同時(shí)處理IPv4節(jié)點(diǎn)和IPv6節(jié)點(diǎn)。
?
?
?
?
?
?
結(jié)論及尚存在的問題
IPv6的主要優(yōu)勢(shì)體現(xiàn)在以下幾方面:擴(kuò)大地址空間、提高網(wǎng)絡(luò)的整體吞吐量、改善服務(wù)質(zhì)量(QoS)、安全性有更好的保證、支持即插即用和移動(dòng)性、更好實(shí)現(xiàn)多播功能。顯然,IPv6的優(yōu)勢(shì)能夠直接或間接地解決IPv4存在的諸多問題。其中最突出的是IPv6大大地?cái)U(kuò)大了地址空間,恢復(fù)了原來(lái)因地址受限而失去的端到端連接功能,為互聯(lián)網(wǎng)的普及與深化發(fā)展提供了基本條件。當(dāng)然,IPv6并非十全十美、一勞永逸,不可能解決所有問題。IPv6只能在發(fā)展中不斷完善,也不可能在一夜之間發(fā)生,過渡需要時(shí)間和成本,但從長(zhǎng)遠(yuǎn)看,IPv6有利于互聯(lián)網(wǎng)的持續(xù)和長(zhǎng)久發(fā)展。
由于IPv6相關(guān)技術(shù)目前還不是很成熟,其應(yīng)用范圍還不是很廣,很多方面還只是停留在研究與實(shí)驗(yàn)階段,因此本畢業(yè)論文的討論范圍也涉及不深,只作淺層次的探索討論;在程序移植性方面也不是做的很好,本設(shè)計(jì)的代碼是在Linux平臺(tái)下調(diào)試運(yùn)行的,因此在跨平臺(tái)方面還有待提高。
在這次畢業(yè)設(shè)計(jì)的過程中,我查閱了大量相關(guān)的書籍與相關(guān)知識(shí),通過自學(xué)與老師的指導(dǎo),我對(duì)畢業(yè)設(shè)計(jì)的要求與內(nèi)容有了深刻的了解,并通過自己的理解與分析思考,圓滿完成了畢業(yè)論文。在完成畢業(yè)設(shè)計(jì)的過程中,我對(duì)“一份耕耘,一份收獲”有了深刻的認(rèn)識(shí),只要你付出了,你就一定會(huì)有收獲。在畢業(yè)設(shè)計(jì)期間,我認(rèn)真查閱資料,虛心請(qǐng)教老師與同學(xué),并仔細(xì)弄清相關(guān)理論知識(shí),理清思路,詳細(xì)構(gòu)思規(guī)劃,很快就有了一個(gè)大致的框架,在后期通過修改與完善,我的論文完成了。在此過程中,我付出了努力,而我也取得了收獲。我學(xué)到了很多以前課堂上沒學(xué)到的知識(shí),在IPv6相關(guān)知識(shí)方面有了更深的理解,并在此過程中,我的查閱資料能力、整體構(gòu)思規(guī)劃能力、解決問題的能力也得到了提高,我從中受益甚大。
?
?
致? 謝
大學(xué)生活即將畫上記號(hào),而于我的人生卻只是一個(gè)逗號(hào),我將面對(duì)又一次征程的開始。四年的求學(xué)生涯在師長(zhǎng)、親友的大力支持下,走得辛苦卻也收獲滿囊,在論文即將付梓之際,思緒萬(wàn)千,心情久久不能平靜。偉人、名人為我所崇拜,可是我更急切地要把我的敬意和贊美獻(xiàn)給我的大學(xué)老師們,是你們的無(wú)私教導(dǎo)讓我從稚嫩走向成熟,而我也從中學(xué)到各種知識(shí)與智慧,讓我可以更容易地面對(duì)社會(huì)生活。在這里,我要特別感謝我的指導(dǎo)老師羅海天老師,他給了我很大的幫助,幫我解決了畢業(yè)設(shè)計(jì)中遇到的很多問題。從論文題目的選定到論文寫作的指導(dǎo),經(jīng)由他悉心的點(diǎn)撥,再經(jīng)思考后的領(lǐng)悟,常常讓我有“山重水復(fù)疑無(wú)路,柳暗花明又一村”的感覺。授人以魚不如授人以漁,置身其間,耳濡目染,潛移默化,使我不僅接受了全新的思想觀念,樹立了宏偉的學(xué)術(shù)目標(biāo),領(lǐng)會(huì)了基本的思考方式,從羅老師的身上,我受益良多。
感謝我的爸爸媽媽,焉得諼草,言樹之背,養(yǎng)育之恩,無(wú)以回報(bào),你們永遠(yuǎn)健康快樂是我最大的心愿。在論文即將完成之際,我的心情無(wú)法平靜,從開始進(jìn)入課題到論文的順利完成,有多少可敬的師長(zhǎng)、同學(xué)、朋友給了我無(wú)言的幫助,在這里請(qǐng)接受我誠(chéng)摯謝意!同時(shí)也感謝學(xué)院為我提供良好的做畢業(yè)設(shè)計(jì)的環(huán)境。最后再一次感謝所有在畢業(yè)設(shè)計(jì)中曾經(jīng)幫助過我的良師益友和同學(xué),以及在設(shè)計(jì)中被我引用或參考的論著的作者。
本畢業(yè)設(shè)計(jì)是我學(xué)習(xí)生涯的最后一份答卷,也是我作為學(xué)生交給母校最后的一份答卷,盡管傾注了我數(shù)月來(lái)的心血和汗水,卻由于自己的基礎(chǔ)知識(shí)不夠扎實(shí),能力確實(shí)有限,多多少少存在著這樣那樣的缺陷。然而,畢竟已經(jīng)盡力,已無(wú)憾矣!
?
?
?
?
?
附錄
程序1 client-gethostby.c:TCP客戶端實(shí)例——通過host/port與服務(wù)端通信,并從服務(wù)端接收信息。該程序不支持IPv6。
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
?
int main_P((int,char **));
?
int main(argc,argv)
? ? ? ? int agrc;
? ? ? ? char **argv;
{
? ? ? ? struct hostent *hp;
? ? ? ? struct servent *sp;
? ? ? ? unsigned long lport;
? ? ? ? u_int16_t? port;
? ? ? ? char *ep;
? ? ? ? struct sockaddr_in dst ;
? ? ? ? int dstlen ;ssize_t l ;
? ? ? ? int s;
? ? ? ? char hbuf[INET_ADDRSTRLEN];
? ? ? ? char buf[1024];
?
/*檢查參數(shù)個(gè)數(shù)*/
? ? ?? if(argc!=3){
? ? ? ?? fprintf(stderr,”usage:test host port/n”);
? ? ? ?? exit(1);
? ? ?? }
? ? ?? /*把主機(jī)名解釋為IP*/
? ? ?? hp=gethostbyname(argv[1]);
? ? ?? if(!hp){
? ? ? ?? fprintf(stderr,”%s:%s/n”,argv[1],hstrerror(h_errno));
? ? ? ?? exit(1);
? ? ? }
? ? ? if(hp->h_length!=sizeof(dst.sin_addr)){
? ? ? ?? fprintf(stderr,”%s:unexpected address length/n”,argv[1]);
? ? ? ?? exit(1);
? ? ? }
? ? ? /*解析端口號(hào)*/
? ? ? sp=getservbyname(argv[2],”tcp”);
? ? ? if(sp){
? ? ? ?? port=sp-s_port& 0xffff;
? ? ? }else{
? ? ? ? ?? ep=NULL;errno=0;
? ? ? ? ?? lport=strtoul(argv[2],&ep,10);
? ? ? ? ?? if(!*argv[2] || errno || !ep || *ep){
? ? ? ? ? ?? fprintf(stderr,”%s:no such service/n”,agrv[2]);
? ? ? ? ? ?? exit(1);
? ? ? ? ? ?}
? ? ? ? ?? if(lport & ~0xffff){
? ? ? ? ? ?? fprintf(stderr,”%s:out of range/n”,argv[2]);
? ? ? ? ? ?? exit(1);
? ? ? ? ?? }
? ? ? ? ?? port=htons(lport & 0xffff);/*將主機(jī)的無(wú)符號(hào)短整數(shù)型數(shù)轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)順序,如12 34—>34 12*/
? ? ?? }
? ? ?? endservent() ;
?
? ? ?? /*只嘗試第一個(gè)地址*/
? ? ?? memset(&dst,0,sizeof(dst)) ;
? ? ?? dst.sin_family=AF_INET ;
? ? ?? /*Linux/Solaris系統(tǒng)不需要下面的一行*/
? ? ?? dst.sin_len=sizeof(struct sockaddr_in) ;
? ? ?? memcpy(&dst.sin_addr,hp->h_addr,sizeof(dst.sin_addr)) ;
? ? ?? dst.sin_port=port ;
? ? ?? dstlen=sizeof(struct sockaddr_in) ;
?
? ? ?? s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
? ? ?? if(s<0){
? ? ? ? ? perror(“socket”);
? ? ? ? ? exit(1);
? ? ?? }
?
? ? ?? inet_ntop(AF_INET,hp->h_addr,hbuf,sizeof(hbuf));
? ? ?? fprintf(stderr,”trying %s port %u/n”,,hbuf,ntohs(port));
?
? ? ?? if(connect(s,(struct sockaddr *)&dst,dstlen)<0){
? ? ? ? ? perror(“connect”);
? ? ? ? ? exit(1);
? ? ?? }
? ? ?? while((l=read(s,buf,sizeof(buf))>0)
? ? ? ? ? ?? write(STDOUT_FILENO,buf,l);close(s);
? ? ? ? ? ?? exit(0);
?? ? ? }
?
程序2 client-getaddrinfo.c:在程序6-1的基礎(chǔ)上使程序?qū)崿F(xiàn)地址族無(wú)關(guān)。
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet.h>
#include <netdb.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
?
int main_P((int,char **));
?
int main(argc,argv)
? ? ? ? int agrc;
? ? ? ? char **argv;
{
? ? ? ? struct addrinfo hints,*res,*res0;
? ? ? ? ssize_t l;
? ? ? ? int s;
? ? ? ? char hbuf[NI_MAXHOST],sbuf[NI_MAXSERV];
? ? ? ? char buf[1024] ;
? ? ? ? int error ;
?
/*檢查參數(shù)個(gè)數(shù)*/
? ? ?? if(argc!=3){
? ? ? ? ?fprintf(stderr,”usage:test host port/n”);
? ? ? ?? exit(1);
? ? ?? }
/*把地址/端口號(hào)轉(zhuǎn)換為sockaddr,基于getaddrinfo(3)返回的結(jié)果,程序代碼運(yùn)行于數(shù)據(jù)驅(qū)動(dòng)模式*/
? ? ?? memset(&hints,0,sizeof(hints)) ;
? ? ?? hints.ai_socktype=SOCK_STREAM;
? ? ?? error=getaddinfo(argv[1],argv[2],&hints,&res0);
? ? ?? if(error){
? ? ? ? ? fprintf(stderr,”%s %s/n”,argv[1],argv[1],gai_strerror(error));continue;
? ? ? ? ? exit(1);
/*嘗試所有的sockaddr直到通信成功*/
? ? ?? for(res=res0;res;res=res->ai_next){
? ? ? ? ?? error=getnameinfo(res->ai_aiaddr,res->ai_addrlen,hbuf,sizeof(hbuf),sbuf,
sizeof(sbuf),NI_NUMERICHOST | NI_NUMERICSERV);
?
? ? ? ? ?? if(error){
? ? ? ? ? ? ? fprintf(stderr,”%s%s:%s/n”,arg[1],argv[1],gai_sterror(error));
? ? ? ? ? ? ? continue;
? ? ? ? ?? }
? ? ? ? ?? fprintf(stderror,”trying %s port %s/n”,hbuf,sbuf);
?
? ? ? ? ?? s=socket(res->ai_family,res->ai_socktype,res->ai_protocol);
? ? ? ? ?? if(s<0)
? ? ? ? ?? continue;
?
? ? ? ? ?? if(connect(s,res-ai_addr,res-ai_addrlen)>0){
? ? ? ? ? ? ?? close(s);
? ? ? ? ? ? ?? s=-1;
? ? ? ? ? ? ?? continue;
? ? ? ? ?? }
?
? ? ? ? ?? while((l=read(s,buf,sizeof(buf)))<0)
? ? ? ? ? ? ? ? write(STDOUT_FILENO,buf,l);close(s);
? ? ? ? ? ? exit(0);
? ? ? ? ? ? }
? ? ? ? ? ? fprintf(stderr,”test:no destination to connect to/n”);
? ? ? ? ? ? exit(1);
? ? ? ? ? ? }
?
程序3 server-single.c 一個(gè)獨(dú)立的TCP服務(wù)器偵聽一個(gè)IPv4端口
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
?
int main_P((int,char **));
?
int main(argc,argv)
? ? ? ? int agrc;
? ? ? ? char **argv;
{
? ? ? ? struct servent *sp;
? ? ? ? unsigned long lport;
? ? ? ? u_int16_t? port;
? ? ? ? char *ep;
? ? ? ? struct sockaddr_in serv ;
? ? ? ? int servlen ;struct sockaddr_in from;
? ? ? ? socklen_t fromlen;
? ? ? ? int s;
? ? ? ? int ls;
? ? ? ? char hbuf[INET_ADDRSTRLEN];
?
if(argc!=2){
? ? ? ? ?? fprintf(stderr,”usage:test port/n”);
? ? ? ? ?? exit(1);
? ? ?? }
? ? ?? sp=getservbyname(argv[1],”tcp”);
? ? ?? if(sp)
? ? ? ?? ? port=sp->s_port & 0xffff;
? ? ?? else{
? ? ? ? ?? ep=NULL;errno=0;
? ? ? ? ?? lport=strtoul(argv[1],&ep,10) ;
? ? ? ? ?? if(!*argv[1] || errno || !ep || *ep){
? ? ? ? ? ? ? frpintf(stderr,”%s: no such service/n”,argv[1]);
? ? ? ? ? ? ? exit(1);
? ? ? ?? ? }
? ? ? ? ?? if(lport & ~0xffff){
? ? ? ? ? ? ? fprintf(stderr,”%s: out of range/n”,argv[1]);
? ? ? ? ? ? ? exit(1);
? ? ? ? ?? }
? ? ? ? ? port=htons(lport &0xffff);
? ? ?? }
? ? ?? endservent() ;
?
? ? ?? memset(&serv,0,sizeof(serv)) ;
? ? ?? serv.sin_family=AF_INET ;
? ? ?? /*Linux/Solaris系統(tǒng)不需要下面的一行*/
? ? ?? serv.sin_len=sizeof(struct sockaddr_in) ;
? ? ?? serv.sin_port=port;
? ? ?? servlen=sizeof(struct sockaddr_in);
? ? ?? s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
? ? ?? if(s<0){
? ? ? ? ? perror(“socket”);
? ? ? ? ? exit(1);
? ? ?? }
?
? ? ?? if(bind(s,(struct sockaddr *)&serv,servlen)<0){
? ? ? ? ? perror(“bind”);
exit(1);
? ? ?? }
? ? ?? if(listen(s,5)<0{
perror(“l(fā)isten
exit(1);
? ? ?? }
?
? ? ?? while(l){
? ? ? ? ? fromlen=sizeof(from);
? ? ? ? ? ls=accept(s,(struct sockaddr *)&from,&fromlen);
? ? ? ? ? if(ls<0)? continue;
? ? ? ? ? if(from.sin_family!=AF_INET || fromlen!=sizeof(struct sockaddr_in)){
exit(1);
? ? ?? }
?
? ? ?? ? ?if(inet_ntop(AF_INET, &from.sin_addr,hbuf,sizeof(hbuf))= =NULL){
exit(1);
? ? ?? }
?
? ? ? ?? write(ls,”hello”,6);
? ? ? ?? write(ls,hbuf,strlen(hbuf));
? ? ? ?? write(ls,”/n”,l);
? ? ? ?? close(ls);
? ? ?? }
}
?
程序4 server-getaddrinfo.c 在程序6-3的基礎(chǔ)上使程序地址族無(wú)關(guān)
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
?
#define MAXSOCK 20
?
int main_P((int,char **));
?
int main(argc,argv)
? ? ? ? int agrc;
? ? ? ? char **argv;
{
? ? ? ? struct addrinfo hints,*res,*res0;
? ? ? ? int error;
? ? ? ? struct sockaddr_storage from;
? ? ? ? socklen_t fromlen;
? ? ? ? int ls;
? ? ? ? int s[MAXSOCK];
? ? ? ? int smax;
? ? ? ? int sockmax;
? ? ? ? fd_set rfd,rfd0;
? ? ? ? int n;
? ? ? ? int i;
? ? ? ? char hbuf[NI_MAXHOST],sbuf[NI_MAXSERV];
#ifdef IPV6_V6ONLY
? ? ? ? const int on=1 ;
#endif
?if(argc!=2){
? ? ?? ? fprintf(stderr,”usage:test port/n”);
? ?? ? ? exit(1);
? ? ?? }
?
memset(&hints,0,sizeof(hints)) ;
? ? ?? hints.ai_socktype=SOCK_STREAM;
? ? ? ?hints.ai_flags=AI_PASSIVE;
? ? ?? error=getaddinfo(NULL,argv[1],&hints,&res0);
? ? ?? if(error){
? ? ? ? ? fprintf(stderr,”%s %s/n”,argv[1], gai_strerror(error));
? ? ? ? ? exit(1);
}
?
smax=0;
sockmax=-1;
for(res=res0;res &&smax rMAXSOCK; res=res->ai_next){
? ?? s[smax]=socket(res-ai_family,res-ai_socktype,res->ai_protocol);
?? if(s[smax]<0? continue;
?
/*避免FD_SET溢出*/
if(s[smax]=FD_SETSIZE){
?? close(s[smax]);
?? s[smax]=-1;
?? continue;
}
#ifdef IPV6_V6ONLY
? ? ?? if(res->ai_family= =AF_INET6 &&
setsockopt(s[smax],IPPROTO_IPV6,IPV6_V6ONLY,&on,
sizeof(on))<0){
?? perror(“bind”);
?? s[smax]=-1;
?? continue;
}
#endif
? ? ? ??
? ? ? ? ? ? if(bind(s[smax],res-ai_addr,res-ai_addrlen) 0){
? ? ? ? ? ? ? ? ? close(s[smax]);
? ? ? ? ? ? ? ? ? s[smax]=-1;
? ? ? ? ? ? ? ? ? continue;
? ? ? ?? }
? ? ? ?? if(listen(s[smax],5) 0){
? ? ? ? ? ? ? ? ? close(s[smax]);
? ? ? ? ? ? ? ? ? s[smax]=-1;
? ? ? ? ? ? ? ? ? continue;
? ? ? ? ?}
?
? ? ? ?? error=getnameinfo(res-ai_addr,res-ai_addrlen,hbuf,sizeof(hbuf),sbuf,sizeof(sbuf),
? ? ? ? ? ? ? ? ? NI_NUMERICHOST | NI_NUMERICSERV);
? ? ? ?? if(error){
fprintf(stderr,”test:%s/n”,gai_strerror(error));
? ? ? ? ? ? exit(1);
? ? ? ?? }
? ? ? ?? fprintf(stderr,”listen to %s %s /n”,hbuf,sbuf);
?
? ? ? ?? if(s[smax]>sockmax)
? ? ? ? ? ?sockmax=s[smax];
? ? ? ?? samx++;
? ? }
?
? ? if(smax= =0){
? ? ? ?? fprintf(stderr,”test:no socket to listen to/n”);
? ? ? ?? exit(1);
? ? }
?
? ? FD_ZERO(&rfd0);
? ? for(i=0;i<smax;i++)
? ? ? ?? FD_SET(s[i],&rfd0);
? ?? while(l){
? ? ? ?? rfd=rfd0;
? ? ? ? ?n=select(sockmax+1,&frd,NULL,NULL,NULL);
? ? ? ?? if(n<0){
? ? ? ? ?? perror(“select”);
? ? ? ? ?? exit(1);
? ? ? ?? }
? ? ? ?? fro(i=0;i<smax;i++){
? ? ? ? ?? if(FD_ISSET(s[i],&rfd)){
? ? ? ? ? ? ?? fromlen=sizeof(from);
? ? ? ? ? ? ?? ls=accept(s[i],(struct sockaddr *)&from &fromlen);
? ? ? ? ? ? if(ls<0)?? continue;
? ? ? ? ? ? write(ls,”hello/n”,6);
? ? ? ? ? ? close(ls);
? ? ? ? ? }
? ? ? }
?? }
}
?
轉(zhuǎn)載于:https://www.cnblogs.com/javaexam2/archive/2010/07/08/2632989.html
總結(jié)
以上是生活随笔為你收集整理的IPv6套接字编程介绍的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: IE6 式样表 Bug
- 下一篇: 代码统计工具1.1版本技术文档