「生活可以更简单, 欢迎来到我的开源世界」
  1. 3.2 套接字的地址结构
    1. IPv4套接字地址结构:
    2. 通用套接字地址结构:
    3. IPv6套接字地址结构:
    4. 新的通用套接字地址结构
  2. 3.3 值-结果参数
  3. 3.4 字节排序函数
  4. 3.5 字节操作函数
  5. 3.6 inet_aton、inet_addr和inet_ntoa函数
  6. 3.7 inet_pton和inet_ntop函数
    1. 总结5个函数
  7. 3.8 sock_ntop和相关函数
  8. 3.9 readn、writen和readline函数
Unix网络编程-第3章 套接字编程简介
2019-12-09
」 「

Unix网络编程-第3章 套接字编程简介

3.2 套接字的地址结构

大多数套接字函数都需要一个指向套接字地址结构的指针作为参数。每个协议族都定义自己的套接字地址结构,这些结构的名字均已sockaddr_开头,并以对应每个协议族的唯一后缀结尾。

IPv4套接字地址结构:

struct in_addr{
in_addr_t s_addr;
};
struct sockaddr_in{
uint8_t sin_len;

//POSIX规范只需要这个结构中的三个字段
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;

char sin_zero[8];
};
字段 数据类型 说明
s_addr in_addr_t 至少32位的无符号整数类型
sin_port in_port_t 至少16位的无符号整数类型
sin_family sa_family_t 任何无符号整数类型。在支持长度字段的实现中,通常是一个8位无符号整数,在不支持长度字段中,是一个16位的无符号整数

套接字地址结构仅在给定主机上使用:虽然结构中某些字段用在不同主机之间的通信,但是结构本身并不在主机之间传递。

为了让套接字函数能够处理来自任何协议族的套接字地址结构,套接字函数定义的参数中使用指向通用套接字地址结构的指针,使用时再进行类型强制转换

通用套接字地址结构:

struct sockaddr{
uint8_t sa_len; //该字段只在一些Unix实现中有
//SuSv3标准不做要求,Linux实现也不存在该字段
sa_family_t sa_family;
char sa_date[14];
};

IPv6套接字地址结构:

struct in6_addr{
uint8_t s6_addr[16];
};

//如果系统支持套接字地址结构中的长度字段,则SIN6_LEN常值必须定义
#define SIN6_LEN

struct sockaddr_in6{
uint8_t sin6_len;
sa_family_t sin6_family;
in_port_t sin6_port;

uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;

uint32_t sin6_scope_id;
}

新的通用套接字地址结构

struct sockaddr_storage{
uint8_t ss_len;
sa_family_t ss_family;
}

套接字地址结构比较:

1579573720073

3.3 值-结果参数

套接字的地址结构总是以引用形式传递给套接字函数的。

套接字的长度作为一个参数传递给套接字函数时,其传递方式取决于该结构的传递方向。

套接字地址结构可以在两个方向上传递:

当套接字地址结构的长度使用值-结果参数时,如果套接字地址结构是固定长度则从内核返回的值总是那个长度,如果是可变长度,则返回值可能小于该结构的最大长度。

3.4 字节排序函数

主机字节序:

1579582483045

最高有效位:MSB:most significant bit

最低有效位:LSB:least significant bit

术语“小端”和“大端”表示:多个字节值的哪一端(小端或大端)存储在该值的起始地址(低地址)。

网络字节序:大端字节序

网络协议必须指定一个网络字节序。由于历史原因和POSIX规范的规定,套接字地址结构中的某些字段必须按照网络字节序进行维护。

主机字节序和网络字节序之间相互转换使用以下4个函数:

//主机:host(h)
//网络:network(n)
//短整型:short(s)
//长整型:long(l)
#include <netinet/in.h>

//返回网络字节序的值
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);

//返回主机字节序的值
uint16_t ntohs(uint16_t net16bitvalue);
uint32_t ntohl(uint32_t net32bitvalue);

