我正在尝试创建一个创建sockaddr结构的void mksockaddr(int af, int proto, char addr[], struct sockaddr* dst)
,这就是我所做的:
void sockaddr(int af, int port, char addr[], struct sockaddr* dst) {
if (af == AF_INET) {
struct sockaddr_in s;
s.sin_family = af;
s.sin_port = htons(port);
inet_pton(af, addr, &s.sin_addr);
memcpy(dst, &s, sizeof(s));
} else {
struct sockaddr_in6 s;
s.sin6_family = af;
s.sin6_port = htons(port);
s.sin6_flowinfo = 0;
inet_pton(af, addr, &s.sin6_addr);
memcpy(dst, &s, sizeof(s));
}
}
AF_INET(IPv4)似乎没有问题,我可以bind()
没有任何问题,但是当我尝试使用AF_INET6时,bind()
会给我无效的参数。
以下是我用于bind()
的代码:
int sock_fd = socket(AF_INET6, SOCK_RAW, proto);
struct sockaddr sin;
sockaddr(AF_INET6, proto, src, &sin);
if(bind(sock_fd, &sin, sizeof(sin)) < 0) {
fprintf(stderr, "[ERR] can't bind socket: %s\n", strerror(errno));
exit(1);
} // got Invalid argument
但是,如果我自己构建一个bind()
,我sockaddr_in6
就可以了。
struct sockaddr_in6 sin;
sin.sin6_port = htons(proto);
sin.sin6_family = AF_INET6;
inet_pton(AF_INET6, src, &sin.sin6_addr);
if(bind(sock_fd, (struct sockaddr*) &sin, sizeof(sin)) < 0) {
fprintf(stderr, "[ERR] can't bind socket.\n");
exit(1);
} // work just fine
所以我将该函数创建的sockaddr
转换回sockaddr_in6
,我可以看到除sin6_scope_id
之外的所有字段都相同。根据我的理解,sin6_scope_id
无关紧要,除非我正在处理链接本地IPv6地址。
我在这里遗漏了什么吗?
答案 0 :(得分:1)
从C角度来看,为了确保您的代码按预期工作,调用者必须将有效指针传递给dst
参数中的正确结构类型。你的例子没有这样做。相反,它声明了struct sockaddr
,并传递指针。类型struct sockaddr
本身从不打算用作实际对象的类型,并且对于所有可能的地址类型来说都不够大。特别是,它对于IPv6地址来说还不够大。
另一方面,POSIX比符合程序的标准C要求更快,更松散。套接字地址尤其明显。它定义了一个类型struct sockaddr_storage
以完全满足您的目的:它足够大并且具有适当的对齐方式来保存任何支持的套接字地址类型的数据。文档特别提到了它在泛然支持IPv4和IPv6方面的用途。 POSIX还会在不同的套接字地址指针类型中强制转换,但这会导致违反C的结构别名规则。
因此,我会重写您的函数以明确使用struct sockaddr_storage
,并且我还会通过适当的强制转换来简化我的代码。此外,我会让我的函数告诉我地址结构的可用大小,它只包含初始化的部分:
void populate_sockaddr(int af, int port, char addr[],
struct sockaddr_storage *dst, socklent_t *addrlen) {
if (af == AF_INET) {
struct sockaddr_in *dst_in4 = (struct sockaddr_in *) dst;
*addrlen = sizeof(*dst_in4);
memset(dst_in4, 0, *addrlen);
dst_in4->sin_family = af;
dst_in4->sin_port = htons(port);
inet_pton(af, addr, &dst_in4->sin_addr);
} else if (af == AF_INET6) {
struct sockaddr_in6 *dst_in6 = (struct sockaddr_in6 *) dst;
*addrlen = sizeof(*dst_in6);
memset(dst_in6, 0, *addrlen);
dst_in6->sin6_family = af;
dst_in6->sin6_port = htons(port);
// unnecessary because of the memset(): dst_in6->sin6_flowinfo = 0;
inet_pton(af, addr, &dst_in6->sin6_addr);
} // else ...
}
然后你会这样使用它:
struct sockaddr_strorage addr;
socklen_t addrlen;
populate_sockaddr(af, port, src, &addr, &addrlen);
if (bind(sock_fd, (struct sockaddr *) &addr, addrlen) < 0) {
// ...
}
请注意,&addr
对struct sockaddr *
类型的强制转换是完全常规的。