socket套接字-UDP(中)

socket套接字-UDP(上)https://blog.csdn.net/Small_entreprene/article/details/147465441?fromshare=blogdetail&sharetype=blogdetail&sharerId=147465441&sharerefer=PC&sharesource=Small_entreprene&sharefrom=from_link

UDP服务器的搭建

在之前的博客中,我们已经完成了一个功能完整的UDP服务器基础架构。通过UdpServer类的实现,我们能够轻松创建一个UDP服务器。该服务器会监听指定端口,接收客户端发送的消息,并通过回调函数对消息进行处理,最后将处理结果返回给客户端。

代码解析

UdpServer.hpp文件中,我们定义了UDP服务器的核心逻辑。我们通过socket系统调用创建套接字,并使用bind将套接字与指定端口绑定。在Start方法中,服务器进入消息循环,不断接收客户端的消息,并调用回调函数处理消息。

void Start()
{_isrunning = true;while (_isrunning){char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);if (s > 0){InetAddr client(peer);buffer[s] = 0;std::string result = _func(buffer, client);sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr*)&peer, len);}}
}

这一部分的代码实现了服务器的基础功能,但此时的服务器功能比较单一,只能对消息进行简单的回显处理。

回调机制的引入

在最初的版本中,服务器的功能是固定的,只能对消息进行简单的回显处理。这存在一个很大的局限性——服务器的功能是固定的,如果想增加新的功能,就必须修改服务器的内部代码。

我思考了一下,如果我想要在将来给服务器增加新的功能,比如翻译功能、计算功能或者其他什么功能,那是不是每次都要修改服务器的内部代码呢?这显然不符合我们追求的模块化、可扩展的设计理念。

于是,我灵机一动,想出了一个好办法——引入回调机制。这个想法其实来源于我们平时使用的很多软件库,它们通过回调函数允许用户自定义行为。

在我们的UDP服务器中,我定义了一个回调函数类型using func_t = std::function<std::string(const std::string &)>,这个函数类型表示我们的回调函数将接收一个字符串作为输入,并返回一个字符串作为输出。然后,我在UdpServer类的构造函数中增加了一个func_t类型的参数,这样在创建服务器的时候,就可以传入我们想要的处理逻辑了。

在服务器的消息循环中,每当我接收到客户端发送的消息时,我就可以直接调用这个回调函数,将消息交给它处理,然后把处理结果发送回客户端。

std::string result = _func(buffer); // 调用回调函数进行处理
sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr *)&peer, len);

这样一来,我们的UDP就服务器变得非常灵活了。只要实现一个符合func_t类型的回调函数,就可以给服务器增加新的功能,而不用再去修改服务器的核心代码了。

翻译功能的实现

有了回调机制之后,我就可以开始实现翻译功能了。这个功能的想法其实来源于我平时学习英语的时候,经常会遇到不认识的单词,需要查字典。我就想,要是能有个服务器,可以让我把不认识的单词发给它,它就能直接返回单词的中文意思,那该多好啊!

于是,我开始构思这个翻译功能的实现。首先,我需要一个字典来存储单词和对应的中文翻译。我决定用一个简单的文本文件来作为字典文件,文件的每一行就是一个单词和它的翻译,中间用特定的分隔符隔开,比如apple: 苹果

然后,我创建了一个Dict类来管理这个字典。这个类有一个方法LoadDict,用来从文件中加载字典数据。在加载的时候,我会逐行读取文件内容,然后按照分隔符把单词和翻译分开,存到一个unordered_map中,方便后续查询。

bool LoadDict()
{std::ifstream in(_dict_path);if (!in.is_open()){LOG(LogLevel::DEBUG) << "打开字典: " << _dict_path << " 错误";return false;}std::string line;while (std::getline(in, line)){auto pos = line.find(sep);if (pos == std::string::npos){LOG(LogLevel::WARNING) << "解析: " << line << " 失败";continue;}std::string english = line.substr(0, pos);std::string chinese = line.substr(pos + sep.size());if (english.empty() || chinese.empty()){LOG(LogLevel::WARNING) << "没有有效内容: " << line;continue;}_dict.insert(std::make_pair(english, chinese));LOG(LogLevel::DEBUG) << "加载: " << line;}in.close();return true;
}

