搜档网
当前位置:搜档网 › IPV4与IPV6兼容socket编程接口详解

IPV4与IPV6兼容socket编程接口详解

IPV4与IPV6兼容socket编程接口详解
IPV4与IPV6兼容socket编程接口详解

IPV4与IPV6 兼容的socket编程

套接字Socket可以看成在两个程序进行通讯连接中的一个端点,一个程序将一段信息写入Socket中,该Socket将这段信息发送给另外一个Socket中,使这段信息能传送到其他程序中。

生成套接字,主要有3个参数:通信目的IP地址、使用的协议,使用的端口号。通过将这3个参数结合起来,应用层就可以和传输层(或网络层)通过套接字接口,区分来自不同应用程序进程或网络连接的通信。

Socket通讯流程图:

TCP编程的服务器端一般步骤是:

1、创建一个socket,用函数socket();

2、设置socket属性,用函数setsockopt(); * 可选

3、绑定IP地址、端口等信息到socket上,用函数bind();

4、开启监听,用函数listen();

5、接收客户端上来的连接,用函数accept();

6、收发数据,用函数send()和recv(),或者read()和write(); 之后close(clientsocket);

7、关闭网络连接; close(socket)

TCP编程的客户端一般步骤是:

1、创建一个socket,用函数socket();

2、设置socket属性,用函数setsockopt();* 可选

3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选

4、设置要连接的对方的IP地址和端口等属性;

5、连接服务器,用函数connect();

6、收发数据,用函数send()和recv(),或者read()和write();

7、关闭网络连接;

UDP编程的服务器端一般步骤是:

1、创建一个socket,用函数socket();

2、设置socket属性,用函数setsockopt();* 可选

3、绑定IP地址、端口等信息到socket上,用函数bind();

不需要listen()

4、数据交互,用函数recvfrom(); sendto()

5、关闭网络连接;

UDP编程的客户端一般步骤是:

1、创建一个socket,用函数socket();

2、设置socket属性,用函数setsockopt();* 可选

3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选

4、设置对方的IP地址和端口等属性;

函数connect();* 可选

5、数据交互,用函数sendto(); recvfrom();

如果调用了connect就直接可以用send和recv了,这时最后两个参数会自动用connect建立时的地址信息填充

6、关闭网络连接;

几个Socket 地址结构相关结构体

注:在操作系统(内核)不同情况下,首两个字节可能是length+family

#ifdef ISC_PLATFORM_HA VESALEN

ntp_u_int8_t ss_len; /* address length */

ntp_u_int8_t ss_family; /* address family */

#else

short ss_family; /* address family */

#endif

struct in_addr {

u_int32_t s_addr; /* 4bytes, IPv4 address */

};

=4

struct in6_addr {

u_int8_t s6_addr[16]; /* IPv6 address */

}

=16

一般socket函数都是传入的这个类型指针

struct sockaddr {

sa_family_t sa_family; /* unsigned short address family, AF_xxx */ char sa_data[14]; /* 14 bytes of protocol address */

};

2+14=16

struct sockaddr_in {

short int sin_family; /* Address family */

unsigned short int sin_port; /* Port number */

struct in_addr sin_addr; /* Internet address */

unsigned char sin_zero[8]; /* Same size as struct sockaddr */

};

2+2+4+8=16

struct sockaddr_in6{

sa_family_t sin6_family; //地址簇类型,为AF_INET6

in_port_t sin6_port; //16 位端口号,网络字节序

uint32_t sin6_flowinfo; //32 位流标签

struct in6_addr sin6_addr; //128 位IP 地址

/*uint32_t sin6_scope_id;*/(新版本可能会多4字节scope id, RFC2553)

}

2+2+4+16=24

#define UNIX_PATH_MAX 108

struct sockaddr_un {

sa_family_t sun_family; /* AF_UNIX,本地进程通讯*/

char sun_path[UNIX_PATH_MAX]; /* pathname */

};

=110

例:

my_addr.sun_family = AF_UNIX;

