基于IPv6的网络程序设计与实践

本文详细介绍了IPv4的局限性以及IPv6的出现背景,对比了IPv4和IPv6的地址结构和寻址方式。在Windows环境下,讨论了IPv6的安装和配置,并通过示例展示了如何使用getaddrinfo()和getnameinfo()函数进行地址解析。最后,通过一个简单的IPv6回应服务器程序,展示了如何进行IPv6网络程序设计,包括客户机和服务器的实现步骤。
2007年10月12日 02:18:00

IPv4 最初是由美国国防部开发的用于网际互联(IP)协议,后来它不仅发展了TCP,而且还进一步发展了IPv4IP 协议4.0版)。IPv4现在已经广泛应用于Internet网络中,同时也应用于大多数计算机系统,局域网和广域网中。然而,随着Internet 中的计算机数量突飞猛涨,IPv4 的局限性越发明显:

1.IPv4地址数目面临耗尽,日近紧张;

2.IPv4寻址并非完全分等级,这使得Internet 枢纽路由器必须维持大量的路由表,负担过重。

3.IPv4的地址必须被静态分配或通过配置协议(如:DHCP)进行分配。IPv6的开发目标之一就是将提供更为简便的配置方案。

于是IPv66.0版本)应运而生。在Window系统中,Windows XP 提供了IPv6developer-release版本;Windows 2000也可在http://www.microsoft.com/ipv6 下载 IPv6协议预览。下图在本人计算机上成功安装的示例图:

-1 IPV6 安装示例

一.IPv4地址及其寻址

1.IPv4地址

IPv4地址(常称IP地址)用一个32位数表示;通常表示位十进制格式,地址的每8位字节被表示转为一个十进制的数值,并由句点分隔,如:192.168.0.1IPv4地址 通常分为ABCDE 五类。

2IPv4寻址

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位字节分段表示,显示为冒号分隔的十六进制:

21DA00D300002F3AB234ED129C5ADAC3

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中引入了新的寻址函数。

1getaddrinfo(),它提供独立于协议的名称解析:

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应设置为NULLservname为欲绑定的端口;AI _CANONNAME 表示nodename 是主机名;AI_NUMBERICHOST 表示, nodename 是一个文字字符串地址(如:“192.168.0.1”)

l ai_family 选值:AI_INETPF_INET(IPv4地址簇);AI_INET6PF_INET6(IPv6地址簇)AI_UNSPEC(未指定,可能是IPv4IPv6 地址簇)

l ai_socktype选值:SOCK_DGRAMUDP类型套接字);SOCK_STREAM (TCP 型套接字)

l ai_protocol 选值:IPPROTO_TCP (TCP/IP协议)

如果函数解析成功,解析后的地址将通过res返回。如果名称被解析为多个地址,则返回一个由ai_next 字段形成的链表。每个由名称解析的地址在ai_addr中表示,长度在ai_addrlen中表示。

2getnameinfo()函数与getaddrinfo()相对应,功能相反。

. int getnameinfo(

const struct sockaddr FAR *sa,

socklen_t salen,

char FAR *host,

DWORD hostlen,

char FAR *serv,

DWORD servlen,

Int flags);

以上参数的含义比较明显,不再一一说明。

3.释放函数: freeaddrinfo(res)

四、兼容IPv4IPv6的网络程序设计

兼容IPv4IPv6的网络程序,显然涉及到两个部分:客户机和服务器。

Windows 网络编程中,Winsock是一种标准的API(应用程序接口)Winsock2版本已经发展成独立于协议的的接口,被广泛应用于Windows平台中。

><客户机程序设计

对于客户机来说,不管是建立TCP/UDP 连接,它都应知道服务器的主机名或IP 地址,同时将服务器地址解析为IPv4IPv6地址都可以,一般可以考虑一下步骤:

SOCKET s;

struct addrinfo,hints,*res=NULL;

char *szRemoteAddress;//主机名或IP 地址

char *szRemotePort;//端口号

int rc;

1.getaddrinfo() 函数解析地址。hins结构中 使用AF_UNSPEC标志,便可以获得地址簇类型(IPv4IPv6)。

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-

if(s==INVALID_SOCKET)

{//创建套接字失败

}

2.使用返回的addrinfo结构中的ai_addr来调用其他函数(connect(),send()).

