Linux平台使用Qt进行DHCP自动获取网络配置
本文介绍了在嵌入式Linux系统中使用Qt框架实现DHCP自动获取网络配置的完整方案。主要内容包括:1. 选用轻量级udhcpc作为DHCP客户端,解析其配置脚本处理不同状态;2. 详细讲解DHCP客户端脚本,包括IP地址分配、路由设置和DNS配置等功能;3. 在Qt中集成DHCP功能,通过系统调用和文件解析获取网络配置;4. 提供完整的实现流程,包括脚本创建、权限设置和Qt模块开发;5. 分析关
引言
在嵌入式Linux系统和网络设备开发中,动态主机配置协议(DHCP)是实现设备自动获取IP地址的关键技术。本文将详细介绍如何在Linux平台上使用Qt框架实现完整的DHCP自动获取网络配置功能,包括DHCP客户端调用、网络配置解析和Qt界面集成。
一、DHCP客户端选择:udhcpc
在嵌入式Linux环境中,我们通常选择轻量级的DHCP客户端。udhcpc是BusyBox工具集的一部分,具有体积小、功能完善的特点,非常适合资源受限的嵌入式设备。
udhcpc基本用法
udhcpc -b -s /opt/app/default.script -i eth0
参数说明:
-
-b: 后台运行模式 -
-s: 指定自定义配置脚本 -
-i: 指定网络接口
二、DHCP客户端脚本详解
脚本核心功能
我们的default.script脚本负责处理DHCP客户端的各个状态(注释版):
#!/bin/sh
# udhcpc script edited by Tim Riker <Tim@Rikers.org>
# 检查是否被 udhcpc 正确调用,如果没有参数则报错退出
[ -z "$1" ] && echo "Error: should be called from udhcpc" && exit 1
# 设置 DNS 配置文件路径
# 如果文件不存在则创建
# 根据 DHCP 提供的参数设置广播地址和子网掩码
# 处理 IPv6 地址
RESOLV_CONF="/etc/resolv.conf"
[ -e $RESOLV_CONF ] || touch $RESOLV_CONF
[ -n "$broadcast" ] && BROADCAST="broadcast $broadcast"
[ -n "$subnet" ] && NETMASK="netmask $subnet"
# Handle stateful DHCPv6 like DHCPv4
[ -n "$ipv6" ] && ip="$ipv6/128"
# 设置等待 IPv6 默认路由的超时时间,默认 10 秒
if [ -z "${IF_WAIT_DELAY}" ]; then
IF_WAIT_DELAY=10
fi
# 作用: 等待 IPv6 默认路由出现
# 循环检查 IPv6 默认路由
# 每秒钟检查一次,最多等待 10 秒
# 显示进度点和超时提示
wait_for_ipv6_default_route() {
printf "Waiting for IPv6 default route to appear"
while [ $IF_WAIT_DELAY -gt 0 ]; do
if [ -z "$(ip -6 route list | grep default)" ]; then
printf "\n"
return
fi
sleep 1
printf "."
: $((IF_WAIT_DELAY -= 1))
done
printf " timeout!\n"
}
# case 语句处理不同 DHCP 状态
# deconfig 状态 - 释放配置
# leasefail|nak 状态 - 租约失败
# renew|bound 状态 - 获取或更新租约
case "$1" in
deconfig)
/sbin/ifconfig $interface up
/sbin/ifconfig $interface 0.0.0.0
# 启用网络接口但清除 IP 地址
# 从 resolv.conf 中删除该接口相关的 DNS 配置
# 使用临时文件安全地修改配置文件
# drop info from this interface
# resolv.conf may be a symlink to /tmp/, so take care
TMPFILE=$(mktemp)
grep -vE "# $interface\$" $RESOLV_CONF > $TMPFILE
cat $TMPFILE > $RESOLV_CONF
rm -f $TMPFILE
# 如果存在 avahi-autoipd (零配置网络服务),则停止它
if [ -x /usr/sbin/avahi-autoipd ]; then
/usr/sbin/avahi-autoipd -k $interface
fi
;;
leasefail|nak)
#DHCP 获取失败时,启动 avahi-autoipd 获取链路本地地址
if [ -x /usr/sbin/avahi-autoipd ]; then
/usr/sbin/avahi-autoipd -wD $interface --no-chroot
fi
;;
renew|bound)
# 停止 avahi-autoipd(如果正在运行)
if [ -x /usr/sbin/avahi-autoipd ]; then
/usr/sbin/avahi-autoipd -k $interface
fi
# 配置接口的 IP 地址、广播地址和子网掩码
/sbin/ifconfig $interface $ip $BROADCAST $NETMASK
# 如果有 IPv6 地址,等待默认路由出现
if [ -n "$ipv6" ] ; then
wait_for_ipv6_default_route
fi
# 删除接口上所有默认网关
# 添加 DHCP 提供的网关作为默认路由
if [ -n "$router" ] ; then
echo "deleting routers"
while route del default gw 0.0.0.0 dev $interface 2> /dev/null; do
:
done
for i in $router ; do
route add default gw $i dev $interface
done
fi
# 清理该接口之前的 DNS 配置
# drop info from this interface
# resolv.conf may be a symlink to /tmp/, so take care
TMPFILE=$(mktemp)
grep -vE "# $interface\$" $RESOLV_CONF > $TMPFILE
cat $TMPFILE > $RESOLV_CONF
rm -f $TMPFILE
# 设置搜索域(优先使用 RFC3397 选项 119)
# prefer rfc3397 domain search list (option 119) if available
if [ -n "$search" ]; then
search_list=$search
elif [ -n "$domain" ]; then
search_list=$domain
fi
[ -n "$search_list" ] &&
echo "search $search_list # $interface" >> $RESOLV_CONF
#添加 DNS 服务器到 resolv.conf
for i in $dns ; do
echo adding dns $i
echo "nameserver $i # $interface" >> $RESOLV_CONF
done
;;
esac
# 钩子脚本执行
# 作用: 执行扩展钩子脚本
# 查找与当前脚本同名的 .d 目录
# 执行该目录下所有可执行文件,传递相同的参数
HOOK_DIR="$0.d"
for hook in "${HOOK_DIR}/"*; do
[ -f "${hook}" -a -x "${hook}" ] || continue
"${hook}" "${@}"
done
# 脚本成功退出
exit 0
default.script(无注释版):
#!/bin/sh
# udhcpc script edited by Tim Riker <Tim@Rikers.org>
[ -z "$1" ] && echo "Error: should be called from udhcpc" && exit 1
RESOLV_CONF="/etc/resolv.conf"
[ -e $RESOLV_CONF ] || touch $RESOLV_CONF
[ -n "$broadcast" ] && BROADCAST="broadcast $broadcast"
[ -n "$subnet" ] && NETMASK="netmask $subnet"
# Handle stateful DHCPv6 like DHCPv4
[ -n "$ipv6" ] && ip="$ipv6/128"
if [ -z "${IF_WAIT_DELAY}" ]; then
IF_WAIT_DELAY=10
fi
wait_for_ipv6_default_route() {
printf "Waiting for IPv6 default route to appear"
while [ $IF_WAIT_DELAY -gt 0 ]; do
if [ -z "$(ip -6 route list | grep default)" ]; then
printf "\n"
return
fi
sleep 1
printf "."
: $((IF_WAIT_DELAY -= 1))
done
printf " timeout!\n"
}
case "$1" in
deconfig)
/sbin/ifconfig $interface up
/sbin/ifconfig $interface 0.0.0.0
# drop info from this interface
# resolv.conf may be a symlink to /tmp/, so take care
TMPFILE=$(mktemp)
grep -vE "# $interface\$" $RESOLV_CONF > $TMPFILE
cat $TMPFILE > $RESOLV_CONF
rm -f $TMPFILE
if [ -x /usr/sbin/avahi-autoipd ]; then
/usr/sbin/avahi-autoipd -k $interface
fi
;;
leasefail|nak)
if [ -x /usr/sbin/avahi-autoipd ]; then
/usr/sbin/avahi-autoipd -wD $interface --no-chroot
fi
;;
renew|bound)
if [ -x /usr/sbin/avahi-autoipd ]; then
/usr/sbin/avahi-autoipd -k $interface
fi
/sbin/ifconfig $interface $ip $BROADCAST $NETMASK
if [ -n "$ipv6" ] ; then
wait_for_ipv6_default_route
fi
if [ -n "$router" ] ; then
echo "deleting routers"
while route del default gw 0.0.0.0 dev $interface 2> /dev/null; do
:
done
for i in $router ; do
route add default gw $i dev $interface
done
fi
# drop info from this interface
# resolv.conf may be a symlink to /tmp/, so take care
TMPFILE=$(mktemp)
grep -vE "# $interface\$" $RESOLV_CONF > $TMPFILE
cat $TMPFILE > $RESOLV_CONF
rm -f $TMPFILE
# prefer rfc3397 domain search list (option 119) if available
if [ -n "$search" ]; then
search_list=$search
elif [ -n "$domain" ]; then
search_list=$domain
fi
[ -n "$search_list" ] &&
echo "search $search_list # $interface" >> $RESOLV_CONF
for i in $dns ; do
echo adding dns $i
echo "nameserver $i # $interface" >> $RESOLV_CONF
done
;;
esac
HOOK_DIR="$0.d"
for hook in "${HOOK_DIR}/"*; do
[ -f "${hook}" -a -x "${hook}" ] || continue
"${hook}" "${@}"
done
exit 0
关键状态处理
-
bound/renew状态:获取新IP或更新现有IP
-
deconfig状态:释放当前网络配置
-
leasefail/nak状态:处理DHCP失败情况
三、Qt应用程序集成
3.1 启动DHCP客户端
在Qt中,我们通过QTimer来管理DHCP客户端:
void NetworkModule::UseDHCP()
{
m_pTimer = new QTimer(this);
connect(m_pTimer, SIGNAL(timeout()), this, SLOT(Slot_Timeout()));
// 启动DHCP客户端
system("udhcpc -b -s /opt/app/default.script -i eth0");
m_pTimer->start(5000); // 5秒后检查结果
}
3.2 网络配置解析
通过系统调用和文件解析获取网络配置信息:
// 将IP地址字符串转换为unsigned long
unsigned long ipToLong(const char* ipStr)
{
struct in_addr addr;
if (inet_pton(AF_INET, ipStr, &addr) <= 0) {
return 0;
}
return ntohl(addr.s_addr);
}
3.3 完整的配置获取函数
// 获取网络接口配置
int getNetworkConfiguration(const char* interface,
unsigned long* ip,
unsigned long* mask,
unsigned long* gateway,
unsigned long* dns)
{
// 初始化输出参数
if (ip) *ip = 0;
if (mask) *mask = 0;
// 获取IP和掩码
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
return 0; // 失败
}
struct ifreq ifr;
strncpy(ifr.ifr_name, interface, IFNAMSIZ - 1);
// 获取IP地址
if (ioctl(fd, SIOCGIFADDR, &ifr) == 0) {
struct sockaddr_in* ipaddr = (struct sockaddr_in*)&ifr.ifr_addr;
if (ip) *ip = ntohl(ipaddr->sin_addr.s_addr);
}
// 获取子网掩码
if (ioctl(fd, SIOCGIFNETMASK, &ifr) == 0) {
struct sockaddr_in* netmask = (struct sockaddr_in*)&ifr.ifr_netmask;
if (mask) *mask = ntohl(netmask->sin_addr.s_addr);
}
close(fd);
// 获取网关 - 读取/proc/net/route
FILE* routeFile = fopen("/proc/net/route", "r");
if (routeFile) {
char line[256];
fgets(line, sizeof(line), routeFile); // 跳过标题行
while (fgets(line, sizeof(line), routeFile)) {
char iface[16];
unsigned long dest, gw, flags;
int refCnt, use, metric, mask_val;
if (sscanf(line, "%15s %lx %lx %x %d %d %d %lx",
iface, &dest, &gw, &flags, &refCnt, &use, &metric, &mask_val) >= 3) {
if (strcmp(iface, interface) == 0 && dest == 0) {
// 转换小端格式到大端格式
if (gateway) {
*gateway = ((gw & 0xFF) << 24) |
((gw & 0xFF00) << 8) |
((gw & 0xFF0000) >> 8) |
((gw & 0xFF000000) >> 24);
}
break;
}
}
}
fclose(routeFile);
}
// 获取DNS - 读取/etc/resolv.conf
FILE* resolvFile = fopen("/etc/resolv.conf", "r");
if (resolvFile) {
char line[256];
while (fgets(line, sizeof(line), resolvFile)) {
if (strncmp(line, "nameserver", 10) == 0) {
char dnsStr[16];
if (sscanf(line + 10, "%15s", dnsStr) == 1) {
if (dns) *dns = ipToLong(dnsStr);
break; // 只获取第一个DNS服务器
}
}
}
fclose(resolvFile);
}
// 如果网关未从/proc/net/route获取,尝试使用route命令
if (gateway && *gateway == 0) {
FILE* routeCmd = popen("route -n", "r");
if (routeCmd) {
char line[256];
while (fgets(line, sizeof(line), routeCmd)) {
if (strstr(line, "0.0.0.0") || strstr(line, "default")) {
char dest[16], gw[16], mask_str[16], flags[16], iface[16];
if (sscanf(line, "%15s %15s %15s %15s %*s %*s %*s %15s",
dest, gw, mask_str, flags, iface) >= 2) {
if (strcmp(iface, interface) == 0) {
*gateway = ipToLong(gw);
break;
}
}
}
}
pclose(routeCmd);
}
}
return 1;
}
3.4 定时器槽函数处理
void NetworkModule::Slot_Timeout()
{
m_pTimer->stop();
unsigned long ip = 0, mask = 0, gateway = 0, dns = 0;
if (getNetworkConfiguration("eth0", &ip, &mask, &gateway, &dns))
{
// 字节序转换
config_data->NetConfig.Ip = ((ip & 0xff000000) >> 24) |
((ip & 0x00ff0000) >> 8) |
((ip & 0x0000ff00) << 8) |
((ip & 0x000000ff) << 24);
// 更新界面显示
UpdateAddress();
}
}
四、关键技术点详解
4.1 字节序处理
在网络编程中,需要正确处理主机字节序和网络字节序的转换:
4.2 多数据源配置获取
我们的实现从多个系统源获取网络配置,确保数据的准确性:
-
ioctl系统调用:获取接口IP和掩码
-
/proc/net/route:解析路由表获取网关
-
/etc/resolv.conf:读取DNS服务器配置
-
route命令备选:当文件解析失败时的备用方案
4.3 优化——可增加错误处理和超时机制
// 在DHCP请求中添加超时保护,并在模块中添加retryCount成员变量判断是否重试
// 在UseDHCP函数中,这个时间可依据实际情况修改
m_pTimer->start(5000); // 5秒超时
retryCount=0;
// 在Slot_Timeout函数中,可以对网络配置获取的容错处理
if (getNetworkConfiguration("eth0", &ip, &mask, &gateway, &dns)) {
// 成功获取配置
} else {
// 失败处理,可能重试或使用备用配置
retryCount++;
if(retryCount<=3)
{
m_pTimer->start(5000);
}
else
{
QMessageBox::warning(this, "错误", "无法获取网络配置");
}
}
五、最终流程
第一步:创建DHCP配置脚本
1.1 创建脚本文件
打开终端,执行以下命令创建脚本文件:
sudo mkdir -p /opt/app sudo nano /opt/app/default.script
1.2 复制粘贴default.script脚本内容
将本文default.script内容复制到default.script文件中(删除所有注释,只保留纯代码):
1.3 设置脚本权限
sudo chmod +x /opt/app/default.script
第二步:创建Qt网络模块
2.1 在头文件中声明变量和函数
在Qt项目中的头文件(如networksettings.h)中添加:
#include <QTimer>
#include <QObject>
class NetworkModule : public QObject
{
Q_OBJECT
public:
explicit NetworkModule(QObject *parent = nullptr);
void UseDHCP();//!!!!!!!!!!!!添加
private slots:
void Slot_Timeout();//!!!!!!!!!!!!添加
private:
QTimer *m_pTimer;//!!!!!!!!!!!!添加
// 更新界面显示
void UpdateAddress(int type);//!!!!!!!!!!!!添加
};
2.2 在cpp文件中实现UseDHCP()函数
2.3 实现Slot_Timeout()函数
第三步:创建dhcp.cpp完整实现
创建dhcp.h和dhcp.cpp文件,实现所有网络配置相关的函数:dhcp.h:
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// IP地址字符串转unsigned long
unsigned long ipToLong(const char* ipStr)
// 获取网络接口配置
int getNetworkConfiguration(const char* interface,
unsigned long* ip,
unsigned long* mask,
unsigned long* gateway,
unsigned long* dns)
dhcp.cpp文件中对函数进行实现。
测试步骤
-
编译项目:在Qt Creator中编译你的项目
-
运行程序:启动应用程序
-
点击DHCP按钮:在界面中点击获取DHCP配置的按钮
-
检查结果:等待5秒后查看网络配置是否更新
常见问题解决
问题1:脚本权限不足
sudo chmod +x /opt/app/default.script
问题2:udhcpc命令不存在
bash
# Ubuntu/Debian sudo apt-get install udhcpc # CentOS/RHEL sudo yum install udhcpc
问题3:网络接口名称不对
如果你的网络接口不是eth0,请修改代码中的接口名称:
-
查看可用接口:
ip addr show -
修改代码中的
"eth0"为你的接口名称
六、总结
本文详细介绍了在Linux平台上使用Qt实现DHCP自动获取网络配置的完整方案。通过结合udhcpc客户端、自定义配置脚本和Qt应用程序,我们实现了一个稳定可靠的网络配置管理系统。
方案优势:
-
轻量高效:使用BusyBox的udhcpc,资源占用小
-
功能完整:支持IP、网关、DNS等完整网络配置
-
界面友好:Qt提供良好的用户交互体验
-
稳定可靠:完善的错误处理和超时机制
这种方案特别适合嵌入式设备、网络设备和物联网设备等需要自动网络配置的应用场景。通过本文的介绍,开发者可以快速在自己的项目中实现类似的DHCP网络配置功能。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐

所有评论(0)