• 首页
  • 中国
  • STM32开发笔记70: 传递参数对套接字地址进行强制类型转换

STM32开发笔记70: 传递参数对套接字地址进行强制类型转换

2023-11-01 128浏览
百检网是一家专业的第三方检测平台,汇聚众多拥有权威资质的第三方检测机构为你提供一站式的检测服务,做检测就上百检网。百检网让检测从此检测,一份报告全国通用,专业值得信赖。

单片机型号:STM32F407VGT6

在进行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函数,再强制转换为相应的结构体。

百检网秉承“客户至上,服务为先,精诚合作,以人为本”的经营理念,始终站在用户的角度解决问题,为客户提供“一站购物式”的新奇检测体验,打开网站,像挑选商品一样简单,方便。打破行业信息壁垒,建构消费和检测机构之间高效的沟通平台