美文网首页Unix网络编程-卷1
Unix Socket 编程中几个地址结构简析

Unix Socket 编程中几个地址结构简析

作者: 一半晴天 | 来源:发表于2018-04-26 23:25 被阅读167次

前言

Unix Socket 编程中与地址有关的结构有这么一些。

  1. struct sockaddr
  2. struct sockaddr_in
  3. struct sockaddr_in6
  4. struct sockaddr_storage

接下来我将对他们进行一些简单的分析和解释。后面将结合实际来展示如何在实际的 Socket 编程中使用他们。

结构

struct sockaddr

其声明在 <sys/socket.h> 头文件中:

typedef __uint8_t       sa_family_t;
/*
 * [XSI] Structure used by kernel to store most addresses.               
 */
struct sockaddr {
    __uint8_t   sa_len;     /* total length */
    sa_family_t sa_family;  /* [XSI] address family */
    char        sa_data[14];    /* [XSI] addr value (actually larger) */
};                                                                                                                                                                                 

struct sockaddr_in

其声明在 <netinet/in.h> 头文件中:

typedef __uint32_t  in_addr_t;  /* base type for internet address */
/*
 * Internet address (a structure for historical reasons)
 */
struct in_addr {
    in_addr_t s_addr;
};
typedef __uint16_t      in_port_t;
/*
 * Socket address, internet style.
 */
struct sockaddr_in {
    __uint8_t   sin_len;
    sa_family_t sin_family;
    in_port_t   sin_port;
    struct  in_addr sin_addr;
    char        sin_zero[8];
};

struct sockaddr_in6

其声明在 <netinet6/in6.h> 头文件中:

/*
 * IPv6 address
 */
struct in6_addr {
    union {
        __uint8_t   __u6_addr8[16];
        __uint16_t  __u6_addr16[8];
        __uint32_t  __u6_addr32[4];
    } __u6_addr;            /* 128-bit IP6 address */
};

struct sockaddr_in6 {
    __uint8_t   sin6_len;   /* length of this struct(sa_family_t) */
    sa_family_t sin6_family;    /* AF_INET6 (sa_family_t) */
    in_port_t   sin6_port;  /* Transport layer port # (in_port_t) */
    __uint32_t  sin6_flowinfo;  /* IP6 flow information */
    struct in6_addr sin6_addr;  /* IP6 address */
    __uint32_t  sin6_scope_id;  /* scope zone index */
};

struct sockaddr_storage

其声明也在 <sys/socket.h> 头文件中:

/*
 * RFC 2553: protocol-independent placeholder for socket addresses
 */
#define _SS_MAXSIZE 128
#define _SS_ALIGNSIZE   (sizeof(__int64_t))
#define _SS_PAD1SIZE    \
        (_SS_ALIGNSIZE - sizeof(__uint8_t) - sizeof(sa_family_t))
#define _SS_PAD2SIZE    \
        (_SS_MAXSIZE - sizeof(__uint8_t) - sizeof(sa_family_t) - \
                _SS_PAD1SIZE - _SS_ALIGNSIZE)

/*
 * [XSI] sockaddr_storage
 */
struct sockaddr_storage {
    __uint8_t   ss_len;     /* address length */
    sa_family_t ss_family;  /* [XSI] address family */
    char            __ss_pad1[_SS_PAD1SIZE];
    __int64_t   __ss_align; /* force structure storage alignment */
    char            __ss_pad2[_SS_PAD2SIZE];
};

接口

对于 TCP 客户端来说,主要使到以下接口 :
socket() -> connect() -> write() -> read() -> close().
对于服务端来说主要用到以下接口:
socket() -> bind() -> listen() -> accept() -> read() -> write() -> close()

下面主要分析其中用到了地址的几个接口函数:

bind

     int
     bind(int socket, const struct sockaddr *address, socklen_t address_len);

connect

    int
    connect(int socket, const struct sockaddr *address,
         socklen_t address_len);

accept

int
     accept(int socket, struct sockaddr *restrict address,
         socklen_t *restrict address_len);

分析

为什么需要提供 address_len 参数?

上面三个用到了 Socket 地址的函数接口中关于地址的参数的签名基本是一致的。
const struct sockaddr *address, socklen_t address_len
使用者需要提供一个 struct sockaddr 结构指针。 还需要提供此结构的长度 address_len。对于刚学习 Socket 编程的人来说,这个参数还是有点让人疑惑的。因为我们从字面上看通过 sizeof( struct sockaddr) 就能得到地址结构的大小(长度)。
而且 struct sockaddr 结构本身还有一个 sa_len 字段呢。

首先来说这里与 Socket API 的发展历史有关。
在 BSD 4.3 时代时 struct sockaddr 的声明大概是这样的。

typedef __uint16_t      sa_family_t;
struct sockaddr {
    sa_family_t sa_family;  /* [XSI] address family */
    char        sa_data[14];    /* [XSI] addr value (actually larger) */
};

注意原来的 sa_family_t 是 16 位长的。
然后 Socket API 被设计成是通用的 API。即可以兼容当时大部分的网络协议。可以看到 <sys/socket.h> 中定义了很多 AF_XXX 的常量,也就是说地址种类是多种多样的。也就是说 sockaddr 本身应该当作一个接口类型来理解。但是 C 语言本身就没有接口类型,但是在 C 语言中对于布局相容的结构通过安全的通过指针转换来进行类型转换。
注意理解布局相容的转换。sockaddr 默认声明的 sa_data 为 14 个字节的大小。
但是如果我们访问 sa_data[15] 这样明显索引越界的操作,编译和运行时也不会报错,只不过里面的值是内存的原始值。所以 Socket API 的函数通过在函数中声明 address_len 参数来让使用者指定长度以便 Socket API 在实现时能正确的访问地址数据。 这里也利用到了 C 语言数组的另一个特别,即结构体中各字段在内存的布局中是连续,数组在内存中的布局也是连续的。