接着,我实现了一个Translate方法,它接收一个单词作为输入,然后在字典中查找对应的翻译。如果找到了,就返回翻译结果;如果没有找到,就返回“None”。

std::string Translate(const std::string &word, InetAddr &client)
{auto iter = _dict.find(word);if (iter == _dict.end()){LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.Ip() << " : " << client.Port() << "]# " << word << "->None";return "None";}LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.Ip() << " : " << client.Port() << "]# " << word << "->" << iter->second;return iter->second;
}

最后,在main函数中,我创建了Dict对象,并调用LoadDict方法加载字典。然后,我创建了UDP服务器对象,并将DictTranslate方法作为回调函数传递给服务器。

int main(int argc, char *argv[])
{if(argc != 2){std::cerr << "Usage: " << argv[0] << " port" << std::endl;return 1;}uint16_t port = std::stoi(argv[1]);Enable_Console_Log_Strategy();Dict dict;dict.LoadDict();std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&dict](const std::string &word, InetAddr&cli)->std::string{return dict.Translate(word, cli);});usvr->Init();usvr->Start();return 0;
}

这样,当客户端发送一个单词给服务器时,服务器就会调用Translate方法,查找单词的翻译,并将结果返回给客户端。

网络地址的封装

在实现翻译功能的过程中,我遇到了一个小问题。我想在服务器的日志中记录每个客户端的IP地址和端口号,这样我就可以知道是谁发来的单词。但是,我发现每次处理客户端消息的时候,都要从sockaddr_in结构体中提取IP和端口号,然后再转换为字符串格式,这样显得有点麻烦。

问题的提出

在早期的代码中,每次收到客户端的消息后,我们需要手动从sockaddr_in结构体中提取IP地址和端口号,并将其转换为便于打印和记录的字符串形式。例如:

int peer_port = ntohs(peer.sin_port);
std::string peer_ip = inet_ntoa(peer.sin_addr);

这种做法存在以下问题:

  • 代码重复 :每次处理客户端消息时,都需要重复这段提取和转换代码,导致代码冗余,增加了维护成本。

  • 可读性差 :直接操作sockaddr_in结构体的成员变量,使得代码的可读性降低,对于不熟悉网络编程的开发者来说,理解起来有一定难度。

  • 扩展性差 :如果后续需要增加与网络地址相关的其他功能,例如地址验证、地址转换等,这种分散的处理方式会使代码难以扩展和维护。

封装InetAddr

为了解决上述问题,我决定封装一个InetAddr类来管理网络地址信息。这个类的构造函数接收一个sockaddr_in结构体,然后在内部将IP地址和端口号提取出来,并转换为方便使用的格式。

InetAddr(struct sockaddr_in &addr) : _addr(addr)
{_port = ntohs(_addr.sin_port);_ip = inet_ntoa(_addr.sin_addr);
}

然后,我为这个类提供了PortIp两个方法,用来获取端口号和IP地址。

uint16_t Port() {return _port;}
std::string Ip() {return _ip;}

封装后的优势

通过封装InetAddr类,我们获得了以下优势:

  • 代码简化 :在处理客户端消息时,只需创建一个InetAddr对象,即可方便地获取客户端的IP地址和端口号,无需重复编写提取和转换代码。例如:

封装前:

int peer_port = ntohs(peer.sin_port);
std::string peer_ip = inet_ntoa(peer.sin_addr);

封装后:

InetAddr client(peer);
std::string ip = client.Ip();
uint16_t port = client.Port();
  • 提高可读性 :封装后的代码更加直观和清晰,开发者可以更容易地理解代码的意图,减少了理解成本。

  • 增强扩展性 :如果后续需要增加与网络地址相关的功能,只需在InetAddr类中进行扩展,而无需修改其他业务逻辑代码,大大提高了代码的可维护性和可扩展性。

回调机制的优化

在最初的设计中,我的回调函数只接收一个参数,那就是客户端发送的消息。但是,在实现翻译功能的时候,我发现我还想在回调函数中使用客户端的IP地址和端口号,比如在日志中记录这些信息。

