程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++中的IPv6網絡程序設計

C++中的IPv6網絡程序設計

編輯:關於C++

IPv4最初是由美國國防部開發的用於網際互聯(IP)協議,後來它不僅發展了TCP,而且還進一步發展了IPv4(IP協議4.0版)。IPv4現在已經廣泛應用於Internet網絡中,同時也應用於大多數計算機系統,局域網和廣域網中。然而,隨著Internet中的計算機數量突飛猛漲,IPv4的局限性越發明顯:

1.IPv4地址數目面臨耗盡,日近緊張;

2.IPv4尋址並非完全分等級,這使得Internet樞紐路由器必須維持大量的路由表,負擔過重。

3.IPv4的地址必須被靜態分配或通過配置協議(如:DHCP)進行分配。IPv6的開發目標之一就是將提供更為簡便的配置方案。

於是IPv6(6.0版本)應運而生。在Window系統中,Windows XP 提供了IPv6的developer-release版本;Windows 2000也可在http://www.microsoft.com/ipv6 下載 IPv6協議預覽。下圖在本人計算機上成功安裝的示例圖:

圖-1 IPV6 安裝示例

一.IPv4地址及其尋址

1.IPv4地址

IPv4地址(常稱IP地址)用一個32位數表示;通常表示位十進制格式,地址的每8位字節被表示轉為一個十進制的數值,並由句點分隔,如:192.168.0.1;IPv4地址 通常分為A、B、C、D、E 五類。

2.IPv4尋址

在Winsock 中,通過SOCKADDR_IN 結構來指定IPv4的地址和服務斷口信息:

struct sockaddr_in {

short sin_family ;//必須為AF_INET,表示使用IPv4地址簇

u_short sin_port; //TCP/UDP 端口

struct in_addr sin_addr;// IP地址(以網絡字節順序排列, 4個字節)

char sin_zero[8];//填充項

}   

二.IPv6地址及其尋址

1.IPv6地址

IPv6地址與IPv4地址的顯著的不同是128位,長度是IPv4地址的4倍。IPv6地址由16位字節分段表示,顯示為冒號分隔的十六進制:

21DA:00D3:0000:2F3A:B234:ED12:9C5A:DAC3

IPv6地址的分配

分配

地址前綴

保留地址0000 0000

為NSAP預留0000 0001

可聚合的全球單播地址001

鏈接-本地單播地址1111 1110 10

站點-本地單播地址1111 1110 11

多播地址1111 1111

2.IPv6的尋址

Winsock中,尋址使用一下結構:

struct  sockaddr_in6{
short sin6_family;// 地址簇:AF_INET6
u_short sin6_port;//端口號
u_long sin6_flowinfo;//連接標記通信量
struct in6_addr sin6_addr;//16字節結構的IPv6 地址
u_long sin6_scope_id;//地址所有的接口索引

}

三.獨立於協議的地址及名稱解析 

由此可見在尋址時,IPv4使用16字節的SOCK_ADDR_IN 結構,IPv6則使用28 字節的SOCK_ADDR_IN6 結構。為了解決這個問題,IPv6中引入了新的尋址函數。 [Page]

1.getaddrinfo(),它提供獨立於協議的名稱解析:

int getaddrinfo(
 const char *FAR *nodename,
 const  char FAR* servname,
 const struct addrinfo FAR *hins,
 struct addrinfo FAR *FAR *res 
);

l 第一參數:nodename,以空字節結束的主機名或文字地址

l 第二參數:servname,包含端口或服務名(如:FTP,TELNET)的以空字節結束的字符串

l 第三個參數:hins 是一個結構(addrinfo),包含名稱解析的執行方式選項

l 第四個參數:res ,用於返回 addrinfo 結構的一個或多個鏈表

結構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函數的地址,此時nodename應設置為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協議)

如果函數解析成功,解析後的地址將通過res返回。如果名稱被解析為多個地址,則返回一個由ai_next 字段形成的鏈表。每個由名稱解析的地址在ai_addr中表示,長度在ai_addrlen中表示。

2.getnameinfo()函數與getaddrinfo()相對應,功能相反。

.      int getnameinfo(
                const struct sockaddr FAR *sa,
                socklen_t salen, [Page]
                char FAR *host,
              DWORD hostlen,
              char FAR *serv,
              DWORD servlen,
              Int flags);

以上參數的含義比較明顯,不再一一說明。

3.釋放函數:freeaddrinfo(res);

四、兼容IPv4和IPv6的網絡程序設計

兼容IPv4和IPv6的網絡程序,顯然涉及到兩個部分:客戶機和服務器。

在Windows 網絡編程中,Winsock是一種標准的API(應用程序接口),Winsock2版本已經發展成獨立於協議的的接口,被廣泛應用於Windows平台中。

<一>客戶機程序設計

對於客戶機來說,不管是建立TCP/UDP 連接,它都應知道服務器的主機名或IP 地址,同時將服務器地址解析為IPv4或IPv6地址都可以,一般可以考慮一下步驟:

SOCKET s;
struct addrinfo,hints,*res=NULl ;
char *szRemoteAddress;//主機名或IP 地址
char *szRemotePort;//端口號
int rc;

