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.pswp.cn/news/903134.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/903134.shtml
英文地址,請注明出處:http://en.pswp.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相關的技術咨詢、項目開發和個…