变化动机

  • 增加信息利用率 :在最初的回调机制中,回调函数只能获取到客户端发送的消息内容,但无法获取到发送该消息的客户端的网络地址信息。这意味着在处理消息时,我们无法根据客户端的地址进行个性化的处理或记录,限制了功能的灵活性和丰富度。

  • 满足功能需求 :以翻译功能为例,我们希望能够记录是哪个客户端发送了哪个单词进行查询,这需要在回调函数中同时获取消息内容和客户端地址信息。此外,像访问统计、基于客户端地址的权限控制等功能的实现,也都需要在回调函数中获取客户端的地址信息。

优化过程

于是,我决定对回调机制进行优化,让回调函数可以接收更多的参数。我修改了回调函数的类型定义,让它可以接收一个InetAddr对象作为第二个参数。

using func_t = std::function<std::string(const std::string&, InetAddr&)>;

然后,在服务器的Start方法中,当调用回调函数的时候,我将InetAddr对象作为参数传递进去。

InetAddr client(peer);
buffer[s] = 0;
std::string result = _func(buffer, client);

这样,在回调函数中,我就可以同时获取到客户端发送的消息以及客户端的网络地址信息了。

代码注释与详细解释

UdpServer.hpp 文件

#pragma once#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include "InetAddr.hpp"using namespace LogModule;// 定义回调函数类型,用于处理接收到的消息
// 回调函数接收两个参数:消息内容和客户端地址,返回处理结果
using func_t = std::function<std::string(const std::string&, InetAddr&)>;// 定义默认的无效套接字文件描述符值
const int defaultfd = -1;// UDP 服务器类
class UdpServer
{
public:// 构造函数,初始化服务器端口和消息处理回调函数UdpServer(uint16_t port, func_t func): _sockfd(defaultfd), // 初始化套接字文件描述符为默认值_port(port),        // 设置服务器端口_isrunning(false),  // 初始化运行状态为停止_func(func)         // 设置消息处理回调函数{}// 初始化服务器,创建套接字并绑定端口void Init(){// 1. 创建套接字// 使用 socket 函数创建一个 UDP 套接字// AF_INET 表示使用 IPv4 地址族// SOCK_DGRAM 表示使用 UDP 协议_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){// 如果创建套接字失败,记录致命错误日志并退出程序LOG(LogLevel::FATAL) << "socket error!";exit(1);}// 记录创建套接字成功的日志LOG(LogLevel::INFO) << "socket success, sockfd : " << _sockfd;// 2. 绑定套接字信息(IP 和端口)// 2.1 填充 sockaddr_in 结构体,用于指定绑定的地址信息struct sockaddr_in local;bzero(&local, sizeof(local)); // 清零结构体,避免未定义行为local.sin_family = AF_INET;   // 设置地址族为 IPv4// 将本地端口号转换为网络字节序(大端字节序)local.sin_port = htons(_port);// 设置本地 IP 地址为 INADDR_ANY,表示监听所有网络接口上的连接// 这样服务器可以接收来自任何 IP 地址的客户端请求local.sin_addr.s_addr = INADDR_ANY;// 调用 bind 函数将套接字绑定到指定的地址和端口int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){// 如果绑定失败,记录致命错误日志并退出程序LOG(LogLevel::FATAL) << "bind error";exit(2);}// 记录绑定成功的日志LOG(LogLevel::INFO) << "bind success, sockfd : " << _sockfd;}// 启动服务器,进入消息处理循环void Start(){_isrunning = true; // 设置服务器运行状态为正在运行while (_isrunning){char buffer[1024]; // 用于存储接收到的消息缓冲区struct sockaddr_in peer; // 用于存储发送端的地址信息socklen_t len = sizeof(peer); // 发送端地址结构体的长度// 1. 接收消息// 使用 recvfrom 函数接收 UDP 消息// 参数包括套接字文件描述符、缓冲区、缓冲区大小、消息标志、发送端地址结构体指针和地址结构体长度指针ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);if (s > 0){// 创建 InetAddr 对象,封装发送端的地址信息InetAddr client(peer);// 在缓冲区末尾添加字符串终止符,确保数据以 C 风格字符串形式存储buffer[s] = 0;// 调用回调函数处理消息,并获取处理结果// 回调函数接收消息内容和客户端地址作为参数std::string result = _func(buffer, client);// 2. 发送响应消息// 使用 sendto 函数将处理结果发送回客户端// 参数包括套接字文件描述符、消息内容、消息长度、消息标志、发送端地址结构体指针和地址结构体长度sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr *)&peer, len);}}}// 析构函数~UdpServer(){}private:int _sockfd; // 套接字文件描述符uint16_t _port; // 服务器端口号bool _isrunning; // 服务器运行状态标志func_t _func; // 消息处理回调函数
};
  • 回调函数的定义与使用 :通过定义func_t作为回调函数类型,并在UdpServer类中使用,实现了将消息处理逻辑与服务器通信逻辑的分离。这样,用户可以通过传入不同的回调函数,轻松地为服务器增加不同的功能。

  • 网络地址的封装 :通过InetAddr类对网络地址信息进行封装,使得在处理客户端消息时,能够更加方便地获取和使用客户端的IP地址和端口号。

