sockaddr、sockaddr_in、sockaddr_in6的区别及转换

本文转自:https://blog.csdn.net/albertsh/article/details/80991684

前言
最近在学习网络相关的知识,虽然之前代码写了不少,但是长时间不写难免会忘记,简单地复习了一下IO多路复用的方式,对比了解了一下epoll模式和select模式的异同,不过写代码的时候发现,这个socket连接中有几个结构还是挺让人头大的,用着用着突然就强转成其他的类型了,加上年前改了半天IPv6的连接,这几个结构体更加混乱,所以今天角色放到一起,从源码的角度看一下sockaddr、sockaddr_in、sockaddr_in6这三个结构体之间的联系,以及为什么有些情况可以直接强转。

代码分析
看一下这三个结构的定义,先说明一下版本,操作系统为CentOS,头文件版本应该挺古老了,在’/usr/include/netinet/in.h’ 中发现版权信息:Copyright (C) 1991, 1992, 1994-2001, 2004, 2006, 2007, 2008, 2009, 2010,看着很古老,但之后的版本应该没有改动很大吧,反正不太清楚,我们就分析当前这一个版本吧。

/* /usr/include/bits/socket.h */
/* Structure describing a generic socket address.  */
struct sockaddr
{
 __SOCKADDR_COMMON (sa_);    /* Common data: address family and length.  */
 char sa_data[14];           /* Address data.  */
};

/* /usr/include/netinet/in.h */
/* Structure describing an Internet socket address.  */
struct sockaddr_in
{
 __SOCKADDR_COMMON (sin_);
 in_port_t sin_port;         /* Port number.  */
 struct in_addr sin_addr;    /* Internet address.  */

 /* Pad to size of `struct sockaddr'.  */
 unsigned char sin_zero[sizeof (struct sockaddr) -
            __SOCKADDR_COMMON_SIZE -
            sizeof (in_port_t) -
            sizeof (struct in_addr)];
};

/* /usr/include/netinet/in.h */

#ifndef __USE_KERNEL_IPV6_DEFS

/* Ditto, for IPv6.  */
struct sockaddr_in6
{
 __SOCKADDR_COMMON (sin6_);
 in_port_t sin6_port;        /* Transport layer port # */
 uint32_t sin6_flowinfo;     /* IPv6 flow information */
 struct in6_addr sin6_addr;  /* IPv6 address */
 uint32_t sin6_scope_id;     /* IPv6 scope-id */
};

#endif /* !__USE_KERNEL_IPV6_DEFS */


看到3个结构的定义想到了什么?只是看着有点像吧,真正的区别我们往下看,其中3个结构里都包含了 __SOCKADDR_COMMON 这个宏,我们先把它的定义找到,最后在’usr/inlcue/bits/sockaddr.h’中找到如下代码,

/* POSIX.1g specifies this type name for the `sa_family' member.  */
typedef unsigned short int sa_family_t;

/* This macro is used to declare the initial common members
of the data types used for socket addresses, `struct sockaddr',
`struct sockaddr_in', `struct sockaddr_un', etc.  */


#define __SOCKADDR_COMMON(sa_prefix) \

sa_family_t sa_prefix##family


#define __SOCKADDR_COMMON_SIZE  (sizeof (unsigned short int))


由此我们知道,这三个结构的第一个字段都是一个unsigned short int 类型,只不过用宏来定义了三个不同的名字,至此第一个结构就清楚了,在一般环境下(short一般为2个字节),整个结构占用16个字节,变量sa_family占用2个字节,变量sa_data 保留14个字节用于保存IP地址信息。

接着我们发现第二个结构中还有in_port_t和struct in_addr两个类型没有定义,继续找下去吧,在文件
‘/usr/include/netinet/in.h’发现以下定义

/* Type to represent a port.  */
typedef uint16_t in_port_t;

/* Internet address.  */
typedef uint32_t in_addr_t;
struct in_addr
{
 in_addr_t s_addr;
};


这么看来sockaddr_in这个结构也不复杂,除了一开始的2个字节表示sin_family,然后是2个字节的变量sin_port表示端口,接着是4个字节的变量sin_addr表示IP地址,最后是8个字节变量sin_zero填充尾部,用来与结构sockaddr对齐

现在我们该分析结构sockaddr_in6了,这里边只有一个未知的结构in6_addr,经过寻找发现其定义也在’/usr/include/netinet/in.h’中

#ifndef __USE_KERNEL_IPV6_DEFS

/* IPv6 address */
struct in6_addr
{
 union
 {
     uint8_t __u6_addr8[16];

#if defined __USE_MISC || defined __USE_GNU

     uint16_t __u6_addr16[8];
     uint32_t __u6_addr32[4];

#endif

 } __in6_u;

#define s6_addr         __in6_u.__u6_addr8


#if defined __USE_MISC || defined __USE_GNU


# define s6_addr16      __in6_u.__u6_addr16


# define s6_addr32      __in6_u.__u6_addr32


#endif

};