因特网另一个重要的约定是位序,IPv4首部前32位的位序如下:

1579584598760

3.5 字节操作函数

操作多字节段的函数有两组,它们既不对数据作解释,也不假设数据是以空字节符结束的C字符串。

当源字节串与目标字节串重叠时,bcopy能够正确处理,memcpy的操作结果却不可知,这种情况必须改用ANSI C的memmove函数。

比较操作是假设两个不等字节均为无符号字符(unsigned char)的情况下完成的。

3.6 inet_aton、inet_addr和inet_ntoa函数

功能介绍:在点分十进制数串和它长度为32位的网络字节序二进制值间转换IPv4地址

#include <arpa/inet.h>

//若字符串有效,则返回1,否则返回0
//如果addrptr指针为空,那么该函数仍然对输入的字符串执行有效性检查,但是不存储任何结果。
int inet_aton(const char *strptr, struct in_addr *addrptr);

//若字符串有效,则返回32位二进制网络字节序的IPv4地址,否则返回INADDR_NONE
//NADDR_NONE常值通常是一个32位均为1的值,这意味着点分十进制数串255.255.255.255不能由该函数处理,因为其二进制值被用来指示函数失败。
in_addr_t inet_addr(const char *strptr);

//返回一个点分十进制数串的指针
char *inet_ntoa(struct in_addr inaddr);

3.7 inet_pton和inet_ntop函数

这两个函数对于IPv4地址和IPv6地址都适用。函数名中p和n分别代表表达(presentation)数值(numeric)

#include <arpa/inet.h>

//函数执行成功返回1,表达的格式无效返回0,出错返回-1
int inet_pton(int family, const char *strptr, void *addptr);

//函数执行成功返回指向结果的指针,出错返回NULL
const char *inet_ntop(int family, const void *addptr, char *strptr, size_t len);

//family参数可以是AF_INET,也可以是AF_INET6,如果以不被支持的地址族作为family参数,两个函数就都返回一个错误,并将errno置为EAFNOSUPPORT

总结5个函数

1579590914742

3.8 sock_ntop和相关函数

本书编写的协议无关性函数。函数名以sock_开头。

#include "unp.h"

//成功返回非空指针,出错返回NULL
char *sock_ntop(const struct sockaddr * sockaddr, socklen_t addrlen);

//成功返回0,出错返回-1
int sock_bind_wild(int sockfd, int family);

//若地址为同一协议族且相同,则返回0,反则返回非0
int sock_cmp_addr(const struct sockaddr *sockaddr1,
const struct sockaddr *sockaddr2, socklen_t addrlen);

//若地址为同一协议族且端口相同,则返回0,反则返回非0
int sock_cmp_addr(const struct sockaddr *sockaddr1,
const struct sockaddr *sockaddr2, socklen_t addrlen);

//返回:若为IPv4或IPv6地址则为非负端口号,否则为-1
int sock_get_port(const struct sockaddr *sockaddr, socklen_t addrlen);

//成功返回非空指针,出错返回NULL
char *sock_ntop_host(const struct sockaddr *sockaddr, socklen_t addrlen);

void sock_set_addr(const struct sockaddr *sockaddr,
socklen_t addrlen, void *ptr);
void sock_set_port(const struct sockaddr *sockaddr,
socklen_t addrlen, int port);
void sock_set_wild(sturct sockaddr *sockaddr, socklen_t addrlen);

3.9 readn、writen和readline函数

字节流套接字上的read和write函数所表现的行为不同于通常文件的I/O。字节流套接字上调用read或write输入或输出的字节数可能比请求的数量少,然而这不是出错状态。原因在于:内核中用于套接字的缓冲区可能已经达到极限。此时需要的是调用者再次调用read或write函数,输入或输出剩余的字节。

这个现象在read一个字节流套接字时很常见,但是在write一个字节流时只能在该套接字为非阻塞的前提下才出现。

为了预防万一,不让返回的字节计数值不足,编写了三个函数。

#include "unp.h"

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);
<⇧>