struct ifreq
是一个数据结构,用于各种与接口相关的输入/输出控制 (ioctl) 调用。它的主要用途是在网络编程中获取和设置网络接口的属性。这个结构体在
头文件中定义。
以下是 struct ifreq
的一些主要字段和它们的用途:
ifr_name: 一个字符数组,表示接口的名称,如 “eth0”, “wlan0” 等。
ifr_addr: 一个 struct sockaddr
类型的结构,表示接口的地址。
ifr_netmask: 同样是一个 struct sockaddr
类型的结构,表示接口的网络掩码。
ifr_broadaddr: 表示接口的广播地址。
ifr_flags: 表示接口的标志,如 IFF_UP
(接口正在运行)、IFF_BROADCAST
(接口支持广播)、IFF_PROMISC
(接口在混杂模式下)等。
ifr_hwaddr: 表示接口的硬件(通常是 MAC)地址。
ifr_mtu: 表示接口的最大传输单元 (MTU)。
还有其他一些字段,但上面列出的是最常用的。
当我们想查询或设置接口的特定属性时,会填充 struct ifreq
的适当字段,并使用 ioctl()
系统调用。例如,要设置接口为混杂模式,我们会填充 ifr_name
字段并设置 IFF_PROMISC
标志,然后调用 ioctl()
。
例如:
struct ifreq ifr;
int sockfd;
// 创建一个套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
// 设置ifr_name为我们想查询或修改的接口名
strncpy(ifr.ifr_name, "eth0", IFNAMSIZ);
// 获取接口标志
if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) {
perror("ioctl");
close(sockfd);
exit(EXIT_FAILURE);
}
// 打印是否处于混杂模式
if (ifr.ifr_flags & IFF_PROMISC) {
printf("eth0 is in promiscuous mode\n");
} else {
printf("eth0 is not in promiscuous mode\n");
}
close(sockfd);
这是一个简单的示例,演示了如何检查网络接口(在这种情况下是 “eth0”)是否处于混杂模式。
ioctl()
系统调用提供了一种通用的设备驱动程序或接口操作机制,可以用于各种设备特定的操作和控制。它通常用于那些不适合用常规系统调用(如 read()
, write()
, open()
等)表示的设备特定的操作。
int ioctl(int fd, unsigned long request, ...);
fd: 是一个已经打开的文件或套接字的文件描述符。
request: 是设备特定的请求码。这个请求码定义了我们想要执行的特定操作或命令。
…: 这是一个可变参数,取决于特定的 request
。它可能是一个指向数据的指针,也可能是一个直接的值。
ioctl()
的返回值取决于特定的操作。通常,成功返回非负值,失败返回 -1,并设置 errno
。网络编程: ioctl()
经常用于查询和设置网络接口的参数。例如,获取或设置接口的 IP 地址,查询或设置接口的标志等。
设备控制: 对于各种设备(如终端、声音卡、图形卡),ioctl()
可以用于执行特定的操作或查询信息。
文件系统: 在某些情况下,ioctl()
也可用于文件系统操作。
获取网络接口的标志:
struct ifreq ifr;
int fd = socket(AF_INET, SOCK_DGRAM, 0);
strncpy(ifr.ifr_name, "eth0", IFNAMSIZ);
ioctl(fd, SIOCGIFFLAGS, &ifr);
设置非阻塞套接字:
int flags = fcntl(fd, F_GETFL, 0);
ioctl(fd, FIONBIO, &flags);
查询音频设备属性:
int volume;
ioctl(fd, SOUND_MIXER_READ_VOLUME, &volume);
不是所有的设备都支持所有的 ioctl 操作。要知道设备支持哪些操作,需要查阅特定设备的文档或头文件。
错误处理:与其他系统调用一样,使用 ioctl()
时也应进行适当的错误处理。它可能会失败,并设置 errno
,所以应该检查它的返回值并相应地处理错误。
可移植性:ioctl()
是一个非常通用且强大的函数,但它经常与特定的设备或平台相关。这意味着在不同的操作系统或硬件平台上,可能需要不同的 ioctl 请求代码或参数。
总之,ioctl()
提供了一种与设备进行交互的方法,允许开发者执行不同的设备特定操作。然而,正确地使用它需要对特定设备的工作方式有深入的了解。
下面让我们用一个简单的例子来展示如何使用 ioctl()
来获取和设置网络接口(例如 eth0
)的 IP 地址。
#include
#include
#include
#include
#include
#include
#include
#include
int main() {
int sockfd;
struct ifreq ifr;
// 创建一个 socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket");
exit(1);
}
// 指定要查询的接口名
strncpy(ifr.ifr_name, "enp5s0", IFNAMSIZ);
// 获取接口的 IP 地址
if (ioctl(sockfd, SIOCGIFADDR, &ifr) < 0) {
perror("ioctl - SIOCGIFADDR ");
exit(1);
}
struct sockaddr_in *ipaddr = (struct sockaddr_in *)&ifr.ifr_addr;
printf("IP address of enp5s0: %s\n", inet_ntoa(ipaddr->sin_addr));
close(sockfd);
return 0;
}
程序运行结果为:
majn@tiger:~/C_Project/network_project$ ./ioctl_setip
IP address of enp5s0: 192.168.31.6
#include
#include
#include
#include
#include
#include
#include
#include
int main() {
int sockfd;
struct ifreq ifr;
struct sockaddr_in *sin = (struct sockaddr_in *)&ifr.ifr_addr;
// 创建一个 socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket");
exit(1);
}
// 指定要设置的接口名
strncpy(ifr.ifr_name, "eth0", IFNAMSIZ);
// 设置新的 IP 地址
sin->sin_family = AF_INET;
inet_aton("192.168.1.100", &sin->sin_addr);
if (ioctl(sockfd, SIOCSIFADDR, &ifr) < 0) {
perror("ioctl - SIOCSIFADDR ");
exit(1);
}
printf("IP address of eth0 set to: %s\n", inet_ntoa(sin->sin_addr));
close(sockfd);
return 0;
}
注意: 设置 IP 地址通常需要 root 权限,所以运行上述代码时可能需要使用 sudo
。
上述示例中,ioctl()
被用来查询或设置网络接口的 IP 地址。具体的操作由 SIOCGIFADDR
(获取地址)和 SIOCSIFADDR
(设置地址)指定。
inet_aton()
函数用于将点分十进制格式的字符串(如 “192.168.1.1”)转换为一个网络字节序的 IPv4 地址。它在
头文件中定义。
int inet_aton(const char *cp, struct in_addr *inp);
struct in_addr
结构体的指针,该结构体用于存储转换后的地址。与 inet_addr()
相比,inet_aton()
函数提供了更清晰的错误检查机制,因为它明确返回成功或失败的状态,而不是使用特殊的地址值来表示错误。
#include
#include
int main() {
const char *ip_string = "192.168.1.1";
struct in_addr ip_addr;
if (inet_aton(ip_string, &ip_addr)) {
printf("Conversion successful. Network byte order value: 0x%x\n", ip_addr.s_addr);
} else {
printf("Conversion failed.\n");
}
return 0;
}
上述程序的运行结果为:
majn@tiger:~/C_Project/network_project$ ./inet_aton_demo
Conversion successful. Network byte order value: 0x101a8c0
在此示例中,我们将点分十进制的 IP 地址字符串 “192.168.1.1” 使用 inet_aton()
转换为 struct in_addr
格式,并输出转换的结果。
注意:inet_aton()
和 inet_ntoa()
是一对,一个用于将字符串转换为二进制格式,另一个用于进行相反的操作。如果您正在寻找一个线程安全的方法或需要处理 IPv6 地址,建议使用 inet_pton()
和 inet_ntop()
函数代替。
inet_ntoa()
是一个网络工具函数,用于将一个网络字节序的 IPv4 地址转换为点分十进制字符串格式。它在
头文件中定义。
char *inet_ntoa(struct in_addr in);
struct in_addr
结构体,包含一个 IPv4 地址(在网络字节序中)。inet_ntoa()
调用可能会覆盖之前的内容。inet_ntoa()
返回一个指向静态存储区的指针,这意味着每次调用都会覆盖前一次的结果。如果需要保留转换的地址,应该自行复制返回的字符串。inet_ntoa()
在多线程环境中可能会导致问题。为了避免这种情况,可以考虑使用更现代、线程安全的函数,例如 inet_ntop()
。#include
#include
int main() {
struct in_addr ip_addr;
ip_addr.s_addr = htonl(0xC0A80101); // 0xC0A80101 = 192.168.1.1 in hexadecimal
char *ip_str = inet_ntoa(ip_addr);
printf("IP address in dotted-decimal format: %s\n", ip_str);
return 0;
}
在此示例中,我们创建了一个 struct in_addr
结构体,设置了 IP 地址(以网络字节序),然后使用 inet_ntoa()
将它转换为字符串。
上述程序的运行结果为:
majn@tiger:~/C_Project/network_project$ ./inet_ntoa_demo
IP address in dotted-decimal format: 192.168.1.1