用c/c++语言在IPv6下的socket通信编程
下面为Daytime这个服务的源代码例子,同时兼容IPV6和IPV4的地址,最后部分有更多说明。
单播模式下的Server方代码:
“listen_server.h”:
#ifndef listen__server__h__
#define listen__server__h__
/*
listen_server
creates a server server socket listening at a hostname:service
using the family and socket type specified in the function
arguments.
*/
int
listen_server(const char *hostname,
const char *service,
int family,
int socktype);
#endif
“listen_server.cpp”:
#include
#include
#include
#include
#include “listen_server.h”
const int LISTEN_QUEUE=128;
int
listen_server(const char *hostname,
const char *service,
int family,
int socktype)
{
struct addrinfo hints, *res, *ressave;
int n, sockfd;
memset(&hints, 0, sizeof(struct addrinfo));
/*
AI_PASSIVE flag: the resulting address is used to bind
to a socket for accepting incoming connections.
So, when the hostname==NULL, getaddrinfo function will
return one entry per allowed protocol family containing
the unspecified address for that family.
*/
hints.ai_flags = AI_PASSIVE;
hints.ai_family = family;
hints.ai_socktype = socktype;
n = getaddrinfo(hostname, service, &hints, &res);
if (n <0) {
fprintf(stderr,
“getaddrinfo error:: [%s]\n”,
gai_strerror(n));
return -1;
}
ressave=res;
/*
Try open socket with each address getaddrinfo returned,
until getting a valid listening socket.
*/
sockfd=-1;
while (res) {
sockfd = socket(res->ai_family,
res->ai_socktype,
res->ai_protocol);
if (!(sockfd < 0)) {
if (bind(sockfd, res->ai_addr, res->ai_addrlen) == 0)
break;
close(sockfd);
sockfd=-1;
}
res = res->ai_next;
}
if (sockfd < 0) {
freeaddrinfo(ressave);
fprintf(stderr,
“socket error:: could not open socket\n”);
return -1;
}
listen(sockfd, LISTEN_QUEUE);
freeaddrinfo(ressave);
return sockfd;
}
TCP模式 “tcp_daytime_server.cpp”:
#include
#include
#include
#include
#include
#include
#include
#include “listen_server.h”
const char *DAYTIME_PORT=”13″;
int
main(int argc, char *argv[])
{
int listenfd, connfd;
socklen_t addrlen;
char timeStr[256];
struct sockaddr_storage clientaddr;
time_t now;
char clienthost[NI_MAXHOST];
char clientservice[NI_MAXSERV];
/* local server socket listening at daytime port=13 */
listenfd = listen_server( NULL, DAYTIME_PORT,
AF_UNSPEC, SOCK_STREAM);
if (listenfd < 0) {
fprintf(stderr,
“listen_socket error:: could not create listening ”
“socket\n”);
return -1;
}
for ( ; 😉 {
addrlen = sizeof(clientaddr);
/* accept daytime client connections */
connfd = accept(listenfd,
(struct sockaddr *)&clientaddr,
&addrlen);
if (connfd < 0)
continue;
memset(clienthost, 0, sizeof(clienthost));
memset(clientservice, 0, sizeof(clientservice));
getnameinfo((struct sockaddr *)&clientaddr, addrlen,
clienthost, sizeof(clienthost),
clientservice, sizeof(clientservice),
NI_NUMERICHOST);
printf(“Received request from host=[%s] port=[%s]\n”,
clienthost, clientservice);
/* process daytime request from a client */
memset(timeStr, 0, sizeof(timeStr));
time(&now);
sprintf(timeStr, “%s”, ctime(&now));
write(connfd, timeStr, strlen(timeStr));
close(connfd);
}
return 0;
}
UDP模式 “udp_daytime_server.cpp”:
#include
#include
#include
#include
#include
#include
#include “listen_server.h”
const char *DAYTIME_PORT=”13″;
int
main(int argc, char *argv[])
{
int listenfd, n;
socklen_t addrlen;
char *myhost;
char timeStr[256];
struct sockaddr_storage clientaddr;
time_t now;
char b[256];
char clienthost[NI_MAXHOST];
char clientservice[NI_MAXSERV];
myhost=NULL;
if (argc > 1)
myhost=argv[1];
listenfd= listen_server(myhost, DAYTIME_PORT, AF_UNSPEC, SOCK_DGRAM);
if (listenfd < 0) {
fprintf(stderr,
“listen_server error:: could not create listening ”
“socket\n”);
return -1;
}
addrlen = sizeof(clientaddr);
for ( ; 😉 {
n = recvfrom(listenfd,
b,
sizeof(b),
0,
(struct sockaddr *)&clientaddr,
&addrlen);
if (n < 0)
continue;
memset(clienthost, 0, sizeof(clienthost));
memset(clientservice, 0, sizeof(clientservice));
getnameinfo((struct sockaddr *)&clientaddr, addrlen,
clienthost, sizeof(clienthost),
clientservice, sizeof(clientservice),
NI_NUMERICHOST);
printf(“Received request from host=[%s] port=[%s]\n”,
clienthost, clientservice);
memset(timeStr, 0, sizeof(timeStr));
time(&now);
sprintf(timeStr, “%s”, ctime(&now));
n = sendto(listenfd, timeStr, sizeof(timeStr), 0,
(struct sockaddr *)&clientaddr,
addrlen);
}
return 0;
}
单播模式下的Client方代码:
“connect_client.h”:
#ifndef connect__client__h__
#define connect__client__h__
int
connect_client (const char *hostname,
const char *service,
int family,
int socktype);
#endif
“connect_client.cpp”:
#include
#include
#include
#include
#include “connect_client.h”
int
connect_client (const char *hostname,
const char *service,
int family,
int socktype)
{
struct addrinfo hints, *res, *ressave;
int n, sockfd;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = family;
hints.ai_socktype = socktype;
n = getaddrinfo(hostname, service, &hints, &res);
if (n <0) {
fprintf(stderr,
“getaddrinfo error:: [%s]\n”,
gai_strerror(n));
return -1;
}
ressave = res;
sockfd=-1;
while (res) {
sockfd = socket(res->ai_family,
res->ai_socktype,
res->ai_protocol);
if (!(sockfd < 0)) {
if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
break;
close(sockfd);
sockfd=-1;
}
res=res->ai_next;
}
freeaddrinfo(ressave);
return sockfd;
}
TCP模式 “tcp_daytime_client.cpp”:
#include
#include
#include
#include
#include
#include
#include “connect_client.h”
const char *DAYTIME_PORT=”13″;
int
main(int argc, char *argv[])
{
int connfd;
char *myhost;
char timeStr[256];
myhost = “localhost”;
if (argc > 1)
myhost = argv[1];
connfd= connect_client(myhost, DAYTIME_PORT, AF_UNSPEC, SOCK_STREAM);
if (connfd < 0) {
fprintf(stderr,
“client error:: could not create connected socket ”
“socket\n”);
return -1;
}
memset(timeStr, 0, sizeof(timeStr));
while (read(connfd, timeStr, sizeof(timeStr)) > 0)
printf(“%s”, timeStr);
close(connfd);
return 0;
}
UDP模式 “udp_daytime_client.cpp”:
#include
#include
#include
#include
#include
#include
#include “connect_client.h”
const char *DAYTIME_PORT=”13″;
int
main(int argc, char *argv[])
{
int connfd, n, m;
char *myhost;
char timeStr[256];
char letter;
myhost = “localhost”;
if (argc > 1)
myhost=argv[1];
connfd = connect_client(myhost, DAYTIME_PORT, AF_UNSPEC, SOCK_DGRAM);
if (connfd < 0) {
fprintf(stderr,
“client error:: could not create connected socket ”
“socket\n”);
return -1;
}
letter = ‘1’;
m= write(connfd, &letter, sizeof(letter));
memset(timeStr, 0, sizeof(timeStr));
n = read(connfd,
timeStr,
sizeof(timeStr));
printf(“%s\n”, timeStr);
close(connfd);
return 0;
}
多播模式下的代码:
服务端和客户端共用 “mcastutil.cpp”:
#include
#include
#include
#include
#include
#include
#include
#include
#include “mcastutil.h”
int
get_addr (const char *hostname,
const char *service,
int family,
int socktype,
struct sockaddr_storage *addr)
{
struct addrinfo hints, *res, *ressave;
int n, sockfd, retval;
retval = -1;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = family;
hints.ai_socktype = socktype;
n = getaddrinfo(hostname, service, &hints, &res);
if (n <0) {
fprintf(stderr,
“getaddrinfo error:: [%s]\n”,
gai_strerror(n));
return retval;
}
ressave = res;
sockfd=-1;
while (res) {
sockfd = socket(res->ai_family,
res->ai_socktype,
res->ai_protocol);
if (!(sockfd < 0)) {
if (bind(sockfd, res->ai_addr, res->ai_addrlen) == 0) {
close(sockfd);
memcpy(addr, res->ai_addr, sizeof(*addr);
retval=0;
break;
}
close(sockfd);
sockfd=-1;
}
res=res->ai_next;
}
freeaddrinfo(ressave);
return retval;
}
int
joinGroup(int sockfd, int loopBack, int mcastTTL,
struct sockaddr_storage *addr)
{
int r1, r2, r3, retval;
retval=-1;
switch (addr->ss_family) {
case AF_INET: {
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr=
((struct sockaddr_in *)addr)->sin_addr.s_addr;
mreq.imr_interface.s_addr= INADDR_ANY;
r1= setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_LOOP,
&loopBack, sizeof(loopBack));
if (r1<0)
perror(“joinGroup:: IP_MULTICAST_LOOP:: “);
r2= setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_TTL,
&mcastTTL, sizeof(mcastTTL));
if (r2<0)
perror(“joinGroup:: IP_MULTICAST_TTL:: “);
r3= setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
(const void *)&mreq, sizeof(mreq));
if (r3<0)
perror(“joinGroup:: IP_ADD_MEMBERSHIP:: “);
} break;
case AF_INET6: {
struct ipv6_mreq mreq6;
memcpy(&mreq6.ipv6mr_multiaddr,
&(((struct sockaddr_in6 *)addr)->sin6_addr),
sizeof(struct in6_addr));
mreq6.ipv6mr_interface= 0; // cualquier interfaz
r1= setsockopt(sockfd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,
&loopBack, sizeof(loopBack));
if (r1<0)
perror(“joinGroup:: IPV6_MULTICAST_LOOP:: “);
r2= setsockopt(sockfd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
&mcastTTL, sizeof(mcastTTL));
if (r2<0)
perror(“joinGroup:: IPV6_MULTICAST_HOPS:: “);
r3= setsockopt(sockfd, IPPROTO_IPV6,
IPV6_ADD_MEMBERSHIP, &mreq6, sizeof(mreq6));
if (r3<0)
perror(“joinGroup:: IPV6_ADD_MEMBERSHIP:: “);
} break;
default:
r1=r2=r3=-1;
}
if ((r1>=0) && (r2>=0) && (r3>=0))
retval=0;
return retval;
}
int
isMulticast(struct sockaddr_storage *addr)
{
int retVal;
retVal=-1;
switch (addr->ss_family) {
case AF_INET: {
struct sockaddr_in *addr4=(struct sockaddr_in *)addr;
retVal = IN_MULTICAST(ntohl(addr4->sin_addr.s_addr));
} break;
case AF_INET6: {
struct sockaddr_in6 *addr6=(struct sockaddr_in6 *)addr;
retVal = IN6_IS_ADDR_MULTICAST(&addr6->sin6_addr);
} break;
default:
;
}
return retVal;
}
服务端 “mcastserver.cpp”:
#include
#include
#include
#include
#include
#include
#include
#include
#include “mcastutil.h”
const char *DAYTIME_PORT=”13″;
int
main(int argc, char *argv[])
{
int sockfd, n;
char *mcastaddr;
char timeStr[256];
char b[256];
struct sockaddr_storage clientaddr, addr;
socklen_t addrlen;
time_t now;
char clienthost[NI_MAXHOST];
char clientservice[NI_MAXSERV];
mcastaddr = “FF01::1111”;
if (argc ==2)
mcastaddr=argv[1];
memset(&addr, 0, sizeof(addr));
if (get_addr(mcastaddr, DAYTIME_PORT, PF_UNSPEC,
SOCK_DGRAM, &addr) <0)
{
fprintf(stderr, “get_addr error:: could not find multicast ”
“address=[%s] port=[%s]\n”, mcastaddr, DAYTIME_PORT);
return -1;
}
if (isMulticast(&addr)<0) {
fprintf(stderr,
“This address does not seem a multicast address [%s]\n”,
mcastaddr);
return -1;
}
sockfd = socket(addr.ss_family, SOCK_DGRAM, 0);
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror(“bind error:: “);
close(sockfd);
return -1;
}
if (joinGroup(sockfd, 0 , 8, &addr) <0) {
close(sockfd);
return -1;
}
addrlen=sizeof(clientaddr);
for ( ; 😉 {
n = recvfrom(sockfd,
b,
sizeof(b),
0,
(struct sockaddr *)&clientaddr,
&addrlen);
if (n <0)
continue;
memset(clienthost, 0, sizeof(clienthost));
memset(clientservice, 0, sizeof(clientservice));
getnameinfo((struct sockaddr *)&clientaddr, addrlen,
clienthost, sizeof(clienthost),
clientservice, sizeof(clientservice),
NI_NUMERICHOST);
printf(“Received request from host=[%s] port=[%s]\n”,
clienthost, clientservice);
memset(timeStr, 0, sizeof(timeStr));
time(&now);
sprintf(timeStr, “%s”, ctime(&now));
n = sendto(sockfd, timeStr, sizeof(timeStr), 0,
(struct sockaddr *)&addr,
sizeof(addr));
if (n<1)
perror(“sendto error:: \n”);
}
return 0;
}
客户端 “mcastclient.cpp”:
#include
#include
#include
#include
#include
#include
#include
#include “mcastutil.h”
const char *DAYTIME_PORT=”13″;
int
main(int argc, char *argv[])
{
int sockfd, n;
char *myhost;
char timeStr[256];
char letter;
struct sockaddr_storage addr, clientaddr;
int addrlen;
socklen_t clientaddrlen;
myhost = “FF01::1111”;
if (argc == 2)
myhost=argv[1];
addrlen=sizeof(addr);
memset(&addr, 0, addrlen);
get_addr(myhost, DAYTIME_PORT, PF_UNSPEC, SOCK_DGRAM, &addr);
sockfd = socket(addr.ss_family, SOCK_DGRAM, 0);
if (bind(sockfd, (struct sockaddr *)&addr, addrlen) <0) {
perror(“bind error:: \n”);
close(sockfd);
return -1;
}
if (joinGroup(sockfd, 0 , 8, &addr) <0) {
close(sockfd);
return -1;
}
letter = ‘1’;
n = sendto(sockfd, &letter, sizeof(letter), 0,
(struct sockaddr *)&addr,
addrlen);
if (n<0) {
perror(“sendto error:: “);
close(sockfd);
return -1;
}
memset(timeStr, 0, sizeof(timeStr));
clientaddrlen=sizeof(clientaddr);
n = recvfrom(sockfd,
timeStr,
sizeof(timeStr),
0,
(struct sockaddr *)&clientaddr,
&clientaddrlen);
if (n<0) {
perror(“sendto error:: “);
close(sockfd);
return -1;
}
printf(“%s\n”, timeStr);
close(sockfd);
return 0;
}
不使用主机名或DNS解析时怎么操作
上面例子中都是用的主机名或者dns解析来得到具体地址的,从hosts文件或者dns解析通过gethostbyname能得到具体的IPV6或IPV4地址,如下图示:
内核支持双栈协议:
如果要直接使用IPV6地址或IPV4地址,如果内核支持双栈协议(见下图),则只需要实现成ipv6地址操作即可,底层内核会自动进行ipv6和ipv4地址的转换:
客户端代码IPv6代码(如果使用ipv4方式,则使用代码中的注释IPv4的代码,同时注释掉IPv6的代码;另一种方式还是用ipv6代码,但是服务器端的地址使用::ffff:a.b.c.d方式去进行连接):
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXBUF 1024
int main(int argc, char **argv)
{
int sockfd, len;
/* struct sockaddr_in dest; */// IPv4
struct sockaddr_in6 dest;// IPv6
char buffer[MAXBUF + 1];
if (argc != 3) {
printf
(“参数格式错误!正确用法如下:/n/t/t%s IP地址 端口/n/t比如:/t%s 127.0.0.1 80/n此程序用来从某个 IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息”,
argv[0], argv[0]);
exit(0);
}
/* 创建一个 socket 用于 tcp 通信 */
/* if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { */ // IPv4
if ((sockfd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) { // IPv6
perror(“Socket”);
exit(errno);
}
printf(“socket created/n”);
/* 初始化服务器端(对方)的地址和端口信息 */
bzero(&dest, sizeof(dest));
/* dest.sin_family = AF_INET; */ // IPv4
dest.sin6_family = AF_INET6; // IPv6
/* dest.sin_port = htons(atoi(argv[2])); */ // IPv4
dest.sin6_port = htons(atoi(argv[2])); // IPv6
/* if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) { */ // IPv4 ,argv[1]: 211.43.56.5
if ( inet_pton(AF_INET6, argv[1], &dest.sin6_addr) < 0 ) { // IPv6 ,argv[1]: 2001:f653:98d3::1
perror(argv[1]);
exit(errno);
}
printf(“address created/n”);
/* 连接服务器 */
if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
perror(“Connect “);
exit(errno);
}
printf(“server connected/n”);
/* 接收对方发过来的消息,最多接收 MAXBUF 个字节 */
bzero(buffer, MAXBUF + 1);
/* 接收服务器来的消息 */
len = recv(sockfd, buffer, MAXBUF, 0);
if (len > 0)
printf(“接收消息成功:’%s’,共%d个字节的数据/n”,
buffer, len);
else
printf
(“消息接收失败!错误代码是%d,错误信息是’%s’/n”,
errno, strerror(errno));
bzero(buffer, MAXBUF + 1);
strcpy(buffer, “这是客户端发给服务器端的消息/n”);
/* 发消息给服务器 */
len = send(sockfd, buffer, strlen(buffer), 0);
if (len < 0)
printf
(“消息’%s’发送失败!错误代码是%d,错误信息是’%s’/n”,
buffer, errno, strerror(errno));
else
printf(“消息’%s’发送成功,共发送了%d个字节!/n”,
buffer, len);
/* 关闭连接 */
close(sockfd);
return 0;
}
服务器端代码(同时支持IPv4和IPv6的客户端来连接):
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXBUF 1024
int main(int argc, char **argv)
{
int sockfd, new_fd;
socklen_t len;
/* struct sockaddr_in my_addr, their_addr; */ // IPv4
struct sockaddr_in6 my_addr, their_addr; // IPv6
unsigned int myport, lisnum;
char buf[MAXBUF + 1];
if (argv[1])
myport = atoi(argv[1]);
else
myport = 7838;
if (argv[2])
lisnum = atoi(argv[2]);
else
lisnum = 2;
/* if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) { */ // IPv4
if ((sockfd = socket(PF_INET6, SOCK_STREAM, 0)) == -1) { // IPv6
perror(“socket”);
exit(1);
} else
printf(“socket created/n”);
bzero(&my_addr, sizeof(my_addr));
/* my_addr.sin_family = PF_INET; */ // IPv4
my_addr.sin6_family = PF_INET6; // IPv6
/* my_addr.sin_port = htons(myport); */ // IPv4
my_addr.sin6_port = htons(myport); // IPv6
if (argv[3])
/* my_addr.sin_addr.s_addr = inet_addr(argv[3]); */ // IPv4
inet_pton(AF_INET6, argv[3], &my_addr.sin6_addr); // IPv6
else
/* my_addr.sin_addr.s_addr = INADDR_ANY; */ // IPv4 ,use 0.0.0.0
my_addr.sin6_addr = in6addr_any; // IPv6 ,use ::
/* if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) */ // IPv4
if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr_in6)) // IPv6
== -1) {
perror(“bind”);
exit(1);
} else
printf(“binded/n”);
if (listen(sockfd, lisnum) == -1) {
perror(“listen”);
exit(1);
} else
printf(“begin listen/n”);
while (1) {
len = sizeof(struct sockaddr);
if ((new_fd =
accept(sockfd, (struct sockaddr *) &their_addr,
&len)) == -1) {
perror(“accept”);
exit(errno);
} else
printf(“server: got connection from %s, port %d, socket %d/n”,
/* inet_ntoa(their_addr.sin_addr), */ // IPv4
inet_ntop(AF_INET6, &their_addr.sin6_addr, buf, sizeof(buf)), // IPv6
/* ntohs(their_addr.sin_port), new_fd); */ // IPv4
their_addr.sin6_port, new_fd); // IPv6
/* 开始处理每个新连接上的数据收发 */
bzero(buf, MAXBUF + 1);
strcpy(buf,
“这是在连接建立成功后向客户端发送的第一个消息/n只能向new_fd这个用accept函数新建立的socket发消息,不能向sockfd这个监听socket发送消息,监听socket不能用来接收或发送消息/n”);
/* 发消息给客户端 */
len = send(new_fd, buf, strlen(buf), 0);
if (len < 0) {
printf
(“消息’%s’发送失败!错误代码是%d,错误信息是’%s’/n”,
buf, errno, strerror(errno));
} else
printf(“消息’%s’发送成功,共发送了%d个字节!/n”,
buf, len);
bzero(buf, MAXBUF + 1);
/* 接收客户端的消息 */
len = recv(new_fd, buf, MAXBUF, 0);
if (len > 0)
printf(“接收消息成功:’%s’,共%d个字节的数据/n”,
buf, len);
else
printf
(“消息接收失败!错误代码是%d,错误信息是’%s’/n”,
errno, strerror(errno));
/* 处理每个新连接上的数据收发结束 */
}
close(sockfd);
return 0;
}
内核只支持隔离栈(独立栈)协议:
则无论是客户端还是服务器端,都必须把ipv4和ipv6分开处理,客户端不用说,使用哪类地址,就使用哪类代码(参考上面双栈协议实现中的注释来支持ipv4),服务器端通过建立两个套接口,然后select检测进来连接的客户端是ipv4还是ipv6来同时支持ipv4或ipv6的连接:
SOCKET ServSock[FD_SETSIZE];
ADDRINFO AI0, AI1;
ServSock[0] = socket(AF_INET6, SOCK_STREAM, PF_INET6);
ServSock[1] = socket(AF_INET, SOCK_STREAM, PF_INET);
…
bind(ServSock[0], AI0->ai_addr, AI0->ai_addrlen);
bind(ServSock[1], AI1->ai_addr, AI1->ai_addrlen);
…
select(2, &SockSet, 0, 0, 0);
if (FD_ISSET(ServSocket[0], &SockSet)) {
// IPv6 connection csock = accept(ServSocket[0], (LPSOCKADDR)&From, FromLen);
…
}
if (FD_ISSET(ServSocket[1], &SockSet))
// IPv4 connection csock = accept(ServSocket[1], (LPSOCKADDR)&From, FromLen);
…
}