1.用getaddrinfo() 函數解析地址。hins結構中 使用AF_UNSPEC標志,便可以獲得地址簇類型(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結構中的ai_family,ai_socketype,ai_protocol字段來創建套接字。

s=socket(res->ai_family,ai_socktype,res->protocol );
if(s==INVALID_SOCKET)
{//創建套接字失敗 
}

2.使用返回的addrinfo結構中的ai_addr來調用其他函數(connect(),send()等).。

rc==connect(s,res->ai_addr,res->addrlen);
if(rc==SOCKET_ERROR)
{//連接失敗;
}
。。。//完成其他編程

<二>服務器程序設計

服務器程序設計,應考慮到IPv4和IPv6 都具有各自的堆棧;因此如果服務器希望能同時接受IPv4和IPv6的連接,就必須能同時創建IPv4和IPv6套接字;一般可以考慮一下步驟:

SOCKET socklisten[2];//監聽Socket變量
char *szPort=”8080”;//監聽端口
struct addinfo hints,*res=NULl ,*ptr=NULl ;
int rc,i=0;

1.  調用getaddrinfo()函數,該結構包含AI_PASSIVE,AF_UNSPEC標志,以及所需的套接字類型、協議及所需的本地端口(用來監聽和接受數據等)。函數將返回的兩個addrinfo結構,分別可用於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結構中的ai_family,ai_socketype,ai_protocol字段來創建套接字後;便可以使用addrinfo結構中的ai_addr 和ar_addrlen 字段調用綁定函數bind()。

while(ptr)
{
socklisten[i]=socket(ptr->ai_family,ptr->ai_socktype,ptr->ai_protocol );
if(socklisten[i]==INVALID_SOCKET){//創建失敗處理;}
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;
}
     。。。
//完成其他編程

五、程序實例

在這裡,給出一個基於IPV6的簡單回應(ECHO)服務器程序.

1.建立CIPv6類

// IPv6.h: 頭文件,這裡使用到了套接字中的“select I/O模型”

#define WIN32_LEAN_AND_MEAN    
#include <winsock2.h>
#include <ws2tcpip.h>
#include <tpipv6.h>    //IPv6 頭文件
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#pragma comment(lib, "ws2_32.lib")//套接字庫文件
#define DEFAULT_PORT "7274" // 默認端口
#define BUFFER_SIZE   64   // 數據緩沖區

class CIPv6
{
public:
 // 創建TCP 服務器
 int CreateServer(char *Port = DEFAULT_PORT,char *Address = NULl );
 void Usage(char *ProgName);//用戶信息提示
 LPSTR DecodeError(int ErrorCode);//獲取錯誤信息
 CIPv6();
 virtual ~CIPv6();

};

// IPv61.cpp: CIPv6類的實現 .
// 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: %s\n",
   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: %s\n", RetVal , gai_strerror(RetVal ));
  WSACleanup();
  return -1;
 }
 // 創建套接字
 ServSock = socket(AddrInfo->ai_family,AddrInfo->ai_socktype, AddrInfo->ai_protocol );
 if (ServSock == INVALID_SOCKET)
 {
  fprintf(stderr, "socket() failed with error %d: %s\n",
   WSAGetLastError(), DecodeError(WSAGetLastError()));
   WSACleanup();
   return -1;
 }
 // 綁定套接字
 if (bind(ServSock, AddrInfo->ai_addr, AddrInfo->ai_addrlen) == SOCKET_ERROR)
 {
  fprintf(stderr,"bind() failed with error %d: %s\n",
   WSAGetLastError(), DecodeError(WSAGetLastError()));
  WSACleanup();
  return -1;
 }
 // 偵聽
 if (listen(ServSock, 5) == SOCKET_ERROR)
 {
  fprintf(stderr, "listen() failed with error %d: %s\n",
   WSAGetLastError(), DecodeError(WSAGetLastError()));
  WSACleanup();
  return -1;
 }
 printf("'Listening' on port %s, protocol %s, protocol family %s\n",
  Port, \"TCP\",
  "PF_INET6");

 freeaddrinfo(AddrInfo);
 //使用select I/O 模型進行收發
 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: %s\n",
    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: %s\n",
   WSAGetLastError(), DecodeError(WSAGetLastError()));
   WSACleanup();
   return -1;
 }
 if (getnameinfo((LPSOCKADDR)&From, FromLen, Hostname,
  sizeof(Hostname), NULl , 0, NI_NUMERICHOST) != 0)
  strcpy(Hostname, "<unknown>");
  printf("\nAccepted connection from %s\n", Hostname);
  while(1)
  {
   //等待接受數據
   AmountRead = recv(ConnSock, Buffer, sizeof(Buffer), 0);
   if (AmountRead == SOCKET_ERROR)
   {
    fprintf(stderr, "recv() failed with error %d: %s\n", WSAGetLastError(), DecodeError(WSAGetLastError()));
     closesocket(ConnSock);
    break;
   }
   if (AmountRead == 0) {
    printf("Client closed connection\n");
     closesocket(ConnSock);
    break;
   }

   printf("Received %d bytes from client: [%.*s]\n",
    AmountRead, AmountRead, Buffer);
   //進行簡單ECHO 回應
   printf("Echoing same data back to client\n");
    RetVal = send(ConnSock, Buffer, AmountRead, 0);
   if (RetVal == SOCKET_ERROR)
   {
    fprintf(stderr, "send() failed: error %d: %s\n",
     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, "port\t\tPort on which to bind. (default %s)\n",
  DEFAULT_PORT);
 fprintf(stderr, "address\tIP 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.應用示例

#include "stdafx.h"
#include "IPv6.h"
int main(int argc, char* argv[])
{
 CIPv6 m_ipv6;
 m_ipv6.CreateServer(); //采用默認創建服務器,
 //如果你成功安裝了IPv6可以使用正常使用
 return 0;
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved