libpcap抓包并分析

2023-12-16 14:38
文章标签 分析 抓包 libpcap

本文主要是介绍libpcap抓包并分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

基于libpcap的数据包抓取

1.libpcap安装

前提安装gcc

然后安装输入如下命令:

yum -y install flex
yum -y install bison

在一个文件夹下下载libpcap源码并解压,在安装如下:

wget -c http://www.tcpdump.org/release/libpcap-1.7.4.tar.gz
进入libpcap-1.7.4,输入:
./configure
make
make install

这里写图片描述

2.库函数及相应功能了解

1)pcap_lookupdev();

函数用来查找网络设备,返回可被pcap_open_live()函数调用的网络设备名指针。

2)pcap_open_live(char *device,int catch_byte,int promisc,int time,char *errbuf);

函数pcap_open_live是用于获取网络接口的函数,函数返回一个指针用于后续对该接口的操作,device是设备名,第二个是每次抓包抓取多少个字节,最多65535个字节,第三个参数是将设备设置成混杂模式,1为混杂模式,其他非混杂,第四个参数是抓取时间,指定需要等地的毫秒数,超过这个时间后,获得数据包的函数会立即返回,0表示一直等待直到有数据包到来,errbuf保存错误信息。

函数返回数据类型为pcap_t指针。

3)pcap_lookupnet(char *device,bpf_u_int32 *ipaddress,bpf_u_int32 *ipmask,char *errbuf);

函数pcap_lookupnet用于查看设备ip和掩码

4)pcap_compile(pcap_t *p,struct bpf_program *fp,char *filterstr,int opt,bpf_u_int32 netmask);
5)pcap_setfilter(pcap_t *p,struct bpf_program *fp);

函数pcap_compile是用来设置过滤条件的,即设置抓取的包的条件,条件为filterstr,配合pcap_setfilter函数完成设置。

6)pcap_loop();

与pcap_next()和pcap_next_ex()两个函数一样用来捕获数据包

3.代码+实验

参考 官方文档

1.确定嗅探设备,比如ens33

可以自己传入一个string作为嗅探设备,也可以使用pcap_lookupdev()函数来让程序给我们提供嗅探设备。

代码分别如下:

#include <stdio.h>
#include <pcap.h>int main(int argc, char *argv[])
{char *dev = argv[1];//传入参数做嗅探设备printf("Device: %s\n", dev);return(0);
}

这里写图片描述

注意编译方式,见上图。
#include <stdio.h>
#include <pcap.h>int main(int argc, char *argv[])
{char *dev,errbuf[1024];dev=pcap_lookupdev(errbuf);if(dev==NULL){printf("%s\n",errbuf);return 0;}printf("Device: %s\n", dev);return 0;
}

这里写图片描述

2.打开嗅探设备,准备嗅探

函数讲解见部分2.库函数及相应功能了解

    pcap_t *pcap_handle=pcap_open_live(dev,65535,1,0,errbuf);if(pcap_handle==NULL){printf("%s\n",errbuf);return 0;}

pcap_t*这个指针很重要,后面基本所有操作都会用到。

获取网络号(ip)和掩码:int pcap_lookupnet(char *device,bpf_u_int32 *ipaddress,bpf_u_int32 *ipmask,char *errbuf);

device为设备,ipaddress记录设备ip,ipmask记录设备掩码,errbuf存储错误信息,返回-1出错,0成功

#include <stdio.h>
#include <pcap.h>
#include<time.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<errno.h>
#include<string.h>int main(int argc, char *argv[])
{char *dev,errbuf[1024];//确定网络接口dev=argv[1];if(dev==NULL){printf("device is null\n");return 0;}//打开网络接口pcap_t *pcap_handle=pcap_open_live(dev,65535,1,0,errbuf);if(pcap_handle==NULL){printf("%s\n",errbuf);return 0;}//获取网络号ip和掩码struct in_addr addr;bpf_u_int32 ipaddress, ipmask;char *dev_ip,*dev_mask;if(pcap_lookupnet(dev,&ipaddress,&ipmask,errbuf)==-1){printf("%s\n",errbuf);return 0;}//输出ipaddr.s_addr=ipaddress;dev_ip=inet_ntoa(addr);printf("ip address : %s\n",dev_ip);//输出掩码addr.s_addr=ipmask;dev_mask=inet_ntoa(addr);printf("netmask : %s\n",dev_mask);return 0;
}

这里写图片描述

不太清楚最后的一个字节为什么为0…可以看看源码研究一下。

3.获取网络数据包

方法1:const u_char *pcap_next(pcap_t *p,struct pcap_pkthdr *h);

捕获一个网络数据包,收到一个数据包立即返回,p:pcap_open_live()返回的pcap_t类型的指针h:数据包头,返回的是接收到的数据的起始地址,即实际数据内容

pcap_pkthdr类型的定义如下:

struct pcap_pkthdr  
{  struct timeval ts; // 抓到包的时间  bpf_u_int32 caplen; // 表示抓到的数据长度,抓取时长度bpf_u_int32 len; // 表示数据包的实际长度,本来应有长度
}  

使用:

    const char *p_packet_content;//实际接收数据起始地址struct pcap_pkthdr protocol_header;//数据包头p_packet_content=pcap_next(pcap_handle,&protocol_header);printf("capture time : %s",ctime((const time_t*)&protocol_header.ts.tv_sec));printf("packet length : %d\n",protocol_header.len);

这里写图片描述

方法2:int pcap_loop(pcap_t *p,int cnt,pcap_handler callback,u_char *user);

循环捕获网络数据包,直到遇到错误或者满足退出条件,每次捕获一个数据包就会调用callback指定的回调函数,所以,可以在回调函数中进行数据包的处理操作,返回值:成功返回0,失败返回负数

·p:pcap_open_live()返回的pcap_t类型的指针

·cnt:指定捕获数据包的个数,一旦抓到cnt个数据包,pcap_loop立即返回,如果是-1,就会一直捕获直到出错

·callback:回调函数,名字任意,可在其中处理数据包

·user:向回调函数中传递的参数

回调函数callback意义:void callback(u_char *userarg,const struct pcap_pkthdr *pkthdr,const u_char *packet);

·userarg:pcap_loop()的最后一个参数,当收到足够数量的包后pcap_loop会调用callback回调函数,同时将pcap_loop()的user参数传递给它

·pkthdr:是收到数据包的pcap_pkthdr类型的指针,和pcap_next()第二个参数是一样的

·packet:收到的数据包数据首地址

代码:

#include <stdio.h>
#include <pcap.h>
#include<time.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<errno.h>
#include<string.h>
//回调函数
void pcap_callback(unsigned char * arg,const struct pcap_pkthdr *packet_header,const unsigned char *packet_content){int *id=(int *)arg;//记录包IDprintf("id=%d\n",++(*id));printf("Packet length : %d\n",packet_header->len);printf("Number of bytes : %d\n",packet_header->caplen);printf("Received time : %s\n",ctime((const time_t*)&packet_header->ts.tv_sec));int i;for(i=0;i<packet_header->caplen;i++){printf(" %02x",packet_content[i]);if((i+1)%16==0){printf("\n");}}printf("\n\n");
}int main(int argc, char *argv[])
{char *dev,errbuf[1024];dev=argv[1];if(dev==NULL){printf("device is null\n");return 0;}pcap_t *pcap_handle=pcap_open_live(dev,65535,1,0,errbuf);if(pcap_handle==NULL){printf("%s\n",errbuf);return 0;}struct in_addr addr;bpf_u_int32 ipaddress, ipmask;char *dev_ip,*dev_mask;if(pcap_lookupnet(dev,&ipaddress,&ipmask,errbuf)==-1){printf("%s\n",errbuf);return 0;}addr.s_addr=ipaddress;dev_ip=inet_ntoa(addr);printf("ip address : %s\n",dev_ip);addr.s_addr=ipmask;dev_mask=inet_ntoa(addr);printf("netmask : %s\n",dev_mask);printf("---------packet--------\n");int id=0;//传入回调函数记录IDif(pcap_loop(pcap_handle,10,pcap_callback,(unsigned char *)&id)<0){//接收十个数据包printf("error\n");return 0;}pcap_close(pcap_handle);return 0;
}

