首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C 語言獲取 Linux 主機 IP 地址:ioctl + SIOCGIFADDR 經典實現

     編輯:關於C語言

核心原理

Linux 通過 ioctl 系統調用與網絡設備驅動交互。獲取 IPv4 地址的標準流程:

  1. 創建 AF_INET + SOCK_DGRAM Socket(僅作 ioctl 控制通道,不通信)
  2. 構造 struct ifreq,設置接口名(如 eth0)
  3. 調用 ioctl(fd, SIOCGIFADDR, &ifreq) 獲取地址
  4. 從 ifreq.ifr_addr 提取 struct sockaddr_in,轉換為點分十進制

完整可運行代碼


/**********************************************************************
 * 獲取指定網卡 IPv4 地址
 * 編譯: gcc -Wall -o getip getip.c
 * 運行: ./getip
 **********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <errno.h>

#define IFACE_NAME "eth0"  // 目標網卡名,可改為參數

int main() {
    int sockfd;
    struct ifreq ifr;
    struct sockaddr_in *sin;
    char ip_str[INET_ADDRSTRLEN];

    /* 1. 創建 Socket */
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket 失敗");
        return 1;
    }

    /* 2. 設置接口名 */
    strncpy(ifr.ifr_name, IFACE_NAME, IFNAMSIZ - 1);
    ifr.ifr_name[IFNAMSIZ - 1] = '\0';  // 確保結束符

    /* 3. ioctl 獲取地址 */
    if (ioctl(sockfd, SIOCGIFADDR, &ifr) < 0) {
        perror("ioctl SIOCGIFADDR 失敗");
        close(sockfd);
        return 1;
    }

    /* 4. 提取並轉換 IP */
    sin = (struct sockaddr_in *)&ifr.ifr_addr;
    if (inet_ntop(AF_INET, &sin->sin_addr, ip_str, sizeof(ip_str)) == NULL) {
        perror("inet_ntop 失敗");
        close(sockfd);
        return 1;
    }

    printf("接口 %s: %s\n", IFACE_NAME, ip_str);
    close(sockfd);
    return 0;
}

關鍵結構體定義


// 簡化版定義 (實際在頭文件中)
struct ifreq {
    char ifr_name[IFNAMSIZ];   // 接口名,如 "eth0"
    union {
        struct sockaddr ifr_addr;     // 地址
        struct sockaddr ifr_dstaddr;  // 目標地址
        struct sockaddr ifr_broadaddr;// 廣播地址
        struct sockaddr ifr_netmask;  // 子網掩碼
        // ...
    } ifr_ifru;
};

struct sockaddr_in {
    sa_family_t sin_family;   // AF_INET
    in_port_t sin_port;       // 端口 (此處不使用)
    struct in_addr sin_addr;  // IPv4 地址
};

struct in_addr {
    in_addr_t s_addr;         // 32位網絡序地址
};

現代替代方案:getifaddrs()

getifaddrs() (BSD 擴展,Linux 支持) 可一次性獲取所有接口地址,無需逐個 ioctl:


#include <ifaddrs.h>

void print_all_ips() {
    struct ifaddrs *ifaddr, *ifa;
    if (getifaddrs(&ifaddr) == -1) { perror("getifaddrs"); return; }
    
    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
        if (ifa->ifa_addr == NULL) continue;
        if (ifa->ifa_addr->sa_family == AF_INET) {
            struct sockaddr_in *sa = (struct sockaddr_in *)ifa->ifa_addr;
            char ip[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &sa->sin_addr, ip, sizeof(ip));
            printf("%s: %s\n", ifa->ifa_name, ip);
        }
    }
    freeifaddrs(ifaddr);
}

優勢:枚舉所有接口、支持 IPv6、代碼更簡潔。缺點:非 POSIX 標準(但主流系統均支持)。

常見問題排查

現象原因解決
ioctl 返回 -1, errno=ENODEV接口名錯誤或不存在用 ip link 或 ifconfig -a 確認名稱
獲取到 127.0.0.1查詢了 lo 回環接口指定物理接口名 (eth0, ens33, enp3s0 等)
多網卡環境需遍歷所有接口用 getifaddrs() 或解析 /proc/net/dev
權限不足非 root 無法創建原始 SocketSOCK_DGRAM 通常無需 root,SOCK_RAW 需要

擴展:同時獲取 MAC、掩碼、廣播地址


// 同一 socket 可複用,依次調用不同 ioctl 命令
ioctl(sockfd, SIOCGIFHWADDR, &ifr);  // MAC 地址
ioctl(sockfd, SIOCGIFNETMASK, &ifr); // 子網掩碼
ioctl(sockfd, SIOCGIFBRDADDR, &ifr); // 廣播地址

MAC 地址在 ifr.ifr_hwaddr.sa_data (6 位元組),需手動格式化為 xx:xx:xx:xx:xx:xx。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved