IPv4 最初是由美國國防部開發(fā)的用于網(wǎng)際互聯(lián)(IP)協(xié)議,后來它不僅發(fā)展了TCP,而且還進一步發(fā)展了IPv4(IP 協(xié)議4.0版)。IPv4現(xiàn)在已經(jīng)廣泛應(yīng)用于Internet網(wǎng)絡(luò)中,同時也應(yīng)用于大多數(shù)計算機系統(tǒng),局域網(wǎng)和廣域網(wǎng)中。然而,隨著Internet 中的計算機數(shù)量突飛猛漲,IPv4 的局限性越發(fā)明顯:
1.IPv4地址數(shù)目面臨耗盡,日近緊張;
2.IPv4尋址并非完全分等級,這使得Internet 樞紐路由器必須維持大量的路由表,負(fù)擔(dān)過重。
3.IPv4的地址必須被靜態(tài)分配或通過配置協(xié)議(如:DHCP)進行分配。IPv6的開發(fā)目標(biāo)之一就是將提供更為簡便的配置方案。
于是IPv6(6.0版本)應(yīng)運而生。在Window系統(tǒng)中,Windows XP 提供了IPv6的developer-release版本;Windows 2000也可在http://www.microsoft.com/ipv6 下載 IPv6協(xié)議預(yù)覽。
一.IPv4地址及其尋址
1.IPv4地址
IPv4地址(常稱IP地址)用一個32位數(shù)表示;通常表示位十進制格式,地址的每8位字節(jié)被表示轉(zhuǎn)為一個十進制的數(shù)值,并由句點分隔,如:192.168.0.1;IPv4地址 通常分為A、B、C、D、E 五類。
2.IPv4尋址
在WiNSock 中,通過SOCKADDR_IN 結(jié)構(gòu)來指定IPv4的地址和服務(wù)斷口信息:
STruct sockaddr_in {
short sin_family ;//必須為AF_INET,表示使用IPv4地址簇
u_short sin_Port; //TCP/UDP 端口
struct in_addr sin_addr;// IP地址(以網(wǎng)絡(luò)字節(jié)順序排列, 4個字節(jié))
char sin_zero[8];//填充項
}
二.IPv6地址及其尋址
1.IPv6地址
IPv6地址與IPv4地址的顯著的不同是128位,長度是IPv4地址的4倍。IPv6地址由16位字節(jié)分段表示,顯示為冒號分隔的十六進制:
21DA:00D3:0000:2F3A:B234:ED12:9C5A:DAC3
IPv6地址的分配
分配
地址前綴
保留地址0000 0000
為NSAP預(yù)留0000 0001
可聚合的全球單播地址001
鏈接-本地單播地址1111 1110 10
站點-本地單播地址1111 1110 11
多播地址1111 1111
2. IPv6的尋址
Winsock中,尋址使用一下結(jié)構(gòu):
struct sockaddr_in6{
short sin6_family;// 地址簇:AF_INET6
u_short sin6_port;//端口號
u_lONg sin6_flowinfo;//連接標(biāo)記通信量
struct in6_addr sin6_addr;//16字節(jié)結(jié)構(gòu)的IPv6 地址
u_long sin6_scope_id;//地址所有的接口索引
}
三.獨立于協(xié)議的地址及名稱解析
由此可見在尋址時,IPv4使用16字節(jié)的SOCK_ADDR_IN 結(jié)構(gòu),IPv6則使用28 字節(jié)的SOCK_ADDR_IN6 結(jié)構(gòu)。為了解決這個問題,IPv6中引入了新的尋址函數(shù)。 [Page]
1.getaddrinfo(),它提供獨立于協(xié)議的名稱解析:
int getaddrinfo(
const char *FAR *nodenAME,
const char FAR* SERvname,
const struct addrinfo FAR *hins,
struct addrinfo FAR *FAR *res
);
l 第一參數(shù):nodename,以空字節(jié)結(jié)束的主機名或文字地址
l 第二參數(shù):servname,包含端口或服務(wù)名(如:FTP,TELNET)的以空字節(jié)結(jié)束的字符串
l 第三個參數(shù):hins 是一個結(jié)構(gòu)(addrinfo),包含名稱解析的執(zhí)行方式選項
l 第四個參數(shù):res ,用于返回 addrinfo 結(jié)構(gòu)的一個或多個鏈表
結(jié)構(gòu)addrinfo 的定義:
struct addrinfo{
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
size_t ai_addrlen;
char *ai_cannoname;
struct sockaddr *ai_addr;
struct addrinfo *ai_next;
}
l ai_flags 選值:AI_PASSIVE:可以用來獲取能夠傳遞給bind函數(shù)的地址,此時nodename應(yīng)設(shè)置為NULL,servname為欲綁定的端口;AI _CANONNAME 表示nodename 是主機名;AI_NUMBERICHOST 表示, nodename 是一個文字字符串地址(如:“192.168.0.1”)
l ai_family 選值:AI_INET或PF_INET(IPv4地址簇);AI_INET6或PF_INET6(IPv6地址簇);AI_UNSPEC(未指定,可能是IPv4或IPv6 地址簇)
l ai_socktype選值:SOCK_DGRAM(UDP類型套接字);SOCK_STREAM (TCP類 型套接字)
l ai_protocol 選值:IPPROTO_TCP (TCP/IP協(xié)議)
如果函數(shù)解析成功,解析后的地址將通過res返回。如果名稱被解析為多個地址,則返回一個由ai_next 字段形成的鏈表。每個由名稱解析的地址在ai_addr中表示,長度在ai_addrlen中表示。
2.getnameinfo()函數(shù)與getaddrinfo()相對應(yīng),功能相反。
. int getnameinfo(
const struct sockaddr FAR *sa,
socklen_t salen, [Page]
char FAR *host,
DWORD hostlen,
char FAR *serv,
DWORD servlen,
Int flags);
以上參數(shù)的含義比較明顯,不再一一說明。
3.釋放函數(shù): freeaddrinfo(res);
四、兼容IPv4和IPv6的網(wǎng)絡(luò)程序設(shè)計
兼容IPv4和IPv6的網(wǎng)絡(luò)程序,顯然涉及到兩個部分:客戶機和服務(wù)器。
在Windows 網(wǎng)絡(luò)編程中,Winsock是一種標(biāo)準(zhǔn)的API(應(yīng)用程序接口),Winsock2版本已經(jīng)發(fā)展成獨立于協(xié)議的的接口,被廣泛應(yīng)用于Windows平臺中。
<一>客戶機程序設(shè)計
對于客戶機來說,不管是建立TCP/UDP 連接,它都應(yīng)知道服務(wù)器的主機名或IP 地址,同時將服務(wù)器地址解析為IPv4或IPv6地址都可以,一般可以考慮一下步驟:
SOCKET s;
struct addrinfo,hints,*res=NULL;
char *szRemoteAddrESS;//主機名或IP 地址
char *szRemotePort;//端口號
int rc;
1.用getaddrinfo() 函數(shù)解析地址。hins結(jié)構(gòu)中 使用AF_UNSPEC標(biāo)志,便可以獲得地址簇類型(IPv4或IPv6)。
memset(&hintas,0,sizeof(hints));
hints.ai_family=AF_UNSPEC;
hints.ai_socktype=SOCK_STREAM;
hints.ai_protocol=IPPROTO_TCP;
rc=getaddrinfo(szRemoteAdddress,szRemotePort,&hints,&res);
if(rc==WSANO_DATA)
{// 無法解析,出錯
}
用返回的addrinfo結(jié)構(gòu)中的ai_family,ai_socketype,ai_protocol字段來創(chuàng)建套接字。
s=Socket(res->ai_family,ai_socktype,res->protocol);
if(s==INVALID_SOCKET)
{//創(chuàng)建套接字失敗
}
2.使用返回的addrinfo結(jié)構(gòu)中的ai_addr來調(diào)用其他函數(shù)(connect(),send()等).。
rc==connect(s,res->ai_addr,res->addrlen);
if(rc==SOCKET_error)
{//連接失敗;
}
。。。//完成其他編程
<二>服務(wù)器程序設(shè)計
服務(wù)器程序設(shè)計,應(yīng)考慮到IPv4和IPv6 都具有各自的堆棧;因此如果服務(wù)器希望能同時接受IPv4和IPv6的連接,就必須能同時創(chuàng)建IPv4和IPv6套接字;一般可以考慮一下步驟:
SOCKET socklisten[2];//*Socket變量
char *szPort=”8080”;//*端口
struct addinfo hints,*res=NULL,*ptr=NULL;
int rc,i=0;
1. 調(diào)用getaddrinfo()函數(shù),該結(jié)構(gòu)包含AI_PASSIVE,AF_UNSPEC標(biāo)志,以及所需的套接字類型、協(xié)議及所需的本地端口(用來*和接受數(shù)據(jù)等)。函數(shù)將返回的兩個addrinfo結(jié)構(gòu),分別可用于IPv4和IPv6*地址: [Page]
memset(&hints,0,sizeof(hints));
hints.ai_family=AF_UNSPEC;
hints.ai_socktype=SOCK_STREAM;
hints.ai_protocol=IPPROTO_TCP;
hints.ai_flags=AI_PASSIVE;
rc=getaddinfo(NULL,szPort,&hints,&res);
if(rc!=0){//失敗處理;}
ptr=res;
2. 用返回的addrinfo結(jié)構(gòu)中的ai_family,ai_socketype,ai_protocol字段來創(chuàng)建套接字后;便可以使用addrinfo結(jié)構(gòu)中的ai_addr 和ar_addrlen 字段調(diào)用綁定函數(shù)bind()。
while(ptr)
{
socklisten[i]=socket(ptr->ai_family,ptr->ai_socktype,ptr->ai_protocol);
if(socklisten[i]==INVALID_SOCKET){//創(chuàng)建失敗處理;}
rc=bind(socklisten[i],ptr->ai_addr,ptr->ai_addrlen);
if(rc==SOCKET_Error){//綁定失敗處理}
rc=listen(slisten[i],7)//開始*
if(rc==SOCKET_ERROR){//*失敗處理}
i++;
ptr=ptr->ai_next;
}
。。。
//完成其他編程
五、程序?qū)嵗?/p>
在這里,給出一個基于IPV6的簡單回應(yīng)(ECHO)服務(wù)器程序.
1.建立CIPv6 類
// IPv6.h: 頭文件,這里使用到了套接字中的“select I/O模型”
#define WIN32_LEAN_AND_MEAN
#include
#include
#include // IPv6 頭文件
#include
#include
#include
#pragma comment(lib, "ws2_32.lib")//套接字庫文件
#define DEFAULT_PORT "7274" // 默認(rèn)端口
#define BUFFER_SIZE 64 // 數(shù)據(jù)緩沖區(qū)
class CIPv6
{
public:
// 創(chuàng)建TCP 服務(wù)器
int CreateServer(char *Port = DEFAULT_PORT,char *Address = NULL);
void Usage(char *ProgName);//用戶信息提示
LPSTR DecodeError(int ErrorCode);//獲取錯誤信息
CIPv6();
virtual ~CIPv6();
};
// IPv61.cpp: CIPv6類的實現(xiàn) .
// IPv61.cpp: implementation of the CIPv6 class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "IPv61.h"
int CIPv6::CreateServer(char *Port, char *Address)
{
char Buffer[BUFFER_SIZE], Hostname[NI_MAXHOST];
int RetVal, FromLen, AmountRead;
SOCKADDR_STORAGE From;
WSADATA wsaData;
ADDRINFO Hints, *AddrInfo;
SOCKET ServSock;
fd_set SockSet;
// 啟動Winsock
if ((RetVal = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0)
{
fprintf(stderr, "WSAStartup failed with error %d: %sn",
RetVal, DecodeError(RetVal));
WSACleanup();
return -1;
}
if (Port == NULL)
{
Usage("Port Error");
}
memset(&Hints, 0, sizeof(Hints));
Hints.ai_family =AF_INET6;// Family;
Hints.ai_socktype =SOCK_STREAM;
Hints.ai_flags = AI_NUMERICHOST | AI_PASSIVE;
RetVal = getaddrinfo(Address, Port, &Hints, &AddrInfo);
if (RetVal != 0)
{
fprintf(stderr, "getaddrinfo failed with error %d: %sn", RetVal, gai_strerror(RetVal));
WSACleanup();
return -1;
}
// 創(chuàng)建套接字
ServSock = socket(AddrInfo->ai_family,AddrInfo->ai_socktype, AddrInfo->ai_protocol);
if (ServSock == INVALID_SOCKET)
{
fprintf(stderr, "socket() failed with error %d: %sn",
WSAGetLastError(), DecodeError(WSAGetLastError()));
WSACleanup();
return -1;
}
// 綁定套接字
if (bind(ServSock, AddrInfo->ai_addr, AddrInfo->ai_addrlen) == SOCKET_ERROR)
{
fprintf(stderr,"bind() failed with error %d: %sn",
WSAGetLastError(), DecodeError(WSAGetLastError()));
WSACleanup();
return -1;
}
// 偵聽
if (listen(ServSock, 5) == SOCKET_ERROR)
{
fprintf(stderr, "listen() failed with error %d: %sn",
WSAGetLastError(), DecodeError(WSAGetLastError()));
WSACleanup();
return -1;
}
printf("'Listening' on port %s, protocol %s, protocol family %sn",
Port, "TCP",
"PF_INET6");
freeaddrinfo(AddrInfo);
//使用select I/O 模型進行收發(fā)
FD_ZERO(&SockSet);
while(1)
{
FromLen = sizeof(From);
if (FD_ISSET(ServSock, &SockSet)) break;
FD_SET(ServSock, &SockSet);
if (select(0, &SockSet, 0, 0, 0) == SOCKET_ERROR)
{
fprintf(stderr, "select() failed with error %d: %sn",
WSAGetLastError(), DecodeError(WSAGetLastError()));
WSACleanup();
return -1;
}
}
if (FD_ISSET(ServSock, &SockSet))
{
FD_CLR(ServSock, &SockSet);
}
//接受一個連接
SOCKET ConnSock;
ConnSock = accept(ServSock, (LPSOCKADDR)&From, &FromLen);
if (ConnSock == INVALID_SOCKET)
{
fprintf(stderr, "accept() failed with error %d: %sn",
WSAGetLastError(), DecodeError(WSAGetLastError()));
WSACleanup();
return -1;
}
if (getnameinfo((LPSOCKADDR)&From, FromLen, Hostname,
sizeof(Hostname), NULL, 0, NI_NUMERICHOST) != 0)
strcpy(Hostname, "");
printf("nAccepted connection from %sn", Hostname);
while(1)
{
//等待接受數(shù)據(jù)
AmountRead = recv(ConnSock, Buffer, sizeof(Buffer), 0);
if (AmountRead == SOCKET_ERROR)
{
fprintf(stderr, "recv() failed with error %d: %sn", WSAGetLastError(), DecodeError(WSAGetLastError()));
closesocket(ConnSock);
break;
}
if (AmountRead == 0) {
printf("Client closed connectionn");
closesocket(ConnSock);
break;
}
printf("Received %d bytes from client: [%.*s]n",
AmountRead, AmountRead, Buffer);
//進行簡單ECHO 回應(yīng)
printf("Echoing same data back to clientn");
RetVal = send(ConnSock, Buffer, AmountRead, 0);
if (RetVal == SOCKET_ERROR)
{
fprintf(stderr, "send() failed: error %d: %sn",
WSAGetLastError(), DecodeError(WSAGetLastError()));
closesocket(ConnSock);
break;
}
}
return 0;
}
void CIPv6::Usage(char *ProgName)
{
fprintf(stderr, "nSimple socket sample server program.n");
fprintf(stderr, "transport tEither TCP or UDP. (default: %s)n",
"TCP");
fprintf(stderr, "portttPort on which to bind. (default %s)n",
DEFAULT_PORT);
fprintf(stderr, "addresstIP address on which to bind.(default: unspecified address)n");
WSACleanup();
exit(1);
}
LPSTR CIPv6::DecodeError(int ErrorCode)
{
static char Message[1024];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_MAX_WIDTH_MASK, NULL, ErrorCode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)Message, 1024, NULL);
return Message;
}
2.應(yīng)用示例
#include "stdafx.h"
#include "IPv6.h"
int main(int argc, char* argv[])
{
CIPv6 m_ipv6;
m_ipv6.CreateServer(); //采用默認(rèn)創(chuàng)建服務(wù)器,
//如果你成功安裝了IPv6可以使用正常使用
return 0;
}