#endif /* !__USE_KERNEL_IPV6_DEFS */



这个结构看起来有点乱,但是如果抛开其中的预编译选项,其实就是8个字节,用来表示IPV6版本的IP地址,一共128位,只不过划分字节的段数有些不同,每段字节多一点那么段数就少一点,反义亦然。

那接下来我们整理一下,为了看的清楚,部分结构使用伪代码,不能通过编译,主要是方便对比,整理如下

/* Structure describing a generic socket address.  */
struct sockaddr
{
 uint16 sa_family;           /* Common data: address family and length.  */
 char sa_data[14];           /* Address data.  */
};

/* Structure describing an Internet socket address.  */
struct sockaddr_in
{
 uint16 sin_family;          /* Address family AF_INET */ 
 uint16 sin_port;            /* Port number.  */
 uint32 sin_addr.s_addr;     /* Internet address.  */
 unsigned char sin_zero[8];  /* Pad to size of `struct sockaddr'.  */
};

/* Ditto, for IPv6.  */
struct sockaddr_in6
{
 uint16 sin6_family;         /* Address family AF_INET6 */
 uint16 sin6_port;           /* Transport layer port # */
 uint32 sin6_flowinfo;       /* IPv6 flow information */
 uint8  sin6_addr[16];       /* IPv6 address */
 uint32 sin6_scope_id;       /* IPv6 scope-id */
};


这么来看是不是就清晰多了,由此我们发现结构 sockaddr 和 sockaddr_in 字节数完全相同,都是16个字节,所以可以直接强转,但是结构 sockaddr_in6 有28个字节,为什么在使用的时候也是直接将地址强制转化成(sockaddr*)类型呢?

强转的可能性
其实sockaddr 和 sockaddr_in 之间的转化很容易理解,因为他们开头一样,内存大小也一样,但是sockaddr和sockaddr_in6之间的转换就有点让人搞不懂了,其实你有可能被结构所占的内存迷惑了,这几个结构在作为参数时基本上都是以指针的形式传入的,我们拿函数bind()为例,这个函数一共接收三个参数,第一个为监听的文件描述符,第二个参数是sockaddr*类型,第三个参数是传入指针原结构的内存大小,所以有了后两个信息,无所谓原结构怎么变化,因为他们的头都是一样的,也就是uint16 sa_family,那么我们也能根据这个头做处理,原本我没有看过bind()函数的源代码,但是可以猜一下:

int bind(int socket_fd, sockaddr* p_addr, int add_size)
{
    if (p_addr->sa_family == AF_INET)
    {
        sockaddr_in* p_addr_in = (sockaddr_in*)p_addr;
        //...
    }
    else if (p_addr->sa_family == AF_INET6)
    {
        sockaddr_in6* p_addr_in = (sockaddr_in6*)p_addr;
        //...
    }
    else
    {
        //...
    }
}


由以上代码完全可以实现IPv4和IPv6的版本区分,所以不需要纠结内存大小的不同

总结
通过等价替换的方式我们可以更好的了解sockaddr、sockaddr_in、sockaddr_in6之间的异同。
网路接口函数针对于IPv4和IPv6虽然有不同的结构,但是接口基本相同,主要是为了用户(开发者)使用方便吧。
有时间可以看一下bind()、accept()等函数,看看其中对于结构的使用到底是怎样的。
 