rc==connect(s,res-

if(rc==SOCKET_ERROR)

{//连接失败;

}

。。。//完成其他编程

><服务器程序设计

服务器程序设计,应考虑到IPv4IPv6 都具有各自的堆栈;因此如果服务器希望能同时接受IPv4IPv6的连接,就必须能同时创建IPv4IPv6套接字;一般可以考虑一下步骤:

SOCKET socklisten[2];//监听Socket变量

char *szPort=”8080”;//监听端口

struct addinfo hints,*res=NULL,*ptr=NULL;

int rc,i=0;

1. 调用getaddrinfo()函数,该结构包含AI_PASSIVEAF_UNSPEC标志,以及所需的套接字类型、协议及所需的本地端口(用来监听和接受数据等)。函数将返回的两个addrinfo结构,分别可用于IPv4IPv6监听地址:

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-

if(socklisten[i]==INVALID_SOCKET){//创建失败处理;}

rc=bind(socklisten[i],ptr-

if(rc==SOCKET_ERROR){//绑定失败处理}

rc=listen(slisten[i],7)//开始监听

if(rc==SOCKET_ERROR){//监听失败处理}

i++;

ptr=ptr-

}

。。。

//完成其他编程

五、程序实例

在这里,给出一个基于IPV6的简单回应(ECHO)服务器程序.

1.建立CIPv6

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

#endif // _MSC_VER < 1000

#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-

if (ServSock == INVALID_SOCKET){

fprintf(stderr, "socket() failed with error %d: %s/n",

WSAGetLastError(), DecodeError(WSAGetLastError()));

WSACleanup();

return -1;

}

// 绑定套接字

if (bind(ServSock, AddrInfo-

{

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;

}

下图就是通过TELNET(成功安装IPV6,telnet将支持IPV6) 与服务器的通信示例

图-2服务端运行示例


参考文献:

[1] Microsoft Windows 网络编程(第二版)

[]Anthony Jones, Jim Ohlund 编著 杨合庆 译

北京:清华大学出版社 2003 1

[2] www.microsoft.com/ipv6 网站



Trackback: http://tb.blog.youkuaiyun.com/TrackBack.aspx?PostId=1821052


校园文件传输说明文档 版本1.0.20100905 使用方法: 首先接受方和发送方必须正常运行本程序,发送方在 接收方地址 栏 填入接收方的IP(也可以是IPv6)地址,打开要发送的文件,点击发送,等待对方回应即可 1.本软件同时支持IPv4和IPv6协议,只要双方主机有一种协议可接入网络即可 2.双击处于Running 状态的任务,可以停止任务,注意本软件暂不支持断点续传,停止后此次任务将失败 3.支持文件鼠标拖拽 4.暂不支持文件夹传送 常见问题(Q&A) 1.传输范围 理论上两台主机,只要发送方能ping通接收方,发送方就可以给接收方发送文件, 局域网内可以互传; 全校内可以互传; 不同学校可以互传; ADSL宽带用户可以传; 注意:如果双方在不同的内网下是无法传输的,记住只要发送方能ping通接收方,发送方就可以给接收方发送文件 2.和飞鸽的区别 飞鸽只是局域网传输,不同局域网是无法传输的,本软件无此限制;关于用户列表问题,只有局域网才可以获得用户列表,所以飞鸽有,本程序没有,因为超脱局域网是无法确定用户的 3.和QQ的区别 首先说明QQ的传输能力很强,本软件很多方面暂时还比不上,但某些方面还是比QQ有优势,如下 a.QQ需要你有账户,并且要登录成功,还只能好友间才能传输,本软件无此限制 b.QQ不支持IPv6协议,对于校园网络用户来说每个月的流量可是很宝贵的,而QQ会占用IPv4流量,致使根本不敢过多传输大文件,而一般高校是不限制IPv6流量的,而且IPv6速度也较快,又不限制流量,何乐而不为呢。事实上本软件的作者就是因为这个原因才开发此软件的。经历多年呕心沥血不吃不喝不眠不休,翻阅n多资料,敲烂n多键盘,终于大功告成。
SixMan 这是上次IPv6文件传输的升级版,新特性如下: 1.加入了即时通信,也许只是鸡肋,大家别BS我啊 2.支持文件夹传输 3.解决上一版中,有些情况下只能单向传文件的重大缺陷,现在只要双方建立了连接,无论哪方发起的连接,只要连接建立就能互传文件... 使用方法: 点击 添加用户-> 双击“新加入的用户” -> 在弹出的聊天窗口 输入 对方(不是你自己的) IP(v6) -> 点击 连接 -> 等待连接成功后 即可 发送文件时 只需将待发送的文件(夹)拖拽的窗口即可 点击 查看本机IP 按键可以查看本机的IP地址信息,其结果取自在cmd窗口运行ipconfig命令的结果 设置按钮里 可以修改昵称 有朋友说还要知道对方IP才能连接,太麻烦了... 问这个问题的朋友可能不太了解现在的网络协议等相关知识,没有网络地址是无法通信的,两个点要想通信必须要知道对方网络地址才行, 可能有朋友不服气,说为什么QQ就不用,这个问题只要懂一点计算机知识的人都知道,人家腾讯有钱,把用户登录服务放在一个有着固定公网 IP的服务器上,这个IP永不会变,你的QQ客户端内部是集成了这个IP的,所以你登陆时不需要你输入IP,而且所有QQ用户都是通过这一个服务器登录, 腾讯的数据库保存着所有QQ用户的个人信息和好友列表... 我可没有钱去置办那么庞大的服务器和数据库,而且我的软件不是QQ那样的商业软件,不过是为了解决平时一些应用而开发的一款小工具,应用范围是不同于 QQ的,也根本不是用来代替QQ的.... 注意: 假设A和B通信 那么只要A连接到B 或者 B连接到A 即可,无需双向连接,连接后即可双向传文件 有时连接不成功可以换一个IP(v6)地址,一般情况下会有多个IPv6地址,一个不行换一个试试,再不行要关闭防火墙。 遇到问题可以找客服 QQ:191730977 Email: acmtiger@gmail.com 感谢大家的支持!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值