一、简介

一些与硬件设备相关的软件需要监听设备的热插拔、断开、连接情况,本文介绍了QT中netlink实现方式。

大致思路:linux设备改变会触发相关事件,使用netlink与linux内核空间通信,再使用QSocketNotifier监听,绑定Qt槽函数

  •   实现:netlink(Linux通信)  + QSocketNotifier
  •   功能:网卡监听、串口监听
  •   优点:监听串口能快速响应
  •   缺点:监听网卡有些迟钝

netlink介绍:

Netlink套接字是用以实现用户进程内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口。

在Linux 内核中,使用netlink 进行应用与内核通信的应用有很多,如

  • 路由 daemon(NETLINK_ROUTE)
  • 用户态 socket 协议(NETLINK_USERSOCK)
  • 防火墙(NETLINK_FIREWALL)
  • netfilter 子系统(NETLINK_NETFILTER)
  • 内核事件向用户态通知(NETLINK_KOBJECT_UEVENT)  本文使用
  • 通用netlink(NETLINK_GENERIC)

参考: linux下netlink的使用简介 - 简书

QSocketNotifier介绍:

用来监听系统文件操作,将操作转换为Qt事件进入系统的消息循环队列。并调用预先设置的事件接受函数,处理事件。

参考:QSocketNotifier Class | Qt Core 5.15.7

定时器的实现方式见我另一篇博客:QT实现linux底层设备监听(QTimer)_俊俊的博客-CSDN博客

二、效果

启动软件,显示当前已经存在的设备

当有设备插入、拔出或者连接、断开时提示

拔出串口设备

三、实现

devicemonitor.h

#ifndef QDEVICEWATCHER_P_H
#define QDEVICEWATCHER_P_H
#include <QBuffer>
#include <QList>
#include <QThread>
#include <QDebug>
#include <QSerialPortInfo>
#include <QNetworkInterface>


class QDeviceWatcherPrivate: public QObject
{
    Q_OBJECT
public:
    QDeviceWatcherPrivate(QObject *parent = 0);
    ~QDeviceWatcherPrivate();
    bool start();
    bool stop();

    QList<QString> getSerialPortNames();
    QList<QString> getNetworkNames();

    //QList<QObject*> event_receivers;

signals:
    void deviceChanged();

private slots:
    void parseDeviceInfo();

private:

    bool init();
    QBuffer buffer;
    void parseLine(const QByteArray& line);
    class QSocketNotifier *socket_notifier;
    int netlink_socket;

    QList<QString> _serialPortNames;
    QList<QString> _networkNames;

};


#endif // QDEVICEWATCHER_P_H

devicemonitor.cpp

#include "devicemonitor.h"
#ifdef Q_OS_LINUX

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include <linux/version.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
#else

#endif

#include <sys/un.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/types.h>
#include <linux/netlink.h>
#include <errno.h>
#include <unistd.h>

#include <QtCore/QCoreApplication>
#include <qregexp.h>
#include <QSocketNotifier>



#define UEVENT_BUFFER_SIZE      2048

enum udev_monitor_netlink_group {
    UDEV_MONITOR_NONE,
    UDEV_MONITOR_KERNEL,
    UDEV_MONITOR_UDEV
};

QDeviceWatcherPrivate::QDeviceWatcherPrivate(QObject *parent)
: QObject(parent)
{
    _serialPortNames = getSerialPortNames();
    _networkNames = getNetworkNames();
    qDebug()<< "初始化设备";
    qDebug()<< "串口:" +  _serialPortNames.join(",");
    qDebug()<< "网卡:" +  _networkNames.join(",");
}

QDeviceWatcherPrivate::~QDeviceWatcherPrivate()
{
    stop();
    close(netlink_socket);
    netlink_socket = -1;
}

bool QDeviceWatcherPrivate::start()
{
    if (!init())
        return false;
    socket_notifier->setEnabled(true);
    return true;
}

bool QDeviceWatcherPrivate::stop()
{
    if (netlink_socket!=-1) {
        socket_notifier->setEnabled(false);
        close(netlink_socket);
        netlink_socket = -1;
    }
    return true;
}

QList<QString> QDeviceWatcherPrivate::getSerialPortNames()
{
    QList<QString> serialPortNames;
    foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
        serialPortNames.append(info.portName());
    }
    return serialPortNames;
}

