嵌入式养成计划-32-网络编程----域套接字模型------抓包工具--wireshark

六十九、 域套接字模型

69.1 域套接字的概念

  1. 只能做一台主机内的进程间通信,协议族(地址族)指定为:AF_UNIX AF_LOCAL
  2. bsp-lcd: s类型文件,就是域套接字
  3. 如果客户端不手动绑定,则操作系统不会创建一个套接字文件给客户端自动绑定的。

69.2 域套接字的函数使用

69.2.1 socket

功能:在内核空间中创建两个缓冲区(接收缓冲区,发送缓冲区),并返回缓冲区的文件描述(套接字文件描述符);
原型:#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>int socket(int domain, int type, int protocol);
参数:int domain:地址族,协议族;Name                Purpose                          Man pageAF_UNIX, AF_LOCAL   Local communication              unix(7)int type:套接字类型SOCK_STREAM:字节流式套接字,流式套接字---》TCP协议SOCK_DGRAM: 数据报式套接字,报式套接字---》UDP协议SOCK_RAW:原始套接字,协议需要在第三个参数指定;int protocol:填0,代表使用默认协议;IPPROTO_TCP:TCP协议    IPPROTO_UDP:UDP协议;
返回值:>=0,套接字文件描述符;=-1,失败,更新errno;

69.2.2 bind

功能:绑定地址信息到指定套接字上;
原型:#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数:int sockfd:指定要绑定到哪个套接字上,填对应的文件描述符;struct sockaddr *addr:通用地址信息结构体,真实的地址信息结构体根据地址族指定。手动填充上地址信息(例如:IP和端口),给bind函数绑定使用;AF_UNIX : man 7 unix struct sockaddr_un {sa_family_t sun_family;		/* AF_UNIX */     必须填AF_UNIXchar        sun_path[108];	/* pathname */    套接字文件路径名必须事先不存在,由bind函数生成};socklen_t addrlen:真实的地址信息结构体的大小,sizeof(struct sockaddr_un);
返回值:成功,返回0;失败,返回-1,更新errno;

69.2.3 access

功能:判断文件是否存在,或者文件是否有某种权限;
原型:

69.2.4 unlink

功能:删除文件(的硬链接)
原型:

69.3 流式域套接字

69.3.1 UNIX TCP服务器

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <sys/un.h>#define ERR_MSG(msg)  do{\fprintf(stderr, "__%d__:", __LINE__); \perror(msg);\
}while(0)int main(int argc, const char *argv[])
{//创建流式套接字 socketint sfd = socket(AF_UNIX, SOCK_STREAM, 0);if(sfd < 0){ERR_MSG("socket");return -1;}printf("socket create success sfd=%d\n", sfd);//判断套接字文件是否存在if(access("./unix", F_OK) == 0){//若存在则删除该套接字文件unlink("./unix");}//填充地址信息结构体给bind函数绑定,//真实的地址信息结构体根据地址族指定 AF_UNIX:man 7 unixstruct sockaddr_un sun;sun.sun_family      = AF_UNIX;      //必须填AF_UNIX;strcpy(sun.sun_path, "./unix");     //必须事先不存在,由bind函数生成//绑定服务器的地址信息---> 必须绑定 bindif(bind(sfd, (struct sockaddr*)&sun, sizeof(sun)) < 0){ERR_MSG("bind");return -1;}printf("bind success\n");//将套接字设置为被动监听状态 listenif(listen(sfd, 128) < 0){ERR_MSG("listen");return -1;}printf("listen success\n");int newfd = -1;struct sockaddr_un cun;             //存储客户端的地址信息socklen_t addrlen = sizeof(cun);    //真实的地址信息结构体的大小//获取一个已经完成的客户端信息,生成一个新的文件描述符 accept//newfd = accept(sfd, NULL, NULL);newfd = accept(sfd, (struct sockaddr*)&cun, &addrlen);if(newfd < 0){ERR_MSG("newfd");return -1;}printf("[]客户端连接成功 newfd=%d\n", newfd);char buf[128] = "";ssize_t res = 0;while(1){//清空字符串bzero(buf, sizeof(buf));    //memset//接收res = recv(newfd, buf, sizeof(buf), 0);if(res < 0){ERR_MSG("recv");return -1;}else if(0 == res){printf("[]客户端下线 newfd=%d\n", newfd);break;}printf("[] newfd=%d : %s\n", newfd, buf);//发送strcat(buf, "*_*");if(send(newfd, buf, sizeof(buf), 0) < 0){ERR_MSG("send");return -1;}printf("send success\n");}//关闭文件名描述符close(newfd);if(close(sfd) < 0){ERR_MSG("close");return -1;}return 0;
}

