PYTHON 解码 IP 层

news2024/11/25 0:37:16

PYTHON 解码 IP 层

      • 引言
      • 1.编写流量嗅探器
      • 1.1 Windows 和 Linux 上的包嗅探
      • 2.解码 IP 层
      • 2.1 struct 库
      • 3.编写 IP 解码器
      • 4.解码 ICMP
      • 5.总结

作者:高玉涵

时间:2023.7.12

环境:Windows 10 专业版 22H2,Python 3.10.4

引言

IP 是 TCP/IP 协议族中最为核心的协议。所有的 TCP、UDP、ICMP 及 IGMP 数据都以 IP 数据报格式传输。图 1-1 所示为典型的 IPv4 头结构。但里面的信息是以二进制形式封装的,许多刚开始接触 TCP/IP 的人(包括我)很难直接读懂。这里我们可以利用 PYTHON 这门简洁且强大的语言来捕获和解码网络数据包的 IP 头部分,结合原理学习网络基础知识,往往能达到事半功倍的效果。

在这里插入图片描述

(图 1-1)

1.编写流量嗅探器

网络上有如 Wireshark 这样现成的嗅控工具,但不管怎样,学会自己编写简单的嗅探器来浏览和解码流量仍然很有好处。你不仅能学到一些新的 PYTHON 技巧,更加深对底层网络实现的理解。这里我将使用原始 socket 来读/写原始 IP 头或 ICMP 头等底层信息。

1.1 Windows 和 Linux 上的包嗅探

在 Windows 和 Linux 上操作原始 socket 的步骤不太相同,但嗅探工具需要具备足够的灵活性以便部署到不同平台。考虑到这一点,我们在创建 socket 对象后会检测系统环境。如果是 Windows 系统,就需要通过 socket 输入/输出控制(IOCTL)机制来设定一些标志,启用网卡的混杂模式。IOCTL 是用户程序和系统内核组件通信的一种方式,更多细节可以参考[百度百科](ioctl_百度百科 (baidu.com))中的解释。

现在我们来写第一个例子——一个简单的原始 socket 嗅探器,它只会读取一个数据包,然后直接退出:

import socket
import os

# 监听的主机
HOST = '192.168.1.105'

def main():
    # 创建原始 socket, 绑定公共接口
    if os.name == 'nt':
        socket_protocol = socket.IPPROTO_IP
    else:
        socket_protocol = socket.IPPROTO_ICMP
    
    sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW,
                            socket_protocol)
    sniffer.bind((HOST, 0))
    # 捕获 IP 头
    sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

    # 开启混杂模式
    if os.name == 'nt':
        sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

    # 读数据包
    print(sniffer.recvfrom(65565))

    # 关闭混杂模式
    if os.name == 'nt':
        sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)


if __name__=='__main__':
    main()

先把 HOST 变量设定成本机 IP 地址,然后构建一个 socket 对象,传入嗅探网卡数据所需的参数。这里 Windows 和 Linux 的区别是,前者允许我们嗅探任何协议的所有流入数据,而后者强制我们指定一个协议来嗅探,这里指定的是 ICMP。注意,你需要拥有 Windows 的管理员权限或 Linux 的 root 权限才能启用网卡的混杂模式。启用混杂模式后,就能嗅探到流经网卡的所有数据包,包括那些不归我们接收的数据包。接着,修改 socket 的设置,让它抓包时包含 IP 头。下一步,判断程序是不是运行在 Windows 上,如果是,就额外发送一条 IOCTL 消息启用网卡的混杂模式。现在我们就做好嗅探数据的准备了。在本例中只输出了原始数据包的全部内容,没有实际解码里面的信息,因为目前我们只想确认核心代码都能正常工作。嗅探完一个数据包后,我们会再次检测现在是不是在 Windows 平台,关闭网卡的混杂模式,然后退出。

Windows 命令提示符窗口,运行以下命令:

python sniffer.py
(b'E\x00\x004\x88\x8c@\x00\x80\x06\x00\x00\xc0\xa8\x01i\xc0\xa8\x1f\xa5\xd5\x8d\x1e\x005\t\xed\xb7\x00\x00\x00\x00\x80\x02\xfa\xf0\xa2\x85\x00\x00\x02\x04\x05\xb4\x01\x03\x03\x08\x01\x01\x04\x02', ('192.168.1.105', 0))

