跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C

Linux Netlink Socket 原理与实战:全面对比 TCP 通信

Linux Netlink Socket 是内核与用户空间进程间通信的 IPC 机制,支持双向通信和多播。 Netlink 的核心特点、常见协议类型及发送消息的完整步骤,包括创建 Socket、绑定地址、构造消息、指定目标及接收回复。通过与 TCP Socket 的对比,阐述了两者在通信对象、协议栈、连接语义等方面的差异,并给出了适用场景建议,帮助开发者根据需求选择合适的通信方式。

黑客发布于 2026/3/16更新于 2026/10/25103 浏览

Linux Netlink Socket 原理与实战:全面对比 TCP 通信

引言:当内核需要和你'私聊'

想象一下这样的场景:你的应用程序正在运行,突然内核发现网卡被拔掉了——这时候内核需要立刻通知你的程序。在 Linux 世界里,这种内核与用户态程序之间的'私聊'需求非常普遍:路由表变化、IP 地址变更、新设备插入…这些事件都需要一种高效的通信机制。

你可能会问:为什么不能用我们熟悉的 TCP socket?毕竟它已经这么成熟了。答案是:TCP 是为跨机器通信设计的,而我们需要的是本机内部内核与用户程序的对话。

这就是 Netlink 登场的地方。

一、Netlink 是什么?

Netlink 是 Linux 内核提供的一种特殊的进程间通信(IPC)机制,专门用于内核与用户空间进程之间的双向通信。它从 Linux 2.2 版本开始引入,现在已经成为了内核与用户态通信的事实标准。

Netlink 的核心特点
  • 基于 socket API:使用标准的 socket 接口(socket、bind、sendmsg、recvmsg),开发者无需学习全新的 API
  • 全双工通信:不仅用户程序可以主动发消息给内核,内核也可以主动'推送'消息给用户程序
  • 支持多播:一个消息可以同时发送给多个接收者,非常适合事件通知场景
  • 异步处理:消息有队列缓冲,不会阻塞发送方
常见的 Netlink 协议类型

Netlink 不是一个单一的协议,而是一个协议家族。通过 socket() 的第三个参数指定具体的协议类型:

协议类型用途
NETLINK_ROUTE路由、链路、地址等网络配置(最常用)
NETLINK_FIREWALL防火墙规则管理
NETLINK_NFLOGNetfilter 日志(iptables/UFW 的后端)
NETLINK_KOBJECT_UEVENT内核热插拔事件(udev 就是用它)
NETLINK_GENERIC通用 Netlink,用于扩展自定义协议
NETLINK_AUDIT审计子系统

实际上,你在终端里用的 ip 命令,底层就是通过 NETLINK_ROUTE 与内核通信的。

二、如何使用 Netlink 发送消息(实战篇)

让我们一步步实现一个 Netlink 通信程序。虽然最终目标是发送消息,但 Netlink 的流程比 TCP 多几个关键步骤。

步骤 1:创建 Socket
#include <sys/socket.h>
#include <linux/netlink.h>

 sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
 (sock_fd < ) {
    perror();
     ;
}
int
if
0
"socket"
return
-1

参数解读:

  • AF_NETLINK:地址族,告诉内核这是 Netlink 通信
  • SOCK_RAW:Netlink 只有这种类型(或者 SOCK_DGRAM,效果相同)
  • NETLINK_ROUTE:我们要与内核的路由子系统对话
步骤 2:Bind(绑定本地地址)

这一步可能让你困惑:本机通信为什么还要 bind?因为 Netlink 需要给每个 socket 一个唯一的'地址',这样内核才能把消息正确地投递给你。

struct sockaddr_nl local_addr;
memset(&local_addr, 0, sizeof(local_addr));
local_addr.nl_family = AF_NETLINK;
local_addr.nl_pid = getpid(); // 用自己的 PID 作为唯一标识
local_addr.nl_groups = 0; // 不加入多播组

if (bind(sock_fd, (struct sockaddr*)&local_addr, sizeof(local_addr)) < 0) {
    perror("bind");
    close(sock_fd);
    return -1;
}

关键点:

  • nl_pid:不一定是进程 PID,但必须是唯一的 32 位整数。通常单线程程序用 getpid() 就足够了;如果同一进程有多个 Netlink socket,可以用 pthread_self() << 16 | getpid() 生成
  • nl_groups:如果想接收多播消息(比如路由变化事件),设置对应的位掩码;否则填 0