69.3.2 UNIX TCP客户端

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <sys/un.h>#define ERR_MSG(msg)  do{\                                              fprintf(stderr, "__%d__:", __LINE__); \perror(msg);\
}while(0)int main(int argc, const char *argv[])
{//创建流式套接字 socketint cfd = socket(AF_UNIX, SOCK_STREAM, 0);if(cfd < 0){ERR_MSG("socket");return -1;}printf("socket create success cfd=%d\n", cfd);//绑定客户端的地址信息---》非必须绑定//当不手动绑定的时候,操作系统会自动给客户端绑定本机IP和随机端口。//填充服务器的地址信息结构体给connect函数连接,//想连接哪个服务器,就填哪个服务器绑定的地址信息//真实的地址信息结构体根据地址族指定 AF_UNIX:man 7 unixstruct sockaddr_un sun;sun.sun_family      = AF_UNIX;      //必须填AF_UNIX;strcpy(sun.sun_path, "./unix");     //服务器绑定的套接字文件//连接指定服务器 connectif(connect(cfd, (struct sockaddr*)&sun, sizeof(sun)) < 0){ERR_MSG("connect");return -1;}printf("connect success\n");char buf[128] = "";ssize_t res = 0;while(1){//清空字符串bzero(buf, sizeof(buf));    //memsetprintf("请输入>>> ");fgets(buf, sizeof(buf), stdin);buf[strlen(buf)-1] = 0;//发送if(send(cfd, buf, sizeof(buf), 0) < 0){ERR_MSG("send");return -1;}printf("send success\n");bzero(buf, sizeof(buf));    //memset//接收res = recv(cfd, buf, sizeof(buf), 0);if(res < 0){ERR_MSG("recv");return -1;}else if(0 == res){printf("服务器下线 cfd=%d\n", cfd);break;}printf("cfd=%d : %s\n", cfd, buf);}//关闭文件名描述符if(close(cfd) < 0){ERR_MSG("close");return -1;}return 0;
}

69.4 报式域套接字

  1. 报式域套接字的编程与网络报式套接字编程思路是一致的,但是由于客户端不手动绑定的话,操作系统不会自动创建一个套接字文件给客户端绑定
  2. 此时若服务器想给客户端回复信息,会找不到客户端的地址信息。此时客户端是必须绑定的。

69.4.1 UNIX UDP服务器

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <sys/un.h>#define ERR_MSG(msg)  do{\fprintf(stderr, "__%d__:", __LINE__); \perror(msg);\
}while(0)int main(int argc, const char *argv[])
{//创建报式套接字 socketint sfd = socket(AF_UNIX, SOCK_DGRAM, 0);if(sfd < 0){ERR_MSG("socket");                                                             return -1;}printf("socket create success sfd=%d\n", sfd);//判断套接字文件是否存在 。 若存在则删除if(access("./myunix", F_OK) == 0){unlink("./myunix");}//填充服务器的地址信息结构体,给bind函数使用//真实的地址信息结构体根据地址族指定,AF_UNIX:man 7 unixstruct sockaddr_un sun;sun.sun_family      = AF_UNIX;      //必须填AF_UNIX;strcpy(sun.sun_path, "./myunix");   //必须事先不存在,由bind函数生成//绑定地址信息结构体--->bindif(bind(sfd, (struct sockaddr*)&sun, sizeof(sun)) < 0){ERR_MSG("bind");return -1;}printf("bind success\n");char buf[128] = "";struct sockaddr_un cun;     //存储发送方的地址信息;socklen_t addrlen = sizeof(cun);while(1){bzero(buf, sizeof(buf));//接收数据if(recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&cun, &addrlen) < 0){ERR_MSG("recv");return -1;}printf("[%s] : %s\n", cun.sun_path, buf);//发送数据----->谁发给我,我发还给谁strcat(buf, "*_*");if(sendto(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&cun, sizeof(cun)) < 0){ERR_MSG("sendto");return -1;}printf("sendto success\n");}//关闭套接字close(sfd);return 0;
}