Dict.hpp 文件

#pragma once#include <iostream>
#include <fstream>
#include <string>
#include <unordered_map>
#include "Log.hpp"
#include "InetAddr.hpp"// 定义字典文件的默认路径为当前目录下的 dictionary.txt 文件
const std::string defaultdict = "./dictionary.txt";
// 定义字典文件中单词和翻译之间的分隔符为 ": "
const std::string sep = ": ";// 引入 LogModule 命名空间,便于使用日志功能
using namespace LogModule;class Dict
{
public:// 构造函数,初始化字典文件路径,默认为 defaultdictDict(const std::string &path = defaultdict) : _dict_path(path){}// 加载字典文件的方法bool LoadDict(){// 打开字典文件std::ifstream in(_dict_path);// 如果文件打开失败,输出错误日志并返回 falseif (!in.is_open()){LOG(LogLevel::DEBUG) << "打开字典: " << _dict_path << " 错误";return false;}// 定义一个字符串变量,用于逐行读取文件内容std::string line;// 循环逐行读取文件while (std::getline(in, line)){// 查找分隔符在当前行中的位置auto pos = line.find(sep);// 如果未找到分隔符,说明该行格式不符合要求,输出警告日志并跳过该行if (pos == std::string::npos){LOG(LogLevel::WARNING) << "解析: " << line << " 失败";continue;}// 提取分隔符前的部分作为单词std::string english = line.substr(0, pos);// 提取分隔符后的部分作为翻译std::string chinese = line.substr(pos + sep.size());// 如果单词或翻译为空,说明内容无效,输出警告日志并跳过该行if (english.empty() || chinese.empty()){LOG(LogLevel::WARNING) << "没有有效内容: " << line;continue;}// 将单词和翻译存入字典中_dict.insert(std::make_pair(english, chinese));// 输出调试日志,记录加载的单词和翻译LOG(LogLevel::DEBUG) << "加载: " << line;}// 关闭文件in.close();// 返回 true,表示字典加载成功return true;}// 翻译方法,根据输入的单词和客户端地址返回翻译结果std::string Translate(const std::string &word, InetAddr &client){// 在字典中查找输入的单词auto iter = _dict.find(word);// 如果未找到该单词,输出调试日志并返回 "None"if (iter == _dict.end()){LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.Ip() << " : " << client.Port() << "]# " << word << "->None";return "None";}// 如果找到该单词,输出调试日志并返回对应的翻译LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.Ip() << " : " << client.Port() << "]# " << word << "->" << iter->second;return iter->second;}// 析构函数~Dict(){}private:// 字典文件的路径std::string _dict_path;// 使用 unordered_map 存储单词和翻译的键值对,键为单词,值为翻译std::unordered_map<std::string, std::string> _dict;
};

dictionary.txt

apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
hello: 
: 你好run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天
  • 字典文件的加载 :在LoadDict方法中,通过读取字典文件并解析每一行的内容,将单词及其对应的翻译存储到unordered_map中,实现了字典数据的快速加载和高效查询。

  • 翻译功能的实现Translate方法通过在字典中查找指定的单词,返回对应的翻译结果。如果找不到,则返回“None”。

InetAddr.hpp 文件

