3.1 概述
- 套接字地址结构。这些结构在两个方向上传递:从进程到内核和从内核到进程。其中从内核到进程方向的传递方式是值-结果参数。
- 地址转换函数。在地址的文本表达和它们存放在套接字地址结构中的二进制值之间进行转换。多数现存IPv4代码使用inet_addr和inet_ntoa这两个函数,inet_pton和inet_ntop同时试用IPv4和IPv6。
3.2 套接字地址结构
- IPv4套接字地址结构
/* 头文件<netinet/in.h> */
struct in_addr {
in_addr_t s_addr; /* 32-bit IPv4 address */
/* network byte ordered */
};
struct sockaddr_in {
uint8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port; /* 16-bit TCP or UDP port number */
/* network byte ordered */
struct in_addr sin_addr;
char sin_zero[8];
};
POSIX规范只需要设置:sin_family、sin_port、sin_addr。几乎所有的实现都增加了sin_zero字段,所以所有的套接字地址结构大小都至少是16字节。
- 通用套接字地址结构
/* 头文件<sys/socket.h> */
struct sockaddr {
uint8_t sa_len;
sa_family_t sa_family; /* AF_xxx */
char sa_data[14]; /* protocol-specific address */
};
- IPv6套接字地址结构
/* 头文件<netinet/in.h> */
struct in6_addr {
uint8_t s6_addr[16]; /* 128-bit IPv4 address */
/* network byte ordered */
};
struct sockaddr_in6 {
uint8_t sin6_len;
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port; /* transport layer port# */
/* network byte ordered */
uint32_t sin6_flowinfo; /* flow information, undefined */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* set of interfaces for a scope */
};
- 新的通用套接字地址结构
/* 头文件<netinet/in.h> */
struct sockaddr_storage {
uint8_t ss_len;
sa_family_t ss_family;
};
- 套接字地址结构比较
3.3 值-结果参数
(1)从进程到内核传递套接字地址结构的函数有3个:bind、connect、sendto。
struct sockaddr_in serv;
/* fill in serv{} */
connect(sockfd, (struct sockaddr *) &serv, sizeof(serv));
(2)从内核到进程传递套接字地址结构的函数有4个:accept、recvfrom、getsockname、getpeername。
struct sockaddr_un cli; /* Unix domain */
socklen_t len;
len = sizeof(cli); /* len is a value */
getpeername(unixfd, (struct sockaddr *) &cli, &len); /* len may have changed */
当函数被调用时,结构大小是一个值(value),它告诉内核该结构的大小,这样内核在写该结构时不至于越界;当该函数返回时,结构大小又是一个结果(result),它告诉进程内核在该结构中究竟存储了多少信息。这种类型的参数称为值-结果(value-result)参数。
3.4 字节函数
小端(little-endian)字节序:将低序字节存储在起始地址。
大端(big-endian)字节序:将高序字节存储在起始地址。
我们把某种给定系统所用的字节序称为主机字节序(host byte order)。
网络协议必须指定一个网络字节序(network byte order)。网际协议使用大端字节序来传送这些多字节整数。
- 字节序转换:
#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);
/* 均返回:主机字节序的值 */
- 字节操纵函数:
#include <strings.h>
void bzero(void *dest, size_t nbytes); /* bzero把目标字节串中指定数目的字节置为0 */
void bcopy(const void *src, void *dest, size_t nbytes); /* bcopy将指定数目的字节从src拷贝到dest中。 */
int bcmp(const void *ptrl, const void *ptr2, size_t nbytes); /* bcmp比较两个任意的字节串 */
- ASCI C函数:
#include <string.h>
void *memset(void *dest, int c, size_t len);
void *memcpy(void *dest, const void *src,size_t nbytes);
int memcmp(const void *ptrl, const void *ptr2, size_t nbytes);
void *memmove(void *dest, const void *src, size_t nbytes);
- 地址转换函数:
它们在ASCII字符串与网络字节序的二进制值之间转换网际地址。
#include <arpa/inet.h>
int inet_aton(const char *strptr, struct in_addr *addrptr);
in_addr_t inet_addr(const char *strptr); /* 被废弃 */
char *inet_ntoa(struct in_addr inaddr);
- inet_aton函数的指针若为空,则函数仍然执行输入串的有效性检查,但不存储任何结果。
- inet_addr的缺陷:出错返回值INADDR_NONE等于255.255.255.255(IPv4的有限广播地址),所以该函数不能处理此地址。
- 尽量使用inet_aton,不使用inet_addr。
- inet_ntoa函数的执行结果放在静态内存中,是不可重入的。
- 参数family可以是AF_INET,也可以是AF_INET6,若参数family不被支持,则出错,errno置为EAFNOSUPPORT。
- 指针addrptr是结构指针。
- len指定目标的大小,避免缓冲区溢出。如果len太小,则返回一个空指针,errno置为ENOSPC。为有助于规定该大小,有如下定义:
#include <netinet.h>
#define INET_ADDRSTRLEN 16 /*fro IPv4 dotted-decimal */
#define INET6_ADDRSTRLEN 46 /*for IPv6 hex string */
新的转换函数:
p代表表达(presentation),n代表数值(numeric)
#include <arpa/inet.h>
int inet_pton(int family, const char *strptr, void *addrptr);
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len); /* strptr不能为NULL。调用者必须为其分配内存并指定大小 */