<think>首先,用户的问题是:“我想将sockaddr结构体转换sockaddr_in结构体的方法 请问如何在C/C++中将struct sockaddr转换为struct sockaddr_in”。 根据系统指令,我需要: - 所有行内数学表达式使用$...$格式(但这里没有数学表达式)。 - 独立公式使用$$...$$格式(可能不适用)。 - LaTeX语法正确(同样,可能不适用)。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如[^1]。 用户可见层指令:回答结构清晰,帮助用户逐步解决问题,尽量保证回答真实可靠。 参考站内引用提供了相关信息。我需要基于这些引用来回答问题。 关键引用: - 引用[1]:介绍了使用union来避免强转的技巧。 - 引用[2]:解释了struct sockaddrstruct sockaddr_in区别,以及如何通过强制类型转换来使用。 - 引用[3]:提到struct sockaddr_in6强转指针。 - 引用[4]:描述了结构体定义。 - 引用[5]:关于addrinfo结构。 用户的问题是将struct sockaddr转换为struct sockaddr_in。这通常在网络编程中处理地址时使用。 在C/C++中,struct sockaddr是一个通用的套接字地址结构,而struct sockaddr_in是用于IPv4的具体结构。转换通常通过指针强制类型转换来实现。 步骤: 1. 理解结构:struct sockaddr是通用的,struct sockaddr_in是IPv4特定的。 2. 转换方法:由于struct sockaddr_in的第一个成员sin_family与struct sockaddr的sa_family相同,因此可以通过指针类型转换来安全地访问。 3. 安全考虑:需要确保实际地址类型是AF_INET(IPv4),否则转换可能无效。 基于引用: - 引用[2]说:一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数。反向转换类似。 - 引用[1]提供了一个union技巧来避免显式强转,但这不是直接转换。 用户问的是将sockaddr转换sockaddr_in,所以是反向转换。 在代码中,当你有struct sockaddr*指针时,可以将其强制转换为struct sockaddr_in*指针来访问IPv4特定字段。 例如: ```c struct sockaddr *sa = ...; // 指向sockaddr的指针 if (sa->sa_family == AF_INET) { // 确保是IPv4 struct sockaddr_in *sin = (struct sockaddr_in *)sa; // 强制类型转换 // 现在可以访问sin->sin_port, sin->sin_addr等 } ``` 引用[1]的union方法可以用于避免显式强转,但本质上还是指针转换。 回答结构: 1. 解释背景:为什么需要转换。 2. 描述方法:使用指针强制类型转换。 3. 提供代码示例。 4. 注意事项:检查地址族以确保安全。 5. 生成相关问题。 最后,添加引用标识。引用来自提供的参考,所以我需要自然地引用它们。 例如,在解释时提到:"如引用[2]所述,..." 相关问题:基于主题生成几个问题。</think>### 如何在C/C++中将struct sockaddr转换为struct sockaddr_in 在网络编程中,`struct sockaddr`是一个通用的套接字地址结构,用于表示各种协议(如IPv4、IPv6)的地址信息。而`struct sockaddr_in`是专门用于IPv4地址的具体结构体。转换通常发生在需要访问IPv4特定字段(如端口号或IP地址)的场景中。以下是详细的转换方法,基于C/C++标准实现,确保安全可靠。 #### 转换原理 - `struct sockaddr`是通用结构体,定义在`<sys/socket.h>`中。它包含一个地址族字段(`sa_family`)一个通用数据数组。 - `struct sockaddr_in`定义在`<netinet/in.h>`中,用于IPv4地址。它的第一个成员`sin_family`与`struct sockaddr`的`sa_family`对齐,因此可以通过指针强制类型转换安全访问IPv4字段(如`sin_port``sin_addr`)。 如引用[2]所述,`sockaddr_in`用于存储IPv4地址信息,而`sockaddr`常用于函数参数,转换时需确保地址类型匹配[^2]。 #### 转换步骤 1. **检查地址族**:在转换前,必须验证`sa_family`是否为`AF_INET`(IPv4地址)。如果地址族不匹配,转换将导致未定义行为。 2. **指针强制类型转换**:将`struct sockaddr*`指针直接强制转换为`struct sockaddr_in*`指针。这利用了结构体内存布局的兼容性(第一个字段对齐)。 3. **访问字段**:转换后,即可安全访问`sin_port`、`sin_addr`等IPv4特定字段。 #### 代码示例 以下是一个完整的C/C++示例,演示如何安全转换: ```c #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> int main() { // 示例:创建一个IPv4地址结构 struct sockaddr_in ipv4_addr; ipv4_addr.sin_family = AF_INET; ipv4_addr.sin_port = htons(8080); // 端口号 inet_pton(AF_INET, "192.168.1.1", &ipv4_addr.sin_addr); // IP地址 // 假设有一个指向sockaddr的指针(常见于accept()或recvfrom()等函数) struct sockaddr *generic_addr = (struct sockaddr *)&ipv4_addr; // 模拟通用地址指针 // 步骤1: 检查地址族是否为IPv4 if (generic_addr->sa_family == AF_INET) { // 步骤2: 强制类型转换 struct sockaddr_in *converted_addr = (struct sockaddr_in *)generic_addr; // 步骤3: 访问IPv4字段 char ip_str[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &converted_addr->sin_addr, ip_str, INET_ADDRSTRLEN); printf("端口号: %d\n", ntohs(converted_addr->sin_port)); printf("IP地址: %s\n", ip_str); } else { printf("错误: 地址类型不是IPv4\n"); } return 0; } ``` #### 注意事项 - **安全性**:必须检查`sa_family`。如果地址是IPv6(`AF_INET6`),应使用`struct sockaddr_in6`转换(引用[3])[^3]。 - **避免直接赋值**:不要直接将`struct sockaddr`变量赋值给`struct sockaddr_in`变量,因为内存大小不同(引用[4])。始终使用指针转换[^4]。 - **替代方法**:使用`union`简化转换(如引用[1]的示例),避免显式强转,但本质相同[^1]。 - **跨平台兼容性**:此方法在Linux、Windows(需包含`winsock2.h`)macOS上均有效。 #### 常见错误 - 未检查地址族:可能导致程序崩溃或数据损坏。 - 错误转换IPv6地址:IPv6需使用`struct sockaddr_in6`(引用[5])[^5]。 通过以上方法,您可以安全地在C/C++中实现`sockaddr`到`sockaddr_in`的转换。如果涉及IPv6或其他协议,请参考相关结构体定义。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值