步骤 3:构造 Netlink 消息

Netlink 消息有固定的格式,每个消息都必须包含一个头部(struct nlmsghdr):

struct nlmsghdr {
    __u32 nlmsg_len;      /* 消息总长度(包括头部) */
    __u16 nlmsg_type;     /* 消息类型(自定义或标准类型) */
    __u16 nlmsg_flags;    /* 标志(如 NLM_F_REQUEST 表示请求) */
    __u32 nlmsg_seq;      /* 序列号(用于匹配请求和响应) */
    __u32 nlmsg_pid;      /* 发送方端口 ID(我们 bind 时设的 pid) */
};

构造消息的代码示例:

#define MAX_PAYLOAD 1024
char buffer[NLMSG_SPACE(MAX_PAYLOAD)];
struct nlmsghdr* nlh = (struct nlmsghdr*)buffer;
nlh->nlmsg_len = NLMSG_LENGTH(MAX_PAYLOAD);
nlh->nlmsg_type = 1; // 自定义消息类型
nlh->nlmsg_flags = NLM_F_REQUEST; // 这是一个请求
nlh->nlmsg_seq = 1; // 序列号
nlh->nlmsg_pid = getpid(); // 发送方 PID

// 填充负载数据
char* payload = NLMSG_DATA(nlh);
strcpy(payload, "Hello Kernel!");

这里有几个宏需要理解:

  • NLMSG_SPACE(len):计算给定负载长度所需的缓冲区总大小(包括对齐)
  • NLMSG_LENGTH(len):计算消息总长度(不包括对齐填充)
  • NLMSG_DATA(nlh):获取负载部分的指针
步骤 4:指定目标地址并发送

我们要发给内核,所以目标地址的 nl_pid 设为 0(内核的'端口号'):

struct sockaddr_nl kernel_addr;
memset(&kernel_addr, 0, sizeof(kernel_addr));
kernel_addr.nl_family = AF_NETLINK;
kernel_addr.nl_pid = 0; // 发给内核
kernel_addr.nl_groups = 0;

struct iovec iov;
iov.iov_base = (void*)nlh;
iov.iov_len = nlh->nlmsg_len;

struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_name = (void*)&kernel_addr;
msg.msg_namelen = sizeof(kernel_addr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

int ret = sendmsg(sock_fd, &msg, 0);
if (ret < 0) {
    perror("sendmsg");
}

这里使用 sendmsg 而不是 sendto,因为 Netlink 消息需要同时传递目标地址和消息体(通过 iovec 结构)。

步骤 5:接收回复

发送请求后,内核会回复一个或多个消息。需要循环接收直到收到结束标记:

char recv_buf[8192];
struct sockaddr_nl src_addr;
socklen_t addr_len = sizeof(src_addr);

int len = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&src_addr, &addr_len);
struct nlmsghdr* nlh_reply = (struct nlmsghdr*)recv_buf;

while (NLMSG_OK(nlh_reply, len)) {
    if (nlh_reply->nlmsg_type == NLMSG_DONE) {
        break; // 多部分消息结束
    }
    if (nlh_reply->nlmsg_type == NLMSG_ERROR) {
        struct nlmsgerr* err = (struct nlmsgerr*)NLMSG_DATA(nlh_reply);
        printf("错误码:%d\n", err->error);
        break;
    }
    // 处理数据...
    printf("收到回复:%s\n", (char*)NLMSG_DATA(nlh_reply));
    nlh_reply = NLMSG_NEXT(nlh_reply, len);
}

宏 NLMSG_OK 用于遍历可能的多条消息,它会检查消息长度和对齐。

完整示例:获取路由表信息

下面是一个完整的例子,用 Netlink 获取系统的路由表信息:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>

