当前位置: 首页 > news >正文

结构体sockaddr、sockaddr_in、sockaddr_in6之间的区别和联系

前言

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

代码分析

  1. 看一下这三个结构的定义,先说明一下版本,操作系统为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 */
    
    
  2. 看到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地址信息。

  3. 接着我们发现第二个结构中还有in_port_tstruct 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对齐

  4. 现在我们该分析结构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位,只不过划分字节的段数有些不同,每段字节多一点那么段数就少一点,反义亦然。

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

    /* 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 */
    };

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

强转的可能性

其实sockaddrsockaddr_in 之间的转化很容易理解,因为他们开头一样,内存大小也一样,但是sockaddrsockaddr_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的版本区分,所以不需要纠结内存大小的不同

总结

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

相关文章:

  • 简述TCP三次握手和四次挥手流程
  • 智能指针(零):分类及简单特性
  • 智能指针(一):auto_ptr浅析
  • 智能指针(二):shared_ptr浅析
  • 智能指针(四):unique_ptr浅析
  • Lua中关于table对象引用传递的注意事项
  • VS2015调试dump文件时提示打不开KERNELBASE.dll
  • Mysql中使用select into语句给变量赋值没有匹配记录时的结果
  • 排序算法系列之(四)——抓扑克牌风格的插入排序
  • linux环境下服务器程序的查看与gdb调试
  • linux环境下运行程序常用的nohup和的区别
  • 排序算法系列之(五)——为目标打好基础的希尔排序
  • linux环境下查找包含指定内容的文件及其所在行数
  • Mysql查询可通过给条件字段添加索引提高查询速度
  • Mysql开启、查看慢查询日志
  • 【腾讯Bugly干货分享】从0到1打造直播 App
  • 5、React组件事件详解
  • codis proxy处理流程
  •  D - 粉碎叛乱F - 其他起义
  • ES6--对象的扩展
  • LeetCode541. Reverse String II -- 按步长反转字符串
  • MySQL QA
  • Python学习笔记 字符串拼接
  • Spark in action on Kubernetes - Playground搭建与架构浅析
  • Stream流与Lambda表达式(三) 静态工厂类Collectors
  • Vue2.0 实现互斥
  • 从@property说起(二)当我们写下@property (nonatomic, weak) id obj时,我们究竟写了什么...
  • 爬虫模拟登陆 SegmentFault
  • 前端之Sass/Scss实战笔记
  • 如何在 Tornado 中实现 Middleware
  • 算法-插入排序
  • 微信端页面使用-webkit-box和绝对定位时,元素上移的问题
  • !! 2.对十份论文和报告中的关于OpenCV和Android NDK开发的总结
  • (13)Latex:基于ΤΕΧ的自动排版系统——写论文必备
  • (ZT) 理解系统底层的概念是多么重要(by趋势科技邹飞)
  • (附源码)计算机毕业设计SSM智慧停车系统
  • (简单有案例)前端实现主题切换、动态换肤的两种简单方式
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理第3章 信息系统治理(一)
  • (三) prometheus + grafana + alertmanager 配置Redis监控
  • (三)centos7案例实战—vmware虚拟机硬盘挂载与卸载
  • (循环依赖问题)学习spring的第九天
  • (已解决)报错:Could not load the Qt platform plugin “xcb“
  • (原創) 系統分析和系統設計有什麼差別? (OO)
  • (转)Oracle 9i 数据库设计指引全集(1)
  • *2 echo、printf、mkdir命令的应用
  • .NET 6 Mysql Canal (CDC 增量同步,捕获变更数据) 案例版
  • .net core 6 集成 elasticsearch 并 使用分词器
  • .net开发时的诡异问题,button的onclick事件无效
  • .NET框架类在ASP.NET中的使用(2) ——QA
  • [ vulhub漏洞复现篇 ] GhostScript 沙箱绕过(任意命令执行)漏洞CVE-2019-6116
  • [\u4e00-\u9fa5] //匹配中文字符
  • [2018][note]用于超快偏振开关和动态光束分裂的all-optical有源THz超表——
  • [ajaxupload] - 上传文件同时附件参数值
  • [CISCN2019 华北赛区 Day1 Web5]CyberPunk --不会编程的崽
  • [COI2007] Sabor