#pragma once#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>// 网络地址和主机地址之间进行转换的类
class InetAddr
{
public:// 构造函数,接收一个 sockaddr_in 结构体作为参数// 该结构体通常由 socket API 返回,包含网络地址信息InetAddr(struct sockaddr_in &addr) : _addr(addr){// 使用 ntohs 将网络字节序的端口号转换为主机字节序// 网络字节序通常是大端字节序(big-endian),而主机字节序可能是小端(little-endian)_port = ntohs(_addr.sin_port);// 使用 inet_ntoa 将 4 字节的网络字节序 IP 地址转换为点分十进制的字符串形式_ip = inet_ntoa(_addr.sin_addr);}// 获取端口号的方法uint16_t Port() { return _port; }// 获取 IP 地址字符串的方法std::string Ip() { return _ip; }// 析构函数,目前无特殊清理操作~InetAddr() {}private:// 存储原始的 sockaddr_in 结构体,包含完整的网络地址信息struct sockaddr_in _addr;// 存储转换后的 IP 地址字符串,格式为 "xxx.xxx.xxx.xxx"std::string _ip;// 存储转换后的端口号,为主机字节序uint16_t _port;
};

网络地址信息的封装 :构造函数接收一个sockaddr_in结构体,并从中提取出IP地址和端口号,将其转换为便于使用的格式。通过PortIp方法,可以方便地获取客户端的端口号和IP地址。

测试代码:UdpServer.cc

#include <iostream>
#include <memory>
#include "Dict.hpp"      // 翻译的功能
#include "UdpServer.hpp" // 网络通信的功能// 测试用的默认消息处理函数
// 用于演示服务器的基本功能,将接收到的消息前面加上 "hello, " 后返回
std::string defaulthandler(const std::string &message)
{std::string hello = "hello, ";hello += message;return hello;
}// 程序入口函数
int main(int argc, char *argv[])
{// 检查命令行参数是否正确,需要提供端口号作为参数if (argc != 2){std::cerr << "Usage: " << argv[0] << " port" << std::endl;return 1;}// 获取命令行参数中的端口号uint16_t port = std::stoi(argv[1]);// 启用控制台日志策略,以便在控制台输出日志信息Enable_Console_Log_Strategy();// 创建字典对象,用于提供翻译功能Dict dict;// 加载字典文件,准备翻译所需的数据dict.LoadDict();// 创建 UDP 服务器对象,并指定端口号和消息处理回调函数// 这里使用 lambda 表达式捕获字典对象,以便在回调函数中调用其翻译方法std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&dict](const std::string &word, InetAddr &cli) -> std::string {return dict.Translate(word, cli);});// 初始化服务器,包括创建套接字和绑定端口等操作usvr->Init();// 启动服务器,进入消息接收和处理循环usvr->Start();return 0;
}

测试代码:UdpClient.cc

#include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>// UDP 客户端程序入口
int main(int argc, char *argv[])
{// 检查命令行参数是否正确,需要提供服务器 IP 和端口号if (argc != 3){std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;return 1;}// 获取服务器 IP 和端口号std::string server_ip = argv[1];uint16_t server_port = std::stoi(argv[2]);// 1. 创建 UDP 套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){std::cerr << "socket error" << std::endl;return 2;}// 2. 填写服务器地址信息struct sockaddr_in server;memset(&server, 0, sizeof(server)); // 初始化内存为零server.sin_family = AF_INET;        // 设置地址族为 IPv4server.sin_port = htons(server_port); // 将端口号转换为网络字节序server.sin_addr.s_addr = inet_addr(server_ip.c_str()); // 设置服务器 IP 地址// 3. 与服务器进行通信的循环while (true){std::string input;std::cout << "Please Enter# ";std::getline(std::cin, input); // 从标准输入获取用户输入// 发送消息到服务器int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));(void)n; // 忽略发送返回值,实际应用中应检查发送是否成功// 接收服务器返回的消息char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);if (m > 0){buffer[m] = 0; // 确保接收缓冲区以 null 字符结尾std::cout << buffer << std::endl; // 输出服务器返回的消息}}return 0;
}

测试结果

总结与展望

通过这一系列的改进,我们的UDP服务器已经从一个简单的消息收发工具,进化成了一个具有实用翻译功能的应用程序。这个过程让我深刻体会到了模块化设计和回调机制的强大之处。它们不仅让我们的代码更加清晰和易于维护,还极大地提高了代码的可扩展性和复用性。

