ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Java抓包实战:用Jpcap解析网络数据包与HTTP流量统计

Java抓包实战:用Jpcap解析网络数据包与HTTP流量统计 写Java写过一阵子的人多少都会遇到一个尴尬时刻代码调得没问题但网络请求就是不对怀疑数据发错了、端口不对、报文没出来可手里没有趁手的工具。Wireshark能抓包可那玩意主要是给人交互分析的没法直接集成到你自己的Java服务里。我之前接了一个校园网设备在线监测的小工具需求是要实时看局域网内哪些终端在向外发请求、访问了哪些域名当时第一反应就是抓包分析但Wireshark把包抓下来还得人工看根本没法跟后台系统对接。后来翻了一圈发现Java生态里做抓包用得最多的就是Jpcap。这库确实有些年头了但胜在够简单、够直接底层通过JNI调用libpcap/WinPcap上层直接给你封装好的TCPPacket、UDPPacket、ARPPacket对象你不用手写二进制报文解析拿包就能读端口、读IP、读Host。这篇教程就是我一整周踩坑的经验汇总从环境搭建、网卡选择到过滤规则、数据包解剖最后带一个完整的HTTP Host统计器适合第一次接触网络抓包、想在Java程序里直接分析流量的开发者。内容会比较长建议先收藏再照着敲。1. Java抓包为什么我会选Jpcap先把方案看明白1.1 Jpcap在技术栈里的位置计算机流量本质上是网卡上流过的一串字节要拿到这串字节操作系统层面得提供抓包能力。Unix-like系统普遍支持libpcapWindows老牌方案是WinPcap后来Npcap接棒。但这些库都是C写的Java程序没法直接调用必须有一层JNI桥接。Jpcap就是那个桥。它在Java侧暴露出JpcapCaptor、JpcapSender、NetworkInterface、Packet这些类底层把libpcap/WinPcap的能力包了起来。你写Java代码时根本不用碰C只需要调用JpcapCaptor.getDeviceList()拿到网卡列表用openDevice打开某张网卡设置过滤条件在回调里接收Packet对象用instanceof判断是TCP还是UDP直接读字段也就是说Jpcap帮我们把“从网卡上扒原始字节”和“按协议栈解析字节”这两件事都做了你拿到手的是已经按协议分好类的Java对象。对于教学、快速原型、中小流量的业务工具来说这层封装非常值。1.2 和Wireshark、tcpdump的定位差异很多人一听说抓包就想到Wireshark但Wireshark解决的是“人工分析”问题它不适合你写一段程序然后每天凌晨自动抓一段流量做统计。tcpdump适合命令行操作但你要把它嵌入到Java业务代码里靠ProcessBuilder起子进程然后再去解析文本输出又绕又脆报文里一个转义字符就能把解析搞崩。Jpcap的正确使用场景是抓包逻辑是你自己程序的一部分。比如我在那个校园网工具里就是启动一个后台线程loopPacket持续抓包按目标域名做计数再把结果写进数据库展示。这类需求用Wireshark根本没法自动完成用tcpdump又要来回传数据用Jpcap则一切都在JVM里闭环。1.3 同类库对比为什么教学首选它Java抓包库还有JNetPcap、Pcap4J等。Pcap4J确实更新、支持更多新特性但它的抽象层次更底层写起来要关心很多pcap细节对新手并不友好。JNetPcap功能强但API复杂中文资料也少。Jpcap最大的优势是核心类很少全库加起来几十个类而已用到的就那几个。对第一次接触抓包的人来说心智负担小一个下午就能跑通。网上相关例子非常多出了问题也好搜。等你会了Jpcap再去玩Pcap4J理解成本会低很多。2. 环境搭建是最容易翻车的环节驱动、dll、JDK版本逐项说很多新手抓包代码明明照着抄的一运行就报UnsatisfiedLinkError问题基本都出在环境这一层。这一节把Windows环境下的搭建顺序完整走一遍。2.1 装驱动WinPcap还是NpcapJpcap底层依赖pcap接口Windows装机必须装一个抓包驱动。老教程都让你装WinPcap 4.1.3但WinPcap已经停止维护很多年新系统上可能出现兼容问题。现在更推荐装Npcap但有一个关键选项安装到最后一步时一定要勾选“Install Npcap in WinPcap API-compatible Mode”。Jpcap是通过WinPcap API去调用底层驱动的不勾这个兼容模式它可能根本找不到设备getDeviceList()返回空数组。这个坑我第一次装的时候就踩了排查了半天最后发现是安装选项的问题。装完之后可以在命令行确认驱动状态如果能看到网络接口信息说明底层抓包能力已经就绪。2.2 拿到jpcap.jar和jpcap.dll这两个文件驱动搞定后需要准备两个东西Java代码编译运行要用的jpcap.jar以及JVM加载native方法时要用的jpcap.dll。网上能下载到官方发布的压缩包里面通常包含jpcap.jar、jpcap.dll、示例代码和文档。注意版本号不用刻意追新老版本配合老驱动反而更稳。下载后先解压到一个固定目录我习惯放在D:\lib\jpcap\下面后面配置和排查都方便。2.3 jpcap.dll加载的三种姿势以及JDK版本的大坑Java代码里想加载native动态库可以用System.loadLibrary(jpcap)它会按java.library.path去搜索jpcap.dll。也可以直接用绝对路径加载System.load(D:/lib/jpcap/jpcap.dll);这两种写法都行但实际用起来差别很大姿势一把jpcap.dll复制到JAVA_HOME/jre/bin目录下。JDK 8及以前可以这么干JDK 9之后默认没有独立的jre目录了这个姿势就废了。姿势二用System.load(完整路径)。我推荐这种方式路径写在你自己的配置里换机器改一下配置就行不污染JDK目录。姿势三启动参数加-Djava.library.pathD:/lib/jpcap然后代码里System.loadLibrary(jpcap)。这种方式也灵活但要注意java.library.path如果没设置好会覆盖默认搜索路径反而影响其他native库加载。还有一个大坑Jpcap官方发布的dll是老古董很多是32位的。如果你本机装的是64位JDK加载32位dll会直接报错提示native library版本不匹配。稳妥的做法是学习阶段用32位JDK配合官方包全部按32位环境跑这样最省心。如果你非要用64位JDK就得自己找第三方编译的64位dll或者自己编译Jpcap源码难度直线上升新手不建议在这个阶段搞。2.4 环境是否就绪一个10行的自检程序配置完之后建议先跑一个最小验证别急着写抓包逻辑import jpcap.JpcapCaptor; import jpcap.NetworkInterface; public class JpcapEnvCheck { public static void main(String[] args) { try { System.load(D:/lib/jpcap/jpcap.dll); } catch (UnsatisfiedLinkError e) { System.out.println(dll路径不对或位数不匹配: e.getMessage()); return; } NetworkInterface[] devices JpcapCaptor.getDeviceList(); System.out.println(发现网卡数量: devices.length); for (NetworkInterface nif : devices) { System.out.println(名称: nif.name , 描述: nif.description); } } }如果能看到网卡数量大于0环境就通了。如果卡在第一步的异常上优先检查dll路径和位数。3. 抓包前先搞清网卡NetworkInterface到底能告诉你什么许多初学者上来就抓包结果抓到一堆莫名其妙的流量或者干脆抓不到原因就是没选对网卡。Jpcap把本机网卡抽象成NetworkInterface对象里面信息不少先看懂它再动手。3.1 一张网卡对象里有什么NetworkInterface的关键字段有name底层设备名Windows下通常类似\Device\NPF_{一段GUID}Linux下是eth0、ens33之类的。description驱动给的描述文案能看出是无线还是有线。mac_address网卡MAC地址就是一个byte数组打印时需要自己转十六进制。addresses这个网卡上绑定的IP地址列表元素是NetworkInterfaceAddress对象。NetworkInterfaceAddress对象里包含addressIP地址、broadcast广播地址、subnet子网掩码三部分。注意一张网卡可能绑定多个IP遍历时要全部看一遍。3.2 遍历网卡并打印信息的完整代码import jpcap.JpcapCaptor; import jpcap.NetworkInterface; import jpcap.NetworkInterfaceAddress; public class ListDevices { public static void main(String[] args) { System.load(D:/lib/jpcap/jpcap.dll); NetworkInterface[] devices JpcapCaptor.getDeviceList(); System.out.println(本机网卡数量: devices.length); for (int i 0; i devices.length; i) { NetworkInterface nif devices[i]; System.out.println(索引: i); System.out.println( 名称: nif.name); System.out.println( 描述: nif.description); System.out.println( MAC: bytesToHex(nif.mac_address)); for (NetworkInterfaceAddress addr : nif.addresses) { if (addr.address ! null) { System.out.println( IP: addr.address.getHostAddress() 广播: (addr.broadcast null ? 无 : addr.broadcast.getHostAddress()) 掩码: (addr.subnet null ? 无 : addr.subnet.getHostAddress())); } } } } private static String bytesToHex(byte[] bytes) { if (bytes null) { return 未知; } StringBuilder sb new StringBuilder(); for (byte b : bytes) { sb.append(String.format(%02X, b)); if (sb.length() bytes.length * 3 - 1) { sb.append(:); } } return sb.toString(); } }实际输出会类似这样本机网卡数量: 3 索引: 0 名称: \Device\NPF_{...} 描述: 有线网卡设备 MAC: AA:BB:CC:DD:EE:FF IP: 192.168.1.101 广播: 192.168.1.255 掩码: 255.255.255.0在多网卡机器上这个列表尤其重要因为VBox虚拟网卡、Docker虚拟网卡、蓝牙网卡都会占一个索引抓错网卡要么什么都抓不到要么抓到一堆虚拟化流量。3.3 代码里自动选择网卡的正确姿势人工看索引可以但程序里不可能每次启动都让你手输。我常用的选择思路是按IP前缀匹配比如只关心192.168.x.x内网流量就遍历网卡找第一个携带这个网段IP的设备。NetworkInterface target null; for (NetworkInterface nif : JpcapCaptor.getDeviceList()) { for (NetworkInterfaceAddress addr : nif.addresses) { if (addr.address ! null addr.address.getHostAddress().startsWith(192.168.)) { target nif; break; } } if (target ! null) { break; } } if (target null) { System.out.println(没找到内网网卡); return; }这个逻辑很土但很实用。真在云服务器上跑的时候网卡可能是内网地址也可能是公网直通你需要根据实际环境改成匹配规则但思路是一致的别让用户切网卡让代码自己认。4. 打开设备开始收包openDevice参数、三种取包方式、过滤规则4.1 openDevice的四个参数怎么给选好网卡后下一步就是通过JpcapCaptor.openDevice打开它。方法签名大概是JpcapCaptor captor JpcapCaptor.openDevice( NetworkInterface intf, // 网卡对象 int snaplen, // 抓取的最大字节数 boolean promisc, // 是否混杂模式 int to_ms // 超时时间毫秒 );四个参数里最需要注意的是snaplen和promisc。snaplen是“快照长度”意思是每个包最多抓前多少字节。如果不关心应用层载荷只要TCP/IP头抓个前64字节就够了效率高。但如果要解析HTTP Host就得抓全包我一般直接给65535这是以太网MTU加协议头后的常见上限基本能保证一个包完整拿下来。promisc是混杂模式true表示网卡会接收所有经过它的包而不仅仅发给本机的包。读取真正的流量时开true才能看到局域网内其他终端的包只分析本机自己访问外网的流量开不开都行但为了通用性我还是习惯开true。to_ms是超时时间单位毫秒。这个值太小可能导致抓包函数频繁返回太大会让程序响应迟钝。我常用50到100兼顾实时性和CPU占用。4.2 用processPacket抓指定数量的包最简单的一次性抓包是用processPacket传入数量和一个回调接口Jpcap收到指定数量的包后自动停止JpcapCaptor captor JpcapCaptor.openDevice(target, 65535, true, 50); captor.setFilter(tcp and dst port 80, true); captor.processPacket(10, packet - { System.out.println(抓到第 count.incrementAndGet() 个包类名: packet.getClass().getName()); }); captor.close();注意这里的回调是PacketReceiver接口写成lambda完全没问题。processPacket是阻塞方法在它返回之前只会回调你传进来的次数上限个包。适合做“抓几个看看”的验证场景。4.3 用loopPacket持续抓包但如果要做监控工具processPacket显然不够你必须让程序一直挂着收包。这时用loopPacket传-1表示无限循环JpcapCaptor captor JpcapCaptor.openDevice(target, 65535, true, 50); captor.setFilter(udp port 53, true); captor.loopPacket(-1, packet - { // 每收到一个包都会走到这里 }); // loopPacket也是阻塞的后面的代码不会立即执行loopPacket阻塞在当前线程里所以一般会单独起一个线程跑主线程负责UI、控制台输出或者其他业务逻辑。想停止时调用captor.close()或者中断线程具体看你的设计方式。4.4 过滤规则别乱写BPF表达式是效率关键抓包最怕流量太大Java层处理不过来。Jpcap底层支持BPF过滤器也就是setFilter的第一个参数用好了能挡掉90%的无关流量。常用过滤器表达式我列了一个表需求BPF表达式只看IP包ip只看TCP包tcp只看UDP的53端口包udp port 53只看访问本机80端口的TCP包tcp and dst port 80只看与某个IP有关联的包host 192.168.1.1排除ARP包not arp组合条件tcp and dst port 80 and host 192.168.1.1setFilter的第二个参数是optimizebool类型我一般直接传true让底层优化表达式。过滤器是在内核驱动层执行的不是先抓上来再丢弃所以能大幅降低用户态处理压力。写监控工具时能过滤的尽量过滤别贪多否则后端的packet回调会瞬间被刷爆。5. 数据包解剖课从IP到端口再到HTTP载荷逐层拆拿到Packet对象后很多人会不知所措不知道这个对象到底是什么。Jpcap的回调返回的其实是一个已经被解析过的最内层协议对象不像Wireshark那样给你一棵完整的协议树。你需要通过instanceof逐层判断。5.1 从instanceof开始TCP、UDP、ICMP怎么区分Jpcap的协议类之间是有继承关系的TCPPacket和UDPPacket都继承自IPPacketIPPacket本身又是一种网络层包。所以判断顺序一定要先特殊后通用if (packet instanceof TCPPacket) { TCPPacket tcp (TCPPacket) packet; System.out.println(TCP包: tcp.src_ip.getHostAddress() : tcp.src_port - tcp.dst_ip.getHostAddress() : tcp.dst_port); } else if (packet instanceof UDPPacket) { UDPPacket udp (UDPPacket) packet; System.out.println(UDP包: udp.src_ip.getHostAddress() : udp.src_port - udp.dst_ip.getHostAddress() : udp.dst_port); } else if (packet instanceof ICMPPacket) { ICMPPacket icmp (ICMPPacket) packet; System.out.println(ICMP包: icmp.src_ip.getHostAddress() - icmp.dst_ip.getHostAddress()); } else if (packet instanceof ARPPacket) { ARPPacket arp (ARPPacket) packet; System.out.println(ARP包: 请求或者应答); }TCPPacket和UDPPacket上直接有src_port和dst_port字段类型是int用起来非常直观。IP层的ttl、protocol、length这些字段也都挂在IPPacket上不用自己解析二进制头。5.2 以太网帧的问题回调里为什么拿不到MAC地址很多新手会问那源MAC地址、目的MAC地址呢这正是Jpcap一个容易让人困惑的地方。回调返回的已经是网络层或传输层对象链路层的以太网头被Jpcap内部处理掉了不像某些库会把整个链路层对象包裹在最外层。所以你直接看TCPPacket是拿不到MAC地址的。如果想拿MAC一般有两个思路一是抓到ARPPacket里面天然有MAC和IP的映射二是用链路层原始数据重新自行解析。保姆级入门阶段我建议先别纠结MAC先把IP、端口、协议类型玩明白后面做局域网设备探测时自然会在ARP里见到MAC。5.3 从TCP载荷里抠出HTTP请求的Host抓包最实用的场景之一就是看本机或局域网设备访问了哪些域名。HTTP请求是明文协议TCP包里的data字段就是应用层载荷我们可以直接按字符串搜。TCPPacket继承自Packetdata字段就是TCP payload的字节数组。完整解析HTTP报文涉及TCP粘包、分片和乱序但最简单的场景——一个请求刚好落在一个包里——可以直接这么干if (packet instanceof TCPPacket) { TCPPacket tcp (TCPPacket) packet; byte[] data tcp.data; if (data null || data.length 0) { return; // ACK包没有载荷 } String payload new String(data, StandardCharsets.UTF_8); if (payload.startsWith(GET ) || payload.startsWith(POST ) || payload.startsWith(HEAD ) || payload.startsWith(PUT )) { String[] lines payload.split(\\r?\\n); for (String line : lines) { if (line.toLowerCase().startsWith(host:)) { System.out.println(访问域名: line.substring(5).trim()); } } } }这里有个经验不要用contains去搜“Host:”万一HTTP响应头里也带了这个字段会把响应包误判成请求。我上面的写法先判断payload是不是以HTTP方法开头再逐行找Host准确性高很多。5.4 ARP包局域网设备探测的基础局域网内要检测谁在线最常用的手段是发ARP请求。Jpcap的ARPPacket把ARP协议的字段直接暴露出来了if (packet instanceof ARPPacket) { ARPPacket arp (ARPPacket) packet; System.out.println(操作类型: (arp.operation ARPPacket.ARP_REQUEST ? 请求 : 应答)); System.out.println(源IP: arp.getSenderProtocolAddress()); System.out.println(源MAC: arp.getSenderHardwareAddress()); System.out.println(目标IP: arp.getTargetProtocolAddress()); System.out.println(目标MAC: arp.getTargetHardwareAddress()); }那段打印MAC的代码我写的是方法形式实际Jpcap的ARPPacket确实提供了类似getSenderHardwareAddress这样的方法也会用byte数组存储打印时注意转成十六进制。ARP能反映局域网里谁在互相找谁比单纯的IP扫描更接近真实网络行为。6. 综合实战写一个本机HTTP请求Host统计器讲到这里知识点已经铺开了但很多观众反馈“单独看都会合起来不知道怎么写”。所以我把之前给校园网工具做的简化版拿过来完整展示一个能用的小工具统计本机所有访问外网的HTTP请求按Host排序输出。6.1 需求拆解与设计思路这个工具要解决三个问题启动后自动选择内网网卡不靠人工输入索引。持续抓取TCP请求包从payload里提取Host。每5秒打印一次统计结果Host出现次数越多排越前。关键过滤条件为什么是tcp and dst port 80因为HTTP请求从本机出去时目的端口是80而响应包回来时目的端口是本机高位端口。只看dst port 80等于只抓请求方向流量这样既能减少处理量又天然规避了把响应包里的“Host”字段统计进去的问题。设计过滤器时先想清楚方向能省不少事。6.2 完整代码import jpcap.JpcapCaptor; import jpcap.NetworkInterface; import jpcap.NetworkInterfaceAddress; import jpcap.packet.TCPPacket; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; public class HttpHostStat { private static final MapString, Integer HOST_COUNT new HashMap(); public static void main(String[] args) throws Exception { System.load(D:/lib/jpcap/jpcap.dll); NetworkInterface target findInternalInterface(); if (target null) { System.err.println(未找到内网网卡请检查网卡IP是否为192.168.x.x); return; } JpcapCaptor captor JpcapCaptor.openDevice(target, 65535, true, 100); captor.setFilter(tcp and dst port 80, true); Thread timerThread new Thread(() - { while (true) { try { Thread.sleep(5000); } catch (InterruptedException e) { return; } System.out.println( HTTP Host 统计 Top 10 ); HOST_COUNT.entrySet().stream() .sorted(Map.Entry.String, IntegercomparingByValue().reversed()) .limit(10) .forEach(e - System.out.println(e.getKey() - e.getValue() 次)); } }); timerThread.setDaemon(true); timerThread.start(); captor.loopPacket(-1, packet - { if (packet instanceof TCPPacket) { TCPPacket tcp (TCPPacket) packet; byte[] data tcp.data; if (data null || data.length 0) { return; } String payload new String(data, StandardCharsets.UTF_8); if (isHttpRequest(payload)) { String host extractHost(payload); if (host ! null !host.isEmpty()) { HOST_COUNT.merge(host, 1, Integer::sum); } } } }); } private static NetworkInterface findInternalInterface() { for (NetworkInterface nif : JpcapCaptor.getDeviceList()) { for (NetworkInterfaceAddress addr : nif.addresses) { if (addr.address ! null addr.address.getHostAddress().startsWith(192.168.)) { return nif; } } } return null; } private static boolean isHttpRequest(String payload) { return payload.startsWith(GET ) || payload.startsWith(POST ) || payload.startsWith(HEAD ) || payload.startsWith(PUT ) || payload.startsWith(DELETE ); } private static String extractHost(String payload) { String[] lines payload.split(\\r?\\n); for (String line : lines) { String lower line.toLowerCase(); if (lower.startsWith(host:)) { return line.substring(5).trim(); } } return null; } }这段代码本身可以直接跑但请注意我写死的dll路径到你机器上需要改成你自己的路径。另外loopPacket是阻塞的所以统计线程用Daemon方式提前启动避免主线程卡死。6.3 跑起来后的现象和几个值得注意的点运行后每5秒会输出一次统计效果类似 HTTP Host 统计 Top 10 www.abc.com - 12 次 api.example.org - 8 次 cdn.news-site.net - 5 次第一个要注意的点HTTPS流量看不到Host。因为HTTP over TLS之后TCP payload里全是密文只有通过TLS握手时的SNI扩展才能看到域名但那需要额外解析不在本文范围内。所以这个工具只能统计明文HTTP站点。第二个要注意的点如果本机开了代理Host字段可能不是目标站点域名而是代理服务器的域名因为HTTP请求里的URL是完整路径Host写的是代理地址。这在企业代理环境下很常见。第三个要注意的点TCP分段和粘包。如果请求很大一个包没装完你可能只拿到了HTTP请求头的一部分Host那一行可能没到如果多个小请求拼在一个包又可能一个payload里出现多个Host行。Jpcap本身不做TCP流重组我上面这段只是教学级简化实现。真要做完整的流量分析得自己维护一个按四元组区分的TCP流缓冲区把乱序和分段数据拼起来再解析。7. 实战排错与性能边界那些坑我替你踩过了7.1 高频报错速查表报错信息最常见原因解决思路no jpcap in java.library.pathdll没有加入加载路径用System.load绝对路径加载或把dll放到JAVA_HOME/jre/binUnsatisfiedLinkError ... 32-bitdll位数和JDK位数不匹配统一用32位JDK官方dll或寻找可用的64位编译包getDeviceList返回长度为0驱动没装好或Npcap没勾兼容模式重装驱动确认WinPcap API兼容选项被勾选openDevice抛出NoSuchDevice网卡索引填写不对或网卡被禁用先跑一遍ListDevices打印索引再填processPacket回调里包数量很少过滤条件太严格或网卡选错先不过滤抓几个包看网卡是否正确程序内存涨得很快回调里处理太慢包被堆积增强过滤、降低抓包范围、简化业务逻辑7.2 为什么你抓到的包经常没有应用层数据这个问题几乎每个用Jpcap的人都遇到过。TCPPacket的data字段为空或者只有几字节就以为代码写错了。其实这是TCP机制的正常现象三次握手里的纯ACK包根本没有载荷大请求被拆成多个TCP段你抓到的某个包可能只包含一段数据包在网络中乱序到达也会出现先抓到的包不完整。所以判断数据完整性的经验法则是先过滤那些data为空或长度小于20字节的包再看前几个字节是不是可打印的HTTP方法名。如果一整个请求头都不在同一个包里就得搬出前文说的TCP流重组方案那已经不是入门内容了。7.3 什么时候Jpcap该退场我做过一个压力测试在百兆流量的局域网里用Jpcap连续抓包回调里只做统计不做复杂解析底层有时候还会丢包。用JpcapCaptor的统计接口能看到已接收包数和丢包数差距大的时候就要警惕了。Jpcap适合的场景是个人电脑上分析自己的流量、校园网/办公网里小规模流量统计、教学实验、协议学习。它不适合做核心交换机上的全量流量分析也不适合做生产级安全监控。真要上量级建议去看Pcap4J或者直接用C语言写采集端数据先落盘再分析。工具选型要量力而行这是做抓包项目最重要的一点。7.4 我自己的一个工作习惯最后分享一个让抓包效率翻倍的习惯写代码前先用Wireshark抓一段真实流量确认目标端口、协议特征、Host所在位置然后再写Jpcap的过滤条件和解析逻辑。不知道为什么很多人会跳过人工观察这一步直接上代码抓然后又回来调半天。先看先想再写能省至少一半时间。我现在做任何抓包小工具都是这个流程已经成肌肉记忆了。另外抓包分析涉及网络隐私练习时尽量用自己的流量、自己的设备别拿这套能力去抓别人网络里的数据。技术本身是用来做事和解决问题的这底线得守住。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表