大家好,我是杂烩君。
你的设备在现场突然断网了,程序却浑然不知,还在疯狂往服务器发数据——这种场景你遇到过吗?
嵌入式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 的完整取值有:up、down、unknown、dormant、lowerlayerdown 等,实际使用时建议都做处理,本例只判断 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但实际不通"的情况坑到。
以上就是本次的分享,如果对你有帮助,欢迎点赞、在看、转发三连支持!
226