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

相关文章

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

C#使用DeepSeek API实现自然语言处理,文本分类和情感分析

《C#使用DeepSeekAPI实现自然语言处理,文本分类和情感分析》在C#中使用DeepSeekAPI可以实现多种功能,例如自然语言处理、文本分类、情感分析等,本文主要为大家介绍了具体实现步骤,... 目录准备工作文本生成文本分类问答系统代码生成翻译功能文本摘要文本校对图像描述生成总结在C#中使用Deep

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Redis主从复制的原理分析

《Redis主从复制的原理分析》Redis主从复制通过将数据镜像到多个从节点,实现高可用性和扩展性,主从复制包括初次全量同步和增量同步两个阶段,为优化复制性能,可以采用AOF持久化、调整复制超时时间、... 目录Redis主从复制的原理主从复制概述配置主从复制数据同步过程复制一致性与延迟故障转移机制监控与维

Redis连接失败:客户端IP不在白名单中的问题分析与解决方案

《Redis连接失败:客户端IP不在白名单中的问题分析与解决方案》在现代分布式系统中,Redis作为一种高性能的内存数据库,被广泛应用于缓存、消息队列、会话存储等场景,然而,在实际使用过程中,我们可能... 目录一、问题背景二、错误分析1. 错误信息解读2. 根本原因三、解决方案1. 将客户端IP添加到Re

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

锐捷和腾达哪个好? 两个品牌路由器对比分析

《锐捷和腾达哪个好?两个品牌路由器对比分析》在选择路由器时,Tenda和锐捷都是备受关注的品牌,各自有独特的产品特点和市场定位,选择哪个品牌的路由器更合适,实际上取决于你的具体需求和使用场景,我们从... 在选购路由器时,锐捷和腾达都是市场上备受关注的品牌,但它们的定位和特点却有所不同。锐捷更偏向企业级和专

Spring中Bean有关NullPointerException异常的原因分析

《Spring中Bean有关NullPointerException异常的原因分析》在Spring中使用@Autowired注解注入的bean不能在静态上下文中访问,否则会导致NullPointerE... 目录Spring中Bean有关NullPointerException异常的原因问题描述解决方案总结

python中的与时间相关的模块应用场景分析

《python中的与时间相关的模块应用场景分析》本文介绍了Python中与时间相关的几个重要模块:`time`、`datetime`、`calendar`、`timeit`、`pytz`和`dateu... 目录1. time 模块2. datetime 模块3. calendar 模块4. timeit