单片机
返回首页

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


进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • 简洁的过零调功器电路设计与分析

  • 单稳态控制电路设计与分析

  • 光控电路设计与分析

  • IGBT模块通过控制门极阻断过电流

  • 运算放大器IC741的基本工作原理及在电路中的实现方式

  • 基于TDA1554的立体声放大器电路

    相关电子头条文章