当前位置: 首页 > news >正文

《Linux高性能服务器编程》笔记03

Linux高性能服务器编程

本文是读书笔记,如有侵权,请联系删除。

参考

Linux高性能服务器编程源码: https://github.com/raichen/LinuxServerCodes

豆瓣: Linux高性能服务器编程

文章目录

  • Linux高性能服务器编程
  • 第07章 Linux服务器程序规范
    • 7.1日志
    • 7.2用户信息
    • 7.3进程间关系
    • 7.4系统资源限制
    • 7.5改变工作目录和根目录
    • 7.6服务器程序后台化
    • 后记

第07章 Linux服务器程序规范

除了网络通信外,服务器程序通常还必须考虑许多其他细节问题。这些细节问题涉及面 广且零碎,而且基本上是模板式的,所以我们称之为服务器程序规范。比如:

Linux服务器程序一般以后台进程形式运行。后台进程又称守护进程(daemon)。它 没有控制终端,因而也不会意外接收到用户输入。守护进程的父进程通常是init进程(PID为1的进程)。

Linux服务器程序通常有一套日志系统,它至少能输出日志到文件,有的高级服务器还能输出日志到专门的UDP服务器。大部分后台进程都在/var/log目录下拥有自己的 日志目录。

Linux服务器程序一般以某个专门的非root身份运行。比如mysqld、httpd、syslogd 等后台进程,分别拥有自己的运行账户mysql、apache和syslog。Linux服务器程序通常是可配置的。服务器程序通常能处理很多命令行选项,如果一 次运行的选项太多,则可以用配置文件来管理。绝大多数服务器程序都有配置文件, 并存放在/etc目录下。比如第4章讨论的squid服务器的配置文件是/etc/squid3/squid.conf。

Linux服务器进程通常会在启动的时候生成一个PID文件并存入/var/run目录中,以 记录该后台进程的 PID。比如syslogd 的PID 文件是/var/run/syslogd.pid。

Linux服务器程序通常需要考虑系统资源和限制,以预测自身能承受多大负荷,比如进程可用文件描述符总数和内存总量等。

在开始系统地学习网络编程之前,我们将用一章的篇幅来探讨服务器程序的一些主要的规范。

7.1日志

7.1.1Linux系统日志

工欲善其事,必先利其器。服务器的调试和维护都需要一个专业的日志系统。Linux提供一个守护进程来处理系统日志—syslogd,不过现在的Linux系统上使用的都是它的升级 版——rsyslogd。rsyslogd守护进程既能接收用户进程输出的日志,又能接收内核日志。用户进程是通过 调用syslog函数生成系统日志的。该函数将日志输出到一个UNIX本地域socket类型(AF_ UNIX)的文件/dev/log中,rsyslogd则监听该文件以获取用户进程的输出。内核日志在老的系统上是通过另外一个守护进程rklogd来管理的,rsyslogd利用额外的模块实现了相同的功能。内核日志由printk等函数打印至内核的环状缓存(ring buffer)中。环状缓存的内容直接 映射到/proc/kmsg文件中。rsyslogd则通过读取该文件获得内核日志。

