C++中的IPv6网络程序设计

C++中的IPv6网络程序设计
2010年06月08日
  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 安装示例
  [b]一.IPv4地址及其寻址
  [/b]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];//填充项
  }
  [b]二.IPv6地址及其寻址[/b]
  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;//地址所有的接口索引
  }
  [b]三.独立于协议的地址及名称解析[/b]
  由此可见在寻址时,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);
  [b]四、兼容IPv4和IPv6的网络程序设计[/b]
  兼容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=socket(ptr->ai_family,ptr->ai_socktype,ptr->ai_protocol);
  if(socklisten==INVALID_SOCKET){//创建失败处理;}
  rc=bind(socklisten,ptr->ai_addr,ptr->ai_addrlen);
  if(rc==SOCKET_ERROR){//绑定失败处理}
  rc=listen(slisten,7)//开始监听
  if(rc==SOCKET_ERROR){//监听失败处理}
  i++;
  ptr=ptr->ai_next;
  }
  。。。
  //完成其他编程
  [b]五、程序实例
  [/b] 在这里,给出一个基于IPV6的简单回应(ECHO)服务器程序.
  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" // 默认端口
  #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, "");
  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;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值