这里写图片描述

4.过滤数据包

设置过滤条件:

举一些例子:

  • src host 192.168.1.177:只接收源ip地址是192.168.1.177的数据包

  • dst port 80:只接收tcp、udp的目的端口是80的数据包

  • not tcp:只接收不使用tcp协议的数据包

  • tcp[13] == 0x02 and (dst port 22 or dst port 23) :只接收 SYN
    标志位置位且目标端口是 22 或 23 的数据包( tcp 首部开始的第 13 个字节)

  • icmp[icmptype] == icmp-echoreply or icmp[icmptype] == icmp-echo:只接收 icmp 的 ping 请求和 ping 响应的数据包

  • ehter dst 00:e0:09:c1:0e:82:只接收以太网 mac 地址是 00:e0:09:c1:0e:82 的数据包

  • ip[8] == 5:只接收 ip 的 ttl=5 的数据包(ip首部开始的第8个字节)

编译BPF过滤规则:int pcap_compile(pcap_t *p,struct bpf_program *fp,char *buf,int optimize,bpf_u_int32 mask);

参数:

  • p:pcap_open_live()返回的pcap_t类型的指针

  • fp:存放编译后的bpf,应用过来规则时需要使用这个指针

  • buf:过滤规则

  • optimize:是否需要优化过滤表达式

  • mask:指定本地网络的网络掩码,不需要时可写0

  • 返回值:成功返回0,失败返回-1

应用BPF过滤规则:int pcap_setfilter(pcap_t *p,struct bpf_program *fp);

功能:应用BPF过滤规则

参数:

  • p:pcap_open_live()返回的pcap_t类型的指针

  • fp:pcap_compile()的第二个参数

  • 返回值:成功返回0,失败返回-1

使用:

    struct bpf_program filter;pcap_compile(pcap_handle,&filter,"dst port 80",1,0);pcap_setfilter(pcap_handle,&filter);

以上代码运行,过滤条件是dst port 80,打开浏览器,数据包就接收到了。

这里写图片描述

附:保存抓取到的数据,并保存至aaa.pcap文件里,可用wireshark打开

函数:

pcap_dumper_t* dumpfp=pcap_dump_open(pcap_t *p,char[] path);

pcap_dump_close(pcap_dumper_t* dumpfp);

还需使用pcap_dump(arg,packet_header,packet_content);放在回调函数里,从而保存数据

以上两个函数是一对

具体代码:

这里写图片描述

这里写图片描述

以上基本函数使用讲解完毕。

4.具体数据包

以下只提供代码,来解析数据包。不同协议分析可仿照如下:(需要学习网络协议)

