专栏首页嵌入式智能硬件UNIX网络编程卷1(第三版)套接字编程简介

UNIX网络编程卷1(第三版)套接字编程简介

IPv4套接字地址结构:

  通常也被成为“网际套接字地址结构”,以sockaddr_in命名,定义在<netinet/in.h>头文件中。 

struct in_addr
  {
    in_addr_t s_addr;  // 32bits的ip地址,如0xFF000001 -> 127.0.0.1
  };
/* Structure describing an Internet socket address.  */
struct sockaddr_in
  {
    uint8_t sin_len;       /* length of structure (16) */
    sa_family_t sin_family;   /* AF_INET */
    in_port_t sin_port;     /* Port number. 16bits */
    struct in_addr sin_addr;    /* Internet address.  */
    char sin_zero[8];           /* unused */
  };  // sin_family,sin_port,sin_addr是一定支持的3个成员

IPv4地址和TCP或UDP端口号在套接字地址结构中总是以网络字节序(区别于主机字节序)来存储。

之所以网际地址(in_addr)是一个结构体,是因为以前这个结构体中允许访问2个16位的值,用于划分A、B、C类,而现在子网划分之后,这些union结构不再需要。

sin_zero字段未曾使用,但我们总是把该字段置为0,按照惯例,我们总是在填写前把整个结构置为0。

以上是IPv4套接字地址结构,然而套接字函数是通用的,并且总是接收一个套接字地址结构的指针(eg, sockaddr_in serv; bind(sockfd, (sockaddr *) &serv, sizeof(serv)); ),可以看到第二个参数被转成了sockaddr类型,这是通用套接字地址结构。在套接字函数定义的时候,还没有通用的指针类型void *,所以必须传入一个恰当的类型,否则会报错,于是在<sys/socket.h>定义了一个通用的套接字地址结构。

通用套接字地址结构用途就是对指向特定于协议的套接字地址结构的指针执行类型强制转换。

struct sockaddr{
    uint8_t      sa_len;
    sa_family_t    sa_family;    /* address family: AF_xxx value */
    char        sa_data[14];    /* protocol-specific address */
};

IPv6地址为128位长,但通常写作8组,每组为四个十六进制数的形式,如 FE80:0000:0000:0000:AAAA:0000:00C2:0002。

IPv6套接字地址结构在<netinet/in.h>头文件中定义:

struct in6_addr
  {
    uint8_t s6_addr[16];
  };

#define SIN6_LEN
 
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 # */
    uint32_t sin6_flowinfo; /* IPv6 flow information */
    struct in6_addr sin6_addr;  /* IPv6 address */
    uint32_t sin6_scope_id; /* IPv6 scope-id */
  };

IPV6的地址族是AF_INET6,而IPv4的地址族是AF_INET。

结构中字段的先后顺序做过编排,使得如果sockaddr_in6的结构本身是64位对齐的,那么128位的sin6_addr字段也是64位对齐的。在一些64位处理机上,如果64位数据存储在某个64位边界位置,那么对它的访问将得到优化处理。

sin6_flowinfo字段分为两个字段:低序20位是流标(flow label),高序12位保留。

对于具备范围的地址(scoped address),sin6_scope_id字段标识其范围(scope),最常见的是链路局部地址(link-local address)的接口索引(interface index)

新的通用套接字地址结构:新的结构克服了sockaddr的一些缺点,新的sockaddr_storage足以容纳系统所支持的任何套接字地址结构。

struct sockaddr_storage {
  uint8_t   ss_len;   /* length of this struct (implementation dependent) */
  sa_family_t  ss_family;    /* address family: AF_xxx value */
  /* implementation-dependent elements to provide:
   * a) alignment sufficient to fulfill the alignment requirements of
   * all socket address types that the system supports.
   * b) enough storage to hold any type of socket address that the
   * system supports.
   */
};

sockaddr_storage能够满足最苛刻的对齐要求。

sockaddr_storage足够大,可以容纳系统支持的任何套接字地址结构,除了ss_family和ss_len(如果有),其他的字段可以任意放置(对用户透明),sockaddr_storage结构必须强制转换成或复制到适合于ss_family字段所给出地址类型的套接字地址结构中,才能访问其他字段。

值-结果参数(说的是传递的参数作为返回结果的引用,eg, func(&res) ):

