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