TCP会话交互用时详细分析(PCAP版)

版本:
    1.4  修正解析IP包总长度结果为0(IP_Total_Length == 0)的情况 wireshark 提示 (reported as 0, presumed to be because of "TCP segmentation offload" (TSO))
主要功能:
    用于分析TCP交互过程中的用时信息,找出网络卡慢原因
前置操作:
    安装 python3.x 环境
    通过tcpdump或wireshark等抓包软件抓包并另存为PCAP文件格式
    在程序末尾设置:
        日志文件存放目录位置
        PCAP文件或存放多个PCAP文件的目录位置
TCP用时分析包含:
    客户端发送请求的传输用时(有多个数据包时计算传输完成用时)
    服务端对客户端请求进行处理的计算用时(服务端处理速度)
    服务端处理完后返回响应数据的传输用时(有多个数据包时计算传输完成用时)
    客户端发起多个请求之间的间隔用时(如:等待用户输入、等待其他进程先完成)
    客户端原因长时间等待保持连接用时(Keep-Alive)
    服务端原因长时间等待保持连接用时(Keep-Alive)
    客户端接收服务器响应数据包时缓冲区满造成等待用时(Windows=0)
    服务端接收客户端请求数据包时缓冲区满造成等待用时(Windows=0)
    会话超时被强制终止前的用时(未完成)
UDP用时分析包含:
    (未完成)
数据解析功能:
    解析TCP数据
    解析HTTP数据
    解析HTTPS数据(未完成)
    解析MySQL数据(未完成)
    解析UDP数据(未完成)
其他功能:
    画流量图(精确到秒)需要先安装 matplotlib 模块
    从PCAP文件(或存放多个PCAP文件的目录中)中提取指定会话的数据另存为PCAP文件


import socket, struct, os, time
from binascii import b2a_hex, a2b_hex
import logging
import re
from io import BytesIO      ## 在内存中组装TCP数据用
import gzip
from urllib.parse import unquote



ReadMe = '''
版本:
    1.4  修正解析IP包总长度结果为0(IP_Total_Length == 0)的情况 wireshark 提示 (reported as 0, presumed to be because of "TCP segmentation offload" (TSO))
主要功能:
    用于分析TCP交互过程中的用时信息,找出网络卡慢原因
前置操作:
    安装 python3.x 环境
    通过tcpdump或wireshark等抓包软件抓包并另存为PCAP文件格式
    在程序末尾设置:
        日志文件存放目录位置
        PCAP文件或存放多个PCAP文件的目录位置
TCP用时分析包含:
    客户端发送请求的传输用时(有多个数据包时计算传输完成用时)
    服务端对客户端请求进行处理的计算用时(服务端处理速度)
    服务端处理完后返回响应数据的传输用时(有多个数据包时计算传输完成用时)
    客户端发起多个请求之间的间隔用时(如:等待用户输入、等待其他进程先完成)
    客户端原因长时间等待保持连接用时(Keep-Alive)
    服务端原因长时间等待保持连接用时(Keep-Alive)
    客户端接收服务器响应数据包时缓冲区满造成等待用时(Windows=0)
    服务端接收客户端请求数据包时缓冲区满造成等待用时(Windows=0)
    会话超时被强制终止前的用时(未完成)
UDP用时分析包含:
    (未完成)
数据解析功能:
    解析TCP数据
    解析HTTP数据
    解析HTTPS数据(未完成)
    解析MySQL数据(未完成)
    解析UDP数据(未完成)
其他功能:
    画流量图(精确到秒)需要先安装 matplotlib 模块
    从PCAP文件(或存放多个PCAP文件的目录中)中提取指定会话的数据另存为PCAP文件
'''




## 显示颜色
import ctypes

STD_INPUT_HANDLE = -10
STD_OUTPUT_HANDLE = -11
STD_ERROR_HANDLE = -12

绿色 = 0x0a # green.
红色 = 0x0c # red.
金色 = 0x0e # yellow.
白色 = 0x0f # white.
蓝色 = 0x09 # blue.
天蓝色 = 0x0b # skyblue.
深灰色 = 0x08 # dark gray.
暗天蓝色 = 0x03 # dark skyblue.

# get handle
std_out_handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)

def 设置CMD颜色(color, handle=std_out_handle):
    Bool = ctypes.windll.kernel32.SetConsoleTextAttribute(handle, color)
    return Bool




####################
## 解析数据包函数 ##
####################

