UNIX网络编程卷一 学习笔记 第十九章 密钥管理套接字

news2025/1/20 5:53:39

随着IP安全体系结构(IPsec)的引入,密钥加密和认证密钥的管理越来越需要一套标准机制。RFC 2367介绍了一个通用密钥管理API,可用于IPsec和其他网络安全服务,该API创建了一个新协议族,即PF_KEY域,这这个密钥管理域中,只支持原始套接字。

在大多系统上,常值AF_KEY被定义成与PF_KEY有相同的值,但RFC 2367明确密钥管理套接字必须用PF_KEY常值。

打开原始密钥管理套接字需要权限,在权限按需分割的系统上,对于打开密钥管理套接字这样的操作会有一个单独的权限,在普通Unix系统上,密钥管理套接字只有超级用户能打开。

IPsec基于安全关联(SA,Security Association)为分组提供安全服务。SA描述了源地址与目的地址(加上可选的传输协议和端口)、机制(如认证)、密钥素材的组合。一个流上可以有多个SA(如一个用于认证、一个用于加密)。存放在系统中的所有SA构成的集合称为安全关联数据库(SADB,Security Association DataBase)。

一个系统的SADB可能用于IPsec以外的场合,如OSPFv2、RIPv2、RSVP、Mobile-IP等在SADB中也可能有各自的表项。因此PF_KEY套接字不仅限IPsec使用。

IPsec还需要一个安全策略数据库(SPDB,Security Policy DataBase),SPDB描述分组流通的需求,如主机A和主机B之间的分组流通必须通过IPsec AH认证,未经认证的一律丢弃。SADB描述如何执行所需的安全步骤,如主机A和主机B之间的分组流通按照策略在使用IPsec AH,SADB就含有所用的算法和密钥。但SPDB没有标准的维护机制,尽管PF_KEY可以维护SADB,但对SPDB无能为力。KAME的IPsec实现使用PF_KEY的扩展来维护SPDB,但它没有标准可循。

密钥管理套接字上支持3种操作:
1.通过写到密钥管理套接字,进程可以往内核和打开着密钥管理套接字的其他进程发消息。SADB表项的增加和删除也采用这种操作实现,如OSPFv2等自行进行安全检查的进程也采用这种方式从某个密钥管理守护进程请求密钥。

2.通过读密钥管理套接字,进程可以从内核或其他进程接收消息。内核可以采用这种操作请求某个密钥管理守护进程为依照策略需要受到保护的新TCP会话安装一个SA。

3.进程可以往内核发送一个倾泻(dumping)请求,内核作为应答倾泻出当前SADB,这是一个调试功能,并非所有系统都可用。

所有密钥管理套接字的消息都有同样的首部,每个消息后跟各种扩展,取决于可提供的或所请求的信息。所有相关结构都定义在头文件net/pfkeyv2.h中。每个消息和扩展都是64位对齐的,消息长度是8字节的整数倍;所有长度字段都是64位的;每个消息如果不足64位的整数倍,必须填充到下一个64位边界,填充字节的具体值没有定义。以下是密钥管理消息首部:
在这里插入图片描述
sadb_msg_type成员的值确定密钥管理消息类型,可选值如下:
在这里插入图片描述
每个sadb_msg首部后跟零个或多个扩展,大多消息类型都有必需和可选的扩展,以下是所有扩展类型:
在这里插入图片描述
在这里插入图片描述
进程使用SADB_DUMP消息倾泻当前SADB,它是最简单的密钥管理消息,不需要任何扩展,单纯是16字节的sadb_msg首部。一个进程通过某个密钥管理套接字发送一个SADB_DUMP消息到内核后,内核通过同一个套接字响应一系列SADB_DUMP消息,每个消息对应一个SADB表项,这个列表的末尾由成员sadb_msg_seq值为0来指示。

通过把SADB_DUMP请求消息的sadb_msg_satype成员设为下图中的某个值,进程可限制返回的SA的类型,若该成员的值为SADB_SATYPE_UNSPEC常值,则SADB中所有SA都会返回:
在这里插入图片描述
并非所有系统都支持所有SA类型,KAME实现仅支持IPsec的两类SA(SADB_SATYPE_AH和SADB_SATYPE_ESP),因此如果试图倾泻SADB_SATYPE_RIPV2类型的SA,将返回EINVAL错误。如果SADB中没有所请求类型的SA,将返回ENOENT错误。

以下是用于倾泻SADB的程序:

void sadb_dump(int type) {
    int s;
    char buf[4096];
    struct sadb_msg msg;
    int goteof;

    // 需要特定系统权限,因为它允许访问敏感的密钥素材
    s = Socket(PF_KEY, SOCK_RAW, PF_KEY_V2);

    /* Build and write SADB_DUMP request */
    bzero(&msg, sizeof(msg));
    // 调用socket时第三个参数为PF_KEY_V2,因此此处的版本也设为该值
    msg.sadb_msg_version = PF_KEY_V2;
    msg.sadb_msg_type = SADB_DUMP;
    msg.sadb_msg_satype = type;
    // sadb_msg_len的单位是8字节块
    msg.sadb_msg_len = sizeof(msg) / 8;
    // 将sadb_msg_pid设置为自己的进程id
    // 从进程到内核的所有消息都要以发送者的PID来标识
    msg.sadb_msg_pid = getpid();
    printf("Sending dump message:\n");
    // 使用我们的print_sadb_msg函数显示本消息
    // 我们不给出该冗长无味的函数的源码,它以直观可读方式
    // 显示正写到或已读自密钥管理套接字的某个消息
    print_sadb_msg(&msg, sizeof(msg));
    Write(s, &msg, sizeof(msg));
    printf("\nMessages returned:\n");
    /* Read and print SADB_DUMP replies until done */
    goteof = 0;
    while (goteof == 0) {
        int msglen;
		struct sadb_msg *msgp;
		msglen = Read(s, &buf, sizeof(buf));
		msgp = (struct sadb_msg *)&buf;
		print_sadb_msg(msgp, msglen);
		if (msgp->sadb_msg_seq == 0) {
		    goteof = 1;
		}
    }
    close(s);
}

int main(int argc, char **argv) {
    int satype = SADB_SATYPE_UNSPEC;
    int c;

    opterr = 0;    /* don't want getopt() writing to stderr */
    // POSIX的getopt函数的第三个参数指定允许出现的命令行选项字符
    // 本例中t字符后跟一个冒号,表示这个选项需要一个参数
    // 如果该参数为Oi:l:v,则表明程序接受4个选项
    // i和l需要参数O和v不需要参数
    // getopt函数与在unistd.h头文件中定义的一下4个全局变量协同工作
    // extern char *optarg;
    // extern int optind, opterr, optopt;
    // 在调用getopt前把opterr设为0,表示发生命令行参数与第3个参数不
    // 匹配等错误时该函数不把出错消息写到标准错误,因为我们想自行处理错误
    // POSIX声称第3个参数以“:”打头也能阻止函数写出到标准错误,但有些实现不支持
    while ((c = getopt(argc, argv, "t:")) != -1) {
        switch (c) {
		case 't':
		    // 使用我们的getsatypebyname函数从文本串得到类型值
		    // 用于指定倾泻指定的SA类型
		    if ((satype = getsatypebyname(optarg)) == -1) {
		        err_quit("invalid -t option %s", optarg);
		    }
		    break;
		default:
		    err_quit("unrecognized option: %c", c);
		}
    }
    sadb_dump(satype);
}

在一个具有2个静态SA(预先配置和固定的安全关联)的系统上运行本倾泻程序的输出:
在这里插入图片描述
在这里插入图片描述
向SADB增加一个SA最直接的方法是手动填写所有参数并发送一个SADB_ADD消息。虽然手动指定密钥素材会导致不易更改密钥(易于更改密钥对避免密码分析攻击很重要),但配置起来很容易:Alice和Bob使用带外数据达成一个密钥和算法,然后使用它们。

SADB_ADD消息必需的扩展有3种:SA、地址和、密钥,可选的扩展也有3种:生命期、身份、敏感性。

SA扩展由sadb_sa结构描述:
在这里插入图片描述
sadb_sa_spi成员含有安全参数索引(SPI,Security Parameters Index),SPI结合目的地址、所用协议(如IPsec AH)唯一标识一个SA。接收分组时,SPI用于查找该分组的SA;发送分组时,SPI插入到分组中供对端使用。SPI没有别的含义,因此其值可以顺序地或随机地分配,也可使用目的系统想使用的方法进行分配。sadb_sa_replay指定反重放窗口的大小。sadb_sa_state成员值在动态创建的SA的生命周期内会发生变化,可取值如下:
在这里插入图片描述
手动创建的SA总是处于SADB_SASTATE_MATURE状态。

sadb_sa_auth成员和sadb_sa_encrypt成员本别指定本SA的认证算法和加密算法,可取值如下:
在这里插入图片描述
sadb_sa_flags成员目前只定义了一个标志,即SADB_SAFLAGS_PFS,该标志要求完备前向安全(PFS,perfect forward security),即密钥的值不依赖于先前的密钥或某个主密钥,PFS确保即使长期的私钥泄露,先前的通信内容也无法被解密,在传统的加密协议中,使用的是长期有效的密钥,如果这些密钥被泄露,攻击者可以将其用于解密先前截获的通信数据,而PFS使用的是临时生成的一次性密钥,这些密钥仅用于加密和解密单个会话或通信。该标志值用于从密钥管理守护进程请求密钥的场合,增加静态SA时不用。

除了以上SA扩展外,SADB_ADD消息的另一种必需的扩展是地址扩展。由常值SADB_EXT_ADDRESS_SRC和SADB_EXT_ADDRESS_DST指定的是源地址和目的地址,这两个地址是必需的;而常值SADB_EXT_ADDRESS_PROXY指定的是代理地址,代理地址是可选的。代理地址详细信息可见RFC 2367。地址扩展使用以下sadb_address结构:
在这里插入图片描述
sadb_address结构中的sadb_address_exttype成员确定本地址是源地址、目的地址还是代理地址。sadb_address_proto成员指定本SA匹配的协议,若为0则匹配所有协议。sadb_address_prefixlen成员给出sadb_address结构表示的地址的有效位数(如IPv4是32位)。sadb_address结构后跟匹配地址族的sockaddr结构(如sockaddr_in或sockaddr_in6)。sockaddr中的端口仅在sadb_address_proto指定的协议支持端口号的前提下(如IPPROTO_TCP)才有效。

SADB_ADD消息的最后一种必需的扩展是认证和加密密钥,分别由SADB_EXT_KEY_AUTH和SADB_EXT_KEY_ENCRYPT指定,由sadb_ley结构描述:
在这里插入图片描述
sadb_key_exttype成员定义本密钥是认证密钥还是加密密钥。sadb_key_bits成员指定本密钥的位数。密钥本身紧跟在sadb_key结构后。

增加一个静态SADB表项的代码:

void sadb_add(struct sockaddr *src, struct sockaddr *dst, int type, int alg,
              int spi, int keybits, unsigned char *keydata) {
    int s;
    char buf[4096], *p;    /* XXX */
    struct sadb_msg *msg;
    struct sadb_sa *saext;
    struct sadb_address *addrext;
    struct sadb_key *keyext;
    int len;
    int mypid;

    s = Socket(PF_KEY, SOCK_RAW, PF_KEY_V2);

    mypid = getpid();

    /* Build and write SADB_ADD request */
    // 构造SADB_ADD消息首部
    bzero(&buf, sizeof(buf));
    p = buf;
    msg = (struct sadb_msg *)p;
    msg->sadb_msg_version = PF_KEY_V2;
    msg->sadb_msg_type = SADB_ADD;
    msg->sadb_msg_satype = type;
    msg->sadb_msg_pid = getpid();
    len = sizeof(*msg);
    p += sizeof(*msg);

    // 添加必需的SA扩展
    saext = (struct sadb_sa *)p;
    saext->sadb_sa_len = sizeof(*saext) / 8;
    saext->sadb_sa_exttype = SADB_EXT_SA;
    saext->sadb_sa_spi = htonl(spi);    // 必须以网络字节序存放
    // 关闭重放保护
    saext->sadb_sa_replay = 0;    /* no replay protection with static keys */
    saext->sadb_sa_state = SADB_SASTATE_MATURE;
    // 设置认证算法
    saext->sadb_sa_auth = alg;
    // 设置加密算法
    saext->sadb_sa_encrypt = SADB_EALG_NONE;
    saext->sadb_sa_flags = 0;
    len += saext->sadb_sa_len * 8;
    p += saext->sadb_sa_len * 8;

    // 将源地址以SADB_EXT_ADDRESS_SRC扩展形式添加到本消息
    addrext = (struct sadb_address *)p;
    // 长度字段先加7再除8,是按64位边界填充后的长度
    addrext->sadb_address_len = (sizeof(*addrext) + salen(src) + 7) / 8;
    addrext->sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
    // 本SA适用于所有协议
    addrest->sadb_address_proty = 0;    /* any protocol */
    // 设置地址长度,IPv4为32位,IPv6为128位
    addrext->sadb_address_prefixlen = prefix_all(src);
    addrext->sadb_address_reserved = 0;
    memcpy(addrext + 1, src, salen(src));
    len += addrext->sadb_address_len * 8;
    p += addrext->sadb_address_len * 8;

    // 与源地址一样的方式将目的地址加入本消息
    // 但sadb_address_exttype为SADB_EXT_ADDRESS_DST
    addrext = (struct sadb_address *)p;
    addrext->sadb_address_len = (sizeof(*addrext) + salen(dst) + 7) / 8;
    addrext->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
    addrext->sadb_address_proto = 0;    /* any protocol */
    addrext->sadb_address_prefixlen = prefix_all(dst);
    addrext->sadb_address_reserved = 0;
    memcpy(addrext + 1, dst, salen(dst));
    len += addrext->sadb_address_len * 8;
    p += addrext->sadb_address_len * 8;

    // 添加认证密钥
    keyext = (struct sadb_key *)p;
    /* "+7" handles alignment requirements */
    keyext->sadb_key_len = (sizeof(*keyext) + (keybits / 8) + 7) / 8;
    keyext->sadb_key_exttype = SADB_EXT_KEY_AUTH;
    keyext->sadb_key_bits = keybits;
    keyext->sadb_key_reserved = 0;
    // 把密钥数据复制到本扩展首部后面
    memcpy(keyext + 1, keydata, keybits / 8);
    len += keyext->sadb_key_len * 8;
    p += keyext->sadb_ley_len * 8;

    msg->sadb_msg_len = len / 8;
    printf("Sending add message:\n");
    print_sadb_msg(buf, len);
    Write(s, buf, len);

    printf("\nReply returned:\n");
    /* Read and print SADB_ADD reply, discarding any others */
    for (; ; ) {
        int msglen;
		struct sadb_msg *msgp;
	
		msglen = Read(s, &buf, sizeof(buf));
		msgp = (struct sadb_msg *)&buf;
		// 寻找pid与本进程一致的消息
		if (msgp->sadb_msg_pid == mypid && msgp->sadb_msg_type == SADB_ADD) {
		    print_sadb_msg(msgp, msglen);
		    break;
		}
    }
    close(s);
}

运行以上程序,发送SADB_ADD消息为127.0.0.1和127.0.0.1之间的分组流通增设一个SA:
在这里插入图片描述
上图中,应答消息中没有给出密钥内容,这是因为应答消息被发送到所有PF_KEY套接字,但不同的套接字可能属于不同的保护域(保护域是一种安全机制,用于隔离和控制不同进程或实体之间的访问权限,每个保护域都有自己的访问规则和权限设置),密钥数据不应该跨越保护域(这是为了防止密钥数据被未经授权的实体或进程访问、修改或泄露)。把这个SA添加到SADB后,我们对127.0.0.1执行ping命令使该SA真正被使用,然后倾泻出SADB以检查所添加的SA:
在这里插入图片描述
在这里插入图片描述
从倾泻的结果可见,内核把我们的IP协议从0改为了255,这是本实现的一个特性(实际是一个缺陷),而非PF_KEY套接字的普遍特性。此外内核把前缀长度由32改为了128(另一个缺陷),它看起来是由内核混淆IPv4和IPv6地址所引起。内核还返回了另一个我们的倾泻程序不认识的扩展(编号19),不认识的扩展我们的倾泻程序会根据它的长度字段跳过。内核还返回了生命期扩展,含有本SA的当前生命期信息,生命期扩展相关的结构如下:
在这里插入图片描述
生命期扩展有3种,SADB_LIFETIME_SOFT和SADB_LIFETIME_HARD这两个扩展分别指定一个SA的软生命期和硬生命期。当软生命期结束时,内核发送一个SADB_EXPIRE消息;当硬生命期结束后,该SA不能再用。最后一种生命期扩展是SADB_LIFETIME_CURRENT扩展,它用于指出相应SA的当前生命期,它会在SADB_DUMP、SADB_EXPIRE、SADB_GET消息中返回。

周期性地重新产生密钥(动态维护安全关联)有助于进一步提高安全性,这种操作通常由诸如IKE之类的协议执行。

为了获悉何时需要为一对主机提供新的SA,密钥管理守护进程应预先用SADB_REGISTER请求消息向内核注册自身,其中的sadb_msg_satype成员指出所能处理的SA类型。如果守护进程能处理多种SA类型,它就为其中每个类型发送一个SADB_REGISTER请求消息。在SADB_REGISTER应答消息中,内核提供一系列受支持算法扩展,用来指出哪些加密和认证机制、哪些密钥长度得到支持。受支持算法扩展由sadb_supported结构描述:
在这里插入图片描述
紧跟在每个sadb_supported结构(相当于该扩展的首部)后的是一系列以sadb_alg结构给出的加密或认证算法的描述。

sadb_supported扩展首部后的每个sadb_alg结构代表系统支持的一个算法,下图是为处理SA类型SADB_SATYPE_ESP 而发出的SADB_REGISTER请求的一个可能应答:
在这里插入图片描述
以下程序使用SADB_REGISTER请求向内核注册自身进程,然后显示内核在应答中返回的受支持算法列表:

void sadb_register(int type) {
    int s;
    char buf[4096];    /* XXX */
    struct sadb_msg msg;
    int goteof;
    int mypid;

    s = Socket(PF_KEY, SOCK_RAW, PF_KEY_V2);

    mypid = getpid();

    /* Build and write SADB_REGISTER request */
    bzero(&msg, sizeof(msg));
    msg.sadb_msg_version = PF_KEY_V2;
    msg.sadb_msg_type = SADB_REGISTER;
    msg.sadb_msg_satype = type;
    msg.sadb_msg_len = sizeof(msg) / 8;
    msg.sadb_msg_pid = mypid;
    printf("Sending register message:\n");
    print_sadb_msg(&msg, sizeof(msg));
    Write(s, &msg, sizeof(msg));
    // SADB_REGISTER请求消息不需要任何扩展

    printf("\nReply returned:\n");
    /* Read and print SADB_REGISTER reply, discarding any others */
    for (; ; ) {
        int msglen;
		struct sadb_msg *msgp;
	
		msglen = Read(s, &buf, sizeof(buf));
		msgp = (struct sadb_msg *)&buf;
		// 应答消息中的pid是本进程的pid,且类型为SADB_REGISTER
		if (msgp->sadb_msg_pid == mypid &&
		    msgp->sadb_msg_type == SADB_REGISTER) {
		    print_sadb_msg(msgp, msglen);
		    break;
		}
    }
    close(s);
}

在一个不仅仅支持RFC 2367中规定协议的系统上运行以上register程序:
在这里插入图片描述
当内核需要与某个目的地址通信时,如果根据策略该单向分组流必须经由一个SA而内核没有可用SA时,内核就向注册了所需SA类型的密钥管理套接字发送一个SADB_ACQUIRE消息,其中含有一个描述内核所提议算法及密钥长度的提议扩展,该提议可能综合了系统支持的配置与限制该单向分组流的预配置策略。提议内容是一个由算法、密钥长度、生命期所构成的按照优先顺序排列的列表。当一个密钥管理守护进程收到一个SADB_ACQUIRE消息后,它执行必要的操作以选择一个内核提议的密钥,再把该密钥安装到内核中。密钥管理守护进程使用SADB_GETSPI消息请求内核从一个期望的范围内选择一个SPI,内核对于该SADB_GETSPI消息的响应还包括建立一个处于larval(幼虫)状态的SA,然后守护进程使用由内核提供的SPI与远端协商安全参数,接着使用SADB_UPDAE更新该SA,使它进入mature(成熟)状态。动态创建的SA通常含有关联的软生命期和硬生命期,任何一个生命期结束时,内核将发送一个SADB_EXPIRE消息,其中指出期满的是软生命期还是硬生命期,如果软生命期结束,SA就进入dying(垂死)状态,期间它仍可使用,但内核应该为它获取一个新SA;如果硬生命期结束,去SA就进入dead(死亡)状态,这种状态的SA不能继续使用,必须从SADB中删除。

