Linux

关注公众号 jb51net

关闭
首页 > 网站技巧 > 服务器 > Linux > Linux监控网络流量

Linux监控系统网络流量的工具大全

作者:Jinkxs

在当今高度互联的世界中,网络流量监控已成为系统运维、安全审计和性能优化的重要组成部分,Linux作为广泛部署的操作系统,提供了丰富多样的工具和接口用于监控网络流量,本文将深入探讨这些工具,需要的朋友可以参考下

在当今高度互联的世界中,网络流量监控已成为系统运维、安全审计和性能优化的重要组成部分。无论是排查网络瓶颈、检测异常行为,还是进行带宽管理,实时掌握服务器或主机的网络吞吐情况都至关重要。Linux作为广泛部署的操作系统,提供了丰富多样的工具和接口用于监控网络流量。本文将深入探讨这些工具,并通过Java代码示例展示如何在应用程序层面实现网络流量采集与分析。

为什么需要监控网络流量?

网络流量监控不仅仅是“看数据跑得多快”,它关系到:

根据Sysdig 2023年度云原生安全与使用报告,超过67%的企业在生产环境中遭遇过因未监控网络流量导致的安全事件。因此,构建有效的网络监控体系是现代IT基础设施不可或缺的一环。

Linux内置网络监控工具概览

Linux内核提供了多个层次的网络信息暴露接口,用户空间程序可通过这些接口获取实时或历史流量数据。以下是一些常用工具:

1.iftop—— 实时带宽使用仪表盘

iftop 是一个类似 top 的实时网络带宽监控工具,可显示每个连接的实时速率(bps)。

sudo iftop -i eth0

输出示例:

interface: eth0
IP address is: 192.168.1.100
MAC address is: aa:bb:cc:dd:ee:ff

                        12.5Kb  25.0Kb  37.5Kb  50.0Kb  62.5Kb
└──────────────────────────────────────────────────────────────
192.168.1.101         => 104.16.109.240       1.23Kb  2.45Kb  3.11Kb
                      <=                      567b    1.12Kb  1.45Kb
192.168.1.102         => 203.0.113.5          890b    1.78Kb  2.01Kb
                      <=                      321b    642b    789b

提示:安装方式:sudo apt install iftop 或 yum install iftop

官方文档:https://www.ex-parrot.com/pdw/iftop/

2.nethogs—— 按进程划分的流量统计

不同于 iftop 按连接统计,nethogs 将流量归因于具体进程,非常适合排查“哪个程序在偷偷上传数据”。

sudo nethogs eth0

输出示例:

NetHogs version 0.8.6

  PID USER     PROGRAM                            DEV        SENT   RECEIVED       
 1234 root     /usr/bin/docker-proxy              eth0      2.34M     5.67M
 5678 www-data /usr/sbin/apache2                  eth0      1.23M     3.45M
 9012 user     /opt/myapp/java -jar app.jar       eth0    890.1K    1.23M

官网:https://github.com/raboof/nethogs (注:仅提供链接,不展开)

3.ss和netstat—— 连接状态快照

虽然不直接显示流量速率,但能列出所有活跃连接及其状态,常用于辅助分析。

ss -tuln

输出:

Netid  State    Recv-Q   Send-Q     Local Address:Port     Peer Address:Port  
tcp    LISTEN   0        100            127.0.0.1:3306          0.0.0.0:*     
tcp    ESTAB    0        0           192.168.1.100:54321   203.0.113.10:443

4./proc/net/dev—— 内核级原始数据源

最底层的网络统计信息存储在 /proc/net/dev 文件中,包含每个网卡自启动以来的总收发字节数、包数、错误数等。

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: 1234567   8901    0    0    0     0          0         0  1234567   8901    0    0    0     0       0          0
  eth0: 98765432 56789    0    0    0     0          0         0  12345678 23456    0    0    0     0       0          0

这是后续我们用 Java 编程读取的核心数据源!

网络监控架构设计思路

在构建自己的监控系统前,先理清整体架构。我们可以采用分层模型:

渲染错误: Mermaid 渲染失败: Lexical error on line 7. Unrecognized text. ... A1[/proc/net/dev] A2[Netlink -----------------------^

这个架构允许我们灵活替换各组件。例如,若不想依赖外部数据库,可只保留 CLI 输出;若追求高性能,可用 eBPF 替代 /proc 读取。

使用Java读取并解析/proc/net/dev

现在进入实战环节!我们将用 Java 编写一个轻量级网络流量监控器,定期读取 /proc/net/dev 并计算每秒收发速率。

第一步:定义数据结构

第二步:解析/proc/net/dev文件

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ProcNetDevParser {

    private static final String PROC_NET_DEV_PATH = "/proc/net/dev";
    private static final Pattern INTERFACE_PATTERN = 
        Pattern.compile("^\\s*(\\w+):\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+\\d+\\s+\\d+\\s+\\d+\\s+\\d+\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)");

    public static List<NetworkInterfaceStats> parse() throws IOException {
        List<NetworkInterfaceStats> statsList = new ArrayList<>();
        
        try (BufferedReader reader = new BufferedReader(new FileReader(PROC_NET_DEV_PATH))) {
            String line;
            // 跳过前两行标题
            reader.readLine();
            reader.readLine();

            while ((line = reader.readLine()) != null) {
                Matcher matcher = INTERFACE_PATTERN.matcher(line);
                if (matcher.find()) {
                    NetworkInterfaceStats stats = new NetworkInterfaceStats();
                    stats.setInterfaceName(matcher.group(1));
                    stats.setReceiveBytes(Long.parseLong(matcher.group(2)));
                    stats.setReceivePackets(Long.parseLong(matcher.group(3)));
                    stats.setReceiveErrors(Long.parseLong(matcher.group(4)));
                    stats.setTransmitBytes(Long.parseLong(matcher.group(5)));
                    stats.setTransmitPackets(Long.parseLong(matcher.group(6)));
                    stats.setTransmitErrors(Long.parseLong(matcher.group(7)));

                    statsList.add(stats);
                }
            }
        }

        return statsList;
    }
}

第三步:计算每秒速率(delta)

由于 /proc/net/dev 提供的是累计值,我们需要两次采样并计算差值。

import java.util.HashMap;
import java.util.Map;
public class NetworkTrafficMonitor {
    private Map<String, NetworkInterfaceStats> lastSnapshot = new HashMap<>();
    private long lastTimestamp = 0;
    public void startMonitoring(long intervalMillis) {
        System.out.println("🚀 开始监控网络流量... 按 Ctrl+C 停止");
        while (!Thread.currentThread().isInterrupted()) {
            try {
                Thread.sleep(intervalMillis);
                captureAndPrintDelta();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    private void captureAndPrintDelta() throws Exception {
        long currentTimestamp = System.currentTimeMillis();
        List<NetworkInterfaceStats> currentSnapshot = ProcNetDevParser.parse();
        if (lastTimestamp == 0) {
            // 首次采样,仅保存快照
            for (NetworkInterfaceStats stat : currentSnapshot) {
                lastSnapshot.put(stat.getInterfaceName(), stat);
            }
            lastTimestamp = currentTimestamp;
            return;
        }
        long timeDeltaSec = (currentTimestamp - lastTimestamp) / 1000.0;
        System.out.println("\n📊 === 网络流量报告 (" + java.time.LocalDateTime.now() + ") ===");
        System.out.printf("%-10s %-12s %-12s %-12s %-12s%n", 
            "接口", "RX 字节/s", "TX 字节/s", "RX 包/s", "TX 包/s");
        for (NetworkInterfaceStats current : currentSnapshot) {
            NetworkInterfaceStats last = lastSnapshot.get(current.getInterfaceName());
            if (last == null) continue;
            double rxBytesPerSec = (current.getReceiveBytes() - last.getReceiveBytes()) / timeDeltaSec;
            double txBytesPerSec = (current.getTransmitBytes() - last.getTransmitBytes()) / timeDeltaSec;
            double rxPacketsPerSec = (current.getReceivePackets() - last.getReceivePackets()) / timeDeltaSec;
            double txPacketsPerSec = (current.getTransmitPackets() - last.getTransmitPackets()) / timeDeltaSec;
            System.out.printf("%-10s %-12.0f %-12.0f %-12.0f %-12.0f%n",
                current.getInterfaceName(),
                rxBytesPerSec,
                txBytesPerSec,
                rxPacketsPerSec,
                txPacketsPerSec);
        }
        // 更新快照
        lastSnapshot.clear();
        for (NetworkInterfaceStats stat : currentSnapshot) {
            lastSnapshot.put(stat.getInterfaceName(), stat);
        }
        lastTimestamp = currentTimestamp;
    }
}

第四步:主程序入口

public class Main {
    public static void main(String[] args) {
        NetworkTrafficMonitor monitor = new NetworkTrafficMonitor();
        monitor.startMonitoring(2000); // 每2秒采样一次
    }
}

运行效果示例:

🚀 开始监控网络流量... 按 Ctrl+C 停止
📊 === 网络流量报告 (2024-06-15T10:30:45.123) ===
接口         RX 字节/s    TX 字节/s    RX 包/s      TX 包/s     
lo         0            0            0            0           
eth0       15234        8976         45           32          
wlan0      0            0            0            0           
📊 === 网络流量报告 (2024-06-15T10:30:47.125) ===
接口         RX 字节/s    TX 字节/s    RX 包/s      TX 包/s     
lo         0            0            0            0           
eth0       18765        9876         52           38          
wlan0      0            0            0            0           

增强功能:过滤虚拟接口 & 单位转换

真实环境中,lodocker0veth* 等虚拟接口可能干扰监控。我们可以添加过滤逻辑:

private boolean shouldIgnoreInterface(String name) {
    return name.equals("lo") || 
           name.startsWith("docker") || 
           name.startsWith("veth") ||
           name.startsWith("br-") ||
           name.startsWith("kube");
}

并在打印前加入判断:

if (shouldIgnoreInterface(current.getInterfaceName())) {
    continue;
}

同时,为提升可读性,可将字节转换为 KB/s、MB/s:

private String formatBytes(double bytes) {
    if (bytes < 1024) return String.format("%.0f B/s", bytes);
    else if (bytes < 1024 * 1024) return String.format("%.1f KB/s", bytes / 1024);
    else return String.format("%.2f MB/s", bytes / (1024 * 1024));
}

修改打印语句:

System.out.printf("%-10s %-12s %-12s %-12.0f %-12.0f%n",
    current.getInterfaceName(),
    formatBytes(rxBytesPerSec),
    formatBytes(txBytesPerSec),
    rxPacketsPerSec,
    txPacketsPerSec);

输出更友好:

接口         RX 字节/s    TX 字节/s    RX 包/s      TX 包/s     
eth0       18.3 KB/s    9.6 KB/s     52           38          
ens33      2.1 MB/s     1.8 MB/s     1200         980         

高级监控:集成 Prometheus 指标导出

如果你希望将数据接入企业级监控平台(如 Prometheus + Grafana),可以使用 Prometheus Java Client 导出指标。

添加 Maven 依赖:

<dependency>
    <groupId>io.prometheus</groupId>
    <artifactId>simpleclient</artifactId>
    <version>0.16.0</version>
</dependency>
<dependency>
    <groupId>io.prometheus</groupId>
    <artifactId>simpleclient_httpserver</artifactId>
    <version>0.16.0</version>
</dependency>

创建自定义 Collector:

import io.prometheus.client.Collector;
import io.prometheus.client.GaugeMetricFamily;
import java.util.ArrayList;
import java.util.List;
public class NetworkTrafficCollector extends Collector {
    @Override
    public List<MetricFamilySamples> collect() {
        List<MetricFamilySamples> mfs = new ArrayList<>();
        try {
            List<NetworkInterfaceStats> stats = ProcNetDevParser.parse();
            GaugeMetricFamily rxBytes = new GaugeMetricFamily(
                "network_interface_receive_bytes_total",
                "Total number of bytes received",
                List.of("interface")
            );
            GaugeMetricFamily txBytes = new GaugeMetricFamily(
                "network_interface_transmit_bytes_total",
                "Total number of bytes transmitted",
                List.of("interface")
            );
            for (NetworkInterfaceStats stat : stats) {
                if (shouldIgnoreInterface(stat.getInterfaceName())) continue;
                rxBytes.addMetric(List.of(stat.getInterfaceName()), stat.getReceiveBytes());
                txBytes.addMetric(List.of(stat.getInterfaceName()), stat.getTransmitBytes());
            }
            mfs.add(rxBytes);
            mfs.add(txBytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return mfs;
    }
}

启动 HTTP Server:

import io.prometheus.client.exporter.HTTPServer;
public class PrometheusExporter {
    public static void main(String[] args) throws Exception {
        // 注册自定义采集器
        new NetworkTrafficCollector().register();
        // 启动 HTTP 服务,默认端口 9091
        HTTPServer server = new HTTPServer(9091);
        System.out.println("✅ Prometheus metrics server started on http://localhost:9091/metrics");
        // 启动定时刷新(可选)
        new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    Thread.sleep(5000);
                    // 强制触发采集(实际由 Prometheus pull 触发)
                } catch (InterruptedException e) {
                    break;
                }
            }
        }).start();
    }
}

访问 http://localhost:9091/metrics 可看到:

# HELP network_interface_receive_bytes_total Total number of bytes received
# TYPE network_interface_receive_bytes_total gauge
network_interface_receive_bytes_total{interface="eth0"} 98765432.0
network_interface_receive_bytes_total{interface="ens33"} 123456789.0
# HELP network_interface_transmit_bytes_total Total number of bytes transmitted
# TYPE network_interface_transmit_bytes_total gauge
network_interface_transmit_bytes_total{interface="eth0"} 12345678.0
network_interface_transmit_bytes_total{interface="ens33"} 98765432.0

Prometheus Java Client 文档:https://prometheus.github.io/client_java/

可视化:终端动态图表(ASCII Art)

想在终端画个简单的趋势图?我们可以用字符绘制柱状图!

private void printBarChart(String label, double value, double maxExpected) {
    int barWidth = 30;
    int filled = (int) ((value / maxExpected) * barWidth);
    if (filled > barWidth) filled = barWidth;
    if (filled < 0) filled = 0;
    StringBuilder bar = new StringBuilder("[");
    for (int i = 0; i < filled; i++) {
        bar.append("█");
    }
    for (int i = filled; i < barWidth; i++) {
        bar.append(" ");
    }
    bar.append("]");
    System.out.printf("%-10s %s %.1f KB/s%n", label, bar, value / 1024);
}

在监控循环中调用:

printBarChart("↑ 发送", txBytesPerSec, 1024 * 1024); // 假设最大1MB/s
printBarChart("↓ 接收", rxBytesPerSec, 1024 * 1024);

输出效果:

↑ 发送     [██████████                    ] 18.3 KB/s
↓ 接收     [████████████████              ] 45.6 KB/s

虽然简陋,但在无图形界面环境下非常实用!

性能与精度考量

采样频率选择

建议默认使用 2~5 秒间隔,在突发流量场景下可临时调整为 1 秒。

多线程 vs 单线程

当前实现是单线程轮询。若需监控多个主机或复杂计算,可考虑:

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(this::captureSnapshot, 0, 2, TimeUnit.SECONDS);

安全与权限注意事项

读取 /proc/net/dev 通常不需要 root 权限,但某些受限环境(如容器、SELinux)可能限制访问。

解决方案:

确保运行用户有读取权限:

ls -l /proc/net/dev
# 应显示 -r--r--r--

若在 Docker 中运行,添加 --cap-add=NET_ADMIN 或挂载 /proc

docker run -v /proc:/hostproc:ro myapp

并在 Java 中读取 /hostproc/net/dev

使用 setcap 赋予 Java 程序能力(不推荐):

sudo setcap cap_net_admin+ep /path/to/java

替代方案:使用 Netlink Socket(进阶)

/proc/net/dev 是最简单的方式,但存在“轮询”开销。Linux 提供了更高效的 Netlink Socket 接口,支持事件驱动式监控。

虽然 Java 标准库不直接支持 Netlink,但可通过 JNA(Java Native Access)调用 C 函数。

示例伪代码:

// 使用 JNA 加载 libc
interface CLibrary extends Library {
    CLibrary INSTANCE = Native.load("c", CLibrary.class);
    int socket(int domain, int type, int protocol);
    int bind(int sockfd, Pointer addr, int addrlen);
    int recv(int sockfd, Pointer buf, int len, int flags);
}
// 创建 NETLINK_ROUTE 类型 socket
int sock = CLibrary.INSTANCE.socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);

完整实现较复杂,适合对性能有极致要求的场景。普通监控建议优先使用 /proc 方案。

Netlink 官方文档:https://man7.org/linux/man-pages/man7/netlink.7.html

打包与部署建议

使用 Fat JAR

通过 Maven Shade Plugin 打包所有依赖:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>3.4.1</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals><goal>shade</goal></goals>
            <configuration>
                <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>Main</mainClass>
                    </transformer>
                </transformers>
            </configuration>
        </execution>
    </executions>
</plugin>

构建后运行:

mvn package
java -jar target/network-monitor-1.0.jar

创建 systemd 服务(生产环境推荐)

创建 /etc/systemd/system/network-monitor.service

[Unit]
Description=Network Traffic Monitor
After=network.target
[Service]
Type=simple
User=root
ExecStart=/usr/bin/java -jar /opt/network-monitor.jar
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target

启用服务:

sudo systemctl daemon-reload
sudo systemctl enable network-monitor
sudo systemctl start network-monitor
sudo systemctl status network-monitor

自动化与告警集成

邮件告警示例

当流量超过阈值时发送邮件:

import javax.mail.*;
import javax.mail.internet.*;
import java.util.Properties;
public class EmailAlert {
    public static void sendAlert(String subject, String body) {
        Properties props = new Properties();
        props.put("mail.smtp.host", "smtp.example.com");
        props.put("mail.smtp.port", "587");
        props.put("mail.smtp.auth", "true");
        props.put("mail.smtp.starttls.enable", "true");
        Session session = Session.getInstance(props, new Authenticator() {
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication("user@example.com", "password");
            }
        });
        try {
            Message message = new MimeMessage(session);
            message.setFrom(new InternetAddress("monitor@example.com"));
            message.setRecipients(Message.RecipientType.TO, InternetAddress.parse("admin@example.com"));
            message.setSubject(subject);
            message.setText(body);
            Transport.send(message);
            System.out.println("📧 告警邮件已发送!");
        } catch (MessagingException e) {
            e.printStackTrace();
        }
    }
}