69.4.2 UNIX UDP客户端

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <sys/un.h>#define ERR_MSG(msg)  do{\fprintf(stderr, "__%d__:", __LINE__); \perror(msg);\
}while(0)int main(int argc, const char *argv[])
{printf("请输入套接字文件名>>> ");char sockname[20] = "";scanf("%s", sockname);while(getchar()!=10);//创建报式套接字 socketint cfd = socket(AF_UNIX, SOCK_DGRAM, 0);if(cfd < 0){ERR_MSG("socket");return -1;}printf("socket create success cfd=%d\n", cfd);/*//绑定客户端的地址信息---》非必须绑定                                                        //若不绑定则操作系统不会自动给客户端绑定套接字文件//若udp服务器想要给客户端回复信息,则客户端必须绑定套接字文件*///填充客户端的地址信息结构体,给bind函数使用struct sockaddr_un cun;cun.sun_family      = AF_UNIX;strcpy(cun.sun_path, sockname);//绑定地址信息结构体--->bindif(bind(cfd, (struct sockaddr*)&cun, sizeof(cun)) < 0){ERR_MSG("bind");return -1;}printf("bind success\n");//填充服务器的地址信息结构体,给sendto函数使用//真实的地址信息结构体根据地址族指定,AF_UNIX:man 7 unixstruct sockaddr_un sun;sun.sun_family      = AF_UNIX;      //必须填AF_UNIX;strcpy(sun.sun_path, "./myunix");   //服务器绑定的套接字文件char buf[128] = "";struct sockaddr_un rcvaddr;     //存储发送方的地址信息;socklen_t addrlen = sizeof(rcvaddr);while(1){bzero(buf, sizeof(buf));//发送数据----->从终端获取数据发送给指定端printf("请输入>>> ");fgets(buf, sizeof(buf), stdin);buf[strlen(buf)-1] = 0;if(strcmp(buf, "quit") == 0){unlink(sockname);   //若客户端下线,则删除套接字文件break;}if(sendto(cfd, buf, sizeof(buf), 0, (struct sockaddr*)&sun, sizeof(sun)) < 0){ERR_MSG("sendto");return -1;}printf("sendto success\n");//接收数据bzero(buf, sizeof(buf));if(recvfrom(cfd, buf, sizeof(buf), 0, (struct sockaddr*)&rcvaddr, &addrlen) < 0){ERR_MSG("recv");return -1;}printf("[%s] : %s\n", rcvaddr.sun_path, buf);}//关闭套接字close(cfd);return 0;
}

七十、 抓包工具— —wireshark

70.1 wireshak安装

  1. 保存虚拟机中代码,因为wireshark安装可能导致计算机重启
  2. 安装wireshark,
  3. 可以修改安装路径到自定义路径
  4. 安装时候看到Desktop Icon选项,请勾选。其余都默认
  5. 安装完毕后,打开wireshark,若出现未找到接口字眼,说明安装失败。

70.2 wireshark使用

70.2.1 wireshark 抓包

  • 开始界面
  1. wireshark是捕获机器上的某一块网卡的网络包,当你的机器上有多块网卡的时候,你需要选择一个网卡。
  2. 双击需要的网卡,开始抓包
  • Wireshark 窗口介绍

在这里插入图片描述

70.2.2 wireshark与对应的OSI七层模型

