// 英特網(wǎng)地址是由IP地址和端口號(hào)唯一定義
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
// 專門用于 IPv4(Internet Protocol version 4)協(xié)議的套接字地址結(jié)構(gòu)體
struct sockaddr_in {
sa_family_t sin_family; // 地址族,必須為 AF_INET
__be16 sin_port; // 端口號(hào)(網(wǎng)絡(luò)字節(jié)序)
struct in_addr sin_addr; // IPv4 地址
unsigned char sin_zero[8];// 填充字節(jié),保持與 struct sockaddr 大小一致
};
// 用于 UNIX 域套接字
struct sockaddr_un {
__kernel_sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
struct sockaddr_in6 {
unsigned short int sin6_family; /* AF_INET6 */
__be16 sin6_port; /* Transport layer port # */
__be32 sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
__u32 sin6_scope_id; /* scope id (new in RFC2553) */
};
// include/uapi/linux/socket.h
typedef __kernel_sa_family_t sa_family_t;
typedef unsigned short __kernel_sa_family_t;
struct sockaddr_in 與 struct sockaddr 的區(qū)別, 其中 in 代表的含義是什么?
1. 區(qū)別
struct sockaddr
作用:通用的套接字地址結(jié)構(gòu)體,作為所有協(xié)議族地址結(jié)構(gòu)體的“基類”。
-
定義(見
include/linux/socket.h或include/uapi/linux/socket.h):struct sockaddr { sa_family_t sa_family; // 地址族(如 AF_INET、AF_INET6、AF_UNIX 等) char sa_data[14]; // 協(xié)議地址數(shù)據(jù)(內(nèi)容由具體協(xié)議解釋) }; -
特點(diǎn):
- 僅用于“通用”接口(如 bind、connect、accept 等),實(shí)際使用時(shí)通常需要強(qiáng)制類型轉(zhuǎn)換為具體協(xié)議的地址結(jié)構(gòu)體。
- 不能直接表達(dá)具體協(xié)議的所有字段。
struct sockaddr_in
作用:專門用于 IPv4(Internet Protocol version 4)協(xié)議的套接字地址結(jié)構(gòu)體。
-
定義(見
include/uapi/linux/in.h):struct sockaddr_in { sa_family_t sin_family; // 地址族,必須為 AF_INET __be16 sin_port; // 端口號(hào)(網(wǎng)絡(luò)字節(jié)序) struct in_addr sin_addr; // IPv4 地址 unsigned char sin_zero[8];// 填充字節(jié),保持與 struct sockaddr 大小一致 }; -
特點(diǎn):
- 明確表達(dá)了 IPv4 的端口和地址字段。
- 只能用于 IPv4 協(xié)議相關(guān)的 socket 操作。
2. in 的含義
-
in是 Internet 的縮寫,表示該結(jié)構(gòu)體用于 Internet 協(xié)議族(即 IPv4)。 - 你還會(huì)看到類似的命名:
-
sockaddr_in6:用于 IPv6(Internet Protocol version 6) -
sockaddr_un:用于 UNIX 域套接字(UNIX domain)
-
3. 總結(jié)
-
struct sockaddr是所有協(xié)議族的通用地址結(jié)構(gòu)體,實(shí)際用時(shí)需強(qiáng)轉(zhuǎn)為具體協(xié)議的結(jié)構(gòu)體。 -
struct sockaddr_in是專門為 IPv4 設(shè)計(jì)的,in代表 Internet(即 IPv4)。 - 你在寫 socket 程序時(shí),通常會(huì)用
struct sockaddr_in填寫地址,然后通過(struct sockaddr *)強(qiáng)轉(zhuǎn)傳遞給 socket API。
如果寫一個(gè) tcp 服務(wù)器程序怎么兼容 ipv4 和 ipv6
這是網(wǎng)絡(luò)編程中非常常見的問題。要讓你的 TCP 服務(wù)器同時(shí)兼容 IPv4 和 IPv6,有兩種主流方法:
方法一:分別監(jiān)聽 IPv4 和 IPv6
-
創(chuàng)建兩個(gè) socket,一個(gè)
AF_INET(IPv4),一個(gè)AF_INET6(IPv6)。 - 分別
bind()到對(duì)應(yīng)的地址(如INADDR_ANY和in6addr_any),然后都listen()。 - 用
select()/poll()/epoll()等等待兩個(gè) socket 的連接。
優(yōu)點(diǎn):最通用,所有平臺(tái)都支持。
缺點(diǎn):代碼略復(fù)雜,需要管理兩個(gè) socket。
方法二:只用 IPv6 socket,開啟雙棧(推薦)
只創(chuàng)建一個(gè)
AF_INET6socket。bind()到in6addr_any(即::,所有 IPv6 地址)。-
關(guān)閉 IPV6_V6ONLY 選項(xiàng)(默認(rèn)通常是關(guān)閉的),這樣該 socket 既能接受 IPv6 也能接受 IPv4 的連接(IPv4-mapped IPv6 address)。
int off = 0; setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off)); 這樣,既能接受 IPv6 客戶端,也能接受 IPv4 客戶端(以 IPv4-mapped IPv6 地址的形式)。
優(yōu)點(diǎn):代碼簡(jiǎn)單,只需管理一個(gè) socket。
缺點(diǎn):部分老系統(tǒng)或特殊配置下可能不支持雙棧。
代碼示例(方法二,推薦)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main() {
int sockfd = socket(AF_INET6, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket");
exit(1);
}
// 關(guān)閉 IPV6_V6ONLY,允許 IPv4-mapped IPv6 地址
int off = 0;
if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off)) < 0) {
perror("setsockopt");
// 不是致命錯(cuò)誤,可以繼續(xù)
}
struct sockaddr_in6 addr;
memset(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_addr = in6addr_any; // 監(jiān)聽所有 IPv6 地址(包括 IPv4-mapped)
addr.sin6_port = htons(12345);
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
exit(1);
}
if (listen(sockfd, 10) < 0) {
perror("listen");
exit(1);
}
printf("Listening on port 12345 (IPv4 & IPv6)...\n");
while (1) {
struct sockaddr_storage client_addr;
socklen_t client_len = sizeof(client_addr);
int clientfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
if (clientfd < 0) {
perror("accept");
continue;
}
char host[NI_MAXHOST], serv[NI_MAXSERV];
getnameinfo((struct sockaddr *)&client_addr, client_len,
host, sizeof(host), serv, sizeof(serv),
NI_NUMERICHOST | NI_NUMERICSERV);
printf("Connection from %s:%s\n", host, serv);
// ... 處理客戶端 ...
close(clientfd);
}
close(sockfd);
return 0;
}
關(guān)鍵點(diǎn)說明
-
AF_INET6socket +in6addr_any+ 關(guān)閉IPV6_V6ONLY,即可同時(shí)接受 IPv4 和 IPv6。 -
accept()返回的地址類型可能是 IPv4-mapped IPv6(::ffff:a.b.c.d),需要用getnameinfo()或手動(dòng)判斷。 - 某些系統(tǒng)(如部分 BSD)默認(rèn)
IPV6_V6ONLY是開啟的,需要手動(dòng)關(guān)閉。