你会看到类似于下面内容的乱七八糟的输出。说实话,嗅探一个数据包用处不大,所以我们添加一些新功能,并解码其中的信息。

2.解码 IP 层

如果你分析过网络实际的数据包,应该能明白为什么我们需要对数据解码。图 1-1 所示。我们需要解码整个 IP 头(除了可选参数部分),并提取协议类型、源 IP 地址和目的 IP 地址等信息。这就意味着要直接跟二进制数据打交道,因此我们要找出一套用 PYTHON 分割 IP 头各个数据段的方案。

在 PYTHON 中,要把外来的二进制数据分解成数据结构有不少办法。比如,你可以用 ctypes 库或 struct 库来定义所需的数据结构。 在网上浏览各种开源工具时,你会发现这两种方法各有不少项目在用。本节会使用 struct 库读取 IPv4 头,因为它更专注于操作二进制数据。

2.1 struct 库

struct 库提供了一些格式字符,用来定义二进制数据的结构。在下面的示例中,我们将定义一个 IP 类来存储 IP 头信息。

import ipaddress
import struct

class IP:
    def __init__(self, buff=None):
        header = struct.unpack('<BBHHHBBH4s4s', buff)
        self.ver = header[0] >> 4
        self.ih1 = header[0] & 0xF
        
        self.tos = header[1]
        self.len = header[2]
        self.id = header[3]
        self.offset = header[4]
        self.ttl = header[5]
        self.protocol_num = header[6]
        self.sum = header[7]
        self.src = header[8]
        self.dst = header[9]

        # 人类可读 IP 地址
        self.src_address = ipaddress.ip_address(self.src)
        self.dst_address = ipaddress.ip_address(self.dst)

        # 协议常量
        self.protocol_map = {1: "ICMP", 6: "TCP", 17: "UDP"}

第一个格式字符(示例中的 < )永远都是用来表示数据的字节序的。C 数据类型一般是按照设备中的原生格式和字节序来存储的。在这个例子中,我们使用的是 Windows 系统(x64架构),它使用的是小端序(little-endian)。在小端序设备上,低位字节会被放在较低的内存地址上,高位字节会被放在较高的内存地址上。

接下来的格式字符是用来表示 IP 头的各部分的。struct 库提供了若干格式字符。对于 IP 头来说,我们只需要用到的只有 B(1字节,unsigned char)、H(2字节,unsigned short)和 s(一个字节数组,数组长度需要另外指定,比如 4s 就代表为 4 字节的字节数组)。留意一下我们的格式字符串和图 1-1 中所示的 IP 头结构是如何一一对应的。

在 struct 库里,不存在对应 4 位二进制位组成的数据格式字符,所以我们需要额外做一些操作,把 ver 和 hdrlen 变量从 IP 头的第一个字节里提取出来。

对于 IP 头的第一个字节,我们只想取高位作为 ver 的值。取某字节高位的常规方法是将其向右位移 4 位,相当于在该字节的开头填 4 个 0,把其尾部的 4 位挤出去。这样我们就得到了原字节的高位值。这一行 PYTHON 代码基本上就是做了如下操作:

0 1 0 1 0 1 1 0  >> 4
----------------
0 0 0 0 0 1 0 1

我们想把低位(或者说原字节的最后 4 个二进制位)填进 hdrlen 里,取某个字节低位的常规方法是将其与数字 0xF(00001111)进行按位与运算。它利用了 0 AND 1 = 0 的特性(0 代表假,1 代表真)。想要 AND 表达式为真,表达式两边都必须为真。所以这个操作相当于删除前 4 个二进制位,因为任何数 AND 0 都得 0;它保持了最后 4 个二进制位不变,因为任何数 AND 1 还是原数字。所以,基本上这一行 PYTHON 代码做的就是如下操作:

    0 1 0 1 0 1 1 0
AND 0 0 0 0 1 1 1 1
--------------------
    0 0 0 0 0 1 1 0

解析 IP 头其实不需要你掌握太多位运算知识,但在这种需要位移操作的情况下,解析二进制数据需要费点心思。其他情况(比如解析 ICMP 消息)大多没这么麻烦:ICMP 消息里的每一个字段位数都是 8 的整数倍,struct 的格式字符位数也都是 8 的整数倍。在图 2-1 所示的 Echo Reply ICMP 消息结构中,你可以看到 ICMP 头的每个字段都可以用一个格式字符组合来表示(BBHHH)。
在这里插入图片描述

