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


struct sockaddr_in


2. in 的含义


3. 总结

如果写一个 tcp 服务器程序怎么兼容 ipv4 和 ipv6

这是网络编程中非常常见的问题。要让你的 TCP 服务器同时兼容 IPv4 和 IPv6,有两种主流方法:


方法一:分别监听 IPv4 和 IPv6

  1. 创建两个 socket,一个 AF_INET(IPv4),一个 AF_INET6(IPv6)。
  2. 分别 bind() 到对应的地址(如 INADDR_ANYin6addr_any),然后都 listen()
  3. select()/poll()/epoll() 等等待两个 socket 的连接。

优点:最通用,所有平台都支持。
缺点:代码略复杂,需要管理两个 socket。


方法二:只用 IPv6 socket,开启双栈(推荐)

  1. 只创建一个 AF_INET6 socket

  2. bind()in6addr_any(即 ::,所有 IPv6 地址)。

  3. 关闭 IPV6_V6ONLY 选项(默认通常是关闭的),这样该 socket 既能接受 IPv6 也能接受 IPv4 的连接(IPv4-mapped IPv6 address)。

    int off = 0;
    setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off));
    
  4. 这样,既能接受 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;
}

关键点说明


参考

上一篇 下一篇

猜你喜欢

热点阅读