在 Linux 系統中,網卡統計資訊 (接收/發送位元組、封包數、錯誤、丟包等) 由核心維護,透過虛擬檔案系統 **/proc/net/dev** 公開給使用者空間。ifconfig、ip -s link、netstat -i 等工具本質上都是解析此檔案。
$ cat /proc/net/dev
Inter-| Receive | Transmit
face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
lo: 123456 1234 0 0 0 0 0 0 123456 1234 0 0 0 0 0 0
eth0: 987654321 123456 0 0 0 0 0 0 123456789 123456 0 0 0 0 0 0
wlan0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
每行欄位 (以 eth0 為例):
| 索引 | 欄位名 | 說明 |
|---|---|---|
| 1 | Interface | 介面名稱 (含尾隨 :) |
| 2 | rx_bytes | 接收位元組數 ← 重點 |
| 3 | rx_packets | 接收封包數 |
| 4 | rx_errs | 接收錯誤 |
| 5 | rx_drop | 接收丟包 |
| 6 | rx_fifo | FIFO 錯誤 |
| 7 | rx_frame | 幀錯誤 |
| 8 | rx_compressed | 壓縮封包 |
| 9 | rx_multicast | 多播封包 |
| 10 | tx_bytes | 發送位元組數 ← 重點 |
| 11 | tx_packets | 發送封包數 |
| 12 | tx_errs | 發送錯誤 |
| 13 | tx_drop | 發送丟包 |
| 14 | tx_fifo | FIFO 錯誤 |
| 15 | tx_colls | 碰撞 |
| 16 | tx_carrier | 載波錯誤 |
| 17 | tx_compressed | 壓縮封包 |
#include#include #include #include #include #include typedef struct { unsigned long long rx_bytes; unsigned long long tx_bytes; const char *iface; } NetStats; // 回傳 0 成功,-1 失敗 int get_net_stats(const char *iface, NetStats *out) { if (!iface || !out) return -1; if (strlen(iface) > 100) return -1; // 防禦 int fd = open("/proc/net/dev", O_RDONLY); if (fd < 0) { perror("open /proc/net/dev"); return -1; } char buf[4096]; ssize_t n = read(fd, buf, sizeof(buf) - 1); close(fd); if (n <= 0) { if (n < 0) perror("read"); return -1; } buf[n] = '\0'; // 尋找介面名 (格式: "eth0:") char target[128]; snprintf(target, sizeof(target), "%s:", iface); char *line = strstr(buf, target); if (!line) { fprintf(stderr, "介面 %s 不存在\n", iface); return -1; } // 解析該行數值 (跳過介面名) line = strchr(line, ':'); if (!line) return -1; line++; // 跳過 ':' // 使用 sscanf 直接提取 rx_bytes (第1欄) 和 tx_bytes (第9欄) unsigned long long rx, tx; // 格式: %*s 跳過 rx_packets, rx_errs... 共 7 個欄位 if (sscanf(line, "%llu %*u %*u %*u %*u %*u %*u %*u %llu", &rx, &tx) != 2) { fprintf(stderr, "解析失敗\n"); return -1; } out->rx_bytes = rx; out->tx_bytes = tx; out->iface = iface; return 0; } // 人性化顯示 (自動單位) void print_human(unsigned long long bytes, const char *label) { const char *units[] = {"B", "KB", "MB", "GB", "TB"}; int i = 0; double val = bytes; while (val >= 1024 && i < 4) { val /= 1024; i++; } printf("%s: %.2f %s\n", label, val, units[i]); } int main(int argc, char *argv[]) { const char *iface = (argc > 1) ? argv[1] : "eth0"; NetStats stats; if (get_net_stats(iface, &stats) == 0) { printf("介面: %s\n", iface); print_human(stats.rx_bytes, "接收流量"); print_human(stats.tx_bytes, "發送流量"); } else { fprintf(stderr, "獲取失敗\n"); return 1; } return 0; }
$ gcc -o netstat_netdev netstat_netdev.c $ ./netstat_netdev eth0 介面: eth0 接收流量: 941.82 MB 發送流量: 117.76 MB $ ./netstat_netdev lo 介面: lo 接收流量: 120.63 KB 發送流量: 120.63 KB
#includeint main() { const char *iface = "eth0"; NetStats prev, curr; get_net_stats(iface, &prev); while (1) { sleep(1); if (get_net_stats(iface, &curr) != 0) break; unsigned long long rx_rate = curr.rx_bytes - prev.rx_bytes; unsigned long long tx_rate = curr.tx_bytes - prev.tx_bytes; printf("\r%s: ↓ %6.2f KB/s ↑ %6.2f KB/s", iface, rx_rate / 1024.0, tx_rate / 1024.0); fflush(stdout); prev = curr; } return 0; }
| 方法 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 讀取 /proc/net/dev | 無需權限、極輕量、標準 Linux | 需自行解析、32位系統計數器可能溢位 | 嵌入式、簡單監控、無 lib 依賴 |
| libnl (netlink) | 官方 API、支援完整屬性、非同步通知 | 需連結庫、學習曲線陡 | NetworkManager、複雜網管工具 |
| getifaddrs + ioctl (SIOCETHTOOL) | POSIX 可攜、可取介面旗標/MTU | 不直接給計數器、需 ioctl | 跨平台 (BSD/macOS 也支援) |
| system("ifconfig") / popen | 快速原型 | 效能差、格式依賴、安全風險 | 臨時腳本、不推薦生產 |
unsigned long 4GB 即溢位,建議用 unsigned long long (64 位) 讀取,核心 2.6+ 已支援 64 位計數器。docker0, veth*, br-* 等也會出現,按名稱過濾。直接解析 /proc/net/dev 是 Linux 下獲取網卡流量最輕量、無依賴、標準的方法。掌握其欄位定義與 sscanf 解析技巧,即可輕鬆實作流量監控、頻寬統計、異常流量告警等功能,無需依賴重型庫或外部命令。