图 2-1

因此,解析 ICMP 头结构的办法非常简单,只要为前两个成员变量分配 1 字节,为后三个成员变量分配 2 字节就可以了。

class ICMP:
    def __init__(self, buff):
        header = struct.unpack('<BBHHH', buff)
        self.type = header[0]
        self.code = header[1]
        self.sum = header[2]
        self.id = header[3]
        self.seq = header[4]

3.编写 IP 解码器

现在,把刚才设计的 IP 头解码代码写下来,文件名就叫 sniffer_ip_header_decode.py,文件内容如下所示:

import ipaddress
import os
import socket
import struct
import sys


class IP:
    def __init__(self, buff=None):
        header = struct.unpack('<BBHHHBBH4s4s', buff)
        self.ver = header[0] >> 4
        self.ih1 = header[0] & 0xF
        
        self.tos = header[1]
        self.len = header[2]
        self.id = header[3]
        self.offset = header[4]
        self.ttl = header[5]
        self.protocol_num = header[6]
        self.sum = header[7]
        self.src = header[8]
        self.dst = header[9]

        # 人类可读 IP 地址
        self.src_address = ipaddress.ip_address(self.src)
        self.dst_address = ipaddress.ip_address(self.dst)

        # 常用服务映射到名字
        self.protocol_map = {1: "ICMP", 6: "TCP", 17: "UDP"}

        try:
            self.protocol = self.protocol_map[self.protocol_num]
        except Exception as e:
            print('%s No protocol for %s' % (e, self.protocol_num))
            self.protocol = str(self.protocol_num)


def sniff(host):
    if os.name == 'nt':
        socket_protocol = socket.IPPROTO_IP
    else:
        socket_protocol = socket.IPPROTO_ICMP

    sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, 
                            socket_protocol)
    sniffer.bind((host, 0))
    sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

    if os.name == 'nt':
        sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

    try:
        while True:
            raw_buffer = sniffer.recvfrom(65565)[0]
            # 取前 20 个字节创建 IP 头
            ip_header = IP(raw_buffer[0:20])
            # 打印检测的 IP 和协议
            print('Protocol: %s %s -> %s' % (ip_header.protocol,
                                             ip_header.src_address,
                                             ip_header.dst_address))
    except KeyboardInterrupt:
        if os.name == 'nt':
            sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
        sys.exit()

if __name__ == '__main__':
    if len(sys.argv) == 2:
        host = sys.argv[1]
    else:
        host = '192.168.1.105'
    sniff(host)

首先,我们写下刚才的 IP 类,它定义了一个 PYTHON 结构,可以把数据包的前 20 字节映射到一个便于读/写的 IP 头对象里。如你所见,我们辨识出的所有字段都和标准的 IP 头结构完美契合。然后整理数据,将其输出为人类可读的形式,展示目前的通信协议和通信双方的 IP 地址。用上了新打造的 IP 头结构后,我们把抓包的逻辑改成持续抓包和解析。每读入一个包,就将它的前 20 字节转换成 IP 头对象。接着,只需要把抓取的信息打印到屏幕上就可以了。

我们来测试刚才写的代码,看看能从原始数据包中提取出什么样的信息。建议在 Windows 设备上测试这些代码,因为这样就能同时看到 TCP、UDP 和 ICMP 等协议的数据,易于进行一些简便的测试(比如直接打开浏览器浏览网页)。如果你不得不使用 Linux 系统,那就做一次 ping 测试吧。

python sniffer_ip_header_decode.py
Protocol: UDP 192.168.1.104 -> 239.255.255.250
Protocol: UDP 192.168.1.1 -> 239.255.255.250
Protocol: UDP 192.168.1.1 -> 239.255.255.250
Protocol: UDP 192.168.1.1 -> 239.255.255.250
Protocol: UDP 192.168.1.1 -> 239.255.255.250
Protocol: UDP 192.168.1.1 -> 239.255.255.250
Protocol: UDP 192.168.1.1 -> 239.255.255.250
Protocol: UDP 192.168.1.1 -> 239.255.255.250
Protocol: UDP 192.168.1.1 -> 239.255.255.250
Protocol: UDP 192.168.1.1 -> 239.255.255.250
Protocol: UDP 192.168.1.1 -> 239.255.255.250
Protocol: UDP 192.168.1.104 -> 239.255.255.250
Protocol: TCP 192.168.1.105 -> 20.198.162.78