密钥管理套接字用于在内核、密钥管理守护进程、诸如路由守护进程等安全相关的消费进程间交换SA。SA既可以手工静态安装,也可以使用密钥协商协议自动动态安装。动态密钥有关联的生命期,当软生命期结束时,密钥守护进程得到通知,这样的SA如果在硬生命期结束前未被新SA替换,那就不能再使用。

进程和内核通过密钥管理套接字交换的信息共有10种类型,每种消息类型都有关联的扩展,有的扩展是必需的,有的是可选的。每个由进程发送的消息的应答被内核发送到所有打开着的密钥管理套接字,但其中含有敏感数据的扩展都会被抹除。

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

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

相关文章

chatgpt赋能python:Python一行输入三个数:方便快捷,还能提高效率!

Python一行输入三个数:方便快捷,还能提高效率! Python是一门非常流行的编程语言,不仅仅因为它简单易用,更因为它拥有丰富的生态系统和强大的库支持。但是,Python的输入方式却是许多人常常感到头疼的部分。…

学会使用“条件断点“来解放你的鼠标

(PS:对调试较为熟悉却没有使用过条件断点的同学可以直接翻到文章底部看操作的GIF图~) 一、背景 "Debug"想必大家在开发的过程中也是有经常使用的,这里简单的介绍一下浏览器"Debug"其中的两种方式。&#xff…

使用idea创建java web项目