在这里插入图片描述

  • 服务器和客户端的代码不能都运行在ubuntu,因为wireshark抓的是流经真实网卡的数据包。
  • 若将服务器客户端都运行在ubuntu,数据直接经过虚拟网卡通信,而不会经过真实网卡。
    在这里插入图片描述

70.3 包头分析

70.3.1 以太网头

以太网中封装了源mac地址以及目的mac地址,还有ip类型,以太网又称之为mac头
在这里插入图片描述

0X0800 只接收发往本机的mac的ipv4类型的数据帧
0X0806 只接收发往本机的ARP类型的数据帧
0x8035 只接受发往本机的RARP类型的数据帧
0X0003 接收发往本机的MAC所有类型:ip,arp,rarp数据帧,接收从本机发出去的数据帧,
混杂模式打开的情况下,会接收到非发往本地的MAC数据帧

70.3.2 IP头

在这里插入图片描述
IP头中需要掌握用于分帧的部分:id flags fregment_offset
两个IP地址:源IP与目的IP地址。
TTL:time to live, 指定数据帧可以最多经过几个路由器,每经过一个路由器TTL-1。当数据帧被目标方接收后,TTL清除为0.
​ 当TTL为0时,从网络中删除。

Linux TTL:64 Winodws:TTL 128

70.3.3 UDP头

在这里插入图片描述

70.3.4 TCP头

在这里插入图片描述

  1. 端口号
  2. SYN:握手包,连接时候出现
    PSH:数据传输包,在传输数据时候出现
    FIN:挥手包,在断开连接的时候出现
    ACK:应答包,用于应答 非应答包
  3. Seq:序列号,占4个字节,用于给数据段进行编号的。所有非应答包的数据段,都有seq。
    Ack:应答号,用于应答非应答包(握手包,挥手包,数据包)。告诉对方下一次从这个seq编号发送数据包。
  4. PSH Ack = Seq+len;
    SYN FIN Ack = Seq+1;
    在这里插入图片描述

70.3.5 三次握手(重点!!!)

  • 三次握手的发起方,肯定是客户端
    在这里插入图片描述
    在这里插入图片描述
  1. 第一次握手:客户端发送SYN包(SYN=1, seq=0)给服务器,并进入SYN_SENT状态,等待服务器返回确认包。
  2. 第二次握手:服务器接收到SYN包,确认客户端的SYN,发送ACK包(ACK=1 , ack=1),同时发送一个SYN包(SYN=1, seq=0),并进入SYN_RCVD状态。
  3. 第三次握手:客户端接收到服务器的SYN包,以及ACK包,进入establish状态,同时向服务器发送ACK包(ACK=1, ack=1)。此时三次握手包发送完毕,服务器也进入establish状态

70.3.6 四次挥手(重点!!!)

  • 四次挥手的发起方可能是服务器,也可能是客户端
    在这里插入图片描述
    在这里插入图片描述
  1. 第一次挥手,主动关闭方发送一个FIN包(FIN=1, seq = u)给被动方,进入FIN_WAIT_1状态;
  2. 第二次挥手:被动方接收到FIN包,给主动方发送一个ACK包(ACK=1, ack=u+1);并进入CLOKSE_WAIT状态。主动方接受到ACK包后,进入FIN_WAIT_2状态。如果有数据没有发送完毕,则继续发送,直到发送完毕为止;
  3. 第三次挥手:被动方发送一个FIN包(FIN=1, seq=w),进入LAST_ACK状态.
  4. 第四次挥手:主动关闭方收到FIN包,回复一个ACK包(ACK=1, ack=w+1)。被动关闭方收到主动关闭方的ACK后关闭连接。

笔试面试题型

  • 这几个问题问的都是同一个:三次握手四次挥手
1. 三次握手四次挥手流程。
2. 请简述TCP建立连接断开连接的过程。(三次握手,四次挥手)
3. 请简述TCP和UDP通信过程中的区别?(三次握手,四次挥手 ,有无应答)
4. 请简述如何用UDP模型实现TCP式传输?(三次握手,四次挥手 ,有无应答)

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

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