int main() {
    int sock_fd;
    struct sockaddr_nl local_addr, kernel_addr;
    struct nlmsghdr* nlh;
    struct msghdr msg;
    struct iovec iov;
    char buf[8192];

    // 创建 socket
    sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);

    // bind 本地地址
    memset(&local_addr, 0, sizeof(local_addr));
    local_addr.nl_family = AF_NETLINK;
    local_addr.nl_pid = getpid();
    bind(sock_fd, (struct sockaddr*)&local_addr, sizeof(local_addr));

    // 构造请求消息(获取路由表)
    nlh = (struct nlmsghdr*)buf;
    nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
    nlh->nlmsg_type = RTM_GETROUTE; // 获取路由表
    nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
    nlh->nlmsg_seq = 1;
    nlh->nlmsg_pid = getpid();

    // 设置目标地址(内核)
    memset(&kernel_addr, 0, sizeof(kernel_addr));
    kernel_addr.nl_family = AF_NETLINK;
    kernel_addr.nl_pid = 0;

    // 发送
    iov.iov_base = (void*)nlh;
    iov.iov_len = nlh->nlmsg_len;
    memset(&msg, 0, sizeof(msg));
    msg.msg_name = (void*)&kernel_addr;
    msg.msg_namelen = sizeof(kernel_addr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    sendmsg(sock_fd, &msg, 0);

    // 接收回复
    int len;
    while ((len = recv(sock_fd, buf, sizeof(buf), 0)) > 0) {
        struct nlmsghdr* nlp = (struct nlmsghdr*)buf;
        for (; NLMSG_OK(nlp, len); nlp = NLMSG_NEXT(nlp, len)) {
            if (nlp->nlmsg_type == NLMSG_DONE) {
                goto done;
            }
            if (nlp->nlmsg_type == RTM_NEWROUTE) {
                printf("收到一条路由条目\n");
                // 这里可以解析具体的路由信息
            }
        }
    }

done:
    close(sock_fd);
    return 0;
}

三、Netlink vs TCP:殊途不同归

现在我们把 Netlink 和我们最熟悉的 TCP socket 放在一起比较。虽然它们都用 socket API,但设计哲学完全不同。

对比表格
对比维度Netlink SocketTCP Socket
通信对象本机内核 或 本机其他进程远程主机 上的进程
地址族AF_NETLINKAF_INET 或 AF_INET6
协议类型NETLINK_ROUTE、NETLINK_GENERIC 等IPPROTO_TCP
套接字类型SOCK_RAW 或 SOCK_DGRAMSOCK_STREAM
通信模式消息边界(datagram)字节流(stream)
可靠性不可靠(但内核队列一般不会丢)可靠(ACK、重传、顺序保证)
流量控制简单的队列机制滑动窗口、拥塞控制
连接概念无连接(但可用 connect 绑定默认对端)面向连接(三次握手)
多播支持原生支持,一个消息可发到多播组不支持(需上层应用实现)
双向性全双工,内核可主动发起通信全双工,但只能由客户端发起连接
性能开销低(无需网络协议栈)较高(协议栈处理、数据拷贝)
使用场景网络配置、设备监控、内核事件通知Web 服务、文件传输、远程访问
核心差异详解
1. 通信对象和范围

TCP 设计的初衷是跨越网络边界,让不同机器上的进程可以通信。它要处理复杂的网络环境:丢包、乱序、拥塞、MTU 分片等等。

Netlink 则完全不需要操心这些——它的通信范围仅限于本机。消息从用户态进程发出,直接进入内核的消息队列,没有网络层的参与。

2. 协议栈的差异

当你用 TCP 发送 'Hello':

用户程序 → socket 缓冲区 → TCP 层(加 TCP 头)→ IP 层(加 IP 头)→ 链路层(加 MAC 头)→ 网卡 → 网络 

这一路下来,数据被层层封装,经历多次拷贝和校验。

而 Netlink 的路径简单得多:

用户程序 → 构造 Netlink 消息 → socket 缓冲区 → 内核 Netlink 核心 → 目标内核模块 

没有协议头的层层封装,没有网卡的参与,效率自然更高。

3. 连接的语义

TCP 是面向连接的。在通信之前,必须通过三次握手建立连接,通信结束后还要四次挥手断开连接。连接是 TCP 可靠性的基础。

Netlink 是无连接的。你随时可以发送消息给内核(nl_pid=0)或其他进程,不需要事先建立连接。当然,如果你只想和一个对端通信,可以用 connect() 绑定默认地址,之后直接用 send() 而不用每次都指定目标。

4. 谁可以主动发起通信?

这是 Netlink 最革命性的设计。

在 TCP 世界里,服务器可以被动接受连接,但主动发起数据推送的永远是客户端(除非客户端先请求)。但在系统管理场景中,内核经常需要主动通知用户程序:网线掉了、新设备插入了、路由变了…

Netlink 完美解决了这个问题:内核可以在任何时间向绑定了特定多播组的用户程序发送消息。这就是所谓的全双工通信——双方都可以随时发起对话。

5. 消息边界 vs 字节流