当往一个套接字函数传递一个套接字地址结构时,该结构总是以引用形式来传递,也就是说传递的是指向该结构的一个指针。该结构的长度也作为一个参数来传递,不过其传递方式取决于该结构的传递方向:是从进程的内核,还是从内核到进程。

  1)从进程到内核传递套接字地址结构的函数有3个:bind、connect、sendto。这些函数的一个参数是指向某个套接字地址结构的指针,另一个参数是该结构的整数大小。(内核需要知道到底从进程复制了多少数据进来)

  2)从内核到进程传递套接字地址结构的函数有4个:accept、recvfrom、getsockname和getpeername。这4个函数的其中两个参数是指向某个套接字地址结构的指针和指向表示该结构大小的整数变量的指针(这是一个结果,所以是引用传值)。

值-结果参数返回的结果:如果套接字地址结构是固定长度(如IPv4 (16) 和IPv6 (28) ),则返回值总是固定长度;对于可变长度(unix域等),返回值可能小于该结构的最大长度。

字节排序函数

小端字节序(little-endian):低序字节存储在起始地址,如0x12345678,在内存中从小到大的地址,存储序列是 78 56 34 12

大端字节序(big-endian):高序字节存储在起始地址,如0x12345678,在内存中从小到大的地址,存储序列是 12 34 56 78

以上两种格式都有系统使用!我们把某个给定系统所用的字节序成为主机字节序(host byte order)

#include    "../unpv13e/unp.h"
#include    "../unpv13e/apueerror.h"
// 以上路径是我自己的配置
// page.64 确定主机字节序的程序(小端对齐还是大端对齐)
 
int
main(int argc, char **argv)
{
    union {
        short  s;
        char   c[sizeof(short)];
    } un;
 
    un.s = 0x0102;
    printf("%s: ", CPU_VENDOR_OS);  // 输出CPU类型、厂家和操作系统版本。
    if (sizeof(short) == 2) {
        if (un.c[0] == 1 && un.c[1] == 2)
            printf("big-endian\n");
        else if (un.c[0] == 2 && un.c[1] == 1)
            printf("little-endian\n");
        else
            printf("unknown\n");
    } else
        printf("sizeof(short) = %d\n", (int)sizeof(short));
 
    exit(0);
}

问题1:网络字节序和主机字节序的区别?

答:网际协议使用大端字节序来传送这些多字节整数,而系统使用的主机字节序可能是大端也可能是小端。

问题2:具体实现方法是怎样?

答:套接字地址结构的字段按照网络字节序(大端)进行维护,所以要通过函数进行转换。(以下h:host,n:network,s:short->16bits port,l:long->32bits ipv4)

htons 返回网络字节序的端口

htonl 返回网络字节序的ip

ntohs 返回主机字节序的端口

ntohl 返回主机字节序的ip

注意:事实上在64位系统中,长整数虽然占用64位,to long的函数操作的仍然是32位的值。

在大端字节序的系统中,这4个函数被定义为空宏。

字节操纵函数

处理字符串的函数被放在string.h中,然而像套接字地址结构这种多字节字段,需要全部清0,则需要用到字节操纵函数(有2组):

#include <strings.h>
// strings.h是从BSD系UNIX系统继承而来,里面定义了一些字符串函数,参考自 http://blog.csdn.net/xin_yu_xin/article/details/38672137
void bzero(void *dest, size_t nbytes);
void bcopy(const void *src, void *dest, size_t nbytes);
int bcmp(const void *ptr1, const void *ptr2, size_t nbytes);    // 0为相等,非0为不相等
#include <string.h>
// ANSI C标准
void *memset(void *dest, int c, size_t len);
void *memcpy(void *dest, const void *src, size_t nbytes);
int memcmp(const void *ptr1, const void *ptr2, size_t nbytes);    // 0为相同,非0为不相同

地址转换函数

作用:从点分十进制数串(如:206.168.112.96)转成网络字节序二进制值

两组函数:

  (1) inet_aton , inet_addr , inet_ntoa (仅适用于IPv4)   

#include <arpa/inet.h>
 
int inet_aton(const char *strptr, struct in_addr *addrptr);    
// 若字符串有效则返回1,否则为0,。如果addrptr指针为空,那么该函数仍然对输入的字符串执行有效性检查,但是不存储任何结果
 