相关文章

【苍穹外卖 | 项目日记】第一天

前言&#xff1a; 我打算用16天的时间写完黑马程序员的苍穹外卖项目&#xff0c;为了督促自己每天坚持写以及记录项目知识点&#xff0c;所以用这种项目日记的方式鞭策自己 目录 前言&#xff1a; 今日完结任务&#xff1a; 今日收获&#xff1a; 1.阅读代码框架&#xf…

web中缓存的几种方式

看了构建高性能的web站点一书&#xff0c;对其中的集中web缓存进行一个总结 1 应用程序实现的动态页面缓存 应用程序把动态文件生成的html文件缓存到文件服务器&#xff0c;以后用户请求动态文件&#xff0c;直接从文件服务器加载对应的静态缓存的html文件返回给用户&#xff…

运行vite项目报错:await import(‘source-map-support‘).then((r) => r.default.install())

项目场景&#xff1a; Electron vue3 vite项目实现屏幕截图功能 问题描述 运行 npm run dev 启动项目报错 await import(source-map-support).then((r) > r.default.install()) PS D:\study\electron\electronDemo> npm run dev> electronDemo0.0.1 dev D:\study…

Kafka客户端核心参数详解

这一部分主要是从客户端使用的角度来理解 Kakfa 的重要机制。重点依然是要建立自己脑海中的 Kafka 消费模型。Kafka 的 HighLevel API 使用是非常简单的&#xff0c;所以梳理模型时也要尽量简单化&#xff0c;主线清晰&#xff0c;细节慢慢扩展。 一、从基础的客户端说起 Kaf…

75.颜色分类

原地排序&#xff1a;空间复杂度为1 class Solution { public:void sortColors(vector<int>& nums) {if(0){//法一&#xff1a;单指针两个遍历int nnums.size();int ptr0;for(int i0;i<n;i){if(nums[i]0){swap(nums[i],nums[ptr]);ptr;}}for(int iptr;i<n;i){…

XMLHttpRequest和Fetch API

XMLHttpRequest和Fetch API 1、XMLHttpRequest2、Fetch API总结 简述&#xff1a;XMLHttpRequest和Fetch API是两种常用的JavaScript网络请求方式&#xff0c;可以用来发送HTTP请求并获取服务器响应。 1、XMLHttpRequest XMLHttpRequest&#xff1a;XMLHttpRequest是一种传统的…

EVALUATING HALLUCINATIONS IN CHINESE LARGE LANGUAGE MODELS

本文是LLM系列文章&#xff0c;针对《EVALUATING HALLUCINATIONS IN CHINESE LARGE LANGUAGE MODELS》的翻译。 中文大语言模型的幻觉评价 摘要1 引言2 HALLUQA基准3 实验4 讨论5 相关工作6 结论 摘要 在本文中&#xff0c;我们建立了一个名为HalluQA (Chinese Hallucination…

RabbitMQ安装与简单使用

安装 下载资源 可以访问官网查看下载信息rabbitmq官网 选择合适的版本&#xff0c;注意&#xff1a;rabbitmq需要下载一个Erlang才能使用 我自己是在一下两个连接中下载的 rabbitmq 3.8.8 erlang 21.3.8.15 需要下载其他版本的同学注意erlang版本是否匹配&#xff0c;可以访…

【问题证明】矩阵方程化为特征值方程求得的特征值为什么是全部特征值?不会丢解吗?

问题 这个问题困扰了我好久&#xff0c;一直感觉如果有其他的特征值没法证伪&#xff0c;不过一直存在思想的层面&#xff0c;没有实际解决&#xff0c;今天突然想到动笔来解决&#xff0c;遂得解&#xff0c;证明如下。 证明 总结 这个证明看似证明过后很直观&#xff0c;但…

微信小程序之本地生活(九宫格)

