IPV4 套接字地址结构
<netstat/in.h>
struct in_addr {
in_addr_t s_addr; /*u32-bit IPv4 address;network byte ordered*/
};
struct sockaddr_in {
uint8_t sin_len; /* length of structure (16) */
sa_family_t sin_family; /* u8-bit(support length) or u16-bit(not support)*/
in_port_t sin_port; /* u16-bit TCP or UDP port number;network byte ordered*/
struct in_addr sin_addr; /* in_addr structure*/
char sin_zero[8]; /* unused */
};
sin_len字段简化了处理不同长度套接字地址结构的情况。用于内核处理不同协议的套接字。除了路由套接字之外,我们不使用这个字段。
有四个函数将套接字地址从进程传给内核bind,connect,sendto,sendmsg
,这些函数通过参数显式设置sin_len参数;另外五个函数将套接字地址从内核传给进程,accept,recvform,recvmsg,getpeername,getsockname
,sin_len在返回进程之前就被设置。
POSIX只需要sin_family,sin_addr,sin_port。大多数实现都增加了sin_zero成员,因此所有的socket address结构都至少为16字节。IPV4地址及端口号都存储为网络字节序,一般使用时都要与主机字节序相互转换。
32位IPV4地址有两种访问方式:sin_addr,将IPV4地址视为in_addr结构;sin_addr.s_addr将IPV4地址视为in_addr_t(uint32_t),当传参数时,应当正确使用IPV4地址。早期实现中,in_addr结构是多个结构的union,允许访问32位中的每个字节。用于A,B,C类型网络获取地址。随着子网出现以及无类型寻址,union结构被废弃。sin_zero字段不使用,总是设为0。实际上在填充sockaddr_in之前,将整个结构填为0。
套接字地址总是引用传参。但是套接字函数会处理多种协议。因此提出了通用的套接字地址结构。
<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 */
};
套接字函数都使用通用套接字地址作为参数。一般情况下这样使用:
int bind(int, struct sockaddr *, socklen_t); /*prototype*/
struct sockaddr_in serv; /* IPv4 socket address structure */
/* fill in serv{} */
bind(sockfd, (struct sockaddr *) &serv, sizeof(serv));
IPV6套接字地址结构 (todo)
使用了新的通用结构,支持所有地址类型
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支持对齐需求。除了ss_family和ss_len,其他字段都是不透明的,需要根据ss_family进行转换。
套接字地址结构比较
IPV4和IPV6结构是定长的,Unix domain和datalink结构是变长的。因此当我们向套接字函数传递套接字指针时,需要传递长度。从4.3BSD开始,所有套接字地址都增加了长度字段,可以将长度包含着结构体之中而不用向函数传递参数。
Value-Result参数
前面提到,套接字地址结构通过引用传递。长度参数的传递方式取决于传递方向。并且,
bind(), connect(), sendto()
从进程将结构指针
以及长度值
传给内核,因此内核知道从哪拷贝多少数据。
accept(), recvfrom(), getsockname(),getpeername()
从内核传给进程,结构及长度都使用指针传递。当函数调用时,进程告诉内核结构的长度;当返回时,内核告诉进程实际存储的数据长度。另外两个函数recvmsg, sendmsg
将长度作为结构成员而不是参数。
字节序
小端:低位在低地址;大端:高位在低地址。网络字节序使用大端。
#include <netinet/in.h>
uint16_t htons(uint16_t ) ;
uint32_t htonl(uint32_t ) ;/*Both return: value in network byte order*/
uint16_t ntohs(uint16_t ) ;
uint32_t ntohl(uint32_t ) ; /*Both return: value in host byte order*/
/*do not care about the actual byte order*/
字符串与地址转换函数
#include <arpa/inet.h>
int inet_aton(const char *strptr, struct in_addr *addrptr); /*Returns: 1 if string was valid, 0 on error*/
in_addr_t inet_addr(const char *strptr);/*Returns: 32-bit binary network byte ordered IPv4 address; INADDR_NONE if error,deprecated*/
char *inet_ntoa(struct in_addr inaddr);/*Returns: pointer to dotted-decimal string*/
这三个函数转换IPV4地址的字符串与32位网络字节序值。inet_aton将字符串转换为32位网络字节;inet_addr发生错误时将返回INADDR_NONE(0xffffffff),因此无法处理255.255.255.255,此函数已经废弃;inet_ntoa返回322位网络字节对应的字符串,返回值在静态内存中,此函数是不可重入的。
#include <arpa/inet.h>
int inet_pton(int family, const char *strptr, void *addrptr); /*Returns: 1 if OK, 0 if input not a valid presentation format, -1 on error*/
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len); /*Returns: pointer to result if OK, NULL on error*/
这两个函数可以处理IPV4或IPV6地址。p代表presentation,n代表numeric。family是AF_INET,AF_INET6之一。inet_ntop的len参数指定缓冲区的长度,使用两个宏来指定大小:
#define INET_ADDRSTRLEN 16 /* for IPv4 dotted-decimal */
#define INET6_ADDRSTRLEN 46 /* for IPv6 hex string */