扫码加入

  • 正文
  • 相关推荐
申请入驻 产业图谱

设备断网了?嵌入式网络状态检测咋整?

6小时前
226
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

大家好,我是杂烩君。

你的设备在现场突然断网了,程序却浑然不知,还在疯狂往服务器发数据——这种场景你遇到过吗?

嵌入式Linux开发中,网络状态检测是一个很常见的需求。但很多人只会用一种方式去检测,踩了不少坑。今天我们来聊聊三种方案,帮你根据实际场景选出最合适的方式。

在开始之前,有一个关键概念需要先理清:链路层状态网络层可达性是两回事。

链路层状态:网线是否插好、Wi-Fi是否已关联AP——反映的是物理连接。

网络层可达性:能不能真正访问到外部服务器——反映的是端到端的通信能力。

下面这张图可以帮你直观理解两者的关系:

链路层 up ≠ 能上网。 网线插着但DHCP失败,链路层是"up",但你依然上不了网。需要搞清楚这个区别。

方法一:Socket连接探测

我们使用socket连接探测,检测网络的可达性。

最直观的思路:创建一个socket client,定时尝试连接一个已知的公网服务器。连得上就说明网络通,连不上就说明断网了。

这里我们选择连接 114.114.114.114(国内常用的公共DNS服务器)的80端口:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

staticintcheck_net_status(void)
{
    int ret = -1;

    int sock_cli = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_cli < 0)
    {
        perror("socket");
        return-1;
    }

    /* 设置连接超时为3秒,避免默认超时过长导致阻塞 */
    structtimevaltimeout = {3, 0};
    setsockopt(sock_cli, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));

    structsockaddr_inservaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(80);
    servaddr.sin_addr.s_addr = inet_addr("114.114.114.114");

    if (connect(sock_cli, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
    {
        perror("connect");
    }
    else
    {
        ret = 0;
    }

    close(sock_cli);
    return ret;
}

有个细节值得注意:

一定要设超时

connect() 默认超时非常长(可达75秒以上),网络一断你的程序就在那干等。这里通过 SO_SNDTIMEO 设为3秒,断网后最多3秒就能感知。

适用场景:需要确认"设备到底能不能访问外网"——比如OTA升级前的网络检查。

局限性:依赖外部服务器,有秒级延迟,不适合对实时性要求高的场景。

方法二:读取sysfs文件

我们可以读取读取sysfs文件,检测链路层状态。

Linux内核通过 /sys/class/net/ 目录暴露了网络接口的运行状态,简单来说就是内核替你盯着网卡,并把状态实时写到文件里。我们直接读取就能知道链路是否连通。

检测无线网络

/sys/class/net/wlan0/operstate

检测有线网络:

/sys/class/net/eth0/operstate

命令行一读便知,返回 up 表示连接,down 表示断开:

operstate 的完整取值有:updownunknowndormantlowerlayerdown 等,实际使用时建议都做处理,本例只判断 up/down 两种。

代码实现如下:

#include<stdio.h>
#include<string.h>
#include<unistd.h>

typedefenum
{
    NET_DISCONNECT = -1,
    NET_UNKNOWN    =  0,
    NET_CONNECT    =  1
} net_conn_status_e;

static net_conn_status_e check_net_status(constchar *iface)
{
    char path[128];
    char state[32] = {0};

    snprintf(path, sizeof(path), "/sys/class/net/%s/operstate", iface);

    FILE *fp = fopen(path, "r");
    if (fp == NULL)
    {
        perror("fopen");
        return NET_UNKNOWN;
    }

    if (fscanf(fp, "%31s", state) != 1)
    {
        fclose(fp);
        return NET_UNKNOWN;
    }
    fclose(fp);

    if (strcmp(state, "up") == 0)
        return NET_CONNECT;
    elseif (strcmp(state, "down") == 0)
        return NET_DISCONNECT;

    return NET_UNKNOWN;
}

intmain(int argc, char **argv)
{
    constchar *iface = (argc > 1) ? argv[1] : "wlan0";

    while (1)
    {
        net_conn_status_e status = check_net_status(iface);

        switch (status)
        {
        case NET_CONNECT:
            printf("[%s] net connectn", iface);
            break;
        case NET_DISCONNECT:
            printf("[%s] net disconnectn", iface);
            break;
        default:
            printf("[%s] net unknownn", iface);
            break;
        }

        usleep(500 * 1000);
    }

    return0;
}

几个要点说明:

**直接用 fopen 而不是 popen("cat ...")**:读一个文件而去fork一个进程跑cat,就像用大卡车送一封信——fopen 更轻量,也更适合嵌入式场景。

需要 NET_UNKNOWN 状态:因为 operstate 不只有 up/down 两种值,不处理就会出现"明明有问题但程序啥也没报"的情况。

运行:

适用场景:判断网口物理连接状态,适合大部分嵌入式应用。

局限性:只反映链路层状态,不代表网络真的通。

方法三:Netlink监听

前面两种方法都是"你去问",这种方法是"它来告诉你"。Netlink socket 让程序订阅内核的网络事件,链路状态一变化,程序立刻收到通知——零延迟、零轮询。

下面这张图展示了三种方法的工作模式区别:

代码实现如下:

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

typedefstruct
{
    int  sock;
} netlink_monitor_t;

typedefvoid(*link_event_cb)(constchar *ifname, int is_up, void *user_data);

staticintnetlink_monitor_init(netlink_monitor_t *mon)
{
    mon->sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    if (mon->sock < 0)
    {
        perror("socket");
        return-1;
    }

    structsockaddr_nladdr;
    memset(&addr, 0, sizeof(addr));
    addr.nl_family = AF_NETLINK;
    addr.nl_groups = RTMGRP_LINK;

    if (bind(mon->sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        perror("bind");
        close(mon->sock);
        mon->sock = -1;
        return-1;
    }

    return0;
}

staticintnetlink_monitor_check(netlink_monitor_t *mon, link_event_cb cb, void *user_data)
{
    char buf[4096];

    int len = recv(mon->sock, buf, sizeof(buf), 0);
    if (len < 0)
    {
        perror("recv");
        return-1;
    }

    structnlmsghdr *nh;
    for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len))
    {
        if (nh->nlmsg_type == RTM_NEWLINK)
        {
            structifinfomsg *ifi = NLMSG_DATA(nh);
            char ifname[IF_NAMESIZE];
            if_indextoname(ifi->ifi_index, ifname);

            int is_up = (ifi->ifi_flags & IFF_RUNNING) ? 1 : 0;

            if (cb)
                cb(ifname, is_up, user_data);
        }
    }

    return0;
}