rsyslogd守护进程在接收到用户进程或内核输入的日志后,会把它们输出至某些特定的 日志文件。默认情况下,调试信息会保存至/var/log/debug 文件,普通信息保存至/var/log/ messages文件,内核消息则保存至/var/log/kern.log文件。不过,日志信息具体如何分发,可 以在rsyslogd的配置文件中设置。rsyslogd的主配置文件是/etc/rsyslog.conf,其中主要可以设置的项包括:内核日志输入路径,是否接收UDP日志及其监听端口(默认是514,见/etc/services文件),是否接收TCP日志及其监听端口,日志文件的权限,包含哪些子配置文件(比如/etc/rsyslog.d/*.conf)。rsyslogd的子配置文件则指定各类日志的目标存储文件。图7-1总结了Linux的系统日志体系。

在这里插入图片描述

7.1.2 syslog 函数

应用程序使用syslog函数与rsyslogd守护进程通信。syslog 函数的定义如下:

#include <syslog.h>
void syslog( int priority, const char* message,. );

该函数采用可变参数(第二个参数message和第三个参数…)来结构化输出。priority参数是所谓的设施值与日志级别的按位或。设施值的默认值是LOG_USER,我们下面的讨论也 只限于这一种设施值。日志级别有如下几个:

在这里插入图片描述

#include <syslog.h>void syslog( int priority, const char* message, ... );

解释:

  • priority 指定消息的优先级,它包括设施(facility)和级别(level)两个部分。可以通过 LOG_FACMASKLOG_PRIMASK 进行掩码操作获取或设置这两个部分。

  • message 要记录的消息的格式字符串,类似于 printf 中的格式化字符串。

示例:

#include <syslog.h>int main() {openlog("my_program", LOG_PID | LOG_CONS, LOG_USER);syslog(LOG_INFO, "This is an informational message.");syslog(LOG_ERR, "An error occurred: %s", "File not found");closelog();return 0;
}

上述示例中,程序首先调用 openlog 打开系统日志,指定了程序标识符为 “my_program”,同时指定了一些选项(LOG_PID 表示在每条日志消息中包含进程ID,LOG_CONS 表示将日志消息发送到控制台)。然后使用 syslog 记录两条日志消息,一条是信息性消息 (LOG_INFO),另一条是错误消息 (LOG_ERR)。最后调用 closelog 关闭系统日志。

下面这个函数可以改变syslog的默认输出方式,进一步结构化日志内容:

#include <syslog.h>
void openlog( const char* ident, int logopt, int facility );

ident参数指定的字符串将被添加到日志消息的日期和时间之后,它通常被设置为程序的名字。logopt参数对后续syslog调用的行为进行配置,它可取下列值的按位或:

在这里插入图片描述

facility参数可用来修改syslog函数中的默认设施值。

此外,日志的过滤也很重要。程序在开发阶段可能需要输出很多调试信息,而发布之后 我们又需要将这些调试信息关闭。解决这个问题的方法并不是在程序发布之后删除调试代码 (因为日后可能还需要用到),而是简单地设置日志掩码,使日志级别大于日志掩码的日志信 息被系统忽略。下面这个函数用于设置syslog的日志掩码:

#include <syslog.h>
int setlogmask( int maskpri );

maskpri参数指定日志掩码值。该函数始终会成功,它返回调用进程先前的日志掩码值。 最后,不要忘了使用如下函数关闭日志功能:

#include <syslog.h> 
void closelog();

7.2用户信息

7.2.1UID、EUID、GID和EGID

用户信息对于服务器程序的安全性来说是很重要的,比如大部分服务器就必须以root身 份启动,但不能以root身份运行。下面这一组函数可以获取和设置当前进程的真实用户ID(UID)、有效用户ID(EUID)、真实组ID(GID)和有效组ID(EGID):

在这里插入图片描述

需要指出的是,一个进程拥有两个用户ID:UID和EUID。EUID存在的目的是方便资源访问:它使得运行程序的用户拥有该程序的有效用户的权限。比如su程序,任何用户都 可以使用它来修改自己的账户信息,但修改账户时su程序不得不访问/etc/passwd文件,而 访问该文件是需要root权限的。那么以普通用户身份启动的su程序如何能访问/etc/passwd 文件呢?窍门就在EUID。用ls命令可以查看到,su程序的所有者是root,并且它被设置了 set-user-id标志。这个标志表示,任何普通用户运行su程序时,其有效用户就是该程序的所有者root。那么,根据有效用户的含义,任何运行su程序的普通用户都能够访问/etc/passwd 文件。有效用户为root的进程称为特权进程(privileged processes)。EGID的含义与EUID类 似:给运行目标程序的组用户提供有效组的权限。

#include <unistd.h>
#include <stdio.h>int main()
{uid_t uid = getuid();uid_t euid = geteuid();printf( "userid is %d, effective userid is: %d\n", uid, euid );return 0;
}

7.2.2切换用户

下面的代码清单7-2展示了如何将以root身份启动的进程切换为以一个普通用户身份运行。

/*** @brief 切换用户和用户组* * @param user_id 目标用户ID* @param gp_id 目标用户组ID* @return 切换是否成功*/
static bool switch_to_user(uid_t user_id, gid_t gp_id)
{// 如果目标用户ID和目标用户组ID都为0,表示无需切换if ((user_id == 0) && (gp_id == 0)){return false;}// 获取当前进程的实际用户组ID和实际用户IDgid_t gid = getgid();uid_t uid = getuid();// 如果当前用户ID不为0,表示已经是非root用户,无需切换if (((gid != 0) || (uid != 0)) && ((gid != gp_id) || (uid != user_id))){return false;}// 如果当前用户ID不为0,表示已经是非root用户,无需切换if (uid != 0){return true;}// 尝试切换用户组和用户IDif ((setgid(gp_id) < 0) || (setuid(user_id) < 0)){return false;}return true;
}

该函数的作用是切换进程的用户ID和用户组ID。函数会检查当前用户ID和用户组ID,如果已经是非root用户或者目标用户ID和目标用户组ID为0,表示无需切换,返回 false。如果当前用户ID为0(root用户),则尝试使用 setgidsetuid 切换到目标用户组和用户ID。切换成功返回 true,否则返回 false

7.3进程间关系

7.3.1进程组

Linux下每个进程都隶属于一个进程组,因此它们除了PID信息外,还有进程组ID(PGID)。我们可以用如下函数来获取指定进程的PGID:

#include <unistd.h>
pid_t getpgid( pid_t pid );

该函数成功时返回进程pid所属进程组的PGID,失败则返回-1并设置errno。每个进程组都有一个首领进程,其PGID和PID相同。进程组将一直存在,直到其中所 有进程都退出,或者加入到其他进程组。

下面的函数用于设置PGID:

#include <unistd.h>
int setpgid( pid_t pid, pid_t pgid );

该函数将PID为pid的进程的PGID设置为pgid。如果pid和pgid相同,则由pid指 定的进程将被设置为进程组首领;如果pid为0,则表示设置当前进程的PGID为pgid;如 果pgid为0,则使用pid作为目标PGID。setpgid函数成功时返回0,失败则返回-1并设置errno。一个进程只能设置自己或者其子进程的PGID。并且,当子进程调用exec系列函数后,我们也不能再在父进程中对它设置PGID。

7.3.2会话

一些有关联的进程组将形成一个会话(session)。下面的函数用于创建一个会话:

#include <unistd.h>
pid_t setsid( void );

该函数不能由进程组的首领进程调用,否则将产生一个错误。对于非组首领的进程,调用该函数不仅创建新会话,而且有如下额外效果:

调用进程成为会话的首领,此时该进程是新会话的唯一成员。

新建一个进程组,其PGID就是调用进程的PID,调用进程成为该组的首领。

调用进程将甩开终端(如果有的话)。

该函数成功时返回新的进程组的PGID,失败则返回-1并设置errno。

Linux进程并未提供所谓会话ID(SID)的概念,但Linux系统认为它等于会话首领所在的进程组的PGID,并提供了如下函数来读取SID:

#include <unistd.h>
pid_t getsid( pid_t pid );

7.3.3用ps命令查看进程关系

执行ps命令可查看进程、进程组和会话之间的关系:

在这里插入图片描述

我们是在 bash shell下执行 ps和less命令的,所以ps和less命令的父进程是bash命令, 这可以从PPID(父进程PID)一列看出。这3条命令创建了1个会话(SID是1943)和2 个进程组(PGID分别是1943和2298)。bash命令的PID、PGID和SID都相同,很明显它既是会话的首领,也是组1943的首领。ps命令则是组2298的首领,因为其PID也是2298。图7-2描述了此三者的关系。

在这里插入图片描述

7.4系统资源限制

Linux上运行的程序都会受到资源限制的影响,比如物理设备限制(CPU数量、内存数量等)、系统策略限制(CPU时间等),以及具体实现的限制(比如文件名的最大长度)。Linux系统资源限制可以通过如下一对函数来读取和设置:

#include <sys/resource.h>
int getrlimit( int resource, struct rlimitlim ):
int setrlimit( int resource, const struct rlimit *rlim ); 

rlim参数是rlimit结构体类型的指针,rlimit结构体的定义如下:

struct rlimit
{rlim_t rlim_cur; rlim_t rlim max;
};

rlim_t是一个整数类型,它描述资源级别。rlim_cur成员指定资源的软限制,rlim_max 成员指定资源的硬限制。软限制是一个建议性的、最好不要超越的限制,如果超越的话,系 统可能向进程发送信号以终止其运行。例如,当进程CPU时间超过其软限制时,系统将向进程发送SIGXCPU信号;当文件尺寸超过其软限制时,系统将向进程发送SIGXFSZ信号 (见第10章)。硬限制一般是软限制的上限。普通程序可以减小硬限制,而只有以root身份 运行的程序才能增加硬限制。此外,我们可以使用ulimit命令修改当前shell环境下的资源限 制(软限制或/和硬限制),这种修改将对该shell启动的所有后续程序有效。我们也可以通 过修改配置文件来改变系统软限制和硬限制,而且这种修改是永久的,详情见第16章。

resource参数指定资源限制类型。表7-1列举了部分比较重要的资源限制类型。

在这里插入图片描述

7.5改变工作目录和根目录

有些服务器程序还需要改变工作目录和根目录,比如我们第4章讨论的 Web 服务器。一般来说,Web服务器的逻辑根目录并非文件系统的根目录“/”,而是站点的根目录(对于Linux的Web 服务来说,该目录一般是/var/www/)。

获取进程当前工作目录和改变进程工作目录的函数分别是:

#include <unistd.h>
char* getcwd( char* buf, size_t size ); 
int chdir( const char* path );

buf参数指向的内存用于存储进程当前工作目录的绝对路径名,其大小由size参数指定。 如果当前工作目录的绝对路径的长度(再加上一个空结束字符“\0”)超过了size,则getcwd 将返回NULL,并设置errno为ERANGE。如果buf为NULL并且size非0,则getcwd可能 在内部使用malloc动态分配内存,并将进程的当前工作目录存储在其中。如果是这种情况, 则我们必须自己来释放getcwd在内部创建的这块内存。getcwd函数成功时返回一个指向目 标存储区(buf指向的缓存区或是getcwd在内部动态创建的缓存区)的指针,失败则返回 NULL并设置errno。

chdir 函数的path参数指定要切换到的目标目录。它成功时返回0,失败时返回-1并设 置errno。改变进程根目录的函数是chroot,其定义如下:

#include <unistd.h>
int chroot( const char* path );

path参数指定要切换到的目标根目录。它成功时返回0,失败时返回-1并设置 errno。 chroot并不改变进程的当前工作目录,所以调用chroot之后,我们仍然需要使用chdir(“/”) 来将工作目录切换至新的根目录。改变进程的根目录之后,程序可能无法访问类似/dev的文 件(和目录),因为这些文件(和目录)并非处于新的根目录之下。不过好在调用chroot之 后,进程原先打开的文件描述符依然生效,所以我们可以利用这些早先打开的文件描述符来 访问调用chroot之后不能直接访问的文件(和目录),尤其是一些日志文件。此外,只有特 权进程才能改变根目录。

7.6服务器程序后台化

最后,我们讨论如何在代码中让一个进程以守护进程的方式运行。守护进程的编写遵循 一定的步骤[2],下面我们通过一个具体实现来探讨,如代码清单7-3所示。

7-3daemonize.cpp

/*** @brief 创建守护进程* * @return 创建是否成功*/
bool daemonize()
{// 创建子进程pid_t pid = fork();if (pid < 0){// 创建失败return false;}else if (pid > 0){// 父进程退出,让子进程成为孤儿进程exit(0);}// 设置文件权限掩码umask(0);// 创建新会话,并使当前进程成为会话首进程和组长进程pid_t sid = setsid();if (sid < 0){// 创建新会话失败return false;}// 切换工作目录到根目录if (chdir("/") < 0){// 切换目录失败,记录日志return false;}// 关闭标准输入、标准输出、标准错误描述符close(STDIN_FILENO);close(STDOUT_FILENO);close(STDERR_FILENO);// 重定向标准输入、标准输出、标准错误到 /dev/nullopen("/dev/null", O_RDONLY);open("/dev/null", O_RDWR);open("/dev/null", O_RDWR);return true;
}

该函数的作用是创建守护进程。首先通过 fork 创建子进程,父进程退出,使子进程成为孤儿进程。然后调用 setsid 创建新会话,并使当前进程成为新会话的首进程和组长进程。接着切换工作目录到根目录,关闭标准输入、标准输出、标准错误描述符,最后将它们重定向到 /dev/null。函数返回创建是否成功。

实际上,Linux提供了完成同样功能的库函数:

#include <unistd.h>
int daemon( int nochdir, int noclose );

其中,nochdir参数用于指定是否改变工作目录,如果给它传递0,则工作目录将被设置 为“/”(根目录),否则继续使用当前工作目录。noclose参数为0时,标准输入、标准输出 和标准错误输出都被重定向到/dev/null文件,否则依然使用原来的设备。该函数成功时返回 0,失败则返回-1并设置errno。

int daemon( int nochdir, int noclose );使用举例:

#include <unistd.h>int main()
{int nochdir = 0;  // 是否切换工作目录,0表示切换,非0表示不切换int noclose = 0;  // 是否关闭标准输入、标准输出、标准错误,0表示关闭,非0表示不关闭int result = daemon(nochdir, noclose);if (result == 0){// 守护进程的代码逻辑while (1){// 守护进程的主体工作// ...}}else{// 创建守护进程失败的处理逻辑// ...}return 0;
}

daemon 函数是一种创建守护进程的方法,该函数会调用 fork 创建子进程,并使父进程退出,从而让子进程成为孤儿进程。然后,setsid 函数将子进程创建为新的会话组长和进程组长,与当前终端脱离关系。最后,根据 nochdirnoclose 参数判断是否切换工作目录和关闭标准输入、标准输出、标准错误。

在代码示例中,通过设定 nochdirnoclose 参数,可以控制是否切换工作目录和关闭标准输入、标准输出、标准错误。函数返回值为0表示创建守护进程成功,-1表示失败。

后记

截至2024年1月20日18点38分,阅读《Linux高性能服务器编程》第七章,记录一下学习过程。

相关文章:

《Linux高性能服务器编程》笔记03

Linux高性能服务器编程 本文是读书笔记&#xff0c;如有侵权&#xff0c;请联系删除。 参考 Linux高性能服务器编程源码: https://github.com/raichen/LinuxServerCodes 豆瓣: Linux高性能服务器编程 文章目录 Linux高性能服务器编程第07章 Linux服务器程序规范7.1日志7.2用…...

Java毕业设计-基于ssm的网上求职招聘管理系统-第85期

获取源码资料&#xff0c;请移步从戎源码网&#xff1a;从戎源码网_专业的计算机毕业设计网站 项目介绍 基于ssm的网上求职招聘管理系统&#xff1a;前端 jsp、jquery&#xff0c;后端 springmvc、spring、mybatis&#xff0c;角色分为管理员、招聘人员、用户&#xff1b;集成…...

UDP和TCP

UDP协议是一种不可靠的、面向无连接的协议。在通信过程中&#xff0c;它并不像TCP那样需要先建立一个连接&#xff0c;只要&#xff08;目的地址&#xff0c;端口号&#xff0c;源地址&#xff0c;端口号&#xff09;确定了&#xff0c;就可以直接发送信息报文&#xff0c;并且…...

【C++】vector容器接口要点的补充

接口缩容 在VS编译器的模式下&#xff0c;类似于erase和insert接口的函数通常会进行缩容&#xff0c;因此&#xff0c;insert和erase行参中的迭代器可能会失效。下图中以erase为例&#xff1a; 代码如下&#xff1a; #include <iostream> #include <vector> #inclu…...

electron-vite中的ipc通信

1. 概述 再electron中&#xff0c;进程间的通信通过ipcMain和ipcRenderer模块&#xff0c;这些通道是任意和双向的 1.1. 什么是上下文隔离进程 ipc通道是通过预加载脚本绑定到window对象的electron对象属性上的 2. 通信方式 2.1. ipcMain&#xff08;也就是渲染进程向主进…...

探秘网络爬虫的基本原理与实例应用

1. 基本原理 网络爬虫是一种用于自动化获取互联网信息的程序&#xff0c;其基本原理包括URL获取、HTTP请求、HTML解析、数据提取和数据存储等步骤。 URL获取&#xff1a; 确定需要访问的目标网页&#xff0c;通过人工指定、站点地图或之前的抓取结果获取URL。 HTTP请求&#…...

音视频编解码学习记录

目录 学习资料个人git仓库 文章 学习资料 个人git仓库 标准,资料,笔记: https://gitee.com/fedorayang/video_and_audio_codec.git 文章 理解低延迟视频编码的正确姿势: https://cloud.tencent.com/developer/article/1358721...

零基础小白刚刚入门Python的注意点总结~

文章目录 一、注意你的Python版本1.print()函数2.raw_input()与input()3.比较符号&#xff0c;使用!替换<>4.repr函数5.exec()函数 二、新手常遇到的问题1、如何写多行程序&#xff1f;2、如何执行.py文件&#xff1f;3、and&#xff0c;or&#xff0c;not4、True和False…...

从 Context 看 Go 设计模式:接口、封装和并发控制

文章目录 Context 的基本结构Context 的实现和传递机制为什么 Context 不直接传递指针案例&#xff1a;DataStore结论 在 Go 语言中&#xff0c; context 包是并发编程的核心&#xff0c;用于传递取消信号和请求范围的值。但其传值机制&#xff0c;特别是为什么不通过指针传递…...

微信小程序字体大小

微信小程序中可以使用以下CSS样式来设置字体大小&#xff1a; font-size: 12px; // 设置字体大小为12像素在小程序中&#xff0c;可以直接在WXML文件和WXSS文件中使用这个样式。...

L1-062 幸运彩票(Java)

彩票的号码有 6 位数字&#xff0c;若一张彩票的前 3 位上的数之和等于后 3 位上的数之和&#xff0c;则称这张彩票是幸运的。本题就请你判断给定的彩票是不是幸运的。 输入格式&#xff1a; 输入在第一行中给出一个正整数 N&#xff08;≤ 100&#xff09;。随后 N 行&#…...

【计算机网络】2、传输介质、通信方向、通信方式、交换方式、IP地址表示、子网划分

文章目录 传输介质双绞线无屏蔽双绞线UTP屏蔽双绞线STP 网线光纤多模光纤MMF单模光纤SMF 无线信道无线电波红外光波 通信方向单工半双工全双工 通信方式异步传输同步传输串行传输并行传输 交换方式电路交换报文交换分组交换 IP地址表示IP地址的定义IP地址的分类无分类编址特殊I…...

【Linux 内核源码分析】堆内存管理

堆 堆是一种动态分配内存的数据结构&#xff0c;用于存储和管理动态分配的对象。它是一块连续的内存空间&#xff0c;用于存储程序运行时动态申请的内存。 堆可以被看作是一个由各个内存块组成的堆栈&#xff0c;其中每个内存块都有一个地址指针&#xff0c;指向下一个内存块…...

Qt 5.15.2 (MSVC 2019)编译 QWT 6.2.0 : 编译MingW或MSVC遇到的坑

MingW下编译QWt 6.2.0 下载qwt最新版本&#xff0c;用git工具 git clone下载源码 git clone https://git.code.sf.net/p/qwt/git qwt-git 或者使用我下载的 qwt 2.6.0 链接&#xff1a;https://pan.baidu.com/s/1KZI-L10N90TJobeqqPYBqw?pwdpq1o 提取码&#xff1a;pq1o 下载…...

模具制造企业ERP系统有哪些?企业怎么选型适配的软件

模具的生产管理过程比较繁琐&#xff0c;涵盖接单报价、车间排期、班组负荷评估、库存盘点、材料采购、供应商选择、工艺流转、品质检验等诸多环节。 有些采用传统管理手段的模具制造企业存在各业务数据传递不畅、信息滞后、不能及时掌握订单和车间生产情况&#xff0c;难以对…...

管理信息系统知识点复习

目录 一、名词解释题1.企业资源规划(ERP)2.面向对象方法&#xff1a;3.电子健康&#xff1a;4.供应链5.数据挖掘6.“自上而下”的开发策略&#xff1a;7.业务流程重组8.面向对象&#xff1a;9.决策支持系统10.聚类11.集成开发环境&#xff1a;12.供应商协同13.数据仓库14.深度学…...

【Bug】.net6 cap总线+rabbitmq延时消息收不到

文章目录 问题问题代码原因解决处理Bug的具体步骤 问题 我有两个服务一个叫05一个叫15 然后用的cap总线rabbitmq 05消息队列发了一条延时消息&#xff0c;到时间了05服务的订阅者能收到 15服务订阅同一个消息的没收到(cap的cashboard)&#xff08;手动requeue05和15都能收到&a…...

在 Python 中检查一个数字是否是同构数

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 同构数&#xff0c;又称为自守数或自同构数&#xff0c;是一类特殊的数字&#xff0c;它们具有一种有趣的性质&#xff1a;将其平方后的数字&#xff0c;可以通过某种方式重新排列得到原来的数字。本文将详细介绍…...

【 Qt 快速上手】-①- Qt 背景介绍与发展前景

文章目录 1.1 什么是 Qt1.2 Qt 的发展史1.3 Qt 支持的平台1.4 Qt 版本1.5 Qt 的优点1.6 Qt的应用场景1.7 Qt的成功案例1.8 Qt的发展前景及就业分析行业发展方向就业方面的发展前景 1.1 什么是 Qt Qt 是一个跨平台的 C 图形用户界面应用程序框架。它为应用程序开发者提供了建立…...

Kafka-消费者-KafkaConsumer分析-PartitionAssignor

Leader消费者在收到JoinGroupResponse后&#xff0c;会按照其中指定的分区分配策略进行分区分配&#xff0c;每个分区分配策略就是一个PartitionAssignor接口的实现。图是PartitionAssignor的继承结构及其中的组件。 PartitionAssignor接口中定义了Assignment和Subscription两个…...

【办公软件篇】软件启动器Lucy打造自己的工具箱

【办公软件篇】软件启动器Lucy打造自己的工具箱 自从Rolan改为订阅制后就弃用了&#xff0c;本次推荐一款快速启动器Lucy&#xff0c;不联网&#xff0c;不写注册表&#xff0c;体积小&#xff0c;绿色免安装&#xff0c;免费无广告&#xff0c;风格简洁但不简单&#xff0c;目…...

C#MQTT编程08--MQTT服务器和客户端(cmd版)

1、前言 前面完成了winform版&#xff0c;wpf版&#xff0c;为什么要搞个cmd版&#xff0c;因为前面介绍了mqtt的报文结构&#xff0c;重点分析了【连接报文】&#xff0c;【订阅报文】&#xff0c;【发布报文】&#xff0c;这节就要就看看实际报文是怎么组装的&#xff0c;这…...

【高等数学之牛莱公式】

一、深入挖掘定积分 二、变限积分 三、变限积分的"天然"连续性 四、微积分基本定理 五、定积分基本方法 5.1、换元法 5.2、分部积分法 六、定积分经典结论 七、区间再现公式 八、三角函数积分变换公式 九、周期函数积分变换公式 十、分段函数求定积分...

基于HFSS的微带线特性阻抗仿真-与基于FDTD的计算电磁学方法对比(Matlab)

基于HFSS的微带线特性阻抗仿真-与基于FDTD的计算电磁学方法对比&#xff08;Matlab&#xff09; 工程下载&#xff1a; HFSS的微带线特性阻抗仿真工程文件&#xff08;注意版本&#xff1a;HFSS2023R2&#xff09;&#xff1a; https://download.csdn.net/download/weixin_445…...

【SQL】SQL语法小结

相关资料 参考链接1&#xff1a;SQL 语法&#xff08;超级详细&#xff09; 参考链接2&#xff1a;史上超强最常用SQL语句大全 SQL练习网站&#xff1a;CSDN、牛客、LeetCode、LintCode SQL相关视频&#xff1a; 推荐书籍&#xff1a; 文章目录 数据分析对SQL的要求SQL语法简介…...

Open CASCADE学习|显示模型

目录 1、编写代码 Viewer.h Viewer.cpp ViewerInteractor.h ViewerInteractor.cpp helloworld.cpp 2、配置 3、编译运行 1、编写代码 Viewer.h #pragma once ​ #ifdef _WIN32 #include <Windows.h> #endif ​ // Local includes #include "ViewerInteract…...

【C++】string的基本使用

从这篇博客开始&#xff0c;我们的C部分就进入到了STL&#xff0c;STL的出现可以说是C发展历史上非常关键的一步&#xff0c;自此C和C语言有了较为明显的差别。那么什么是STL呢&#xff1f; 后来不断的演化&#xff0c;发展成了知名的两个版本&#xff0c;一个叫做P.J.版本&am…...

vue 里 props 类型为 Object 时设置 default: () => {} 返回的是 undefined 而不是 {}?

问题 今天遇到个小坑&#xff0c;就是 vue 里使用 props 传参类型为 Object 的时候设置 default: () > {} 报错&#xff0c;具体代码如下 <template><div class"pre-archive-info"><template v-if"infoData.kaimo ! null">{{ infoD…...

04 SpringMVC响应数据之页面跳转控制+返回JSON数据+返回静态资源

1. handler方法分析 /*** TODO: 一个controller的方法是控制层的一个处理器,我们称为handler* TODO: handler需要使用RequestMapping/GetMapping系列,声明路径,在HandlerMapping中注册,供DS查找!* TODO: handler作用总结:* 1.接收请求参数(param,json,pathVariable,共享…...

Python圣诞主题绘图:用turtle库打造冬日奇妙画面【第31篇—python:圣诞节】

文章目录 Python圣诞主题绘图导言代码结构概览详细解析drawlight函数tree函数xzs函数drawsnow函数五角星的绘制 完整代码代码解析总结 Python圣诞主题绘图 导言 圣诞季节是个充满欢乐和创意的时刻。在这个技术博客中&#xff0c;我们将深入探讨如何使用Python的turtle库创建一…...