STM32开发笔记70: 传递参数时,为何要对套接字地址进行强制
2019-07-15 来源:eefocus
在进行IPV6的UDP设计时,偶然发现一个问题,就是大部分套接字函数都需对地址进行强制转换,先看一下程序:
这是bind函数:
bind(sockIPV6, (struct sockaddr*)&sockAddr, sizeof(sockAddr))
这是recvfrom函数:
recvfrom(sockIPV6, UdpBuffer, 100, 0, (struct sockaddr*)&sockAddr, &slen)
这是sendto函数:
sendto(sockIPV6, UdpBuffer, len, 0, (const struct sockaddr*)&sockAddr, sizeof(sockAddr))
无一例外,这些函数在处理sockAddr之前都进行了强制数据类型转换,将其转换为sockaddr。
这个问题,在进行IPV4设计时,稀里糊涂的就过去了,没有深究过,今天在写IPV6时,疑惑就比较大了。
在IPV6中,sockAddr定义的数据类型是sockaddr_in6,在IPV4中定义的数据类型是sockaddr_in,为何能转换成同一数据类型呢?仔细分析后,发现里面有很大的玄机。
sockaddr_in的定义:
struct sockaddr_in {
u8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
#define SIN_ZERO_LEN 8
char sin_zero[SIN_ZERO_LEN];
};
sin_len:1字节,指明结构体有用数据的长度
sin_family:1字节,表示结构体的Family类型,指明是IPV4,还是IPV6
sin_port:2字节,端口号
sin_addr:4字节,IP地址
sin_zero:8字节,占位用
合计:16字节
sockaddr_in6的定义:
struct sockaddr_in6 {
u8_t sin6_len; /* length of this structure */
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port; /* Transport layer port # */
u32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
u32_t sin6_scope_id; /* Set of interfaces for scope */
}
sin6_len:1字节,指明结构体有用数据的长度
sin6_family:1字节,表示结构体的Family类型,指明是IPV4,还是IPV6
sin6_port:2字节,端口号
sin6_flowinfo:4字节,包含IPV6报头中的通信流类别字段和流标签字段
sin6_addr:16字节,IPV6地址
sin6_scope_id:4字节,包含了范围ID,它用于标识一系列的接口,这些接口与地址字段中的地址相对应
合计:28字节
sockaddr的定义:
struct sockaddr {
u8_t sa_len;
sa_family_t sa_family;
char sa_data[14];
};
sin_len:1字节,指明结构体有用数据的长度
sin_family:1字节,表示结构体的Family类型,指明是IPV4,还是IPV6
sa_data:14字节,占位用
合计:16字节
将sockaddr_in和sockaddr_in6转换为sockaddr是为保证代码的统一性,这样做后,socket中函数就可以采用统一的格式进行调用。
LwIP在进行初始设计时,本身不支持IPV6,所以将sockaddr_in和sockaddr定义为相同的长度。
在windows操作系统中,这3个结构体定义的长度是一致的的,都是28字节。
如果,这样问题又来了,在LwIP中sockaddr_in6和sockaddr长度不一致,是如何完成转换的呢?
我们那一个socket函数进行分析就好,例如我们选择bind函数,其内部定义如下:
int
lwip_bind(int s, const struct sockaddr *name, socklen_t namelen)
{
struct lwip_sock *sock;
ip_addr_t local_addr;
u16_t local_port;
err_t err;
sock = get_socket(s);
if (!sock) {
return -1;
}
if (!SOCK_ADDR_TYPE_MATCH(name, sock)) {
/* sockaddr does not match socket type (IPv4/IPv6) */
sock_set_errno(sock, err_to_errno(ERR_VAL));
return -1;
}
/* check size, family and alignment of 'name' */
LWIP_ERROR('lwip_bind: invalid address', (IS_SOCK_ADDR_LEN_VALID(namelen) &&
IS_SOCK_ADDR_TYPE_VALID(name) && IS_SOCK_ADDR_ALIGNED(name)),
sock_set_errno(sock, err_to_errno(ERR_ARG)); return -1;);
LWIP_UNUSED_ARG(namelen);
SOCKADDR_TO_IPADDR_PORT(name, &local_addr, local_port);
LWIP_DEBUGF(SOCKETS_DEBUG, ('lwip_bind(%d, addr=', s));
ip_addr_debug_print_val(SOCKETS_DEBUG, local_addr);
LWIP_DEBUGF(SOCKETS_DEBUG, (' port=%'U16_F')n', local_port));
#if LWIP_IPV4 && LWIP_IPV6
/* Dual-stack: Unmap IPv4 mapped IPv6 addresses */
if (IP_IS_V6_VAL(local_addr) && ip6_addr_isipv4mappedipv6(ip_2_ip6(&local_addr))) {
unmap_ipv4_mapped_ipv6(ip_2_ip4(&local_addr), ip_2_ip6(&local_addr));
IP_SET_TYPE_VAL(local_addr, IPADDR_TYPE_V4);
}
#endif /* LWIP_IPV4 && LWIP_IPV6 */
err = netconn_bind(sock->conn, &local_addr, local_port);
if (err != ERR_OK) {
LWIP_DEBUGF(SOCKETS_DEBUG, ('lwip_bind(%d) failed, err=%dn', s, err));
sock_set_errno(sock, err_to_errno(err));
return -1;
}
LWIP_DEBUGF(SOCKETS_DEBUG, ('lwip_bind(%d) succeededn', s));
sock_set_errno(sock, 0);
return 0;
}
可以看到,对于sockaddr其使用指针进行传递的。有关name的处理有很多函数,我们通过字面上理解,可定位于下面的宏。
SOCKADDR_TO_IPADDR_PORT(name, &local_addr, local_port);
继续找到其具体定义:
#define SOCKADDR_TO_IPADDR_PORT(sockaddr, ipaddr, port) sockaddr_to_ipaddr_port(sockaddr, ipaddr, &(port))
再找到sockaddr_to_ipaddr_port的具体定义:
static void
sockaddr_to_ipaddr_port(const struct sockaddr* sockaddr, ip_addr_t* ipaddr, u16_t* port)
{
if ((sockaddr->sa_family) == AF_INET6) {
SOCKADDR6_TO_IP6ADDR_PORT((const struct sockaddr_in6*)(const void*)(sockaddr), ipaddr, *port);
ipaddr->type = IPADDR_TYPE_V6;
} else {
SOCKADDR4_TO_IP4ADDR_PORT((const struct sockaddr_in*)(const void*)(sockaddr), ipaddr, *port);
ipaddr->type = IPADDR_TYPE_V4;
}
}
到这里,我们就看的很清楚了,通过sockaddr_to_ipaddr_port函数,再强制转换为相应的结构体。