#include <stdio.h>
#include <pcap.h>
#include<time.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<errno.h>
#include<string.h>typedef struct eth_hdr
{u_char dst_mac[6];u_char src_mac[6];u_short eth_type;
}eth_hdr;
eth_hdr *ethernet;typedef struct ip_hdr
{int version:4;int header_len:4;u_char tos:8;int total_len:16;int ident:16;int flags:16;u_char ttl:8;u_char protocol:8;int checksum:16;u_char sourceIP[4];u_char destIP[4];
}ip_hdr;
ip_hdr *ip;typedef struct tcp_hdr
{u_short sport;u_short dport;u_int seq;u_int ack;u_char head_len;u_char flags;u_short wind_size;u_short check_sum;u_short urg_ptr;
}tcp_hdr;
tcp_hdr *tcp;typedef struct udp_hdr
{u_short sport;u_short dport;u_short tot_len;u_short check_sum;
}udp_hdr;
udp_hdr *udp;void pcap_callback(unsigned char * arg,const struct pcap_pkthdr *packet_header,const unsigned char *packet_content){static int id=1;printf("id=%d\n",id++);pcap_dump(arg,packet_header,packet_content);printf("Packet length : %d\n",packet_header->len);printf("Number of bytes : %d\n",packet_header->caplen);printf("Received time : %s\n",ctime((const time_t*)&packet_header->ts.tv_sec));int i;for(i=0;i<packet_header->caplen;i++){printf(" %02x",packet_content[i]);if((i+1)%16==0){printf("\n");}}printf("\n\n");u_int eth_len=sizeof(struct eth_hdr);u_int ip_len=sizeof(struct ip_hdr);u_int tcp_len=sizeof(struct tcp_hdr);u_int udp_len=sizeof(struct udp_hdr);printf("analyse information:\n\n");printf("ethernet header information:\n");ethernet=(eth_hdr *)packet_content;printf("src_mac : %02x-%02x-%02x-%02x-%02x-%02x\n",ethernet->src_mac[0],ethernet->src_mac[1],ethernet->src_mac[2],ethernet->src_mac[3],ethernet->src_mac[4],ethernet->src_mac[5]);printf("dst_mac : %02x-%02x-%02x-%02x-%02x-%02x\n",ethernet->dst_mac[0],ethernet->dst_mac[1],ethernet->dst_mac[2],ethernet->dst_mac[3],ethernet->dst_mac[4],ethernet->dst_mac[5]);printf("ethernet type : %u\n",ethernet->eth_type);if(ntohs(ethernet->eth_type)==0x0800){printf("IPV4 is used\n");printf("IPV4 header information:\n");ip=(ip_hdr*)(packet_content+eth_len);printf("source ip : %d.%d.%d.%d\n",ip->sourceIP[0],ip->sourceIP[1],ip->sourceIP[2],ip->sourceIP[3]);printf("dest ip : %d.%d.%d.%d\n",ip->destIP[0],ip->destIP[1],ip->destIP[2],ip->destIP[3]);if(ip->protocol==6){printf("tcp is used:\n");tcp=(tcp_hdr*)(packet_content+eth_len+ip_len);printf("tcp source port : %u\n",tcp->sport);printf("tcp dest port : %u\n",tcp->dport);}else if(ip->protocol==17){printf("udp is used:\n");udp=(udp_hdr*)(packet_content+eth_len+ip_len);printf("udp source port : %u\n",udp->sport);printf("udp dest port : %u\n",udp->dport);}else {printf("other transport protocol is used\n");}}else {printf("ipv6 is used\n");}printf("------------------done-------------------\n");printf("\n\n");
}int main(int argc, char *argv[])
{char *dev,errbuf[1024];dev=argv[1];if(dev==NULL){printf("device is null\n");return 0;}pcap_t *pcap_handle=pcap_open_live(dev,65535,1,0,errbuf);if(pcap_handle==NULL){printf("%s\n",errbuf);return 0;}struct in_addr addr;bpf_u_int32 ipaddress, ipmask;char *dev_ip,*dev_mask;if(pcap_lookupnet(dev,&ipaddress,&ipmask,errbuf)==-1){printf("%s\n",errbuf);return 0;}addr.s_addr=ipaddress;dev_ip=inet_ntoa(addr);printf("ip address : %s\n",dev_ip);addr.s_addr=ipmask;dev_mask=inet_ntoa(addr);printf("netmask : %s\n",dev_mask);struct bpf_program filter;if(pcap_compile(pcap_handle,&filter,"dst port 80",1,0)<0){printf("error\n");return 0;}if(pcap_setfilter(pcap_handle,&filter)<0){printf("error\n");return 0;}printf("---------packet--------\n");int id=0;pcap_dumper_t* dumpfp=pcap_dump_open(pcap_handle,"./save1.pcap");if(pcap_loop(pcap_handle,20,pcap_callback,(unsigned char *)dumpfp)<0){printf("error\n");return 0;}pcap_dump_close(dumpfp);pcap_close(pcap_handle);return 0;
}

结果:

这里写图片描述

参考:

https://www.cnblogs.com/danielStudy/p/7007689.html

http://www.tcpdump.org/pcap.html

另:我的Tenkey同学用python抓包,代码如下,可以学习一波,代码链接

#!/usr/bin/env python3
"""
Use DPKT to read in a pcap file and print out the contents of the packets
This example is focused on the fields in the Ethernet Frame and IP packet
Using Pypcap module to capture the packet and Using the Dpkt to read the packet
and save it to a .pcap file by timestamp or you can name it by yourself
by Tenkey 
"""
import pcap
import dpkt
import datetime
import socket
import os
from dpkt.compat import compat_ordfile_name_time = Nonedef mac_addr(address):"""Convert a MAC address to a readable/printable stringArgs:address (str): a MAC address in hex form (e.g. '\x01\x02\x03\x04\x05\x06')Returns:str: Printable/readable MAC address"""return ':'.join('%02x' % compat_ord(b) for b in address)def inet_to_str(inet):"""Convert inet object to a stringArgs:inet (inet struct): inet network addressReturns:str: Printable/readable IP address"""# First try ipv4 and then ipv6try:return socket.inet_ntop(socket.AF_INET, inet)except ValueError:return socket.inet_ntop(socket.AF_INET6, inet)def print_packets(pcap):"""Print out information about each packet in a pcapArgs:pcap: an pcap.pcap object (a network packet) """# For each packet in the pcap process the contentsglobal file_name_timewith open('unnamed.pcap', 'wb') as file:writer = dpkt.pcap.Writer(file)for timestamp, buf in pcap:if file_name_time == None:file_name_time = str(datetime.datetime.utcfromtimestamp(timestamp))writer.writepkt(buf, timestamp)# Print out the timestamp in UTCprint('Timestamp: ', str(datetime.datetime.utcfromtimestamp(timestamp)))# Unpack the Ethernet frame (mac src/dst, ethertype)eth = dpkt.ethernet.Ethernet(buf)print('Ethernet Frame: ', mac_addr(eth.src), mac_addr(eth.dst), eth.type)# Make sure the Ethernet data contains an IP packetif not isinstance(eth.data, dpkt.ip.IP):print('Non IP Packet type not supported %s\n' %eth.data.__class__.__name__)continue# Now unpack the data within the Ethernet frame (the IP packet)# Pulling out src, dst, length, fragment info, TTL, and Protocolip = eth.data# Pull out fragment information (flags and offset all packed into off field, so use bitmasks)do_not_fragment = bool(ip.off & dpkt.ip.IP_DF)more_fragments = bool(ip.off & dpkt.ip.IP_MF)fragment_offset = ip.off & dpkt.ip.IP_OFFMASK# Print out the infoprint('IP: %s -> %s   (len=%d ttl=%d DF=%d MF=%d offset=%d)' %(inet_to_str(ip.src), inet_to_str(ip.dst), ip.len, ip.ttl, do_not_fragment, more_fragments, fragment_offset))# Print out the detials in packetif isinstance(ip.data, dpkt.tcp.TCP):tcp = ip.dataprint('TCP: SrcPort[%d] -> DstPort[%d] Seq=%d Ack=%d Win=%d\n' %(tcp.sport, tcp.dport, tcp.seq, tcp.ack, tcp.win))elif isinstance(ip.data, dpkt.udp.UDP):udp = ip.dataprint('UDP: SrcPort[%d] -> DstPort[%d] Len=%d Check=%d\n' %(udp.sport, udp.dport, udp.ulen, udp.sum))elif isinstance(ip.data, dpkt.icmp.ICMP):print("ICMP: This is ICMP packet for checking error on route\n")else:print("Other Protocol: there may be other Protocols\n")def getPcap():nic = "ens33"fil = Noneans_nc = input("do you want to set filter for network card ? [y/n]\n")if ans_nc == "y":nic = input("typing in the network card name:\n")print("......setting filter for network card sucessfully......\n")ans_fil = input("do you want to set filter in th BPF(Berkeley Packet Filter) syntax ? [y/n]\n")if ans_fil == "y":fil = input("typing the BPF syntax Filter:\n")print("......setting filter in BPF syntax sucessfully......\n")sniffer = pcap.pcap(nic)if fil != None:sniffer.setfilter(fil)return snifferdef main():"""Using Pypcap(pcap) and DPKT(dpkt) modules to capture the network packetand unpack the packet to show the detials in every layer(this demo just show parts of them)and then save it into a .pcap file which can be opened by various open-source network capturing tools such as wireshark """sniffer = getPcap()print("-------------Start to Unpack-------------\n")try:print_packets(sniffer)except KeyboardInterrupt:print("-------------Unpack Ended-------------\n")nf = input("Do you want to name your capture file ? (or it will automatically named by time) [y/n]\n")if nf == "y":file_name_user = input("Just type the name: \n")os.rename('unnamed.pcap', file_name_user+'.pcap')else:os.rename('unnamed.pcap', file_name_time+'.pcap')print("File will automatically be named after time , Bye~\n")print("-------------Saving sucessfully-------------\n")if __name__ == '__main__':main()