在监控循环中加入判断:

if (txBytesPerSec > 10 * 1024 * 1024) { // >10MB/s
    EmailAlert.sendAlert(
        "[ALERT] 高网络流量",
        String.format("接口 %s 发送速率: %.2f MB/s", 
            current.getInterfaceName(), txBytesPerSec / (1024*1024))
    );
}

云环境适配技巧

在 AWS EC2、Azure VM 或 GCP 实例中,网卡名称可能是 ens5eth0enp0s3 等,且可能存在弹性IP、NAT等复杂情况。

建议策略:

自动识别主网卡

ip route get 8.8.8.8 | awk '{print $5; exit}'

在 Java 中执行此命令获取默认路由接口。

忽略云平台虚拟接口

private boolean isCloudVirtualInterface(String name) {
    return name.startsWith("veth") || 
           name.startsWith("flannel") ||
           name.startsWith("cali") || // Calico CNI
           name.matches("tap.*") ||
           name.matches("tun.*");
}

使用云厂商监控API补充数据

虽然超出本文范围,但建议在混合架构中结合使用。

最佳实践总结

  1. 明确监控目标:是看总量、速率、还是异常?
  2. 选择合适粒度:接口级、进程级、连接级?
  3. 避免过度采样:2~5秒通常足够
  4. 设置合理阈值告警:避免误报
  5. 日志与指标分离:原始数据存日志,聚合指标进TSDB
  6. 权限最小化原则:不要用 root 运行监控程序
  7. 优雅关闭:注册 shutdown hook 清理资源
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    System.out.println("🛑 正在停止监控...");
    // 清理工作
}));

未来展望:eBPF 与可观测性融合

虽然本文基于传统 /proc 文件系统,但下一代 Linux 监控正朝向 eBPF(extended Berkeley Packet Filter) 发展。eBPF 允许在内核中安全运行沙箱程序,实现零拷贝、事件驱动的高性能监控。

结语

通过本文,我们不仅掌握了多种 Linux 网络监控工具的使用方法,还亲手用 Java 实现了一个功能完整的流量监控器。从读取 /proc/net/dev 到计算速率、单位转换、Prometheus 集成、终端绘图,每一步都贴近真实工程需求。

网络监控不是“一次性配置”,而是一个持续演进的过程。随着架构变化、业务增长,你的监控策略也应随之调整。希望本文为你打下坚实基础,助你在 Linux 系统管理与 Java 开发之路上更进一步!

以上就是Linux监控系统网络流量的工具大全的详细内容,更多关于Linux监控网络流量的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文