您的位置:首页 > 其它

UDP协议下:单一socket复用 IPV4与IPV6地址

2014-04-03 17:04 513 查看
最近学习套接字编程,简单的总结了一下IPV4与IPV6地址复用的工作。

UDP协议下:单一socket复用 IPV4与IPV6地址

1服务器端

通常情况下,对于一个简单的UDP服务器,其工作流程如下:

1.1初始化套接字:

Socketfd = socket(int domain,int type,int protocol)

对于希望复用ipv4与ipv6地址的服务器来说,比较好的做法是将domain参数定义为AF_INET6。

1.2获取服务器地址信息:

获取与socket关联的地址,复用IPV4以及IPV6的套接字,应该调用下面函数进行地址信息的获取:

getaddrinfo(char * host,char * server,struct addrinfo * hint,struct addrinfo ** result)

该函数将返回一个 struct addrinfo * 结构体指针链表,其表头地址储存于 struct addrinfo ** result 中。

下面简单介绍一下linux中struct addrinfo的实现

Struct addrinfo

{

int ai_flags;

int ai_family;

int ai_socktype;

int ai_protocol;

socklen_len ai_addrlen;

struct sockaddr * ai_addr;

char *ai_canonname;

struct addrinfo * ai_next;

}

该结构体各成员具体含义稍后介绍。

再来观察getaddrinfo()函数,其中参数 struct addrinfo * hint可以理解为一个用于过滤返回内容的参数。只有与hint属性匹配的addrinfo *才回被添加到返回的链表中。

回头来介绍一下struct addrinfo中各成员的含义,以及在调用getaddrinfo()函数时,hint中可以被赋值从而用于过滤的成员。

1.2.1 ai_flag选项

可以是零个或多个在一起的AI_XXX值,具体如下:

AI_PASSIVE 套接字将用于被动打开(或监听绑定),通常配置于server端。

AI_CANONNAME 告知getaddrinfo()函数返回主机的规范名字。

AI_ADDRCONFIG 按照所在主机的配置选择返回地址类型。也就是说返回除本机回馈接口以外的网络接口配置的IP地址版本一致的IP地址。

1.2.2 ai_family 选项

AF_INET 指定返回IPV4地址信息;

AF_INET6 指定返回IPV6地址信息;

AF_UNSPEC 未指定返回IP地址版本,也就是说符合hint过滤条件的所有地址信息都将返回。

1.2.3 ai_socktype 选项

SOCK_DGRAM 简单理解为UDP

SOCK_STREAM 简单理解为TCP

1.2.4 ai_protocol 选项

该选项通常可由ai_family以及ai_socktype共同确定。

以上4个成员为getaddrinfo()函数的调用者可以在hint中配置的选项。

值得注意的是,在通用IPV4与IPV6地址的服务器中,调用getaddrinfo()函数时,通常将主机地址设置为NULL,并且hint->ai_flag = AI_PASSIVE,hint->ai_family = AF_INET6,这样将会得到本机的通配地址,并且套接字用于被动接受数据。从而支持IPV4与IPV6地址的通用。

下面给出一个调用getaddrinfo()函数返回地址信息的简单用例

#include <stdio.h>

#include <stdlib.h>

#include <sys/socket.h>

#include <netdb.h>

#include <arpa/inet.h>

#include <netinet/in.h>

void printf_family(struct addrinfo * aip)

{

……

}

void printf_type (struct addrinfo *aip)

{

……

}

void printf_protocol(struct addrinfo * aip)

{

……

}

void printf_flags(struct addrinfo * aip)

{

……

}

int main(int argc , char * argv[])

{

struct addrinfo *ailist, * aip;

struct addrinfo hint;

const char * addr;

int err;

char abuf_v4[INET_ADDRSTRLEN]; // IPV4

char abuf_v6[INET6_ADDRSTRLEN];//IPV6

hint.ai_flags = 0 ;

hint.ai_family = AF_UNSPEC;

hint.ai_socktype = SOCK_DGRAM;

hint.ai_protocol = 0;

hint.ai_addrlen = 0;

hint.ai_canonname = NULL;

hint.ai_addr = NULL;

hint.ai_next =NULL;

if((err = getaddrinfo(argv[1],argv[2],&hint,&ailist)) != 0)

{

printf("get addrinfo err \n %s\n",gai_strerror(err));

return 0;

}

for(aip = ailist; aip != NULL ; aip = aip->ai_next )

{

printf_flags(aip);

printf_family(aip);

printf_type(aip);

printf_protocol(aip);

printf("\n");

printf("%s",aip->ai_canonname);

printf("\n");

int sfd = socket(aip->ai_family, aip->ai_socktype,

aip->ai_protocol);

if (sfd == -1)

continue;

}

exit (0);

}

1.3将地址与套接字绑定

bind(int sockfd, struct sockaddr * addr, socklen_t addr_len)

将上面配置的socket与getaddrinfo()函数返回的地址信息绑定。

1.4 接收数据包并向客户端返回消息

作为UDP连接,调用函数:

recvfrom(int sockfd,,char * buf, int bufsize,int flag,struct sockaddr *peer_addr, socklen_t * len);

该函数接受客户端发送到本机地址的数据,并通过peer_addr,返回客户端地址,len保存该地址信息有效长度。

对于同时兼容IPV4以及IPV6地址的服务器,需要处理不同版本的IP地址,因此需要引用一个通用的结构体用来保存两种不同长度的地址信息,所以有比较介绍一下struct sockaddr_storage结构

不同于struct sockaddr,新的struct sockaddr_storage足以容纳系统所支持的任何套接字地址结构,sockaddr_storage结构在<netinet/in.h>头文件中定义

struct sockaddr_storage

{

uint8_t ss_len; /* length of this struct (implementation dependent) */

sa_family_t ss_family; /* address family: AF_xxx value */

/*其他字段对用户来说是透明的 故没有列出*/

};

sockaddr_storage和sockaddr的主要差别:

sockaddr_storage通用套接字地址结构满足对齐要求

sockaddr_storage通用套接字地址结构足够大,能够容纳系统支持的任何套接字地址结构。

上面使用recvfrom函数,通过struct sockaddr_storage * peer_addr返回了客户端地址信息,从而可以使用函数:

Sendto (int sockfd, void * buf, int size, int flags,struct sockaddr * peer_addr, socklen_t peer_addr_len)

向客户端返回消息。

经过以上流程,就可以完成一个简单的兼容IPV4、IPV6地址的UDP服务器。

2客户端

2.1初始化套接字

Socketfd = socket(int domain,int type,int protocol)

与服务器端一样,对于希望复用ipv4与ipv6地址的用户,比较好的做法是将domain参数定义为AF_INET6。

2.2 获取服务器地址信息

getaddrinfo(char * server_ip,char * server_port,struct addrinfo * hint,struct addrinfo ** result)

2.3将地址与套接字绑定

bind(int sockfd, struct sockaddr * addr, socklen_t addr_len)

将上面配置的socket与getaddrinfo()函数返回的地址信息绑定。如果将套接字绑定到本机的一个特定IP(IPV4或IPV6地址),将无法实现IP地址的通用,为了解决这个问题,可将套接字绑定为本机的通配地址(0.0.0.0 or 0::0)。

或者,可以使用套接字选项中的SO_BINDTODEVICE选项,将套接字绑定至固定的网卡。

2.4 向服务器发送数据包并接收返回消息

在获取了地址信息后,客户端发送数据与服务器回复消息的行为相同,同样调用sendto函数向对端地址发送数据。

客户端使用recvfrom函数接收服务器传回的消息。

至此,我们完成了一个可通用IPV4与IPV6地址的服务器---客户端模型。

下面是简单的代码,基本借用了man getaddrinfo中的用例。通过man getaddrinfo 可以得到更加详细的说明。

服务器端:

#include <sys/types.h>

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

#include <sys/socket.h>

#include <netdb.h>

#define BUF_SIZE 500

#ifndef HOST_NAME_MAX

#define HOST_NAME_MAX 256

#endif

void printf_family(struct addrinfo * aip)

{

printf(" family ");

switch (aip->ai_family)

{

case AF_INET:

printf("ip_v4");

break;

case AF_INET6:

printf("ip_v6");

break;

default:

printf("unknown");

}

}

void printf_type (struct addrinfo *aip)

{

printf(" type ");

switch (aip->ai_socktype)

{

case SOCK_STREAM:

printf("stream");

break;

case SOCK_DGRAM:

printf("datagram");

break;

case SOCK_SEQPACKET:

printf("seqpacket");

break;

case SOCK_RAW:

printf("raw");

break;

default:

printf("unknown (%d)",aip->ai_socktype);

}

}

void printf_protocol(struct addrinfo * aip)

{

printf(" protocol ");

switch (aip->ai_protocol)

{

case 0:

printf("default");

break;

case IPPROTO_TCP:

printf("TCP");

break;

case IPPROTO_UDP:

printf("UDP");

break;

case IPPROTO_RAW:

printf("RAW");

break;

default:

printf("unknown (%d)",aip->ai_protocol);

}

}

void printf_flags(struct addrinfo * aip)

{

printf(" flags ");

if(aip->ai_flags == 0)

printf(" 0 ");

else

{

if (aip->ai_flags & AI_PASSIVE)

{

printf("
baa7
passive");

return;

}

if (aip->ai_flags & AI_CANONNAME)

{

printf(" canon");

}

else

{

printf(" unknown");

}

}

}

//________________________________________//

int main(int argc, char *argv[])

{

struct addrinfo hints;

struct addrinfo *result, *rp;

int sfd, s;

struct sockaddr_storage peer_addr;

socklen_t peer_addr_len;

ssize_t nread;

char buf[BUF_SIZE];

if (argc != 2)

{

fprintf(stderr, "Usage: %s port\n", argv[0]);

exit(EXIT_FAILURE);

}

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

hints.ai_family = AF_INET6; /* Allow IPv4 or IPv6 */

hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */

hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */

hints.ai_protocol = 0; /* Any protocol */

hints.ai_canonname = NULL;

hints.ai_addr = NULL;

hints.ai_next = NULL;

s = getaddrinfo(NULL, argv[1], &hints, &result);

if (s != 0)

{

fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));

exit(EXIT_FAILURE);

}

printf("get addr info end \n");

for (rp = result; rp != NULL; rp = rp->ai_next)

{

printf("init socket\n");

sfd = socket(rp->ai_family, rp->ai_socktype,rp->ai_protocol);

printf("socket end\n");

if (sfd == -1)

continue;

if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)

break;

else

printf("bind err \n");

close(sfd);

}

if (rp == NULL)

{ /* No address succeeded */

fprintf(stderr, "Could not bind\n");

exit(EXIT_FAILURE);

}

freeaddrinfo(result); /* No longer needed */

for (;;)

{

peer_addr_len = sizeof(struct sockaddr_storage);

nread = recvfrom(sfd, buf, BUF_SIZE, 0,(struct sockaddr *) &peer_addr, &peer_addr_len);

if (nread == -1)

continue;

char host[NI_MAXHOST], service[NI_MAXSERV];

s = getnameinfo((struct sockaddr *) &peer_addr,peer_addr_len, host, NI_MAXHOST,service, NI_MAXSERV, NI_NUMERICSERV);

if (s == 0)

{

printf("Received %ld bytes from %s:%s\n sockaddr_len %d\n",(long) nread, host, service,peer_addr_len);

printf("msg is : %s \n",buf);

}

else

fprintf(stderr, "getnameinfo: %s\n", gai_strerror(s));

if (sendto(sfd, buf, nread, 0,(struct sockaddr *) &peer_addr,peer_addr_len) != nread)

fprintf(stderr, "Error sending response\n");

}

}

客户端:

#include <sys/types.h>

#include <sys/socket.h>

#include <netdb.h>

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

#include <errno.h>

#include <net/if.h>

#define BUF_SIZE 500

int sv_sock_bind_device(int s, char * dev)

{

struct ifreq ifr;

strncpy(ifr.ifr_name,dev,IFNAMSIZ);

if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, (char*)&ifr, sizeof(struct ifreq)) != 0)

{

return -1;

}

return 0;

}

void printf_family(struct addrinfo * aip)

{

if(aip == NULL)

{

printf("prt NULL\n");

}

else

printf(" family ");

switch (aip->ai_family)

{

case AF_INET:

printf("ip_v4\n");

break;

case AF_INET6:

printf("ip_v6\n");

break;

default:

printf("unknown\n");

}

}

int main(int argc, char *argv[])

{

struct addrinfo hints;

struct addrinfo *result, *rp;

struct addrinfo *result_local, *rp_local;

int sfd, s, j;

int sfd_bak;

size_t len;

ssize_t nread;

char buf[BUF_SIZE];

char buf2[BUF_SIZE];

struct sockaddr scr_addr;

struct sockaddr_storage peer_addr;

socklen_t peer_addr_len;

peer_addr_len = sizeof(struct sockaddr_storage);

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

char ip_addr[INET6_ADDRSTRLEN];

if (argc < 3)

{

fprintf(stderr, "Usage: %s host port msg...\n", argv[0]);

exit(EXIT_FAILURE);

}

sfd = socket(AF_INET6,SOCK_DGRAM,0);

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

hints.ai_family = AF_UNSPEC;

hints.ai_socktype = SOCK_DGRAM;

hints.ai_flags = AI_PASSIVE;

hints.ai_protocol = 0;

s = getaddrinfo(argv[1], argv[2], &hints, &result);

if (s != 0)

{

fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));

exit(EXIT_FAILURE);

}

for (rp = result; rp != NULL; rp = rp->ai_next)

{

printf_family(rp);

break;

}

//if(sv_sock_bind_device(sfd, "eth0")==-1)

//printf("bind err\n");

int ret = sendto(sfd, argv[3],strlen(argv[3]) , 0,rp->ai_addr,28) ;

if(ret != strlen(argv[3]))

{

printf("ret=%d len=%d error:%d %s\n", ret, strlen(argv[3]), errno, strerror(errno));

exit(EXIT_FAILURE);

}

int socklen = sizeof(struct sockaddr);

nread = recvfrom(sfd, buf2, BUF_SIZE, 0,(struct sockaddr *)&peer_addr, &peer_addr_len);

printf("Received msg back %s\n",buf2);

exit(EXIT_SUCCESS);

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  socket ipv4 ipv6