QList<QString> QDeviceWatcherPrivate::getNetworkNames()
{
    QList<QString> networkNames;
    QList<QNetworkInterface> list = QNetworkInterface::allInterfaces();
    foreach (QNetworkInterface interfaceItem, list) {
        if (!interfaceItem.isValid())
            continue;
        QList<QNetworkAddressEntry> addressEntryList = interfaceItem.addressEntries();
        foreach(QNetworkAddressEntry addressEntryItem, addressEntryList)
        {
            if(addressEntryItem.ip().protocol()==QAbstractSocket::IPv4Protocol&&
                    addressEntryItem.ip().toString().left(3)=="192")
            {
                networkNames.append(interfaceItem.name());
            }
        }
    }
    return networkNames;
}


bool QDeviceWatcherPrivate::init()
{
    struct sockaddr_nl snl;
    const int buffersize = 16 * 1024 * 1024;
    int retval;

    memset(&snl, 0x00, sizeof(struct sockaddr_nl));
    snl.nl_family = AF_NETLINK;
    snl.nl_groups = UDEV_MONITOR_KERNEL;

    netlink_socket = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);

    if (netlink_socket == -1) {
        qWarning("error getting socket: %s", strerror(errno));
        return false;
    }

    /* set receive buffersize */
    setsockopt(netlink_socket, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize));
    retval = bind(netlink_socket, (struct sockaddr*) &snl, sizeof(struct sockaddr_nl));
    if (retval < 0) {
        qWarning("bind failed: %s", strerror(errno));
        close(netlink_socket);
        netlink_socket = -1;
        return false;
    } else if (retval == 0) {
        //from libudev-monitor.c
        struct sockaddr_nl _snl;
        socklen_t _addrlen;

        /*
         * get the address the kernel has assigned us
         * it is usually, but not necessarily the pid
         */
        _addrlen = sizeof(struct sockaddr_nl);
        retval = getsockname(netlink_socket, (struct sockaddr *)&_snl, &_addrlen);
        if (retval == 0)
            snl.nl_pid = _snl.nl_pid;
    }

    socket_notifier = new QSocketNotifier(netlink_socket, QSocketNotifier::Read, this);
    connect(socket_notifier, SIGNAL(activated(int)), SLOT(parseDeviceInfo())); //will always active
    socket_notifier->setEnabled(false);
    return true;
}


void QDeviceWatcherPrivate::parseDeviceInfo()
{
    /*
    问题记录:串口热插拔能够快速反应,但网卡反应比较迟钝
    */

    // zDebug("%s active", qPrintable(QTime::currentTime().toString()));
    QByteArray data;
    data.resize(UEVENT_BUFFER_SIZE*2);
    data.fill(0);
    size_t len = read(socket_notifier->socket(), data.data(), UEVENT_BUFFER_SIZE*3);
//    qDebug("read fro socket %d bytes", len);
    data.resize(len);
    data = data.replace(0, '\n').trimmed(); //In the original line each information is seperated by 0
    if (buffer.isOpen())
        buffer.close();
    buffer.setBuffer(&data);
    buffer.open(QIODevice::ReadOnly);
    while(!buffer.atEnd()) { //buffer.canReadLine() always false?
        parseLine(buffer.readLine().trimmed());
    }
    buffer.close();
}


void QDeviceWatcherPrivate::parseLine(const QByteArray &line)
{
//    qDebug()<< line.constData();
//    if (line.contains("remove@") ||  line.contains("add@"))    //寻找关键词
//    {
//        emit deviceChanged();
//    }
    QList<QString> serialPortNames = getSerialPortNames();
    QList<QString> networkNames = getNetworkNames();
    if(_serialPortNames != serialPortNames)
    {
        if (serialPortNames.length() > _serialPortNames.length())
        {
            _serialPortNames = serialPortNames;
            qDebug() << "串口插入:" + _serialPortNames.join(",");
            emit deviceChanged();
        }

        if (serialPortNames.length() < _serialPortNames.length())
        {
            _serialPortNames = serialPortNames;
            qDebug() << "串口拔出:" + _serialPortNames.join(",");
            emit deviceChanged();
        }
    }
    if(_networkNames != networkNames)
    {
        if(networkNames.length()  > _networkNames.length())
        {
            _networkNames = networkNames;
            qDebug() << "网卡连接:" + _networkNames.join(",");
            emit deviceChanged();
        }
        if(networkNames.length()  < _networkNames.length())
        {
            _networkNames = networkNames;
            qDebug() << "网卡断开:" + _networkNames.join(",");
            emit deviceChanged();
        }
    }
}

#endif //Q_OS_LINUX

使用

//#include "mainwindow.h"

#include "devicemonitor.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
//    MainWindow w;
//    w.show();
    QDeviceWatcherPrivate *watcher = new QDeviceWatcherPrivate;
    watcher->start();
    return a.exec();
}

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