在未来的开发中,我计划继续优化这个服务器。比如,增加对更多语言的支持,或者让服务器能够同时处理多个客户端的请求。另外,我还想尝试将这个服务器部署到云平台上,让更多的用户能够使用这个翻译服务。

如果你对这个项目感兴趣,或者有任何建议和想法,欢迎随时与我交流。让我们一起在编程的世界里不断探索,创造更多有趣的作品!

下期预告:群聊实现及补充收尾!!!😝

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

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

相关文章

C++入门小馆: STL 之queue和stack

嘿&#xff0c;各位技术潮人&#xff01;好久不见甚是想念。生活就像一场奇妙冒险&#xff0c;而编程就是那把超酷的万能钥匙。此刻&#xff0c;阳光洒在键盘上&#xff0c;灵感在指尖跳跃&#xff0c;让我们抛开一切束缚&#xff0c;给平淡日子加点料&#xff0c;注入满满的pa…

ALTER TABLE 删除DROP表列的报错: 因为有一个或多个对象访问此列

目录 1.问题 2.解决办法 1.问题 删除某个列名的时候&#xff0c;提示错误因为有一个或多个对象访问此列 2.解决办法 2.1 添加或删除表新列名 将表中的字段设置Default 或 NOT NULL 都会给该字段添加约束&#xff0c;增加了这些约束后&#xff0c;再SQL脚本修改类型、删除会发生…

python源码打包为可执行的exe文件

文章目录 简单的方式&#xff08;PyInstaller&#xff09;特点步骤安装 PyInstaller打包脚本得到.exe文件 简单的方式&#xff08;PyInstaller&#xff09; 特点 支持 Python 3.6打包为单文件&#xff08;–onefile&#xff09;或文件夹形式自动处理依赖项 步骤 安装 PyIns…

【2025最近Java面试八股】Spring中循环依赖的问题?怎么解决的?

1. 什么是循环依赖&#xff1f; 在Spring框架中&#xff0c;循环依赖是指两个或多个bean之间相互依赖&#xff0c;形成了一个循环引用的情况。如果不加以处理&#xff0c;这种情况会导致应用程序启动失败。导致 Spring 容器无法完成依赖注入。 例如&#xff1a; Service publi…

JimuBI 积木报表 v1.9.5发布,大屏和仪表盘,免费数据可视化

项目介绍 JimuBI (积木报表BI) 是一款免费的数据可视化产品&#xff0c;含大屏和仪表盘、门户、移动图表&#xff0c;像搭建积木一样完全在线设计&#xff01; 大屏采用类word风格&#xff0c;可以随意拖动组件&#xff0c;想怎么设计怎么设计&#xff0c;可以像百度和阿里一样…

云原生课程-Docker

一次镜像&#xff0c;到处运行。 1. Docker详解&#xff1a; 1.1 Docker简介&#xff1a; Docker是一个开源的容器化平台&#xff0c;可以帮助开发者将应用程序和其依赖的环境打包成一个可移植的&#xff0c;可部署的容器。 docker daemon:是一个运行在宿主机&#xff08;DO…

HikariCP 6.3.0 完整配置与 Keepalive 优化指南

HikariCP 6.3.0 完整配置与 Keepalive 优化指南 HikariCP 是一个高性能、轻量级的 JDBC 连接池框架&#xff0c;广泛应用于 Java 应用&#xff0c;尤其是 Spring Boot 项目。本文档基于 HikariCP 6.3.0 版本&#xff0c;详细介绍其功能、配置参数、Keepalive 机制以及优化建议…

基于springboot+vue的摄影师分享交流社区的设计与实现

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

ComfyUI for Windwos与 Stable Diffusion WebUI 模型共享修复

#工作记录 虽然在安装ComfyUI for Windwos时已经配置过extra_model_paths.yaml 文件&#xff0c;但升级ComfyUI for Windwos到最新版本后发现原先的模型配置失效了&#xff0c;排查后发现&#xff0c;原来是 extra_model_paths.yaml 文件在新版本中被移动到了C盘目录下&#x…

【最新版】沃德代驾源码全开源+前端uniapp

一.系统介绍 基于ThinkPHPUniapp开发的代驾软件。系统源码全开源&#xff0c;代驾软件的主要功能包括预约代驾、在线抢单、一键定位、在线支付、车主登记和代驾司机实名登记等‌。用户可以通过小程序预约代驾服务&#xff0c;系统会估算代驾价格并推送附近代驾司机供用户选择&…

