Linux应用层如何监测网线的插拔状态

本文介绍两种在Linux下监控网线插拔状态的方法。方法一通过编写类似ifconfig的函数,利用ioctl系统调用获取网络接口的状态。方法二通过监听/sys/class/net/eth0/carrier的值变化,结合Netlink和RTMGRP_LINK事件实现网线状态的实时监测。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

方法一:

参考:https://blog.youkuaiyun.com/hbk320/article/details/47300067

由于linux下的ifconfig命令就能够实现在应用层监控网线插拔状态,例如当网线连接正常时,使用ifconfig eth0命令,打印的信息中会有RUNNING,而拔掉网线后,再使用ifconfig eth0命令,RUNNING就不见了。所以,实现Linux应用层监控网线插入状态就相当于自己写一个ifconfig函数。

#include <stdio.h>
#include <string.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/if.h>
#include <arpa/inet.h>


int net_get_gateway(char *gateway)
{
    FILE *fp = NULL;
    char buf[256];
    char cmd[64];
    char *tmp;

    strcpy(cmd, "ip route");
    fp = popen(cmd, "r");
    if(NULL == fp)
    {
        perror("popen error");
        return -1;
    }
    while(fgets(buf, sizeof(buf), fp) != NULL)
    {
        tmp = buf;
        // 例如: default via 192.168.2.1 dev eth0
        // 找到default的起始点
        while(*tmp && isspace(*tmp))
        {
            ++ tmp;
        }
        if(strncmp(tmp, "default", strlen("default")) == 0)
            break;
    }
    // %*s表示以空格为分隔符, 字符串将被忽略掉
    sscanf(buf, "%*s%*s%s", gateway);
    pclose(fp);

    return 0;
}

int net_detect(char* net_name)
{
    int skfd = 0;
    struct ifreq ifr;
    struct sockaddr_in *pAddr = NULL;

    skfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(skfd < 0)
    {
        printf("Open socket error\n");
        return -1;
    }

    // get ifr_flags
    strcpy(ifr.ifr_name, net_name);
    if(ioctl(skfd, SIOCGIFFLAGS, &ifr) < 0)
    {
        printf("SIOCGIFFLAGS IOCTL error, %s is not valid\n", ifr.ifr_name);
        close(skfd);
        return -1;
    }

    if(ifr.ifr_flags & IFF_RUNNING)
    {
        // 先判断running再判断up还是down
        // 因为即使拔掉网线, IFF_UP依然是up
        if(ifr.ifr_flags & IFF_UP)
        {
            printf("%s: is up\n", ifr.ifr_name);
        }
        printf("%s: is running\n", ifr.ifr_name);
    }
    else
    {   // 如果是not running, 那就直接判断为down
        printf("%s: is down\n", ifr.ifr_name);
        printf("%s: is NOT running\n", ifr.ifr_name);
    }

    // get HW address
    if(ioctl(skfd, SIOCGIFHWADDR, &ifr) < 0)
    {
        printf("SIOCGIFHWADDR IOCTL error\n");
        close(skfd);
        return -1;
    }
    printf("%s: HW_ADDR: %02X:%02X:%02X:%02X:%02X:%02X\n",
        net_name,
        (unsigned char)ifr.ifr_hwaddr.sa_data[0],
        (unsigned char)ifr.ifr_hwaddr.sa_data[1],
        (unsigned char)ifr.ifr_hwaddr.sa_data[2],
        (unsigned char)ifr.ifr_hwaddr.sa_data[3],
        (unsigned char)ifr.ifr_hwaddr.sa_data[4],
        (unsigned char)ifr.ifr_hwaddr.sa_data[5]);

    // get IP address
    if(ioctl(skfd, SIOCGIFADDR, &ifr) < 0)
    {
        printf("SIOCGIFADDR IOCTL error\n");
        close(skfd);
        return -1;
    }
    pAddr = (struct sockaddr_in *)&(ifr.ifr_addr);
    printf("%s: IP_ADDR: %s\n", net_name, inet_ntoa(pAddr->sin_addr));

    // get broadcast address
    if(ioctl(skfd, SIOCGIFBRDADDR, &ifr) < 0)
    {
        printf("SIOCGIFBRDADDR IOCTL error\n");
        close(skfd);
        return -1;
    }
    pAddr = (struct sockaddr_in *)&(ifr.ifr_addr);
    printf("%s: BROADCAST: %s\n", net_name, inet_ntoa(pAddr->sin_addr));

    // get netmask address
    if(ioctl(skfd, SIOCGIFNETMASK, &ifr) < 0)
    {
        printf("SIOCGIFNETMASK IOCTL error\n");
        close(skfd);
        return -1;
    }
    pAddr = (struct sockaddr_in *)&(ifr.ifr_addr);
    printf("%s: NET_MASK: %s\n", net_name, inet_ntoa(pAddr->sin_addr));

    // get gateway address
    char gateway[16] = {0};
    net_get_gateway(gateway);
    printf("%s: GATEWAY: %s\n", net_name, gateway);

    close(skfd);
    return 0;
}