strncpy(my_addr.sun_path, MY_SOCK_PATH, sizeof(my_addr.sun_path) - 1);

sockaddr_storage能提供严格的结构对齐

sockaddr_storage能容纳系统支持的更大的地址结构,容器

在使用sockaddr_storage定义的变量时,尽量强制转换成struct sockaddr, struct sockaddr_in, struct sockaddr_in6后操作

struct __kernel_sockaddr_storage {

unsigned short ss_family; /* address family */

/* Following field(s) are implementation specific */

char __data[_K_SS_MAXSIZE - sizeof(unsigned short)];

/* space to achieve desired size, */

/* _SS_MAXSIZE value minus size of ss_family */

} __attribute__ ((aligned(_K_SS_ALIGNSIZE))); /* force desired alignment */

#define sockaddr_storage __kernel_sockaddr_storage

=128

不同于sockaddr_storag的一个IPV4,IPV6兼容结构体(来源于busybox)

typedef struct len_and_sockaddr {

socklen_t len;

union {

struct sockaddr sa;

struct sockaddr_in sin;

struct sockaddr_in6 sin6;

};

} len_and_sockaddr;

用struct sockaddr_storage的好处,看:

取得res后声明一个struct sockaddr_storage变量以兼容IPV4和IPV6 //….getaddrinfo后建立socket

struct sockaddr_storage local_addr;

memset(&local_addr, 0, sizeof(struct sockaddr_storage));

if(res->ai_family == AF_INET6)

{

struct sockaddr_in6 *local_addr6 = (struct sockaddr_in6 *)&local_addr;

local_addr6->sin6_family = AF_INET6;

local_addr6->sin6_port = htons(udp_local_port);

local_addr6->sin6_addr = in6addr_any;

}

else if(res->ai_family == AF_INET)

{

struct sockaddr_in *local_addr4 = (struct sockaddr_in *)&local_addr;

local_addr4->sin_family = AF_INET;

local_addr4->sin_port = htons(udp_local_port);

local_addr4->sin_addr.s_addr = htonl(INADDR_ANY);

}

if(bind(usd, (struct sockaddr *)&local_addr, sizeof(local_addr)) == -1)

{

perror("bind");

fprintf(stderr, "could not bind to local udp port %d\n", udp_local_port);

exit(1);

}

freeaddrinfo(res);

//….send recv等数据交互

一些重要函数

主机字节序到网络字节序间的转换函数

uint16_t htons(uint16_t hostshort);

uint16_t ntohs(uint16_t netshort);

uint32_t htonl(uint32_t hostlong);

uint32_t ntohl(uint32_t netlong);

例:本机字节序与网络字节序

short int port=0x1234; //本地存储, windows为小端系统,所以存34 12 sockaddr_in.sin_port = htons(port); //网络传输,TCP包头里的数据顺序

IPV4字符串地址和网络序ip地址的转换函数

int inet_aton(const char *cp, struct in_addr *inp);

char *inet_ntoa(struct in_addr in);

in_addr_t inet_addr(const char *cp);

IPV6,IPV4兼容的字符串地址和网络序ip地址的转换函数

int inet_pton(int af, const char *src, void *dst);

const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt);

两个地址长度的宏

#define INET_ADDRSTRLEN 16

#define INET6_ADDRSTRLEN 46

IPV4主机名和地址的转换

struct hostent *gethostbyname(const char *name);

struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);

IPV6,IPV4兼容的主机名和地址的转换函数

struct hostent *gethostbyname2(const char *name, int af); //扩展版本,也支持IPV6

int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);

void freeaddrinfo(struct addrinfo *res); //调用时机:在bind或connect之后就可以释放该内存

int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags);

getaddrinfo函数原型

addrinfo结构

首先了解下:

1.通配地址

IPV4:0.0.0.0

IPV6:::

https://www.sodocs.net/doc/2f6878260.html,stat指令查询监听,绑定等的IP和port信息

作为服务器端时:

struct addrinfo hints, *res=NULL;

memset(&hints,0,sizeof(hints));

hints.ai_family=AF_UNSPEC;

//hints.ai_family=AF_INET6;

hints.ai_socktype=SOCK_DGRAM;

//hints.ai_protocol=IPPROTO_UDP;

hints.ai_flags=AI_PASSIVE;

rc=getaddrinfo(NULL,"123",&hints,&res);

socket=socket(res->ai_family,res->ai_socktype,res->ai_protocol);

bind (socket,res->ai_addr,res->ai_addrlen);

hints可看作是过滤条件

res返回一个地址链表,一般来说用第一个bind成功就可以了

可以看到sa_addr的sa_data在端口后面是全0的,相当于是通配地址

因此其相当于下面不调用getaddrinfo方法的简化版本:

usd = socket(AF_INET6, SOCK_DGRAM, 0);

struct sockaddr_storage local_addr;

memset(&local_addr, 0, sizeof(struct sockaddr_storage));

struct sockaddr_in6 *local_addr6 = (struct sockaddr_in6 *)&local_addr;

local_addr6->sin6_family = AF_INET6;

local_addr6->sin6_port = htons(123);

local_addr6->sin6_addr = in6addr_any;

if(bind(usd, (struct sockaddr *)&local_addr, sizeof(local_addr)) == -1)

……

提示:用IPV6建立服务器端的话, 即使客户端仍用IPV4的socket连接也可以正常通讯, IPV4的地址会被转换成这种地址::ffff:192.168.27.25

而旧的IPV4时用以下代码:

usd = socket(AF_INET, SOCK_DGRAM, 0);

struct sockaddr_in servaddr;

bzero(&servaddr,sizeof(servaddr));

servaddr.sin_family = AF_INET;

servaddr.sin_port = htons(123);

servaddr.sin_addr.s_addr = htons(INADDR_ANY);

if (bind(servfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1)

……

服务器端从IPV4移植到IPV6要做些什么?

可以调用getaddrinfo得到AF_INET6的通配地址,也可以直接将sockaddr_in结构体更改为sockaddr_in6结构体并相应初始化即可

客户器端如何同时兼容IPV4和IPV6?

按如上方法就可同时兼容IPV4和IPV6的连接请求

作为客户器端时:

memset(&hints,0,sizeof(hints));

hints.ai_family=AF_UNSPEC;

hints.ai_socktype=SOCK_DGRAM;

//hints.ai_protocol=IPPROTO_UDP;

hints.ai_flags=AI_CANONNAME;

rc=getaddrinfo("https://www.sodocs.net/doc/2f6878260.html,","ntp",&hints,&res);

socketfd=socket(res->ai_family,res->ai_socktype,res->ai_protocol);

connect(sockfd,res->ai_addr , res->ai_addrlen);

如图res返回两个ai_addr,一个sa_family是0x2为AF_INET,另一个为0x17 AF_INET6;

说明该主机既有AAAA(或A6)记录(IPV6)地址,同时又有A记录(IPV4)地址,那么AAAA 记录将作为sockaddr_in6结构返回,而A记录则作为sockaddr_in结构返回

dig AAAA https://www.sodocs.net/doc/2f6878260.html,

客户器端从IPV4移植到IPV6要做些什么?

如果服务器端具有IPV4地址则基本不需改动,因为IPV6的服务器对IPV4客户端是兼容的,如果是域名且是IPV6的地址则必须调用getaddrinfo得到addrinfo的可用IP地址。

客户器端如何同时兼容IPV4和IPV6?

指定getaddrinfo的family为AF_UNSPEC,会返回可用的IPV4和IPV6 IP地址链表。用返回的family、socktype、protocol建立socket,用返回的IP地址进行连接请求

实例:

修改ntpclient.c以支持IPV6的NTP服务器,还添加了一个参数[-v ipversion],具体请直接查看代码,

旧的ntpclient源码:

附录:

IPV4包头20字节

IPV6包头40字节

相关主题