创建web项目有很多方法,就说一个最简单的方法吧。 创建一个java项目,点击创建右击项目选择添加框架支持。勾选上web应用程序,点击确定。 再点击当前文件,编辑配置 点击加号,选择Tomcat服务器(本地&#xf…

【连续介质力学】变形梯度

变形梯度 简介 本节讨论两个不同质点P和Q的之间的相对运动变化 拉伸比和相对伸长 d X ⃗ d\vec X dX : 在参考构形连接质点P和Q的向量,线单元 M ^ \hat M M^: d X ⃗ d\vec X dX 方向的单位向量 d x ⃗ d\vec x dx : 在当前构形连接质点P’和Q‘的向量&#xff…

对象存储分布式代理-go初学者的开源练手项目

最近写了一个go语言练手项目osproxy和osproxy-grpc,代码已完全开源到github。 相信不少接触go语言的读者,都是被它"极简协程",“高并发”,"高性能"的特性所吸引,我也不例外,想着学完之…

基于MATLAB涡度通量数据处理技术应用

点击查看原文 本文基于MATLAB语言、以实践案例为主,提供代码、原理与操作结合 1、以涡度通量塔的高频观测数据为例:基于MATLAB开展上机操作 2、涡度通量观测基本概况:观测技术方法、数据获取与预处理等 3、涡度通量数据质量控制&#xff1…

第3章 需求分析(上)

第3章 需求分析(上) 3.1 需求分析任务 3.1.1 确定对系统的综合要求 1. 功能需求 通过需求分析应该划分出必须完成的所有功能。 2. 性能需求 性能需求指定系统必须满足的定时约束或容量约束 3. 可靠性和可用性需求 可靠性需求定量地指定系统的可靠…

二叉树的最近公共祖先LCA

一、什么是最近公共祖先 LCA为最近公共祖先(Lowest Common Ancestor)的缩写。 对于一棵有根树T的两个节点u,v,最近公共祖先LCA(T,u,v)代表一个节点x。 LCA(5,6) 2 LCA(7,12) 3 LCA(2,1)1 二、公共祖先的朴素解法 两个节点先调整到相同的深度每一次…

Qt6使用cmake创建项目

目录 创建普通项目 创建qt项目 创建具有资源文件的qt项目 在QT6中,官方推荐在新的项目中使用CMake。 在Qt Creator中使用CMake构建系统时,与往常的qmake有一些不一样,因此写一篇文章来记录一下。 创建普通项目 我们先写一个最简单的项目…

chatgpt赋能python:Python的Unicode编码介绍

Python的Unicode编码介绍 什么是Unicode Unicode是一个字符集,它定义了世界上几乎所有的字符,包括字母、数字、符号和标点。Unicode使用唯一的数字编码来表示每个字符,这使得在不同的操作系统和编程语言中展示和处理字符非常方便。在Python…

chatgpt赋能python:Python怎样完成更新?

Python怎样完成更新? Python是一种非常流行的编程语言,由于它的简单易学和广泛的应用领域,许多程序员选择使用Python编写程序。但是,随着时间的推移和技术的不停发展,Python需要不断更新以保持全球开发者的使用体验。…

ES 如何重建索引

场景: ES索引中,为了效率和存储空间,有些字段可以设定为不被索引,然后某一天又需要改成能索引,此时就需要对ES进行重建索引,操作如下 1、修改 ES 索引模板文件 cd /data/elk/logstash/es-template/ vim e…

JavaSE笔记(七)

Java反射和注解 **注意:**本章节涉及到JVM相关底层原理,难度会有一些大。 反射就是把Java类中的各个成分映射成一个个的Java对象。即在运行状态中,对于任意一个类,都能够知道这个类所有的属性和方法,对于任意一个对象…

几十款游戏的简单分析

文章目录 一、 介绍二、 影响游戏体验的因素三、 游戏能爆火的因素1.影响游戏爆火因素的排名2.玩游戏的两种经典心理3.经典案例分析Qq农场植物大战僵尸水果忍者召唤神龙羊了个羊 4.游戏公司可借鉴的经验5.未来游戏面对的诸多挑战 四、 几十款游戏的多方面分析FC红白游戏机十二人…

chatgpt赋能python:Python中的import使用详解

Python中的import使用详解 介绍 在Python中,import是将一个模块引入到当前脚本中使用的关键字。Python中的模块是指一个包含所有定义、函数和变量等的Python文件,也可以包含其他模块,从而构成一个Python程序。在Python中,有很多…

spark相关理论

系列文章目录 ubuntu虚拟机下搭建zookeeper集群,安装jdk压缩包,搭建Hadoop集群与spark集群的搭建【上篇】_ubuntu搭建zookeeper集群 ubuntu虚拟机下搭建zookeeper集群,安装jdk压缩包,搭建Hadoop集群与spark集群的搭建【下篇】 …

Redux基本使用和实践

Redux的核心是store,store作为应用状态的容器,保存着这个页面的状态数据树。 store 但是store本质上是一个JavaScript对象,这个对象含有了dispatch以及获取页面状态数据的方法等等。 如上图所示,store提供几个方法给开发者调用&…

[论文阅读笔记75]P-Tuning v2

1. 基本信息 题目论文作者与单位来源年份P-Tuning v2: Prompt Tuning Can Be Comparable to Fine-tuning Universally Across Scales and TasksXiao Liu等Tsinghua University清华大学2021 Citations, References 论文链接:https://arxiv.org/pdf/2110.07602.pdf…

chatgpt赋能python:Python的IDLE是什么?——初探IDLE的用途和功能

Python的IDLE是什么?——初探IDLE的用途和功能 Python的IDLE是一个Python集成开发环境(IDE),可以简单地将其视为为开发者提供编写、调试和执行代码的工具。IDLE包括一个交互式解释器,使开发更加快速和简便。它还提供了代码编辑器、调试器和其…

chatgpt赋能python:Python技巧:一行代码实现所有数据的输出

Python技巧:一行代码实现所有数据的输出 Python是一种高级动态语言,因其简单易学和灵活性而广受欢迎。Python的语法简单明了,适合初学者学习、理解和实践,同时也是专业程序员的首选开发语言之一。 在实际的编程中,有…