staticvoidnetlink_monitor_deinit(netlink_monitor_t *mon)
{
    if (mon->sock >= 0)
    {
        close(mon->sock);
        mon->sock = -1;
    }
}

staticvoidon_link_event(constchar *ifname, int is_up, void *user_data)
{
    (void)user_data;
    printf("[%s] link %sn", ifname, is_up ? "up" : "down");
}

intmain(void)
{
    netlink_monitor_t mon;

    if (netlink_monitor_init(&mon) < 0)
        return-1;

    printf("Listening for network link events...n");

    while (1)
    {
        if (netlink_monitor_check(&mon, on_link_event, NULL) < 0)
            break;
    }

    netlink_monitor_deinit(&mon);
    return0;
}

核心要点:

**netlink_monitor_init**:创建 NETLINK_ROUTE 类型的socket,加入 RTMGRP_LINK 多播组——相当于订阅了"链路变化频道"。

**netlink_monitor_check**:接收一条内核消息并解析。内核在网络接口状态变化时会推送 RTM_NEWLINK 消息,通过 IFF_RUNNING 标志判断链路是否真正可用(比单纯看 IFF_UP 更准确——IFF_UP 只表示接口被激活,IFF_RUNNING 才表示物理层真的通了)。

**netlink_monitor_deinit**:清理资源。

回调函数设计:收到事件后做什么由调用方决定(打日志、触发重连、上报云端...),监听层不用改。

适用场景:需要实时响应网络变化的生产环境,比如自动重连、状态上报、故障切换等。

注意:Netlink的消息解析相对复杂一些,但带来的收益是实时性和低开销。

组合使用才是最优解

在实际项目中,单一方案往往不够用,常常需要组合使用效果最好,如:

简单来说:用方法三 Netlink实时感知链路变化(网线拔插、Wi-Fi断连),这是"哨兵"。

链路恢复后,用方法一 Socket连接确认外网真的通了再执行业务逻辑,这是"确认哨"。

这样既有实时性,又能确保网络真正可用,不会被"链路up但实际不通"的情况坑到。

以上就是本次的分享,如果对你有帮助,欢迎点赞、在看、转发三连支持!

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录

本公众号专注于嵌入式技术,包括但不限于C/C++、嵌入式、物联网、Linux等编程学习笔记,同时,公众号内包含大量的学习资源。欢迎关注,一同交流学习,共同进步!