TCP 是字节流协议,它不保留消息边界。如果你连续发送两个 'hello',接收方可能一次收到 'hellohello',也可能分多次收到。应用层需要自己设计消息边界(如加长度头、特殊分隔符)。

Netlink 是消息协议,每条 sendmsg 发送的数据对应一个完整的 Netlink 消息,接收方 recvmsg 一次正好拿到一条消息(如果缓冲区够大)。这对应用层开发来说省心不少。

四、什么时候用 Netlink,什么时候用 TCP?

用 Netlink 的场景
  1. 网络配置管理:你想实现一个类似 ip 命令的工具,修改 IP 地址、路由表
  2. 监控网络事件:监听网卡状态变化、路由更新
  3. 与内核模块通信:你写了一个内核模块,需要和用户态程序交换数据
  4. 获取系统信息:获取链路状态、ARP 表、邻居信息等
  5. 设备热插拔监控:监听 USB、SD 卡等设备的插拔事件
用 TCP 的场景
  1. Web 服务器/客户端:HTTP 协议基于 TCP
  2. 数据库连接:MySQL、PostgreSQL 都使用 TCP
  3. 远程登录:SSH、Telnet
  4. 文件传输:FTP、SCP
  5. 任何需要跨机器通信的场景

五、总结:殊途同归的 socket API

Netlink 和 TCP 共用同一套 socket API,这是 Linux 设计哲学的美妙之处——统一的接口,多样的实现。

  • 创建:都是 socket(),只是参数不同
  • 绑定:都是 bind(),只是地址结构不同
  • 发送:都是 sendmsg()/sendto(),只是目标地址含义不同
  • 接收:都是 recvmsg()/recvfrom()

但背后的机制天差地别:

  • TCP 是网络通信协议,用于跨机器
  • Netlink 是内核 IPC 机制,用于本机内核 - 用户通信

目录

  1. Linux Netlink Socket 原理与实战:全面对比 TCP 通信
  2. 引言:当内核需要和你“私聊”
  3. 一、Netlink 是什么?
  4. Netlink 的核心特点
  5. 常见的 Netlink 协议类型
  6. 二、如何使用 Netlink 发送消息(实战篇)
  7. 步骤 1:创建 Socket
  8. 步骤 2:Bind(绑定本地地址)
  9. 步骤 3:构造 Netlink 消息
  10. 步骤 4:指定目标地址并发送
  11. 步骤 5:接收回复
  12. 完整示例:获取路由表信息
  13. 三、Netlink vs TCP:殊途不同归
  14. 对比表格
  15. 核心差异详解
  16. 1. 通信对象和范围
  17. 2. 协议栈的差异
  18. 3. 连接的语义
  19. 4. 谁可以主动发起通信?
  20. 5. 消息边界 vs 字节流
  21. 四、什么时候用 Netlink,什么时候用 TCP?
  22. 用 Netlink 的场景
  23. 用 TCP 的场景
  24. 五、总结:殊途同归的 socket API

更多推荐文章

查看全部
  • Claude Code 模型配置详解
  • Python Flask 企业合同管理系统技术要点与选型对比
  • WSL2 + Ubuntu 24.04 + Docker Desktop 本地开发环境搭建
  • rrweb snapshot 深度解密:DOM 序列化与重建技术
  • 前端实现 PC 网站微信扫码授权登录
  • 自然语言处理在教育领域的实战应用
  • 多模态大模型评测框架 LMMs-Eval 详解
  • 基于 YOLOv8 的无人机枸杞树病害检测数据集与训练方案
  • Ubuntu 22.04 安装与配置 ROS2 Humble 详解
  • 前端 Word 文档生成实战:DOCX.js 完整使用指南
  • Flutter 开发入门与原理详解
  • Whisper v0.2 本地语音转文字工具安装与使用指南
  • Spring Cloud 熔断降级详解:Sentinel 实战与原理类比
  • Codex 与 Copilot:AI 编程助手的本质与区别
  • AIGC 内容安全:cv_resnet50 人脸重建嵌入 Deepfake 检测预处理
  • Spring Boot 核心原理与面试高频考点解析
  • LLM 大模型职业方向解析与新人入行实战指南
  • npm 安装 OpenClaw 遇到 Git 错误及权限问题处理
  • iPad 端 Obsidian 结合 Gitee 实现 Git 同步实战
  • 华为 OD 机试:分割均衡字符串

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online