<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>AKTK-Lab</title><description>There is something that I miss in my life.</description><link>https://astro-pure.js.org</link><item><title>Ardupilot part.1: 项目编译和构建</title><link>https://astro-pure.js.org/blog/apm-build</link><guid isPermaLink="true">https://astro-pure.js.org/blog/apm-build</guid><description>以mcu为stm32的飞控板为例，在不同环境下配置构建环境和构建项目</description><pubDate>Tue, 23 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;clone源码：fork一个自己的仓库&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;git clone --recurse-submodules &amp;#x3C;your-fork-url&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Linux / WSL2 环境配置&lt;/h2&gt;
&lt;p&gt;Linux有自己的安装脚本，gcc-arm-none-eabi会有脚本无法安装的情况，自己拿包管理器装就行了，实际测试可以编译成功&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sudo apt-get update
sudo apt-get install git
sudo apt-get install gitk git-gui
Tools/environment_install/install-prereqs-ubuntu.sh -y
~/.profile
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;macOS&lt;/h2&gt;
&lt;p&gt;在开始之前确认安装好 xcode 和 brew，python3&lt;/p&gt;
&lt;h3&gt;建立 python 虚拟环境&lt;/h3&gt;
&lt;p&gt;试过了不能用conda，只能用python的venv，注意不要使用conda的python创建虚拟环境&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;cd ardupilot
conda deactivate
python3 -m venv ardupilot-venv # 建立名字为 ardupilot-venv 的虚拟环境
source ardupilot-venv/bin/activate # 启动虚拟环境
brew install gcc-arm-none-eabi genromfs
brew install gawk
python3 -m pip install empy pyserial
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装mavproxy&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sudo python3 -m pip install wxPython
sudo python3 -m pip install gnureadline
sudo python3 -m pip install billiard
sudo python3 -m pip install numpy pyparsing
sudo python3 -m pip install MAVProxy
python3 -m pip install pexpect
python3 -m pip install dronecan
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;编译构建项目&lt;/h2&gt;
&lt;h3&gt;设置编译选项&lt;/h3&gt;
&lt;p&gt;ardupilot使用waf编译，waf是一个基于 Python 的跨平台构建系统，主要用于自动编译和安装 C/C++ 等代码。waf不需要额外安装，已经包含在ardupilot项目的文件夹里了。&lt;/p&gt;
&lt;p&gt;以编译运行在pixhawk4上的rover代码为例，pixhawk4的MCU是STM32F765，ardupilot运行在ChibiOS(一种RTOS)上&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;./waf configure --board Pixhawk4
Setting top to                           : /home/akatsuki/ardupilot
Setting out to                           : /home/akatsuki/ardupilot/build
Autoconfiguration                        : enabled
Checking for program &apos;python&apos;            : /home/akatsuki/venv-ardupilot/bin/python3
Checking for python version &gt;= 3.9.0     : 3.12.3
Setting board to                         : Pixhawk4
Using toolchain                          : arm-none-eabi
Checking for &apos;g++&apos; (C++ compiler)        : /usr/bin/arm-none-eabi-g++
Checking for &apos;gcc&apos; (C compiler)          : /usr/bin/arm-none-eabi-gcc
Checking for c flags &apos;-MMD&apos;              : yes
Checking for cxx flags &apos;-MMD&apos;            : yes
Checking for program &apos;arm-none-eabi-nm&apos;  : /usr/bin/arm-none-eabi-nm
CXX Compiler                             : g++ 13.2.1
Checking for program &apos;make&apos;              : /usr/bin/make
Checking for program &apos;arm-none-eabi-objcopy&apos; : /usr/bin/arm-none-eabi-objcopy
Processing /home/akatsuki/ardupilot/libraries/AP_HAL_ChibiOS/hwdef/Pixhawk4/hwdef.dat
Processing /home/akatsuki/ardupilot/libraries/AP_HAL_ChibiOS/hwdef/fmuv5/hwdef.dat
Removing PB1
Removing PB1
Removing PC7
Removing PC7
Removing HAL_BATT_VOLT_SCALE
Removing HAL_BATT_VOLT_SCALE
Removing HAL_BATT_CURR_SCALE
Removing HAL_BATT_CURR_SCALE
Setup for MCU STM32F767xx
Default parameters path from hwdef: /home/akatsuki/ardupilot/libraries/AP_HAL_ChibiOS/hwdef/Pixhawk4/defaults.parm
Writing hwdef setup in /home/akatsuki/ardupilot/build/Pixhawk4/hwdef.h
MCU Flags: cortex-m7 [&apos;-mcpu=cortex-m7&apos;, &apos;-mfpu=fpv5-d16&apos;, &apos;-mfloat-abi=hard&apos;]
args=[&apos;AP_Baro_MS5611::probe&apos;, &apos;&quot;ms5611&quot;&apos;]
Writing DMA map
Setting up as normal
Generating ldscript.ld
env set ENABLE_DFU_BOOT=0
env set WITH_FATFS=1
env set PROCESS_STACK=0x1C00
env set MAIN_STACK=0x600
env set IOMCU_FW=0
env set IOMCU_FW_WITH_HEATER=0
env set PERIPH_FW=0
env set HAL_NUM_CAN_IFACES=2
env set HAL_CANFD_SUPPORTED=0
env set BOARD_FLASH_SIZE=2048
env set EXT_FLASH_SIZE_MB=0
env set INT_FLASH_PRIMARY=False
env set ENABLE_CRASHDUMP=True
env set CPU_FLAGS=[&apos;-mcpu=cortex-m7&apos;, &apos;-mfpu=fpv5-d16&apos;, &apos;-mfloat-abi=hard&apos;, &apos;-DARM_MATH_CM7&apos;, &apos;-u_printf_float&apos;]
env set CORTEX=cortex-m7
env set APJ_BOARD_ID=50
env set APJ_BOARD_TYPE=STM32F767xx
env set USBID=0x1209/0x5740
env set FLASH_RESERVE_START_KB=32
env set EXT_FLASH_RESERVE_START_KB=0
env set FLASH_TOTAL=2064384
env set HAS_EXTERNAL_FLASH_SECTIONS=0
env set ROMFS_FILES=[(&apos;io_firmware.bin&apos;, &apos;Tools/IO_Firmware/iofirmware_lowpolh.bin&apos;), (&apos;io_firmware_dshot.bin&apos;, &apos;Tools/IO_Firmware/iofirmware_dshot_lowpolh.bin&apos;), (&apos;defaults.parm&apos;, &apos;/home/akatsuki/ardupilot/build/Pixhawk4/processed_defaults.parm&apos;), (&apos;hwdef.dat&apos;, &apos;/home/akatsuki/ardupilot/build/Pixhawk4/hw.dat&apos;), (&apos;bootloader.bin&apos;, &apos;/home/akatsuki/ardupilot/Tools/bootloaders/Pixhawk4_bl.bin&apos;)]
env set CHIBIOS_BUILD_FLAGS=USE_FATFS=yes CHIBIOS_STARTUP_MK=os/common/startup/ARMCMx/compilers/GCC/mk/startup_stm32f7xx.mk CHIBIOS_PLATFORM_MK=os/hal/ports/STM32/STM32F7xx/platform.mk MCU=cortex-m7 ENV_UDEFS=-DCHPRINTF_USE_FLOAT=1
Enabling ChibiOS asserts                     : no
Disabling Watchdog                           : no
Enabling malloc guard                        : no
Enabling ChibiOS thread statistics           : no
Enabling -Werror                             : no
Checking for intelhex module:                : OK
Enabled OpenDroneID                          : no
Enabled firmware ID checking                 : no
GPS Debug Logging                            : no
Enabled custom controller                    : no
Checking for HAVE_CMATH_ISFINITE             : yes
Checking for HAVE_CMATH_ISINF                : yes
Checking for HAVE_CMATH_ISNAN                : yes
Checking for NEED_CMATH_ISFINITE_STD_NAMESPACE : yes
Checking for NEED_CMATH_ISINF_STD_NAMESPACE    : yes
Checking for NEED_CMATH_ISNAN_STD_NAMESPACE    : yes
Checking for header endian.h                   : not found
Checking for header byteswap.h                 : not found
Checking for HAVE_MEMRCHR                      : no
Configured VSCode Intellisense:                : no
DC_DSDL compiler in                            : /home/akatsuki/ardupilot/modules/DroneCAN/dronecan_dsdlc
Source is git repository                       : yes
Update submodules                              : yes
Checking for program &apos;git&apos;                     : /usr/bin/git
Gtest                                          : STM32 boards currently don&apos;t support compiling gtest
Checking for program &apos;arm-none-eabi-size&apos;      : /usr/bin/arm-none-eabi-size
Benchmarks                                     : disabled
Unit tests                                     : disabled
Scripting                                      : maybe
Scripting runtime checks                       : enabled
Debug build                                    : disabled
Coverage build                                 : disabled
Consistent build                               : disabled
Force 32-bit build                             : disabled
Checking for program &apos;rsync&apos;                   : /usr/bin/rsync
Removing target_list file /home/akatsuki/ardupilot/build/Pixhawk4/target_list
&apos;configure&apos; finished successfully (2.935s)         
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编译构建后产生build summary&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;./waf rover # 编译rover
Waf: Leaving directory `/home/akatsuki/ardupilot/build/Pixhawk4&apos;

BUILD SUMMARY
Build directory: /home/akatsuki/ardupilot/build/Pixhawk4
Target         Text (B)  Data (B)  BSS (B)  Total Flash Used (B)  Free Flash (B)  External Flash Used (B)
---------------------------------------------------------------------------------------------------------
bin/ardurover   1425312      2776   126076               1428088          636292  Not Applicable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来看一下编译之后都生成了什么&lt;/p&gt;
&lt;h2&gt;编译构建过程&lt;/h2&gt;
&lt;p&gt;产生的 &lt;code&gt;build/Pixhawk4&lt;/code&gt; 文件内容大致如下&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;.
├── Rover
├── bin
├── lib
├── libraries
│   ├── AC_AttitudeControl
│   ├── AC_Avoidance
│   ...
└── modules
    ├── ChibiOS
    │   ├── lst
    │   └── obj
    ├── DroneCAN
    │   └── libcanard
    │       └── dsdlc_generated
    │           ├── include
    │           └── src
    └── lwip
        └── src
            ├── api
            ├── core
            │   └── ipv4
            └── netif
                └── ppp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;compile_commands.json&lt;/code&gt; 也在该目录下，在项目的 &lt;code&gt;.vscode&lt;/code&gt; 目录中建立工作区配置 &lt;code&gt;settings.json&lt;/code&gt; 加入这个目录，就能让clangd找到头文件&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
    &quot;clangd.arguments&quot;: [
        &quot;--compile-commands-dir=build/Pixhawk4&quot;
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编译时，&lt;code&gt;modules/ChibiOS&lt;/code&gt; 里的内核、HAL 和 STM32 驱动会先编译成 &lt;code&gt;obj&lt;/code&gt; 目录下的大量 &lt;code&gt;.o&lt;/code&gt; 文件，然后与 Rover 模块、&lt;code&gt;AP_GPS&lt;/code&gt;、&lt;code&gt;AP_HAL&lt;/code&gt; 等 ArduPilot 库一起链接生成 &lt;code&gt;bin&lt;/code&gt; 目录下最终的 &lt;code&gt;ardurover.hex&lt;/code&gt; 和 apj 固件&lt;/p&gt;
&lt;p&gt;&lt;code&gt;modules&lt;/code&gt; 除了ChibiOS还有DroneCAN（基于CAN的无人机开源通信协议）和lwIP（轻量级 TCP/IP 协议栈），ChibiOS 负责线程和驱动，DroneCAN 负责 CAN 设备通信，lwIP 负责 TCP/IP 网络协议栈&lt;/p&gt;
&lt;h3&gt;硬件描述文件 &lt;code&gt;hwdef&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;源码中 &lt;code&gt;AP_HAL/AP_HAL_ChibiOS/Pixhawk4&lt;/code&gt; 的硬件描述文件 &lt;code&gt;hwdef.dat&lt;/code&gt; 会编译成 &lt;code&gt;build/Pixhawk4/hwdef.h&lt;/code&gt;，在代码中头文件也会引入刚才编译生成的 &lt;code&gt;hwdef.h&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;该文件定义了晶振、内存区、描述外设信息的设备表、外设引脚设置等&lt;/p&gt;
&lt;h2&gt;在wsl2使用SITL进行模拟&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ardupilot.org/dev/_images/ArdupilotSoftwareintheLoopSITL.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;SITL(Software in the Loop)可以在不需要任何硬件的条件下模拟ardupilot，主要用于测试算法等。ardupilot通过TCP5763端口通过TCP5760端口直接和Mission Planner地面站进行通信，也可以使用MAVProxy协议进行通信，再通过UDP14550和其他地面站例如QGroundControl进行通信。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ardupilot.org/dev/docs/sitl-examples.html&quot;&gt;Example of using SITL by Vehicle&lt;/a&gt;提供了多种使用SITL模拟的案例。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;./waf configure --board sitl
cd Rover
../Tools/autotest/sim_vehicle.py --map --console --no-wsl2-network # wsl配置镜像代理
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时在构建完的目标在 &lt;code&gt;build/sitl&lt;/code&gt; 中，这边使用QGroundControl做地面站，QGroundControl运行在宿主机中，ardupilot在wsl2内编译构建。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://astro-pure.js.org/_vercel/image?url=_astro%2FQGC.Ckv-0YHp.png&amp;#x26;w=828&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://astro-pure.js.org/_vercel/image?url=_astro%2FQGC2.CcVO4UZB.png&amp;#x26;w=828&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;使用SITL运行仓库提供的 &lt;code&gt;example&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;https://ardupilot.org/dev/docs/using-sitl-for-ardupilot-testing.html#using-sitl-for-ardupilot-testing&lt;/p&gt;
&lt;p&gt;查看仓库中可用的测试样例&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;./waf list | grep &apos;examples&apos;

examples/AC_PID_test 
examples/AHRS_Test 
examples/AP_Common 
examples/AP_Compass_test 
examples/AP_Declination_test 
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;构建example的过程参照下面部分&lt;/p&gt;
&lt;h2&gt;第一个ardupilot程序： &lt;code&gt;example/Hello&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;以构建 &lt;code&gt;Tools/Hello/Hello.cpp&lt;/code&gt; 为例，该测试样例相当于ardupilot的hello world，主要功能是每隔1000ms（模拟器时间）打印一个 &lt;code&gt;*&lt;/code&gt;，此外还有别的可以用于测试的程序例如 &lt;code&gt;UART_test.cpp&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;/*
  simple hello world sketch
  Andrew Tridgell September 2011
*/

#include &amp;#x3C;AP_HAL/AP_HAL.h&gt;

void setup();
void loop();

const AP_HAL::HAL&amp;#x26; hal = AP_HAL::get_HAL();

void setup()
{
    hal.console-&gt;printf(&quot;hello world\n&quot;);
}

void loop()
{
    hal.scheduler-&gt;delay(1000);
    hal.console-&gt;printf(&quot;*\n&quot;);
}

AP_HAL_MAIN();

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;运行&lt;/h3&gt;
&lt;p&gt;设置编译选项后构建，然后运行&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;./waf configure --board=sitl
./waf build --target examples/Hello
./build/sitl/examples/Hello -M quad -C # 运行
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到在终端中很快的打印出 &lt;code&gt;*&lt;/code&gt; 字符，比1s快很多。因为 &lt;code&gt;delay(1000)&lt;/code&gt; 表示延迟1000ms，但是等待的不是实际时间，是HAL的时间(Scheduler Time)，在SITL中这个时间来自模拟器，如果模拟器速度是100x则现实中只需要等待10ms。该样例仅用于测试 &lt;code&gt;setup()&lt;/code&gt; 和 &lt;code&gt;loop()&lt;/code&gt; 是否正常。&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://roackb2.medium.com/%E6%95%99%E4%BD%A0%E5%BE%9E%E7%84%A1%E5%88%B0%E6%9C%89%E9%96%8B%E5%A7%8B%E8%87%AA%E5%B7%B1-build-ardupilot-%E9%A3%9B%E6%8E%A7%E7%A8%8B%E5%BC%8F%E7%A2%BC-macos-c17d81cd9326&quot;&gt;教你從無到有開始自己 build ArduPilot 飛控程式碼！(MacOS)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ardupilot.org/dev/docs/building-setup-mac.html#building-setup-mac&quot;&gt;# Setting up the Build Environment (MacOSX)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ArduPilot/ardupilot/blob/master/BUILD.md&quot;&gt;ardupilot/BUILD.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ardupilot.org/dev/docs/sitl-on-windows-wsl.html&quot;&gt;SITL on Windows using WSL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ardupilot.org/dev/docs/learning-ardupilot-the-example-sketches.html&quot;&gt;Library Example Sketches&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Qt part.1: Qt的基础概念和常用机制</title><link>https://astro-pure.js.org/blog/qt-basic</link><guid isPermaLink="true">https://astro-pure.js.org/blog/qt-basic</guid><description>Qt的基础机制</description><pubDate>Sat, 06 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;信号槽机制&lt;/h2&gt;
&lt;p&gt;信号槽是一种类型安全的&lt;strong&gt;对象间通信的机制&lt;/strong&gt;。当一个对象状态发生变化时，发出一个信号 &lt;code&gt;signal&lt;/code&gt;通知事件，其他对象可以连接这个信号到一个 &lt;code&gt;slot&lt;/code&gt; 槽函数上响应这些事件，从而实现松耦合的通信。信号槽机制替代传统的、不安全的回调函数（如函数指针），槽函数可以是虚函数。&lt;/p&gt;
&lt;p&gt;实现原理：单线程主要是由观察者模式与函数指针的联合使用，多线程本质上是事件机制&lt;/p&gt;
&lt;p&gt;连接方法：UI信号槽自动连接、Lamda表达式、自定义信号槽&lt;/p&gt;
&lt;p&gt;连接方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;自动连接(Auto Connection)&lt;/strong&gt; ：默认方式，Qt 会自动决定信号与槽是同步还是异步调用，取决于信号和槽是否在同一线程。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;直接连接(Direct Connection)&lt;/strong&gt; ：信号发出时直接在发出者所在的线程中调用槽。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;队列连接(Queued Connection)&lt;/strong&gt; ：将信号的调用排入目标对象线程的事件队列，槽会在目标线程中执行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;阻塞队列连接(BlockingQueuedConnection)&lt;/strong&gt; :（信号和槽必须在不同的线程中，否则就产生死锁）：这个是完全同步队列，只有槽线程执行完成才会返回，否则发送线程也会一直等待，相当于是不同的线程可以同步起来执行&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;独立连接(Unique Connection)&lt;/strong&gt; ：确保信号和槽之间的连接是唯一的，避免重复连接。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;松耦合&lt;/strong&gt; ：信号与槽之间不直接依赖，便于扩展和修改。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;线程安全&lt;/strong&gt; ：在多线程环境下，Qt 会自动处理信号与槽的同步。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;易于维护&lt;/strong&gt; ：信号与槽的方式使得代码结构清晰，维护性好。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;缺点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;性能开销&lt;/strong&gt; ：信号和槽的调用机制会增加一定的性能开销。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;调试困难&lt;/strong&gt; ：如果连接不当，可能会导致难以追踪的问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个信号可以连接多个槽，信号发射时，所有连接的槽会依次被调用。一个槽可以响应多个信号，只要它的参数签名与信号匹配。&lt;/p&gt;
&lt;p&gt;自定义信号和槽：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;自定义信号：在类的 &lt;code&gt;signals&lt;/code&gt; 部分使用 &lt;code&gt;signals&lt;/code&gt; 关键字定义&lt;/li&gt;
&lt;li&gt;自定义槽：在类的 &lt;code&gt;public slots&lt;/code&gt; 或 &lt;code&gt;private slots&lt;/code&gt; 部分定义&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;元对象系统(Meta-Object System)&lt;/h2&gt;
&lt;p&gt;https://www.bilibili.com/video/BV12GqWYkEZ4/?spm_id_from=333.337.search-card.all.click&amp;#x26;vd_source=9d28f0e4734f1bde4c84c3169e3a429d&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;元对象系统&lt;/strong&gt;是 Qt 提供的一种机制，用于支持信号与槽、动态属性、类型识别等功能。
它依赖于 &lt;code&gt;Q_OBJECT&lt;/code&gt; 宏，并通过元对象编译器(MOC)生成元对象信息，使得 Qt 可以在运行时获取对象的类型信息。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;组成：&lt;code&gt;QObject&lt;/code&gt; 类，MOC，信号与槽机制&lt;/li&gt;
&lt;li&gt;作用：提供信号槽机制实现对象之间的高效通信，动态属性、类型识别的功能&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;code&gt;Q_OBJECT&lt;/code&gt; 宏&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Q_OBJECT&lt;/code&gt; 宏启用 Qt 的元对象系统，支持信号与槽机制、动态属性、对象反射等功能。每个使用信号与槽的类都必须声明该宏，以便 MOC 生成相关的元对象代码。&lt;/p&gt;
&lt;h3&gt;元对象编译器(MOC)&lt;/h3&gt;
&lt;p&gt;MOC 是 Qt 提供的工具，负责处理含有 &lt;code&gt;Q_OBJECT&lt;/code&gt; 宏的类。它会生成包含元对象信息的代码，以支持信号与槽、动态属性等功能。生成的代码会被编译并链接到最终的应用程序中。&lt;/p&gt;
&lt;h2&gt;内存管理&lt;/h2&gt;
&lt;p&gt;Qt 使用 &lt;strong&gt;父子对象机制&lt;/strong&gt; 来自动管理内存。每个对象如果有父对象，父对象会在销毁时自动销毁所有子对象。没有父对象的对象需要手动调用 &lt;code&gt;delete&lt;/code&gt; 来销毁&lt;/p&gt;
&lt;h3&gt;对象树机制&lt;/h3&gt;
&lt;p&gt;Qt 的 &lt;strong&gt;对象树机制&lt;/strong&gt; 是一种父子对象关系管理方式。每个 &lt;code&gt;QObject&lt;/code&gt; 对象都有一个父对象，父对象会自动销毁其所有子对象。父子关系在内存管理中非常重要，可以简化资源管理，避免手动 &lt;code&gt;delete&lt;/code&gt; 。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;父对象销毁了子对象一起被销毁，避免内存泄漏&lt;/li&gt;
&lt;li&gt;智能指针 &lt;code&gt;QSharedPointer&lt;/code&gt; &lt;code&gt;QWeakPointer&lt;/code&gt;，处理对象之间引用的复杂情况&lt;/li&gt;
&lt;li&gt;内存分配策略及优化&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;code&gt;deleteLater()&lt;/code&gt; 延迟销毁对象&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;deleteLater()&lt;/code&gt; 函数将对象的销毁操作推迟到当前事件处理完毕后执行，避免在事件处理过程中删除对象引起的崩溃或未定义行为。常用于异步删除对象。&lt;/p&gt;
&lt;h2&gt;事件处理机制&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Qt 的事件机制基于事件循环和事件队列&lt;/strong&gt;，所有事件都会先进入队列，然后依次分发到合适的控件。&lt;/p&gt;
&lt;p&gt;事件传递的顺序：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;事件首先由事件源对象接收并放入事件队列&lt;/li&gt;
&lt;li&gt;事件队列中的事件会依次传递给目标对象，首先传递到事件接收对象，每个继承 &lt;code&gt;QObject&lt;/code&gt; 的都可以通过重写事件处理函数来处理特定的事件&lt;/li&gt;
&lt;li&gt;如果该对象处理了事件，则停止传递，否则，事件会传递到该对象的父对象，直到根对象&lt;/li&gt;
&lt;li&gt;如果事件一直没有被处理，Qt会使用默认的事件处理机制&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;自定义事件处理：继承 &lt;code&gt;QEvent&lt;/code&gt; 类&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;event()&lt;/code&gt; 和 &lt;code&gt;eventFilter()&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;event()&lt;/code&gt; 是每个 &lt;code&gt;QObject&lt;/code&gt; 的成员函数，主要用于处理传递给对象的事件。可以通过重写此函数来处理不同类型的事件（如鼠标、键盘等）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eventFilter()&lt;/code&gt; 用于设置事件过滤器，拦截传递到目标控件的事件，在事件到达目标控件前对其进行处理。它是通过安装到目标对象上的方式来拦截事件的&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;事件过滤器的实现&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;创建一个类继承 &lt;code&gt;QObject&lt;/code&gt; 并重写 &lt;code&gt;eventFilter()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;installEventFilter()&lt;/code&gt; 方法将事件过滤器安装到目标控件上，这样目标控件的事件就会先经过过滤器&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;GUI常用事件函数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;paintEvent()&lt;/code&gt; ：用于绘制控件的外观，当控件需要重新绘制时（如大小变化或需要更新显示）会调用此函数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;resizeEvent()&lt;/code&gt; ：当控件的大小发生变化时调用，通常用来调整控件的布局或重新绘制内容。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mousePressEvent()&lt;/code&gt; ：当鼠标按下时调用，用于处理鼠标点击事件，如点击按钮或拖动控件等。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;事件循环&lt;/h2&gt;
&lt;p&gt;Qt 的事件循环机制是指程序启动后，通过一个循环不断从事件队列中取出事件并分发处理的过程。&lt;/p&gt;
&lt;p&gt;事件循环由 &lt;code&gt;QEventLoop&lt;/code&gt; 实现，应用程序启动后进入事件循环，只有在事件循环运行时，Qt 的界面刷新、信号槽、定时器等机制才能正常工作。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;exec()&lt;/code&gt; 启动事件循环&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;QApplication::exec()&lt;/code&gt; ：启动整个应用程序的主事件循环&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QDialog::exec()&lt;/code&gt; ：启动一个局部事件循环，使对话框以模态方式运行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当调用 &lt;code&gt;exec()&lt;/code&gt; 后，程序会阻塞在此函数中，直到事件循环结束，函数才会返回&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;QCoreApplication::processEvents()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;手动处理当前事件队列中的事件，常用于在耗时操作中避免界面卡死 / 强制刷新界面 / 临时处理待处理的事件，它会立即处理当前队列中的事件，但不会进入完整的事件循环&lt;/p&gt;
&lt;p&gt;缺点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;频繁调用可能导致逻辑混乱&lt;/li&gt;
&lt;li&gt;不能替代事件循环&lt;/li&gt;
&lt;li&gt;更推荐使用多线程或异步方式解决耗时问题&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;定时器&lt;/h2&gt;
&lt;h3&gt;定时器的实现方式&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;QTimer&lt;/code&gt; 最常用的定时器方式，基于事件循环，支持单次和周期定时&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QObject::startTimer()&lt;/code&gt; / &lt;code&gt;timerEvent()&lt;/code&gt; 通过对象启动一个底层定时器，在 &lt;code&gt;timerEvent()&lt;/code&gt; 中处理超时事件，适合轻量级定时需求&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QBasicTimer&lt;/code&gt; 是对 &lt;code&gt;startTimer()&lt;/code&gt; 的封装，效率较高，常用于对性能要求较高的场景&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QElapsedTimer&lt;/code&gt; 用于测量时间间隔，不触发超时事件，主要用于性能统计和耗时测量&lt;/li&gt;
&lt;li&gt;系统定时器（结合线程）在子线程中配合 &lt;code&gt;QTimer&lt;/code&gt; 或系统接口实现更高精度的定时控制&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;&lt;code&gt;QTimer&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;QTimer&lt;/code&gt; 是 Qt 中最常用的定时器类，使用方式主要包括以下几步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建 &lt;code&gt;QTimer&lt;/code&gt; 对象&lt;/li&gt;
&lt;li&gt;连接 &lt;code&gt;timeout()&lt;/code&gt; 信号到槽函数&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;start()&lt;/code&gt; 启动定时器，设置时间间隔（毫秒）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;QTimer&lt;/code&gt; 定时器启动后，每到达设定的时间间隔就会触发 &lt;code&gt;timeout&lt;/code&gt; 信号，在槽函数中处理定时任务&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;QTimer timer; // 定时器定时刷新
QObject::connect(&amp;#x26;timer, &amp;#x26;QTimer::timeout, timer_task);
timer.start(5000); // 5s 执行一次 事件驱动
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以使用 &lt;code&gt;setSingleShot(true)&lt;/code&gt; 设置为单次定时器，也可以使用静态函数 &lt;code&gt;QTimer::singleShot()&lt;/code&gt; 实现一次性延时执行&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;startTimer()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;startTimer()&lt;/code&gt; 是 &lt;code&gt;QObject&lt;/code&gt; 提供的底层接口，而 &lt;code&gt;QTimer&lt;/code&gt; 是对定时器机制的高级封装。&lt;/p&gt;
&lt;p&gt;主要区别有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用方式不同：&lt;code&gt;startTimer()&lt;/code&gt; 需要重写 &lt;code&gt;timerEvent()&lt;/code&gt; 来处理定时事件，&lt;code&gt;QTimer&lt;/code&gt; 使用信号与槽机制&lt;/li&gt;
&lt;li&gt;功能丰富程度不同：&lt;code&gt;QTimer&lt;/code&gt; 支持单次定时、暂停、重启等功能，&lt;code&gt;startTimer()&lt;/code&gt; 功能相对简单&lt;/li&gt;
&lt;li&gt;可读性和维护性：&lt;code&gt;QTimer&lt;/code&gt; 更符合 Qt 编程风格，&lt;code&gt;startTimer()&lt;/code&gt; 更偏底层，适合对性能敏感的场景&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一般情况下推荐使用 &lt;code&gt;QTimer&lt;/code&gt; ，除非有特殊性能或底层控制需求。&lt;/p&gt;
&lt;h2&gt;隐式共享&lt;/h2&gt;
&lt;p&gt;对于两份内容相同的数据，不用重新开辟一份内存来存储，而是使用一份浅拷贝即只拷贝一个指针，这样两份数据就共用一份内存和数据，浅拷贝一次引用计数就增加，计数变成0的时候就删除共享的数据。&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Qt part.3: 网络编程 / 数据库编程</title><link>https://astro-pure.js.org/blog/qt-db-network</link><guid isPermaLink="true">https://astro-pure.js.org/blog/qt-db-network</guid><description>直接调API不好吗</description><pubDate>Sat, 06 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;网络编程&lt;/h2&gt;
&lt;h3&gt;网络编程常用类&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;QTcpSocket&lt;/code&gt; / &lt;code&gt;QUdpSocket&lt;/code&gt; ：用于 TCP / UDP 网络通信的客户端套接字&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QTcpServer&lt;/code&gt; ：用于创建 TCP 服务器，监听客户端连接&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QNetworkAccessManager&lt;/code&gt; ：用于发起和管理网络请求，支持 HTTP、HTTPS、FTP 等协议。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QNetworkReply&lt;/code&gt; ：封装来自 &lt;code&gt;QNetworkAccessManager&lt;/code&gt; 的响应信息。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QHostAddress&lt;/code&gt; ：用于表示 IP 地址。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QNetworkConfigurationManager&lt;/code&gt; ：用于管理网络配置，如网络接口、连接状态等。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QWebSocket&lt;/code&gt; ：用于 WebSocket 通信的客户端和服务器。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些类提供了方便的接口来实现各种类型的网络功能，包括 socket 通信、HTTP 请求、FTP 操作等。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;QTcpSocket&lt;/code&gt; / &lt;code&gt;QUdpSocket&lt;/code&gt;：套接字&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;QTcpSocket&lt;/code&gt;：TCP（Transmission Control Protocol） 是一种面向连接的协议，保证数据的可靠性和顺序性。使用场景：需要可靠、顺序传输数据的应用，如聊天应用、文件传输、数据库连接等。保证数据传输的顺序和完整性，适合需要持久连接的应用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;QUdpSocket&lt;/code&gt;：UDP（User Datagram Protocol） 是一种无连接的协议，数据包可以乱序到达且可能丢失。使用场景：对实时性要求高但对数据丢失容忍度较高的应用，如视频流、实时游戏、VoIP 等。适合大规模、多点广播的应用，但不保证数据的顺序和可靠性。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;使用 &lt;code&gt;QTcpServer&lt;/code&gt; 实现服务器&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;创建一个 &lt;code&gt;QTcpServer&lt;/code&gt; 对象并调用 &lt;code&gt;listen()&lt;/code&gt; 方法启动监听。&lt;/li&gt;
&lt;li&gt;连接 &lt;code&gt;newConnection()&lt;/code&gt; 信号，当有客户端连接时接收到此信号。&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;newConnection()&lt;/code&gt; 里创建 &lt;code&gt;QTcpSocket&lt;/code&gt; 对象来与客户端通信。&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;QTcpSocket&lt;/code&gt; 的 &lt;code&gt;read()&lt;/code&gt; 和 &lt;code&gt;write()&lt;/code&gt; 方法进行数据收发。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;QTcpServer *server = new QTcpServer(this);
if (server-&gt;listen(QHostAddress::Any, 1234)) {
    connect(server, &amp;#x26;QTcpServer::newConnection, this, &amp;#x26;MyServer::onNewConnection);
}

void MyServer::onNewConnection() {
    QTcpSocket *clientSocket = server-&gt;nextPendingConnection();
    connect(clientSocket, &amp;#x26;QTcpSocket::readyRead, this, &amp;#x26;MyServer::onReadyRead);
}

void MyServer::onReadyRead() {
    QTcpSocket *clientSocket = qobject_cast&amp;#x3C;QTcpSocket*&gt;(sender());
    QByteArray data = clientSocket-&gt;readAll();
    // 处理收到的数据
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;QNetworkAccessManager&lt;/code&gt;：管理和发起网络请求的类&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;QNetworkAccessManager&lt;/code&gt; 是 Qt 网络编程中用于管理和发起网络请求的类。它支持多种协议（如 HTTP、HTTPS、FTP、FTPS）并可以进行 GET、POST 等请求。&lt;/p&gt;
&lt;p&gt;作用：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;发送 HTTP/HTTPS 请求，并返回 &lt;code&gt;QNetworkReply&lt;/code&gt; 对象&lt;/li&gt;
&lt;li&gt;支持异步网络请求，通过信号与槽机制接收请求结果&lt;/li&gt;
&lt;li&gt;提供对上传和下载的控制（如设置请求头、发送表单数据等）&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;QNetworkAccessManager *manager = new QNetworkAccessManager(this);
QNetworkRequest request(QUrl(&quot;http://example.com&quot;));
QNetworkReply *reply = manager-&gt;get(request);

connect(reply, &amp;#x26;QNetworkReply::finished, this, &amp;#x26;MyClass::onFinished);

void MyClass::onFinished() {
    QNetworkReply *reply = qobject_cast&amp;#x3C;QNetworkReply*&gt;(sender());
    QByteArray data = reply-&gt;readAll();
    // 处理响应数据
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;QNetworkAccessManager&lt;/code&gt; 还允许设置请求头、处理 SSL/TLS 加密等功能&lt;/p&gt;
&lt;h3&gt;Websocket&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;QWebSocket&lt;/code&gt; 类提供了对 WebSocket 协议的支持，用于进行双向通信。WebSocket 适用于实时消息推送应用，如在线聊天、实时数据流等。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;客户端：使用 &lt;code&gt;QWebSocket&lt;/code&gt; 连接到 WebSocket 服务器。可以通过 &lt;code&gt;sendTextMessage()&lt;/code&gt; 发送消息，&lt;code&gt;textMessageReceived()&lt;/code&gt; 信号接收消息。&lt;/li&gt;
&lt;li&gt;服务器端：使用 &lt;code&gt;QWebSocketServer&lt;/code&gt; 启动 WebSocket 服务器，监听客户端连接。当连接建立后，使用 &lt;code&gt;QWebSocket&lt;/code&gt; 进行通信。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;QWebSocket *socket = new QWebSocket();
socket-&gt;open(QUrl(&quot;ws://example.com&quot;));

connect(socket, &amp;#x26;QWebSocket::textMessageReceived, this, &amp;#x26;MyClass::onMessageReceived);
connect(socket, &amp;#x26;QWebSocket::disconnected, this, &amp;#x26;MyClass::onDisconnected);

void MyClass::onMessageReceived(const QString &amp;#x26;message) {
    // 处理收到的消息
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;处理数据的粘包和拆包&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;粘包&lt;/strong&gt;和&lt;strong&gt;拆包&lt;/strong&gt;是网络编程中的常见问题，通常发生在基于流的协议（如 TCP）中，解决这个问题的常见方法是使用自定义的协议进行数据分隔和解析。&lt;/p&gt;
&lt;p&gt;常见解决方案：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;固定长度数据包：每个数据包有固定的大小，接收方按固定长度读取。优点：实现简单，适合数据量固定的场景。缺点：不能处理长度变化的数据。&lt;/li&gt;
&lt;li&gt;使用分隔符：数据包之间使用特定的字符（如 &lt;code&gt;\n&lt;/code&gt; 或 &lt;code&gt;\0&lt;/code&gt;）作为分隔符，接收方根据分隔符划分数据包。优点：适用于变长数据包。缺点：需要确保分隔符不会出现在数据中。&lt;/li&gt;
&lt;li&gt;数据包头部包含长度信息：在每个数据包的头部添加数据包长度信息，接收方先读取长度字段，再根据长度读取完整数据。优点：适用于变长数据。缺点：需要管理长度字段。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;数据库编程&lt;/h2&gt;
&lt;h3&gt;连接数据库&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;加载数据库驱动&lt;/strong&gt; ：Qt 支持多种数据库（如 SQLite、MySQL、PostgreSQL 等）。需要加载适当的数据库驱动。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;创建&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;QSqlDatabase&lt;/code&gt;&lt;/strong&gt; &lt;strong&gt;对象&lt;/strong&gt; ：通过 &lt;code&gt;QSqlDatabase::addDatabase()&lt;/code&gt; 来创建一个数据库连接对象，并设置连接类型（例如 SQLite、MySQL）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;设置连接参数&lt;/strong&gt; ：配置数据库连接的参数，如数据库文件路径、用户名、密码等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;打开数据库&lt;/strong&gt; ：调用 &lt;code&gt;open()&lt;/code&gt; 方法来打开数据库连接，并检查连接是否成功。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;void MainWindow::open_db() {
    db = QSqlDatabase::addDatabase(&quot;QSQLITE&quot;);
    db.setDatabaseName(&quot;/Users/akatsuki/account.db&quot;);
    if (db.open()) {
        qDebug() &amp;#x3C;&amp;#x3C; &quot;open db success&quot;;
    } else {
        qDebug() &amp;#x3C;&amp;#x3C; db.lastError().text();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;操作 SQLite 数据库&lt;/h3&gt;
&lt;p&gt;SQLite 是一种轻量级的嵌入式数据库，不需要安装额外的数据库服务，适合存储本地数据。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;配置数据库驱动&lt;/strong&gt; ：确保 SQLite 驱动已启用（Qt 默认支持 SQLite）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;创建数据库连接&lt;/strong&gt; ：通过 &lt;code&gt;QSqlDatabase::addDatabase(&quot;QSQLITE&quot;)&lt;/code&gt; 来连接 SQLite 数据库，并指定数据库文件路径。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;执行 SQL 语句&lt;/strong&gt; ：使用 &lt;code&gt;QSqlQuery&lt;/code&gt; 执行 SQL 语句进行查询或操作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用模型（如&lt;code&gt;QSqlTableModel&lt;/code&gt;）&lt;/strong&gt; ：可以通过 &lt;code&gt;QSqlTableModel&lt;/code&gt; 直接操作数据库中的表格，简化数据展示和交互。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;事务处理&lt;/h3&gt;
&lt;p&gt;https://www.bilibili.com/video/BV17qKbzGEW7/?spm_id_from=333.337.search-card.all.click&amp;#x26;vd_source=9d28f0e4734f1bde4c84c3169e3a429d&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数据库的事务处理&lt;/strong&gt;：作为单个逻辑工作单元执行的一系列数据库操作。这些操作要么全部成功要么全部失败回滚，绝不存在“部分成功”的中间状态，以此来保证数据的完整性和一致性。事务处理可以通过 &lt;code&gt;QSqlDatabase&lt;/code&gt; 提供的接口实现。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;开始事务：调用 &lt;code&gt;QSqlDatabase::transaction()&lt;/code&gt; 来启动一个事务。这会确保后续的所有数据库操作都被视为一个单独的事务。&lt;/li&gt;
&lt;li&gt;执行数据库操作：在事务中进行一系列数据库操作（如插入、更新、删除等）。这些操作不会立即提交到数据库中。&lt;/li&gt;
&lt;li&gt;提交事务：如果所有操作都成功执行，调用 &lt;code&gt;QSqlDatabase::commit()&lt;/code&gt; 来提交事务，将所有变更保存到数据库。&lt;/li&gt;
&lt;li&gt;回滚事务：如果在操作过程中出现错误，可以调用 &lt;code&gt;QSqlDatabase::rollback()&lt;/code&gt; 来回滚事务，撤销所有变更，恢复到事务开始时的状态。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;QSqlDatabase db = QSqlDatabase::database(); // 获取默认数据库连接

// 1. 启动事务
if (db.transaction()) {
    QSqlQuery query(db);
    
    // 2. 执行一系列 SQL 操作
    query.exec(&quot;INSERT INTO accounts (id, balance) VALUES (1, 100)&quot;);
    query.exec(&quot;UPDATE accounts SET balance = balance - 100 WHERE id = 2&quot;);

    // 3. 检查执行结果并提交或回滚
    if (query.lastError().isValid()) {
        db.rollback(); // 出现错误，撤销所有操作
    } else {
        db.commit();   // 无错误，提交事务生效
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;事务的好处&lt;/strong&gt; ：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;确保数据一致性：即使在系统崩溃或错误发生时，数据也不会被破坏。&lt;/li&gt;
&lt;li&gt;提供原子性：一组操作要么全部成功，要么全部失败。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;常见应用场景&lt;/strong&gt; ：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;批量插入数据时使用事务可以提高性能。&lt;/li&gt;
&lt;li&gt;多步操作必须全部成功的情况下使用事务，如转账操作等。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;数据库的 Model&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;QSqlQueryModel&lt;/code&gt;：是一个只读模型，用于显示从 SQL 查询返回的结果集。它适合用于显示执行 &lt;code&gt;SELECT&lt;/code&gt; 查询的结果，通常是一次性的查询结果。不支持直接修改数据，只用于显示。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QSqlTableModel&lt;/code&gt;：用于操作数据库中的某一张表，支持增、删、改操作。它会与数据库中的表进行同步，自动更新显示内容。提供了与视图（如&lt;code&gt;QTableView&lt;/code&gt;）绑定的能力，可以方便地展示和操作数据库表数据。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QSqlRelationalTableModel&lt;/code&gt;：是 &lt;code&gt;QSqlTableModel&lt;/code&gt; 的扩展，支持外键关系。适合在表格中展示有关系的多个表格数据。它允许自动处理外键字段的显示，使得操作更为灵活和直观。&lt;/li&gt;
&lt;/ol&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Qt part.2: GUI</title><link>https://astro-pure.js.org/blog/qt-gui</link><guid isPermaLink="true">https://astro-pure.js.org/blog/qt-gui</guid><description>使用QWidget制作GUI</description><pubDate>Sat, 06 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;GUI&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;QWidget&lt;/code&gt; 是所有控件的基类，用于创建任何类型的控件&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QMainWindow&lt;/code&gt; 是一个窗口的基类，通常用来创建应用程序的主窗口，支持菜单栏、工具栏、状态栏等多种组件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Qt Designer是 Qt 提供的可视化界面设计工具，通过拖拽控件来快速设计界面，生成 &lt;code&gt;.ui&lt;/code&gt; 文件，后续通过 &lt;code&gt;uic&lt;/code&gt; 工具转换成代码，也可以手写代码创建界面，适合自定义复杂界面，但开发周期较长。&lt;/p&gt;
&lt;h3&gt;布局管理器&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;QHBoxLayout&lt;/code&gt;：水平布局，将控件从左到右排列，不够了会自己调整&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QVBoxLayout&lt;/code&gt; ：垂直布局，将控件从上到下排列，不够了会自己调整&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QGridLayout&lt;/code&gt;：网格布局，控件按照行和列排列，适合创建表单和矩阵类型的布局&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QFormLayout&lt;/code&gt; ：表单布局，自动将标签和控件（如文本框）按表单格式排列&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QStackedLayout&lt;/code&gt; ：堆叠布局，可以堆叠多个控件，只有一个控件可见&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QSplitter&lt;/code&gt; ：分割布局，允许动态调整控件的大小，常用于分隔不同区域&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;自定义控件&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;继承 &lt;code&gt;QWidget&lt;/code&gt; 或其他合适的 Qt 控件（如 &lt;code&gt;QPushButton&lt;/code&gt; ）&lt;/li&gt;
&lt;li&gt;重写必要的事件函数，如 &lt;code&gt;paintEvent()&lt;/code&gt; 来实现自定义绘制， &lt;code&gt;mousePressEvent()&lt;/code&gt; 来处理鼠标事件等&lt;/li&gt;
&lt;li&gt;重写 &lt;code&gt;resizeEvent()&lt;/code&gt; 来调整控件在大小变化时的行为（可选）&lt;/li&gt;
&lt;li&gt;根据需要，重写 &lt;code&gt;sizeHint()&lt;/code&gt; 和 &lt;code&gt;minimumSizeHint()&lt;/code&gt; 来指定控件的默认大小（可选）&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Qt样式表(Qss)&lt;/h3&gt;
&lt;p&gt;类似于 CSS，用于定制控件的外观，可以设置控件的颜色、字体、边框、背景等。通过调用控件的 &lt;code&gt;setStyleSheet()&lt;/code&gt; 方法，可以应用QSS样式表来改变控件的外观。&lt;/p&gt;
&lt;p&gt;例如，QPushButton 可以通过 QSS 设置背景色、文字颜色和字体大小等属性。&lt;/p&gt;
&lt;p&gt;全局加载 &lt;code&gt;style.qss&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;QFile file(&quot;:/style.qss&quot;);
if (file.open(QFile::ReadOnly)) {
    QString style = QLatin1String(file.readAll());
    app.setStyleSheet(style);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;界面国际化&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;QTranslator&lt;/code&gt; 类来实现国际化：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;创建翻译文件&lt;/strong&gt; ：使用 Qt Linguist 工具创建 &lt;code&gt;.ts&lt;/code&gt; 文件，进行语言翻译。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;加载翻译文件&lt;/strong&gt; ：在应用启动时，通过 &lt;code&gt;QTranslator&lt;/code&gt; 加载并安装翻译文件，动态更改界面语言。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;code&gt;QPainter&lt;/code&gt; 绘图&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;QPainter&lt;/code&gt; 是 Qt 中用于绘制图形和文本的核心类，基于 &lt;code&gt;QPaintDevice&lt;/code&gt; 和 &lt;code&gt;QPaintEngine&lt;/code&gt;，通常与 &lt;code&gt;QWidget&lt;/code&gt; 的 &lt;code&gt;paintEvent()&lt;/code&gt; 一起使用&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;创建 &lt;code&gt;QPainter&lt;/code&gt; 对象&lt;/strong&gt; ：通过传递目标设备（如 &lt;code&gt;QWidget&lt;/code&gt; 、 &lt;code&gt;QPixmap&lt;/code&gt; 、 &lt;code&gt;QImage&lt;/code&gt; 等）来创建一个 &lt;code&gt;QPainter&lt;/code&gt; 对象。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;设置绘制参数&lt;/strong&gt; ：使用 QPainter 提供的 API 设置绘图的属性，例如笔触颜色（ &lt;code&gt;setPen()&lt;/code&gt; ）、画刷颜色（ &lt;code&gt;setBrush()&lt;/code&gt; ）、字体（ &lt;code&gt;setFont()&lt;/code&gt; ）等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;执行绘制操作&lt;/strong&gt; ：调用 QPainter 的绘图函数，如 &lt;code&gt;drawRect()&lt;/code&gt;, &lt;code&gt;drawText()&lt;/code&gt; 等，来绘制具体内容。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结束绘制&lt;/strong&gt; ：完成绘制后，调用 &lt;code&gt;end()&lt;/code&gt; 来结束绘图操作，释放相关资源。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;示例流程：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;void MyWidget::paintEvent(QPaintEvent *event) {
    QPainter painter(this);
    painter.setPen(QPen(Qt::blue, 2));  // 设置蓝色的粗笔
    painter.drawRect(10, 10, 100, 100);  // 绘制矩形
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;QPaintDevice&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;所有可以进行绘制操作的对象的基类，如 &lt;code&gt;QWidget&lt;/code&gt;, &lt;code&gt;QImage&lt;/code&gt;, &lt;code&gt;QPixmap&lt;/code&gt; 等。它提供了一个统一的接口，使得 &lt;code&gt;QPainter&lt;/code&gt; 可以在不同的设备上进行绘制。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;QPaintEngine&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;QPaintEngine&lt;/code&gt; 是 &lt;code&gt;QPainter&lt;/code&gt; 的一个底层类，负责将绘图操作实际输出到设备（如屏幕、打印机、图片等）。不同的绘图设备（如窗口、图像等）有不同的 &lt;code&gt;QPaintEngine&lt;/code&gt; 实现。它为 &lt;code&gt;QPainter&lt;/code&gt; 提供了实现细节，保证绘图操作被正确渲染。&lt;/p&gt;
&lt;h2&gt;图像数据处理&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;QPixmap&lt;/code&gt;：用于优化屏幕显示的图像，特别是在高效渲染时使用。适用场景：显示图像到屏幕、处理图像的高效渲染，通常在需要快速显示图像时使用（如游戏开发、界面绘制）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QImage&lt;/code&gt;：用于处理原始像素数据，能够支持多种格式（如 JPEG、PNG 等），且提供了更灵活的接口来访问像素数据。适用场景：需要对图像数据进行修改（如像素级别的操作、图像处理）时使用&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QBitmap&lt;/code&gt;：特殊类型的图像，主要用于存储位图（黑白图像）。QBitmap 是 QImage 的一个子类，但仅支持黑白像素的存储。适用场景：通常用于图标、掩码或透明区域的处理&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;模型-视图架构 (Model/View)&lt;/h2&gt;
&lt;p&gt;一种将&lt;strong&gt;数据、界面显示和用户交互分离&lt;/strong&gt;的设计模式&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Model&lt;/strong&gt; 负责数据的存储和管理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;View&lt;/strong&gt; 负责数据的展示&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Delegate&lt;/strong&gt; 负责数据的绘制和编辑&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;模型和视图之间通过 &lt;strong&gt;信号与槽机制&lt;/strong&gt;自动同步，当模型中的数据发生变化时，视图会自动更新显示。这种架构可以让同一份数据被多个视图同时显示，提高代码的复用性和可维护性。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;QAbstractItemModel&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;QAbstractItemModel&lt;/code&gt; 是 Qt 所有模型类的抽象基类，用于定义模型与视图交互的统一接口&lt;/p&gt;
&lt;p&gt;它规定了模型必须实现的基本函数，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rowCount()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;columnCount()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setData()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;index()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;parent()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过继承 &lt;code&gt;QAbstractItemModel&lt;/code&gt; ，可以自定义任意结构的数据模型，供 &lt;code&gt;QListView&lt;/code&gt; 、 &lt;code&gt;QTableView&lt;/code&gt; 、 &lt;code&gt;QTreeView&lt;/code&gt; 等视图使用&lt;/p&gt;
&lt;h3&gt;自定义Model&lt;/h3&gt;
&lt;p&gt;一般通过继承&lt;code&gt;QAbstractItemModel&lt;/code&gt;或其子类来实现&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;简单场景：继承 &lt;code&gt;QAbstractListModel&lt;/code&gt; / &lt;code&gt;QAbstractTableModel&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;复杂场景：直接继承 &lt;code&gt;QAbstractItemModel&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;实现时需要重写的关键函数包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rowCount()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;columnCount()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setData()&lt;/code&gt; （如果需要可编辑）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flags()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果是树形结构，还需要实现 &lt;code&gt;index()&lt;/code&gt;和&lt;code&gt;parent()&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;视图&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;QListView&lt;/code&gt; 用于显示一维列表数据只有行，没有列适合显示简单列表，如文件名列表、日志列表&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QTableView&lt;/code&gt; 用于显示二维表格数据有行和列常用于表格数据，如配置表、数据库表&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QTreeView&lt;/code&gt; 用于显示层级结构数据支持父子节点关系适合目录结构、组织结构、树形数据&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;三者的主要区别在于数据结构不同，但都可以使用同一个Model&lt;/p&gt;
&lt;h3&gt;数据角色&lt;/h3&gt;
&lt;p&gt;数据角色（Role）用于&lt;strong&gt;区分同一数据在不同用途下的表现形式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;data()&lt;/code&gt; 函数中，根据不同的 Role 返回不同的数据内容。&lt;/p&gt;
&lt;p&gt;常见角色包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Qt::DisplayRole&lt;/code&gt; ：用于显示的文本&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Qt::EditRole&lt;/code&gt; ：用于编辑的数据&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Qt::DecorationRole&lt;/code&gt; ：图标或图片&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Qt::ToolTipRole&lt;/code&gt; ：提示信息&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Qt::TextAlignmentRole&lt;/code&gt; ：对齐方式&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Qt::ForegroundRole&lt;/code&gt; ：文字颜色&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Qt::BackgroundRole&lt;/code&gt; ：背景颜色&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过 Role 机制，可以在不改变数据结构的情况下，实现丰富的显示效果。&lt;/p&gt;
&lt;h3&gt;代理(Delegate)&lt;/h3&gt;
&lt;p&gt;主要负责数据的绘制和编辑，它决定了数据在视图中的显示样式，以及用户如何编辑数据。Delegate 常用于实现自定义单元格样式、下拉框、复选框、进度条等编辑效果。&lt;/p&gt;
&lt;p&gt;Qt默认使用 &lt;code&gt;QStyledItemDelegate&lt;/code&gt; ，但在需要自定义显示或编辑控件时，可以继承它来自定义Delegate。&lt;/p&gt;
&lt;p&gt;自定义 Delegate 通常需要重写以下函数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;paint()&lt;/code&gt; ：自定义绘制方式&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sizeHint()&lt;/code&gt; ：设置单元项大小&lt;/li&gt;
&lt;li&gt;&lt;code&gt;createEditor()&lt;/code&gt; ：创建编辑控件&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setEditorData()&lt;/code&gt; ：将模型数据设置到编辑器&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setModelData()&lt;/code&gt; ：将编辑结果写回模型&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Qt part.4: 多线程编程</title><link>https://astro-pure.js.org/blog/qt-multithread</link><guid isPermaLink="true">https://astro-pure.js.org/blog/qt-multithread</guid><description>天天被拷打线程池烦都烦死了</description><pubDate>Sat, 06 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;实现多线程的方法&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;QThread&lt;/code&gt; 类 ：继承 &lt;code&gt;QThread&lt;/code&gt; 并重写其 &lt;code&gt;run()&lt;/code&gt; 方法，创建和管理线程。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QObject::moveToThread()&lt;/code&gt; ：将一个对象移到另一个线程中运行，通常配合事件循环使用，适用于 GUI 与工作线程之间的分离。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QtConcurrent&lt;/code&gt; ：这是 Qt 提供的并行编程框架，通过较为简单的接口（如 &lt;code&gt;QtConcurrent::run()&lt;/code&gt; ）来并行执行函数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QThreadPool&lt;/code&gt; ：管理一组线程，允许将任务提交给线程池，而不需要手动创建和管理线程。适用于任务较为简单且数量较多的情况。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;&lt;code&gt;QThread&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;QThread&lt;/code&gt; 是 Qt 用来创建和管理线程的类，通常的使用步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;继承 &lt;code&gt;QThread&lt;/code&gt; 并重写 &lt;code&gt;run()&lt;/code&gt;：在 &lt;code&gt;run()&lt;/code&gt; 中编写需要在线程中执行的代码&lt;/li&gt;
&lt;li&gt;在主线程或别的地方创建线程类的实例&lt;/li&gt;
&lt;li&gt;启动线程：通过调用 &lt;code&gt;start()&lt;/code&gt; 启动线程，&lt;code&gt;start()&lt;/code&gt; 会自动调用 &lt;code&gt;run()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;如果需要线程交互、获得线程执行结果，使用信号槽机制实现线程间通信&lt;/li&gt;
&lt;li&gt;执行完毕时调用 &lt;code&gt;quit()&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class MyThread : public QThread {
    Q_OBJECT

public:
    void run() override {
        // 执行耗时操作
    }
};

MyThread *thread = new MyThread();
thread-&gt;start();  // 启动线程
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;等待线程完成：可以使用 &lt;code&gt;wait()&lt;/code&gt; 方法来阻塞主线程，直到子线程完成执行。&lt;/li&gt;
&lt;li&gt;停止线程：可以使用 &lt;code&gt;terminate()&lt;/code&gt; 强制停止线程（不推荐），或者通过设置标志位安全地结束线程。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;继承 &lt;code&gt;QThread&lt;/code&gt; 的方法适合在新线程中执行一些独立的工作逻辑，当线程与 &lt;code&gt;QObject&lt;/code&gt; 结合时（如 GUI 线程中的工作对象），不建议直接继承 &lt;code&gt;QThread&lt;/code&gt;。因为 &lt;code&gt;QObject&lt;/code&gt; 不允许在多个线程中同时访问。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;moveToThread&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;将一个 &lt;code&gt;QObject&lt;/code&gt; 派生的对象移到一个新的线程中，这样该对象的槽函数就可以在该线程中执行。适用于需要将某个对象放入工作线程，但不需要继承 &lt;code&gt;QThread&lt;/code&gt;。可以避免 &lt;code&gt;QObject&lt;/code&gt; 在多个线程中共享的问题，线程管理更灵活，但需要手动管理线程的启动、停止和对象的销毁。&lt;/p&gt;
&lt;h2&gt;线程间通信&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;信号与槽机制：最常用的线程间通信方式。Qt 的信号与槽机制支持线程间的通信，线程 A 发射信号，线程 B 连接槽，并自动在线程之间传递信号。&lt;/li&gt;
&lt;li&gt;事件队列：通过 &lt;code&gt;QCoreApplication::postEvent()&lt;/code&gt; 将事件发送到目标线程的事件队列，目标线程可以在 &lt;code&gt;event()&lt;/code&gt; 中处理事件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QMutex&lt;/code&gt; 和 &lt;code&gt;QSemaphore&lt;/code&gt;：用于多线程间的同步，确保线程安全地访问共享资源。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QWaitCondition&lt;/code&gt; 和 &lt;code&gt;QMutex&lt;/code&gt;：用于线程间的同步，允许一个线程等待某个条件满足再继续执行。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;信号槽在多线程的工作方式&lt;/h3&gt;
&lt;p&gt;Qt 的信号与槽机制可以跨线程工作。Qt 的信号与槽机制支持线程间的通信，线程 A 发射信号，线程 B 连接槽，并自动在线程之间传递信号。当信号发射方和槽接收方在不同线程中时，Qt 会根据连接类型自动处理线程间的通信。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Qt::AutoConnection&lt;/code&gt;（默认）：自动决定使用哪种方式。当信号和槽在同一线程时，直接调用槽；如果在不同线程，则使用队列连接。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Qt::DirectConnection&lt;/code&gt;：信号与槽在同一线程时直接调用槽函数；如果在不同线程，信号会被丢弃。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Qt::QueuedConnection&lt;/code&gt;：即使在同一线程中也会将信号放入接收线程的事件队列中进行处理，适用于跨线程的信号槽连接。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Qt::BlockingQueuedConnection&lt;/code&gt;：与 &lt;code&gt;QueuedConnection&lt;/code&gt; 类似，但发射信号的线程会被阻塞，直到槽函数执行完毕。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;Qt::DirectConnection&lt;/code&gt; 适合同线程之间的通信， &lt;code&gt;Qt::QueuedConnection&lt;/code&gt; 适合跨线程的通信。&lt;/p&gt;
&lt;h2&gt;同步与互斥&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;QMutex&lt;/code&gt;：互斥锁，确保在同一时刻只有一个线程可以访问共享资源。适用于线程需要独占资源的场景。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QReadWriteLock&lt;/code&gt;：读写锁，允许多个线程同时读取共享资源，但当有线程写入时，其他线程（包括读线程）不能访问。适用于读多写少的场景，如缓存读取。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QSemaphore&lt;/code&gt;：信号量，用于控制资源的访问数量，允许有限多个数量的线程访问资源。适用于有限资源的场景，如连接池、线程池等。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;QMutex m_Mutex;
m_Mutex.lock();
m_Mutex.unlock();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;类似 &lt;code&gt;lock_guard&lt;/code&gt; 和 &lt;code&gt;unique_lock&lt;/code&gt;，也有RAII的锁 &lt;code&gt;QMutexLockermutexLocker&lt;/code&gt;，从声明处开始（在构造函数中加锁），出了作用域自动解锁（在析构函数中解锁）&lt;/p&gt;
&lt;p&gt;类似条件变量的 &lt;code&gt;QWaitCondition&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;QWaitCondtion m_WaitCondition;
m_WaitConditon.wait(&amp;#x26;m_muxtex, time);
m_WaitCondition.wakeAll();
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;死锁避免&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;避免循环锁定：确保多个线程获取资源时遵循统一的顺序。比如线程 A 获取资源 1 后再获取资源 2，线程 B 获取资源 2 后再获取资源 1，避免交叉等待。&lt;/li&gt;
&lt;li&gt;使用超时机制：可以在锁定时设置超时（如 &lt;code&gt;QMutex::tryLock()&lt;/code&gt;），如果获取锁失败则放弃等待，避免长时间等待。&lt;/li&gt;
&lt;li&gt;死锁检测：定期检查锁定情况，通过监控机制检测并及时处理潜在死锁。&lt;/li&gt;
&lt;li&gt;避免嵌套锁：尽量避免多个线程同时请求多个资源，减少死锁发生的可能。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;线程安全&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;QMutex&lt;/code&gt;：使用互斥锁来保护共享资源，确保每次只有一个线程可以访问该资源。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QReadWriteLock&lt;/code&gt;：在读多写少的场景下，允许多个线程同时读取资源，但写线程会独占资源。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QAtomic&lt;/code&gt; 类：提供对原子操作的支持，确保对数据的访问是线程安全的（如 &lt;code&gt;QAtomicInt&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;信号槽机制：Qt 的信号槽机制本身是线程安全的，可以用于线程间安全地传递消息。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;code&gt;QtConcurrent&lt;/code&gt;：并行计算框架&lt;/h2&gt;
&lt;p&gt;QtConcurrent是 Qt 提供的并行计算框架，提供了高层次的并行操作接口，简化了多线程编程。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;并行处理任务&lt;/strong&gt; ：如图像处理、文件处理等，能够将大任务拆分成多个小任务并行执行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据处理和计算密集型任务&lt;/strong&gt; ：通过简单的接口（如 &lt;code&gt;QtConcurrent::run()&lt;/code&gt; ）来并行执行 CPU 密集型操作，显著提高效率。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高效的线程池管理&lt;/strong&gt; ： &lt;code&gt;QtConcurrent&lt;/code&gt; 使用线程池来管理线程，避免直接创建和销毁线程的开销。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;QtConcurrent::run(someFunction);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;code&gt;QThreadPool&lt;/code&gt;：线程池&lt;/h2&gt;
&lt;p&gt;Qt的线程池，使用 &lt;code&gt;start()&lt;/code&gt; 开始指定的任务函数，线程池全局实例会接管函数的生命周期&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class HelloWorldTask : public QRunnable {
    void run() override {
        qDebug() &amp;#x3C;&amp;#x3C; &quot;Hello world from thread&quot; &amp;#x3C;&amp;#x3C; QThread::currentThread();
    }
};

int main() {
    //...
    HelloWorldTask *hello = new HelloWorldTask();
    // QThreadPool takes ownership and deletes &apos;hello&apos; automatically
    QThreadPool::globalInstance()-&gt;start(hello);
    //...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;start()&lt;/code&gt;有两个函数原型:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;void start(QRunnable *runnable, int priority = 0);
void start(Callable &amp;#x26;&amp;#x26;callableToRun, int priority = 0);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;预留一个线程并用它来运行可运行对象，除非该线程会导致当前线程数超过 &lt;code&gt;maxThreadCount()&lt;/code&gt;。在这种情况下，可运行对象会被添加到运行队列中。可以使用 &lt;code&gt;priority&lt;/code&gt; 参数来控制运行队列的执行顺序。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://doc.qt.io/qt-6/qrunnable.html#details&quot;&gt;官方文档的示例代码&lt;/a&gt;定义一个任务类继承 &lt;code&gt;QRunnable&lt;/code&gt; ，实际上 &lt;code&gt;QRunnable&lt;/code&gt; 类可以是普通函数，也可以是lambda函数， &lt;code&gt;QRunnable&lt;/code&gt; 提供了一个静态工厂函数 &lt;code&gt;create&lt;/code&gt; 在内部直接从lambda表达式构建 &lt;code&gt;QRunnable&lt;/code&gt; 对象&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;QThreadPool* tp = QThreadPool::globalInstance();
void timer_task() {
    tp-&gt;start([&amp;#x26;]() {
	        //...
	    }
	);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;线程池全局实例指针 &lt;code&gt;tp&lt;/code&gt; 启动一个lambda函数作为线程任务。&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>VSCode配置stm32开发环境(OpenOCD调试)</title><link>https://astro-pure.js.org/blog/stm32-vscode</link><guid isPermaLink="true">https://astro-pure.js.org/blog/stm32-vscode</guid><description>为什么不让我用Linux</description><pubDate>Mon, 02 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;工具介绍&lt;/h2&gt;
&lt;h3&gt;交叉编译工具链&lt;/h3&gt;
&lt;p&gt;ARM-GCC是一套交叉编译工具链家族，其命名规则统一为：&lt;strong&gt;arch [-vendor] [-os] [-(gnu)eabi]&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;arch&lt;/strong&gt;代表芯片的体系架构，比如ARM，MIPS等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;vendor&lt;/strong&gt;代表工具链的提供商&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;os&lt;/strong&gt;代表目标开发板所使用的操作系统&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;eabi&lt;/strong&gt;代表 &lt;strong&gt;Embedded Application Binary Interface&lt;/strong&gt;，即嵌入式应用二进制接口&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用的是 &lt;strong&gt;arm-none-eabi-gcc&lt;/strong&gt; (ARM architecture, &lt;strong&gt;no&lt;/strong&gt; vendor, &lt;strong&gt;not&lt;/strong&gt; target an operating system, complies with the ARM EABI)主要用于编译ARM架构的裸机系统（包括ARM Linux的Boot和Kernel，不适用编译Linux应用），一般适合ARM7、Cortex-M和Cortex-R内核等芯片使用，不支持那些跟操作系统关系密切的函数。除此之外，该编译器在底层使用了newlib这个专用于嵌入式系统的C库。&lt;/p&gt;
&lt;h3&gt;OpenOCD&lt;/h3&gt;
&lt;p&gt;Open On-Chip Debuger 调试器。硬件的ROM、RAM或FLASH空间是非常有限的，你只有通过学习才能知道如何通过设置编译器的命令参数来优化.bin或.elf生成文件的大小。除此之外，你还能通过配置链接脚本中TEXT段、DATA段、BSS段以及堆、栈的起始地址和空间容量等参数来获得定制STM32程序运行时的能力。&lt;/p&gt;
&lt;h2&gt;安装必要项&lt;/h2&gt;
&lt;p&gt;在你的wsl发行版中用包管理器安装 gcc, g++, cmake, ninja, openOCD&lt;/p&gt;
&lt;p&gt;目前找到的教程是使用wsl1的，因为不支持外设和wsl1通信所以他使用的是在宿主机安装openOCD然后挂载到wsl里的方法，wsl2使用这个方法会造成tcl port通信的问题，很麻烦，直接在wsl2里安装openOCD然后在settings.json里指明路径就好了&lt;/p&gt;
&lt;h2&gt;在vscode中开发构建好的项目&lt;/h2&gt;
&lt;p&gt;使用STM32CubeMX通过cmake生成项目后，通过挂载路径在vscode远程连接wsl打开，可以把他移到你喜欢的位置。源码位于Core文件夹中，里面的两个文件夹分别包含头文件和.c文件，主循环在 &lt;code&gt;main.c&lt;/code&gt; 中&lt;/p&gt;
&lt;h3&gt;build&lt;/h3&gt;
&lt;p&gt;安装cmake拓展，对于生成好的工程项目点击下方状态栏的build即可构建，构建前确认安装好cmake和ninja，使用cmake构建Release，生成的东西会在 &lt;code&gt;build/Release&lt;/code&gt;文件夹，注意有没有&lt;code&gt;compile_commands.json&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;clangd无法找到头文件的问题&lt;/h3&gt;
&lt;p&gt;在工作区设定的&lt;code&gt;settings.json&lt;/code&gt;里添加clangd参数&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
    &quot;cortex-debug.gdbPath&quot;: &quot;/usr/bin/gdb-multiarch&quot;,
    &quot;cortex-debug.openocdPath&quot;: &quot;/mnt/d/openocd-v0.12.0-i686-w64-mingw32/bin/openocd.exe&quot;,
    &quot;clangd.arguments&quot;: [
        &quot;--compile-commands-dir=build/Release&quot;
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后在命令中选择重启clangd就会生效&lt;/p&gt;
&lt;h2&gt;运行一个Demo&lt;/h2&gt;
&lt;p&gt;f103c8t6的PC13点灯&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int main(void)
{
    HAL_Init();                // 1. HAL 库初始化
    SystemClock_Config();      // 2. 配置系统时钟
    MX_GPIO_Init();            // 3. 初始化 LED 对应的 GPIO

    while (1)
    {
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // 4. 翻转 LED 引脚电平
        HAL_Delay(500);                         // 5. 延时 500ms
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;连接st-link v2到wsl调试&lt;/h2&gt;
&lt;p&gt;vscode远程连接时安装cortex-debug扩展，在宿主机安装usbipd，找到STLink的设备&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;winget install usbipd # 安装
usbipd list

Connected:
BUSID  VID:PID    DEVICE                                                        STATE
1-4    048d:c102  USB 输入设备                                                  Not shared
2-3    0489:e0d8  RZ616 Bluetooth(R) Adapter                                    Not shared
7-2    3710:5406  USB 输入设备                                                  Not shared
7-3    0483:3748  STM32 STLink                                                  Not shared

Persisted:
GUID                                  DEVICE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用&lt;a href=&quot;https://zadig.akeo.ie/&quot;&gt;Zadig&lt;/a&gt;替换驱动，可以发现原来根本没有装驱动，点击安装&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://astro-pure.js.org/_vercel/image?url=_astro%2Fzadig.wwmTCG7w.png&amp;#x26;w=828&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;绑定设备&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;usbipd bind --busid 7-3
usbipd attach --wsl --busid 7-3
usbipd: info: Using WSL distribution &apos;Ubuntu&apos; to attach; the device will be available in all WSL 2 distributions.
usbipd: info: Loading vhci_hcd module.
usbipd: info: Detected networking mode &apos;mirrored&apos;.
usbipd: info: Using IP address 127.0.0.1 to reach the host.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;回到wsl中使用lsusb查看，如果没有lsusb按照终端指示安装，找到St-link&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 0483:3748 STMicroelectronics ST-LINK/V2
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在左边run &amp;#x26; debug页面点击生成 launch.json ，设定好调试服务器和可执行文件，svd自己去谷歌找，放入项目目录。注意executable项目因人而异，总之要找到可执行的 .elf 文件&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
    &quot;version&quot;: &quot;0.2.0&quot;,
    &quot;configurations&quot;: [
        {
            &quot;name&quot;: &quot;STM32F103&quot;,
            &quot;cwd&quot;: &quot;${workspaceFolder}&quot;,
            &quot;type&quot;: &quot;cortex-debug&quot;,
            &quot;request&quot;: &quot;launch&quot;,
            &quot;servertype&quot;: &quot;openocd&quot;,
            &quot;executable&quot;: &quot;${workspaceFolder}/build/Release/1LED.elf&quot;,
            &quot;device&quot;: &quot;STM32F103C8&quot;,
            &quot;configFiles&quot;: [
                &quot;interface/stlink.cfg&quot;,
                &quot;target/stm32f1x.cfg&quot;
            ],
            &quot;svdFile&quot;: &quot;${workspaceFolder}/STM32F103.svd&quot;,
            &quot;runToEntryPoint&quot;: &quot;main&quot;,
            &quot;showDevDebugOutput&quot;: &quot;raw&quot;,
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="/_astro/thumbnail.DnLQi00U.png"/><enclosure url="/_astro/thumbnail.DnLQi00U.png"/></item><item><title>C语言 part.1 语言特性</title><link>https://astro-pure.js.org/blog/c-basic</link><guid isPermaLink="true">https://astro-pure.js.org/blog/c-basic</guid><description>脑残八股差不多得了</description><pubDate>Fri, 13 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;为了面试某嵌入式小厂进行刷题训练的产物，我实在不明白这种规模的厂居然还搞笔试。&lt;/p&gt;
&lt;h2&gt;运算符&lt;/h2&gt;
&lt;h3&gt;二进制位运算&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;a |= 1 &amp;#x3C;&amp;#x3C; n // 设置第n位
a &amp;#x26;= ~(1 &amp;#x3C;&amp;#x3C; n) // 清空第n位
a ^= 1 &amp;#x3C;&amp;#x3C; n // 翻转第n位
a &amp;#x26; (1 &amp;#x3C;&amp;#x3C; n) // 第n位是否为1
if (x &amp;#x26; 1) // 判断x是否为奇
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;十六进制位运算&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;在不改变其他位的值的情况下，对某几个位设值，先对需要置值的位用 &lt;code&gt;&amp;#x26;&lt;/code&gt; 操作符清零，然后用 &lt;code&gt;|&lt;/code&gt; 操作符设值，比如要改变GPIOA的状态，可以先对寄存器内的值进行清零操作&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;GIOA-&gt;CRL &amp;#x26;= 0XFFFFFF0F // 将4~7位清零
GPIOA-&gt;CRL |= 0X00000040;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;移位，例如这个操作就是将BSRR寄存器的第 &lt;code&gt;pinpos&lt;/code&gt; 位设置为&lt;code&gt;1&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;GPIOx-&gt;BSRR = (((uint32_t)0x01) &amp;#x3C;&amp;#x3C; pinpos);
GPIOx-&gt;BSRR = 0X0030; // 直接设置值
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第一种写法更好，这是因为可以很直接地知道&lt;strong&gt;这个操作置的是BSRR寄存器的哪个功能位&lt;/strong&gt;，这样通过查看微控制器的寄存器手册就可以知道这个操作所实现的功能是什么了。而第二种写法则还需要一个转换的过程。&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;取反，例如定时器TIMx的SR寄存器的每一位都表示一个外设的状态，如果某个时刻想要去置某一位为0，同时保留其他位为1，则简单的方式是给寄存器直接置值，这行代码的功能是将SR寄存器的第0位设置为0&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;TIMx-&gt;SR = 0XFFFE;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可读性比较差，工程里好的写法如下，这样通过取反的方式就可以看出操作的目的就是将第0位置零&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define TIM_FLAG_Update      ((uint16_t) 0X0001)
#define TIM_FLAG_CC1         ((uint16_t) 0X0002)
TIMx-&gt;SR = (uint16_t) ~TIM_FLAG;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;运算符优先级&lt;/h3&gt;
&lt;p&gt;| 优先级 | 运算符                                                    | 名称/说明            | 结合性  |
| --- | ------------------------------------------------------ | ---------------- | ---- |
| 1   | &lt;code&gt;()&lt;/code&gt; &lt;code&gt;[]&lt;/code&gt; &lt;code&gt;-&gt;&lt;/code&gt; &lt;code&gt;.&lt;/code&gt;                                     | 圆括号、数组下标、结构体成员访问 | 从左到右 |
| 1   | &lt;code&gt;expr++&lt;/code&gt; &lt;code&gt;expr--&lt;/code&gt;                                      | 后缀自增、自减          | 从左到右 |
| 2   | &lt;code&gt;++expr&lt;/code&gt; &lt;code&gt;--expr&lt;/code&gt;                                      | 前缀自增、自减          | 从右到左 |
| 2   | &lt;code&gt;+&lt;/code&gt; &lt;code&gt;-&lt;/code&gt;                                                | 正号、负号（一元）        | 从右到左 |
| 2   | &lt;code&gt;!&lt;/code&gt; &lt;code&gt;~&lt;/code&gt;                                                | 逻辑非、按位取反         | 从右到左 |
| 2   | &lt;code&gt;*&lt;/code&gt;                                                    | 指针解引用            | 从右到左 |
| 2   | &lt;code&gt;&amp;#x26;&lt;/code&gt;                                                    | 取地址              | 从右到左 |
| 2   | &lt;code&gt;(type)&lt;/code&gt;                                               | 强制类型转换           | 从右到左 |
| 2   | &lt;code&gt;sizeof&lt;/code&gt;                                               | 取大小              | 从右到左 |
| 3   | &lt;code&gt;*&lt;/code&gt; &lt;code&gt;/&lt;/code&gt; &lt;code&gt;%&lt;/code&gt;                                            | 乘、除、取模           | 从左到右 |
| 4   | &lt;code&gt;+&lt;/code&gt; &lt;code&gt;-&lt;/code&gt;                                                | 加、减              | 从左到右 |
| 5   | &lt;code&gt;&amp;#x3C;&amp;#x3C;&lt;/code&gt; &lt;code&gt;&gt;&gt;&lt;/code&gt;                                              | 左移、右移            | 从左到右 |
| 6   | &lt;code&gt;&amp;#x3C;&lt;/code&gt; &lt;code&gt;&amp;#x3C;=&lt;/code&gt; &lt;code&gt;&gt;&lt;/code&gt; &lt;code&gt;&gt;=&lt;/code&gt;                                      | 关系运算             | 从左到右 |
| 7   | &lt;code&gt;==&lt;/code&gt; &lt;code&gt;!=&lt;/code&gt;                                              | 相等、不等            | 从左到右 |
| 8   | &lt;code&gt;&amp;#x26;&lt;/code&gt;                                                    | 按位与              | 从左到右 |
| 9   | &lt;code&gt;^&lt;/code&gt;                                                    | 按位异或             | 从左到右 |
| 10  | &lt;code&gt;\|&lt;/code&gt;                                                   | 按位或              | 从左到右 |
| 11  | &lt;code&gt;&amp;#x26;&amp;#x26;&lt;/code&gt;                                                   | 逻辑与              | 从左到右 |
| 12  | &lt;code&gt;\|\|&lt;/code&gt;                                                 | 逻辑或              | 从左到右 |
| 13  | &lt;code&gt;?:&lt;/code&gt;                                                   | 条件（三目）运算符        | 从右到左 |
| 14  | &lt;code&gt;=&lt;/code&gt; &lt;code&gt;+=&lt;/code&gt; &lt;code&gt;-=&lt;/code&gt; &lt;code&gt;*=&lt;/code&gt; &lt;code&gt;/=&lt;/code&gt; &lt;code&gt;%=&lt;/code&gt; &lt;code&gt;&amp;#x3C;&amp;#x3C;=&lt;/code&gt; &lt;code&gt;&gt;&gt;=&lt;/code&gt; &lt;code&gt;&amp;#x26;=&lt;/code&gt; &lt;code&gt;^=&lt;/code&gt; &lt;code&gt;=&lt;/code&gt; | 赋值运算             | 从右到左 |
| 15  | &lt;code&gt;,&lt;/code&gt;                                                    | 逗号运算符&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当 &lt;code&gt;x=3, y=1, z=0&lt;/code&gt; 时 &lt;code&gt;x&amp;#x3C;=y&amp;#x3C;=z&lt;/code&gt; 的结果：从左到右判断，&lt;code&gt;x≤y&lt;/code&gt; 是0，&lt;code&gt;0≤z&lt;/code&gt; 为1&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(x++ * 1/3)&lt;/code&gt; 的执行顺序：&lt;code&gt;x++&lt;/code&gt; 是在 &lt;code&gt;；&lt;/code&gt; 后执行的，也就是 &lt;code&gt;(x*1/3)；x++；&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;code&gt;sizeof&lt;/code&gt; 运算符&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;sizeof&lt;/code&gt; 是C语言的一种单目操作符，并不是函数。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sizeof&lt;/code&gt; 以字节形式给出了其操作数的存储大小。操作数可以是一个表达式或括在括号内的类型名。操作数的存储大小由操作数的类型决定。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sizeof&lt;/code&gt; 的结果类型是 &lt;code&gt;size_t&lt;/code&gt;,该类型保证能容纳实现所建立的最大对象的字节大小。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;sizeof(char)          = 1;
sizeof(unsigned char) = 1;
sizeof(signed char)   = 1;
sizeof(int)            = 4;
sizeof(unsigned int)   = 4;
sizeof(short int)      = 2;
sizeof(unsigned short) = 2;
sizeof(long int)       = 4;
sizeof(unsigned long)  = 4;
sizeof(float)          = 4;
sizeof(double)         = 8;
sizeof(long double)    = 12;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;操作数为指针的时候结果依靠编译器，一般Unix/Linux的指针字节数为4&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;char *p;      //Linux中
sizeof(p) = 4;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;操作数为数组类型时结果是数组的总大小。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;char a[5];
sizeof(a) = 5;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;操作数是具体字符串/数值时会自动转化为对应类型。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;sizeof(8)    = 4;  //自动转化为int类型
sizeof(8.8)  = 8;  //自动转化为double类型，注意，不是float类型
sizeof(&quot;ab&quot;) = 3   //自动转化为数组类型
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;操作数是 &lt;code&gt;union&lt;/code&gt; 时为最大成员的大小&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;union u{            
    char c;
    double d;
} u;
sizeof(u) = 8;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;操作数是 &lt;code&gt;struct&lt;/code&gt; 时考虑边界对齐，该例中 &lt;code&gt;b&lt;/code&gt; 要加上7个byte对齐到能够整除8&lt;/p&gt;
&lt;p&gt;&lt;code&gt;#pragma pack(n)&lt;/code&gt; 按照 &lt;code&gt;n&lt;/code&gt; 字节对齐，否则按结构体里最大的成员对齐（取倍数）&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct a{             
    char　b;　
    double　x;
}a;
sizeof(a) = 16;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;操作数是函数中的数组形参或函数形参，&lt;code&gt;sizeof&lt;/code&gt; 为其指针大小&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;restrict&lt;/code&gt; 关键字(C99)&lt;/h2&gt;
&lt;p&gt;告诉编译器该指针是访问其所指对象的唯一途径，编译器可以据此进行更激进的优化（如减少内存读取）&lt;/p&gt;
&lt;h2&gt;柔性数组(C99)&lt;/h2&gt;
&lt;p&gt;例如 &lt;code&gt;data[0]&lt;/code&gt;  在结构体中不占空间&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct list_t{
		struct list_t *next;
		struct list_t *prev;
		char data[0];
} list_t;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在32bit系统中 &lt;code&gt;sizeof(list_t)&lt;/code&gt;  大小为 8byte&lt;/p&gt;
&lt;p&gt;&lt;code&gt;*next&lt;/code&gt; 和 &lt;code&gt;*prev&lt;/code&gt; 指针类型，占4字节，&lt;code&gt;data[0]&lt;/code&gt;  零长度数组，不占用实际空间，内存对齐，整个结构体的大小需要是最大基本类型大小(这里是4)的整数倍。&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;printf()&lt;/code&gt;：格式化输出&lt;/h2&gt;
&lt;h3&gt;格式输出控制符&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;%&lt;/code&gt;：格式说明的起始符号，不可缺少。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-&lt;/code&gt;： 有表示左对齐输出，如省略表示右对齐输出。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0&lt;/code&gt;：有表示指定空位填0,如省略表示指定空位不填。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;m.n&lt;/code&gt;：&lt;code&gt;m&lt;/code&gt; 指域宽，即对应的输出项在输出设备上所占的字符数。&lt;code&gt;n&lt;/code&gt; 指精度，用于说明输出的实型数的小数位数。未指定 &lt;code&gt;n&lt;/code&gt; 时，隐含的精度为&lt;code&gt;n=6&lt;/code&gt; 位。针对字符输出 &lt;code&gt;%m.ns&lt;/code&gt;：输出占 &lt;code&gt;m&lt;/code&gt;列，但只取字符串中左端&lt;code&gt;n&lt;/code&gt; 个字符&lt;/li&gt;
&lt;li&gt;&lt;code&gt;l&lt;/code&gt;：对整型指&lt;code&gt;long&lt;/code&gt;型，对实型指&lt;code&gt;double&lt;/code&gt;型。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;h&lt;/code&gt;：用于将整型的格式字符修正为&lt;code&gt;short&lt;/code&gt;型&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;格式输出数据类型控制&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;d格式：用来输出十进制整数。&lt;code&gt;%ld&lt;/code&gt;：输出长整型数据。&lt;code&gt;%md&lt;/code&gt;：&lt;code&gt;m&lt;/code&gt; 为指定的输出字段的宽度。&lt;/li&gt;
&lt;li&gt;o格式：以无符号八进制形式输出整数。对长整型可以用 &lt;code&gt;%lo&lt;/code&gt; 格式输出。同样也可以指定字段宽度用 &lt;code&gt;%mo&lt;/code&gt; 格式输出。&lt;/li&gt;
&lt;li&gt;x格式：以无符号十六进制形式输出整数。对长整型可以用 &lt;code&gt;%lx&lt;/code&gt; 格式输出。同样也可以指定字段宽度用 &lt;code&gt;%mx&lt;/code&gt;格式输出。&lt;/li&gt;
&lt;li&gt;u格式：以无符号十进制形式输出整数。对长整型可以用 &lt;code&gt;%lu&lt;/code&gt; 格式输出。同样也可以指定字段宽度用 &lt;code&gt;%mu&lt;/code&gt; 格式输出。&lt;/li&gt;
&lt;li&gt;c格式：输出一个字符。&lt;/li&gt;
&lt;li&gt;s格式：用来输出一个字符串&lt;/li&gt;
&lt;li&gt;f格式：用来输出实数（包括单、双精度），以小数形式输出。&lt;/li&gt;
&lt;li&gt;e格式：以指数形式输出实数。&lt;/li&gt;
&lt;li&gt;g格式：自动选 &lt;code&gt;f&lt;/code&gt; 格式或 &lt;code&gt;e&lt;/code&gt; 格式中较短的一种输出，且不输出无意义的零。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;宏定义&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define 标识符 字符串
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;条件编译&lt;/h2&gt;
&lt;p&gt;在嵌入式开发过程中经常会遇到：当满足某个条件时才对一组语句进行编译，而当条件不满足时则编译另一组语句&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#ifdef 标识符
程序1
#else
程序2
#endif
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;code&gt;extern&lt;/code&gt; 外部声明&lt;/h2&gt;
&lt;p&gt;C语言中的 &lt;code&gt;extern&lt;/code&gt; 声明可以置于变量或函数前，以表示变量或函数的定义在别的文件中，提示编译器遇到此变量或函数时在其他文件中去寻找其定义。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;可以在多个文件中进行extern声明，但是只能定义一次&lt;/strong&gt;。而且编译器默认头文件中变量和函数的声明都是 &lt;strong&gt;&quot;外部声明的&quot;&lt;/strong&gt;，即看做是进行了 &lt;strong&gt;隐式extern声明&lt;/strong&gt;，不需要额外进添加 &lt;code&gt;extern&lt;/code&gt; 标识符，但是为了代码的风格统一，建议加上。&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;typedef&lt;/code&gt; 类型重定义&lt;/h2&gt;
&lt;p&gt;用于为现有的类型创建一个新的别名，用于简化变量定义或使得变量名具有更好的可读性。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct _GPIO {
    __IO uint32_t CRL;
    __IO uint32_t CRH;
    __IO uint32_t IDR;
    ...
} GPIO_TypeDef;
GPIO_TypeDef GPIOA;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;类型转换&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;当不同数据类型进行混合运算时,较小的数据类型会自动向较大的数据类型转换&lt;/li&gt;
&lt;li&gt;数据类型按照精度和范围从小到大的顺序为: &lt;code&gt;char &amp;#x3C; int &amp;#x3C; float &amp;#x3C; double&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;字符串数组&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;char acX[]=&quot;abc&quot;;
char acY[]={&apos;a&apos;,&apos;b&apos;,&apos;c&apos;};
char *szX=&quot;abc&quot;;
char *szY=&quot;abc&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;第一种为字符串常量，存储在只读数据段,内容不可修改，会自带 &lt;code&gt;\0&lt;/code&gt; 结束符&lt;/li&gt;
&lt;li&gt;第二种可修改&lt;/li&gt;
&lt;li&gt;相同的字符串常量会被编译器优化到同一地址&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/74066384&quot;&gt;c语言详解sizeof&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://breezetemple.github.io/2015/11/02/printf-formats/&quot;&gt;printf格式化输出&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://microdynamics.github.io/5.%20Breeze%E7%9F%A5%E8%AF%86%E5%BA%93/2.1%20%E5%B5%8C%E5%85%A5%E5%BC%8FC%E8%AF%AD%E8%A8%80%E6%8A%80%E5%B7%A7/&quot;&gt;嵌入式C语言开发技巧&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>UNIX网络编程 part.1: 基础API</title><link>https://astro-pure.js.org/blog/unix-network-porg-api</link><guid isPermaLink="true">https://astro-pure.js.org/blog/unix-network-porg-api</guid><description>UNIX网络编程的基础API</description><pubDate>Fri, 13 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h1&gt;网络编程基础 API&lt;/h1&gt;
&lt;h2&gt;创建 socket&lt;/h2&gt;
&lt;p&gt;socket 是：可读 可写 可控制 可关闭的&lt;strong&gt;文件描述符&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include&amp;#x3C;sys/socket.h&gt;
int socket(int domain, int type, int protocol)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回文件描述符，失败时返回-1 并设置&lt;code&gt;errno&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;domain: 套接字中使用的协议族信息&lt;/li&gt;
&lt;li&gt;type : 套接字数据传输类型信息，主要有&lt;code&gt;SOCK_STREAM&lt;/code&gt; 流服务 &lt;code&gt;SOCK_DGRAM&lt;/code&gt; 数据报&lt;/li&gt;
&lt;li&gt;protocol : 在前两个参数构成的协议集合下，再选择一个具体的协议，大部分情况可以向第三个参数传输 0，除非&lt;strong&gt;同一协议族中存在多个数据传输方式相同的协议&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;协议族信息&lt;/h4&gt;
&lt;p&gt;| 协议族    | 含义                   |
| --------- | ---------------------- |
| PF_INET   | IPv4                   |
| PF_INET6  | IPv6                   |
| PF_LOCAL  | 本地通信的 UNIX 协议族 |
| PF_PACKET | 底层套接字的协议族     |
| PF_IPX    | IPX Novell 协议族      |&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
//tcp套接字
int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
//udp套接字
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;地址信息的表示&lt;/h2&gt;
&lt;h3&gt;通用 socket 地址&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct sockaddr {
	sa_family_t sa_family;
	char sa_data[14];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;专用 socket 地址&lt;/h3&gt;
&lt;p&gt;通用 socket 地址在设置/获取 ip 地址和端口号的时候要进行位操作，很不方便。Linux 为各个协议族提供了专用的结构体：&lt;/p&gt;
&lt;h4&gt;TCP/IP：ipv4&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct sockaddr_in{
	sa_family_t sin_family; //地址族
	uint16_t sin_port; //16位TCP/UDP端口号，以网络字节序保存
	struct in_addr sin_addr; //32位IPv4地址，以网络字节序保存
}

struct in_addr{
	uin32_t s_addr; //32位IPv4地址
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;TCP/IP：ipv6&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct sockaddr_in6{
	sa_family_t sin6_family; //地址族
	uint16_t sin6_port; //16位TCP/UDP端口号，以网络字节序保存
	uint32_t sin6_flowinfo; // 流信息 应设置为0
	struct in6_addr sin6_addr; //ipv6地址结构体
	uint32_t sin6_scope_id; // scope ID 尚处于试验阶段
}

struct in6_addr{
	unsigned char sa_addr[16]; //ipv6地址
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;协议族 地址族&lt;/h4&gt;
&lt;p&gt;| 地址族   | 对应协议族 |               含义               |
| -------- | :--------: | :------------------------------: |
| AF_INET  |  PF_INET   |        IPv4 使用的地址族         |
| AF_INET6 |  PF_INET6  |        IPv6 使用的地址族         |
| AF_UNIX  |  PF_UNIX   | 本地通信中采用 UNIX 协议的地址族 |&lt;/p&gt;
&lt;h3&gt;字节序转换&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;h 代表主机字节序，n 代表网络字节序，长整型用于 IP 地址，短整型用于端口&lt;/p&gt;
&lt;h2&gt;网络地址的初始化与分配&lt;/h2&gt;
&lt;p&gt;ipv4 地址是点分十进制字符串，ipv6 地址是十六进制字符串，编程中先转换为整数（二进制数），
记录日志时把整数 ip 地址转换为字符串&lt;/p&gt;
&lt;h3&gt;ipv4 字符串信息转换为网络字节序整数型&lt;/h3&gt;
&lt;p&gt;点分十进制字符串转换为网络字节序的 ipv4 地址&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include&amp;#x3C;arpa/inet.h&gt;
in_addr_t inet_addr(const char* string);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回 32 位大端序整数值，失败时返回&lt;code&gt;INADDR_NONE&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;与&lt;code&gt;inet_addr&lt;/code&gt;相同，但储存在指针中&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include&amp;#x3C;arpa/inet.h&gt;
int inet_aton(const char* string, struct in_addr* addr);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回 1，失败时返回 0&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;string：含需转换的 IP 地址信息的字符串地址值&lt;/li&gt;
&lt;li&gt;addr：将保存转换结果的 in_addr 结构体变量的地址值&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;将网络字节序表示的 ipv4 地址转换为点分十进制字符串表示，用静态变量存储，不可重入&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include&amp;#x3C;arpa/inet.h&gt;
char* inet_ntoa(struct in_addr addr);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回转换的字符串地址值，失败时返回-1，若需要长期保存，调用完该函数后应立即将字符串信息复制到其他的内存空间&lt;/p&gt;
&lt;h3&gt;同时适用于 ipv4 和 ipv6 的函数&lt;/h3&gt;
&lt;p&gt;将字符串表示的 IP 地址 src 转换成网络字节序整数表示的 IP 地址，结果储存于&lt;code&gt;dst&lt;/code&gt;指向的内存中&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;arpa/inet.h&gt;
int inet_pton(int af, const char* src, void* dst);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回 1，失败时返回 0 并设置&lt;code&gt;errno&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;af：地址族&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;arpa/inet.h&gt;
const char* inet_ntop(int af, const void* src, char* dst, socklen_t cnt)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回目标存储单元地址，失败返回&lt;code&gt;NULL&lt;/code&gt;并设置&lt;code&gt;errno&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cnt：指定目标存储单元的大小&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;自动获取运行服务器端的 IP 地址&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;INADDR_ANY&lt;/code&gt; 自动获取运行服务器端的 IP 地址，若同一计算机中已分配多个 IP 地址（多宿主计算机，例如路由器），只要端口号一致就可以从不同 IP 地址接收数据，服务器端优先考虑这种方式&lt;/p&gt;
&lt;h4&gt;向 socket 分配网络地址&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include&amp;#x3C;sys/socket.h&gt;
int bind(int sockfd, struct sockaddr* myaddr, socklen_t addrlen);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回 0，失败时返回-1&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sockfd：要分配地址信息的 socket 文件描述符&lt;/li&gt;
&lt;li&gt;myaddr：存有地址信息的结构体变量地址&lt;/li&gt;
&lt;li&gt;addrlen：该结构体变量的长度&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;监听 socket&lt;/h2&gt;
&lt;p&gt;创建一个监听队列来存放待处理的客户连接&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include&amp;#x3C;sys/socket.h&gt;
int listen(int sock, int backlog);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回 0，失败时返回-1 并设置&lt;code&gt;errno&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sock：希望进入等待连接请求状态的 socket 文件描述符&lt;/li&gt;
&lt;li&gt;backlog：连接监听队列的长度，表示最多使几个连接请求进入队列，与服务器端的特性有关，典型参数为 5，频繁接收的 web 服务器端至少要 15。超过长度服务器不受理新的客户端连接，客户端收到&lt;code&gt;ECONNREFUSED&lt;/code&gt;信息。内核 2.2 之前指半连接状态和完全连接状态的 socket 的上限，2.2 之后只表示完全连接状态的 socket 上限。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;服务器接受连接&lt;/h2&gt;
&lt;p&gt;从监听队列中接受一个连接（不论连接处于什么状态，不关心网络状态变化）&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include&amp;#x3C;sys/socket.h&gt;
int accept(int sock, struct sockaddr* addr, socklen_t *addrlen);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回创建的套接字文件描述符，失败时返回-1 并设置&lt;code&gt;errno&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sock：执行&lt;code&gt;listen&lt;/code&gt;调用的监听 socket&lt;/li&gt;
&lt;li&gt;addr：获取被接受连接的远端 socket 地址，长度由 addrlen 指出&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;客户端主动连接&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;sys/types.h&gt;
#include &amp;#x3C;sys/socket.h&gt;
int connect(int sockfd, const struct sockaddr* serv_addr, socklen_t addrlen);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回 0，&lt;code&gt;sockfd&lt;/code&gt;唯一的标识这个连接失败时返回-1 并设置&lt;code&gt;errno&lt;/code&gt;，常见的有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ECONNREFUSED&lt;/code&gt;：目标端口不存在，连接被拒绝&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ETIMEOUT&lt;/code&gt;：连接超时&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;关闭连接&lt;/h2&gt;
&lt;p&gt;并非立即关闭，而是将&lt;code&gt;fd&lt;/code&gt;的引用次数-1，当引用次数为 0 时才是真正关闭连接&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include&amp;#x3C;unistd.h&gt;
int close(int fd);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回 0，失败时返回-1&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fd：需要关闭的文件或套接字的文件描述符&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果需要立即关闭终止连接而不是引用次数-1 可以使用&lt;code&gt;shutdown&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include&amp;#x3C;sys/socket.h&gt;
int shutdown(int sock, int howto)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回 0，失败时返回-1(Win：SOCKET_ERROR)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sock:需要断开的 socket 文件描述符&lt;/li&gt;
&lt;li&gt;howto：传递断开方式信息&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;howto 的可选值&lt;/p&gt;
&lt;p&gt;| 可选值       | 含义                                  |
| --------- | ----------------------------------- |
| SHUT_RD   | 断开输入流，输入缓冲收到数据也会被消除                 |
| SHUT_WR   | 断开输出流，输出缓冲如果还有未传输的数据，则传递至目标主机       |
| SHUT_RDWR | 同时断开 I/O 流，等于调用&lt;code&gt;SHUT_RD，SHUT_WR&lt;/code&gt;各一次 |&lt;/p&gt;
&lt;h2&gt;TCP 数据读写&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;sys/socket.h&gt;
ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回发送的字节数, 失败时返回-1 并设置&lt;code&gt;errno&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sockfd: 表示与数据传输对象的连接的套接字文件描述符&lt;/li&gt;
&lt;li&gt;buf: 保存待传输数据的缓冲地址值&lt;/li&gt;
&lt;li&gt;nbytes: 待传输的字节数&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;flags: 指定的可选项信息&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;sys/socket.h&gt;
ssize_t recv(int sockfd, const void *buf, size_t nbytes, int flags);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回接收的字节数, 收到 EOF 返回 0, 失败时返回-1 并设置&lt;code&gt;errno&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sockfd: 表示数据接收对象的连接的套接字文件描述符&lt;/li&gt;
&lt;li&gt;buf: 保存接收数据的缓冲地址值&lt;/li&gt;
&lt;li&gt;nbytes: 能接收的最大字节数&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;flags: 指定的可选项信息&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;flags(可选项)可利用位或(&lt;em&gt;bit OR&lt;/em&gt;)运算同时传递多个信息, 不同操作系统对可选项的支持有不同, 不受操作系统影响的:&lt;/p&gt;
&lt;p&gt;| 选项           | 含义                                           | send | recv |
| -------------- | ---------------------------------------------- | ---- | ---- |
| &lt;code&gt;MSG_OOB&lt;/code&gt;      | 发送/接受紧急数据                              | Y    | Y    |
| &lt;code&gt;MSG_PEEK&lt;/code&gt;     | 窥探读缓存中的数据，此次操作不会导致数据被清除 | N    | Y    |
| &lt;code&gt;MSG_DONTWAIT&lt;/code&gt; | 对 socket 的此次操作是非阻塞的                 | Y    | Y    |&lt;/p&gt;
&lt;h3&gt;紧急模式工作原理&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;MSG_OOB&lt;/code&gt;的意义在督促数据接收对象尽快处理数据, TCP&quot;保持传输顺序&quot;的传输特性仍然成立.&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;MSG_OOB&lt;/code&gt;:发送/接受紧急数据&lt;/h4&gt;
&lt;p&gt;用于创建特殊发送方法和通道, 来发送优先级更高的紧急消息(带外数据, &lt;em&gt;out-of-band data&lt;/em&gt;: 通过完全不同的通信路径传输的数据)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;./oob_recv.out 9190
./oob_send.out 127.0.0.1 9190

Urgent message: 0
12356789
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过&lt;code&gt;MSG_OOB&lt;/code&gt;, &lt;code&gt;urge_handler&lt;/code&gt;读取数据只能读 1 字节, 剩下的用普通输入读取, 因为 TCP 不存在真正意义上的带外数据, oob 应该要用单独的通信路径高速传输,但是 TCP 不提供, 只能用紧急模式传输&lt;/p&gt;
&lt;h4&gt;输出缓冲&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;send(sock, &quot;890&quot;, strlen(&quot;890&quot;), MSG_OOB)&lt;/code&gt;
偏移量 3 的位置存着紧急指针, 指向紧急消息的下一位置, 向对方主机传送消息: 紧急指针指向偏移量 3, 之前的部分是紧急消息&lt;/p&gt;
&lt;p&gt;| 偏移量 | 0   | 1   | 2   | 3        | ... |
| ------ | --- | --- | --- | -------- | --- |
|        | 8   | 9   | 0   | 紧急指针 |     |&lt;/p&gt;
&lt;p&gt;实际只用 1 个字节表示紧急消息信息&lt;/p&gt;
&lt;p&gt;| TCP 头                | 数据 |
| --------------------- | ---- |
| URG = 1, URG 指针 = 3 | 890  |&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;URG=1: 载有紧急消息的数据包&lt;/li&gt;
&lt;li&gt;URG 指针: 紧急指针位于偏移量 3 的位置
我们无法得知具体消息内容, 紧急消息的意义在于督促消息处理, 而不是紧急传输形式受限的消息.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;code&gt;MSG_PEEK&lt;/code&gt;和&lt;code&gt;MSG_DONTWAIT&lt;/code&gt;：检查输入缓冲&lt;/h4&gt;
&lt;p&gt;同时设置&lt;code&gt;MSG_PEEK&lt;/code&gt;和&lt;code&gt;MSG_DONTWAIT&lt;/code&gt;来验证输入缓冲中是否存在接收的数据.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置&lt;code&gt;MSG_PEEK&lt;/code&gt;并调用&lt;code&gt;recv()&lt;/code&gt;, 即使读取了输入缓冲的数据也不会删除, 所以通常和&lt;code&gt;MSG_DONTWAIT&lt;/code&gt;一起用, 以非阻塞方式验证待读数据存在与否.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./peek_recv.out 8190
./peek_send.out 127.0.0.1 8190

Buffering 3 bytes: 123
Read again: 123 # 设置MSG_PEEK 数据被读取2次
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;UDP 数据读写&lt;/h2&gt;
&lt;p&gt;UDP 通信没有连接的概念，每次读取数据都要获取发送端的 socket 地址。
这两个函数也可以用于面向连接的数据读写，只要把最后两个参数都设置&lt;code&gt;NULL&lt;/code&gt;以忽略发送端/接收端 socket 地址&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include&amp;#x3C;sys/socket.h&gt;
ssize_t sendto(int sock, void *buf, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回传输字节数，失败时返回-1&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sock：传输数据的 UDP 套接字文件描述符&lt;/li&gt;
&lt;li&gt;buf：保存待传输数据的缓冲地址值&lt;/li&gt;
&lt;li&gt;nbytes：待传输的数据长度，以字节为单位&lt;/li&gt;
&lt;li&gt;flags：可选参数，默认 0&lt;/li&gt;
&lt;li&gt;to：存有目标地址信息的 sockaddr 结构体变量的地址值&lt;/li&gt;
&lt;li&gt;addrlen：传输给参数 to 的结构体变量长度的变量地址值&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include&amp;#x3C;sys/socket.h&gt;
ssize_t recvfrom(int sock, void* buf, size_t nbytes, int flags, struct sockaddr* from, socklen_t* addrlen);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回传输字节数，失败时返回-1&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sock：接收数据的 UDP 套接字文件描述符&lt;/li&gt;
&lt;li&gt;buf：保存数据的缓冲地址值&lt;/li&gt;
&lt;li&gt;nbytes：可接收的最大字节数，无法超过参数 buff 所指的缓冲大小&lt;/li&gt;
&lt;li&gt;flags：可选参数，默认 0&lt;/li&gt;
&lt;li&gt;from：存有发送端地址信息的 sockaddr 结构体变量的地址值&lt;/li&gt;
&lt;li&gt;addrlen：保存参数 from 的结构体变量长度的变量地址值&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;通用数据读写&lt;/h2&gt;
&lt;p&gt;不仅用于 TCP，也可以用于 UDP&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;sys/socket.h&gt;
ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr* msg, int flags);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct msghdr {
	void* msg_name; // socket地址
	socklen_t msg_namelen; // socket地址长度
	struct iovec* msg_iov; // 分散的内存块
	int msg_iovlen; // 分散内存块数量
	void* msg_control; // 指向辅助数据的起始位置
	socklen_t msg_controllen; // 辅助数据大小
	int msg_flags; // 复制函数中的flag参数 调用过程中更新
};

struct iovec {
	void* iov_base; // 内存起始地址
	size_t iov_len; // 内存长度
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回发送的字节数, 失败时返回-1 并设置&lt;code&gt;errno&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;recvmsg&lt;/code&gt; 时数据将被读取，存放在&lt;code&gt;msg_iovlen&lt;/code&gt;块分散的内存中，位置和长度由&lt;code&gt;msg_iov&lt;/code&gt;指向的数组指定，这是分散读(&lt;em&gt;scatter read&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sendmsg&lt;/code&gt;时&lt;code&gt;msg_iovlen&lt;/code&gt;块分散内存中的数据将被一并发送，称为集中写(&lt;em&gt;gather write&lt;/em&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sockfd: 被操作目标 socket&lt;/li&gt;
&lt;li&gt;msg：指针&lt;/li&gt;
&lt;li&gt;flags：可选参数，默认&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;带外标记&lt;/h2&gt;
&lt;p&gt;Linux 内核检测到 TCP 紧急标志的时候，先通知应用程序：有带外数据需要接受
有两种方式：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I/O 复用产生的异常事件&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SIGURG&lt;/code&gt;信号
还需要知道带外数据在数据流中的具体位置：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;判断&lt;code&gt;sockfd&lt;/code&gt;是否处于带外标记（下一个被读取到的数据是否是带外数据）&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;sys/socket.h&gt;
int sockatmark(int sockfd);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果是则返回 1，此时可以利用带&lt;code&gt;MSG_OOB&lt;/code&gt;的&lt;code&gt;recv&lt;/code&gt;来接受，不是则返回 0&lt;/p&gt;
&lt;h2&gt;地址信息函数&lt;/h2&gt;
&lt;p&gt;获取&lt;code&gt;sockfd&lt;/code&gt;对应的本端 socket 地址，将其存储于&lt;code&gt;address&lt;/code&gt;参数指定的内存中，该 socket 地址的长度则存储于&lt;code&gt;address_len&lt;/code&gt;指向的变量中（实际 socket 地址长度比&lt;code&gt;address&lt;/code&gt;所指内存大的时候，地址会被截断）&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;sys/socket.h&gt;
int getsockname(int sockfd, struct sockaddr* address, socklen_t* address_len);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int getpeername(int sockfd, struct sockaddr* address, socklen_t* address_len);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回 0，失败时返回-1 并设置 &lt;code&gt;errno&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;socket 的可选项&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;sys/socket.h&gt;
int getsockopt(int sock, int level, int optname,  void *optval, socklen_t *optlen );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回 0，失败时返回-1&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sock:查看选项的 socket 文件描述符&lt;/li&gt;
&lt;li&gt;level：查看选项的协议层&lt;/li&gt;
&lt;li&gt;optname：查看的可选项名&lt;/li&gt;
&lt;li&gt;optval：保存查看结果的缓冲地址值&lt;/li&gt;
&lt;li&gt;optlen：向 optval 传递的缓冲大小，调用函数后，该变量保存通过 optval 返回的可选项的字节数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对可选项修改：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;sys/socket.h&gt;
int setsockopt(int sock, int level, int optname,  const void *optval, socklen_t optlen );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回 0，失败时返回-1&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sock:更改选项的 socket 文件描述符&lt;/li&gt;
&lt;li&gt;level：更改选项的协议层&lt;/li&gt;
&lt;li&gt;optname：更改的可选项名&lt;/li&gt;
&lt;li&gt;optval：保存更改结果的缓冲地址值&lt;/li&gt;
&lt;li&gt;optlen：向 optval 传递的可选项的字节数&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;code&gt;SO_REUSEADDR&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;[[TIME_WAIT]]
服务器可以通过设置 socket 选项 &lt;code&gt;SO_REUSEADDR&lt;/code&gt; 来强制使用被处于 TIME_WAIT 状态的连接占用的 socket 地址&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int sock = socket(PF_INET, SOCK_STREAM, 0);
int reuse = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &amp;#x26;reuse, sizeof(reuse));
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;SO_RCVBUF&lt;/code&gt; 和 &lt;code&gt;SO_SNDBUF&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;修改 TCP 缓冲区大小。使用&lt;code&gt;setsockopt&lt;/code&gt; 设置 TCP 发送和接收的缓冲区大小时，系统会将其加倍且不得小于某个最小值，来确保一个 TCP 连接有足够的空闲缓冲区来处理拥塞（比如快速重传期望 TCP 接收缓冲区至少容纳 4 个大小为 SMSS 的 TCP 报文段）&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SO_RCVBUF&lt;/code&gt;：接收缓冲区大小(min:256byte 不同系统可能不同)
&lt;code&gt;SO_SNDBUF&lt;/code&gt;：发送缓冲区大小(max:2048byte)&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;SO_RCVLOWAT&lt;/code&gt; 和 &lt;code&gt;SO_SNDLOWAT&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;SO_RCVLOWAT&lt;/code&gt;：接收缓冲区的低水位标记 (1byte)
&lt;code&gt;SO_SNDLOWAT&lt;/code&gt;：发送缓冲区的低水位标记 (1byte)&lt;/p&gt;
&lt;p&gt;一般被 I/O 复用系统调用来判断 socket 是否可读或者可写：&lt;/p&gt;
&lt;p&gt;TCP 接收缓冲区中可读数据的总数大于其低水位标记时，I/O 复用系统调用将通知应用程序可以从对应的 socket 上读取。
TCP 发送缓冲区中可读数据的总数大于其低水位标记时，I/O 复用系统调用将通知应用程序可以往对应的 socket 上写入。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;SO_LINGER&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;控制&lt;code&gt;close&lt;/code&gt;在关闭 TCP 连接的行为&lt;/p&gt;
&lt;p&gt;设置&lt;code&gt;SO_LINGER&lt;/code&gt;时我们给&lt;code&gt;setsockopt&lt;/code&gt; / &lt;code&gt;getsockopt&lt;/code&gt; 调用传递一个&lt;code&gt;linegr&lt;/code&gt; 类型结构体：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;sys/socket.h&gt;
struct linger {
	int l_onoff; // 开启/关闭
	int l_linger; // 滞留时间
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;l_oneoff = 0&lt;/code&gt; 时 &lt;code&gt;SO_LINGER&lt;/code&gt;不起作用，&lt;code&gt;close&lt;/code&gt; 默认关闭 socket&lt;/li&gt;
&lt;li&gt;&lt;code&gt;l_oneoff != 0 l_linger = 0&lt;/code&gt;，&lt;code&gt;close&lt;/code&gt;立即返回，TCP 模块丢弃关闭的 socket 对应的 TCP 发送缓冲区中残留的数据，给对方发一个复位报文，提供了异常终止一个连接的方法&lt;/li&gt;
&lt;li&gt;&lt;code&gt;l_oneoff != 0, l_linegr &gt; 0&lt;/code&gt;，&lt;code&gt;close&lt;/code&gt; 行为取决于：被关闭的 socket 对应的 TCP 发送缓冲区是否有残留数据， 该 socket 是否非阻塞。阻塞 socket 等待时间为&lt;code&gt;l_linger&lt;/code&gt;直到 TCP 模块发送完所有残留数据并得到对方确认，如果没有成功就返回-1 并设置&lt;code&gt;errno&lt;/code&gt;，非阻塞则立即返回&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;网络信息 API&lt;/h2&gt;
&lt;h3&gt;获取主机完整信息&lt;/h3&gt;
&lt;p&gt;根据主机名称获取主机完整信息，先在本地&lt;code&gt;/etc/hosts&lt;/code&gt;配置文件寻找主机，找不到就访问 DNS 服务器&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include&amp;#x3C;netdb.h&gt;
struct hostent* gethostbyname(const char* hostname)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;hostname: 域名字符串&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;根据 IP 地址获取主机完整信息&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include&amp;#x3C;netdb.h&gt;
struct hostent* gethostbyaddr(const char* addr, socklen_t len, int family)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;addr:含有 ip 地址信息的 in_addr 结构体指针，为了同时传递 IPv4 地址以外的信息，应该声明为 char 指针&lt;/li&gt;
&lt;li&gt;len：向第一个参数传递的地址信息的字节数，IPv4：4 IPv6：16&lt;/li&gt;
&lt;li&gt;family：地址族信息&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;都返回&lt;code&gt;hostent&lt;/code&gt;结构体类型的指针：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct hostent {
	char* h_name; //官方域名
	char** h_aliases;//同一IP绑定的其他域名
	int h_addrtype; //地址族信息(eg. IPv4:AF_INET)
	int h_length; // IP地址长度(byte)(IPv4:4 IPv6:16)
	char** h_addr_list //以整数形式保存域名对应的IP地址，用户较多的网站有可能分配多个IP给同一域名，利用多个服务器进行负载均衡。
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;获取某个服务完整信息&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;netdb.h&gt;
struct servent* getservbyname(const char* name, const char* proto);
struct servent* getservbyport(int port, const char* proto);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;name: 指定服务名字&lt;/li&gt;
&lt;li&gt;port：指定服务的端口号&lt;/li&gt;
&lt;li&gt;proto：服务类型(&lt;code&gt;&quot;tcp&quot;&lt;/code&gt;：流服务 &lt;code&gt;&quot;udp&quot;&lt;/code&gt;：数据报服务&lt;code&gt;NULL&lt;/code&gt;：所有类型)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct servent {
  char* s_name; // 服务名
  char** s_aliases; // 服务别名
  short s_port; // 端口号
  char* s_proto; // 服务类型 tcp/udp
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;getaddrinfo&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;既能通过主机名获得 ip 地址，也能通过服务名获取端口号&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;netdb.h&gt;
int getaddrinfo(const char* hostname, const char* service, const struct addrinfo* hints, struct addrinfo** result);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;hostname：主机名/字符串 ip 地址&lt;/li&gt;
&lt;li&gt;service：服务名/字符串端口&lt;/li&gt;
&lt;li&gt;hints：应用程序给这个函数的一个提示，可设&lt;code&gt;NULL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;result：指向存储&lt;code&gt;getaddrinfo&lt;/code&gt;反馈结果的链表&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct addrinfo {
	int ai_flags;  //AI_PASSIVE,AI_CANONNAME,AI_NUMERICHOST
	int ai_family;        //AF_INET,AF_INET6
	int ai_socktype;    //SOCK_STREAM,SOCK_DGRAM
	int ai_protocol;    //IPPROTO_IP, IPPROTO_IPV4,IPPROTO_IPV6 etc.
	size_t ai_addrlen;            //must be zero or a null pointer
	char* ai_canonname;            //must be zero or a null pointer
	struct sockaddr* ai_addr;    //must be zero or a null pointer
	struct addrinfo* ai_next;    //must be zero or a null pointer
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们使用&lt;code&gt;hints&lt;/code&gt;时只设置前四个字段，后面设&lt;code&gt;NULL&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;该函数隐式地分配堆内存，因为&lt;code&gt;res&lt;/code&gt;原本没有指向一块合法内存，所以调用结束我们要用配对的函数释放这块内存&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;netdb.h&gt;
void freeaddrinfo(struct addrinfo* res);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;getnameinfo&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;通过 socket 地址同时获得以字符串表示的主机名和服务名&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;netdb.h&gt;
int getnameinfo (const struct sockaddr* sockaddr, socklen_t addrlen, char* host, socklen_t hostlen, char* serv, socklen_t servlen, int flags);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将返回的主机名存储在&lt;code&gt;host&lt;/code&gt;指向的缓存中，将服务名存储在&lt;code&gt;serv&lt;/code&gt;指向的缓存中&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hostlen servlen：指定两块缓存的长度&lt;/li&gt;
&lt;li&gt;flag: 指定行为&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>UNIX网络编程 part.2: Epoll和Reactor模式</title><link>https://astro-pure.js.org/blog/epoll-io</link><guid isPermaLink="true">https://astro-pure.js.org/blog/epoll-io</guid><description>介绍最常见的多路复用Epoll用法和常用架构Reactor模式</description><pubDate>Tue, 13 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Epoll&lt;/h1&gt;
&lt;h2&gt;内核事件表和系统调用&lt;/h2&gt;
&lt;p&gt;Linux 特有的 I/O 复用函数，创建一个文件描述符标识内核的一个事件表，然后把用户关心的文件描述符上的事件放在这个事件表中，使用 &lt;code&gt;epoll_create&lt;/code&gt; 或 &lt;code&gt;epoll_create1&lt;/code&gt; 创建事件表：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;#x3C;sys/epoll.h&gt;
int epoll_create(int size);
int epoll_create1(int flag);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回文件描述符。&lt;code&gt;epoll_create&lt;/code&gt; 的 &lt;code&gt;size&lt;/code&gt; 仅用于提示内核事件表的大小。&lt;code&gt;epoll_create1&lt;/code&gt; 更现代化，引入了&lt;code&gt;flags&lt;/code&gt;参数，允许传递标志来控制&lt;code&gt;epoll&lt;/code&gt;实例的特性，例如 &lt;code&gt;EPOLL_CLOEXEC&lt;/code&gt; 设置&lt;code&gt;close-on-exec&lt;/code&gt;标志，使得这个文件描述符在执行&lt;code&gt;execve()&lt;/code&gt;后自动关闭，避免资源泄露。&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;epoll_ctl&lt;/code&gt; 操作内核事件表：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功返回 &lt;code&gt;0&lt;/code&gt;，失败返回 &lt;code&gt;-1&lt;/code&gt; 并设置 &lt;code&gt;errno&lt;/code&gt;。&lt;code&gt;epfd&lt;/code&gt; 为刚才创建好的事件表的文件描述符，&lt;code&gt;op&lt;/code&gt; 指定操作类型，&lt;code&gt;fd&lt;/code&gt; 为要操作的文件描述符，&lt;code&gt;event&lt;/code&gt; 指定事件。&lt;/p&gt;
&lt;p&gt;操作类型有3种宏：&lt;code&gt;EPOLL_CTL_ADD&lt;/code&gt; 往事件表中注册 &lt;code&gt;fd&lt;/code&gt; 的事件，&lt;code&gt;EPOLL_CTL_MOD&lt;/code&gt; 表示修改，&lt;code&gt;EPOLL_CTL_DEL&lt;/code&gt; 表示删除。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;event&lt;/code&gt; 的类型是结构体 &lt;code&gt;epoll_event&lt;/code&gt;，包括事件和对应用户数据，其中用户数据的类型是一个 &lt;code&gt;union&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;struct epoll_event {
	__uint32_t events; // epoll 事件
	epoll_data_t data; // 用户数据
}

typedef union epoll_data{
	void* ptr; // 指定相关用户数据
	int fd; // 指定事件所属的目标文件描述符
	uint32_t u32;
	uint64_t u64;
} epoll_data_t;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;设置好内核事件表后，使用 &lt;code&gt;epoll_wait&lt;/code&gt; 系统调用，它在一段超时时间内等待一组文件描述符上的事件：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时返回就绪的文件描述符个数，失败返回 &lt;code&gt;-1&lt;/code&gt; 并设置 &lt;code&gt;errno&lt;/code&gt;，超时返回 &lt;code&gt;0&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;maxevents&lt;/code&gt; 指定最多监听多少个事件。&lt;code&gt;timeout&lt;/code&gt; 控制其阻塞行为，允许应用程序在等待多个文件描述符事件时实现非阻塞、阻塞或带超时等待。&lt;code&gt;timeout = -1&lt;/code&gt; (无限期阻塞直到事件发生)，&lt;code&gt;timeout = 0&lt;/code&gt;(立即返回，不阻塞)，大于 0 (阻塞指定毫秒数，但不保证精确)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;epoll_wait&lt;/code&gt; 如果检测到事件，就把所有就绪的事件从 &lt;code&gt;epfd&lt;/code&gt; 指向的内核事件表复制到 &lt;code&gt;events&lt;/code&gt; 指向的数组，该数组只输出 &lt;code&gt;epoll_wait&lt;/code&gt; 检测到的就绪事件，提高了应用程序索引文件描述符的效率，和 &lt;code&gt;select&lt;/code&gt; 和 &lt;code&gt;poll&lt;/code&gt; 的数组有所不同。&lt;/p&gt;
&lt;h2&gt;操作模式&lt;/h2&gt;
&lt;p&gt;默认以 LT 工作&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LT(Level Trigger)：相当于效率较高的 &lt;code&gt;poll&lt;/code&gt;。支持非阻塞和阻塞 socket。&lt;code&gt;epoll_wait&lt;/code&gt; 监测到该事件 &lt;code&gt;fd&lt;/code&gt; 已经就绪并通知应用程序后，可以不立即处理该事件（对一个 fd 进行 I/O 操作），然后每调用一次 &lt;code&gt;epoll_wait&lt;/code&gt; 就继续通知直到该事件被处理。&lt;/li&gt;
&lt;li&gt;ET(Edge Trigger)：只支持非阻塞 socket。&lt;code&gt;epoll_wait&lt;/code&gt; 监测到该事件 &lt;code&gt;fd&lt;/code&gt; 已经就绪并通知应用程序后，必须立即处理该事件，做了某些操作直到该 &lt;code&gt;fd&lt;/code&gt; 不再是就绪状态。注意变成就绪态后，如果一直不对这个 &lt;code&gt;fd&lt;/code&gt; 作 I/O操作从而导致它再次变成未就绪，内核不会发送更多的通知。ET 降低同一个事件被重复触发的次数，效率比 LT 更高。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Reactor模式&lt;/h1&gt;
&lt;p&gt;Reactor 是用面向对象思想对 I/O 复用进行的封装，用于同步 I/O
Reactor可以有一个或多个，处理资源池可以是一个或多个进程 / 线程，可分为单 Reactor 单线程 / 单 Reactor 多线程 / 多Reactor 多线程三种模式。&lt;/p&gt;
&lt;h2&gt;中心思想&lt;/h2&gt;
&lt;p&gt;将所有要处理的I/O事件注册到一个中心 I/O 多路复用器上，同时主线程/进程阻塞在多路复用器上，主线程（I/O 处理单元）&lt;strong&gt;只负责监听文件描述上是否有事件发生&lt;/strong&gt;，一旦有I/O事件到来或是准备就绪(文件描述符或socket可读、写)，多路复用器返回并将事先注册的相应I/O事件分发到对应的处理器中。&lt;/p&gt;
&lt;h2&gt;事件驱动机制&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;和普通函数调用不同，并不是主动调用 API 处理事件，而是应用程序需要提供相应的接口并注册到Reactor上，先有事件发生，再由 Reactor 主动调用应用程序注册的接口，这些接口是类中的回调函数&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;事件驱动机制可以用软件工程的 Hollywood 原则形容，即 &lt;em&gt;&quot;Don&apos;t call us, we&apos;ll call you&quot;&lt;/em&gt;，用于软件工程以降低模块间耦合。在高层组件（如框架）与低层组件的关系中，低层组件将自己挂钩到系统，由框架负责在其需要时主动调用低层组件，实现了控制反转。Reactor 并没有被具体的事件处理器调度，而是管理器调度具体的事件处理器，由事件处理器对发生的事件作出处理，这就是Hollywood 原则。&lt;/p&gt;
&lt;p&gt;Reactor 模式与 Observer 模式在某些方面极为相似：当一个主体发生改变时，所有依属体都得到通知。不过，Observer 模式与单个事件源关联，而 Reactor 模式则与多个事件源关联。观察者模式允许对象在状态改变时通知多个观察者对象。&lt;code&gt;std::function&lt;/code&gt; 可以用来存储观察者对象的回调函数，使得实现观察者模式更加灵活和简洁。&lt;/p&gt;
&lt;h2&gt;参与部件&lt;/h2&gt;
&lt;h3&gt;描述符&lt;/h3&gt;
&lt;p&gt;操作系统提供的资源，用于识别每一个事件，如 Socket 描述符、文件描述符、信号的值等。
在Linux中，它用一个整数来表示。事件可以来自外部，如来自客户端的连接请求、数据等。事件也可以来自内部，如信号、定时器事件。&lt;/p&gt;
&lt;h3&gt;同步事件多路分离器&lt;/h3&gt;
&lt;p&gt;事件循环：事件的到来是随机的、异步的，无法预知程序何时收到一个客户连接请求或收到一个信号。所以程序要循环等待并处理事件。&lt;/p&gt;
&lt;p&gt;在事件循环中，等待事件一般使用I/O复用实现。一般是 &lt;code&gt;select&lt;/code&gt;、&lt;code&gt;poll&lt;/code&gt;、&lt;code&gt;epol_waitl&lt;/code&gt; 等系统调用，用来等待一个或多个事件的发生。I/O 框架库一般将各种 I/O 复用系统调用封装成统一的接口，称为事件多路分离器。调用者会被阻塞，直到分离器分离的描述符集上有事件发生。&lt;/p&gt;
&lt;h3&gt;事件处理器&lt;/h3&gt;
&lt;p&gt;I/O 框架库提供的事件处理器通常是由一个或多个模板函数组成的接口。这些模板函数描述了和应用程序相关的对某个事件的操作，用户需要继承它来实现自己的事件处理器，即具体事件处理器。&lt;/p&gt;
&lt;p&gt;事件处理器中的回调函数可以有多种实现方式，比如&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用传统的虚类、虚函数来设计一个接口，缺点是使用麻烦，程序可读性差&lt;/li&gt;
&lt;li&gt;C++11 的 &lt;code&gt;std::function&lt;/code&gt; 和 &lt;code&gt;std::bind&lt;/code&gt;，右值引用，&lt;code&gt;std::move&lt;/code&gt;，然而现在有更新的标准， &lt;code&gt;std::bind&lt;/code&gt; 已建议不要使用&lt;/li&gt;
&lt;li&gt;待补充&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;具体的事件处理&lt;/h3&gt;
&lt;p&gt;事件处理器接口的实现。它实现了应用程序提供的某个服务。每个具体的事件处理器总和一个描述符相关。它使用描述符来识别事件、识别应用程序提供的服务。&lt;/p&gt;
&lt;h3&gt;Reactor 管理器&lt;/h3&gt;
&lt;p&gt;一个类，定义了一些接口，用于应用程序控制事件调度，以及应用程序注册、删除事件处理器和相关的描述符。它是事件处理器的调度核心。 Reactor 管理器使用同步事件分离器来等待事件的发生。一旦事件发生，Reactor管理器先是分离每个事件，然后调度事件处理器，最后调用相关的模 板函数来处理这个事件。&lt;/p&gt;
&lt;h2&gt;为什么网络编程要使用 Reactor&lt;/h2&gt;
&lt;p&gt;只使用 I/O复用的 &lt;code&gt;epoll&lt;/code&gt; 已经可以使服务器并发几十万连接，但是软件工程层面上不够。I/O 复用接口的一次调用返回若干个活跃连接等待处理，先根据连接的指针找出对象，然后循环处理每个连接找出对象的上下文状态，再用 &lt;code&gt;read&lt;/code&gt; &lt;code&gt;write&lt;/code&gt; 来获取操作内容，结合上下文状态查询此时应当选择哪个业务方法处理，调用相应方法完成操作后，若请求结束，则删除对象及其上下文。&lt;/p&gt;
&lt;p&gt;单纯 I/O 复用是面向过程的，达不到快速响应。Reactor 将事件驱动框架分离出具体业务，将不同类型请求之间用面向对象的思想分离。通常，Reactor 不仅使用 I/O 复用处理网络事件驱动，还会实现定时器来处理时间事件的驱动（请求的超时处理或者定时任务的处理）。&lt;/p&gt;
&lt;h2&gt;Reactor 的模式&lt;/h2&gt;
&lt;h3&gt;单线程模式&lt;/h3&gt;
&lt;p&gt;Reactor 负责多路分离套接字，Accept 新连接，并分派请求到处理器链中。该模型适用于处理器链中业务处理组件能快速完成的场景。但不能充分利用多核资源。&lt;/p&gt;
&lt;h3&gt;单 Reactor 多线程模式&lt;/h3&gt;
&lt;p&gt;在事件处理器链部分采用了多线程 / 线程池，是后端程序常用的模型。&lt;/p&gt;
&lt;h3&gt;多 Reactor 多线程模式&lt;/h3&gt;
&lt;p&gt;将 Reactor 分为两部分，mainReactor负责监听并 accept新连接，然后将建立的 socket 通过多路复用器（Acceptor）分派给 subReactor。subReactor 负责多路分离已连接的 socket，读写网络数据，业务处理功能，其交给 worker 线程池完成。通常，subReactor 个数上可与CPU个数等同。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://astro-pure.js.org/_vercel/image?url=_astro%2Fmulti_thread_reactor.bgwGoYs-.png&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;网络编程范例&lt;/h2&gt;
&lt;p&gt;半同步半异步的范例，线程池为同步，Reactor 为异步&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./Reactor.svg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;主线程往 epoll 内核事件表中注册 socket 读就绪事件&lt;/li&gt;
&lt;li&gt;主线程调用 &lt;code&gt;epoll_wait&lt;/code&gt; 等待 socket 上有数据可读&lt;/li&gt;
&lt;li&gt;当 socket 上有数据可读，&lt;code&gt;epoll_wait&lt;/code&gt; 通知主线程。主线程则将 socket 可读事件放入请求队列&lt;/li&gt;
&lt;li&gt;睡眠在请求队列上的某个工作线程被唤醒，从 socket 读取数据，处理客户请求，然后往 epoll 内核事件表中注册该 socket 上的写就绪事件&lt;/li&gt;
&lt;li&gt;主线程调用 &lt;code&gt;epoll_wait&lt;/code&gt; 等待 socket 可写&lt;/li&gt;
&lt;li&gt;当 socket 可写时，&lt;code&gt;epoll_wait&lt;/code&gt; 通知主线程，主线程将 socket 可写事件放入请求队列&lt;/li&gt;
&lt;li&gt;睡眠在请求队列上的某个工作线程被唤醒，往 socket 写入服务器处理客户端请求的结果&lt;/li&gt;
&lt;/ol&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>KDE桌面环境更换窗口管理器为bspwm</title><link>https://astro-pure.js.org/blog/unixporn-kde-bspwm</link><guid isPermaLink="true">https://astro-pure.js.org/blog/unixporn-kde-bspwm</guid><description>KDE桌面环境更换窗口管理器为bspwm</description><pubDate>Tue, 06 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;运行环境&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;arch系发行版（以manjaro为例）&lt;/li&gt;
&lt;li&gt;桌面环境 kde plasma 6.0.5&lt;/li&gt;
&lt;li&gt;qt 6.7.2&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;安装组件&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sudo pacman -S bspwm sxhkd picom nitrogen rofi polybar dunst
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;bspwm: 窗口管理器&lt;/li&gt;
&lt;li&gt;sxhkd: 改变快捷键&lt;/li&gt;
&lt;li&gt;picom: 窗口效果混成器, 改变不透明度等&lt;/li&gt;
&lt;li&gt;dunst: 通知（可选）&lt;/li&gt;
&lt;li&gt;polybar: 桌面顶部的状态栏&lt;/li&gt;
&lt;li&gt;rofi: 应用选择菜单&lt;/li&gt;
&lt;li&gt;nitrogen: 壁纸更改（可选）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;替换kwin为bspwm&lt;/h2&gt;
&lt;p&gt;为当前用户禁用 plasma-kwin_x11.service 服务避免KWin启动&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;systemctl --user mask plasma-kwin_x11.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建文件 &lt;code&gt;~/.config/systemd/user/plasma-custom-wm.service&lt;/code&gt; 作为新的用户单元，编辑内容&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;[Install]
WantedBy=plasma-workspace.target

[Unit]
Description=Plasma Custom Window Manager
Before=plasma-workspace.target

[Service]
ExecStart=/usr/bin/bspwm # 你的窗口管理器的安装位置
Slice=session.slice
Restart=on-failure
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重新扫描用户单元，确保Kwin服务plasma-kwin_x11.service已经禁用，然后启用新的plasma-custom-wm.service窗口管理器服务&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;systemctl --user daemon-reload
systemctl --user enable plasma-custom-wm.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;复制默认配置文件&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;cp /usr/share/doc/bspwm/examples/bspwmrc bspwm/
cp /usr/share/doc/sxhkd/examples/sxhkdrc sxhkd/
cp /etc/xdg/picom.conf picom/
cp /etc/polybar/config.ini polybar/
cp /etc/dunst/dunstrc dunst/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在bspwmrc启动脚本中加入自动启动应用，注意配置文件有哪些组件记得启动&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sxhkd &amp;#x26;
picom --config $HOME/.config/picom/picom.conf &amp;#x26;
nitrogen --restore &amp;#x26;
dunst &amp;#x26;
polybar &amp;#x26;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注销账号重新进入即可&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=XTcf8g54RuU&quot;&gt;BSPWM Installation and basic Configuration Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.archlinux.org/title/KDE&quot;&gt;arch linux wiki - KDE&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>使用TOML配置Alacritty(&gt;=0.13.2)</title><link>https://astro-pure.js.org/blog/unixporn-alacritty</link><guid isPermaLink="true">https://astro-pure.js.org/blog/unixporn-alacritty</guid><description>配置终端模拟器Alacritty</description><pubDate>Sun, 28 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;TOML是一种简单易读的配置文件语法，Alacritty在新版本已经把配置从yaml语言移到TOML了。&lt;/p&gt;
&lt;h2&gt;安装 Alacritty&lt;/h2&gt;
&lt;p&gt;Windows: 前往仓库的&lt;a href=&quot;https://github.com/alacritty/alacritty/releases&quot;&gt;Release页面&lt;/a&gt;下载.msi可执行文件&lt;/p&gt;
&lt;p&gt;UNIX: 自行使用系统对应的包管理器命令安装，不要使用 cargo 给自己找麻烦&lt;/p&gt;
&lt;h2&gt;建立配置文件&lt;/h2&gt;
&lt;p&gt;Windows 路径: &lt;code&gt;%APPDATA%/alacritty/alacritty.toml&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;UNIX 路径: &lt;code&gt;$HOME/.config/alacritty/alacritty.toml&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;配置&lt;/h2&gt;
&lt;p&gt;使用vscode编辑时安装TOML Language Support获得语法高亮&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;#设置改变配置自动启动 [window]的需要重启
live_config_reload = true

#改变窗口设置
[window]
#窗口大小
dimensions = { columns = 135, lines = 30 }
padding = { x = 4, y = 2 }
dynamic_padding = true
#透明度
opacity = 0.9
#窗口名字
title = &quot;Alacritty&quot;

#字体
[font]
normal = { family = &quot;FiraCode Nerd Font

&quot;, style = &quot;Bold&quot; }
bold = { family = &quot;FiraCode Nerd Font&quot;, style = &quot;Bold&quot; }
italic = { family = &quot;FiraCode Nerd Font&quot;, style = &quot;Bold&quot; }
size = 12.0

#颜色主题可以在github上找想要的主题，复制他的配色.
[colors.primary]
background = &quot;#1a1b26&quot;
foreground = &quot;#a9b1d6&quot;

[colors.normal]
black = &quot;#32344a&quot;
red = &quot;#f7768e&quot;
green = &quot;#9ece6a&quot;
yellow = &quot;#e0af68&quot;
blue = &quot;#7aa2f7&quot;
magenta = &quot;#ad8ee6&quot;
cyan =  &quot;#449dab&quot;
white = &quot;#787c99&quot;

[colors.bright]
black = &quot;#444b6a&quot;
red = &quot;#ff7a93&quot;
green = &quot;#b9f27c&quot;
yellow = &quot;#ff9e64&quot;
blue =  &quot;#7da6ff&quot;
magenta = &quot;#bb9af7&quot;
cyan =  &quot;#0db9d7&quot;
white = &quot;#acb0d0&quot;

#光标
[cursor]
style = { shape = &quot;Beam&quot;, blinking = &quot;On&quot; }

[terminal]
#设定复制黏贴行为 默认只能复制
osc52 = &quot;CopyPaste&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://alacritty.org/config-alacritty.html&quot;&gt;配置指南 Alacritty - TOML configuration file format&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://toml.io/en/&quot;&gt;TOML语法wiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alacritty/alacritty-theme&quot;&gt;Alacritty-Theme&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/zatchheems/tokyo-night-alacritty-theme&quot;&gt;tokyo-night-alacritty-theme&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item></channel></rss>