因为 Windows 是个挺“健谈”的系统,所以你很可能立即就看到了测试结果。接下来,我们要运用解码 IP 头的技术来解码 ICMP 消息。

4.解码 ICMP

现在我们已经可以完整解码数据包层的 IP 层,接下来还需要解码网络数据包触发的 ICMP 响应。不同的 ICMP 消息之间千差万别,但有三个字段是一定存在的:类型(type)、代码(code)和校验和(checksum)。类型和代码两个字段告诉接收者,接下来要接收的 ICMP 信息是什么类型的,也就指明了如何正确地解码里面的数据。

这里我们需要检查类型为3、代码为 3 的 ICMP 消息。类型为 3 表示目标不可达(Destination Unreachable),而代码为 3 表示导致目标不可达的具体原因是端口不可达(Port Unreachable)。图 4-1 展示的就是 Destination Unreachable 类型的 ICMP 消息结构。
在这里插入图片描述

图 4-1

可以看到,数据包开头的 8 个二进制位代表类型,其后的 8 个二进制位代表 ICMP 代码。这里注意一个有意思的细节:当一台主机发送出 ICMP 消息的时候,会把触发 ICMP 消息的原始数据包的 IP 头附在消息未尾。另外,为了确认这个 ICMP 消息是被我们触发的,还可以自定义 8 字节的特征数据放在 UDP 数据包的开头,然后与接收到的 ICMP 消息的最后 8 字节 进行对比。关于这个技巧常用于黑客扫描工具,本文我们只研究解码数据包,另,找机会再讨论。

文件名 sniffer_with_icmp.py,文件内容如下所示:

import ipaddress
import os
import socket
import struct
import sys

class IP:
    def __init__(self, buff=None):
        header = struct.unpack('<BBHHHBBH4s4s', buff)
        self.ver = header[0] >> 4
        self.ih1 = header[0] & 0xF
        
        self.tos = header[1]
        self.len = header[2]
        self.id = header[3]
        self.offset = header[4]
        self.ttl = header[5]
        self.protocol_num = header[6]
        self.sum = header[7]
        self.src = header[8]
        self.dst = header[9]

        # 人类可读 IP 地址
        self.src_address = ipaddress.ip_address(self.src)
        self.dst_address = ipaddress.ip_address(self.dst)

        # 常用服务映射到名字
        self.protocol_map = {1: "ICMP", 6: "TCP", 17: "UDP"}

        try:
            self.protocol = self.protocol_map[self.protocol_num]
        except Exception as e:
            print('%s No protocol for %s' % (e, self.protocol_num))
            self.protocol = str(self.protocol_num)


class ICMP:
    def __init__(self, buff):
        header = struct.unpack('<BBHHH', buff)
        self.type = header[0]
        self.code = header[1]
        self.sum = header[2]
        self.id = header[3]
        self.seq = header[4]


def sniff(host):
    if os.name == 'nt':
        socket_protocol = socket.IPPROTO_IP
    else:
        socket_protocol = socket.IPPROTO_ICMP

    sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, 
                            socket_protocol)
    sniffer.bind((host, 0))
    sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

    if os.name == 'nt':
        sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

    try:
        while True:
            raw_buffer = sniffer.recvfrom(65565)[0]
            # 取前 20 个字节创建 IP 头
            ip_header = IP(raw_buffer[0:20])
			# ICMP
            if ip_header.protocol == "ICMP":
                print('Protocol: %s %s -> %s' % (ip_header.protocol,
                                                 ip_header.src_address,ip_header.dst_address))
                print(f'Version:{ip_header.ver}')
                print(f'Header Lenght:{ip_header.ih1} TTL:{ip_header.ttl}')
                
                # 计算 ICMP 包开始位置
                offset = ip_header.ih1 * 4
                buf = raw_buffer[offset:offset + 8]
                # 创建 ICMP 对象
                icmp_header = ICMP(buf)
                print('ICMP -> Type: %s Code: %s\n' %
                      (icmp_header.type, icmp_header.code))
            
    except KeyboardInterrupt:
        if os.name == 'nt':
            sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
        sys.exit()


if __name__ == '__main__':
    if len(sys.argv) == 2:
        host = sys.argv[1]
    else:
        host = '192.168.1.105'
    sniff(host)