注:以上所有操作均在作者在网上搜集资料后,在个人电脑上实验成功,若读者实验时失败,可能由一些未知因素导致,可与作者联系。编写的教程可能由于疏忽出错,请与作者联系。

这篇关于libpcap抓包并分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/500825

相关文章

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

MySQL中的表连接原理分析

《MySQL中的表连接原理分析》:本文主要介绍MySQL中的表连接原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、环境3、表连接原理【1】驱动表和被驱动表【2】内连接【3】外连接【4编程】嵌套循环连接【5】join buffer4、总结1、背景

python中Hash使用场景分析

《python中Hash使用场景分析》Python的hash()函数用于获取对象哈希值,常用于字典和集合,不可变类型可哈希,可变类型不可,常见算法包括除法、乘法、平方取中和随机数哈希,各有优缺点,需根... 目录python中的 Hash除法哈希算法乘法哈希算法平方取中法随机数哈希算法小结在Python中,

Java Stream的distinct去重原理分析

《JavaStream的distinct去重原理分析》Javastream中的distinct方法用于去除流中的重复元素,它返回一个包含过滤后唯一元素的新流,该方法会根据元素的hashcode和eq... 目录一、distinct 的基础用法与核心特性二、distinct 的底层实现原理1. 顺序流中的去重

关于MyISAM和InnoDB对比分析

《关于MyISAM和InnoDB对比分析》:本文主要介绍关于MyISAM和InnoDB对比分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录开篇:从交通规则看存储引擎选择理解存储引擎的基本概念技术原理对比1. 事务支持:ACID的守护者2. 锁机制:并发控制的艺

MyBatis Plus 中 update_time 字段自动填充失效的原因分析及解决方案(最新整理)

《MyBatisPlus中update_time字段自动填充失效的原因分析及解决方案(最新整理)》在使用MyBatisPlus时,通常我们会在数据库表中设置create_time和update... 目录前言一、问题现象二、原因分析三、总结:常见原因与解决方法对照表四、推荐写法前言在使用 MyBATis

Python主动抛出异常的各种用法和场景分析

《Python主动抛出异常的各种用法和场景分析》在Python中,我们不仅可以捕获和处理异常,还可以主动抛出异常,也就是以类的方式自定义错误的类型和提示信息,这在编程中非常有用,下面我将详细解释主动抛... 目录一、为什么要主动抛出异常?二、基本语法:raise关键字基本示例三、raise的多种用法1. 抛

github打不开的问题分析及解决

《github打不开的问题分析及解决》:本文主要介绍github打不开的问题分析及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、找到github.com域名解析的ip地址二、找到github.global.ssl.fastly.net网址解析的ip地址三

Mysql的主从同步/复制的原理分析

《Mysql的主从同步/复制的原理分析》:本文主要介绍Mysql的主从同步/复制的原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录为什么要主从同步?mysql主从同步架构有哪些?Mysql主从复制的原理/整体流程级联复制架构为什么好?Mysql主从复制注意

java -jar命令运行 jar包时运行外部依赖jar包的场景分析

《java-jar命令运行jar包时运行外部依赖jar包的场景分析》:本文主要介绍java-jar命令运行jar包时运行外部依赖jar包的场景分析,本文给大家介绍的非常详细,对大家的学习或工作... 目录Java -jar命令运行 jar包时如何运行外部依赖jar包场景:解决:方法一、启动参数添加: -Xb