文章目录 一.创建项目二.配置修改json三.编写WXML四.编写WXSS五.最终效果 一.创建项目 创建新的项目&#xff0c;名称为&#xff1a;本地生活 二.配置修改json 在app.json中删除其他页面 将index改为grid 自动生成新的文件 添加自己的轮播图片 源代码&#xff1a; <!--…

Android Studio 是如何和我们的手机共享剪贴板的

背景 近期完成了target33的项目适配升级,随着AGP和gradle的版本升级,万年老版本Android Studio(后文简称AS)也顺便升级到了最新版Android Studio Giraffe | 2022.3.1,除了新UI外,最让我好奇的是这次的Running Devices功能(官方也称为Device mirroring)可以控制真机了. 按照操…

异步爬虫实战:实际应用asyncio和aiohttp库构建异步爬虫

在网络爬虫的开发中&#xff0c;异步爬虫已经成为一种非常流行的技术。它能够充分利用计算机的资源&#xff0c;提高爬虫效率&#xff0c;并且能够处理大量的运算请求。Python中的asyncio和aiohttp库提供了强大的异步爬虫支持&#xff0c;使得开发者能够轻松构建高效的异步爬虫…

LVGL8.3.6 Flex(弹性布局)

使用lv_obj_set_flex_flow(obj, flex_flow)函数 横向拖动 LV_FLEX_FLOW_ROW 将子元素排成一排而不包裹 LV_FLEX_FLOW_ROW_WRAP 将孩子排成一排并包裹起来 LV_FLEX_FLOW_ROW_REVERSE 将子元素排成一行而不换行&#xff0c;但顺序相反 LV_FLEX_FLOW_ROW_WRAP_REVERSE 将子元素…

在pycharm中出现下载软件包失败的解决方法

一. 一般情况下我们会选择在设置中下载软件包,过程如下. 1. 直接点击左上角的文件, 再点击设置, 再点击项目, 在右边选择python解释器,点击号,输入要下载的软件包, 在下面的一系列的包中选择相对应的包,点击安装就可以了,有的时候我们下载的是最新的版本,如果要下载固定的版本…

算法框架-LLM-1-Prompt设计(一)

原文&#xff1a;算法框架-LLM-1-Prompt设计&#xff08;一&#xff09; - 知乎 目录 收起 1 prompt-engineering-for-developers 1.1 Prompt Engineering 1.1.1 提示原则 1. openai的环境 2. 两个基本原则 3. 示例 eg.1 eg.2 结构化输出 eg.3 模型检验 eg.4 提供示…

【计算机基础】Git系列3:常用操作

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

JavaScript 作用域

作用域是可访问变量的集合。 JavaScript 作用域 在 JavaScript 中, 对象和函数同样也是变量。 在 JavaScript 中, 作用域为可访问变量&#xff0c;对象&#xff0c;函数的集合。 JavaScript 函数作用域: 作用域在函数内修改。 JavaScript 局部作用域 变量在函数内声明&…

如何使用CDN加载静态资源

文章目录 前言工具场景七牛云创建空间存储 控制台查看后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;前端 &#x1f431;‍&#x1f453;博主在前端领域还有很多知识和技术需要掌握&#xff0c;正在不断努力填补技术短板。(如果出现错误…

python socket 传输opencv读取的图像

python socket网络编程 将ros机器人摄像头捕捉的画面在上位机实时显示&#xff0c;需要用到socket网络编程&#xff0c;提供了TCP和UDP两种方式 TCP服务器端代码: 创建TCP套接字: s socket(AF_INET, SOCK_STREAM) 创建了一个TCP套接字。SOCK_STREAM 表示这是一个TCP套接字&…

QFluentWidgets: 基于 C++ Qt 的 Fluent Design 组件库

简介 QFluentWidgets 是一个基于 Qt 的 Fluent Designer 组件库&#xff0c;内置超过 150 个开箱即用的 Fluent Designer 组件&#xff0c;支持亮暗主题无缝切换和自定义主题色。搭配所见即所得的 Fluent Designer 软件&#xff0c;只需拖拖拽拽&#xff0c;不用编写一行 QSS&…