int main(int argc, char *argv[])
{
    char net_name[8] = {0};

    if(argc != 2)
    {
        printf("arg error\n");
        return -1;
    }

    memcpy(net_name, argv[1], strlen(argv[1]));

    return net_detect(net_name);
}

以下程序运行示例:./a.out eth0

显然,以上程序要使用者用轮询的方式去监测网线的状态

 

 

方法二:

参考:https://blog.youkuaiyun.com/muclenerd/article/details/49950257

参考:https://blog.youkuaiyun.com/al86866365/article/details/79066227

参考:https://blog.youkuaiyun.com/gt945/article/details/45315911

cat /sys/class/net/eth0/carrier

如果carrier为1表示connect,否则disconnect。

平时没有状态变化时,以下程序会每隔5秒(由select函数控制)循环一次,一旦监测到可读,则read()函数就去读数据,之后的程序就解析各个事件的数据

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

#define BUFLEN 20480

int main(int argc, char *argv[])
{
    int fd, retval;
    char buf[BUFLEN] = {0};
    int len = BUFLEN;
    struct sockaddr_nl addr;
    struct nlmsghdr *nh;
    struct rtattr *attr;

    fd_set rd_set;
    struct timeval timeout;
    int select_r;

    // 1. 打开 NetLink Socket
    // 第一个参数必须是AF_NETLINK或PF_NETLINK, 在Linux中, 它们俩实际为一个东西, 它表示要使用netlink
    // 第二个参数必须是SOCK_RAW或SOCK_DGRAM
    // 第三个参数指定netlink协议类型, NETLINK_ROUTE 意为“路由守护进程”
    // 绑定该协议所创建出来的fd可以接收到来自内核的路由通知事件(如网路接口eth0上线)
    fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len));

    // 2. 设定接收类型并绑定Socket
    memset(&addr, 0, sizeof(addr));
    addr.nl_family = AF_NETLINK;
    // 指定接收路由多播组消息 + IPV4消息
    addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR;
    // bind()用于把一个打开的netlink socket与netlink源socket地址绑定在一起
    bind(fd, (struct sockaddr*)&addr, sizeof(addr));

    while(1)
    {
        //每次循环都要清空集合,否则不能检测描述符变化
        FD_ZERO(&rd_set);
        //添加描述符
        FD_SET(fd, &rd_set);
        timeout.tv_sec = 5;
        timeout.tv_usec = 0;
        // read()函数读不到东西会一直阻塞, 用select()函数可以达到不阻塞的效果
        // select()能够监视我们需要监视的文件描述符的变化情况——读写或是异常
        // 此处select()监视到fd可读之后, 返回值select_r大于0; fd不可读, 则返回0
        select_r = select(fd + 1, &rd_set, NULL, NULL, &timeout);
        if(select_r < 0)
        {
            printf("select() return error\n");
        }
        else if(select_r > 0)
        {
            if(FD_ISSET(fd, &rd_set))
            {
                retval = read(fd, buf, BUFLEN);

                for(nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, retval); nh = NLMSG_NEXT(nh, retval))
                {
                    if(nh->nlmsg_type == NLMSG_DONE)
                        break;
                    else if(nh->nlmsg_type == NLMSG_ERROR)
                        return;
                    else if(nh->nlmsg_type == RTM_NEWLINK)
                    {
                        struct ifinfomsg *ifinfo;
                        ifinfo = NLMSG_DATA(nh);
                        printf("NEWLINK: %s",  (ifinfo->ifi_flags & IFF_LOWER_UP) ? "up" : "down");

                        attr = (struct rtattr*)(((char*)nh) + NLMSG_SPACE(sizeof(*ifinfo)));
                        len = nh->nlmsg_len - NLMSG_SPACE(sizeof(*ifinfo));
                        for(; RTA_OK(attr, len); attr = RTA_NEXT(attr, len))
                        {
                            if(attr->rta_type == IFLA_IFNAME)
                            {
                                printf(" %s", (char*)RTA_DATA(attr));
                                break;
                            }
                        }
                        printf("\n");
                    }
                    else if(nh->nlmsg_type == RTM_NEWADDR || nh->nlmsg_type == RTM_DELADDR)
                    {
                        struct ifaddrmsg *ifaddr;
                        ifaddr = NLMSG_DATA(nh);
                        printf("%s:", (nh->nlmsg_type==RTM_NEWADDR)?"NEWADDR":"DELADDR");

                        attr = (struct rtattr*)(((char*)nh) + NLMSG_SPACE(sizeof(*ifaddr)));
                        len = nh->nlmsg_len - NLMSG_SPACE(sizeof(*ifaddr));
                        for(; RTA_OK(attr, len); attr = RTA_NEXT(attr, len))
                        {
                            if(attr->rta_type == IFA_ADDRESS)
                            {
                                char tmp[32] = {0};
                                inet_ntop(ifaddr->ifa_family, RTA_DATA(attr), tmp, sizeof(tmp));
                                printf(" %s", tmp);
                            }

                            if(attr->rta_type == IFA_LABEL)
                            {
                                printf(" %s", (char*)RTA_DATA(attr));
                                break;
                            }
                        }
                        printf("\n");
                    }
                }
            }
        }
    }

    close(fd);
    return 0;
}

 

