Linux socket 相关结构体
2025-07-05 本文已影响0人
Linux系统编程
// 英特网地址是由IP地址和端口号唯一定义
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)协议的套接字地址结构体
struct sockaddr_in {
sa_family_t sin_family; // 地址族,必须为 AF_INET
__be16 sin_port; // 端口号(网络字节序)
struct in_addr sin_addr; // IPv4 地址
unsigned char sin_zero[8];// 填充字节,保持与 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 的区别, 其中 in 代表的含义是什么?
1. 区别
struct sockaddr
-
作用:通用的套接字地址结构体,作为所有协议族地址结构体的“基类”。
-
定义(见
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]; // 协议地址数据(内容由具体协议解释) }; -
特点:
- 仅用于“通用”接口(如 bind、connect、accept 等),实际使用时通常需要强制类型转换为具体协议的地址结构体。
- 不能直接表达具体协议的所有字段。
struct sockaddr_in
-
作用:专门用于 IPv4(Internet Protocol version 4)协议的套接字地址结构体。
-
定义(见
include/uapi/linux/in.h):struct sockaddr_in { sa_family_t sin_family; // 地址族,必须为 AF_INET __be16 sin_port; // 端口号(网络字节序) struct in_addr sin_addr; // IPv4 地址 unsigned char sin_zero[8];// 填充字节,保持与 struct sockaddr 大小一致 }; -
特点:
- 明确表达了 IPv4 的端口和地址字段。
- 只能用于 IPv4 协议相关的 socket 操作。
2. in 的含义
-
in是 Internet 的缩写,表示该结构体用于 Internet 协议族(即 IPv4)。 - 你还会看到类似的命名:
-
sockaddr_in6:用于 IPv6(Internet Protocol version 6) -
sockaddr_un:用于 UNIX 域套接字(UNIX domain)
-
3. 总结
-
struct sockaddr是所有协议族的通用地址结构体,实际用时需强转为具体协议的结构体。 -
struct sockaddr_in是专门为 IPv4 设计的,in代表 Internet(即 IPv4)。 - 你在写 socket 程序时,通常会用
struct sockaddr_in填写地址,然后通过(struct sockaddr *)强转传递给 socket API。
如果写一个 tcp 服务器程序怎么兼容 ipv4 和 ipv6
这是网络编程中非常常见的问题。要让你的 TCP 服务器同时兼容 IPv4 和 IPv6,有两种主流方法:
方法一:分别监听 IPv4 和 IPv6
-
创建两个 socket,一个
AF_INET(IPv4),一个AF_INET6(IPv6)。 - 分别
bind()到对应的地址(如INADDR_ANY和in6addr_any),然后都listen()。 - 用
select()/poll()/epoll()等等待两个 socket 的连接。
优点:最通用,所有平台都支持。
缺点:代码略复杂,需要管理两个 socket。
方法二:只用 IPv6 socket,开启双栈(推荐)
-
只创建一个
AF_INET6socket。 -
bind()到in6addr_any(即::,所有 IPv6 地址)。 -
关闭 IPV6_V6ONLY 选项(默认通常是关闭的),这样该 socket 既能接受 IPv6 也能接受 IPv4 的连接(IPv4-mapped IPv6 address)。
int off = 0; setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off)); -
这样,既能接受 IPv6 客户端,也能接受 IPv4 客户端(以 IPv4-mapped IPv6 地址的形式)。
优点:代码简单,只需管理一个 socket。
缺点:部分老系统或特殊配置下可能不支持双栈。
代码示例(方法二,推荐)
#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);
}
// 关闭 IPV6_V6ONLY,允许 IPv4-mapped IPv6 地址
int off = 0;
if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off)) < 0) {
perror("setsockopt");
// 不是致命错误,可以继续
}
struct sockaddr_in6 addr;
memset(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_addr = in6addr_any; // 监听所有 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;
}
关键点说明
-
AF_INET6socket +in6addr_any+ 关闭IPV6_V6ONLY,即可同时接受 IPv4 和 IPv6。 -
accept()返回的地址类型可能是 IPv4-mapped IPv6(::ffff:a.b.c.d),需要用getnameinfo()或手动判断。 - 某些系统(如部分 BSD)默认
IPV6_V6ONLY是开启的,需要手动关闭。