react的 Fiber 节点的链表存储

在React Fiber架构中&#xff0c;Fiber节点的链表存储是一种重要的数据结构组织方式&#xff0c;用于管理和遍历Fiber节点。以下是关于Fiber节点链表存储的详细介绍&#xff1a; 链表结构 单链表&#xff1a;React Fiber节点通过next指针形成单链表结构。每个Fiber节点都有一…

Kafka + Kafka-UI

文章目录 前言&#x1f433; 一、使用纯 Kafka Kafka-UI &#xff08;无 Zookeeper&#xff09;Docker 配置&#x1f680; 启动步骤✅ 服务启动后地址&#x1f525; 注意事项&#xff08;使用 Kraft&#xff09;✅ NestJS Kafka 连接不变&#x1f9e0; 额外补充&#x1f4e6; …

AI声像融合守护幼儿安全——打骂/异常声音报警系统的智慧防护

幼儿园是孩子们快乐成长的摇篮&#xff0c;但打骂、哭闹或尖叫等异常事件可能打破这份宁静&#xff0c;威胁幼儿的身心安全。打骂/异常声音报警系统&#xff0c;依托尖端的AI声像融合技术&#xff0c;结合语音识别、情绪分析与视频行为检测&#xff0c;为幼儿园筑起一道智能安全…

Qt网络数据解析方法总结

在Qt中解析网络数据通常涉及接收原始字节流&#xff0c;并将其转换为有意义的应用层数据。以下是详细步骤和示例&#xff1a; 1. 网络数据接收 使用QTcpSocket或QUdpSocket接收数据&#xff0c;通过readyRead()信号触发读取&#xff1a; // 创建TCP Socket并连接信号 QTcpSo…

unity编辑器的json验证及格式化

UNITY编辑器的json格式化和验证工具资源-CSDN文库https://download.csdn.net/download/qq_38655924/90676188?spm1001.2014.3001.5501 反复去别的网站验证json太麻烦了 用这个工具能方便点 # Unity JSON工具 这是一个Unity编辑器扩展&#xff0c;用于验证、格式化和压缩JSO…

学习笔记:Qlib 量化投资平台框架 — FIRST STEPS

学习笔记&#xff1a;Qlib 量化投资平台框架 — FIRST STEPS Qlib 是微软亚洲研究院开源的一个面向人工智能的量化投资平台&#xff0c;旨在实现人工智能技术在量化投资中的潜力&#xff0c;赋能研究&#xff0c;并创造价值&#xff0c;从探索想法到实施生产。Qlib 支持多种机器…

操作系统:计算机世界的基石与演进

一、操作系统的本质与核心功能 操作系统如同计算机系统的"总管家"&#xff0c;在硬件与应用之间架起关键桥梁。从不同视角观察&#xff0c;其核心功能呈现多维价值&#xff1a; 硬件视角的双重使命&#xff1a; 硬件管理者&#xff1a;通过内存管理、进程调度和设…

基于单片机的温湿度采集系统(论文+源码)

2.1系统的功能 本系统的研制主要包括以下几项功能&#xff1a; (1)温度检测功能&#xff1a;对所处环境的温度进行检测&#xff1b; (2)湿度检测功能&#xff1a;对所处环境的湿度进行检测&#xff1b; (3)加热和制冷功能&#xff1a;可以完成加热和制冷功能。 (4)加湿和除…

webrtc使用

demo https://www.webrtc-experiment.com/ github开源demo https://github.com/muaz-khan/WebRTC-Experiment.git ws传递webrtc信令,本机不需要stun服务器,远端电脑需要ice服务器建立peer连接 const WebSocket = require(ws); const express =

【数据可视化-25】时尚零售销售数据集的机器学习可视化分析

🧑 博主简介:曾任某智慧城市类企业算法总监,目前在美国市场的物流公司从事高级算法工程师一职,深耕人工智能领域,精通python数据挖掘、可视化、机器学习等,发表过AI相关的专利并多次在AI类比赛中获奖。CSDN人工智能领域的优质创作者,提供AI相关的技术咨询、项目开发和个…