当前位置: 首页 > 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两个…...

网络编程(Modbus进阶)

思维导图 Modbus RTU&#xff08;先学一点理论&#xff09; 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议&#xff0c;由 Modicon 公司&#xff08;现施耐德电气&#xff09;于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查

在对接支付宝API的时候&#xff0c;遇到了一些问题&#xff0c;记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

Java 语言特性(面试系列1)

一、面向对象编程 1. 封装&#xff08;Encapsulation&#xff09; 定义&#xff1a;将数据&#xff08;属性&#xff09;和操作数据的方法绑定在一起&#xff0c;通过访问控制符&#xff08;private、protected、public&#xff09;隐藏内部实现细节。示例&#xff1a; public …...

多场景 OkHttpClient 管理器 - Android 网络通信解决方案

下面是一个完整的 Android 实现&#xff0c;展示如何创建和管理多个 OkHttpClient 实例&#xff0c;分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

Frozen-Flask :将 Flask 应用“冻结”为静态文件

Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是&#xff1a;将一个 Flask Web 应用生成成纯静态 HTML 文件&#xff0c;从而可以部署到静态网站托管服务上&#xff0c;如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解

本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

SpringTask-03.入门案例

一.入门案例 启动类&#xff1a; package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

springboot 日志类切面,接口成功记录日志,失败不记录

springboot 日志类切面&#xff0c;接口成功记录日志&#xff0c;失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...

人工智能 - 在Dify、Coze、n8n、FastGPT和RAGFlow之间做出技术选型

在Dify、Coze、n8n、FastGPT和RAGFlow之间做出技术选型。这些平台各有侧重&#xff0c;适用场景差异显著。下面我将从核心功能定位、典型应用场景、真实体验痛点、选型决策关键点进行拆解&#xff0c;并提供具体场景下的推荐方案。 一、核心功能定位速览 平台核心定位技术栈亮…...