<think>我们正在解决网线插拔后如何重新连接网络的问题。根据引用,我们有几个方法可以检测网线插拔状态,然后执行重连操作。下面我们将分步骤说明如何实现。 ### 1. 检测网线插拔状态 在重新连接网络之前,我们需要先检测网线插拔状态。根据引用,我们可以通过以下几种方式检测: #### a) 使用底层网络接口状态(如LwIP) 引用[1]中提供了两个函数: - `Cable_Is_Plugged_In()`:通过检查网络接口的链路状态(`netif_is_link_up`)来判断网线是否插入。 - `IP_Get_Ok()`:检查是否已经成功获取IP地址。 示例代码: ```c // 检测网线是否插入 int Cable_Is_Plugged_In() { if (netif_is_link_up(&gnetif)) { return 1; // 已插入 } else { return 0; // 未插入 } } // 检测IP地址是否获取成功 int IP_Get_Ok() { // 假设ipaddr是预期的IP地址,这里比较当前IP和预期IP if (ip_addr_cmp(&(gnetif.ip_addr), &ipaddr)) { return 0; // 已获取 } else { return 1; // 未获取 } } ``` #### b) 使用Linux命令`ifconfig` 引用[2]提到,在Linux中可以使用`ifconfig`命令来查看网线状态。如果网线连接正常,输出中会有`RUNNING`标志;拔掉网线后,该标志消失。 我们可以编写一个类似`ifconfig`的函数来检测状态,或者直接解析`ifconfig`的输出。 #### c) 使用Netlink套接字(Linux) 引用[4]提到,可以通过Netlink套接字来检测网线插拔事件。这是一种更底层、更高效的方法。 示例代码(创建Netlink套接字并接收事件): ```c #include <linux/netlink.h> #include <linux/rtnetlink.h> #include <net/if.h> int netlink_socket = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (netlink_socket < 0) { perror("socket"); return -1; } struct sockaddr_nl addr; memset(&addr, 0, sizeof(addr)); addr.nl_family = AF_NETLINK; addr.nl_groups = RTMGRP_LINK; // 监听链路变化 if (bind(netlink_socket, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); close(netlink_socket); return -1; } // 然后通过recvmsg接收事件,解析事件类型(如网线插拔) ``` #### d) 使用Socket轮询(如C#) 引用[3]提到,在C#中可以通过轮询Socket的状态来检测连接是否断开。例如,使用`Select`方法检查读状态,如果返回`false`则说明连接已关闭。 ### 2. 重新连接网络 检测到网线重新插入后,我们需要重新连接网络。具体步骤包括: #### a) 重新启用网络接口 在Linux中,我们可以通过命令重新启用网络接口: ```bash sudo ifconfig eth0 down sudo ifconfig eth0 up ``` 或者使用`ip`命令: ```bash sudo ip link set eth0 down sudo ip link set eth0 up ``` #### b) 重新获取IP地址 如果是使用DHCP,需要重新获取IP地址: ```bash sudo dhclient eth0 ``` #### c) 在程序中自动重连 如果是在应用程序中,我们需要在检测到网线重新插入后,重新初始化网络连接。例如,在嵌入式系统中使用LwIP,可以重新启动网络接口: ```c // 当检测到网线重新插入 if (Cable_Is_Plugged_In()) { // 重新启动网络接口 netif_set_link_down(&gnetif); netif_set_link_up(&gnetif); // 如果使用DHCP,重新请求IP dhcp_start(&gnetif); } ``` ### 3. 实现自动重连的循环检测 我们可以创建一个循环,定期检测网线状态,并在状态变化时执行重连操作。 示例代码(伪代码): ```c int last_state = -1; // 上一次的状态 while (1) { int current_state = Cable_Is_Plugged_In(); if (last_state != current_state) { if (current_state == 1) { printf("网线已插入,重新连接网络...\n"); // 执行重连操作 Reconnect_Network(); } else { printf("网线已拔出\n"); } last_state = current_state; } sleep(1); // 每秒检测一次 } ``` ### 4. 使用系统事件监听(高级) 在Linux中,我们可以使用Netlink监听网络事件,避免轮询。当有网线插拔事件时,内核会发送消息,我们只需要在程序中接收并处理即可。 示例代码片段(基于Netlink): ```c // 创建Netlink套接字(如前所述) // 然后循环接收事件 char buffer[4096]; struct iovec iov = { buffer, sizeof(buffer) }; struct sockaddr_nl snl; struct msghdr msg = { (void *)&snl, sizeof(snl), &iov, 1, NULL, 0, 0 }; while (1) { int len = recvmsg(netlink_socket, &msg, 0); if (len < 0) { perror("recvmsg"); continue; } // 解析消息 for (struct nlmsghdr *nh = (struct nlmsghdr *)buffer; NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) { if (nh->nlmsg_type == RTM_NEWLINK || nh->nlmsg_type == RTM_DELLINK) { struct ifinfomsg *ifinfo = NLMSG_DATA(nh); // 根据ifinfo->ifi_flags判断链路状态 if (ifinfo->ifi_flags & IFF_RUNNING) { // 网线插入,执行重连 Reconnect_Network(); } else { // 网线拔出 } } } } ``` ### 5. 注意事项 - **权限问题**:在Linux中,普通用户可能没有权限操作网络接口,需要以root权限运行或配置sudo免密。 - **多线程**:在图形界面程序中,建议将检测放在后台线程,避免阻塞主线程。 - **重连频率**:避免过于频繁的重连操作,可以设置一个重连间隔。 ### 总结步骤 1. **检测网线状态**:通过轮询(如检查接口状态)或事件监听(如Netlink)来检测网线插拔。 2. **执行重连**:当检测到网线重新插入时,重新启用网络接口并重新获取IP地址(如使用DHCP)。 3. **程序重连**:在应用程序中,重新初始化网络连接(如重新连接Socket)。 [^1]: 基于LwIP网络接口状态检测 [^2]: Linux ifconfig命令检测链路状态 [^3]: Socket状态轮询检测连接 [^4]: Netlink套接字检测网线插拔 §§相关问题§§ 1. 如何在Linux中使用Netlink监听网络接口状态变化? 2. 在嵌入式系统中如何实现断网自动重连? 3. 如何通过编程方式在Linux中重启网络接口?</think>### 网线插拔后重新连接网络的完整解决方案 当检测到网线插拔事件后,重新连接网络需要结合状态检测和重连机制。以下是分步实现方案: --- ### 🔌 1. **检测网线插拔状态** #### a) Linux系统级检测(推荐) ```c #include <linux/netlink.h> #include <linux/rtnetlink.h> // 创建Netlink套接字监听网线状态 int create_netlink_socket() { int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); struct sockaddr_nl addr = { .nl_family = AF_NETLINK, .nl_groups = RTMGRP_LINK // 监听链路变化 }; bind(sock, (struct sockaddr*)&addr, sizeof(addr)); return sock; } // 解析网线状态 int check_cable_status(int sock) { char buffer[4096]; struct nlmsghdr *nlh = (struct nlmsghdr *)buffer; recv(sock, nlh, sizeof(buffer), 0); if (nlh->nlmsg_type == RTM_NEWLINK || nlh->nlmsg_type == RTM_DELLINK) { struct ifinfomsg *ifinfo = NLMSG_DATA(nlh); return (ifinfo->ifi_flags & IFF_RUNNING) ? 1 : 0; // 1=已连接 } return -1; // 无状态变化 } ``` [^4] #### b) 应用层快速检测 ```bash # 通过ifconfig检测RUNNING标志 ifconfig eth0 | grep "RUNNING" && echo "网线已连接" || echo "网线未连接" ``` [^2] --- ### 🔄 2. **执行重连操作** #### a) 系统命令级重连 ```bash # 禁用再启用网卡 sudo ip link set eth0 down sleep 1 sudo ip link set eth0 up # 强制DHCP更新 sudo dhclient -r eth0 # 释放IP sudo dhclient eth0 # 重新获取IP ``` #### b) 程序内自动重连(C语言示例) ```c #include "lwip/netif.h" void reconnect_network(struct netif *gnetif) { netif_set_link_down(gnetif); // 先断开连接 netif_set_link_up(gnetif); // 重新激活连接 // 如果是DHCP模式 dhcp_stop(gnetif); dhcp_start(gnetif); } // 状态检测函数 int Cable_Is_Plugged_In(struct netif *gnetif) { return netif_is_link_up(gnetif) ? 1 : 0; } ``` [^1] --- ### 📡 3. **TCP连接级重连(C#示例)** ```csharp // 检测连接状态 bool IsConnected(Socket socket) { try { return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0); } catch { return false; } } // 重连逻辑 void Reconnect() { while (true) { if (!IsConnected(mainSocket)) { mainSocket.Close(); mainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); mainSocket.Connect("192.168.1.100", 8080); // 重新连接 } Thread.Sleep(1000); // 每秒检测一次 } } ``` [^3] --- ### 🛠 4. 完整工作流程 ```mermaid graph TD A[启动监听线程] --> B{检测网线状态} B -->|已插入| C[执行重连操作] B -->|已拔出| D[记录断开事件] C --> E[重启网络接口] E --> F[重新获取IP] F --> G[重建TCP连接] G --> H[恢复数据传输] H --> B ``` --- ### ⚠ 5. 注意事项 1. **权限问题**:操作网络接口需要root权限,可通过`sudo`或配置sudoers文件 2. **重连频率控制**:添加重连间隔(建议1-3秒),避免频繁操作 3. **异常处理**: ```c if (dhcp_start(gnetif) != ERR_OK) { printf("DHCP获取失败,尝试静态IP回退"); netif_set_addr(gnetif, &static_ip, &netmask, &gateway); } ``` 4. **日志记录**:记录所有插拔事件和重连操作,便于故障排查 --- ### 🔧 6. 调试技巧 ```bash # 实时监控内核事件 sudo udevadm monitor --kernel # 查看网络接口状态 ip -o link show eth0 | awk '{print $3,$9}' # 测试TCP重连(使用netcat) while true; do nc -zv 192.168.1.1 80 && break || sleep 2; done ``` 通过结合底层状态检测和应用层重连逻辑,可实现完整的网线插拔恢复机制。实际部署时建议使用Netlink监听+定时状态检测的双重保障[^1][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值