这段简单的代码在之前的 IP 结构下方又创建了一个 ICMP 结构。在负责接收数据包的主循环中,我们会判断接收到的数据包是否为 ICMP 数据包,然后计算出 ICMP 数据在原始数据包中的偏移,最后将数据按照 ICMP 结构进行解析,输出其中的类型(type)和代码(code)字段。IP 头的长度是基于 IP 头中的 ih1字段计算的,该字段记录了 IP 头中有多少个 32 位(4字节)长的数据块。所以只需要将这个字段乘 4,就能计算出 IP 头的大小,以及数据包中下一个网络层(这里指 ICMP)开始的位置。

Windows 命令提示符窗口,运行以下命令:

python sniffer_with_icmp.py

另,打开一个命令行窗口执行 ping 命令:

ping www.baidu.com

输出结果:

Protocol: ICMP 192.168.1.105 -> 47.254.33.193
Version:4
Header Lenght:5 TTL:128
ICMP -> Type: 8 Code: 0

Protocol: ICMP 47.254.33.193 -> 192.168.1.105
Version:4
Header Lenght:5 TTL:50
ICMP -> Type: 0 Code: 0

Protocol: ICMP 192.168.1.105 -> 47.254.33.193
Version:4
Header Lenght:5 TTL:128
ICMP -> Type: 8 Code: 0

这表明 ping (ICMP Echo)响应数据被正确地接收并解码了。

5.总结

通过上述举例。我的初衷只是为了让原本乏味枯燥的,网络相关理论知识变的有趣,为我们接下来深入学习起到“抛转引玉”的作用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/747017.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JWT的深入理解

1、JWT是什么 JWT&#xff08;JSON Web Token&#xff09;是一种开放标准&#xff08;RFC 7519&#xff09;&#xff0c;用于在不同实体之间安全地传输信息。它由三部分组成&#xff0c;即头部&#xff08;Header&#xff09;、载荷&#xff08;Payload&#xff09;和签名&…

获取QT界面坐标的各种方法

链接 ract() 获取rect所在部件的尺寸。 rect()返回的QRect对象可以用来做什么

openResty的Redis模块踩坑记录

OpenResty提供了操作Redis的模块&#xff0c;我们只要引入该模块就能直接使用。说是这样说&#xff0c;但是实践起来好像并不太顺利。 1.设置了密码的redis&#xff0c;lua业务逻辑中需要添加身份认证代码 网上很多资料、文章似乎都是没有设置redis密码&#xff0c;说来也奇怪…

JS区域滤镜

思路 简单一点的&#xff0c;像素点X坐标小于图宽1/3和大于2/3的点变灰&#xff0c;中间的点不变。 复杂的暂时不会搞。 原图 处理后 <html> <style> #canvas { width:100%; } </style> <body> <input id"file" type"file" …

python中的生成器(generator)

一、生成器 生成器是 Python 中非常有用的一种数据类型&#xff0c;它可以让你在 Python 中更加高效地处理大量数据。生成器可以让你一次生成一个值&#xff0c;而不是一次生成一个序列&#xff0c;这样可以节省内存并提高性能 二、实现generator的两种方式 python中的gener…

SAP从放弃到入门系列之WIP Batch(Work-in-Process ) -Part1

目录 一、 概述二、 系统配置三、 数据设置最后 ERP系统的复杂性并不单是架构设计和技术造成的&#xff0c;而是它所要支撑的业务场景&#xff0c;涉及行业越广泛越复杂软件功能越复杂&#xff0c;复杂的背后是业务实践沉淀和优化的流程。平时看着部分系统功能很复杂&#xff0…

47.判断类关键字 if else switch case default

目录 1 if 2 else 3 判断的嵌套 4 switch,case,default 4.1 基本使用 4.2 需要注意的点 1 if if后面的括号加表达式的内容&#xff0c;大括号中加入 条件为true 时要运行的代码 经测试如果我们将a的值设置为0&#xff0c;则不会弹出警告框 2 else 和if配合使用…

ubuntu netplan工具原理(网络配置、ip修改ip、固定ip)(NetworkManager)

https://netplan.io/ 文章目录 netplan工作原理netplan -h原翻译命令释义- help&#xff1a;显示netplan的帮助消息。- apply&#xff1a;将当前netplan配置应用到运行系统。示例命令&#xff1a;netplan apply --debug- generate&#xff1a;从/etc/netplan/*.yaml生成特定于后…

iOS开发 - NotificationService语音播报