in_addr_t inet_addr(const char *strptr);   
// 字符串有效则返回32位二进制网络字节序的IPv4地址,否则返回INADDR_NONE(通常是255.255.255.255,这意味着这个有限广播地址不能由该函数来处理,还有一个问题是一些编译器编译的程序将返回-1的结果,而不是INADDR_NONE,所以这个函数现在已经被废弃,要用inet_aton或inet_pton来代替)
 
char *inet_ntoa(struct in_addr inaddr);
// 注意,参数是一个结构而不是一个结构指针(这是非常罕见的..),返回值是指向一个点分十进制数串的指针,该函数的返回值指向的字符串是驻留在静态内存中的,以为着该函数是不可重入的(后面的概念)

(2) inet_pton , inet_ntop (对IPv4和IPv6都适用)   p for presentation(表达) n for numeric(数值)

#include <arpa/inet.h>
 
int inet_pton(int family, const char *strptr, void *addrptr);
// 成功则返回1,输入不是有效的表达格式则返回0,出错返回-1
 
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
// 成功则返回指向结果的指针,出错返回NULL,len是目标存储单元的大小,用于防止缓冲区溢出,为了有助于指定这个大小,在<netinet/in.h>头文件中定义了
// #define     INET_ADDRSTRLEN     16
// #define     INET6_ADDRSTRLEN    46
// 如果len太小,不足以容纳表达格式的结果(包括结尾的空字符),则返回一个空指针,置errno为ENOSPC,strptr参数不可以是一个空指针,必须先分配大小,调用成功时,这个指针就是该函数的返回值。
只支持IPv4的inet_pton和inet_ntop函数的简单定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int
inet_pton(int family, const char *strptr, void *addrptr)
{
    if (family == AF_INET) {
        struct in_addr  in_val;
 
        if (inet_aton(strptr, &in_val)) {
            memcpy(addrptr, &in_val, sizeof(struct in_addr));
            return (1);
        }
        return(0);
    }
    errno = EAFNOSUPPORT;
    return (-1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const char *
inet_ntop(int family, const void *addrptr, char *strptr, size_t len)
{
    const u_char *p = (const u_char *) addrptr;
 
    if (family == AF_INET) {
        char    temp[INET_ADDRSTRLEN];
 
        snprintf(temp, sizeof(temp), "%d.%d.%d.%d",
                 p[0], p[1], p[2], p[3]);
        if (strlen(temp) >= len) {
            errno = ENOSPC;
            return (NULL);
        }
        strcpy(strptr, temp);
        return (strptr);
    }
    errno = EAFNOSUPPORT;
    return (NULL);
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • pthread的使用

    编译的时候发现,报错对‘pthread_create’未定义的引用,由于pthread库不是Linux系统默认的库,连接时需要使用库libpthread.a,所...

    心跳包
  • 【PMP】8.7早上题

    1.在一个软件项目结束时,项目超前于进度并低于预算。一名关键团队成员建议项目经理增加成本,可以向软件添加对客户有利的新功能。这不会影响初始预算或进度。作为项目经...

    心跳包
  • zbar中的zbar_scan_image 函数

    图像扫描的工作都是由zbar_scan_image完成的,zbar_scan_image主要根据设定的扫描密度(density)控制像素点读取(Z字形),sca...

    心跳包
  • 用一个脚本学习 python

    # -*- coding: utf-8 -*- # Python 2.7 学习参考脚本 # print 打印函数 print "Hello Worl...

    MachineLP
  • keras_bert文本多分类

    用户1750490
  • python 快速入门

    py3study
  • wtfPython—Python中一些奇妙的代码

    wtfPython是github上的一个项目,作者收集了一些奇妙的Python代码片段,这些代码的输出结果会和我们想象中的不太一样; 通过探寻产生这种结果的内部...

    小小科
  • for of和for in的区别

    两个遍历方式的最终区别就在于:for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值。

    无邪Z
  • Spring Cloud 多版本怎么选择?帮你解惑!

    Java技术栈
  • 实操指南|关于Python中的列表理解

    列表理解通常在Python中用于编写单行语句,这些语句通过循环访问可迭代对象来创建新列表或字典。本文将首先介绍有关for循环在Python中的工作原理,然后说明...

    用户6543014

扫码关注云+社区

领取腾讯云代金券