筆記-TCP/IP 網絡套接字地址結構
大多數套接字函數都需要一個指向套接字地址結構指針作爲參數。每一個協議簇都定義專屬的套接字地址結構,以sockaddr_爲前綴。
IPv4 套接字地址結構
IPv4 套接字地址結構通常也稱爲 “網際套接字地址結構”,以 sockaddr_in 命名,16個字節。定義在 <netinet/in.h> 中。
struct sockaddr_in {
sa_family_t sin_family; /* AF_INET */
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
sizeof (sa_family_t) -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
typedef uint32_t in_addr_t;
struct in_addr {
in_addr_t s_addr; /* IPv4 address */
};
- sin_family(協議簇)、sin_port(端口號)、 sin_addr(IP地址)三個字段是 POSIX 規範規定必須有的。
- IPv4 地址、 TCP 或 UDP 端口號在套接字地址結構中總是以網絡字節序來存儲的,注意轉換。
- 32 位 IPv4 地址存在兩種不同的訪問方式:sockaddr_in.sin_addr ,sockaddr_in.sin_addr.s_addr。
IPv6 套接字地址結構
IPv6 套接字地址結構以 sockaddr_in6 命名,28個字節。在 <netinet/in.h> 中定義。
struct sockaddr_in6 {
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 */
};
struct in6_addr {
union {
uint8_t u6_addr8[16];
uint16_t u6_addr16[8];
uint32_t u6_addr32[4];
} in6_u;
#define s6_addr in6_u.u6_addr8
#define s6_addr16 in6_u.u6_addr16
#define s6_addr32 in6_u.u6_addr32
};
- IPv6 協議簇爲 AF_INET6,IPv4 協議簇爲 AF_INET。
- 結構體中的先後順序是精心編排的,如果 sockaddr_in6 結構本身是 64 位對齊,則 128 位的 sin6_addr 也是 64 位對齊的。
- sin_flowinfo 字段被分爲兩個字段。高 12 位保留,低 20 位是流標。
- 對於具備範圍的地址,sin6_scope_id 字段標識其範圍。
通用套接字1地址結構
套接字函數傳遞套接字地址結構總是以通用套接字地址結構指針形式來傳遞,支持任何協議簇的套接字地址結構。
通用套接字地址結構,以sockaddr命名,16個字節,定義在<sys/socket.h> 中。
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
通用套接字2地址結構
適應多種套接字地址結構,定義了新的通用套接字地址結構,克服了現有 struct sockaddr 的一些缺點。新的通用套接字地址結構 sockaddr_storage 在 <netinet/in.h> 中定義。
/* Structure large enough to hold any socket address
(with the historical exception of AF_UNIX). 128 bytes reserved. */
#if ULONG_MAX > 0xffffffff
# define __ss_aligntype __uint64_t
#else
# define __ss_aligntype __uint32_t
#endif
#define _SS_SIZE 128
#define _SS_PADSIZE (_SS_SIZE - (2 * sizeof (__ss_aligntype)))
struct sockaddr_storage
{
sa_family_t ss_family; /* Address family */
__ss_aligntype __ss_align; /* Force desired alignment. */
char __ss_padding[_SS_PADSIZE];
};
- 能夠滿足苛刻的對齊要求。
- 足夠大,能夠容納系統支持的任何套接字地址結構。
- 結構中的其他字段對用戶透明。
套接字地址結構比較
五種套接字地址結構的比較:
IPv4 與IPv6 套接字地址結構是固定長度的, Unix 域結構和數據鏈路結構是可變長度的。爲處理長度可變的結構,把指向某個套接字地址結構的指針作爲一個參數傳遞給某個套接字函數時,也把該結構的長度作爲另外一個參數傳遞給該函數。
IPv4/IPv6混合編程示例
u_short port=x; // 端口號
char* ip="xxxxx"; // IP地址
struct sockaddr_storage addr;
memset(&addr, 0, sizeof(struct sockaddr_storage));
if (isIPv6 == TRUE) // 自行判斷IP地址類型
{
struct sockaddr_in6 *addr_v6 = (struct sockaddr_in6 *)&addr;
addr_v6->sin6_family = AF_INET6;
addr_v6->sin6_port = htons(port);
inet_pton(AF_INET6, ip, &(addr_v6->sin6_addr));
}
else
{
struct sockaddr_in *addr_v4 = (struct sockaddr_in *)&addr;
addr_v4->sin_family = AF_INET;
addr_v4->sin_port = htons(port);
inet_pton(AF_INET, ip, &(addr_v6->sin6_addr));
}
sendto(sock, buf, len, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_storage));
更加節省空間的方案
sockaddr_storage是能夠保存所有sockaddr下屬的類型,但是128字節的大小有時候有點不可接受,而且每次使用都需要做類型轉換。下面提供一個更加優雅的方案,大小是28字節,節省了很多。
union sockaddr_union {
struct sockaddr sa;
struct sockaddr_in in;
struct sockaddr_in6 in6;
} m_addr;
if (AF_INET == m_addr.sa.sa_family) {
return ntohs(m_addr.in.sin_port);
} else if (AF_INET6 == m_addr.sa.sa_family) {
return ntohs(m_addr.in6.sin6_port);
}