iOS NotificationService语音播报 最近碰到个接收到推送要实现语音播报的需求&#xff0c;需要后台推送通知&#xff0c;APP客户端收到通知之后语音播放&#xff1a;“您的账户收到一笔巨款”的功能。 因为工程之前已经集成了极光推送服务。这里直接使用Notification Service…

【科研绘图】MacOS系统OmniGraffle实用指南

用过不少绘图软件&#xff0c;包括Visio (only for Windows)、ProcessOn、draw.io等主流软件&#xff0c;然后换Mac后尝试了实验室在用的OmniGraffle&#xff0c;才第一次感受到了绘图软件的人性化和强大&#xff01; 实用操作总结 按住Shift后调整元素位置或调整线段&#x…

使用STM32 再实现感应开关盖垃圾桶

硬件介绍 SG90舵机 如上图所示的舵机SG90&#xff0c;橙线对应PWM信号&#xff0c;而PWM波的频率不能太高&#xff0c;大约50Hz&#xff0c;即周期0.02s&#xff0c;20ms左右。 在20ms的周期内&#xff0c;高电平占多少秒和舵机转到多少度的关系如下&#xff1a; 0.5ms-----0度…

性能测试持续学习 Docker 新建镜像,启动 POD

目录 前言&#xff1a; 1、构建镜像 2、使用已有镜像启动 Pod 前言&#xff1a; 在进行性能测试时&#xff0c;持续学习Docker的使用可以帮助测试团队更好地管理测试环境和资源。通过使用Docker&#xff0c;可以轻松创建和管理测试环境的镜像&#xff0c;并通过启动POD来快…

win32汇编资源编译RC2103错误 - end of file in string literal

现在有如下的一个资源rc文件&#xff0c; #include <resource.h>#define DLG_MAIN 1 #define IDC_COUNT 101DLG_MAIN DIALOG 50, 50, 113, 40 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "例子" FONT 9, "宋体&…

什么是端口号【图解TCP/IP(笔记十一)】

文章目录 端口号端口号定义根据端口号识别应用通过IP地址、端口号、协议号进行通信识别端口号如何确定端口号与协议 TCP具有代表性的知名端口号UDP具有代表性的知名端口号 端口号 端口号定义 数据链路和IP中的地址&#xff0c;分别指的是MAC地址和IP地址。前者用来识别同一链…

【论文笔记】Guided Skill Learning and Abstraction for Long-Horizon Manipulation

【论文笔记】Guided Skill Learning and Abstraction for Long-Horizon Manipulation 更多笔记&#xff08;在耕&#xff09;&#xff1a;这里 文章目录 【论文笔记】Guided Skill Learning and Abstraction for Long-Horizon ManipulationAbstractI. INTRODUCTIONII. RELATED…

flashFXP 提示: 数据 Socket 错误: 连接已超时 阿里云 安全组

flashFXP 提示: 数据 Socket 错误: 连接已超时的解决办法, 公司搬家后,ip换了.ftp进不去了.当然要查一下服务器防火墙,ftp软件上的端口是否开放.比如自定义的端口为21221,则需要在安全组中开放这个端口.但发现没问题. 同时重新修改了ftp用户的密码.发现也无效 网上有人说,传输模…

MySQL每日一练:多表查询——连接查询、子查询

目录 1、首先创建员工表emp和部门表dept&#xff1a; dept表&#xff1a; emp表&#xff1a; 2、插入数据&#xff1a; dept表&#xff1a; emp表&#xff1a; 3、 按条件查找 1、首先创建员工表emp和部门表dept&#xff1a; dept表&#xff1a; create table dept (…

量子纠缠:超越时空的连接

亲爱的读者&#xff0c; 欢迎回到量子力学系列文章。在前几篇文章中&#xff0c;我们介绍了量子力学的起源、基本概念&#xff0c;以及叠加态和超级定位的奇特现象。今天&#xff0c;我们将探索量子力学中最为神奇和令人惊叹的现象之一&#xff1a;量子纠缠。 量子纠缠是一种特…

opencv读取图像数据并修改通道转变内存连续

opencv读取图像数据并修改通道转变内存连续

试题小结3

项目和项目之间的通信 两个java项目&#xff0c;他们之间进行信息的通信 前提&#xff1a;必须知道要通信的java项目&#xff08;接收请求方&#xff09;的服务器的IP地址和访问路径。 其实两个java项目之间的通信还是使用HTTP的请求。主要有两种方式&#xff1a; ①使用ap…