struct sockaddr 中的 sa_len 字段

后来在 BSD 4.4 中 开发者对 struct sockaddr 这个接口类型进行了升级。将 struct sockaddr 这个接口类型的子类的长度信息添加进 struct sockaddr 这个接口类型结构中。为了兼容以前的接口,可以利用 C 语言结构的内存布局特性,将 sa_family 由原来的16位长度改为 8 位长度(嗯,8位长度应该也够用了)。前面的 8位留给 sa_len 字段。

typedef __uint8_t       sa_family_t;
struct sockaddr {
    __uint8_t   sa_len;     /* total length */
    sa_family_t sa_family;  /* [XSI] address family */
    char        sa_data[14];    /* [XSI] addr value (actually larger) */
};

sockaddr接口实现类的眼光看 sockaddr_insockaddr_in6

sockaddr_in 结构名称中的 in 是什么意思呢,我估计是 internet 的缩写,
sockaddr_in6 存在的原因则是因为 sockaddr_in 原来是为存储 IPv4 地址而设计的,其大小存储不了 IPv6 的地址,所以 sockaddr_in6 就是为了存储 IPv6 地址而设计的了。(虽然 sockaddr_in 中的 sin_zero 字段保留了 64 位长度的空间。但是 IPv6 的地址长度是 128 位,所以终究是不够的。)

一般来说我们把像 struct sockaddr * 这种用作接口类型的指针叫做 Opaque Pointer

另外值得说明的是 sockaddr_in6 设计成是 64 位对齐的:

IPv6 addresses carried in data structures should be 64-bit

  aligned.  This is necessary in order to obtain optimum performance
  on 64-bit machine architectures.

如果让我们用面向对象的方式来设计那大概是这样的表示形式。

interface SocketAddress{
    uint8_t getLen();
   uint8_t getFamily();
   byte[] data();
}

class InternetSocketAddress implement SocketAddress{
  uint16_t getPort();
 IPv4Address getAddress();
}
class InternetSocketAddressV6 implement SocketAddress{
  uint16_t getPort();
 IPv6Address getAddress();
}

传入或传出参数

另外一个值得说明地方是:

  1. bind()connect() 函数中的 struct sockaddr * 指针是将数据传入到函数里面供函数使用。
  2. accept() 中的函数的struct sockaddr * 指针则是用来提供分配好的内存结构以便 accept() 函数将接收到的连接请求的客户端的地址写入到 struct sockaddr * 指针所指向的内存中。

sockaddr_storage

上面介绍了, sockaddr_insockaddr_in6 ,那么当一个 sockaddr 我们不知道其内存布局是 sockaddr_in 还是 sockaddr_in6 的时候怎么办呢?
当我们的函数要同时兼容 IPv4 和 IPv6 时就需要思考这些问题了。
比如上面的 accept 函数,比如域名解析时需要用到的接口 getaddrinfo 中的 struct addrinfo,声明在 <netdb.h> 中。

struct addrinfo {
    int ai_flags;   /* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */
    int ai_family;  /* PF_xxx */
    int ai_socktype;    /* SOCK_xxx */
    int ai_protocol;    /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
    socklen_t ai_addrlen;   /* length of ai_addr */
    char    *ai_canonname;  /* canonical name for hostname */
    struct  sockaddr *ai_addr;  /* binary address */
    struct  addrinfo *ai_next;  /* next structure in linked list */
};
int
     getaddrinfo(const char *hostname, const char *servname,
         const struct addrinfo *hints, struct addrinfo **res);

sockaddr_storage 就是为了兼容 sockaddr_insockaddr_in6 的等内存结构布局而来。因此在 struct sockaddr_storage 中只有 sa_lensa_family 两个字段是公开的。其他的只是内存布局的占位而已。(其中 __ss_align 为字节对齐而生)

参考

  1. RFC 3493
  2. Implementing AF-independent application

If this article is useful to you, may be you want to buy me a candy.

相关文章

  • Unix Socket 编程中几个地址结构简析

    前言 Unix Socket 编程中与地址有关的结构有这么一些。 struct sockaddr struct s...

  • socket API

    socket 结构 socket domain:填地址族 AF_INET,AF_INET6,AF_UNIX typ...

  • socket

    Socket Families(地址簇) socket.AF_UNIX  unix本机进程间通信 socket.A...

  • Go语言的网络编程

    socket编程 Socket是BSD UNIX的进程通信机制,通常也称作”套接字”,用于描述IP地址和端口,是一...

  • Unix Socket 编程的函数接口-golang中的Sock

    在 Unix/Linux 中的 Socket 编程主要通过调用 listen, accept, write rea...

  • 网络编程中的数据结构与API

    在网络编程中,网络层数据结构存储了网络传输的地址族,目的ip地址,目的端口号等重要信息,socket API为程序...

  • 书单

    Unix 编程艺术Java编程思想数据结构与算法分析unix环境高级编程代码大全Effective javajav...

  • 0607

    chatroom_utils中的一些数据结构 参考链接 [gcc编程] socket编程——sockaddr_in...

  • unix domain socket 浅析

    unix domain socket unix domain socket 是在socket架构上发展起来的用于同...

  • Linux下的Socket编程(主要包括TCP部分)

    Linux下的Socket编程(主要包括TCP部分) 转载麻烦注明原文地址本文是Linux下基本的Socket编程...

网友评论

    本文标题:Unix Socket 编程中几个地址结构简析

    本文链接:https://www.haomeiwen.com/subject/zjaelftx.html