## TCP校验
def 计算校验和(DATA):
    LEN = len(DATA)
    if LEN % 2 == 0:
        FMT = '!' + str(LEN//2) + 'H'
    else:
        DATA += b'\x00'
        LEN = len(DATA)
        FMT = '!' + str(LEN//2) + 'H'
    X = struct.unpack(FMT, DATA)
    SUM = 0
    for i in X:
        SUM += i

    while(SUM > 65535):     ## 值大于 65535 说明二进制位数超过16bit
        H16 = SUM >> 16     ## 取高16位
        L16 = SUM & 0xffff  ## 取低16位
        SUM = H16 + L16
    校验和 = SUM ^ 0xffff
    #Log.debug(f"计算 TCP_Checksum {hex(校验和)}")
    return(校验和)

## 解析包头(16字节)
def Packet_Header(BytesData):
    PacketHeader = struct.unpack('IIII', BytesData)
    时间戳 = PacketHeader[0]
    微秒 = PacketHeader[1]
    抓取数据包长度 = PacketHeader[2]       # 所抓获的数据包保存在pcap文件中的实际长度,以字节为单位。
    实际数据包长度 = PacketHeader[3]       # 所抓获的数据包的真实长度,如果文件中保存不是完整的数据包,那么这个值可能要比前面的数据包长度的值大。
    #return(时间戳, 微秒, 抓取数据包长度, 实际数据包长度)
    时间戳_float = 时间戳 + 微秒/1000000
    return(时间戳_float, 抓取数据包长度, 实际数据包长度)

## 解析帧头(14字节)
def EthernetII_Header(BytesData):
    DstMAC = BytesData[0:6]             # 目的MAC地址
    SrcMAC = BytesData[6:12]            # 源MAC地址
    FrameType = BytesData[12:14]        # 帧类型
    return(DstMAC, SrcMAC, FrameType)

## 解析IP头(20字节)
def IPv4_Header(BytesData):
    IP_B, IP_TOS, IP_Total_Length, IP_Identification, IP_H, IP_TTL, IP_Protocol, IP_Header_checksum, IP_Source, IP_Destination = struct.unpack('!BBHHHBBHII', BytesData)                                     # B
    IP_Version = IP_B >> 4          # 取1字节的前4位
    IP_Header_Length = IP_B & 0xF   # 取1字节的后4位
    IP_Flags = IP_H >> 13
    IP_Fragment_offset = IP_H & 0b0001111111111111
    return(IP_Version, IP_Header_Length, IP_TOS, IP_Total_Length, IP_Identification, IP_Flags, IP_Fragment_offset, IP_TTL, IP_Protocol, IP_Header_checksum, IP_Source, IP_Destination)

## 解析TCP头(20字节)
def TCP_Header(BytesData):
    TCP_Source_Port, TCP_Destination_Port, TCP_Sequence_Number, TCP_Acknowledgment_Number, TCP_H, TCP_Window, TCP_Checksum, TCP_Urgent_Pointers = struct.unpack('!HHLLHHHH', BytesData)                                                                # H(2B)
    TCP_Data_Offset = TCP_H >> 12
    先去掉后6位 = TCP_H >> 6
    TCP_Reserved = 先去掉后6位 & 0b0000111111
    TCP_Flags = TCP_H & 0b0000000000111111
    return(TCP_Source_Port, TCP_Destination_Port, TCP_Sequence_Number, TCP_Acknowledgment_Number, TCP_Data_Offset, TCP_Reserved, TCP_Flags, TCP_Window, TCP_Checksum, TCP_Urgent_Pointers)

## 解析TCP头中Options(0字节或4字节的倍数)
def TCP_Options(BytesData):
    #print("TCP_Options", BytesData)
    ## 格式 Kind/Type(1 Byte) + Length(1 Byte) + Value(X Bytes)
    ## EOL 和 NOP Option 只有 Kind/Type(1 Byte)
    ## 准备需要return的变量
    MSS = 0             # 本端可以接受的最大实际数据长度(单位字节,不含TCP Header),默认值536,最大65535
    WSOPT = 0           # 窗口扩大系数(转成倍数是 2**WSOPT)
    #SACK_Premitted = 0  # 告知对方自己支持SACK(允许只重传丢失部分数据),自定义值,1表示启用,0表示不启用
    L_SACK_INFO = []    # 重传的数据信息
    
    N = 0                          # 记录已经读到的位置
    while N < len(BytesData):
        # 读取首字节判断 Kind/Type(1 Byte)
        Kind = BytesData[N:N+1]
        #print("  Kind/Type", Kind)
        if Kind == b'\x02':
            ## 最大Segment长度(MSS)
            #print("    Kind:   Maximum Segment Size (最大Segment长度)")
            Length = 4  ## 固定为4
            #print("    Length:", Length)
            Value = struct.unpack('!H', BytesData[N+2:N+Length])[0]
            #print("    Value: ", Value)
            N += Length     ## 更新N为实际已经读取了的字数
            MSS = Value     ## 赋值准备return的变量
        elif Kind == b'\x01':
            ## 补位填充
            #print("    Kind:   NOP Option (补位填充)")
            N +=1
        elif Kind == b'\x00':
            ## 选项列表结束
            #print("    Kind:   EOL (选项列表结束)")
            N +=1
        elif Kind == b'\x03':
            ## 窗口扩大系数
            #print("    Kind:   Window Scaling Factor (窗口扩大系数)")
            Length = 3      ## 固定为3
            #print("    Length:", Length)
            Value = struct.unpack('B', BytesData[N+2:N+Length])[0]
            #print("    Value: ", Value)
            N += Length
            WSOPT = Value
        elif Kind == b'\x04':
            ## 支持SACK
            #print("      Kind:   SACK-Premitted(支持SACK)")
            Length = 2  ## 固定为2
            #print("    Length:", Length)
            N += Length
            #SACK_Premitted = 1  ## 自定义,1表示启用,0表示不启用
        elif Kind == b'\x05':
            ## 乱序/丢包数据
            #print("    Kind:   SACK Block(乱序/丢包信息)")
            ## 长度不固定,存在后续1个字节中
            Length = struct.unpack('B', BytesData[N+1:N+2])[0]
            #print("    Length:", Length)
            Value = BytesData[N+2:N+Length]
            #print("    Value: ", b2a_hex(Value))
            #print("len(Value)", len(Value))
            for i in range(0, len(Value)-2, 8):
                T = struct.unpack('!II', Value[i:i+8])
                #print("T", T)
                L_SACK_INFO.append(T)
            N += Length
        elif Kind == b'\x08':
            ## Timestamps(随时间单调递增的值)
            #print("    Kind:   Timestamps(随时间单调递增的值)")
            Length = 10 ## 固定为10
            #print("    Length:", Length)
            Value = BytesData[N+2:N+Length]
            #print("    Value: ", b2a_hex(Value))
            N += Length
        elif Kind == b'\x13':   # 19
            ## MD5认证
            #print("    Kind:   TCP-MD5(MD5认证)")
            Length = 18 ## 固定为18
            #print("    Length:", Length)
            Value = BytesData[N+2:N+Length]
            #print("    Value: ", b2a_hex(Value))
            N += Length
        elif Kind == b'\x1c':   # 28
            ## 超过一定闲置时间后拆除连接
            #print("    Kind:   User Timeout(超过一定闲置时间后拆除连接)")
            Length = 4 ## 固定为4
            #print("    Length:", Length)
            Value = BytesData[N+2:N+Length]
            #print("    Value: ", b2a_hex(Value))
            N += Length
        elif Kind == b'\x1d':   # 29
            ## 认证(可选用各种算法)
            #print("    Kind:   TCP-AO(认证(可选用各种算法))")
            ## 长度不固定,存在后续1个字节中
            Length = struct.unpack('B', BytesData[N+1:N+2])[0]
            #print("    Length:", Length)
            Value = BytesData[N+2:N+Length]
            #print("    Value: ", b2a_hex(Value))
            N += Length
        elif Kind == b'\xfd':   # 253
            ## 科研实验保留
            #print("    Kind:   Experimental(科研实验保留/253)")
            ## 长度不固定,存在后续1个字节中
            Length = struct.unpack('B', BytesData[N+1:N+2])[0]
            #print("    Length:", Length)
            Value = BytesData[N+2:N+Length]
            #print("    Value: ", b2a_hex(Value))
            N += Length
        elif Kind == b'\xfe':   # 254
            ## 科研实验保留
            #print("    Kind:   Experimental(科研实验保留/254)")
            ## 长度不固定,存在后续1个字节中
            Length = struct.unpack('B', BytesData[N+1:N+2])[0]
            #print("    Length:", Length)
            Value = BytesData[N+2:N+Length]
            #print("    Value: ", b2a_hex(Value))
            N += Length
        else:
            print(f"【W】未知 TCP Option,终止继续解析TCP选项部分,Kind={Kind}")
            Value = BytesData[N:]
            print("    Value: ", b2a_hex(Value))
            break
    return(MSS, WSOPT, L_SACK_INFO)

## 粗略解析TCP实际数据(以UTF8编码解析,忽略解析不出的部分)
def TCP_DATA(BytesData):
    print(BytesData.decode('UTF8', errors='ignore'))



##################################################
## 解析PCAP文件中每个数据包,结果存入全局总字典 ##
##################################################
## 总字典                   # {(源地址,源端口,目的地址,目的端口):D_TCP, (目的地址,目的端口,源地址,源端口):D_TCP}
## 参数 D_TCP               # 子字典,存单个TCP会话交互中的包信息
## 参数 PacketHeader_Bytes  # PCAP文件存储每个数据包的包头(16字节)内容为:时间戳, 抓取数据包长度, 实际数据包长度
## 参数 PacketData          # 数据包实际内容
## 记录 D_TCP['L_DATA']     # TCP会话交互中的包简要信息列表 (编号,时间戳,SrcIP,DstIP,SrcPort,DstPort,Seq,Ack,FLAGE,TCP_DATA_LEN,TCP_req_or_ack,TCP_Window,hex(TCP_Checksum),PS)
## 前置条件:不能含VLAN信息,这些由前置拆分PCAP文件的函数解决
##################################################
def D_SAVE(D_TCP, PacketHeader_Bytes, PacketData, 编号):
    TCP校验 = 0   # 是否检查TCP校验和,0关闭 1开启
    PS = ''       # 初始化每个包的备注信息
    时间戳, 抓取数据包长度, 实际数据包长度 = Packet_Header(PacketHeader_Bytes)
    
    DstMAC, SrcMAC, FrameType = EthernetII_Header(PacketData[0:14])     # 以太帧头
    ###########【IPv4】###########
    if FrameType == b'\x08\x00':
        ## 解析IP头(20字节[14:34])
        (IP_Version, IP_Header_Length, IP_TOS, IP_Total_Length, IP_Identification, IP_Flags, IP_Fragment_offset, IP_TTL, IP_Protocol, IP_Header_checksum, IP_Source, IP_Destination) = IPv4_Header(PacketData[14:34])
        IP部首字节长度 = IP_Header_Length*4
        ###########【TCP】###########
        if IP_Protocol == 6:               # TCP b'\x06'
            TCP_Source_Port, TCP_Destination_Port, TCP_Sequence_Number, TCP_Acknowledgment_Number, TCP_Data_Offset, TCP_Reserved, TCP_Flags, TCP_Window, TCP_Checksum, TCP_Urgent_Pointers = TCP_Header(PacketData[34:54])
            if TCP_Reserved != 0:
                Log.debug("【注意,保留6bit被使用,支持(CWR/ECN)功能】TCP_Reserved(6bit)", TCP_Reserved)
            
            ## 根据TCP部首长度计算是否有TCP可选字段
            TCP部首实际长度 = TCP_Data_Offset*4
            TCP部首固定长度 = 20
            TCP选项长度 = TCP部首实际长度 - TCP部首固定长度
            ## 初始化TCP选项信息
            MSS = 0             # 本端可以接受的最大实际数据长度(单位字节,不含TCP Header),默认值536,最大65535
            WSOPT = 0           # 窗口扩大系数
            L_SACK_INFO = []    # 放丢包信息
            if TCP选项长度 > 0:
                MSS, WSOPT, L_SACK_INFO = TCP_Options(PacketData[54:54+TCP选项长度])
                if MSS == 0:
                    MSS = 536   # 使用默认值
            
            TCP_DATA_LEN = IP_Total_Length - IP部首字节长度 - TCP部首固定长度 - TCP选项长度
            
            ## 计算数据长度
            ## 最小60字节,不足会填充
            #剩余全部数据 = PacketData[54+TCP选项长度:]
            剩余有效数据 = PacketData[54+TCP选项长度:54+TCP选项长度+TCP_DATA_LEN]
            #print("剩余全部数据字节数(TCP选项后全部)", len(剩余全部数据))           ## 当不足最小长度会填充,这个会比下面的大
            #print("剩余有效数据字节数(计算得到)", len(剩余有效数据))
            
            ## 修正解析IP包总长度结果为0的情况 wireshark 提示 (reported as 0, presumed to be because of "TCP segmentation offload" (TSO))
            if IP_Total_Length == 0:
                剩余有效数据 = PacketData[54+TCP选项长度:]    # 修正内容数据值为剩余全部数据值
                TCP_DATA_LEN = len(剩余有效数据)              # 修正内容数据的长度值
            
            ## 是否进行TCP校验,校验错误的包可能可以正常使用
            if TCP校验 == 1:
                ## 校验TCP是否正确
                #print("提取 TCP_Checksum", hex(TCP_Checksum))
                TCP_Pseudo_Total_Length = TCP部首实际长度 + TCP_DATA_LEN
                #print("TCP_Pseudo_Total_Length", TCP_Pseudo_Total_Length)
                TCP_Pseudo_Header = PacketData[26:34] + b'\x00\x06' + struct.pack('!H', TCP_Pseudo_Total_Length)    # 构造TCP伪部首
                if TCP选项长度 == 0:
                    DATA = TCP_Pseudo_Header + PacketData[34:54] + 剩余有效数据
                else:
                    TCP选项_Bytes = PacketData[54:54+TCP选项长度]
                    DATA = TCP_Pseudo_Header + PacketData[34:54] + TCP选项_Bytes + 剩余有效数据
                校验结果 = 计算校验和(DATA)                      #校验通过返回为0
                if 校验结果 != 0:
                    PS = PS + f"TCP校验错误({hex(校验结果)})"
            
            ## 分析数据包中的TCP内容
            TCP_req_or_ack = ''                     # 标识一下是请求还是响应(一般都是客户端发起请求,服务端响应)
            FLAGE = ''
            SrcIP_Bytes = PacketData[26:30]         # 源IP地址
            DstIP_Bytes = PacketData[30:34]         # 目的IP地址
            SrcIP = socket.inet_ntoa(SrcIP_Bytes)
            DstIP = socket.inet_ntoa(DstIP_Bytes)
            SrcPort = TCP_Source_Port
            DstPort = TCP_Destination_Port
            Seq = TCP_Sequence_Number
            Ack = TCP_Acknowledgment_Number
            if TCP_Flags == 24:                     # 24 011000 URG ACK PSH RST SYN FIN
                FLAGE = 'ACK PSH'
            elif TCP_Flags == 16:                   # 16 010000 URG ACK PSH RST SYN FIN
                FLAGE = 'ACK'
            elif TCP_Flags == 17:                   # 17 010001 URG ACK PSH RST SYN FIN
                FLAGE = 'ACK FIN'
            elif TCP_Flags == 18:                   # 18 010010 URG ACK PSH RST SYN FIN
                FLAGE = 'ACK SYN'
            elif TCP_Flags == 2:                    #  2 000010 URG ACK PSH RST SYN FIN
                FLAGE = 'SYN'
            elif TCP_Flags == 20:                   # 20 010100 URG ACK PSH RST SYN FIN
                FLAGE = 'ACK RST'
            elif TCP_Flags == 4:                    #  4 000100 URG ACK PSH RST SYN FIN
                FLAGE = 'RST'
            elif TCP_Flags == 25:                   # 25 011001 URG ACK PSH RST SYN FIN
                FLAGE = 'A.P.F'
            elif TCP_Flags == 28:                   # 28 011100 URG ACK PSH RST SYN FIN
                FLAGE = 'A.P.R'
            else:
                FLAGE = 'NA'
                Log.error("【ERROR】TCP_Flags unknown %d URG ACK PSH RST SYN FIN 编号 %d" % (TCP_Flags, 编号))
            
            ## 先判断出客户端和服务端的IP及端口
            ## 如果前面还没有判断出客户端和服务端,且这个包不是'SYN'和'ACK SYN',就根据端口大小估计客户端和服务端,大的为客户端
            if D_TCP['CLIENT'] == ():
                if FLAGE not in ('SYN', 'ACK SYN'):
                    if SrcPort > DstPort:
                        D_TCP['CLIENT'] = (SrcIP, SrcPort)
                        D_TCP['SERVER'] = (DstIP, DstPort)
                        D_TCP['C_Tx_DATA_ALL'] = Seq
                        D_TCP['S_Tx_DATA_ALL'] = Ack
                    else:
                        D_TCP['CLIENT'] = (DstIP, DstPort)
                        D_TCP['SERVER'] = (SrcIP, SrcPort)
                        D_TCP['S_Tx_DATA_ALL'] = Seq
                        D_TCP['C_Tx_DATA_ALL'] = Ack
            
            ## 根据FLAGE分类存数据包
            if FLAGE in ('ACK', 'ACK PSH'):
                ## 先区分是哪端发的ACK
                if SrcPort == D_TCP['CLIENT'][1]:
                    ## 客户端发ACK
                    ## Seq = C发的这个包的数据编号(累计值,不含当次数据,不含重发)
                    ## Ack = C确认已经接到S数据的编号(丢包/乱序信息放在TCP选项SACK中发送)
                    
                    ## 判断是否有数据
                    if TCP_DATA_LEN == 0:
                        TCP_ACK_KeepAlive_Mark = 0  # 标记是否是Keep-Alive包,0表示不是。1表示是,初始化为0
                        if TCP_Window == 0:                                                                        # 记录C发的ACK且窗口为0的包信息,说明C接收缓存满,同一段接收满的包的(Seq,Ack)相同,就只记录一次Ack
                            PS = PS + '接收缓冲区已满(C)'
                            if Ack not in D_TCP['D_C_WIN_0']:
                                D_TCP['D_C_WIN_0'][Ack] = [(时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号)]     # 记录C接收缓存满的包信息
                                D_TCP['C_ACK_WIN_0'].append((Seq,Ack))                                             # 记录C接收缓存满的包信息(判断对方探测包使用)
                            else:
                                D_TCP['D_C_WIN_0'][Ack].append((时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号)) # 记录C接收缓存满的包信息
                        if L_SACK_INFO != []:
                            if L_SACK_INFO[0][0] +1 == L_SACK_INFO[0][1]:
                                PS = PS + f'【TCP Keep-Alive ACK】(C) 响应保持连接包,L_SACK_INFO={L_SACK_INFO}'
                                TCP_ACK_KeepAlive_Mark = 1  # 标记是Keep-Alive包
                                ## 保存C响应保持连接的包信息
                                if Ack in D_TCP['C_TCP_Keep_Alive_ACK']:
                                    D_TCP['C_TCP_Keep_Alive_ACK'][Ack].append((时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号))
                                else:
                                    D_TCP['C_TCP_Keep_Alive_ACK'][Ack] = [(时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号)]
                            else:
                                PS = PS + f'C丢包乱序通知={L_SACK_INFO}'
                        ## 保存纯确认消息包到 D_C_ACK (C确认收完S数据的纯回应包会在这里)
                        if TCP_ACK_KeepAlive_Mark == 0:     # 忽略响应保持连接包
                            if Ack in D_TCP['D_C_ACK']:
                                D_TCP['D_C_ACK'][Ack].append((时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window))
                            else:
                                D_TCP['D_C_ACK'][Ack] = [(时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window)]
                    ## 数据长度为1,Seq(自发送累计) 主动变小1的,应该就是C发的keep-alive包,不累计大小
                    elif TCP_DATA_LEN == 1 and 剩余有效数据 == b'\x00':
                        PS = PS + '【TCP Keep-Alive】    (C) 请求保持连接包(长度1,内容空)'
                        ## 保存C发起保持连接的包信息
                        if Ack in D_TCP['C_TCP_Keep_Alive']:
                            D_TCP['C_TCP_Keep_Alive'][Ack].append((时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号))
                        else:
                            D_TCP['C_TCP_Keep_Alive'][Ack] = [(时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号)]
                    elif TCP_DATA_LEN == 1 and (Ack,Seq) in D_TCP['S_ACK_WIN_0']:                   # TCP数据长度是1且能和S缓存满的消息能对应上,说明是C发的探测S窗口的包
                        PS = PS + '(C=>S Window=?)'
                    #elif TCP_DATA_LEN == 1:     # 查看长度为1情况
                    #    print(编号, 剩余有效数据, len(剩余有效数据))
                    else:
                        ## 已经避开了纯消息包和保持连接的包
                        ## 记录TCP有效数据的包信息(C)发起的(一般都是客户端发起请求,包信息保存到 C_TCP_REQ 字典 Key=Ack Value=[(包信息1),(包信息2)] 一个请求可能会分成多个包发送)
                        TCP_req_or_ack = 'C_req'            # 备注标识为:客户端发起请求
                        if Ack in D_TCP['D_C_ACK_DATA']:
                            D_TCP['D_C_ACK_DATA'][Ack].append((时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号))
                        else:
                            D_TCP['D_C_ACK_DATA'][Ack] = [(时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号)]
                        ## 重复的情况
                        if (Seq,Ack) not in D_TCP['P_C_Tx_Seq_Ack']:
                            D_TCP['P_C_Tx_Seq_Ack'].add((Seq,Ack))                                           # 加入C发送过的数据集合,查重复用
                            D_TCP['C_Tx_DATA_ALL'] += TCP_DATA_LEN                                            # 累计C发送数据(也可以用于识别C发的keep-alive)
                        else:
                            PS = PS + 'C重发数据(Seq,Ack)完全重复'
                else:
                    ## 服务端发ACK
                    ## Seq = S自己已经发数据的累计(不含本次的数据量,不累计重发数据)
                    ## Ack = S已经接到了C发的多少数据(S告诉C端,S是接到C多少数据后的回应,在HTTP里这个可以用于区别这个回应是针对哪个请求的)
                    if TCP_DATA_LEN == 0:
                        ## 保存到 D_S_ACK
                        if Ack in D_TCP['D_S_ACK']:
                            D_TCP['D_S_ACK'][Ack].append((时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window))
                        else:
                            D_TCP['D_S_ACK'][Ack] = [(时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window)]
                        if TCP_Window == 0:                                 # 记录S发的ACK且窗口为0的包信息(Seq,Ack)说明S接收缓存满
                            PS = '接收缓冲区已满(S)'
                            if Ack not in D_TCP['D_S_WIN_0']:
                                D_TCP['D_S_WIN_0'][Ack] = [(时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号)]
                                D_TCP['S_ACK_WIN_0'].append((Seq,Ack))      # 记录S接收缓存满的包信息
                            else:
                                D_TCP['D_S_WIN_0'][Ack].append((时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号))
                        if L_SACK_INFO != []:
                            if L_SACK_INFO[0][0] +1 == L_SACK_INFO[0][1]:
                                PS = PS + f'【TCP Keep-Alive ACK】(S) 响应保持连接包,L_SACK_INFO={L_SACK_INFO}'
                                ## 保存S响应保持连接的包信息
                                if Ack in D_TCP['S_TCP_Keep_Alive_ACK']:
                                    D_TCP['S_TCP_Keep_Alive_ACK'][Ack].append((时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号))
                                else:
                                    D_TCP['S_TCP_Keep_Alive_ACK'][Ack] = [(时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号)]
                            else:
                                PS = PS + f'S丢包乱序通知={L_SACK_INFO}'
                    ## 数据长度是1且能和C缓存满的消息能对应上(Seq Ack 交换位置),说明是S发的探测C窗口的包
                    elif TCP_DATA_LEN == 1 and (Ack,Seq) in D_TCP['C_ACK_WIN_0']:
                        PS = PS + '探测窗口(S=>C Window=?)'
                    ## (S) 请求保持连接包(长度1,内容空)
                    elif TCP_DATA_LEN == 1 and 剩余有效数据 == b'\x00':
                        PS = PS + '【TCP Keep-Alive】    (S) 请求保持连接包(长度1,内容空)'
                        ## 保存S发起保持连接的包信息
                        if Ack in D_TCP['S_TCP_Keep_Alive']:
                            D_TCP['S_TCP_Keep_Alive'][Ack].append((时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号))
                        else:
                            D_TCP['S_TCP_Keep_Alive'][Ack] = [(时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号)]
                    else:
                        ## 已经避开了纯消息包和保持连接的包
                        TCP_req_or_ack = '    S_ack'            # 备注标识为:服务端响应请求
                        ## 记录TCP有效数据的包信息(S)发起的
                        if Ack in D_TCP['D_S_ACK_DATA']:
                            D_TCP['D_S_ACK_DATA'][Ack].append((时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号))
                        else:
                            D_TCP['D_S_ACK_DATA'][Ack] = [(时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号)]
                        ## 重复的情况
                        if (Seq,Ack) not in D_TCP['P_S_Tx_Seq_Ack']:
                            D_TCP['P_S_Tx_Seq_Ack'].add((Seq,Ack))
                            D_TCP['S_Tx_DATA_ALL'] += TCP_DATA_LEN                                                        # 累计S发送数据(也可以用于识别S发的keep-alive)
                        else:
                            PS = PS + 'S重发数据(Seq,Ack)完全重复'
            elif FLAGE in ('ACK FIN', 'A.P.F'):             ## 终止连接
                if DstPort == D_TCP['CLIENT'][1]:           ## 是 S 发起的 ACK FIN
                    PS = PS + '(S)发起终止连接'
                    if Seq in D_TCP['S_ACK_FIN_SeqKey']:
                        D_TCP['S_ACK_FIN_SeqKey'][Seq].append((时间戳, Seq, Ack, TCP_DATA_LEN))
                    else:
                        D_TCP['S_ACK_FIN_SeqKey'][Seq] = [(时间戳, Seq, Ack, TCP_DATA_LEN)]
                else:                                       ## 是 C 发起的 ACK FIN
                    PS = PS + '(C)发起终止连接'
                    D_TCP['C_ACK_FIN'] = (时间戳, Seq, Ack, TCP_DATA_LEN)
            elif FLAGE in ('ACK RST', 'RST', 'A.P.R'):      ## 强制断开连接
                if DstPort == D_TCP['CLIENT'][1]:
                    if Ack in D_TCP['S_ACK_RST']:
                        PS = PS + 'S强制终止连接(重复)'
                    else:
                        D_TCP['S_ACK_RST'][Ack] = (时间戳, Seq, Ack, TCP_DATA_LEN)
                        PS = PS + 'S强制终止连接'
                else:
                    ## C终止自己的请求 C_ACK_RST_AckKey 以Ack为KEY,给没有数据回应情况用请求Ack匹配RST的ACK
                    if Ack in D_TCP['C_ACK_RST_AckKey']:
                        PS = PS + 'C强制终止连接(重复)'
                    else:
                        PS = PS + 'C强制终止连接'
                        D_TCP['C_ACK_RST_AckKey'][Ack] = (时间戳, Seq, Ack, TCP_DATA_LEN)  # 记录
                    ## C终止自己的请求 C_ACK_RST_SeqKey 以Seq为KEY,期望S响应的Ack=这个Seq,给有回应的匹配用
                    if Seq in D_TCP['C_ACK_RST_SeqKey']:
                        PS = PS + '(匹配有回应)'
                    else:
                        D_TCP['C_ACK_RST_SeqKey'][Seq] = (时间戳, Seq, Ack, TCP_DATA_LEN)  # 记录
                        PS = PS + '(记录)'
            elif FLAGE == 'SYN':                                        ## 发起连接方是客户端
                if D_TCP['CLIENT'] != ():
                    if D_TCP['CLIENT'] == (SrcIP, SrcPort):
                        PS = PS + f"重复SYN【C 新建TCP连接】初始Seq={D_TCP['C_Tx_DATA_ALL']} MSS={MSS} 窗口扩大系数={WSOPT}"
                    else:
                        D_TCP['CLIENT'] = (SrcIP, SrcPort)
                        D_TCP['SERVER'] = (DstIP, DstPort)
                        D_TCP['C_SYN'] = (时间戳, Seq, Ack, 0)
                        D_TCP['C_Tx_DATA_ALL'] = Seq+1                  ## 客户端初始数据编号
                        PS = PS + f"前面判断谁是客户端错误或端口被复用,重置【C 新建TCP连接】初始Seq={D_TCP['C_Tx_DATA_ALL']} MSS={MSS} 窗口扩大系数={WSOPT}"
                else:
                    D_TCP['CLIENT'] = (SrcIP, SrcPort)
                    D_TCP['SERVER'] = (DstIP, DstPort)
                    D_TCP['C_SYN'] = (时间戳, Seq, Ack, 0)
                    D_TCP['C_Tx_DATA_ALL'] = Seq+1                      ## 客户端初始数据编号
                    PS = PS + f"【C 新建TCP连接】初始Seq={D_TCP['C_Tx_DATA_ALL']} MSS={MSS} 窗口扩大系数={WSOPT}"
            elif FLAGE == 'ACK SYN':                                    ## 回应SYN请求,是本次连接的服务端,Seq随机,Ack为发起方Seq+1
                if D_TCP['CLIENT'] == ():
                    D_TCP['CLIENT'] = (DstIP, DstPort)
                    D_TCP['SERVER'] = (SrcIP, SrcPort)
                if D_TCP['C_SYN'] != ():
                    if Ack == D_TCP['C_SYN'][1] + 1:
                        D_TCP['S_ACK_SYN'] = (时间戳, Seq, Ack, 0)
                        D_TCP['S_Tx_DATA_ALL'] = Seq+1                  ## 服务端初始数据编号
                        PS = PS + f"【S 响应TCP连接】初始Seq={D_TCP['S_Tx_DATA_ALL']} MSS={MSS} 窗口扩大系数={WSOPT}"
                    else:
                        PS = PS + 'SYN_ACK错误 或 SYN重连 或 端口被复用'
                else:
                    DEBUG = f"【DEBUG】编号={编号} ACK SYN 前面没有SYN"
                    Log.debug(DEBUG)
            else:
                Log.error(f"【ERROR】未定义怎么处理的 FLAGE {FLAGE} 忽略")
                PS = PS + f"【ERROR】未定义怎么处理的 FLAGE {FLAGE} 忽略"
            
            ## 保存信息用于显示(TCP校验通过的包/忽略TCP校验的包)
            D_TCP['L_DATA'].append((编号,时间戳,SrcIP,DstIP,SrcPort,DstPort,Seq,Ack,FLAGE,TCP_DATA_LEN,TCP_req_or_ack,TCP_Window,hex(TCP_Checksum),PS))
            
        elif IP_Protocol == 17:
            Log.error("【ERROR】UDP PASS")
        else:
            Log.error("【ERROR】NOT TCP/UDP PASS")
    else:
        Log.error("【ERROR】NOT IPv4 Ethernet")



## 记录数据包信息
def SHOW_PACK_INFO(L_DATA):
    Log.info('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-5s %5s %s %s' % ('ID','TIME','SrcIP','DstIP','SPort','DPort','Seq','Ack','FLAGE','LEN','请求/响应','WIN','校验码','备注说明'))
    for i in L_DATA:
        Log.info('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s' % i)
    Log.info('')



#############################
## 分析请求/响应的交互时间 ##
#############################

## 以客户端视角(在客户端处抓包)或服务端视角(在服务端处抓包)分析每个请求/响应的交互时间,记录各过程耗时,返回列表
## 请求完成耗时   COMP
## 发完请求耗时   C_req
## 处理请求耗时   SYS
## 传回结果耗时   S_send
## 请求包数量     C_PKS
## 响应包数量     S_PKS
## 设置了一下可以反应问题的自定义响应码:
# 响应状态码 = 0    # 自定义初始值,如果后面解析不出,就响应码设置为0
# 响应状态码 = 1    # 自定义,两种方法都找不到响应信息,且发现了C发的RST信息
# 响应状态码 = 2    # 自定义,两种方法都找不到响应信息,且没有发现C发的RST信息,应该是真没有响应
# 响应状态码 = 3    # 自定义,两种方法都找不到响应信息,且找到服务端发起了FIN终止
# 响应状态码 = 4    # 自定义,有响应的情况下表示C发起了RST,强制断开连接
# 响应状态码 = 5    # 自定义,两种方法都找不到响应信息,且发现了S发的RST信息

## 分析请求的结果及用时信息(一次TCP会话中全部交互过程),返回分析结果 L_TCP_TIME
def TCP_SESSION_TIME(D_TCP):
    ## 客户端发起的请求都在 D_TCP['D_C_ACK_DATA'] 中,有数据,大请求分成多个包的话seq最小那个是第一个包(可在乱序中找到实际第一个包)
    ## 在 D_TCP['D_C_ACK_DATA'] 中找出起始请求包,生成包信息列表
    L_C_ACK = []
    for K in D_TCP['D_C_ACK_DATA']:     # {1653587983: [(1625798739.757637, 1741930757, 1653587983, 87, 8212, 5)], ...}
        L = D_TCP['D_C_ACK_DATA'][K]
        MIN_INFO = L[0]
        MIN_SEQ = L[0][1]
        for Ln in range(1, len(L)):
            if L[Ln][1] < MIN_SEQ:
                MIN_SEQ = L[Ln][1]
                MIN_INFO = L[Ln]
        L_C_ACK.append(MIN_INFO)
    
    ## 服务端响应生成一个以Seq为Key,Ack为值的字典,方便客户端以自己Ack去匹配服务端Seq然后找出相应服务端Ack
    D_S_ACK_DATA_SeqKey = {}
    for K in D_TCP['D_S_ACK_DATA']:
        L = D_TCP['D_S_ACK_DATA'][K]
        MIN_SEQ = L[0][1]
        ACK = L[0][2]
        for Ln in range(1, len(L)):
            if L[Ln][1] < MIN_SEQ:
                MIN_SEQ = L[Ln][1]
                ACK = L[Ln][2]
        D_S_ACK_DATA_SeqKey[MIN_SEQ] = ACK
    
    L_TCP_TIME = []                     # 记录用时分析结果
    for C_REQ in L_C_ACK:
        C发送请求时间戳 = C_REQ[0]
        REQ_seq = C_REQ[1]
        REQ_ack = C_REQ[2]
        REQ_len = C_REQ[3]
        
        ## 设置初始值
        响应状态码 = 0                  # 正常响应设置为0
        请求类型 = "TCP"
        
        ## 处理 C 发送的请求包
        Log.debug(f"C 发送 {请求类型} 请求 ACK={REQ_ack}")
        C发送请求包列表 = D_TCP['D_C_ACK_DATA'][REQ_ack]
        
        ## 检查一下有没有异常的数据包(乱序/编号有问题)
        if 检查数据包编号(C发送请求包列表) == 1:
            Log.debug("    【WARNING】数据包编号有乱序")
        else:
            Log.debug("    数据包编号正常(单包请求忽略检查)")

        ## 开始分析请求时间信息(没有处理重发/乱序问题)
        REQ_DATA_LEN = 0                                     # 累计请求数据总长度(字节)
        C发送请求尾包 = C发送请求包列表[-1]                  # 请求包按时间最后一个
        C发完请求耗时 = C发送请求尾包[0] - C发送请求时间戳   # 最后一个减去第一个包的时间,单包请求结果为0
        for i in C发送请求包列表:
            Log.debug(f'        {请求类型} 请求信息 {i}')
            REQ_DATA_LEN += i[3]
        累计计算预估S响应ACK = REQ_seq+REQ_DATA_LEN
        
        ## C 发送的请求包数量(含重复包)
        C发送请求包数量 = len(C发送请求包列表)
        
        Log.debug(f"    C 发送 {请求类型} 数据统计:数量 {C发送请求包数量} 个,共 {REQ_DATA_LEN} 字节")
        Log.debug(f"    C 发送 {请求类型} 时间 {C发送请求时间戳}")
        Log.debug(f"    C 发完 {请求类型} 时间 {C发送请求尾包[0]}")
        Log.debug("    C 发完 %s 耗时 %.6f 秒 (含重发数据的时间)" % (请求类型, C发完请求耗时))
        
        if REQ_ack in D_S_ACK_DATA_SeqKey:      ## D_S_ACK_DATA_SeqKey 的KEY是HTTP首回应包的seq,值等于请求的ack
            C_REQ提取S_ACK = D_S_ACK_DATA_SeqKey[REQ_ack]
        else:
            C_REQ提取S_ACK = -1
        
        if C_REQ提取S_ACK == 累计计算预估S响应ACK:
            Log.debug(f"    【I】期望 S 回应 ACK  {C_REQ提取S_ACK}(请求ACK查响应SEQ) == {累计计算预估S响应ACK}(发数据累计预估)")
        else:
            Log.debug(f"    【W】期望 S 回应 ACK  {C_REQ提取S_ACK}(请求ACK查响应SEQ) != {累计计算预估S响应ACK}(发数据累计预估)")
        
        ## 检查有没有响应数据
        if C_REQ提取S_ACK in D_TCP['D_S_ACK_DATA']:        ## 初始值是负数,没有找到可用值时使用初始可以保证这个判断失败
            Log.debug(f"    【I】使用(请求ACK查响应SEQ)找到的响应信息")
            S响应ACK = C_REQ提取S_ACK
        elif 累计计算预估S响应ACK in D_TCP['D_S_ACK_DATA']:
            Log.debug(f"    【I】使用(累计计算预估S响应ACK)找到的响应信息")
            S响应ACK = 累计计算预估S响应ACK
        elif 累计计算预估S响应ACK in D_TCP['S_ACK_RST']:
            Log.debug(f"    【E】使用(累计计算预估S响应ACK)找到S强制终止连接的响应信息")
            响应状态码 = 5 ## 自定义,两种方法都找不到响应信息,且发现了S发的RST信息
            L_TCP_TIME.append((REQ_ack, C发送请求时间戳, 请求类型, 响应状态码, 0, C发完请求耗时, 0, 0, C发送请求包数量, 0))
            continue
        elif REQ_ack in D_TCP['C_ACK_RST_AckKey']:
            Log.debug(f"    【E】找不到响应数据,找到C主动终止了连接:发现ACK={REQ_ack} 在 D_TCP['C_ACK_RST_AckKey']出现")
            响应状态码 = 1 ## 自定义,两种方法都找不到响应信息,且发现了C发的RST信息
            L_TCP_TIME.append((REQ_ack, C发送请求时间戳, 请求类型, 响应状态码, 0, C发完请求耗时, 0, 0, C发送请求包数量, 0))
            continue
        elif REQ_ack in D_TCP['S_ACK_FIN_SeqKey']:
            Log.debug(f"    【E】找不到响应数据,找到服务端发送FIN终止连接:{D_TCP['S_ACK_FIN_SeqKey'][REQ_ack]}")
            响应状态码 = 3 ## 自定义,两种方法都找不到响应信息,且找到服务端发起了FIN终止
            L_TCP_TIME.append((REQ_ack, C发送请求时间戳, 请求类型, 响应状态码, 0, C发完请求耗时, 0, 0, C发送请求包数量, 0))
            continue
        else:
            Log.debug("    【E】两种方法都找不到响应信息,且没有发现C发的RST信息,应该是真没有响应")
            响应状态码 = 2 ## 自定义,两种方法都找不到响应信息,且没有发现C发的RST信息,应该是真没有响应
            L_TCP_TIME.append((REQ_ack, C发送请求时间戳, 请求类型, 响应状态码, 0, C发完请求耗时, 0, 0, C发送请求包数量, 0))
            continue
        
        ## 2个方法中任意成功一个就继续分析
        S发送响应数据包列表 = D_TCP['D_S_ACK_DATA'][S响应ACK]
        
        ## 有响应响应信息会继续下面的分析

        
        ## 当客户端发起RST,服务器依据可能返回响应数据,为了区分出这种情况,最后再查一下这个,修改响应码标识
        if S响应ACK in D_TCP['C_ACK_RST_SeqKey']:
            Log.debug(f"    【W】有响应的情况下C主动终止了连接:RST_Seq=S响应ACK={S响应ACK} 在 D_TCP['C_ACK_RST_SeqKey']出现(设置自定义SC=4)")
            响应状态码 = 4    # 自定义,有响应的情况下表示C发起了RST,强制断开连接
        
        ## 检查一下有没有异常的数据包(乱序/编号有问题)
        if 检查数据包编号(S发送响应数据包列表) == 1:
            Log.debug("    【WARNING】数据包编号有乱序")
        else:
            Log.debug("    数据包编号正常(单包请求忽略检查)")
        
        ## 接到的正常响应包
        R_REQ_DATA_LEN = 0
        for i in S发送响应数据包列表:
            Log.debug(f"        {请求类型} 响应信息 {i}")
            R_REQ_DATA_LEN += i[3]
        
        ## S重发响应数据包,(Seq,Ack)重复的包
        S发送请求包数量 = len(S发送响应数据包列表)
        #        S发送请求包数量 += 1
        
        Log.debug("    S 响应数据统计:数量 %d 个,共 %d 字节,响应状态码 %d" % (S发送请求包数量, R_REQ_DATA_LEN, 响应状态码))
        S发送响应数据包首数据 = S发送响应数据包列表[0]
        S发送响应数据包尾数据 = S发送响应数据包列表[-1]
        S处理请求耗时 = S发送响应数据包首数据[0] - C发送请求尾包[0]
        Log.debug(f'    S 发送响应数据时间 {S发送响应数据包首数据[0]}')
        Log.debug(f'    S 发完响应数据时间 {S发送响应数据包尾数据[0]}')
        S发完响应数据耗时 = S发送响应数据包尾数据[0] - S发送响应数据包首数据[0]
        Log.debug("    S 发完响应数据耗时 %.6f (含重发数据的时间)" % (S发完响应数据耗时))
        Log.debug("    S 处理 %s  耗时 %.6f 秒 (S发送响应数据首包时间 - C发送请求数据尾包时间)" % (请求类型, S处理请求耗时))
        C完成请求耗时 = S发送响应数据包尾数据[0] - C发送请求时间戳
        Log.debug("    完成 %s  总耗时 %.6f 秒 (S发送响应数据尾包时间 - C发送请求数据首包时间)" % (请求类型, C完成请求耗时))
        ## 记录耗时(正常情况)
        L_TCP_TIME.append((REQ_ack, C发送请求时间戳, 请求类型, 响应状态码, C完成请求耗时, C发完请求耗时, S处理请求耗时, S发完响应数据耗时, C发送请求包数量, S发送请求包数量))

        ## 查看重复的首个请求信息
        #if K in D_TCP['C_HTTP_REQ_RPT']:
        #    Log.warning(f"【WARNING】请求首包有重发 {D_TCP['C_HTTP_REQ_RPT'][K]}")
        Log.debug('')
    return(L_TCP_TIME)


## 请求耗时信息汇总 L_TCP_TIME
def SHOW_TCP_ALTERNATELY_TIME(L_TCP_TIME, 视角='S'):
    Log.info('')
    if L_TCP_TIME != []:
        if 视角 == 'S':
            Log.info("【每个请求发出到收完响应数据的耗时信息(服务端抓包)单位秒】")
            Log.info(" %-9s %-9s %4s %3s     %5s %7s %7s %7s  %4s  %4s" % ('请求(ack)','时间戳','类型','SC','COMP','C_req','SYS','S_send','C_PKS','S_PKS'))
        else:
            Log.info("【每个请求发出到收完响应数据的耗时信息(客户端抓包)单位秒】")
            Log.info(" %-9s %-9s %4s %3s     %5s %7s %7s %7s  %4s  %4s" % ('请求(ack)','时间戳','类型','SC','COMP','C_req','SYS_NET','S_send','C_PKS','S_PKS'))
        for i in L_TCP_TIME:
            Log.info(" %10d  %.2f  %4s  %-4d %7.3f %7.3f %7.3f %7.3f   %4d   %4d" % i)
        Log.info('')



## 正常返回0 错误返回1
def 检查数据包编号(L):
    #Log.debug(f"   L{L}")
    #Log.debug(f"    S 0 {L[0]}")
    数量 = len(L)
    if 数量 > 1:
        SEQ = L[0][1]
        LEN = L[0][3]
        for i in range(1, 数量):
            if L[i][1] == SEQ + LEN:
                SEQ = SEQ + LEN
                LEN = L[i][3]
                #Log.debug(f"    V {i} {L[i]}")
            else:
                #Log.debug(f"    X {i} {L[i]}")
                Log.debug(f"    【DEBUG】错误包{L[i]}")
                return(1)
    return(0)



## 整个TCP交互的开始和结束时间(估计值,不精确)
def 整个TCP交互的开始和结束时间(D_TCP, L_DATA):
    if D_TCP['C_SYN'] == ():
        C_发起开始时间 = L_DATA[0][1]             # 第一个包的时间戳
    else:
        C_发起开始时间 = D_TCP['C_SYN'][0]
    if D_TCP['C_ACK_FIN'] == ():
        C_发起结束时间 = L_DATA[-1][1]            # 最后一个包的时间戳
    else:
        C_发起结束Ack = D_TCP['C_ACK_FIN'][2]                    ## D_TCP['C_ACK_FIN'] = (时间戳, Seq, Ack, TCP数据长度)
        if C_发起结束Ack in D_TCP['D_C_ACK']:
            C_发起结束时间 = D_TCP['D_C_ACK'][C_发起结束Ack][0][0]   ## C FIN 前的ack纯确认包作为实际交互结束时间点
        else:
            C_发起结束Seq = D_TCP['C_ACK_FIN'][1]
            C_发起结束时间 = D_TCP['D_S_ACK_DATA'][C_发起结束Seq][0][0]   ## C FIN 前的服务端最后数据包作为交互结束时间点(客户端直接结束交互的情况)

    return(C_发起开始时间, C_发起结束时间)
    


## 从TCP会话字典信息D_TCP中统计分析 Keep-Alive 时间(分服务端原因造成的和客户端原因造成的情况)
## L_C_KeepAlive_RANGE (客户端原因等待的时间戳区间列表:可能是客户端在等待什么,如等待用户输入,等待其他任务完成)
## L_S_KeepAlive_RANGE (服务端原因等待的时间戳区间列表:可能是服务端在处理耗时的请求)
def 分析KeepAlive耗时(D_TCP):
    L_C_KeepAlive_RANGE = []
    L_S_KeepAlive_RANGE = []

    ## 先分析C首先发起KeepAlive的情况
    Log.debug(f"  遍历 D_TCP['C_TCP_Keep_Alive']")
    for Ack in D_TCP['C_TCP_Keep_Alive']:                                                                   # 每个客户端发起每段保持连接信息(相同Ack是同一KeepAlive区间)
        Log.debug(f"    {Ack}:{D_TCP['C_TCP_Keep_Alive'][Ack]}")
        归属标识 = ''                                                                                       # 需要判断是客户端还是服务器原因造成的KeepAlive
        C_time = D_TCP['C_TCP_Keep_Alive'][Ack][0][0]                                                       # 本段C发起KeepAlive的第一个包的时间戳
        C_seq = D_TCP['C_TCP_Keep_Alive'][Ack][0][1]                                                        # 本段C发起KeepAlive的第一个包的seq
        if C_seq+1 in D_TCP['S_TCP_Keep_Alive'] and D_TCP['S_TCP_Keep_Alive'][C_seq+1][0][0] < C_time:      # S比C早发起保持连接,忽略C,按S的算(防止后面处理S发起KeepAlive时重复计算)
            Log.debug(f"    忽略{D_TCP['C_TCP_Keep_Alive'][Ack][0]}")
        else:
            Log.debug(f"    确认{D_TCP['C_TCP_Keep_Alive'][Ack][0]}")
            ## 根据 KeepAlive 前后的数据包判断 KeepAlive 是谁的原因造成的
            ## 往前找开始KeepAlive的包(比KeepAlive包提前一个才是真实开始等待的时间)
            if C_seq+1 in D_TCP['D_S_ACK_DATA'] and C_time>=D_TCP['D_S_ACK_DATA'][C_seq+1][-1][0]:          # 客户端发KeepAlive,客户端seq+1会是服务端发数据Ack,所以在D_TCP['D_S_ACK_DATA']中找
                Log.debug(f"    (始)前包服务器响应数据,归客户端原因 ACK(C_seq+1)={C_seq+1} in D_TCP['D_S_ACK_DATA']={D_TCP['D_S_ACK_DATA'][C_seq+1]}")
                选择开始保持连接的数据包 = D_TCP['D_S_ACK_DATA'][C_seq+1][-1]                               # 开始部分要找最后一个数据包
                归属标识 = 'C'
            elif Ack in D_TCP['D_C_ACK_DATA'] and C_time>=D_TCP['D_C_ACK_DATA'][Ack][-1][0]:                # 客户端发KeepAlive,但是客户端发数据在等待服务端,所以在D_TCP['D_C_ACK_DATA']中找,ACK相同
                Log.debug(f"    (始)前包客户端响应数据,归服务端原因ACK={Ack} in D_TCP['D_C_ACK_DATA']={D_TCP['D_C_ACK_DATA'][Ack]}")
                选择开始保持连接的数据包 = D_TCP['D_C_ACK_DATA'][Ack][-1]                                   # 开始部分要找最后一个数据包
                归属标识 = 'S'
            else:
                if D_TCP['C_TCP_Keep_Alive'][Ack][0][5] == D_TCP['L_DATA'][0][0]:                           # 在会话抓到的开头的包就是KeepAlive(第一个包的第一个编号判断)
                    ## 往后找
                    Log.debug(f"    开头的包就是KeepAlive(C发起),根据KeepAlive结束后是C发请求还是S响应数据来判断是谁的原因造成本次KeepAlive")
                    if Ack in D_TCP['D_C_ACK_DATA'] and C_time <= D_TCP['D_C_ACK_DATA'][Ack][0][0]:
                        Log.debug(f"    (始)后包客户端请求数据,归客户端原因 ACK={Ack} in D_TCP['D_C_ACK_DATA']={D_TCP['D_C_ACK_DATA'][Ack]} 本身为开始计时包{D_TCP['C_TCP_Keep_Alive'][Ack]}")
                        归属标识 = 'C'
                    elif C_seq+1 in D_TCP['D_S_ACK_DATA'] and C_time <= D_TCP['D_S_ACK_DATA'][C_seq+1][0][0]:
                        Log.debug(f"    (始)后包服务器响应数据,归服务端原因 ACK(C_seq+1)={C_seq+1} in D_TCP['D_S_ACK_DATA']={D_TCP['D_S_ACK_DATA'][C_seq+1]} 本身为开始计时包{D_TCP['C_TCP_Keep_Alive'][Ack]}")
                        归属标识 = 'S'
                    else:
                        Log.debug(f"    判断不出KeepAlive是哪方原因造成的")
                        continue
                    选择开始保持连接的数据包 = D_TCP['C_TCP_Keep_Alive'][Ack][0]
                else:
                    Log.debug(f"    不在D_S_ACK_DATA及D_C_ACK_DATA中,有问题")
                    continue
            
            等待开始时间 = 选择开始保持连接的数据包[0]
            
            ## 找结束KeepAlive,开始发有效数据的第一个包(比KeepAlive包晚一个才是真实结束等待的时间)
            if Ack in D_TCP['D_C_ACK_DATA'] and C_time<=D_TCP['D_C_ACK_DATA'][Ack][0][0]:
                Log.debug(f"    (终)ACK={Ack} in D_TCP['D_C_ACK_DATA']={D_TCP['D_C_ACK_DATA'][Ack]}")
                选择标识结束保持连接的数据包 = D_TCP['D_C_ACK_DATA'][Ack][0]
            elif C_seq+1 in D_TCP['D_S_ACK_DATA'] and C_time<=D_TCP['D_S_ACK_DATA'][C_seq+1][0][0]:
                Log.debug(f"    (终)ACK(C_seq+1)={C_seq+1} in D_TCP['D_S_ACK_DATA']={D_TCP['D_S_ACK_DATA'][C_seq+1]}")
                选择标识结束保持连接的数据包 = D_TCP['D_S_ACK_DATA'][C_seq+1][0]
            else:
                ## 有可能等待最后都没有后续数据,找最后一个保持连接的包
                Log.debug("    (W)找不到后续数据包")
                X1 = D_TCP['C_TCP_Keep_Alive'][Ack][-1]
                if C_seq+1 in D_TCP['S_TCP_Keep_Alive']:
                    X2 = D_TCP['S_TCP_Keep_Alive'][C_seq+1][-1]
                    if X1[0] > X2[0]:
                        选择标识结束保持连接的数据包 = X1
                    else:
                        选择标识结束保持连接的数据包 = X2
                else:
                    选择标识结束保持连接的数据包 = X1
                Log.debug(f"    (终)选择标识结束保持连接的数据包={选择标识结束保持连接的数据包}")
                
            等待结束时间 = 选择标识结束保持连接的数据包[0]
            Log.debug(f"      {选择开始保持连接的数据包} <-{归属标识} KeepAlive 时间区间-> {选择标识结束保持连接的数据包}")

            if 归属标识 == 'C':
                L_C_KeepAlive_RANGE.append((等待开始时间, 等待结束时间))
            elif 归属标识 == 'S':
                L_S_KeepAlive_RANGE.append((等待开始时间, 等待结束时间))
            else:
                pass
        #Log.debug("")
    
    ## 再分析S首先发起KeepAlive的情况
    Log.debug(f"  遍历 D_TCP['S_TCP_Keep_Alive']")
    for Ack in D_TCP['S_TCP_Keep_Alive']:                                                                   # 每个服务端发起每段保持连接信息(相同Ack是同一KeepAlive区间)
        Log.debug(f"    {Ack}:{D_TCP['S_TCP_Keep_Alive'][Ack]}")
        归属标识 = ''                                                                                       # 需要判断是客户端还是服务器原因造成的KeepAlive
        C_time = D_TCP['S_TCP_Keep_Alive'][Ack][0][0]                                                       # 本段S发起KeepAlive的第一个包的时间戳
        C_seq = D_TCP['S_TCP_Keep_Alive'][Ack][0][1]                                                        # 本段S发起KeepAlive的第一个包的seq
        if C_seq+1 in D_TCP['C_TCP_Keep_Alive'] and D_TCP['C_TCP_Keep_Alive'][C_seq+1][0][0] < C_time:      # C比S早发起保持连接,忽略S,按C的算(前面已经处理防止重复计算)
            Log.debug(f"    忽略{D_TCP['S_TCP_Keep_Alive'][Ack][0]}")
        else:
            Log.debug(f"    确认{D_TCP['S_TCP_Keep_Alive'][Ack][0]}")
            ## 根据 KeepAlive 前后的数据包判断 KeepAlive 是谁的原因造成的
            ## 往前找开始KeepAlive的包(比KeepAlive包提前一个才是真实开始等待的时间)
            if C_seq+1 in D_TCP['D_C_ACK_DATA'] and C_time>=D_TCP['D_C_ACK_DATA'][C_seq+1][-1][0]:          # 客户端发KeepAlive,客户端seq+1会是服务端发数据Ack,所以在D_TCP['D_C_ACK_DATA']中找
                Log.debug(f"    (始)前包客户端请求数据,归服务端原因 ACK(C_seq+1)={C_seq+1} in D_TCP['D_C_ACK_DATA']={D_TCP['D_C_ACK_DATA'][C_seq+1]}")
                选择开始保持连接的数据包 = D_TCP['D_C_ACK_DATA'][C_seq+1][-1]
                归属标识 = 'S'
            elif Ack in D_TCP['D_S_ACK_DATA'] and C_time>=D_TCP['D_S_ACK_DATA'][Ack][-1][0]:                # 客户端发KeepAlive,但是客户端发数据在等待服务端,所以在D_TCP['D_S_ACK_DATA']中找,ACK相同
                Log.debug(f"    (始)前包服务端响应数据,归客户端原因ACK={Ack} in D_TCP['D_S_ACK_DATA']={D_TCP['D_S_ACK_DATA'][Ack]}")
                选择开始保持连接的数据包 = D_TCP['D_S_ACK_DATA'][Ack][-1]
                归属标识 = 'C'
            else:
                if D_TCP['S_TCP_Keep_Alive'][Ack][0][5] == D_TCP['L_DATA'][0][0]:                           # 在会话抓到的开头的包就是KeepAlive(第一个包的第一个编号判断)
                    Log.debug(f"    开头的包就是KeepAlive(S发起),根据KeepAlive结束后是C发请求还是S响应数据来判断是谁的原因造成本次KeepAlive")
                    ## 往后找
                    if C_seq+1 in D_TCP['D_C_ACK_DATA'] and C_time <= D_TCP['D_C_ACK_DATA'][C_seq+1][0][0]: # C请求
                        Log.debug(f"    (始)后包客户端请求数据,归客户端原因 ACK={Ack} in D_TCP['D_C_ACK_DATA']={D_TCP['D_C_ACK_DATA'][Ack]} 本身为开始计时包{D_TCP['S_TCP_Keep_Alive'][Ack]}")
                        归属标识 = 'C'
                    elif Ack in D_TCP['D_S_ACK_DATA'] and C_time <= D_TCP['D_S_ACK_DATA'][Ack][0][0]:       # S响应
                        Log.debug(f"    (始)后包服务器响应数据,归服务端原因 ACK(C_seq+1)={C_seq+1} in D_TCP['D_S_ACK_DATA']={D_TCP['D_S_ACK_DATA'][C_seq+1]} 本身为开始计时包{D_TCP['C_TCP_Keep_Alive'][Ack]}")
                        归属标识 = 'S'
                    else:
                        Log.debug(f"    判断不出KeepAlive是哪方原因造成的")
                        continue
                    选择开始保持连接的数据包 = D_TCP['S_TCP_Keep_Alive'][Ack][0]
                else:
                    Log.error(f"    不在D_C_ACK_DATA及D_S_ACK_DATA中,有问题")
                    continue
            
            等待开始时间 = 选择开始保持连接的数据包[0]
            
            ## 找结束KeepAlive,开始发有效数据的第一个包(比KeepAlive包晚一个才是真实结束等待的时间)
            if Ack in D_TCP['D_S_ACK_DATA'] and C_time<=D_TCP['D_S_ACK_DATA'][Ack][0][0]:
                Log.debug(f"    (终)ACK={Ack} in D_TCP['D_S_ACK_DATA']={D_TCP['D_S_ACK_DATA'][Ack]}")
                选择标识结束保持连接的数据包 = D_TCP['D_S_ACK_DATA'][Ack][0]
            elif C_seq+1 in D_TCP['D_C_ACK_DATA'] and C_time<=D_TCP['D_C_ACK_DATA'][C_seq+1][0][0]:
                Log.debug(f"    (终)ACK(C_seq+1)={C_seq+1} in D_TCP['D_C_ACK_DATA']={D_TCP['D_C_ACK_DATA'][C_seq+1]}")
                选择标识结束保持连接的数据包 = D_TCP['D_C_ACK_DATA'][C_seq+1][0]
            else:
                ## 有可能等待最后都没有后续数据,找最后一个保持连接的包
                Log.debug("    (W)找不到后续数据包")
                X1 = D_TCP['S_TCP_Keep_Alive'][Ack][-1]
                if C_seq+1 in D_TCP['C_TCP_Keep_Alive']:
                    X2 = D_TCP['C_TCP_Keep_Alive'][C_seq+1][-1]
                    if X1[0] > X2[0]:
                        选择标识结束保持连接的数据包 = X1
                    else:
                        选择标识结束保持连接的数据包 = X2
                else:
                    选择标识结束保持连接的数据包 = X1
                Log.debug(f"    (终)选择标识结束保持连接的数据包={选择标识结束保持连接的数据包}")
                
            等待结束时间 = 选择标识结束保持连接的数据包[0]
            Log.debug(f"      {选择开始保持连接的数据包} <-{归属标识} KeepAlive 时间区间-> {选择标识结束保持连接的数据包}")

            if 归属标识 == 'C':
                L_C_KeepAlive_RANGE.append((等待开始时间, 等待结束时间))
            elif 归属标识 == 'S':
                L_S_KeepAlive_RANGE.append((等待开始时间, 等待结束时间))
            else:
                pass
        #Log.debug("")
    
    return(L_C_KeepAlive_RANGE, L_S_KeepAlive_RANGE)



## 客户端响应ACK消息和下次客户端发送请求的时间间隔(这两个seq和ack相同的情况是没有Keep-Alive的,可区分)
def 客户端发起请求间隔耗时(D_TCP):
    '''
    28 1625539397.99094  192.168.100.252 192.168.100.13   9577  1433 3622347657 1299493386 ACK PSH   45 C_req       256  0x5e0 
    29 1625539397.991202 192.168.100.13  192.168.100.252  1433  9577 1299493386 3622347702 ACK PSH   26     S_ack   260  0xe40
    
    30 1625539398.031743 192.168.100.252 192.168.100.13   9577  1433 3622347702 1299493412 ACK        0             256 0x6ddb 上次交互完成(响应以接完S数据包)
    31 1625539414.326215 192.168.100.252 192.168.100.13   9577  1433 3622347702 1299493412 ACK PSH  275 C_req       256 0x3438 下次交互开始
    
    32 1625539414.326322 192.168.100.13  192.168.100.252  1433  9577 1299493412 3622347977 ACK PSH   68     S_ack   258 0x4780 
    '''
    L_TIME_Client_WAIT = []                                         # 客户端发起请求间隔耗时的每段时间戳
    for ACK in D_TCP['D_C_ACK']:                                    # 遍历C响应收到S数据的包(先响应后发数据,说明本地数据处理要时间或等待用户输入指令等)
        if ACK not in D_TCP['C_TCP_Keep_Alive']:                    # 不是 Keep-Alive 的情况才计算相邻两次请求的间隔时间
            TIME1, Seq1, Ack1, A1, B1 = D_TCP['D_C_ACK'][ACK][0]    # 客户端响应服务器对上次请求收到服务器的响应【多个包只取第一个】
            if Ack1 in D_TCP['D_C_ACK_DATA']:                       # 找到紧接着的下一次请求
                for j in D_TCP['D_C_ACK_DATA'][Ack1]:
                    TIME2, Seq2, Ack2, A2, B2, C2 = j
                    if Seq1 == Seq2 and Ack1 == Ack2:
                        #Log.debug(f"[TIME]= {TIME2-TIME1} 下次交互开始={j} - 上次交互完成={D_TCP['D_C_ACK'][ACK][0]}")
                        L_TIME_Client_WAIT.append((TIME1, TIME2))
                        break
        #    else:
        #        Log.debug("找不到下次请求信息,Ack1 不在 D_TCP['D_C_ACK_DATA'] 中")
        #else:
        #    Log.debug(f"D_TCP['D_C_ACK'][ACK] 有保持连接记录,忽略计算 {D_TCP['D_C_ACK'][ACK]}")
        #Log.debug('')
    return(L_TIME_Client_WAIT)



## 从TCP会话字典信息D_TCP中统计分析 接收窗口大小=0 造成的时间消耗(分服务端原因造成的和客户端原因造成的情况)
## 返回 L_C_WIN_0_RANGE 客户端接收窗口满时间段列表 [(TimeStart, TimeEnd),]
## 返回 L_S_WIN_0_RANGE 服务端接收窗口满时间段列表 [(TimeStart, TimeEnd),]
def 分析接收窗口满耗时(D_TCP):
    #print("D_TCP['D_C_WIN_0']", D_TCP['D_C_WIN_0'])
    #print("D_TCP['D_S_WIN_0']", D_TCP['D_S_WIN_0'])
    L_C_WIN_0_RANGE = []
    L_S_WIN_0_RANGE = []
    for K in D_TCP['D_C_WIN_0']:
        TimeStart = D_TCP['D_C_WIN_0'][K][0][0]
        if K in D_TCP['D_C_ACK']:
            TimeEnd = D_TCP['D_C_ACK'][K][-1][0]
        else:
            TimeEnd = D_TCP['D_C_WIN_0'][K][-1][0]
        L_C_WIN_0_RANGE.append((TimeStart, TimeEnd))
    for K in D_TCP['D_S_WIN_0']:
        TimeStart = D_TCP['D_S_WIN_0'][K][0][0]
        if K in D_TCP['D_S_ACK']:
            TimeEnd = D_TCP['D_S_ACK'][K][-1][0]
        else:
            TimeEnd = D_TCP['D_S_WIN_0'][K][-1][0]
        L_S_WIN_0_RANGE.append((TimeStart, TimeEnd))
    return(L_C_WIN_0_RANGE, L_S_WIN_0_RANGE)



## 画图,TCP会话交互中各种用时信息画成图(开始时间结束时间、保持连接时间和客户端请求间隔等待时间等)
def TCP会话用时信息画图(TCP_START_TIME_MIN, TCP_END_TIME_MAX, D_C_KeepAlive, D_C_WAIT, D_S_KeepAlive, D_C_WIN_0_TIME, D_S_WIN_0_TIME):

    开始时间点 = int(TCP_START_TIME_MIN)
    结束时间点 = int(TCP_END_TIME_MAX)
    X = [i for i in range(开始时间点, 结束时间点+1)]
    X_TEXT = [time.strftime('%H:%M:%S', time.localtime(i)) for i in range(开始时间点, 结束时间点+1)]
    #print(f"X轴时间戳 X[0]={X[0]} X[-1]={X[-1]}")
    #print(X_TEXT)
    
    D_Y = {}
    #print("D_C_KeepAlive", D_C_KeepAlive)
    for K in D_C_KeepAlive:
        
        L_S_KeepAlive_RANGE = D_S_KeepAlive[K]              # 提取这个key下的KeepAlive时间区间列表
        P_S_KeepAlive_TIME = set()                          # 把所有时间按秒生成集合,方便匹配查找
        for TIMEs in L_S_KeepAlive_RANGE:                   # 遍历每一个KeepAlive时间区间
            t1 = TIMEs[0]                                   # 开始时间
            t2 = TIMEs[1]                                   # 结束时间
            L = [i for i in range(int(t1), int(t2))]        # 以秒为单位生成时间区间列表
            LP = set(L)                                     # 时间区间列表 转 时间区间集合
            P_S_KeepAlive_TIME = P_S_KeepAlive_TIME | LP    # 并集
            
        L_C_KeepAlive_RANGE = D_C_KeepAlive[K]
        P_C_KeepAlive_TIME = set()
        for TIMEs in L_C_KeepAlive_RANGE:
            L = [i for i in range(int(TIMEs[0]), int(TIMEs[1]))]
            P_C_KeepAlive_TIME = P_C_KeepAlive_TIME | set(L)

        L_TIME_Client_WAIT = D_C_WAIT[K]
        P_C_WAIT_TIME = set()
        for TIMEs in L_TIME_Client_WAIT:
            L = [i for i in range(int(TIMEs[0]), int(TIMEs[1]))]
            P_C_WAIT_TIME = P_C_WAIT_TIME | set(L)

        L_C_WIN_0_RANGE = D_C_WIN_0_TIME[K]
        P_C_WIN_0_TIME = set()
        for TIMEs in L_C_WIN_0_RANGE:
            L = [i for i in range(int(TIMEs[0]), int(TIMEs[1]))]
            P_C_WIN_0_TIME = P_C_WIN_0_TIME | set(L)
        
        L_S_WIN_0_RANGE = D_S_WIN_0_TIME[K]
        P_S_WIN_0_TIME = set()
        for TIMEs in L_S_WIN_0_RANGE:
            L = [i for i in range(int(TIMEs[0]), int(TIMEs[1]))]
            P_S_WIN_0_TIME = P_S_WIN_0_TIME | set(L)
        
        Y = []                              # 为每个Key对应的交互生成各自Y值
        ## ax.set_yticklabels(['-2','Start/End(-1)','RUN(0)','Client-Wait(1)','(C)Keep-Alive(2)','(S)Keep-Alive(3)','(C)windows=0(4)','(S)windows=0(5)']) # 自定义Y轴刻度
        for i in X:                         # 遍历整个时间区间(所有交互统一的时间区间)
            if i < 开始时间点 or i > 结束时间点:
                Y.append(-2)                # 超出开始和结束时间
            elif i in (开始时间点, 结束时间点):
                Y.append(-1)                # 标记开始和结束时间
            elif i in P_C_KeepAlive_TIME:
                Y.append(2)                 # C发起保持连接造成的等待
            elif i in P_S_KeepAlive_TIME:
                Y.append(3)                 # S发起保持连接造成的等待
            elif i in P_C_WAIT_TIME:
                Y.append(1)                 # 等待客户端发包造成的等待
            elif i in P_C_WIN_0_TIME:
                Y.append(4)                 # 客户端接收窗口满造成的等待
            elif i in P_S_WIN_0_TIME:
                Y.append(5)                 # 服务端接收窗口满造成的等待
            else:
                Y.append(0)                 # 正在运行的时间
                #Log.debug(f"RUN {i}")
        D_Y[K] = Y

    ## 分成多个图显示(通用、可控)
    X轴刻度数量 = 10               # X轴只显示10个刻度
    X轴间隔 = len(X)//X轴刻度数量
    if X轴间隔 == 0:               # 按秒合并的时间刻度,可能会少于10个
        X轴间隔 = 1                # 显示每一个刻度
    图数量 = len(D_Y)
    import matplotlib.pyplot as plt
    import matplotlib.ticker as ticker                                     # 解决时间为X轴可能会造成刻度太密集,看不清问题
    if 图数量 == 1:
        L_KEY = [KEY for KEY in D_Y]
        fig, ax = plt.subplots(1,1)
        ax.xaxis.set_major_locator(ticker.MultipleLocator(X轴间隔))        # X轴间隔刻度
        ax.yaxis.set_major_locator(ticker.MultipleLocator(1))              # 强制Y轴每个刻度都显示
        ax.plot(X_TEXT, D_Y[L_KEY[0]], '.', label=L_KEY[0], markersize=2)
        ax.set_yticklabels(['-2','Start/End(-1)','RUN(0)','Client-Wait(1)','(C)Keep-Alive(2)','(S)Keep-Alive(3)','(C)windows=0(4)','(S)windows=0(5)']) # 自定义Y轴刻度
        ax.legend()                                  # 显示图例label
        plt.ylim(-1.5,5.5)                           # 控制y轴显示范围,隐藏-1值
        #plt.grid()                                  # 使用网格,方便查看XY对应
        plt.xlabel('Time')                           # X轴标注
        #plt.title('TITLE')                          # 标题
        plt.show()
    else:
        fig, ax = plt.subplots(图数量, 1, sharex=True, sharey=True, figsize=(20,10))                    # n行1列,统一X和Y轴刻度
        plt.ylim(-1.5,3.5)                                                                              # 控制y轴显示范围,隐藏-1值
        AX_List = ax.ravel()                                                                            # 子图列表:AX_List[0]第一个子图,AX_List[1]第二个子图...
        L_KEY = [KEY for KEY in D_Y]
        
        for n in range(0, 图数量):
            AX_List[n].plot(X_TEXT,D_Y[L_KEY[n]], '.', label=L_KEY[n], markersize=2)                    # 第1个图的第1根线
            AX_List[n].legend()                                                                         # 显示图例label
            AX_List[n].set_yticklabels(['-2','Start/End(-1)','RUN(0)','Client-Wait(1)','(C)Keep-Alive(2)','(S)Keep-Alive(3)','(C)windows=0(4)','(S)windows=0(5)']) # 自定义Y轴刻度
            AX_List[n].xaxis.set_major_locator(ticker.MultipleLocator(X轴间隔))                         # X轴间隔N标一个刻度
            AX_List[n].yaxis.set_major_locator(ticker.MultipleLocator(1))                               # 强制Y轴每个刻度都显示
        plt.show()



## 记录日志
def SHOW_TIME_WAIT(C_发起开始时间, C_发起结束时间, L_TIME_Client_WAIT, L_C_KeepAlive_RANGE, L_S_KeepAlive_RANGE, L_C_WIN_0_RANGE, L_S_WIN_0_RANGE, D_TCP):

    会话总用时 = C_发起结束时间 - C_发起开始时间
    开始时间 = time.strftime('%H:%M:%S', time.localtime(C_发起开始时间))
    结束时间 = time.strftime('%H:%M:%S', time.localtime(C_发起结束时间))
    源地址, 源端口 = D_TCP['CLIENT']
    目的地址, 目的端口 = D_TCP['SERVER']
    
    ## 时间合计
    C_KeepAlive时间合计 = sum([i[1]-i[0] for i in L_C_KeepAlive_RANGE])
    C_KeepAlive间隔明细 = [(time.strftime('%H:%M:%S', time.localtime(i[0])), time.strftime('%H:%M:%S', time.localtime(i[1]))) for i in L_C_KeepAlive_RANGE]
    
    S_KeepAlive时间合计 = sum([i[1]-i[0] for i in L_S_KeepAlive_RANGE])
    S_KeepAlive间隔明细 = [(time.strftime('%H:%M:%S', time.localtime(i[0])), time.strftime('%H:%M:%S', time.localtime(i[1]))) for i in L_S_KeepAlive_RANGE]
    
    C间隔发包时间合计 = sum([i[1]-i[0] for i in L_TIME_Client_WAIT])
    
    C接收窗口满耗时合计 = sum([i[1]-i[0] for i in L_C_WIN_0_RANGE])
    C接收窗口满耗时明细 = [(time.strftime('%H:%M:%S', time.localtime(i[0])), time.strftime('%H:%M:%S', time.localtime(i[1]))) for i in L_C_WIN_0_RANGE]
    S接收窗口满耗时合计 = sum([i[1]-i[0] for i in L_S_WIN_0_RANGE])
    S接收窗口满耗时明细 = [(time.strftime('%H:%M:%S', time.localtime(i[0])), time.strftime('%H:%M:%S', time.localtime(i[1]))) for i in L_S_WIN_0_RANGE]
    
    程序正常交互用时合计 = 会话总用时 - C_KeepAlive时间合计 - S_KeepAlive时间合计 - C间隔发包时间合计 - C接收窗口满耗时合计 - S接收窗口满耗时合计
    
    T_DATA = (源地址, 源端口, 目的地址, 目的端口, 开始时间, 结束时间, round(会话总用时,2), round(C_KeepAlive时间合计,2), round(S_KeepAlive时间合计,2), round(C间隔发包时间合计,2), round(C接收窗口满耗时合计,2), round(S接收窗口满耗时合计,2), round(程序正常交互用时合计,2))
    Log.info("%-15s %-5s <--> %-15s %-5s    %8s %8s %10s %10s  %9s %13s %13s %13s %9s" % T_DATA)
    Log.debug(f"  C_KeepAlive间隔明细={C_KeepAlive间隔明细}")
    Log.debug(f"  S_KeepAlive间隔明细={S_KeepAlive间隔明细}")
    Log.debug(f"  C接收窗口满耗时明细={C接收窗口满耗时明细}")
    Log.debug(f"  S接收窗口满耗时明细={S接收窗口满耗时明细}")
    Log.debug(f"  C_发包间隔明细")
    for i in L_TIME_Client_WAIT:
        TIME1,TIME2 = i
        #if TIME2-TIME1 > 0.5:   # 间隔大于0.5秒的才记录明细,太短没有显示必要
        Log.debug(f"    {i}={time.strftime('%H:%M:%S', time.localtime(TIME1))} -> {time.strftime('%H:%M:%S', time.localtime(TIME2))} 时间间隔={round(TIME2-TIME1, 6)}秒")
    
    return(T_DATA)



## 统计整个会话中收发数据情况
def 统计整个会话中收发数据情况(D_TCP):
    C_Tx_DATA_ALL = D_TCP['C_Tx_DATA_ALL']
    S_Tx_DATA_ALL = D_TCP['S_Tx_DATA_ALL']
    try:
        C_FIN_SEQ = D_TCP['C_ACK_FIN'][1]
    except:
        Log.debug('无C_FIN_SEQ')
        Log.debug(f"C_Tx_DATA_ALL={C_Tx_DATA_ALL}")
        Log.debug(f"S_Tx_DATA_ALL={S_Tx_DATA_ALL}")
    else:
        if C_Tx_DATA_ALL == C_FIN_SEQ:
            Log.debug(f"C_Tx_DATA_ALL={C_Tx_DATA_ALL} 正常")
        else:
            Log.debug(f"C_Tx_DATA_ALL={C_Tx_DATA_ALL} 异常")
        
        for K in D_TCP['S_ACK_FIN_SeqKey']:
            S_FIN_SEQ = D_TCP['S_ACK_FIN_SeqKey'][K][0][1]
            if S_Tx_DATA_ALL == S_FIN_SEQ:
                Log.debug(f"S_Tx_DATA_ALL={S_Tx_DATA_ALL} 正常")
            else:
                Log.debug(f"S_Tx_DATA_ALL={S_Tx_DATA_ALL} 异常")
        Log.debug('')



## 返回 D_TIME_TCP_SESSION_PACKET  字典:Key=时间戳(精确到秒),Value=这一秒内包数据和大小统计信息
def 提取PCAP中指定TCP会话双向流量信息(PCAP_File_OR_Dir, SIP, SPORT, DIP, DPORT):
    #print((PCAP_File_OR_Dir, SaveDir, 匹配内容, UserIP, UserPort))
    
    ## 统计数据包信息的字典
    D_TIME_TCP_SESSION_PACKET = {}            # {时间戳:{(SIP,SPORT,DIP,DPORT):{'TCP_Packets':0, 'TCP_Bytes':0}, (DIP,DPORT,SIP,SPORT):{'TCP_Packets':0, 'TCP_Bytes':0}}}

    ## 开始处理单个或多个PCAP文件
    E = 0
    if os.path.isfile(PCAP_File_OR_Dir):
        PATH,FileName = os.path.split(PCAP_File_OR_Dir) # 拆分路径和文件名
        #print(PATH,FileName)
        PCAP_File_OR_Dir = PATH                         # 这个后面但目录用
        L_FileName = ['\\'+FileName]                    # 文件名
    elif os.path.isdir(PCAP_File_OR_Dir):
        L_FileName = [i for i in os.listdir(PCAP_File_OR_Dir) if os.path.isfile(PCAP_File_OR_Dir+i)]       # 目录内内容列表(只取文件)
    else:
        print(f"{PCAP_File_OR_Dir} 不存在")
        E = 1
    
    if E == 0:
        
        文件数量 = len(L_FileName)
        TCP会话数量 = 0
        计数 = 1
        for F in L_FileName:
            FullFileName = PCAP_File_OR_Dir+str(F)
            print(f"拆分PCAP文件:{FullFileName} 进度:{计数}/{文件数量}")       ## 根据源目的IP和端口分成多个小pcap文件
            f = open(FullFileName, 'rb')        # 以二进制方式读取pcap格式文件
            PCAP_DATA = f.read(24)              # 读取前24字节头信息,忽略
            计数 += 1
            
            N = 0
            while 1:
                
                包头 = f.read(16)
                if not 包头:				    # 判断 包头 是否为空(读完或者本身为空时 S 为空)
                    break
                
                N += 1
                #if N == 14:
                #    break
                #print("N", N)

                PacketHeader = struct.unpack('IIII', 包头)
                时间戳 = PacketHeader[0]
                #微秒 = PacketHeader[1]
                #抓取数据包长度 = PacketHeader[2]       # 所抓获的数据包保存在pcap文件中的实际长度,以字节为单位。
                实际数据包长度 = PacketHeader[3]        # 所抓获的数据包的真实长度,如果文件中保存不是完整的数据包,那么这个值可能要比前面的数据包长度的值大。
                PacketData = f.read(实际数据包长度)
                
                
                
                # 以太部首
                FrameType = PacketData[12:14]               # 帧类型
                # IP数据报头
                if FrameType == b'\x08\x00':                # 普通包
                    pass
                elif FrameType == b'\x81\x00':              # VLAN包
                    PacketData = PacketData[0:12] + b'\x08\x00' + PacketData[18:]                                       ## 剔除VLAN数据
                    包头 = struct.pack('IIII', PacketHeader[0], PacketHeader[1], PacketHeader[2]-4, PacketHeader[3]-4)  ## 修改包头
                else:
                    #print("其他包忽略")
                    pass
                
                ## SrcIP SrcPort 和 DstIP DstPort 互换位置是同个TCP交互,要存入同个对象
                IP_Protocol = PacketData[23:24]
                ## 只要TCP协议
                if IP_Protocol == b'\x06':                  # 协议(TCP 6)(UDP 17)
                    
                    SrcIP_Bytes = PacketData[26:30]         # 源IP地址
                    DstIP_Bytes = PacketData[30:34]         # 目的IP地址
                    SrcIP = socket.inet_ntoa(SrcIP_Bytes)
                    DstIP = socket.inet_ntoa(DstIP_Bytes)
                    
                    SrcPort_Bytes = PacketData[34:36]
                    DstPort_Bytes = PacketData[36:38]
                    SrcPort = struct.unpack('>H', SrcPort_Bytes)[0]
                    DstPort = struct.unpack('>H', DstPort_Bytes)[0]
                    
                    ## 每一次TCP发数据生成2个Key
                    TCP_KEY_SD = (SrcIP,SrcPort,DstIP,DstPort)  # 以当前包的源到目的生成一个Key
                    #TCP_KEY_DS = (DstIP,DstPort,SrcIP,SrcPort)  # 以当前包的目的到源生成一个Key
                    
                    ## 只有分离出指定的IP的交互内容
                    if SrcIP == SIP and SrcPort == SPORT:
                        SAVE = 1
                    elif DstIP == SIP and DstPort == SPORT:
                        SAVE = 1
                    else:
                        SAVE = 0
                    
                    ## 匹配到是需要分析的数据包
                    if SAVE == 1:
                        if 时间戳 in D_TIME_TCP_SESSION_PACKET:                                                ## 全部流量统计
                            D_TIME_TCP_SESSION_PACKET[时间戳][TCP_KEY_SD]['Packets_Count'] += 1                ## 全部流量统计:全部数据包数量累计加1
                            D_TIME_TCP_SESSION_PACKET[时间戳][TCP_KEY_SD]['Packets_Bytes'] += 实际数据包长度   ## 全部流量统计:全部数据包大小累计
                        else:
                            D_TIME_TCP_SESSION_PACKET[时间戳] = {TCP_KEY_SD:{'Packets_Count': 1, 'Packets_Bytes':实际数据包长度}}

            f.close()
        print(f"【Done {time.strftime('%Y-%m-%d %H:%M:%S')}】")
    else:
        print("有错误终止操作")

    return(D_TIME_TCP_SESSION_PACKET)




## 返回 D_TCP_ALL      字典:Key=(源IP,源端口,目的IP,目的端口),Value=TCP信息字典
## 返回 D_UDP_ALL      字典:Key=(源IP,源端口,目的IP,目的端口),Value=()
## 返回 D_TIME_PACKET  字典:Key=时间戳(精确到秒),Value=这一秒内包数据和大小统计信息
## 返回 D_TCP_PACKET   字典:Key=TCP数据流方向,Value=这个方向上的数据包统计信息
def 提取PCAP中指定TCP会话数据(PCAP_File_OR_Dir, SaveDir, 匹配内容, UserIP, UserPort):
    #print((PCAP_File_OR_Dir, SaveDir, 匹配内容, UserIP, UserPort))
    
    ## 每个会话解析为TCP会话信息字典
    D_TCP_ALL = {}
    
    ## 存储每个UDP会话信息
    D_UDP_ALL = {}
    
    ## 统计数据包信息的字典
    D_TIME_PACKET = {}            # {时间戳:{'Packets_Count':0, 'Packets_Bytes':0, 'TCP_Count':0, 'TCP_Bytes':0, 'UDP_Count':0, 'UDP_Bytes':0, 'OTHER_Count':0, 'OTHER_Bytes':0}}
    D_TCP_PACKET = {}             # {(源IP,源端口,目的IP,目的端口):{'Packets':包数量, 'Bytes_Len':网络传输字节数量, 'Time_End':最后一个包的时间戳}}
    D_UDP_PACKET = {}

    ## 开始处理单个或多个PCAP文件
    E = 0
    if os.path.isfile(PCAP_File_OR_Dir):
        PATH,FileName = os.path.split(PCAP_File_OR_Dir) # 拆分路径和文件名
        #print(PATH,FileName)
        PCAP_File_OR_Dir = PATH                         # 这个后面但目录用
        L_FileName = ['\\'+FileName]                    # 文件名
    elif os.path.isdir(PCAP_File_OR_Dir):
        L_FileName = [i for i in os.listdir(PCAP_File_OR_Dir) if os.path.isfile(PCAP_File_OR_Dir+i)]       # 目录内内容列表(只取文件)
        if L_FileName == []:
            E = 1
            print(f"{PCAP_File_OR_Dir} 中没有发现文件")
    else:
        print(f"{PCAP_File_OR_Dir} 不存在")
        E = 1
    
    try:
        if not os.path.isdir(SaveDir):
            os.makedirs(SaveDir)
        os.chdir(SaveDir)               ## 切换到存放拆分后文件的目录
    except Exception as e:
        print(e)
        E = 1
    else:
        pass
    
    if E == 0:
        
        文件数量 = len(L_FileName)
        TCP会话数量 = 0
        UDP会话数量 = 0
        计数 = 1
        for F in L_FileName:
            FullFileName = PCAP_File_OR_Dir+str(F)
            print(f"  拆分PCAP文件:{FullFileName} 进度:{计数}/{文件数量}")       ## 根据源目的IP和端口分成多个小pcap文件
            f = open(FullFileName, 'rb')        # 以二进制方式读取pcap格式文件
            PCAP_DATA = f.read(24)              # 读取前24字节头信息,忽略
            计数 += 1
            
            N = 0
            while 1:
                
                包头 = f.read(16)
                if not 包头:				    # 判断 包头 是否为空(读完或者本身为空时 S 为空)
                    break
                
                N += 1
                #if N == 14:
                #    break
                #print("N", N)

                PacketHeader = struct.unpack('IIII', 包头)
                时间戳 = PacketHeader[0]
                #微秒 = PacketHeader[1]
                #抓取数据包长度 = PacketHeader[2]       # 所抓获的数据包保存在pcap文件中的实际长度,以字节为单位。
                实际数据包长度 = PacketHeader[3]        # 所抓获的数据包的真实长度,如果文件中保存不是完整的数据包,那么这个值可能要比前面的数据包长度的值大。
                PacketData = f.read(实际数据包长度)
                
                ## ==全部流量统计==
                if 时间戳 in D_TIME_PACKET:                                    ## 全部流量统计
                    D_TIME_PACKET[时间戳]['Packets_Count'] += 1                ## 全部流量统计:全部数据包数量累计加1
                    D_TIME_PACKET[时间戳]['Packets_Bytes'] += 实际数据包长度   ## 全部流量统计:全部数据包大小累计
                else:
                    D_TIME_PACKET[时间戳] = {'Packets_Count': 1, 'Packets_Bytes':实际数据包长度, 'TCP_Count':0, 'TCP_Bytes':0, 'UDP_Count':0, 'UDP_Bytes':0, 'OTHER_Count':0, 'OTHER_Bytes':0, '筛选出的TCP包数量合计':0, '筛选出的TCP包大小合计':0}
                ## ==全部流量统计==
                
                # 以太部首
                FrameType = PacketData[12:14]               # 帧类型
                # IP数据报头
                if FrameType == b'\x08\x00':                # 普通包
                    pass
                elif FrameType == b'\x81\x00':              # VLAN包
                    PacketData = PacketData[0:12] + b'\x08\x00' + PacketData[18:]                                       ## 剔除VLAN数据
                    包头 = struct.pack('IIII', PacketHeader[0], PacketHeader[1], PacketHeader[2]-4, PacketHeader[3]-4)  ## 修改包头
                else:
                    #print("其他包忽略")
                    pass
                
                ## SrcIP_SrcPort 和 DstIP_DstPort 互换位置是同个TCP交互,要存入同个文件对象
                IP_Protocol = PacketData[23:24]
                ## 只要TCP协议
                if IP_Protocol == b'\x06':                  # 协议(TCP 6)(UDP 17)
                
                    D_TIME_PACKET[时间戳]['TCP_Count'] += 1                ## 全部流量统计:TCP数据包数量累计加1
                    D_TIME_PACKET[时间戳]['TCP_Bytes'] += 实际数据包长度   ## 全部流量统计:TCP数据包大小累计
                    
                    SrcIP_Bytes = PacketData[26:30]         # 源IP地址
                    DstIP_Bytes = PacketData[30:34]         # 目的IP地址
                    SrcIP = socket.inet_ntoa(SrcIP_Bytes)
                    DstIP = socket.inet_ntoa(DstIP_Bytes)
                    
                    SrcPort_Bytes = PacketData[34:36]
                    DstPort_Bytes = PacketData[36:38]
                    SrcPort = struct.unpack('>H', SrcPort_Bytes)[0]
                    DstPort = struct.unpack('>H', DstPort_Bytes)[0]
                    
                    ## 每一次TCP发数据生成2个Key
                    TCP_KEY_SD = (SrcIP,SrcPort,DstIP,DstPort)  # 以当前包的源到目的生成一个Key
                    TCP_KEY_DS = (DstIP,DstPort,SrcIP,SrcPort)  # 以当前包的目的到源生成一个Key
                    
                    ## 通过IP端口等匹配出需要的交互内容
                    if 匹配内容 == 'IP':
                        if UserIP in (SrcIP, DstIP):
                            SAVE = 1
                        else:
                            SAVE = 0
                    elif 匹配内容 == 'PORT':
                        if UserPort in (SrcPort, DstPort):
                            SAVE = 1
                        else:
                            SAVE = 0
                    elif 匹配内容 == 'IP+PORT':
                        if SrcIP == UserIP and SrcPort == UserPort:
                            SAVE = 1
                        elif DstIP == UserIP and DstPort == UserPort:
                            SAVE = 1
                        else:
                            SAVE = 0
                    elif 匹配内容 == 'ALL':
                        SAVE = 1            # 全都要
                    else:
                        SAVE = 0            # 这个包不需要
                    
                    ## 匹配到是需要分析的数据包
                    if SAVE == 1:
                        ## 筛选出的TCP流量统计
                        D_TIME_PACKET[时间戳]['筛选出的TCP包数量合计'] += 1                ## 筛选出的TCP流量统计:TCP数据包数量累计加1
                        D_TIME_PACKET[时间戳]['筛选出的TCP包大小合计'] += 实际数据包长度   ## 筛选出的TCP流量统计:TCP数据包大小累计
                    
                        ## TCP会话统计流量
                        if TCP_KEY_SD in D_TCP_PACKET:                                  ## TCP会话统计流量
                            D_TCP_PACKET[TCP_KEY_SD]['Packets'] +=1                     ## TCP会话统计流量:TCP会话数据包数量累计加1
                            D_TCP_PACKET[TCP_KEY_SD]['Bytes_Len'] += 实际数据包长度     ## TCP会话统计流量:TCP会话数据包大小累计
                        else:
                            D_TCP_PACKET[TCP_KEY_SD] = {'Packets':1, 'Bytes_Len':实际数据包长度}
                    
                        ## 对TCP交互数据包进行分析并保存到字典对象中
                        ## 交互的两个方向会同时记录到字典key中,且两个方向Key对应的Value是同一个对象,只要匹配任意方向一次即可
                        if TCP_KEY_SD in D_TCP_ALL:
                            D_SAVE(D_TCP_ALL[TCP_KEY_SD], 包头, PacketData, N)
                        else:
                            Log.debug(f"    新建 D_TCP_ALL Key(SD)={TCP_KEY_SD} Key(DS)={TCP_KEY_DS} Value=D_TCP(同一个对象)")
                            TCP会话数量 += 1
                            ## 两个方向对应同一个文件
                            D_TCP = {}
                            D_TCP['CLIENT'] = ()               ## (HOST, PORT)         # 客户端IP和端口
                            D_TCP['SERVER'] = ()               ## (HOST, PORT)         # 服务端IP和端口
                            D_TCP['C_SYN'] = ()                ## (时间戳, seq, ack),  # 客户端发起连接
                            D_TCP['S_ACK_SYN'] = ()            ## (时间戳, seq, ack),  # 服务端确认连接
                            D_TCP['C_ACK'] = ()                ## (时间戳, seq, ack),  # 客户端确认连接
                            D_TCP['S_ACK_FIN_SeqKey'] = {}     ## 服务端发起的终止信息,以Seq为KEY,因为Seq=对应请求的Ack
                            D_TCP['C_ACK_FIN'] = ()            ## C已经确认S接收完全部数据的信息
                            D_TCP['S_ACK_RST'] = {}            ## 服务端强制断开TCP连接
                            D_TCP['C_ACK_RST_AckKey'] = {}     ## 客户端强制断开TCP连接
                            D_TCP['C_ACK_RST_SeqKey'] = {}     ## 客户端强制断开TCP连接
                            D_TCP['C_CLIENT_SEND_DATA'] = {}   ## 客户端发起数据(用于判断重发的情况)以ACK为Key,是在响应S某部分交互的时候重复发,要以Ack为依据
                            D_TCP['TCP_CS_ERR'] = []           ## TCP校验失败的包编号
                            D_TCP['D_C_ACK'] = {}              # C 发送的ACK信息(纯ACK确认消息,不含数据)
                            D_TCP['D_S_ACK'] = {}              # S 发送的ACK信息(纯ACK确认消息,不含数据)
                            D_TCP['D_C_ACK_DATA'] = {}         # C 发送的ACK及数据(一般都是客户端发起请求,包信息保存到 C_TCP_REQ 字典 Key=Ack Value=[(包信息1),(包信息2)] 一个请求可能会分成多个包发送)
                            D_TCP['D_S_ACK_DATA'] = {}         # S 发送的ACK及数据(一般为服务端响应数据)
                            D_TCP['C_TCP_Keep_Alive'] = {}     # C 发起的请求保持连接包
                            D_TCP['S_TCP_Keep_Alive'] = {}     # S 发起的请求保持连接包
                            D_TCP['C_TCP_Keep_Alive_ACK'] = {} # C 响应请求保持连接包
                            D_TCP['S_TCP_Keep_Alive_ACK'] = {} # S 响应请求保持连接包
                            D_TCP['C_ACK_WIN_0'] = []          ## 记录C发的ACK且窗口为0的包信息(Seq,Ack)说明C缓存满,S会发ACK数据为1的包探测查看状态,S的(Seq,Ack)和C的刚好相反
                            D_TCP['S_ACK_WIN_0'] = []          ## 记录S发的ACK且窗口为0的包信息(Seq,Ack)说明S缓存满
                            D_TCP['D_C_WIN_0'] = {}            # 记录C接收窗口满的信息,用于计算因C窗口满消耗的时间
                            D_TCP['D_S_WIN_0'] = {}            # 记录S接收窗口满的信息,用于计算因S窗口满消耗的时间
                            D_TCP['C_Tx_DATA_ALL'] = 0         ## 在最后的时候更新到主字典里
                            D_TCP['S_Tx_DATA_ALL'] = 0         ## 在最后的时候更新到主字典里
                            D_TCP['P_C_Tx_Seq_Ack'] = set()    # C发的含数据的ACK包的(Seq,Ack)信息集合,遇到重复说明是重发的包,在S处抓包这个就是 L_C_Rx_Info 接收到C发数据,重复说明重收,接不到对方丢的包的
                            D_TCP['P_S_Tx_Seq_Ack'] = set()    # S发的含数据的ACK包的(Seq,Ack)信息集合,遇到重复说明是重发的包,在C处抓包这个就是 L_S_Rx_Info 接收到S发数据,重复说明重收,接不到对方丢的包的
                            D_TCP['L_DATA'] = []
                            D_TCP_ALL[TCP_KEY_SD] = D_TCP      ### Key(方向1)对应新建的字典对象D_TCP
                            D_TCP_ALL[TCP_KEY_DS] = D_TCP      ### Key(方向2)对应新建的字典对象D_TCP
                            D_SAVE(D_TCP_ALL[TCP_KEY_SD], 包头, PacketData, N)
                elif IP_Protocol == b'\x11':                  ## 协议(TCP 6)(UDP 17)
                    D_TIME_PACKET[时间戳]['UDP_Count'] += 1                ## 全部流量统计:UDP数据包数量累计加1
                    D_TIME_PACKET[时间戳]['UDP_Bytes'] += 实际数据包长度   ## 全部流量统计:UDP数据包大小累计
                    
                    SrcIP_Bytes = PacketData[26:30]         # 源IP地址
                    DstIP_Bytes = PacketData[30:34]         # 目的IP地址
                    SrcIP = socket.inet_ntoa(SrcIP_Bytes)
                    DstIP = socket.inet_ntoa(DstIP_Bytes)
                    
                    SrcPort_Bytes = PacketData[34:36]
                    DstPort_Bytes = PacketData[36:38]
                    SrcPort = struct.unpack('>H', SrcPort_Bytes)[0]
                    DstPort = struct.unpack('>H', DstPort_Bytes)[0]
                    
                    ## 每一次UDP发数据生成2个Key
                    UDP_KEY_SD = (SrcIP,SrcPort,DstIP,DstPort)  # 以当前包的源到目的生成一个Key
                    UDP_KEY_DS = (DstIP,DstPort,SrcIP,SrcPort)  # 以当前包的目的到源生成一个Key
                    
                    ## 只有分离出指定的IP的交互内容
                    if 匹配内容 == 'IP':
                        if UserIP in (SrcIP, DstIP):
                            SAVE = 1
                        else:
                            SAVE = 0
                    elif 匹配内容 == 'PORT':
                        if UserPort in (SrcPort, DstPort):
                            SAVE = 1
                        else:
                            SAVE = 0
                    elif 匹配内容 == 'IP+PORT':
                        if SrcIP == UserIP and SrcPort == UserPort:
                            SAVE = 1
                        elif DstIP == UserIP and DstPort == UserPort:
                            SAVE = 1
                        else:
                            SAVE = 0
                    elif 匹配内容 == 'ALL':
                        SAVE = 1            # 全都要
                    else:
                        SAVE = 0            # 这个包不需要
                    
                    ## 匹配到是需要分析的数据包
                    if SAVE == 1:
                        ## UDP会话统计流量
                        if UDP_KEY_SD in D_UDP_PACKET:                                  ## TCP会话统计流量
                            D_UDP_PACKET[UDP_KEY_SD]['Packets'] +=1                     ## TCP会话统计流量:TCP会话数据包数量累计加1
                            D_UDP_PACKET[UDP_KEY_SD]['Bytes_Len'] += 实际数据包长度     ## TCP会话统计流量:TCP会话数据包大小累计
                        else:
                            D_UDP_PACKET[UDP_KEY_SD] = {'Packets':1, 'Bytes_Len':实际数据包长度}
                        
                        ## 对UDP交互数据包进行分析保存
                        if UDP_KEY_SD in D_UDP_ALL:
                            pass
                        else:
                            Log.debug(f"    UDP {UDP_KEY_SD}")
                            UDP会话数量 += 1
                            ## 两个方向对应同一个文件
                            D_UDP_ALL[UDP_KEY_SD] = ()    # 方向1
                            D_UDP_ALL[UDP_KEY_DS] = ()    # 方向2
                else:
                    D_TIME_PACKET[时间戳]['OTHER_Count'] += 1              ## 全部流量统计:其它数据包数量累计加1
                    D_TIME_PACKET[时间戳]['OTHER_Bytes'] += 实际数据包长度 ## 全部流量统计:其它数据包大小累计
            f.close()
        print(f"  完成 {time.strftime('%Y-%m-%d %H:%M:%S')}")
        #print(f"  TCP会话数量: {TCP会话数量}")
        #print(f"  UDP会话数量: {UDP会话数量}")
    else:
        print("【ERROR】有错误终止操作")

    return(D_TCP_ALL, D_TIME_PACKET, D_TCP_PACKET, D_UDP_ALL, D_UDP_PACKET)




## 去除重复内容的Key 返回 L_TCP_SESSION_KEY
def TCP会话Key去重(D_TCP_ALL):
    L_TCP_SESSION_KEY = []
    P = set()
    for k in D_TCP_ALL:
        if k in P:
            pass
        else:
            L_TCP_SESSION_KEY.append(k)
            P.add(k)
            P.add((k[2],k[3],k[0],k[1]))
            #L_DATA = D_TCP_ALL[k]['L_DATA']
    return(L_TCP_SESSION_KEY)

## 去除重复内容的Key 返回 L_UDP_SESSION_KEY
def UDP会话Key去重(D_UDP_ALL):
    L_UDP_SESSION_KEY = []
    P = set()
    for k in D_UDP_ALL:
        if k in P:
            pass
        else:
            L_UDP_SESSION_KEY.append(k)
            P.add(k)
            P.add((k[2],k[3],k[0],k[1]))
    return(L_UDP_SESSION_KEY)


## 按时间戳存储数据包大小的字典转成画图可用的XY轴列表
def D_TIME_PACKET_2_XY(D_TIME_PACKET):
    L_TIME_KEY = [i for i in D_TIME_PACKET]      # 时间戳Key列表
    Y_Packets_Count = []
    Y_Packets_Bytes = []
    Y_TCP_Count = []
    Y_TCP_Bytes = []
    Y_UDP_Count = []
    Y_UDP_Bytes = []
    Y_TCP刷选出部分_Count = []
    Y_TCP刷选出部分_Bytes = []
    for i in L_TIME_KEY:
        if i in D_TIME_PACKET:
            Y_Packets_Count.append(D_TIME_PACKET[i]['Packets_Count'])
            Y_Packets_Bytes.append(D_TIME_PACKET[i]['Packets_Bytes'])
            Y_TCP_Count.append(D_TIME_PACKET[i]['TCP_Count'])
            Y_TCP_Bytes.append(D_TIME_PACKET[i]['TCP_Bytes'])
            Y_UDP_Count.append(D_TIME_PACKET[i]['UDP_Count'])
            Y_UDP_Bytes.append(D_TIME_PACKET[i]['UDP_Bytes'])
            Y_TCP刷选出部分_Count.append(D_TIME_PACKET[i]['筛选出的TCP包数量合计'])
            Y_TCP刷选出部分_Bytes.append(D_TIME_PACKET[i]['筛选出的TCP包大小合计'])
        else:
            Y_Packets_Count.append(0)
            Y_Packets_Bytes.append(0)
            Y_TCP_Count.append(0)
            Y_TCP_Bytes.append(0)
            Y_UDP_Count.append(0)
            Y_UDP_Bytes.append(0)
            Y_TCP刷选出部分_Count.append(0)
            Y_TCP刷选出部分_Bytes.append(0)
    return(L_TIME_KEY, Y_Packets_Count, Y_Packets_Bytes, Y_TCP_Count, Y_TCP_Bytes, Y_UDP_Count, Y_UDP_Bytes, Y_TCP刷选出部分_Count, Y_TCP刷选出部分_Bytes)


## 流量画图
def 画流量图(D_TIME_PACKET):
    X, Y_Packets_Count, Y_Packets_Bytes, Y_TCP_Count, Y_TCP_Bytes, Y_UDP_Count, Y_UDP_Bytes, Y_TCP刷选出部分_Count, Y_TCP刷选出部分_Bytes = D_TIME_PACKET_2_XY(D_TIME_PACKET)
    X_TEXT = [time.strftime('%H:%M:%S', time.localtime(i)) for i in X]  # 文本时间
    #print("X", X)
    #print("X_TEXT", X_TEXT)
    ## 分成多个图显示(通用、可控)
    X轴刻度数量 = 10               # X轴只显示10个刻度
    X轴间隔 = len(X)//X轴刻度数量
    if X轴间隔 == 0:               # 按秒合并的时间刻度,可能会少于10个
        X轴间隔 = 1                # 显示每一个刻度
    Log.debug(f"原X轴刻度数={len(X)} 设置X轴间隔={X轴间隔}")
    图数量 = 3
    import matplotlib.pyplot as plt
    import matplotlib.ticker as ticker                                                  # 解决时间为X轴可能会造成刻度太密集,看不清问题

    fig, ax = plt.subplots(图数量, 1, sharex=True, sharey=False, figsize=(20,10))       # n行1列,统一X轴刻度
    AX_List = ax.ravel()                                                                # 子图列表:AX_List[0]第一个子图,AX_List[1]第二个子图...
    
    ## 子图1每秒总流量,用左边y轴
    AX_List[0].plot(X_TEXT, Y_Packets_Count, '-', label='ALL Packets Count', markersize=2)  # 第1个图的第1根线
    AX_List[0].legend(loc=2)                                                            # 显示图例label(位置左上)
    #AX_List[0].xaxis.set_major_locator(ticker.MultipleLocator(X轴间隔))                # X轴间隔N标一个刻度
    AX_List[0].set_ylabel('Packets')
    
    ## 子图1每秒包数量,用右边y轴
    AX_0_Y2 = AX_List[0].twinx()
    AX_0_Y2.plot(X_TEXT, Y_Packets_Bytes, ':r', label='ALL Packets Bytes', markersize=1)    # 第1个图的第2根线
    AX_0_Y2.legend(loc=1)                                                               # 显示图例label(位置右上)
    #AX_0_Y2.xaxis.set_major_locator(ticker.MultipleLocator(X轴间隔))
    AX_0_Y2.set_ylabel('Bytes')
    AX_0_Y2.get_yaxis().get_major_formatter().set_scientific(False)                     # y轴值不使用科学记数法
    
    ## 子图2 总TCP和UDP的包数量和流量情况
    AX_List[1].plot(X_TEXT, Y_TCP_Count, ':', label='All TCP Count', markersize=2)
    AX_List[1].plot(X_TEXT, Y_UDP_Count, '-', label='All UDP Count', markersize=1)
    AX_List[1].legend(loc=2)
    #AX_List[1].xaxis.set_major_locator(ticker.MultipleLocator(X轴间隔))
    AX_List[1].set_ylabel('Packets')
    
    AX_1_Y2 = AX_List[1].twinx()
    AX_1_Y2.plot(X_TEXT, Y_TCP_Bytes, ':', label='ALL TCP Bytes', markersize=2)
    AX_1_Y2.plot(X_TEXT, Y_UDP_Bytes, '-', label='ALL UDP Bytes', markersize=1)
    AX_1_Y2.legend(loc=1)
    AX_1_Y2.set_ylabel('Bytes')
    AX_1_Y2.xaxis.set_major_locator(ticker.MultipleLocator(X轴间隔))
    AX_1_Y2.get_yaxis().get_major_formatter().set_scientific(False)
    
    ## 子图3 筛选出的TCP会话的流量图
    ## 子图3每秒总流量,用左边y轴
    AX_List[2].plot(X_TEXT, Y_TCP刷选出部分_Count, '-', label='SELECT TCP Packets Count', markersize=2)
    AX_List[2].legend(loc=2)
    AX_List[2].xaxis.set_major_locator(ticker.MultipleLocator(X轴间隔))
    AX_List[2].set_ylabel('Packets')
    
    ## 子图3每秒包数量,用右边y轴
    AX_2_Y2 = AX_List[2].twinx()
    AX_2_Y2.plot(X_TEXT, Y_TCP刷选出部分_Bytes, ':r', label='SELECT TCP Packets Bytes', markersize=1)
    AX_2_Y2.legend(loc=1)
    AX_2_Y2.xaxis.set_major_locator(ticker.MultipleLocator(X轴间隔))
    AX_2_Y2.set_ylabel('Bytes')
    AX_2_Y2.get_yaxis().get_major_formatter().set_scientific(False)
    
    plt.xlabel('Time')
    plt.show()




def 合并会话(D_TCP_PACKET):
    D_TCP_SESSION = {}
    for Key in D_TCP_PACKET:
        if Key in D_TCP_SESSION:
            print("重复")
        else:
            SrcIP, SrcPort, DstIP, DstPort = Key
            Key_New = (DstIP, DstPort, SrcIP, SrcPort)
            if Key_New in D_TCP_SESSION:
                #print("合并")
                D_TCP_SESSION[Key_New]['Packets'] += D_TCP_PACKET[Key]['Packets']      # 发包数量累加
                D_TCP_SESSION[Key_New]['Bytes_Len'] += D_TCP_PACKET[Key]['Bytes_Len']  # 内容长度累加
            else:
                D_TCP_SESSION[Key] = D_TCP_PACKET[Key]                                 # 复制
    
    return(D_TCP_SESSION)



## 从PCAP文件中提取出指定会话的内容另存为PCAP文件
## 参数 PCAP_File_OR_Dir  PCAP文件名或PCAP文件目录(多个PCAP文件需要按时间顺序存放)
## 参数 SaveDir           提取出的PCAP文件存放的目录
## 参数 D_SESSION_INFO    需要提取的会话信息(IP和端口正反方向都要写入) {'TCP':[(源IP,源端口,目的IP,目的端口),(目的IP,目的端口,源IP,源端口),], 'UDP':[(源IP,源端口,目的IP,目的端口),(目的IP,目的端口,源IP,源端口),]}
def 提取PCAP中指定会话数据另存为独立PCAP(PCAP_File_OR_Dir, SaveDir, D_SESSION_INFO):
    print(f"【I】源PCAP文件或目录 {PCAP_File_OR_Dir}")
    print(f"【I】保存目录 {SaveDir}")
    E = 0
    if os.path.isfile(PCAP_File_OR_Dir):
        PATH,FileName = os.path.split(PCAP_File_OR_Dir) # 拆分路径和文件名
        #print(PATH,FileName)
        PCAP_File_OR_Dir = PATH                         # 这个后面但目录用
        L_FileName = ['\\'+FileName]                    # 文件名
    elif os.path.isdir(PCAP_File_OR_Dir):
        L_FileName = [i for i in os.listdir(PCAP_File_OR_Dir) if os.path.isfile(PCAP_File_OR_Dir+i)]       # 目录内内容列表(只取文件)
    else:
        print(f"【E】{PCAP_File_OR_Dir} 不存在")
        E = 1
    
    try:
        if not os.path.isdir(SaveDir):
            os.makedirs(SaveDir)
        os.chdir(SaveDir)               ## 切换到存放拆分后文件的目录
    except Exception as e:
        print(e)
        E = 1
    else:
        pass
    
    if E == 0:
        
        ## 本地文件名保存到字典中
        D_Local_File_Name = {}
        
        文件数量 = len(L_FileName)
        新建PCAP文件数量 = 0
        计数 = 1
        for F in L_FileName:
            FullFileName = PCAP_File_OR_Dir+str(F)
            print(f"【I】拆分PCAP文件:{FullFileName} 进度:{计数}/{文件数量}")       ## 根据源目的IP和端口分成多个小pcap文件
            f = open(FullFileName, 'rb')        # 以二进制方式读取pcap格式文件
            PCAP_DATA = f.read(24)          # 读取前24字节头信息,忽略
            计数 += 1
            
            while 1:
                包头 = f.read(16)
                if not 包头:				    # 判断 包头 是否为空(读完或者本身为空时 S 为空)
                    break
                
                PacketHeader = struct.unpack('IIII', 包头)
                PacketData = f.read(PacketHeader[3])
                
                # 以太部首
                FrameType = PacketData[12:14]               # 帧类型
                # IP数据报头
                if FrameType == b'\x08\x00':                # 普通包
                    pass
                elif FrameType == b'\x81\x00':              # VLAN包
                    PacketData = PacketData[0:12] + b'\x08\x00' + PacketData[18:]                                       ## 剔除VLAN数据
                    包头 = struct.pack('IIII', PacketHeader[0], PacketHeader[1], PacketHeader[2]-4, PacketHeader[3]-4)  ## 修改包头
                else:
                    #print("其他包忽略")
                    pass
                
                ## SrcIP_SrcPort 和 DstIP_DstPort 互换位置是同个TCP交互,要存入同个文件对象
                IP_Protocol = PacketData[23:24]
                ## 只要TCP协议
                if IP_Protocol == b'\x06':                  # 协议(TCP 6)(UDP 17)
                    SrcIP_Bytes = PacketData[26:30]         # 源IP地址
                    DstIP_Bytes = PacketData[30:34]         # 目的IP地址
                    SrcIP = socket.inet_ntoa(SrcIP_Bytes)
                    DstIP = socket.inet_ntoa(DstIP_Bytes)
                    
                    SrcPort_Bytes = PacketData[34:36]
                    DstPort_Bytes = PacketData[36:38]
                    SrcPort = struct.unpack('>H', SrcPort_Bytes)[0]
                    DstPort = struct.unpack('>H', DstPort_Bytes)[0]
                    
                    ## 每一次连接生成2个文件名
                    SRC = SrcIP+'_'+ str(SrcPort)
                    DST = DstIP+'_'+ str(DstPort)
                    TCP_F_NAME_SD = 'TCP_'+SRC+'-'+DST+'.pcap'     # 以当前包的源到目的生成一个文件
                    TCP_F_NAME_DS = 'TCP_'+DST+'-'+SRC+'.pcap'     # 以当前包的目的到源生成一个文件
                    #print("TCP_F_NAME_SD", TCP_F_NAME_SD)
                    #print("TCP_F_NAME_DS", TCP_F_NAME_DS)
                    
                    ## 另存为小文件
                    if (SrcIP, SrcPort, DstIP, DstPort) in D_SESSION_INFO['TCP']:
                        if TCP_F_NAME_SD in D_Local_File_Name:
                            #print("  归属", TCP_F_NAME_SD)
                            fp = open(D_Local_File_Name[TCP_F_NAME_SD], 'ba')
                            fp.write(包头)
                            fp.write(PacketData)
                            fp.close()
                        else:
                            print("【I】    新建", TCP_F_NAME_SD)
                            fp = open(TCP_F_NAME_SD, 'ba')              # 新建一个文件对象,名字用客户端到服务端方向
                            fp.write(PCAP_DATA)
                            fp.write(包头)
                            fp.write(PacketData)
                            fp.close()
                            新建PCAP文件数量 += 1
                            ## 两个方向对应同一个文件
                            D_Local_File_Name[TCP_F_NAME_SD] = TCP_F_NAME_SD    # 方向1
                            D_Local_File_Name[TCP_F_NAME_DS] = TCP_F_NAME_SD    # 方向2
                elif IP_Protocol == b'\x11':                # 协议(TCP 6)(UDP 17)
                    SrcIP_Bytes = PacketData[26:30]         # 源IP地址
                    DstIP_Bytes = PacketData[30:34]         # 目的IP地址
                    SrcIP = socket.inet_ntoa(SrcIP_Bytes)
                    DstIP = socket.inet_ntoa(DstIP_Bytes)
                    
                    SrcPort_Bytes = PacketData[34:36]
                    DstPort_Bytes = PacketData[36:38]
                    SrcPort = struct.unpack('>H', SrcPort_Bytes)[0]
                    DstPort = struct.unpack('>H', DstPort_Bytes)[0]
                    
                    ## 每一次连接生成2个文件名
                    SRC = SrcIP+'_'+ str(SrcPort)
                    DST = DstIP+'_'+ str(DstPort)
                    UDP_F_NAME_SD = 'UDP_'+SRC+'-'+DST+'.pcap'     # 以当前包的源到目的生成一个文件
                    UDP_F_NAME_DS = 'UDP_'+DST+'-'+SRC+'.pcap'     # 以当前包的目的到源生成一个文件
                    
                    ## 另存为小文件
                    if (SrcIP, SrcPort, DstIP, DstPort) in D_SESSION_INFO['UDP']:
                        if UDP_F_NAME_SD in D_Local_File_Name:
                            #print("  归属", UDP_F_NAME_SD)
                            fp = open(D_Local_File_Name[UDP_F_NAME_SD], 'ba')
                            fp.write(包头)
                            fp.write(PacketData)
                            fp.close()
                        else:
                            print("【I】    新建", UDP_F_NAME_SD)
                            fp = open(UDP_F_NAME_SD, 'ba')              # 新建一个文件对象,名字用客户端到服务端方向
                            fp.write(PCAP_DATA)
                            fp.write(包头)
                            fp.write(PacketData)
                            fp.close()
                            新建PCAP文件数量 += 1
                            ## 两个方向对应同一个文件
                            D_Local_File_Name[UDP_F_NAME_SD] = UDP_F_NAME_SD    # 方向1
                            D_Local_File_Name[UDP_F_NAME_DS] = UDP_F_NAME_SD    # 方向2
                else:
                    #print("IP_Protocol", IP_Protocol)
                    pass
            f.close()
        print(f"【I】完成 {time.strftime('%Y-%m-%d %H:%M:%S')}")
        print(f"【I】新建PCAP文件数量: {新建PCAP文件数量}")
    else:
        print("【E】有错误,终止 提取PCAP中指定会话数据() 操作")









## 返回传入元组对象的第二个值,排序要用
def T2(X):
    return(X[1])



def 过滤出的包数据画流量图(D_TCP_PACKET, D_TIME_PACKET):
    ## 单向TCP会话统计 D_TCP_PACKET
    Log.info("单向分别统计")
    L_TCP_PACKET = [(Key,D_TCP_PACKET[Key]['Bytes_Len']) for Key in D_TCP_PACKET]
    L_TCP_PACKET.sort(key=T2)
    #for Key in D_TCP_PACKET:
    #    Log.info(f"  {Key} {D_TCP_PACKET[Key]}")
    for i in L_TCP_PACKET:
        Log.info(f"  {i[0]} {D_TCP_PACKET[i[0]]}")
    
    ## 双向TCP会话统计 D_TCP_SESSION
    D_TCP_SESSION = 合并会话(D_TCP_PACKET)
    Log.info("双向合并统计")
    #for Key in D_TCP_SESSION:
    #    Log.info(f"  {Key} {D_TCP_SESSION[Key]}")
    L_TCP_SESSION = [(Key,D_TCP_SESSION[Key]['Bytes_Len']) for Key in D_TCP_SESSION]
    L_TCP_SESSION.sort(key=T2)
    for i in L_TCP_SESSION:
        Log.info(f"  {i[0]} {D_TCP_SESSION[i[0]]}")
    
    画流量图(D_TIME_PACKET)



def TCP会话交互过程用时分析(L_会话编号, L_TCP_SESSION_KEY, D_TCP_ALL, 图, 选择日志等级):
    设置CMD颜色(天蓝色)
    Log.info("-----------------------------------------------------------------------------------------------------------------------------------------------------------------")
    TCP_START_TIME = []
    TCP_END_TIME = []
    D_S_KeepAlive = {}              # S发起KeepAlive信息字典,Key=交互信息,Value=[(KeepAlive每次开始, 结束时间), ]
    D_C_KeepAlive = {}              # {'Key标识数据归属':[('10:18:29', '10:20:24'), [('10:19:50', '10:24:40')]],}
    D_C_WAIT = {}
    D_C_WIN_0_TIME = {}
    D_S_WIN_0_TIME = {}
    
    头行 = ('客户端', '端口', '服务端', '端口', '开始时间', '结束时间', '会话总用时', 'C_Keep时间', 'S_Keep时间', 'C间隔发包合时', 'C缓冲窗满合时', 'S缓冲窗满合时', '程序其它用时合计')
    Log.info("%s          %s       %s          %s     %s %s %s %s %s %s %s %s %s" % 头行)
    for 会话编号 in L_会话编号:
        选择会话KEY = L_TCP_SESSION_KEY[会话编号]
        L_DATA = D_TCP_ALL[选择会话KEY]['L_DATA']
        C_发起开始时间 = L_DATA[0][1]             # TCP会话第一个包的时间戳
        C_发起结束时间 = L_DATA[-1][1]            # TCP会话最后一个包的时间戳
        TCP_START_TIME.append(C_发起开始时间)
        TCP_END_TIME.append(C_发起结束时间)
        L_TIME_Client_WAIT = 客户端发起请求间隔耗时(D_TCP_ALL[选择会话KEY])                    # 客户端发起新请求和上次完成交互之间的间隔时间(下次交互开始-上次交互完成)
        L_C_KeepAlive_RANGE, L_S_KeepAlive_RANGE = 分析KeepAlive耗时(D_TCP_ALL[选择会话KEY])   # 因保持连接造成的等待耗时
        L_C_WIN_0_RANGE, L_S_WIN_0_RANGE = 分析接收窗口满耗时(D_TCP_ALL[选择会话KEY])          # 因接收窗口满造成的时间消耗
        #print("L_C_WIN_0_RANGE", L_C_WIN_0_RANGE)
        #print("L_S_WIN_0_RANGE", L_S_WIN_0_RANGE)
        
        源地址, 源端口 = D_TCP_ALL[选择会话KEY]['CLIENT']
        目的地址, 目的端口 = D_TCP_ALL[选择会话KEY]['SERVER']
    
        D_C_KeepAlive[f"{源地址}:{源端口}<=>{目的地址}:{目的端口}"] = L_C_KeepAlive_RANGE
        D_S_KeepAlive[f"{源地址}:{源端口}<=>{目的地址}:{目的端口}"] = L_S_KeepAlive_RANGE
        D_C_WAIT[f"{源地址}:{源端口}<=>{目的地址}:{目的端口}"] = L_TIME_Client_WAIT
        D_C_WIN_0_TIME[f"{源地址}:{源端口}<=>{目的地址}:{目的端口}"] = L_C_WIN_0_RANGE
        D_S_WIN_0_TIME[f"{源地址}:{源端口}<=>{目的地址}:{目的端口}"] = L_S_WIN_0_RANGE
        
        T_DATA = SHOW_TIME_WAIT(C_发起开始时间, C_发起结束时间, L_TIME_Client_WAIT, L_C_KeepAlive_RANGE, L_S_KeepAlive_RANGE, L_C_WIN_0_RANGE, L_S_WIN_0_RANGE, D_TCP_ALL[选择会话KEY])
        ## 每次交互的详细信息
        L_TCP_TIME = TCP_SESSION_TIME(D_TCP_ALL[选择会话KEY])     # 分析TCP每次交互用时信息
        SHOW_TCP_ALTERNATELY_TIME(L_TCP_TIME, 视角)               # 记录每次客户端发请求到收完响应数据的统计信息
        TCP交互用时合计 = sum([i[4] for i in L_TCP_TIME])
        客户端发请求用时合计 = sum([i[5] for i in L_TCP_TIME])
        服务端处理用时合计 = sum([i[6] for i in L_TCP_TIME])
        服务端发包用时合计 = sum([i[7] for i in L_TCP_TIME])
        Log.info(f"  程序其它用时合计包含: TCP交互用时合计={round(TCP交互用时合计,3)}(秒) (客户端发请求用时合计={round(客户端发请求用时合计,3)}, 服务端处理用时合计={round(服务端处理用时合计,3)}, 服务端发包用时合计={round(服务端发包用时合计,3)})")
        
        ## == debug == 标记用时分析的起止点
        if 选择日志等级 == '0':
            L_C_KeepAlive_RANGE_start = [i[0] for i in L_C_KeepAlive_RANGE]
            L_C_KeepAlive_RANGE_end = [i[1] for i in L_C_KeepAlive_RANGE]
            L_S_KeepAlive_RANGE_start = [i[0] for i in L_S_KeepAlive_RANGE]
            L_S_KeepAlive_RANGE_end = [i[1] for i in L_S_KeepAlive_RANGE]
            L_TIME_Client_WAIT_start = [i[0] for i in L_TIME_Client_WAIT]
            L_TIME_Client_WAIT_end = [i[1] for i in L_TIME_Client_WAIT]
            L_C_WIN_0_RANGE_start = [i[0] for i in L_C_WIN_0_RANGE]
            L_C_WIN_0_RANGE_end = [i[1] for i in L_C_WIN_0_RANGE]
            L_S_WIN_0_RANGE_start = [i[0] for i in L_S_WIN_0_RANGE]
            L_S_WIN_0_RANGE_end = [i[1] for i in L_S_WIN_0_RANGE]
            Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-5s %5s %s %s' % ('ID','TIME','SrcIP','DstIP','SPort','DPort','Seq','Ack','FLAGE','LEN','请求/响应','WIN','校验码','备注说明'))
            for i in L_DATA:
                if i[1] in L_C_KeepAlive_RANGE_start:
                    X = i + ('[T]L_C_KeepAlive_RANGE_start',)
                    Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s %s' % X)
                elif i[1] in L_C_KeepAlive_RANGE_end:
                    X = i + ('[T]L_C_KeepAlive_RANGE_end',)
                    Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s %s' % X)
                elif i[1] in L_S_KeepAlive_RANGE_start:
                    X = i + ('[T]L_S_KeepAlive_RANGE_start',)
                    Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s %s' % X)
                elif i[1] in L_S_KeepAlive_RANGE_end:
                    X = i + ('[T]L_S_KeepAlive_RANGE_end',)
                    Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s %s' % X)
                elif i[1] in L_TIME_Client_WAIT_start:
                    X = i + ('[T]L_TIME_Client_WAIT_start',)
                    Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s %s' % X)
                elif i[1] in L_TIME_Client_WAIT_end:
                    X = i + ('[T]L_TIME_Client_WAIT_end',)
                    Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s %s' % X)
                elif i[1] in L_C_WIN_0_RANGE_start:
                    X = i + ('[T]L_C_WIN_0_RANGE_start',)
                    Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s %s' % X)
                elif i[1] in L_C_WIN_0_RANGE_end:
                    X = i + ('[T]L_C_WIN_0_RANGE_end',)
                    Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s %s' % X)
                elif i[1] in L_S_WIN_0_RANGE_start:
                    X = i + ('[T]L_S_WIN_0_RANGE_start',)
                    Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s %s' % X)
                elif i[1] in L_S_WIN_0_RANGE_end:
                    X = i + ('[T]L_S_WIN_0_RANGE_end',)
                    Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s %s' % X)
                else:
                    Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s' % i)
            Log.debug('')
        ## == debug ==
    设置CMD颜色(白色)
    print(f"\n结果已写入日志文件:{LOG_FILE}\n\n")
    ## 画图:TCP会话交互中各种用时信息画成图
    ## 计算最小开始时间和最大结束时间,统一X时间轴用
    if 图 == 1:
        TCP_START_TIME_MIN = min(TCP_START_TIME)
        TCP_END_TIME_MAX = max(TCP_END_TIME)
        TCP会话用时信息画图(TCP_START_TIME_MIN, TCP_END_TIME_MAX, D_C_KeepAlive, D_C_WAIT, D_S_KeepAlive, D_C_WIN_0_TIME, D_S_WIN_0_TIME)





## 组装TCP数据

## 读取数据包的 Seq, TCP数据长度, TCP实际有效数据
## Seq
## TCP_DATA_LEN
## 剩余有效数据
def 读取包信息(PacketData):
    Seq = 0
    TCP_DATA_LEN = 0
    剩余有效数据 = b''
    
    DstMAC, SrcMAC, FrameType = EthernetII_Header(PacketData[0:14])     # 以太帧头
    ###########【IPv4】###########
    if FrameType == b'\x08\x00':
        ## 解析IP头(20字节[14:34])
        (IP_Version, IP_Header_Length, IP_TOS, IP_Total_Length, IP_Identification, IP_Flags, IP_Fragment_offset, IP_TTL, IP_Protocol, IP_Header_checksum, IP_Source, IP_Destination) = IPv4_Header(PacketData[14:34])
        IP部首字节长度 = IP_Header_Length*4
        ###########【TCP】###########
        if IP_Protocol == 6:               # TCP b'\x06'
            TCP_Source_Port, TCP_Destination_Port, TCP_Sequence_Number, TCP_Acknowledgment_Number, TCP_Data_Offset, TCP_Reserved, TCP_Flags, TCP_Window, TCP_Checksum, TCP_Urgent_Pointers = TCP_Header(PacketData[34:54])
            ## 根据TCP部首长度计算是否有TCP可选字段
            TCP部首实际长度 = TCP_Data_Offset*4
            TCP部首固定长度 = 20
            TCP选项长度 = TCP部首实际长度 - TCP部首固定长度
            TCP_DATA_LEN = IP_Total_Length - IP部首字节长度 - TCP部首固定长度 - TCP选项长度

            ## 计算数据长度
            ## 最小60字节,不足会填充
            #剩余全部数据 = PacketData[54+TCP选项长度:]
            剩余有效数据 = PacketData[54+TCP选项长度:54+TCP选项长度+TCP_DATA_LEN]
            #print("剩余全部数据字节数(TCP选项后全部)", len(剩余全部数据))           ## 当不足最小长度会填充,这个会比下面的大
            #print("剩余有效数据字节数(计算得到)", len(剩余有效数据))
            
            ## 修正解析IP包总长度结果为0的情况 wireshark 提示 (reported as 0, presumed to be because of "TCP segmentation offload" (TSO))
            if IP_Total_Length == 0:
                剩余有效数据 = PacketData[54+TCP选项长度:]    # 修正内容数据值为剩余全部数据值
                TCP_DATA_LEN = len(剩余有效数据)              # 修正内容数据的长度值
            
            ## 分析数据包中的TCP内容
            #SrcIP_Bytes = PacketData[26:30]         # 源IP地址
            #DstIP_Bytes = PacketData[30:34]         # 目的IP地址
            #SrcIP = socket.inet_ntoa(SrcIP_Bytes)
            #DstIP = socket.inet_ntoa(DstIP_Bytes)
            #SrcPort = TCP_Source_Port
            #DstPort = TCP_Destination_Port
            Seq = TCP_Sequence_Number
            Ack = TCP_Acknowledgment_Number
            
        elif IP_Protocol == 17:
            Log.error("【ERROR】UDP PASS")
        else:
            Log.error("【ERROR】NOT TCP/UDP PASS")
    else:
        Log.error("【ERROR】NOT IPv4 Ethernet")
    
    #print(f"    【DEBUG】Seq={Seq:10}   TCP_DATA_LEN={TCP_DATA_LEN:<10} TCP_DATA={剩余有效数据}")
    return(Seq, TCP_DATA_LEN, 剩余有效数据)
   
##【4】
## 传入 L_需要用到的Seq      # 列表,所需的数据包Seq列表
## 返回 D_Seq_Len_DATA = {}  # 字典,Key=(Seq,Len) Value=b''
def 从PCAP文件中提取TCP有效数据_生成以SeqLen为KEY的字典(PCAP_File_OR_Dir, P_Seq):
    D_Seq_Len_DATA = {}
    L_DATA_Other = []
    ## 开始处理单个或多个PCAP文件
    E = 0
    if os.path.isfile(PCAP_File_OR_Dir):
        PATH,FileName = os.path.split(PCAP_File_OR_Dir) # 拆分路径和文件名
        #print(PATH,FileName)
        PCAP_File_OR_Dir = PATH                         # 这个后面但目录用
        L_FileName = ['\\'+FileName]                    # 文件名
    elif os.path.isdir(PCAP_File_OR_Dir):
        L_FileName = [i for i in os.listdir(PCAP_File_OR_Dir) if os.path.isfile(PCAP_File_OR_Dir+i)]       # 目录内内容列表(只取文件)
        if L_FileName == []:
            E = 1
            print(f"{PCAP_File_OR_Dir} 中没有发现文件")
    else:
        print(f"{PCAP_File_OR_Dir} 不存在")
        E = 1
    
    if E == 0:
        
        文件数量 = len(L_FileName)
        计数 = 1
        for F in L_FileName:
            FullFileName = PCAP_File_OR_Dir+str(F)
            print(f"  解析PCAP文件:{FullFileName} 进度:{计数}/{文件数量}")       ## 根据源目的IP和端口分成多个小pcap文件
            f = open(FullFileName, 'rb')        # 以二进制方式读取pcap格式文件
            PCAP_DATA = f.read(24)              # 读取前24字节头信息,忽略
            计数 += 1
            
            N = 0
            while 1:
                
                包头 = f.read(16)
                if not 包头:				    # 判断 包头 是否为空(读完或者本身为空时 S 为空)
                    break
                
                N += 1
                #if N == 14:
                #    break
                #print("N", N)
                
                PacketHeader = struct.unpack('IIII', 包头)
                时间戳 = PacketHeader[0]
                #微秒 = PacketHeader[1]
                #抓取数据包长度 = PacketHeader[2]       # 所抓获的数据包保存在pcap文件中的实际长度,以字节为单位。
                实际数据包长度 = PacketHeader[3]        # 所抓获的数据包的真实长度,如果文件中保存不是完整的数据包,那么这个值可能要比前面的数据包长度的值大。
                PacketData = f.read(实际数据包长度)
                
                # 以太部首
                FrameType = PacketData[12:14]               # 帧类型
                # IP数据报头
                if FrameType == b'\x08\x00':                # 普通包
                    pass
                elif FrameType == b'\x81\x00':              # VLAN包
                    PacketData = PacketData[0:12] + b'\x08\x00' + PacketData[18:]                                       ## 剔除VLAN数据
                    包头 = struct.pack('IIII', PacketHeader[0], PacketHeader[1], PacketHeader[2]-4, PacketHeader[3]-4)  ## 修改包头
                else:
                    #print("其他包忽略")
                    pass
                
                IP_Protocol = PacketData[23:24]
                ## 只要TCP协议
                if IP_Protocol == b'\x06':                  # 协议(TCP 6)(UDP 17)
                    Seq,Len,DATA_Bytes = 读取包信息(PacketData)
                    if Len > 0:                                                     # 取有实际数据的包
                        if Seq in P_Seq:                                            # 只记录需要的部分
                            if (Seq,Len) in D_Seq_Len_DATA:                         # (Seq,Len) 重复的数据包
                                if D_Seq_Len_DATA[(Seq,Len)] == DATA_Bytes:
                                    Log.debug(f"重复(Seq,Len)=({Seq},{Len}) 数据也一致")
                                else:
                                    Log.debug(f"重复(Seq,Len)=({Seq},{Len}) 数据不一致")
                                    L_DATA_Other.append((Seq,Len,DATA_Bytes))
                            else:
                                D_Seq_Len_DATA[(Seq,Len)] = DATA_Bytes              # 保存找到的TCP数据(Bytes)
                    elif Len < 0:
                        Log.error(f"【ERROR】解析异常,长度={Len}<0,PacketData={PacketData}")
            f.close()
        print(f"  完成 {time.strftime('%Y-%m-%d %H:%M:%S')}")
    else:
        print("【ERROR】有错误终止操作")

    return(D_Seq_Len_DATA, L_DATA_Other)


##【4】
def 根据SEQ整理组装数据为MBF(包信息列表, D_Seq_Len__Bytes):
    MIN_DEQ = min([i[1] for i in 包信息列表])
    Log.debug(f"  MIN_DEQ={MIN_DEQ}")
    
    Log.debug("  组装数据")
    MBF = BytesIO()                     # 内存文件,类似 f = open('new.data', 'ab')
    
    ## DEBUG
    for i in 包信息列表:
        Seq = i[1]
        Len = i[3]
        if Len <= 0:
            Log.error(f"  【ERROR】有异常长度包 ({Seq},{Len}) {Seq-MIN_DEQ}:{Len} DATA={D_Seq_Len__Bytes[(Seq,Len)]}")
        MBF.seek(Seq-MIN_DEQ)                   # 定位到指定位置
        DATA_OLD = MBF.read(Len)                # 原有数据
        DATA_NEW = D_Seq_Len__Bytes[(Seq,Len)]  # 新增数据
        MBF.seek(Seq-MIN_DEQ)                   # 定位到指定位置
        MBF.write(DATA_NEW)                     # 写入新数据
        ## 分析对比刚写入的新旧数据
        if DATA_OLD == b'\x00'*Len or DATA_OLD == b'':
            #MBF.seek(Seq-MIN_DEQ)
            #MBF.write(DATA_NEW)
            Log.debug(f"  全空,写入数据({Seq},{Len}) {Seq-MIN_DEQ}:{Len}")
        elif DATA_OLD == DATA_NEW:
            Log.debug(f"  重复,忽略数据({Seq},{Len}) {Seq-MIN_DEQ}:{Len}")
        else:
            #print(f"  DATA_OLD={DATA_OLD}")
            #print(f"  DATA_NEW={DATA_NEW}")
            #MBF.seek(Seq-MIN_DEQ)
            #MBF.write(DATA_NEW)
            Log.debug(f"  部分,重写数据({Seq},{Len}) {Seq-MIN_DEQ}:{Len}")
            LIN_MIN = min(len(DATA_OLD), len(DATA_NEW)) # 取两者最短长度进行对比
            for n in range(0, LIN_MIN):
                if DATA_OLD[n] != 0:
                    if DATA_OLD[n] != DATA_NEW[n]:
                        Log.warning(f"    START={Seq-MIN_DEQ} n={n} DATA_OLD[n]={DATA_OLD[n]} DATA_NEW[n]={DATA_NEW[n]}")     # 记录非空不相等字节信息
                        print(f"【W】START={Seq-MIN_DEQ} n={n} DATA_OLD[n]={DATA_OLD[n]} DATA_NEW[n]={DATA_NEW[n]}")
                n+=1
    MBF.seek(0)
    return(MBF)


## 解析HTTP请求数据(拼接数据包后的完整数据),直接写入日志
def MBF_HTTP_TEXT_C_ALL(MBF, FileSaveDir):
    ## 请求信息做成字典
    D_HTTP_Header = {}
    BytesData = MBF.getvalue()
    CRLRx2_Index, L_CRLR_index = HTTP_CRLRx2_Index(BytesData)
    Log.debug(f"  【DEBUG】CRLR结果:信息和数据分隔点={CRLRx2_Index} 信息内分隔点{L_CRLR_index}")
    if CRLRx2_Index > 0:
        ## 信息部分
        HTTP_INFO = BytesData[0:CRLRx2_Index-1]                        # -1可以把结尾的\r\n只留前一个\r\n
        Log.debug(f"  【DEBUG】信息部分 {HTTP_INFO}")
        HTTP_INFO_SP = HTTP_INFO.decode('ASCII', errors='ignore').split('\r\n')
        ## 解析请求行
        请求行SP = HTTP_INFO_SP[0].split(' ')
        请求方法, URL, 协议版本 = 请求行SP
        D_HTTP_Header['Request'] = 请求方法
        if 请求方法 == 'GET':
            URL = unquote(URL)
        D_HTTP_Header['URL'] = URL
        D_HTTP_Header['Version'] = 协议版本
        
        ## 解析请求头
        Log.info(f"  ===[ 请求信息 ]===")
        for n in range(1, len(HTTP_INFO_SP)):
            #Log.info(f"    {HTTP_INFO_SP[n]}")
            SP = HTTP_INFO_SP[n].split(': ')
            if len(SP) == 2:
                if SP[0] == 'Referer':
                    D_HTTP_Header['Referer'] = unquote(SP[1])   ## 解码
                else:
                    D_HTTP_Header[SP[0]] = SP[1]
        
        for K in D_HTTP_Header:
            Log.info(f"  %-17s : %s" % (K, D_HTTP_Header[K]))

        ## 数据部分
        HTTP_DATA = BytesData[CRLRx2_Index+1:]               # +1可以省去开头的\r\n
        Log.info("  ===[ 请求数据 ]===")
        if len(HTTP_DATA) != 0:
            if 'Content-Type' in D_HTTP_Header:
                MIME = D_HTTP_Header['Content-Type']
                if MIME in ('application/x-www-form-urlencoded; charset=UTF-8'):
                    REQ_DATA = unquote(HTTP_DATA.decode('ASCII', errors='ignore'))  # 解码请求数据
                    Log.info(REQ_DATA)
                elif MIME[0:30] == 'multipart/form-data; boundary=':
                    if len(MIME) > 30:
                        Boundary = bytes(MIME[30:], 'UTF8')                         # 提取分隔标识转成bytes
                        POST_form_data(HTTP_DATA, Boundary, FileSaveDir)
                    else:
                        Log.info('multipart/form-data; boundary=空')
                else:
                    Log.info(f"  放弃解析 {MIME}")
            else:
                Log.info('  没有 Content-Type 显示原始数据')
                Log.info(f"{HTTP_DATA}")
        else:
            Log.info("  无数据")
    else:
        Log.error(f"【ERROR】没有找到连续2个CRLR {CRLRx2_Index} {L_CRLR_index}")
    Log.info("  ===[ LEN ]===")
    Log.info(f"  TCP  DATA LEN: {len(BytesData)} (信息+数据)")
    Log.info(f"  HTTP DATA LEN: {len(HTTP_DATA)} (数据)")
    if 'Content-Length' in D_HTTP_Header:
        Log.debug(f"  Content-Length:{D_HTTP_Header['Content-Length']}")
    Log.info('')

## 解析HTTP响应数据(拼接数据包后的完整数据),直接写入日志
def MBF_HTTP_TEXT_S_ALL(MBF, FileSaveDir):
    D_HTTP_Header = {'Content-Encoding':'', 'Transfer-Encoding':'', 'ETag':''}          ## 请求信息做成字典,初始化几个重要变量
    BytesData = MBF.getvalue()
    CRLRx2_Index, L_CRLR_index = HTTP_CRLRx2_Index(BytesData)
    Log.debug(f"  【DEBUG】CRLR结果:信息和数据分隔点={CRLRx2_Index} 信息内分隔点{L_CRLR_index}")
    if CRLRx2_Index > 0:
        ## 信息部分
        HTTP_INFO = BytesData[0:CRLRx2_Index-1] ## -1可以把结尾的\r\n只留前一个\r\n
        Log.debug(f"  【DEBUG】信息部分 {HTTP_INFO}")
        HTTP_INFO_SP = HTTP_INFO.decode('ASCII', errors='ignore').split('\r\n')
        ## 解析响应行
        响应行SP = HTTP_INFO_SP[0].split(' ')
        D_HTTP_Header['HTTP_INFO'] = 响应行SP
        ## 解析响应头
        for n in range(1, len(HTTP_INFO_SP)):
            #Log.info(f"    {HTTP_INFO_SP[n]}")
            SP = HTTP_INFO_SP[n].split(': ')
            if len(SP) == 2:
                D_HTTP_Header[SP[0]] = SP[1]
        
        Log.info(f"  ===[ 响应信息 ]===")
        for K in D_HTTP_Header:
            Log.info(f"  %-17s : %s" % (K, D_HTTP_Header[K]))

        ## 数据部分
        HTTP_DATA = BytesData[CRLRx2_Index+1:]  ## +1可以省去开头的\r\n
        Log.info("  ===[ 响应数据 ]===")
        if len(HTTP_DATA) != 0:
            字符集 = 'UTF8'
            if 'Content-Type' in D_HTTP_Header:
                MIME = D_HTTP_Header['Content-Type']
                if MIME in ('text/plain;charset=UTF-8', 'text/html;charset=UTF-8'):
                    if D_HTTP_Header['Transfer-Encoding'] == 'chunked':
                        Log.info(f"  尝试 chunked 解码")
                        R, MBF = Chunked_BytesData_HTTP_DATA(HTTP_DATA)    # chunked 数据拼接
                        if R == 0:
                            if D_HTTP_Header['Content-Encoding'] == 'gzip':
                                Log.info(f"  尝试 gzip 解码")
                                HTTP_TEXT = MBF_UNGZIP_Return_TEXT(MBF, 字符集)             # gzip 解压
                                HTTP_TEXT_LEN = len(HTTP_TEXT)
                                if HTTP_TEXT_LEN == 0:
                                    Log.info(f"  解码后为空")
                                elif HTTP_TEXT_LEN > 10000:
                                    Log.info(f"    HTTP_TEXT_LEN = {HTTP_TEXT_LEN} > 10000 保存到本地")
                                    HTML_TEXT_SAVE(HTTP_TEXT, FileSaveDir, 字符集)
                                else:
                                    Log.info(f"{HTTP_TEXT}")
                            else:
                                Log.info(f"{MBF}")
                        else:
                            Log.error(f"  解码 chunked 失败")
                          
                elif MIME in ('application/javascript', 'text/javascript;charset=utf-8'):
                    if 'Content-Disposition' in D_HTTP_Header:
                        FileName = unquote(D_HTTP_Header['Content-Disposition'].split('filename=')[1][1:-1])
                        扩展名 = ''
                    elif 'Content-disposition' in D_HTTP_Header:
                        FileName = unquote(D_HTTP_Header['Content-disposition'].split('filename=')[1][1:-1])
                        扩展名 = ''
                    else:
                        FileName = re.sub('[^a-f0-9\-]', '', D_HTTP_Header['ETag'])
                        扩展名 = '.js'
                    if D_HTTP_Header['Transfer-Encoding'] == 'chunked':
                        Log.info(f"  尝试 chunked 解码")
                        R, MBF = Chunked_BytesData_HTTP_DATA(HTTP_DATA)    # chunked 数据拼接
                        if R == 0:
                            if D_HTTP_Header['Content-Encoding'] == 'gzip':
                                Log.info(f"  尝试 gzip 解码")
                                DATA_UNGZIP = MBF_UNGZIP(MBF)             # gzip 解压
                                if type(DATA_UNGZIP) == bytes:
                                    if DATA_UNGZIP != b'':
                                        B_SAVE(DATA_UNGZIP, FileSaveDir, FileName, 扩展名)
                                    else:
                                        Log.info(f"  内容为空")
                                else:
                                    Log.error(f"  解码 gzip 失败")
                            else:
                                DATA = MBF.getvalue()
                                B_SAVE(DATA, FileSaveDir, FileName, 扩展名)
                        else:
                            Log.error(f"  解码 chunked 失败")
                    else:
                        B_SAVE(HTTP_DATA, FileSaveDir, FileName, 扩展名)
                elif MIME in ('text/css', 'text/css;charset=utf-8'):
                    if 'Content-Disposition' in D_HTTP_Header:
                        FileName = unquote(D_HTTP_Header['Content-Disposition'].split('filename=')[1][1:-1])
                        扩展名 = ''
                    elif 'Content-disposition' in D_HTTP_Header:
                        FileName = unquote(D_HTTP_Header['Content-disposition'].split('filename=')[1][1:-1])
                        扩展名 = ''
                    else:
                        FileName = re.sub('[^a-f0-9\-]', '', D_HTTP_Header['ETag'])
                        扩展名 = '.css'
                    #print(f"FileName={FileName} 扩展名={扩展名}")
                    if D_HTTP_Header['Transfer-Encoding'] == 'chunked':
                        Log.info(f"  尝试 chunked 解码")
                        R, MBF = Chunked_BytesData_HTTP_DATA(HTTP_DATA)    # chunked 数据拼接
                        if R == 0:
                            if D_HTTP_Header['Content-Encoding'] == 'gzip':
                                Log.info(f"  尝试 gzip 解码")
                                DATA_UNGZIP = MBF_UNGZIP(MBF)             # gzip 解压
                                if type(DATA_UNGZIP) == bytes:
                                    if DATA_UNGZIP != b'':
                                        B_SAVE(DATA_UNGZIP, FileSaveDir, FileName, 扩展名)
                                    else:
                                        Log.info(f"  内容为空")
                                else:
                                    Log.error(f"  解码 gzip 失败")
                            else:
                                DATA = MBF.getvalue()
                                B_SAVE(DATA, FileSaveDir, FileName, 扩展名)
                        else:
                            Log.error(f"  解码 chunked 失败")
                    else:
                        B_SAVE(HTTP_DATA, FileSaveDir, FileName, 扩展名)
                elif MIME == 'image/jpeg':
                    FileName = re.sub('[^a-f0-9\-]', '', D_HTTP_Header['ETag'])
                    B_SAVE(HTTP_DATA, FileSaveDir, FileName, '.jpeg')
                elif MIME == 'image/gif':
                    FileName = re.sub('[^a-f0-9\-]', '', D_HTTP_Header['ETag'])
                    B_SAVE(HTTP_DATA, FileSaveDir, FileName, '.gif')
                elif MIME == 'image/png':
                    FileName = re.sub('[^a-f0-9\-]', '', D_HTTP_Header['ETag'])
                    B_SAVE(HTTP_DATA, FileSaveDir, FileName, '.png')
                elif MIME == 'application/x-msdownload;charset=UTF-8':
                    if 'Content-Disposition' in D_HTTP_Header:
                        FileName = unquote(D_HTTP_Header['Content-Disposition'].split('filename=')[1][1:-1])
                        Log.info(f"  文件 x-msdownload FileName={FileName}")
                        R, MBF = Chunked_BytesData_HTTP_DATA(HTTP_DATA)
                        if R == 0:
                            MBF_SAVE(MBF, FileSaveDir, FileName) # 保存到本地
                        else:
                            Log.info(f"{MBF}")
                elif MIME == 'application/octet-stream;charset=UTF-8':
                    if D_HTTP_Header['Transfer-Encoding'] == 'chunked':
                        R, MBF = Chunked_BytesData_HTTP_DATA(HTTP_DATA)
                        if R == 0:
                            FileName = 'un.data'
                            MBF_SAVE(MBF, FileSaveDir, FileName) # 保存到本地
                        else:
                            Log.info(f"{MBF}")
                elif MIME == 'video/mp4':
                    if 'Content-Range' in D_HTTP_Header and D_HTTP_Header['Content-Range'] != '':   ## 抖音mp4,Content-Range     : bytes 12017629-13681403/13681404
                        TEXT = D_HTTP_Header['Content-Range']
                        字节编号, 视频编号 = TEXT.split('/')
                        #SP_字节编号_开始, SP_字节编号_结束 = 字节编号[5:].split('-')
                        FileName = f"{视频编号}_{字节编号[6:]}"
                    else:
                        FileName = 'video_mp4'
                    B_SAVE(HTTP_DATA, FileSaveDir, FileName, '.mp4') # 保存到本地
                else:
                    Log.info(f"放弃解析 {MIME}")
                    if len(HTTP_DATA) < 100000:
                        Log.info(f"{HTTP_DATA}")
                    else:
                        Log.info(f"原始数据>100000不打印了")
            else:
                Log.info('没有Content-Type')
        else:
            Log.info('  无数据')
    else:
        Log.error(f"【ERROR】没有找到连续2个CRLR {CRLRx2_Index} {L_CRLR_index}")
    Log.info("  ===[ LEN ]===")
    Log.info(f"  TCP  DATA LEN: {len(BytesData)} (信息+数据)")
    Log.info(f"  HTTP DATA LEN: {len(HTTP_DATA)} (数据)")
    if 'Content-Length' in D_HTTP_Header:
        Log.debug(f"  Content-Length:{D_HTTP_Header['Content-Length']}")
    Log.info('')

## 二进制文件保存(文件名为'Content-Disposition'值或ETag或随机数字)
def B_SAVE(HTTP_DATA, FileSaveDir, FileName, 扩展名):
    try:
        os.chdir(FileSaveDir)
    except Exception as e:
        Log.error(f"  文件保存失败,目录 {FileSaveDir} 不存在或没有写入权限 {e}")
    else:
        if FileName == '':
            FileName = '无名文件'
        while 1:
            if os.path.exists(FileName + 扩展名):
                FileName += '_重复'
            else:
                break
        try:
            f = open(FileName + 扩展名, 'bw')
            f.write(HTTP_DATA)
            f.close()
        except Exception as e:
            Log.error(f"  文件 {FileName}{扩展名} 保存失败 {e}")
        else:
            Log.info(f"  文件 {FileName}{扩展名} 已保存到 {FileSaveDir} 目录")

## HTML TEXT 保存到本地(文件名为随机数字)
def HTML_TEXT_SAVE(TEXT, FileSaveDir, 字符集):
    try:
        os.chdir(FileSaveDir)
    except Exception as e:
        Log.error(f"  文件保存失败,目录 {FileSaveDir} 不存在或没有写入权限 {e}")
    else:
        FileName = '无名文件'
        while 1:
            if os.path.exists(FileName + '.html'):
                FileName += '_重复'    # 修改文件名防止重复造成覆盖
            else:
                break
        try:
            f = open(FileName + '.html', 'tw', encoding=字符集)
            f.write(TEXT)
            f.close()
        except Exception as e:
            Log.error(f"  生成随机文件名 {FileName}.html 保存失败 {e}")
        else:
            Log.info(f"  生成随机文件名 {FileName}.html 已保存到 {FileSaveDir} 目录")

## MBF 保存到本地
def MBF_SAVE(MBF, FileSaveDir, FileName):
    try:
        os.chdir(FileSaveDir)
        f = open(FileName, 'bw')
        f.write(MBF.getvalue())
        f.close()
    except Exception as e:
        Log.error(f"  文件 {FileName} 保存失败,目录 {FileSaveDir} 不存在或没有写入权限 {e}")
    else:
        Log.debug(f"  文件 {FileName} 已保存到 {FileSaveDir} 目录")

## MBF gzip解压成字节码
def MBF_UNGZIP(MBF):
    #print("GZIP", MBF.getvalue())
    MBF.seek(0)     ## 必须移动到MBF文件开头
    f = gzip.open(MBF,'rb')
    try:
        DATA_UNGZIP = f.read()
    except Exception as e:
        print("GZIP解码前原值", f.read())
        print(f"GZIP解码失败信息 {e}")
        return('')
    else:
        return(DATA_UNGZIP)

## MBF gzip解压成文本
def MBF_UNGZIP_Return_TEXT(MBF, 字符集):
    #print("GZIP", MBF.getvalue())
    MBF.seek(0)     ## 必须移动到MBF文件开头
    f = gzip.open(MBF,'rb')
    DATA_UNGZIP = f.read()
    #print(f"DATA_UNGZIP {DATA_UNGZIP}")
    try:
        TEXT = DATA_UNGZIP.decode(字符集)
    except Exception as e:
        TEXT = f"用字符集{字符集}解码失败"
    #print("TEXT", TEXT)
    return(TEXT)

## 解析 HTTP Chunked 内容,传入BytesData,返回内存文件对象MBF
def Chunked_BytesData_HTTP_DATA(BytesData):
    ChunkSizeBytes = b''    # 二进制类型ChunkSize
    MBF_R = BytesIO()
    MBF_R.write(BytesData)
    MBF_R.seek(0)
    
    MBF_W = BytesIO()       # ChunkData 保存在内存文件中
    while 1:
        Bx = MBF_R.read(1)
        if not Bx:
            break
        #Log.debug(f"Bx {Bx}")
        if Bx == b'\r':
            By = MBF_R.read(1)
            if By == b'\n':
                try:
                    ChunkSize = int(ChunkSizeBytes, 16)     # 转成十进制数字类型ChunkSize
                except Exception as e:
                    Log.debug(f"  【ERROR】CRLR ChunkSizeBytes {ChunkSizeBytes} LEN {len(ChunkSizeBytes)} E {e}")
                    return(1, '拼接数据异常,ChunkSize 异常')
                else:
                    Log.debug(f"  【DEBUG】CRLR ChunkSizeBytes {ChunkSizeBytes} LEN {len(ChunkSizeBytes)} ChunkSize(int) {ChunkSize}")
                    if ChunkSize == 0:
                        return(0, MBF_W)
                    ChunkSizeBytes = b''
                    ChunkData = MBF_R.read(ChunkSize)       # 读取实际数据
                    MBF_R.read(2)                           # 再向后读掉\r\n这两个字节
                    #print("ChunkData", ChunkData)
                    MBF_W.write(ChunkData)                  # 写入实际数据
            else:
                ChunkSizeBytes += By
        else:
            ChunkSizeBytes += Bx
    return(1, '拼接数据异常,没有结尾0')

## 解析POST提交文件信息
def POST_form_data(HTTP_DATA, Boundary_in, FileSaveDir):
    Boundary = b'--' + Boundary_in              # 实际分隔标识要加 b'--'
    Boundary_SP = b'\r\n'+Boundary+b'\r\n'
    Boundary_S = Boundary+b'\r\n'
    Boundary_E = b'\r\n' + Boundary + b'--\r\n'
    LEN_S = len(Boundary_S)
    LEN_E = -len(Boundary_E)
    
    if HTTP_DATA[0:LEN_S] == Boundary_S and HTTP_DATA[LEN_E:] == Boundary_E:
        Log.info("  检查数据首尾部分:正常")
        L = HTTP_DATA[LEN_S:LEN_E].split(Boundary_SP)
        for i in L:
            ## 通过 b'\r\n\r\n' 分离信息和数据
            INFO, DATA = i.split(b'\r\n\r\n')
            #print(f"INFO {INFO}")
            INFO_SP = INFO.split(b'\r\n')
            ## 先处理信息部分
            FileName = ''       # 准备文件名,有文件的话,保存到本地
            for j in INFO_SP:
                #print(j.split(b'; '))
                for k in j.split(b'; '):
                    Log.info(f"  {k}")
                    k_sp = k.split(b'=')
                    if len(k_sp) == 2:
                        if k_sp[0] == b'filename' and k_sp[1] != b'""':
                            FullFileName = unquote(k_sp[1][1:-1].decode('UTF8', errors='ignore')) # 去掉头尾引号再转ASCII再解码
                            Log.info(f"  FullFileName {FullFileName}")
                            FileName = os.path.basename(FullFileName)
                            Log.debug(f"  提取文件名 FileName {FileName}")
            if FileName != '':
                try:
                    os.chdir(FileSaveDir)
                    f = open(FileName, 'bw')
                    f.write(DATA)
                    f.close()
                except Exception as e:
                    Log.error(f"  文件 {FileName} 保存失败,目录 {FileSaveDir} 不存在或没有写入权限 {e}")
                else:
                    Log.info(f"  文件 {FileName} 已保存到 {FileSaveDir} 目录")
            else:
                Log.info(f"  非文件数据(直接写入日志) {DATA}")
    else:
        Log.error("  检查数据首尾部分:错误")
        Log.error(f"  Boundary_S {Boundary_S}")
        Log.error(f"  DATA_S     {HTTP_DATA[0:LEN_S]}")
        Log.error(f"  Boundary_E {Boundary_E}")
        Log.error(f"  DATA_E     {HTTP_DATA[LEN_E:]}")

## 查找CRLRCRLR位置(请求头部信息和实际数据的分界点)
##            16 16 10
## 回车 CR \r 0d 13 19    0x13 = 19
## 换行 LR \n 0a 10 16    0x13 = 16
## 空格          20 32
## 返回 CRLRx2_Index(连续2次CRLR位置,定位实际数据开始位置用), L_CRLR_index(CRLR位置列表,给解析行和头定位用)
def HTTP_CRLRx2_Index(BytesData):
    CR = 0
    LR = 0
    字节当前位置 = 0
    CRLR位置记录 = -2   # 记录找到的连续2次CRLR位置,定位实际数据开始位置用,初始为-2是网络避开第一次匹配
    L_CRLR_index = []   # 保存找到的CRLR位置,给解析行和头定位用
    for B in BytesData:
        if B == 13:
            CR = 字节当前位置
        if B == 10:
            LR = 字节当前位置
            if CR+1 == LR:                                # 前一个CR的位置在当前LR位置的前一字节,说明CRLR连续
                if 字节当前位置 == CRLR位置记录 + 2:
                    #print("连续2次CRLR", 字节当前位置)
                    return(字节当前位置, L_CRLR_index)    # 找到连续2次CRLR位置,直接终止返回结果
                else:
                    CRLR位置记录 = 字节当前位置
                    L_CRLR_index.append(字节当前位置)
        字节当前位置 += 1                                 # 每运行一次,位置加1
    return(字节当前位置, L_CRLR_index)                    # 没有找到连续2次CRLR位置




##【4】【4 显示会话中每次交互信息】
def 显示会话中每次交互信息(L_会话编号, L_TCP_SESSION_KEY, D_TCP_ALL, 选择日志等级):
    #print("L_会话编号", L_会话编号)                 # [0]
    #print("L_TCP_SESSION_KEY", L_TCP_SESSION_KEY)   # [('192.168.63.2', 40230, '120.39.196.244', 80)]
    
    L_交互信息 = []
    for 会话编号 in L_会话编号:
        选择会话KEY = L_TCP_SESSION_KEY[会话编号]
        #print(f"  会话编号={会话编号} 选择会话KEY={选择会话KEY}")
        #L_DATA = D_TCP_ALL[选择会话KEY]['L_DATA']
        #for i in L_DATA:
        #    print(i)
        
        D_TCP_SESSION = D_TCP_ALL[选择会话KEY]
        D_单个TCP会话中全部客户端请求 = D_TCP_SESSION['D_C_ACK_DATA']   # {2614655155: [(1633684486.114341, 1407086186, 2614655155, 753, 128, 4)], 2616554750: [(1633684521.717116, 1407086939, 2616554750, 755, 2269, 976)]}
        D_单个TCP会话中全部服务端响应 = D_TCP_SESSION['D_S_ACK_DATA']
        #print(f"D_单个TCP会话中全部客户端请求={D_单个TCP会话中全部客户端请求}")
        
        ## 遍历全部请求
        P_有请求的响应ACK = set()                             # 记录有请求的响应ACK,后来找未抓到请求的响应包需要用到
        for C_请求_ACK in D_单个TCP会话中全部客户端请求:
            L_C_请求包信息 = D_单个TCP会话中全部客户端请求[C_请求_ACK] # [(1633684486.114341, 1407086186, 2614655155, 753, 128, 4)]
            C_请求包数量 = len(L_C_请求包信息)
            C_请求包大小 = sum([i[3] for i in L_C_请求包信息])
            ## 找请求对应的响应信息,请求的Ack = 响应的起始包的Seq
            找到_S_响应_ACK = '-'
            S_响应包数量 = 0
            S_响应包大小 = 0
            for S_响应_ACK in D_单个TCP会话中全部服务端响应:
                L_S_响应包信息 = D_单个TCP会话中全部服务端响应[S_响应_ACK]
                for S_响应包信息 in L_S_响应包信息:
                    S_响应包_Seq = S_响应包信息[1]
                    S_响应包_Ack = S_响应包信息[2]
                    if S_响应包_Seq == C_请求_ACK:
                        找到_S_响应_ACK = S_响应包_Ack
                        P_有请求的响应ACK.add(S_响应包_Ack)  # 记录有请求的响应ACK,后来找未抓到请求的响应包需要用到
                        break
                if 找到_S_响应_ACK != '-':
                    S_响应包数量 = len(L_S_响应包信息)
                    S_响应包大小 = sum([i[3] for i in L_S_响应包信息])
                    break
            #print(f"    C_请求_ACK={C_请求_ACK} C_请求包数量={C_请求包数量} C_请求包大小={C_请求包大小} | S_响应_ACK={找到_S_响应_ACK} S_响应包数量={S_响应包数量} S_响应包大小={S_响应包大小}")
            L_交互信息.append((C_请求_ACK, C_请求包数量, C_请求包大小, 找到_S_响应_ACK, S_响应包数量, S_响应包大小, 选择会话KEY))
    
        ## 可能出现没有抓到请求,但抓到了响应的情况
        for S_响应_ACK in D_单个TCP会话中全部服务端响应:
            if S_响应_ACK not in P_有请求的响应ACK:
                L_S_响应包信息 = D_单个TCP会话中全部服务端响应[S_响应_ACK]
                S_响应包数量 = len(L_S_响应包信息)
                S_响应包大小 = sum([i[3] for i in L_S_响应包信息])
                C_请求_ACK = '-'
                C_请求包数量 = 0
                C_请求包大小 = 0
                找到_S_响应_ACK = S_响应_ACK
                L_交互信息.append((C_请求_ACK, C_请求包数量, C_请求包大小, 找到_S_响应_ACK, S_响应包数量, S_响应包大小, 选择会话KEY))
        
    ## 显示
    #for i in L_交互信息:
    #    print(i)
    return(L_交互信息)


##【4】【选择TCP交互解析方式】【TCP会话交互数据解析】
def TCP会话交互数据解析(L_可用交互编号, L_交互信息, PCAP_File_OR_Dir, D_TCP_ALL, ROOT_DIR):
    #print("L_可用交互编号", L_可用交互编号)
    #print("L_交互信息", L_交互信息)
    for 编号 in L_可用交互编号:
        Log.debug(f"TCP内容解析 {L_交互信息[编号]}")          # (2614655155, 1, 753, 1407086939, 612, 2125895, ('192.168.63.2', 40230, '2.3.19.2', 80))
        C_请求_ACK, C_请求包数量, C_请求包大小, S_响应_ACK, S_响应包数量, S_响应包大小, TCP会话KEY = L_交互信息[编号]
        
        ## 生成存放目录,以客户端服务端信息为名
        FileSaveDir = ROOT_DIR+'_'.join([str(i) for i in TCP会话KEY])+'\\'
        if not os.path.exists(FileSaveDir):
            os.makedirs(FileSaveDir)
        
        if C_请求_ACK != '-':
            ## 收集本次需要用到的请求和响应数据包的Seq
            P_Seq = set()
            D_TCP_会话交互 = D_TCP_ALL[TCP会话KEY]
            for i in D_TCP_会话交互['D_C_ACK_DATA'][C_请求_ACK]:  # [(1633684486.114341, 1407086186, 2614655155, 753, 128, 4)]
                Seq = i[1]
                P_Seq.add(Seq)
            if S_响应_ACK != '-':     # 有响应才继续
                for i in D_TCP_会话交互['D_S_ACK_DATA'][S_响应_ACK]:
                    Seq = i[1]
                    P_Seq.add(Seq)
            ## 收集本次需要用到的请求和响应数据包的有效数据,做成字典
            D_Seq_Len_DATA,L_DATA_Other = 从PCAP文件中提取TCP有效数据_生成以SeqLen为KEY的字典(PCAP_File_OR_Dir, P_Seq)
            
            ## 保存请求数据
            MBF = 根据SEQ整理组装数据为MBF(D_TCP_会话交互['D_C_ACK_DATA'][C_请求_ACK], D_Seq_Len_DATA)
            #Log.info(f"  DATA_Bytes={MBF.getvalue()}")
            保存文件名 = f"请求数据_{C_请求_ACK}_{C_请求包数量}_{C_请求包大小}"
            #print(f"  保存文件名={保存文件名}")
            Log.info(f"  ===[ TCP请求 ACK={C_请求_ACK} ] [ {len(MBF.getvalue())}(Bytes) ] \t数据文件:{保存文件名} 保存目录:{FileSaveDir}")
            MBF_SAVE(MBF, FileSaveDir, 保存文件名)
            
            ## 保存响应数据
            if S_响应_ACK != '-':
                MBF = 根据SEQ整理组装数据为MBF(D_TCP_会话交互['D_S_ACK_DATA'][S_响应_ACK], D_Seq_Len_DATA)
                #Log.info(f"  DATA_Bytes={MBF.getvalue()}")
                保存文件名 = f"响应数据_{S_响应_ACK}_{S_响应包数量}_{S_响应包大小}"
                #print(f"  保存文件名={保存文件名}")
                Log.info(f"  ===[ TCP响应 ACK={S_响应_ACK} ] [ {len(MBF.getvalue())}(Bytes) ] \t数据文件:{保存文件名} 保存目录:{FileSaveDir}")
                MBF_SAVE(MBF, FileSaveDir, 保存文件名)
            else:
                Log.info(f"  ===[ TCP响应 未找到 ]=====")
        else:   ## 没有抓到请求,只抓到响应
            ## 收集本次需要用到的响应数据包的Seq
            P_Seq = set()
            D_TCP_会话交互 = D_TCP_ALL[TCP会话KEY]
            for i in D_TCP_会话交互['D_S_ACK_DATA'][S_响应_ACK]:
                Seq = i[1]
                P_Seq.add(Seq)
            ## 收集本次需要用到的请求和响应数据包的有效数据,做成字典
            D_Seq_Len_DATA,L_DATA_Other = 从PCAP文件中提取TCP有效数据_生成以SeqLen为KEY的字典(PCAP_File_OR_Dir, P_Seq)
            
            Log.info(f"  ===[ TCP请求 未找到 ]=====")
            
            ## 保存响应数据
            MBF = 根据SEQ整理组装数据为MBF(D_TCP_会话交互['D_S_ACK_DATA'][S_响应_ACK], D_Seq_Len_DATA)
            #Log.info(f"  DATA_Bytes={MBF.getvalue()}")
            保存文件名 = f"响应数据_{S_响应_ACK}_{S_响应包数量}_{S_响应包大小}"
            #print(f"  保存文件名={保存文件名}")
            Log.info(f"  ===[ TCP响应 ACK={S_响应_ACK} ] [ {len(MBF.getvalue())}(Bytes) ] \t数据文件:{保存文件名} 保存目录:{FileSaveDir}")
            MBF_SAVE(MBF, FileSaveDir, 保存文件名)


##【4】【选择TCP交互解析方式】【尝试以HTTP协议解析】
def 尝试以HTTP协议解析(L_可用交互编号, L_交互信息, PCAP_File_OR_Dir, D_TCP_ALL, ROOT_DIR):
    #print("L_可用交互编号", L_可用交互编号)
    #print("L_交互信息", L_交互信息)
    for 编号 in L_可用交互编号:
        Log.debug(f"HTTP解析 {L_交互信息[编号]}")           # (2614655155, 1, 753, 1407086939, 612, 2125895, ('192.168.63.2', 40230, '2.3.19.2', 80))
        C_请求_ACK, C_请求包数量, C_请求包大小, S_响应_ACK, S_响应包数量, S_响应包大小, TCP会话KEY = L_交互信息[编号]
        
        ## 生成存放目录,以客户端服务端信息为名
        FileSaveDir = ROOT_DIR+'_'.join([str(i) for i in TCP会话KEY])+'\\'
        if not os.path.exists(FileSaveDir):
            os.makedirs(FileSaveDir)
        
        if C_请求_ACK != '-':
            ## 收集本次需要用到的请求和响应数据包的Seq
            P_Seq = set()
            D_HTTP = D_TCP_ALL[TCP会话KEY]
            for i in D_HTTP['D_C_ACK_DATA'][C_请求_ACK]:        # [(1633684486.114341, 1407086186, 2614655155, 753, 128, 4)]
                Seq = i[1]
                P_Seq.add(Seq)
            if S_响应_ACK != '-':     # 有响应才继续
                for i in D_HTTP['D_S_ACK_DATA'][S_响应_ACK]:
                    Seq = i[1]
                    P_Seq.add(Seq)
            ## 收集本次需要用到的请求和响应数据包的有效数据,做成字典
            D_Seq_Len_DATA,L_DATA_Other = 从PCAP文件中提取TCP有效数据_生成以SeqLen为KEY的字典(PCAP_File_OR_Dir, P_Seq)
            
        
            Log.info(f"=====[ HTTP请求 ACK={C_请求_ACK} ]=====")
            MBF = 根据SEQ整理组装数据为MBF(D_HTTP['D_C_ACK_DATA'][C_请求_ACK], D_Seq_Len_DATA)
            try:
                MBF_HTTP_TEXT_C_ALL(MBF, FileSaveDir)
            except:
                Log.error(f"=====[ 不是HTTP请求格式 ]=====")
            
            if S_响应_ACK != '-':
                Log.info(f"=====[ HTTP响应 ACK={S_响应_ACK} ]=====")
                MBF = 根据SEQ整理组装数据为MBF(D_HTTP['D_S_ACK_DATA'][S_响应_ACK], D_Seq_Len_DATA)
                try:
                    MBF_HTTP_TEXT_S_ALL(MBF, FileSaveDir)
                except:
                    Log.error(f"=====[ 不是HTTP响应格式 ]=====")
            else:
                Log.info(f"=====[ HTTP响应 未找到 ]=====")
        else:   ## 没有抓到请求,只抓到响应
            ## 收集本次需要用到的响应数据包的Seq
            P_Seq = set()
            D_HTTP = D_TCP_ALL[TCP会话KEY]
            for i in D_HTTP['D_S_ACK_DATA'][S_响应_ACK]:
                Seq = i[1]
                P_Seq.add(Seq)
            ## 收集本次需要用到的请求和响应数据包的有效数据,做成字典
            D_Seq_Len_DATA,L_DATA_Other = 从PCAP文件中提取TCP有效数据_生成以SeqLen为KEY的字典(PCAP_File_OR_Dir, P_Seq)
            
            Log.info(f"=====[ HTTP请求 未找到 ]=====")
            
            Log.info(f"=====[ HTTP响应 ACK={S_响应_ACK} ]=====")
            MBF = 根据SEQ整理组装数据为MBF(D_HTTP['D_S_ACK_DATA'][S_响应_ACK], D_Seq_Len_DATA)
            try:
                MBF_HTTP_TEXT_S_ALL(MBF, FileSaveDir)
            except:
                Log.error(f"=====[ 不是HTTP响应格式 ]=====")




def 解析用户输入的编号(TEXT):
    TEXT = TEXT.replace(' ', '')
    L = []
    LSPa = TEXT.split(',')
    #print("LSPa", LSPa)
    for i in LSPa:
        if i != '':
            LSPb = i.split('-')
            if len(LSPb) == 2:
                A = int(LSPb[0])
                B = int(LSPb[1])
                if A <= B:
                    for j in range(A, B+1):
                        L.append(j)
            elif len(LSPb) == 1:
                #print("LSPb", LSPb)
                A = int(LSPb[0])
                L.append(A)
            else:
                pass
    return(L)



## 交互操作提示内容

## 【0】选择日志等级(写入日志文件)
选择日志等级_TEXT = '''
::::::::::::::::::::::::::::::::::::::::::::::
:: 0 DEBUG     日志记录详细信息             ::
:: 1 INFO      日志记录主要信息并在终端显示 ::
:: 2 WARNING                                ::
:: 3 ERROR                                  ::
:: 4 CRITICAL                               ::
:: q 退出                                   ::
::::::::::::::::::::::::::::::::::::::::::::::
【0】选择日志等级(写入日志文件): '''

## 【1】选择过滤规则
选择过滤规则_TEXT = '''
::::::::::::::::::::::::::::::::::::::::::::::
:: 0 IP                                     ::
:: 1 PORT                                   ::
:: 2 IP+PORT                                ::
:: 3 ALL        (全部提取分析)              ::
:: q 返回上级                               ::
::::::::::::::::::::::::::::::::::::::::::::::
【1】选择过滤规则: '''

## 【2】选择TCP或UDP
选择TCP或UDP_TEXT = '''
::::::::::::::::::::::::::::::::::::::::::::::
:: t   查看TCP                              ::
:: u   查看UDP                              ::
:: t2p 每个TCP会话导出为单独PCAP文件        ::
:: u2p 每个UDP会话导出为单独PCAP文件        ::
:: q   返回上级                             ::
::::::::::::::::::::::::::::::::::::::::::::::
【2】选择TCP或UDP: '''

## 【4】选择TCP解析功能
TCP选择功能_TEXT = '''
::::::::::::::::::::::::::::::::::::::::::::::
:: 0 TCP会话交互过程用时分析(无图)          ::
:: 1 TCP会话交互过程用时分析(画图)          ::
:: 2 选中TCP会话导出为PCAP文件              ::
:: 3 全部TCP会话导出为PCAP文件              ::
:: 4 显示会话中每次交互信息                 ::
:: q 返回上级                               ::
::::::::::::::::::::::::::::::::::::::::::::::
【4】选择TCP解析功能: '''

## 【4】选择UDP解析功能
UDP选择功能_TEXT = '''
::::::::::::::::::::::::::::::::::::::::::::::
:: 0 选中UDP会话导出为PCAP文件              ::
:: q 返回上级                               ::
::::::::::::::::::::::::::::::::::::::::::::::
【4】选择UDP解析功能: '''

## 【5】选择TCP交互选择功能
TCP交互选择功能_TEXT = '''
::::::::::::::::::::::::::::::::::::::::::::::
:: tcp   TCP会话交互数据解析                ::
:: udp   UDP会话交互数据解析                ::
:: http  尝试以HTTP协议解析                 ::
:: https 尝试以HTTPS协议解析                ::
:: mysql 尝试以MySQL协议解析                ::
:: q     返回上级                           ::
::::::::::::::::::::::::::::::::::::::::::::::
【4】选择TCP交互解析方式: '''



## D_TCP_PACKET 用于显示交互的实际Bytes
def 显示_选择TCP会话编号(L_TCP_SESSION_KEY, D_TCP_PACKET, 筛选=''):
    设置CMD颜色(暗天蓝色)
    N = 0
    print("----------------------------------------------------------------------------")
    print("|【TCP】会话信息")
    print("----------------------------------------------------------------------------")
    print("| 编号          源地址:端口              目的地址:端口   Bytes=>  |  <=Bytes")
    print("----------------------------------------------------------------------------")
    if 筛选 == '':
        for i in L_TCP_SESSION_KEY:
            SIP,SPORT,DIP,DPORT = i
            方向1Bytes = D_TCP_PACKET[i]['Bytes_Len']
            if (DIP,DPORT,SIP,SPORT) in D_TCP_PACKET:   # 判断是否有反方向流量
                方向2Bytes = D_TCP_PACKET[(DIP,DPORT,SIP,SPORT)]['Bytes_Len']
            else:
                方向2Bytes = 0
            print(f"| {N:<4} {SIP:>15}:{SPORT:<5}  <=> {DIP:>15}:{DPORT:<5}  {方向1Bytes:<9} {方向2Bytes:9}")
            N += 1
        print("----------------------------------------------------------------------------")
        print(f"| TCP会话总数 {N}")
        print("----------------------------------------------------------------------------")
    else:
        匹配规则 = f"(.*){筛选}(.*)"
        n = 0
        for i in L_TCP_SESSION_KEY:
            X = re.search(匹配规则, str(i))
            if X:
                SIP,SPORT,DIP,DPORT = i
                方向1Bytes = D_TCP_PACKET[i]['Bytes_Len']
                if (DIP,DPORT,SIP,SPORT) in D_TCP_PACKET:   # 判断是否有反方向流量
                    方向2Bytes = D_TCP_PACKET[(DIP,DPORT,SIP,SPORT)]['Bytes_Len']
                else:
                    方向2Bytes = 0
                print(f"| {N:<4} {SIP:>15}:{SPORT:<5}  <=> {DIP:>15}:{DPORT:<5}  {方向1Bytes:<9} {方向2Bytes:9}")
                n += 1
            N += 1
        print("----------------------------------------------------------------------------")
        print(f"| 匹配字符串='{筛选}'")
        print("----------------------------------------------------------------------------")
        print(f"| 筛选出TCP会话数 {n}")
        print("----------------------------------------------------------------------------")
    设置CMD颜色(金色)
    print("::::::::::::::::::::::::::::::::::::::::::::::")
    print(":  x 画流量图  -  s 筛选结果  -  q 返回上级  :")
    print(":  输入会话编号分析会话用时信息(例 0,1,3-5)  :")
    print("::::::::::::::::::::::::::::::::::::::::::::::")
    设置CMD颜色(白色)


def 显示_选择UDP会话编号(L_UDP_SESSION_KEY, D_UDP_PACKET, 筛选=''):
    设置CMD颜色(暗天蓝色)
    N = 0
    print("----------------------------------------------------------------------------")
    print("| UDP交互信息")
    print("| 编号          源地址:端口              目的地址:端口   Bytes=>  |  <=Bytes")
    if 筛选 == '':
        for i in L_UDP_SESSION_KEY:
            SIP,SPORT,DIP,DPORT = i
            方向1Bytes = D_UDP_PACKET[i]['Bytes_Len']
            if (DIP,DPORT,SIP,SPORT) in D_UDP_PACKET:   # 判断是否有反方向流量
                方向2Bytes = D_UDP_PACKET[(DIP,DPORT,SIP,SPORT)]['Bytes_Len']
            else:
                方向2Bytes = 0
            print(f"| {N:<4} {SIP:>15}:{SPORT:<5}  <=> {DIP:>15}:{DPORT:<5}  {方向1Bytes:<9} {方向2Bytes:9}")
            N += 1
        print(f"| UDP会话总数 {N}")
        print("----------------------------------------------------------------------------")
    else:
        匹配规则 = f"(.*){筛选}(.*)"
        n = 0
        for i in L_UDP_SESSION_KEY:
            X = re.search(匹配规则, str(i))
            if X:
                SIP,SPORT,DIP,DPORT = i
                方向1Bytes = D_UDP_PACKET[i]['Bytes_Len']
                if (DIP,DPORT,SIP,SPORT) in D_UDP_PACKET:   # 判断是否有反方向流量
                    方向2Bytes = D_UDP_PACKET[(DIP,DPORT,SIP,SPORT)]['Bytes_Len']
                else:
                    方向2Bytes = 0
                print(f"| {N:<4} {SIP:>15}:{SPORT:<5}  <=> {DIP:>15}:{DPORT:<5}  {方向1Bytes:<9} {方向2Bytes:9}")
                n += 1
            N += 1
        print(f"| 匹配字符串={筛选}")
        print(f"| 筛选出UDP会话数 {n}")
        print("----------------------------------------------------------------------------")
    设置CMD颜色(金色)
    print("::::::::::::::::::::::::::::::::::::::::::::::")
    print(":  x 画流量图  -  s 筛选结果  -  q 返回上级  :")
    print(":  输入会话编号分析会话用时信息(例 0,1,3-5)  :")
    print("::::::::::::::::::::::::::::::::::::::::::::::")
    设置CMD颜色(白色)


def 显示_选择TCP会话中交互编号(L_交互信息):
    设置CMD颜色(暗天蓝色)
    N = 0
    print("------------------------------------------------------------------------------------------------------------")
    print("|【TCP】会话中交互信息")
    print("| 编号    请求Ack  包数     字节     响应Ack  包数     字节  TCP会话")
    print("------------------------------------------------------------------------------------------------------------")
    for i in L_交互信息:
        请求ACK, 请求Packets, 请求Bytes, 响应ACK, 响应Packets, 响应Bytes, TCP会话 = i
        print(f"| {N:<4} {请求ACK:10} {请求Packets:5} {请求Bytes:8}  {响应ACK:10} {响应Packets:5} {响应Bytes:8}  {TCP会话}")
        N += 1
    print("------------------------------------------------------------------------------------------------------------")
    print(f"| TCP会话总数 {N}")
    print("------------------------------------------------------------------------------------------------------------")
    设置CMD颜色(金色)
    print(":::::::::::::::::::::::::::::::::::::::")
    print(":  q 返回上级                         :")
    print(":  选择TCP会话中交互编号(例 0,1,3-5)  :")
    print(":::::::::::::::::::::::::::::::::::::::")
    设置CMD颜色(白色)


def 显示_选中TCP会话信息(L_会话编号, L_TCP_SESSION_KEY, D_TCP_PACKET):
    设置CMD颜色(红色)
    可用编号范围 = [i for i in range(0, len(L_TCP_SESSION_KEY))]
    L_可用会话编号 = []
    print("----------------------------------------------------------------------------")
    print("| 已选【TCP】会话")
    print("----------------------------------------------------------------------------")
    print("| 编号          源地址:端口              目的地址:端口   Bytes=>  |  <=Bytes")
    print("----------------------------------------------------------------------------")
    for n in L_会话编号:
        if n in 可用编号范围:
            SIP,SPORT,DIP,DPORT = L_TCP_SESSION_KEY[n]
            方向1Bytes = D_TCP_PACKET[(SIP,SPORT,DIP,DPORT)]['Bytes_Len']
            if (DIP,DPORT,SIP,SPORT) in D_TCP_PACKET:   # 判断是否有反方向流量
                方向2Bytes = D_TCP_PACKET[(DIP,DPORT,SIP,SPORT)]['Bytes_Len']
            else:
                方向2Bytes = 0
            print(f"| {n:<4} {SIP:>15}:{SPORT:<5}  <=> {DIP:>15}:{DPORT:<5}  {方向1Bytes:<9} {方向2Bytes:9}")
            L_可用会话编号.append(n)
        else:
            print(f"| {n:<4} 编号不存在,忽略")
    print("----------------------------------------------------------------------------")
    设置CMD颜色(白色)
    return(L_可用会话编号)


def 显示_选中UDP会话信息(L_会话编号, L_UDP_SESSION_KEY):
    设置CMD颜色(暗天蓝色)
    可用编号范围 = [i for i in range(0, len(L_UDP_SESSION_KEY))]
    L_可用会话编号 = []
    print("-------------------------------------------------------------")
    print("| 已选【UDP】会话编号")
    for n in L_会话编号:
        if n in 可用编号范围:
            print(f"| {n}\t{L_UDP_SESSION_KEY[n]}")
            L_可用会话编号.append(n)
        else:
            print(f"| {n}\t编号不存在,忽略")
    print("-------------------------------------------------------------")
    设置CMD颜色(白色)
    return(L_可用会话编号)


def 显示_选中交互信息(L_交互信息, L_交互编号):
    设置CMD颜色(红色)
    可用交互编号范围 = [i for i in range(0, len(L_交互信息))]
    L_可用交互编号 = []
    print("------------------------------------------------------------------------------------------------------------")
    print("| 已选交互编号")
    print("| 编号    请求Ack  包数     字节     响应Ack  包数     字节  TCP会话")
    print("------------------------------------------------------------------------------------------------------------")
    for n in L_交互编号:
        if n in 可用交互编号范围:
            请求ACK, 请求Packets, 请求Bytes, 响应ACK, 响应Packets, 响应Bytes, TCP会话 = L_交互信息[n]
            #print(f"| {n}\t{L_交互信息[n]}")
            print(f"| {n:<4} {请求ACK:10} {请求Packets:5} {请求Bytes:8}  {响应ACK:10} {响应Packets:5} {响应Bytes:8}  {TCP会话}")
            L_可用交互编号.append(n)
        else:
            print(f"| {n:<4} 编号不存在,忽略")
    print("------------------------------------------------------------------------------------------------------------")
    设置CMD颜色(白色)
    return(L_可用交互编号)



## 交互运行
def RUN(LOG_DIR):
    while 1:
        设置CMD颜色(金色)
        选择日志等级 = input(选择日志等级_TEXT)
        设置CMD颜色(白色)
        if 选择日志等级 == '0':
            Log.setLevel(logging.DEBUG)
        elif 选择日志等级 == '1':
            Log.setLevel(logging.INFO)
        elif 选择日志等级 == '2':
            Log.setLevel(logging.WARNING)
        elif 选择日志等级 == '3':
            Log.setLevel(logging.ERROR)
        elif 选择日志等级 == '4':
            Log.setLevel(logging.CRITICAL)
        elif 选择日志等级 == 'q':
            break
        else:
            print("【W】请重新选择")
            continue
        
        while 1:
            UserIP = ''
            UserPort = 0
            设置CMD颜色(金色)
            选择过滤规则 = input(选择过滤规则_TEXT)
            设置CMD颜色(白色)
            if 选择过滤规则 == '0':
                匹配内容 = 'IP'
                UserIP = input("  匹配IP: ")
            elif 选择过滤规则 == '1':
                匹配内容 = 'PORT'
                UserPort = input("  匹配PORT: ")
                UserPort = int(UserPort)
            elif 选择过滤规则 == '2':
                匹配内容 = 'IP+PORT'
                UserIP = input("  匹配IP: ")
                UserPort = input("  匹配PORT: ")
                UserPort = int(UserPort)
            elif 选择过滤规则 == '3':
                匹配内容 = 'ALL'
            elif 选择过滤规则 == 'q':
                break
            else:
                print("【W】请重新选择")
                continue
            设置CMD颜色(红色)
            print(f"  选择过滤规则: {选择过滤规则} 匹配: {匹配内容}")
            设置CMD颜色(白色)
            ## 每个会话解析为TCP会话信息字典
            D_TCP_ALL, D_TIME_PACKET, D_TCP_PACKET, D_UDP_ALL, D_UDP_PACKET = 提取PCAP中指定TCP会话数据(PCAP_File_OR_Dir, SaveDir, 匹配内容, UserIP, UserPort)
            L_TCP_SESSION_KEY = TCP会话Key去重(D_TCP_ALL)
            L_UDP_SESSION_KEY = UDP会话Key去重(D_UDP_ALL)
            设置CMD颜色(绿色)
            print(f"  TCP会话数量: {len(L_TCP_SESSION_KEY)}")
            print(f"  UDP会话数量: {len(L_UDP_SESSION_KEY)}")
            设置CMD颜色(白色)
    
            ## 循环多次查看分析结果
            while 1:
                匹配字符串 = ''      # 初始化筛选匹配字符串
                设置CMD颜色(金色)
                选择TCP或UDP = input(选择TCP或UDP_TEXT).lower()   # 输入结果转成小写字母
                设置CMD颜色(白色)
                if 选择TCP或UDP == 'q':
                    print("返回上级")
                    break
                elif 选择TCP或UDP == 't':
                    while 1:
                        ## 选择会话编号或操作指令
                        if '匹配字符串' not in locals():     # 判断变量是否存在
                            匹配字符串 = ''
                        显示_选择TCP会话编号(L_TCP_SESSION_KEY, D_TCP_PACKET, 匹配字符串)
                        选择会话编号或操作指令 = input("【3】选择要分析的会话编号: ")
                        if 选择会话编号或操作指令 == 'q':
                            print("返回上级")
                            break
                        elif 选择会话编号或操作指令 == 's':
                            匹配字符串 = input("匹配字符串: ")
                        elif 选择会话编号或操作指令 == 'x':
                            过滤出的包数据画流量图(D_TCP_PACKET, D_TIME_PACKET)
                        else:
                            ## 检查用户输入
                            L_会话编号 = 解析用户输入的编号(选择会话编号或操作指令)
                            L_可用会话编号 = 显示_选中TCP会话信息(L_会话编号, L_TCP_SESSION_KEY, D_TCP_PACKET)
                            while 1:
                                设置CMD颜色(金色)
                                TCP选择功能 = input(TCP选择功能_TEXT)
                                设置CMD颜色(白色)
                                if TCP选择功能 == 'q':
                                    print("返回上级")
                                    break
                                elif TCP选择功能 == '0':                                                            ## 0 TCP会话交互过程用时分析(无图)
                                    图 = 0
                                    TCP会话交互过程用时分析(L_可用会话编号, L_TCP_SESSION_KEY, D_TCP_ALL, 图, 选择日志等级)
                                elif TCP选择功能 == '1':                                                            ## 1 TCP会话交互过程用时分析(画图)
                                    图 = 1
                                    TCP会话交互过程用时分析(L_可用会话编号, L_TCP_SESSION_KEY, D_TCP_ALL, 图, 选择日志等级)
                                elif TCP选择功能 == '2':                                                            ## 2 选中TCP会话导出为PCAP文件
                                    D_SESSION_INFO = {'TCP':[], 'UDP':[]}
                                    for i in L_可用会话编号:
                                        SIP,SPORT,DIP,DPORT = L_TCP_SESSION_KEY[i]
                                        D_SESSION_INFO['TCP'].append(L_TCP_SESSION_KEY[i])
                                        D_SESSION_INFO['TCP'].append((DIP,DPORT,SIP,SPORT))
                                    设置CMD颜色(绿色)
                                    提取PCAP中指定会话数据另存为独立PCAP(PCAP_File_OR_Dir, SaveDir, D_SESSION_INFO)
                                    设置CMD颜色(白色)
                                elif TCP选择功能 == '3':                                                            ## 3 全部TCP会话导出为PCAP文件
                                    print("【I】请返回上级的上级使用 [t2p 每个TCP会话导出为单独PCAP文件] 功能")
                                elif TCP选择功能 == '4':                                                            ## 4 显示会话中每次交互信息
                                    while 1:
                                        print("显示会话中每次交互信息")
                                        L_交互信息 = 显示会话中每次交互信息(L_可用会话编号, L_TCP_SESSION_KEY, D_TCP_ALL, 选择日志等级)
                                        设置CMD颜色(暗天蓝色)
                                        显示_选择TCP会话中交互编号(L_交互信息)
                                        设置CMD颜色(白色)
                                        
                                        选择交互编号或操作指令 = input("【5】选择要分析的交互编号: ")
                                        if 选择交互编号或操作指令 == 'q':
                                            print("返回上级")
                                            break
                                        else:
                                            ## 解析用户输入
                                            L_交互编号 = 解析用户输入的编号(选择交互编号或操作指令)
                                            L_可用交互编号 = 显示_选中交互信息(L_交互信息, L_交互编号)
                                            while 1:
                                                设置CMD颜色(金色)
                                                TCP交互选择功能 = input(TCP交互选择功能_TEXT).lower()
                                                设置CMD颜色(白色)
                                                if TCP交互选择功能 == 'q':
                                                    print("返回上级")
                                                    break
                                                elif TCP交互选择功能 == 'tcp':   ## TCP会话交互数据解析
                                                    设置CMD颜色(绿色)
                                                    TCP会话交互数据解析(L_可用交互编号, L_交互信息, PCAP_File_OR_Dir, D_TCP_ALL, ROOT_DIR)
                                                    设置CMD颜色(白色)
                                                elif TCP交互选择功能 == 'udp':   ## UDP会话交互数据解析
                                                    print("【W】未实现")
                                                elif TCP交互选择功能 == 'http':  ## 尝试以HTTP协议解析
                                                    设置CMD颜色(绿色)
                                                    尝试以HTTP协议解析(L_可用交互编号, L_交互信息, PCAP_File_OR_Dir, D_TCP_ALL, ROOT_DIR)
                                                    设置CMD颜色(白色)
                                                elif TCP交互选择功能 == 'https': ## 尝试以HTTPS协议解析
                                                    print("【W】未实现")
                                                elif TCP交互选择功能 == 'mysql': ## 尝试以MySQL协议解析
                                                    print("【W】未实现")
                                                else:
                                                    print("【W】请重新选择")
                                                    continue
                                else:
                                    print("【W】请重新选择")
                                    continue
                elif 选择TCP或UDP == 'u':
                    while 1:
                        ## 选择会话编号或操作指令
                        if '匹配字符串' not in locals():     # 判断变量是否存在
                            匹配字符串 = ''
                        显示_选择UDP会话编号(L_UDP_SESSION_KEY, D_UDP_PACKET, 匹配字符串)
                        选择会话编号或操作指令 = input("【3】选择要分析的会话编号: ")
                        if 选择会话编号或操作指令 == 'q':
                            print("返回上级")
                            break
                        elif 选择会话编号或操作指令 == 's':
                            匹配字符串 = input("匹配字符串: ")
                        else:
                            ## 检查用户输入
                            L_会话编号 = 解析用户输入的编号(选择会话编号或操作指令)
                            L_可用会话编号 = 显示_选中UDP会话信息(L_会话编号, L_UDP_SESSION_KEY)
                            while 1:
                                UDP选择功能 = input(UDP选择功能_TEXT)
                                if UDP选择功能 == 'q':
                                    print("返回上级")
                                    break
                                elif UDP选择功能 == '0':
                                    D_SESSION_INFO = {'TCP':[], 'UDP':[]}
                                    for i in L_可用会话编号:
                                        SIP,SPORT,DIP,DPORT = L_UDP_SESSION_KEY[i]
                                        D_SESSION_INFO['UDP'].append(L_UDP_SESSION_KEY[i])
                                        D_SESSION_INFO['UDP'].append((DIP,DPORT,SIP,SPORT))
                                    设置CMD颜色(绿色)
                                    提取PCAP中指定会话数据另存为独立PCAP(PCAP_File_OR_Dir, SaveDir, D_SESSION_INFO)
                                    设置CMD颜色(白色)
                                else:
                                    print("【W】请重新选择")
                                    continue
                elif 选择TCP或UDP == 't2p':
                    设置CMD颜色(绿色)
                    print("【W】数量多时比较耗时且执行不稳定")
                    D_SESSION_INFO = {'TCP':[], 'UDP':[]}
                    for i in D_TCP_ALL:                    # 取全部TCP
                        D_SESSION_INFO['TCP'].append(i)    # 取全部TCP
                    提取PCAP中指定会话数据另存为独立PCAP(PCAP_File_OR_Dir, SaveDir, D_SESSION_INFO)
                    设置CMD颜色(白色)
                elif 选择TCP或UDP == 'u2p':
                    设置CMD颜色(绿色)
                    print("【W】数量多时比较耗时且执行不稳定")
                    D_SESSION_INFO = {'TCP':[], 'UDP':[]}
                    for i in D_UDP_ALL:                    # 取全部UDP
                        D_SESSION_INFO['UDP'].append(i)    # 取全部UDP
                    提取PCAP中指定会话数据另存为独立PCAP(PCAP_File_OR_Dir, SaveDir, D_SESSION_INFO)
                    设置CMD颜色(白色)
                else:
                    print("【W】请重新选择")
                    continue


## 设置保存文件的根目录
ROOT_DIR = 'A:\\TEST\\'
if os.path.exists(ROOT_DIR):
    if os.path.isdir(ROOT_DIR):
        print(f"已经存在ROOT_DIR={ROOT_DIR}但是文件")
    else:
        print(f"已经存在ROOT_DIR={ROOT_DIR}但是文件")
        exit()
else:
    print(f"新建根目录 ROOT_DIR={ROOT_DIR}")
    os.makedirs(ROOT_DIR)

## 设置日志
LOG_DIR = ROOT_DIR + 'LOG\\'    # 设置日志文件存放目录
if not os.path.exists(LOG_DIR):
    os.makedirs(LOG_DIR)
Log = logging.getLogger("Main")
LOG_FILE = LOG_DIR + time.strftime('%Y-%m-%d_%H%M%S')+'.log'
formatter = logging.Formatter('%(message)s')                    # 指定logger输出格式
file_handler = logging.FileHandler(LOG_FILE,encoding='UTF8')    # 日志文件路径和编码
file_handler.setFormatter(formatter)                            # 可以通过setFormatter指定输出格式
Log.addHandler(file_handler)
#Log.setLevel(logging.DEBUG)                                    # 日志等级 DEBUG INFO WARNING ERROR CRITICAL 在交互时设置
## 日志内容同时在终端显示
console = logging.StreamHandler()
console.setLevel(logging.INFO)                 # INFO级日志打印到终端
Log.addHandler(console)

## 设置PCAP文件或目录位置
#PCAP_File_OR_Dir = ROOT_DIR + 'test.pcap'     # PCAP文件位置
PCAP_File_OR_Dir = ROOT_DIR                    # PCAP目录位置
SaveDir = ROOT_DIR + 'PCAP_Save\\'             # 拆分PCAP文件时存放的目录位置
视角 = 'S'


设置CMD颜色(暗天蓝色)
print(ReadMe)
设置CMD颜色(白色)
RUN(LOG_DIR)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 从pcap包内提取TCP会话并重组是一种网络分析的常见任务,它可以帮助我们理解和分析网络流量。 首先,我们需要使用适当的工具打开pcap包文件,如Wireshark或tshark。这些工具可以读取pcap文件并显示其中的网络流量。 接下来,我们需要筛选出TCP协议的网络流量,可以通过过滤器来实现。在Wireshark中,我们可以使用表达式"tcp"来过滤出所有的TCP流量。 一旦我们得到了TCP流量,我们需要根据IP地址和端口号来识别和重组TCP会话。每一对IP地址和端口号组合代表一次TCP会话。在Wireshark中,我们可以使用右键单击一个TCP流,然后选择"Follow",再选择"TCP Stream"来查看完整的重组会话。 如果我们想以程序的方式从pcap包中提取并重组TCP会话,我们可以使用特定的编程库或工具,例如Scapy、dpkt等。这些工具提供了API和函数,可以让我们编写代码来读取pcap文件,提取TCP流量,并进行会话重组。 通过以上步骤,我们就可以从pcap包中提取并重组TCP会话,以便于进一步的分析和调查。这对于网络管理员、安全分析师或研究人员来说,都是非常有价值的工作。 ### 回答2: 从pcap包内提取TCP会话并重组是一种常见的网络流量分析技术,用于对网络通信进行深入研究和分析。下面是一个简单的步骤,用于从pcap包中提取TCP会话并重组: 1. 使用网络流量分析工具(如Wireshark)打开pcap文件,并过滤出TCP流量。 2. 根据TCP协议的特性,使用源端口和目的端口以及IP地址信息来区分不同的TCP会话。 3. 对于每个TCP会话,按照TCP协议的顺序对包进行排序,确保按照传输顺序进行重组。 4. 通过比较每个TCP包的序列号和确认号,以及TCP首部中的ACK标志位来确定数据包的重组顺序。 5. 将重组后的TCP包按照正确的顺序进行拼接,以恢复原始的TCP会话数据。 6. 对重组后的TCP会话数据进行进一步分析,可以提取出TCP会话中的各种信息,如源IP和目的IP、源端口和目的端口、连接建立时间、连接关闭时间、数据传输的总大小等。 通过从pcap包内提取TCP会话并重组,可以更好地理解网络通信的细节,如数据传输顺序、传输延迟、丢包情况等。同时,还可以利用这些数据进行网络性能分析、故障排除和安全检测等工作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值