UNIX网络编程 - Chapter 3 套接字编程简介

Chapter 3 套接字编程简介

套接字地址结构

  • IPv4(sockaddr_in), 定义在<netinet/in.h>

    • POSIX定义

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      struct in_addr {
      in_addr_t s_addr; // 32-bit IPV4 address, 至少是一个32位的无符号整数类型

      };

      strict sockaddr_in {
      uint8_t sin_len; // lengtht of structure (16)
      sa_family_t sin_family; // AF_INET可以为任何任何无符号整数类型, 在支持长度字段的实现中, 通常为一个8位的无符号整数, 不支持长度时为16位
      in_port_t sin_port; // 16-biit TCP or UDP port number

      struct in_addr sin_addr; // 32-bit IPv4 address, 至少是一个16位的无符号类型

      char sin_zero[8] // unused
      };
    • POSIX数据类型为:

      ![POSIX DATA](UNIX网络编程-3/POSIX DATA.png)

    • IPv4地址和TCP或UDP端口在套接字地质结构中总是以网络字节序来存储

    • 32位IPv4地址存在两种不同的访问方法。举例来说,如果serv定义为某个网际套接字地址结构,那么serv.sin_addr将按in_addr结构引用其中的32位IPv4地址,而serv.sin_addr.s_addr将按in_addr_t(通常是一个无符号的32位整数)引用同一个32位IPv4地址。因此,我们必须正确地使用IPv4地址,尤其是在将它作为函数的参数时,因为编译器对传递结构和传递整数的处理是完全不同的。

  • 通用套接字地址结构(sockaddr), 定义在<sys/socket.h>

    • 定义如下

      1
      2
      3
      4
      5
      struct sockeraddr {
      uint8_t sa_len;
      sa_family_t sa_family; // address family: AF_xxx value
      char sa_data[14]; // protocal-specific addresss
      };
    • 套接字函数被定义为一直想某个通用套接字结构的一个指针作为其参数之一

      1
      2
      3
      4
      int bind(int, struct sockaddr *, socklen_t);		// 第二个参数为通用套接字结构sockaddr, 而非sockaddr_in / sockaddr_in6
      |
      struct sockaddr_in serv; // IPv4 socket address structure
      bind(sockfd, (struct sockaddr *) &serv, sizeof(serv)); // 这里要进行强制类型转换
  • IPv6(sockaddr_in6), 定义在<netinet/in.h>

    • 定义如下

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      struct in6_addr {
      uint_t s6_addr[16]; // 128-bit IPv6 address

      };
      #define SIN6_LEN // required for compile-time tests
      struct sockaddr_in6 {
      uint8_t sin6_len; // length of this struct (28)
      sa_family_t sin6_family; // AF_INET6
      in_port_t sin6_port; // transport layer port
      // network byte ordered
      uint32_t sin6_flowinfo; // flow infomation, undefined
      struct in6_addr sin6_addr; // IPv6 address
      // network byte ordered
      uint32_t sin6_scope_id; // set of interfaces for a scope
      }
  • 小端序和大端序

    ![小端序和大端序](UNIX网络编程-3/little-endian & big-endian.png)

  • 字节操纵函数

    Berkeley函数

    1
    2
    3
    4
    #include <strings.h>
    void bzero(void *dest, size_t nbytes); // 把目标字节串中指定书目的字节置为0
    void bcopy(const void *src, void *dest, size_t nbytes);
    int bcmp(const void *ptr1, const void *ptr2, size_t nbytes); // return 0 if equal

    ANSI C函数

    1
    2
    3
    4
    #include <string.h>
    void *memset(void *dest, int c, size_t len);
    void *memcpy(void *dest, const coid *src, size_t nbytes);
    int memcmp(const void ptr1, const void *ptr2, size_t nbytes); // return 0 if equal
  • 地址转换函数

    在ASCII字符串和网络字节序的二进制值之间转换网际地址

    • inet_aton, inet_addrinet_ntoa在点分十进制数串(“206.111.222.4”)与它长度为32位的网络字节序二进制间转换IPv4地址

      1
      2
      3
      4
      5
      6
      7
      #include <arpa/inet.h>

      int inet_aton(const char *strptr, struct in_addr *addrptr); // return 1 if str is valid

      in_addr_t inet(const char *strptr); // 字符串有效则为32位二进制网络字节序的IPv4地址, 否则为INADDR_NONE
      char *inet_ntoa(struct in_addr inaddr); // 返回一个指向点分十进制数串的指针

      第一个函数inet_atonstrptr所指C字符串转换成一个32位的网络字节序二进制值,并通过指针addrptr来存储。若成功则返回1,否则返回0。
      inet_aton函数有一个没写入正式文档中的特征:如果addrptr指针为空,那么该函数仍然对输入的字符串执行有效性检查,但是不存储任何结果。

      inet_addr进行相同的转换,返回值为 32 位的网络字节序二进制值。该函数存在一个问题:所有(2^{32})个可能的二进制值都是有效的 IP 地址(从 0.0.0.0 到 255.255.255.255),但是当出错时该函数返回INADDR_NONE常值(通常是一个 32 位均为 1 的值)。这意味着点分十进制数串 255.255.255.255(这是 IPv4 的有限广播地址,见 20.2 节)不能由该函数处理,因为它的二进制值被用来指示该函数失败。

      inet_addr函数还存在一个潜在的问题:一些手册页面声明该函数出错时返回 - 1 而不是INADDR_NONE。这样在对该函数的返回值(一个无符号的值)和一个常数值(-1)进行比较时可能会发生问题,具体取决于 C 编译器。

      如今inet_addr已被废弃,新的代码应该改用inet_aton函数。

      inet_ntoa函数将一个 32 位的网络字节序二进制 IPv4 地址转换成相应的点分十进制数串。由该函数的返回值所指向的字符串驻留在静态内存中。这意味着该函数是不可重入的,该函数以一个结构而不是以指向该结构的一个指针作为其参数。

      函数以结构为参数是罕见的,更常见的是以指向结构的指针为参数。

    • inet_ptoninet_ntop对于IPv4和IPv6地址都适用(表达式presentation to numeric)

      1
      2
      3
      4
      5
      #include <arpa/inet.h>

      int inet_pton(int family, const chat *strptr, void *addrptr); // 成功返回1, 输入不为有效表达式返回0,出错返回-1

      const char *inet_ntop(int family ,const void *addrptr, char *strptr, size_t len); // 成功返回指向结果的指针, 失败为NULL
  • 读写套接字的函数

    1
    2
    3
    ssize_t readn(int filedes, void * buff, size_t nbytes);
    ssize_t written(int filedes, const void *buff, size_t nbytes);
    ssize_t readline(int filedes, void *buff, size_t maxlen); // 均返回读或写的字节数, 出错返回-1
  • Q&A

    问题 3.1:探讨套接字地址结构长度等参数用指针传递的原因。

    这样他的值可以被函数更改

    问题 3.2readn/writenvoid*转为char*的原因。

    char* 是最小的内存单元, 便于按字节操纵数据

    问题 3.3inet_atoninet_addr函数对于接受什么作为点分十进制数 IPv4 地址串一直相当随意:允许由小数点分隔的 1~4 个数,也允许由一个前导的0x来指定一个十六进制数,还允许由一个前导的0来指定一个八进制数。(尝试运行telnet 0xe来检验一下这些特性。)inet_pton函数对 IPv4 地址的要求却严格得多,明确要求用三个小数点来分隔四个在 0~255 之间的十进制数。当指定地址族为AF_INET6时,inet_pton不允许指定点分十进制数地址,不过有人可能争辩说应该允许,返回值就是对应这个点分十进制数串的 IPv4 映射的 IPv6 地址(见图 A-10)。

    试写一个名为inet_pton_loose的函数,它能处理如下情形:如果地址族为AF_INETinet_pton返回 0,那就调用inet_aton看是否成功;类似地,如果地址族为AF_INET6inet_pton返回 0,那就调用inet_aton看是否成功,若成功则返回其 IPv4 映射的 IPv6 地址。