
简介这是一份面向高校计算机与网络相关专业学生的Java Web课程设计资源主题为跨平台网络流量实时监控与分析软件。项目采用Java完成后台开发前端以Web客户端形式呈现主要解决无图形界面操作系统或远程目标机难以本地展示流量数据的问题通过浏览器即可完成数据接收与分析同时兼顾传输安全性与运行稳定性适合作为课程设计参考或Java Web综合练习项目。压缩包共77个文件约11.31MB以27个Java源码和15个JavaScript、9个JSX文件为核心辅以Gradle构建脚本、HTML页面、JSON配置、证书与密钥文件等整体结构覆盖后端逻辑、前端界面与安全通信模块。目前已有323人学习下载。资源内含课程设计报告书、任务说明文档及可执行jar包便于读者理解需求分析、系统架构与实现思路并对照源码梳理流量采集、Web展示与安全传输等关键环节快速完成同类课程设计或二次开发。1. 从一次线上卡顿说起Java 做 Web 流量分析到底在分析什么有次生产环境接口大面积超时运维第一反应是带宽被打满结果抓包一看出口流量才用了三成。真正的问题藏在请求分布里某个内部服务在疯狂重试QPS 涨了二十倍但每次请求体都很小带宽指标根本看不出来。这件事让我彻底信了——网络流量分析不能只盯着总带宽得拆到会话、协议、请求路径这一层去看。而用Java来做这件事最大的好处是它天生就在 Web 服务生态里你写的分析程序可以直接复用业务侧的线程模型、Netty 栈和 JSON 处理库不用在语言之间来回倒腾数据。这个标题讲的就是这么一件事用 Java 实现一套面向 Web 场景的流量分析软件。它要解决的不是「抓包」本身抓包工具已经够多了它要解决的是把抓到的原始字节变成能回答「谁在什么时候、用什么协议、访问了哪个路径、耗时多少、有没有异常」的结构化结论。适合谁看适合已经会写 Java、但没系统做过流量侧工具的 Web 后端和运维开发。如果你只会调 Wireshark 界面或者只会看 Nginx 日志这篇文章会帮你把中间那层「自己写分析器」补上。2. 抓包与解析Java 侧的三条技术路线怎么选2.1 从网卡到字节数组pcap 与原生抓包的取舍Java 本身没有直接读网卡的能力绕不开原生库。常见做法是走 jNetPcap 或 pcap4j 这类封装底层还是 libpcap。选它们的原因是 API 稳定、协议解析器现成缺点是部署时要带.so/.dll容器里得给NET_RAW权限。另一条路是纯 Java 的 Netty 自己起代理让流量从你的程序里过这样不碰网卡、权限干净但只能分析经过你的流量覆盖不到旁路镜像。我一般这样判断要做旁路监控、分析全量流量用 pcap4j要做应用内埋点式的流量分析用 Netty 代理。下面是一个 pcap4j 打开网卡并抓一个包的最小例子。// 依赖org.pcap4j:pcap4j-core 与 pcap4j-packetfactory-static PcapNetworkInterface nif Pcaps.getDevByName(eth0); // snaplen 设 65536 保证不截断大包promisc 打开混杂模式 PcapHandle handle nif.openLive(65536, PcapNetworkInterface.PromiscuousMode.PROMISCUOUS, 10); // 只抓 TCP 80/443减少无关包进入解析层 handle.setFilter(tcp port 80 or tcp port 443, BpfCompileMode.OPTIMIZE); Packet packet handle.getNextPacketEx(); IpV4Packet ip packet.get(IpV4Packet.class); TcpPacket tcp packet.get(TcpPacket.class); System.out.println(ip.getHeader().getSrcAddr() - tcp.getHeader().getDstPort());逻辑说明openLive的第三个参数是读超时毫秒数设太小会频繁空转设太大会让关闭变慢10 到 50 之间比较稳。setFilter用的是 BPF 语法能在内核层就把不关心的包丢掉这一步对性能影响极大千万别抓完再在 Java 里过滤。参数上snaplen如果设成 96 这种小值你会拿到截断的包解析 HTTP 头时直接报错这是新手最容易翻车的地方。2.2 协议解析为什么不能只靠端口号判断拿到字节后要判断协议。很多人图省事看到 80 就当 HTTP、443 就当 TLS这在今天基本等于自欺欺人——gRPC 跑在 443 上、内部服务把 HTTP 挂在 8080 上、WebSocket 升级后还是同一条连接。可靠的做法是「端口初判 内容特征二次确认」。HTTP 的确认很简单看开头是不是GET、POST、HTTP/1.。TLS 看第一个字节是不是0x16握手记录类型后面两字节是不是版本号。WebSocket 则要看 HTTP 头里有没有Upgrade: websocket。下面这段做内容嗅探。// payload 是 TCP 载荷的字节数组 boolean isHttp(byte[] p) { if (p.length 4) return false; String head new String(p, 0, Math.min(p.length, 8), StandardCharsets.US_ASCII); return head.startsWith(GET ) || head.startsWith(POST ) || head.startsWith(HTTP/1.) || head.startsWith(PUT ); } boolean isTls(byte[] p) { // 0x16 handshake, 0x03 主流 TLS 主版本 return p.length 3 (p[0] 0xFF) 0x16 (p[1] 0xFF) 0x03; }逻辑说明 0xFF是把有符号 byte 转成无符号整数再比较Java 里 byte 是有符号的直接和0x16比在某些值上会出错这是血泪经验。参数上嗅探只看前几个字节就够不要为了「更准」去扫全包那会让 CPU 直接起飞。如果一段流里前几个包是 TLS 握手、后面才是应用数据你需要按连接维护状态而不是逐包独立判断。2.3 流重组把散落的包拼回一次请求TCP 是字节流一个 HTTP 请求可能被拆成好几个包顺序还可能乱。要分析出「一次请求耗时多少」必须做流重组。核心是用四元组源 IP、源端口、目的 IP、目的端口做 key把同一连接的双向包按序列号排序、去重、拼接。// 用四元组标识一条流双向各存一个方向 String flowKey(String src, int sport, String dst, int dport) { // 排序保证 A-B 和 B-A 落到同一个 key return src.compareTo(dst) 0 ? src : sport - dst : dport : dst : dport - src : sport; }逻辑说明key 里把两端排序是为了让请求和响应归到同一条流否则你统计耗时时会找不到对应的响应包。参数上序列号回绕超过 2^32要处理简单做法是用 32 位无符号比较加偏移量。重组缓冲区要设上限比如单流 1MB超了就丢弃并标记为「异常大流」不然内存会被慢速攻击撑爆。这一步做完你才有资格谈「请求耗时」「响应码分布」这些指标。3. 指标计算与存储让分析结果能查、能告警3.1 从流里抽出哪些指标才算有用重组完一条流能算的东西很多但真正有用的就那么几个请求方法、路径、状态码、请求体大小、响应体大小、首字节时间TTFB、总耗时。TTFB 尤其关键它把「服务端处理慢」和「网络传输慢」分开了。下面是从重组后的请求响应字节里抽 HTTP 元信息。// reqHead 是请求头字符串respHead 是响应头字符串 Pattern REQ_LINE Pattern.compile(^(\\w) (\\S) HTTP/1\\.[01]); Pattern STATUS Pattern.compile(^HTTP/1\\.[01] (\\d{3})); void extract(String reqHead, String respHead, long startNanos, long firstByteNanos, long endNanos) { Matcher m REQ_LINE.matcher(reqHead); if (m.find()) { String method m.group(1); // GET / POST String path m.group(2); // /api/order long ttfbMs (firstByteNanos - startNanos) / 1_000_000; long totalMs (endNanos - startNanos) / 1_000_000; // 落到指标对象后续聚合 } Matcher s STATUS.matcher(respHead); if (s.find()) { int code Integer.parseInt(s.group(1)); // 200 / 500 } }逻辑说明路径要归一化/api/order/123和/api/order/456应该归成/api/order/{id}否则指标基数会爆炸这是做 Web 流量分析绕不开的一步。参数上时间统一用纳秒采集、毫秒输出避免中途精度丢失。状态码按 2xx/3xx/4xx/5xx 分桶统计比逐个码存更省空间也更好看趋势。3.2 存储选型时序库还是自己写聚合指标算出来后要存。常见做法是写进时序数据库比如 InfluxDB 或 Prometheus 的 remote write也可以先用内存里的滑动窗口做聚合只把分钟级结果落库。选哪种取决于你的查询需求要按路径、按状态码、按时间范围任意切片就上时序库只关心「最近五分钟有没有异常」内存聚合加告警就够了。我一般会先用一个ConcurrentHashMap做分钟级聚合key 是「路径 状态码」value 是计数和耗时直方图每分钟刷一次到存储。这样即使存储挂了分析程序也不会被拖死。参数上滑动窗口大小设 1 分钟、保留 5 个窗口能覆盖大多数突发检测场景。直方图的桶边界要按你的业务定比如 10ms、50ms、100ms、500ms、1s、5s别用默认的等距桶Web 请求的耗时分布是长尾的。3.3 异常检测阈值告警为什么总在半夜误报最简单的异常检测是阈值5xx 比例超过 1% 就告警。但线上流量有昼夜规律固定阈值要么白天漏报、要么半夜误报。更稳的做法是用同比拿当前窗口和昨天同一时刻比或者用滑动平均加标准差超过 3 倍标准差才报。// 用过去 N 个窗口的均值与标准差判断当前是否异常 boolean isAnomaly(double current, double[] history) { double mean Arrays.stream(history).average().orElse(0); double var Arrays.stream(history).map(v - (v - mean) * (v - mean)).average().orElse(0); double std Math.sqrt(var); // 标准差为 0 说明流量极稳用均值 50% 作为兜底阈值 double threshold std 0 ? mean * 1.5 : mean 3 * std; return current threshold; }逻辑说明历史窗口至少要有 10 个才有统计意义太少会让标准差抖动。参数上3 倍标准差对应大约 99.7% 的置信区间想更灵敏就降到 2 倍但误报会变多。这里要提醒一句异常检测的输入必须是归一化后的指标比如「5xx 占该路径总请求的比例」而不是 5xx 的绝对数量否则流量一涨就全在告警。4. 避坑与排查那些让分析结果失真的细节4.1 现象统计到的请求数比 Nginx 日志少一半原因通常是抓包点选错了。如果你在容器里抓eth0而流量实际走的是veth对或 overlay 网络很多包根本到不了你的网卡。另一个常见原因是 BPF 过滤器写得太窄比如只抓了tcp port 80把 443 的流量全漏了。解决方法是先用tcpdump -i any确认流量到底从哪个接口过再把过滤器放宽到tcp在应用层做协议判断。4.2 现象内存持续上涨几小时后 OOM流重组的状态没清理。TCP 连接关闭后对应的流状态还留在 map 里长连接和半开连接会越积越多。解决方法是给每条流加最后活跃时间后台线程定期扫描超过 60 秒没新包的流直接淘汰。参数上淘汰周期设 10 秒、超时设 60 秒能覆盖绝大多数正常请求又不至于让内存失控。4.3 现象TTFB 算出来是负数或大得离谱时间戳来源不一致。请求开始时间用的是抓包时间首字节时间用的是应用日志时间两个时钟没对齐。解决方法是所有时间戳都从同一个抓包源取或者统一用System.nanoTime()在同一个进程内采集。跨机器做时间对齐时NTP 偏差要控制在毫秒级否则 TTFB 这个指标就没有意义。4.4 现象路径基数爆炸存储写不进去没做路径归一化。/user/1、/user/2被当成不同路径几百万用户就是几百万个时间线。解决方法是用正则把数字、UUID、长哈希替换成占位符比如/user/{id}。参数上归一化规则要按业务定制别用一条通用正则硬套否则会把/api/v2/order里的2也替换掉。4.5 现象分析程序自己把 CPU 吃满逐包做字符串构造和正则匹配。每个包都new String再跑正则GC 和 CPU 都扛不住。解决方法是先做字节级快速判断只有确认是 HTTP 头部的包才转字符串正则预编译成static final别在循环里Pattern.compile。参数上给分析线程设个 CPU 亲和或限流别让它和业务进程抢资源。5. 进阶把分析结果接回 Web 侧做闭环做到这里你已经有一套能跑的分析器了。但真正让这套东西值钱的是把它接回 Web 侧形成闭环。我一般会做两件事一是把异常指标通过一个内部 HTTP 接口暴露出去让网关或服务发现组件能主动拉取实现「发现某路径 5xx 突增就自动摘流量」二是把慢请求的原始流样本存下来出问题时能直接回放而不是只看到一个数字。验证分析结果准不准有个笨但有效的办法拿一段时间的分析结果和 Nginx access log 做对账。请求总数、状态码分布、Top 路径这三个维度对得上说明你的抓包、重组、归一化链路是通的。对不上就按第 4 章的排查顺序往回找先看抓包点再看过滤器最后看归一化规则。// 暴露一个极简的指标查询接口供网关拉取 // 依赖com.sun.net.httpserver.HttpServerJDK 自带无需额外依赖 HttpServer server HttpServer.create(new InetSocketAddress(9090), 0); server.createContext(/metrics/anomaly, exchange - { String body anomalyPathsJson(); // 返回当前异常路径列表 exchange.sendResponseHeaders(200, body.getBytes().length); exchange.getResponseBody().write(body.getBytes()); exchange.close(); }); server.start();逻辑说明用 JDK 自带的 HttpServer 是为了不引入额外依赖生产环境换成你熟悉的框架即可。参数上这个接口要加访问控制别裸奔在公网返回体只给路径和异常类型别把原始流量数据吐出去。回放样本要脱敏请求头里的 Cookie、Authorization 必须过滤掉这是合规底线。最后说个我自己的习惯每次上线新的分析规则我都会先让它「只观察不告警」跑一天把结果和人工判断对一遍确认误报率可接受再打开告警。流量分析这东西规则写得再漂亮没经过真实流量验证都是纸上谈兵。希望帮到你。本文还有配套的精品资源点击获取