【Linux網絡】構建UDP服務器與字典翻譯系統

📢博客主頁:https://blog.csdn.net/2301_779549673
📢博客倉庫:https://gitee.com/JohnKingW/linux_test/tree/master/lesson
📢歡迎點贊 👍 收藏 ?留言 📝 如有錯誤敬請指正!
📢本文由 JohnKi 原創,首發于 CSDN🙉
📢未來很長,值得我們全力奔赴更美好的生活?

在這里插入圖片描述

在這里插入圖片描述

文章目錄

  • 🏳??🌈一、服務端更新
    • 1.1 函數對象聲明
    • 1.2 UdpServer 類成員更新
    • 1.3 構造函數更新
    • 1.4 開始 - Start() 更新
  • 🏳??🌈二、Dictionary 字典類設計
    • 2.1 基本結構
    • 2.2 加載字典文件 - LoadDictionary(const std::string& path)
    • 2.3 構造函數
    • 2.4 翻譯函數
    • 2.5 服務端運行更新
  • 🏳??🌈三、整體代碼
  • 👥總結


上一篇文章中,我們實現了回顯客戶端輸入的功能,這功能往往是不夠的,為了更好地模擬現實需求,我們現在多增加一個功能 - 字典翻譯功能

🏳??🌈一、服務端更新

1.1 函數對象聲明

別的功能、成員名保持不變,為了新增字典翻譯功能,我們需要引入函數對象類型

// 回調函數對象聲明
using func_t = std::function<std::string(std::string)>;

1.2 UdpServer 類成員更新

class UdpServer : public nocopy{public:UdpServer(func_t func,uint16_t localport = glocalport);void InitServer();void Start();~UdpServer();private:int _sockfd;            // 文件描述符uint16_t _localport;    // 端口號std::string _localip;   // 本地IP地址bool _isrunning;        // 運行狀態func_t _func;           // 回調函數
};

1.3 構造函數更新

  • 構造函數只需增加一個函數對象參數,初始化列表初始化變量即可!!!
UdpServer(uint16_t localport = gdefaultport, func_t func = nullptr): _sockfd(gsockfd), _localport(localport), _isrunning(false), _func(func) {}

1.4 開始 - Start() 更新

  • 之前只需要回顯的時候,我們直接接收客戶端信息,將網絡字節序的客戶端ip和端口號轉換為主機字節序,再返回就行了
  • 現在我們要在這之間添加一個環節,使收到的客戶端信息,先通過字典翻譯回調函數,將處理后的值傳回去
void Start() {_isrunning = true;while (true) {char inbuffer[1024];              // 接收緩沖區struct sockaddr_in peer;          // 接收客戶端地址socklen_t peerlen = sizeof(peer); // 計算接收的客戶端地址長度// 接收數據報// recvfrom(int sockfd, void* buf, size_t len, int flags, struct// sockaddr* src_addr, socklen_t* addrlen)// 從套接字接收數據,并存入buf指向的緩沖區中,返回實際接收的字節數// 參數sockfd:套接字文件描述符// 參數buf:指向接收緩沖區的指針,c_str()函數可以將字符串轉換為char*,以便存入緩沖區// 參數len:接收緩沖區的長度// 參數flags:接收標志,一般設為0// 參數src_addr:指向客戶端地址的指針,若不為NULL,函數返回時,該指針指向客戶端的地址,是網絡字節序// 參數addrlen:客戶端地址長度的指針,若不為NULL,函數返回時,該指針指向實際的客戶端地址長度ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0,CONV(&peer), &peerlen);if (n > 0) {// 將英文單詞 轉換為 中文std::string result = _func(inbuffer);::sendto(_sockfd, result.c_str(), result.size(), 0, CONV(&peer),peerlen);}}
}

🏳??🌈二、Dictionary 字典類設計

字典類執行加載字典文件 和 執行翻譯的功能

2.1 基本結構

class Dictionary{private:// 加載字典文件void LoadDictionary(const std::string& path);public:// 構造函數Dictionary(const std::string& path);// 翻譯std::string Translate(const std::string& word);// 析構函數 ~Dictionary();private:std::unordered_map<std::string, std::string> _dict;     // 字典結構std::string _dict_path;                                 // 文件路徑
};

2.2 加載字典文件 - LoadDictionary(const std::string& path)

我們以 ": " 一個冒號加一個空格的形式,進行翻譯

  • 加載字典文件的本質是以KV的形式將英文單詞和中文翻譯插入到_dict哈希表中!

  • 加載文件包含3個大的步驟

    1. 讀方式打開文件
    2. 按行讀取內容[需要考慮中間有空格情況,一行中沒找到分隔符情況]
    3. 關閉文件
// 加載字典文件
void LoadDictionary(const std::string& path) {// 1. 讀方式打開文件std::ifstream in(path);if (!in.is_open()) {LOG(LogLevel::FATAL) << "open " << path.c_str() << " failed";Die(1);}std::string line;// 2. 按行讀取內容while (std::getline(in, line)) {LOG(LogLevel::DEBUG) << line.c_str() << "load success";if (line.empty())continue; // 中間有空格情況auto pos = line.find(sep); // 使用find找到分隔符位置,返回迭代器位置if (pos == std::string::npos)continue; // 找不到分隔符,跳過該行std::string key = line.substr(0, pos); // 前閉后開if (key.empty())continue; // 鍵為空,跳過該行std::string value = line.substr(pos + sep.size());if (value.empty())continue; // 值為空,跳過該行_dict.insert(std::make_pair(key, value));}LOG(LogLevel::INFO) << path.c_str() << " load success";// 3. 關閉文件in.close();
}

2.3 構造函數

初始化字典文件,并將鍵值對加載到本地保存

// 構造函數
Dictionary(const std::string& path = gpath + gdictname) { LoadDictionary(path); }

2.4 翻譯函數

在鍵值對中查找是否有該單詞,有單詞就返回值,沒有返回None

// 翻譯
std::string Translate(const std::string& word) {auto iter = _dict.find(word);if (iter == _dict.end())return "None";return iter->second;
}

2.5 服務端運行更新

因為我們現在需要將字典類的查找方法 作為回調函數傳給服務端 ,所以需要進行一些變化

#include "UdpServer.hpp"
#include "Dictionary.hpp"int main(int argc, char *argv[])
{if(argc != 2){std::cerr << "Usage: " << argv[0] << " localport" << std::endl;Die(1);}uint16_t port = std::stoi(argv[1]);ENABLE_CONSOLE_LOG();   // 日期類方法,使日志在控制臺輸出std::shared_ptr<Dictionary> dict_ptr = std::make_shared<Dictionary>();std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&dict_ptr]( const std::string& word){std::cout << "|" << word << "|" << std::endl;return dict_ptr->Translate(word);});usvr->InitServer(); // 初始化服務端usvr->Start();      // 啟動服務端return 0;
}

在這里插入圖片描述

🏳??🌈三、整體代碼

UdpServer.hpp

#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include <functional>
#include <cerrno>   // 這個頭文件包含了errno定義,用于存放系統調用的返回值
#include <strings.h>    // 屬于POSIX擴展?(非標準C/C++),常見于Unix/Linux系統,提供額外字符串函數(如 bcopy, bzero)#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"using namespace LogModule;const static int gsockfd = -1;
const static std::string gdefaultip = "127.0.0.1"; // 表示本地主機
const static uint16_t gdefaultport = 8080;// 回調函數對象聲明
using func_t = std::function<std::string(std::string)>;class nocopy{public:nocopy(){}~nocopy(){}nocopy(const nocopy&) = delete;     // 禁止拷貝構造函數const nocopy& operator=(const nocopy&) = delete;   // 禁止拷貝賦值運算符
};class UdpServer : public nocopy{public:UdpServer(uint16_t localport = gdefaultport, func_t func = nullptr): _sockfd(gsockfd),_localport(localport),_isrunning(false),_func(func){}void InitServer(){// 1. 創建套接字// socket(int domain, int type, int protocol)// 返回一個新的套接字文件描述符,或者在出錯時返回-1// 參數domain:協議族,AF_INET,表示IPv4協議族// 參數type:套接字類型,SOCK_DGRAM,表示UDP套接字// 參數protocol:協議,0,表示默認協議_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if(_sockfd < 0){LOG(LogLevel::FATAL) << "socket: " << strerror(errno);// exit(SOCKET_ERR) 表示程序運行失敗,并返回指定的錯誤碼exit(SOCKET_ERR);}LOG(LogLevel::DEBUG) << "socket success, sockfd is: " << _sockfd;// 2. bind// sockaddr_in struct sockaddr_in local;// 將local全部置零,以便后面設置memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; // IPv4協議族local.sin_port = htons(_localport); // 端口號,網絡字節序local.sin_addr.s_addr = htonl(INADDR_ANY); // 本地IP地址,網絡字節序// 將套接字綁定到本地地址// bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen)// 綁定一個套接字到一個地址,使得套接字可以接收來自該地址的數據報// 參數sockfd:套接字文件描述符// 參數addr:指向sockaddr_in結構體的指針,表示要綁定的地址// 參數addrlen:地址長度,即sizeof(sockaddr_in)// 返回0表示成功,-1表示出錯int n = ::bind(_sockfd, (struct sockaddr* )&local, sizeof(local));if(n < 0){LOG(LogLevel::FATAL) << "bind: " << strerror(errno);exit(BIND_ERR);}LOG(LogLevel::DEBUG) << "bind success";}void Start(){_isrunning = true;while(true){char inbuffer[1024];                // 接收緩沖區struct sockaddr_in peer;            // 接收客戶端地址socklen_t peerlen = sizeof(peer);   // 計算接收的客戶端地址長度// 接收數據報// recvfrom(int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen)// 從套接字接收數據,并存入buf指向的緩沖區中,返回實際接收的字節數// 參數sockfd:套接字文件描述符// 參數buf:指向接收緩沖區的指針,c_str()函數可以將字符串轉換為char*,以便存入緩沖區// 參數len:接收緩沖區的長度// 參數flags:接收標志,一般設為0// 參數src_addr:指向客戶端地址的指針,若不為NULL,函數返回時,該指針指向客戶端的地址,是網絡字節序// 參數addrlen:客戶端地址長度的指針,若不為NULL,函數返回時,該指針指向實際的客戶端地址長度ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, CONV(&peer), &peerlen);if(n > 0){// 將英文單詞 轉換為 中文std::string result = _func(inbuffer);::sendto(_sockfd, result.c_str(), result.size(), 0, CONV(&peer), peerlen);}}}~UdpServer(){// 判斷 _sockfd 是否是一個有效的套接字文件描述符// 有效的文件描述符(如套接字、打開的文件等)是非負整數?(>= 0)if(_sockfd > -1) ::close(_sockfd);}private:int _sockfd;            // 文件描述符uint16_t _localport;    // 端口號std::string _localip;   // 本地IP地址bool _isrunning;        // 運行狀態func_t _func;           // 回調函數
};

UdpServer.cpp

#include "UdpServer.hpp"
#include "Dictionary.hpp"int main(int argc, char *argv[])
{if(argc != 2){std::cerr << "Usage: " << argv[0] << " localport" << std::endl;Die(1);}uint16_t port = std::stoi(argv[1]);ENABLE_CONSOLE_LOG();   // 日期類方法,使日志在控制臺輸出std::shared_ptr<Dictionary> dict_ptr = std::make_shared<Dictionary>();std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&dict_ptr]( const std::string& word){std::cout << "|" << word << "|" << std::endl;return dict_ptr->Translate(word);});usvr->InitServer(); // 初始化服務端usvr->Start();      // 啟動服務端return 0;
}

UdpClient.hpp

#pragma once#include "Common.hpp"
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

UdpClient.cpp

#include "UdpClient.hpp"int main(int argc, char* argv[]){if(argc != 3){std::cerr << argv[0] << " serverip server" << std::endl;Die(USAGE_ERR);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// 1. 創建套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){std::cerr << "create socket error" << std::endl;Die(SOCKET_ERR);}// 1. 填充 server 信息struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = ::htons(serverport);server.sin_addr.s_addr = ::inet_addr(serverip.c_str());// 2. 發送數據while(true){std::cout << "Please Enter# ";std::string msg;std::getline(std::cin, msg);// client 必須自己的ip和端口。但是客戶端,不需要顯示調用bind// 客戶端首次 sendto 消息的時候,由OS自動bind// 1. 如何理解 client 自動隨機bind端口號? 一個端口號,只能讀一個進程bind// 2. 如何理解 server 要顯示地bind? 必須穩定!必須是眾所周知且不能輕易改變的int n = ::sendto(sockfd, msg.c_str(), msg.size(), 0, CONV(&server), sizeof(server));(void)n;struct sockaddr_in temp;socklen_t len = sizeof(temp);char buffer[1024];n = ::recvfrom(sockfd, buffer,sizeof(buffer) - 1, 0, CONV(&temp), &len);if(n > 0){buffer[n] = 0;std::cout << buffer << std::endl;}}return 0;
}

Common.hpp

#pragma once#include <iostream>#define Die(code)   \do              \{               \exit(code); \} while (0)#define CONV(v) (struct sockaddr *)(v)enum 
{USAGE_ERR = 1,SOCKET_ERR,BIND_ERR
};

InetAddr.hpp

#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"class InetAddr
{
private:void PortNet2Host(){_port = ::ntohs(_net_addr.sin_port);}void IpNet2Host(){char ipbuffer[64];const char *ip = ::inet_ntop(AF_INET, &_net_addr.sin_addr, ipbuffer, sizeof(ipbuffer));(void)ip;}public:InetAddr(){}InetAddr(const struct sockaddr_in &addr) : _net_addr(addr){PortNet2Host();IpNet2Host();}InetAddr(uint16_t port) : _port(port), _ip(""){_net_addr.sin_family = AF_INET;_net_addr.sin_port = htons(_port);_net_addr.sin_addr.s_addr = INADDR_ANY;}struct sockaddr *NetAddr() { return CONV(&_net_addr); }socklen_t NetAddrLen() { return sizeof(_net_addr); }std::string Ip() { return _ip; }uint16_t Port() { return _port; }~InetAddr(){}private:struct sockaddr_in _net_addr;std::string _ip;uint16_t _port;
};

Dictionary.hpp

#pragma once#include <iostream>
#include <string>
#include <fstream>
#include <unordered_map>
#include "Log.hpp"
#include "Common.hpp"using namespace LogModule;const static std::string sep = ": ";
const static std::string gpath = "./";
const static std::string gdictname = "dict.txt";class Dictionary{private:// 加載字典文件void LoadDictionary(const std::string& path){// 1. 讀方式打開文件std::ifstream in(path);if(!in.is_open()){LOG(LogLevel::FATAL) << "open " << path.c_str() << " failed";Die(1);}std::string line;// 2. 按行讀取內容while(std::getline(in, line)){LOG(LogLevel::DEBUG) << line.c_str() << "load success";if(line.empty())continue; // 中間有空格情況auto pos = line.find(sep);  // 使用find找到分隔符位置,返回迭代器位置if(pos == std::string::npos)continue; // 找不到分隔符,跳過該行std::string key = line.substr(0, pos); // 前閉后開if(key.empty())continue; // 鍵為空,跳過該行std::string value = line.substr(pos + sep.size()); if(value.empty())continue; // 值為空,跳過該行_dict.insert(std::make_pair(key, value));}LOG(LogLevel::INFO) << path.c_str() << " load success";// 3. 關閉文件in.close(); }public:// 構造函數Dictionary(const std::string& path = gpath + gdictname){LoadDictionary(path);}// 翻譯std::string Translate(const std::string& word){auto iter = _dict.find(word);if(iter == _dict.end()) return "None";return iter->second;}// 析構函數 ~Dictionary(){}private:std::unordered_map<std::string, std::string> _dict;     // 字典結構std::string _dict_path;                                 // 文件路徑
};

Log.hpp

#pragma once#include <iostream>
#include <cstdio>
#include <string>
#include <fstream>
#include <sstream>
#include <memory>
#include <filesystem> //C++17
#include <unistd.h>
#include <time.h>
#include "Mutex.hpp"namespace LogModule
{using namespace LockModule;// 獲取一下當前系統的時間std::string CurrentTime(){time_t time_stamp = ::time(nullptr);struct tm curr;localtime_r(&time_stamp, &curr); // 時間戳,獲取可讀性較強的時間信息5char buffer[1024];// bugsnprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d:%02d:%02d",curr.tm_year + 1900,curr.tm_mon + 1,curr.tm_mday,curr.tm_hour,curr.tm_min,curr.tm_sec);return buffer;}// 構成: 1. 構建日志字符串 2. 刷新落盤(screen, file)//  1. 日志文件的默認路徑和文件名const std::string defaultlogpath = "./log/";const std::string defaultlogname = "log.txt";// 2. 日志等級enum class LogLevel{DEBUG = 1,INFO,WARNING,ERROR,FATAL};std::string Level2String(LogLevel level){switch (level){case LogLevel::DEBUG:return "DEBUG";case LogLevel::INFO:return "INFO";case LogLevel::WARNING:return "WARNING";case LogLevel::ERROR:return "ERROR";case LogLevel::FATAL:return "FATAL";default:return "None";}}// 3. 刷新策略.class LogStrategy{public:virtual ~LogStrategy() = default;virtual void SyncLog(const std::string &message) = 0;};// 3.1 控制臺策略class ConsoleLogStrategy : public LogStrategy{public:ConsoleLogStrategy(){}~ConsoleLogStrategy(){}void SyncLog(const std::string &message){LockGuard lockguard(_lock);std::cout << message << std::endl;}private:Mutex _lock;};// 3.2 文件級(磁盤)策略class FileLogStrategy : public LogStrategy{public:FileLogStrategy(const std::string &logpath = defaultlogpath, const std::string &logname = defaultlogname): _logpath(logpath),_logname(logname){// 確認_logpath是存在的.LockGuard lockguard(_lock);if (std::filesystem::exists(_logpath)){return;}try{std::filesystem::create_directories(_logpath);}catch (std::filesystem::filesystem_error &e){std::cerr << e.what() << "\n";}}~FileLogStrategy(){}void SyncLog(const std::string &message){LockGuard lockguard(_lock);std::string log = _logpath + _logname; // ./log/log.txtstd::ofstream out(log, std::ios::app); // 日志寫入,一定是追加if (!out.is_open()){return;}out << message << "\n";out.close();}private:std::string _logpath;std::string _logname;// 鎖Mutex _lock;};// 日志類: 構建日志字符串, 根據策略,進行刷新class Logger{public:Logger(){// 默認采用ConsoleLogStrategy策略_strategy = std::make_shared<ConsoleLogStrategy>();}void EnableConsoleLog(){_strategy = std::make_shared<ConsoleLogStrategy>();}void EnableFileLog(){_strategy = std::make_shared<FileLogStrategy>();}~Logger() {}// 一條完整的信息: [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] + 日志的可變部分(<< "hello world" << 3.14 << a << b;)class LogMessage{public:LogMessage(LogLevel level, const std::string &filename, int line, Logger &logger): _currtime(CurrentTime()),_level(level),_pid(::getpid()),_filename(filename),_line(line),_logger(logger){std::stringstream ssbuffer;ssbuffer << "[" << _currtime << "] "<< "[" << Level2String(_level) << "] "<< "[" << _pid << "] "<< "[" << _filename << "] "<< "[" << _line << "] - ";_loginfo = ssbuffer.str();}template <typename T>LogMessage &operator<<(const T &info){std::stringstream ss;ss << info;_loginfo += ss.str();return *this;}~LogMessage(){if (_logger._strategy){_logger._strategy->SyncLog(_loginfo);}}private:std::string _currtime; // 當前日志的時間LogLevel _level;       // 日志等級pid_t _pid;            // 進程pidstd::string _filename; // 源文件名稱int _line;             // 日志所在的行號Logger &_logger;       // 負責根據不同的策略進行刷新std::string _loginfo;  // 一條完整的日志記錄};// 就是要拷貝,故意的拷貝LogMessage operator()(LogLevel level, const std::string &filename, int line){return LogMessage(level, filename, line, *this);}private:std::shared_ptr<LogStrategy> _strategy; // 日志刷新的策略方案};Logger logger;#define LOG(Level) logger(Level, __FILE__, __LINE__)
#define ENABLE_CONSOLE_LOG() logger.EnableConsoleLog()
#define ENABLE_FILE_LOG() logger.EnableFileLog()
}

Mutex.hpp

#pragma once
#include <iostream>
#include <pthread.h>namespace LockModule
{class Mutex{public:Mutex(const Mutex&) = delete;const Mutex& operator = (const Mutex&) = delete;Mutex(){int n = ::pthread_mutex_init(&_lock, nullptr);(void)n;}~Mutex(){int n = ::pthread_mutex_destroy(&_lock);(void)n;}void Lock(){int n = ::pthread_mutex_lock(&_lock);(void)n;}pthread_mutex_t *LockPtr(){return &_lock;}void Unlock(){int n = ::pthread_mutex_unlock(&_lock);(void)n;}private:pthread_mutex_t _lock;};class LockGuard{public:LockGuard(Mutex &mtx):_mtx(mtx){_mtx.Lock();}~LockGuard(){_mtx.Unlock();}private:Mutex &_mtx;};
}

Makefile

.PHONY: all
all:server_udp client_udpserver_udp:UdpServer.cppg++ -o $@ $^ -std=c++17client_udp:UdpClient.cpp g++ -o $@ $^ -std=c++17.PHONY: clean
clean:rm -f server_udp client_udp

👥總結

本篇博文對 【Linux網絡】構建Udp服務器與字典翻譯系統 做了一個較為詳細的介紹,不知道對你有沒有幫助呢

覺得博主寫得還不錯的三連支持下吧!會繼續努力的~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/76627.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/76627.shtml
英文地址,請注明出處:http://en.pswp.cn/web/76627.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【項目管理】成本類計算 筆記

項目管理-相關文檔&#xff0c;希望互相學習&#xff0c;共同進步 風123456789&#xff5e;-CSDN博客 &#xff08;一&#xff09;知識總覽 項目管理知識域 知識點&#xff1a; &#xff08;項目管理概論、立項管理、十大知識域、配置與變更管理、績效域&#xff09; 對應&…

div(HTML標準元素)和view(微信小程序專用組件)的主要區別體

div&#xff08;HTML標準元素&#xff09;和view&#xff08;微信小程序專用組件&#xff09;的主要區別體現在以下方面&#xff1a; 一、應用場景與開發框架 ?適用平臺不同? div是HTML/CSS開發中通用的塊級元素&#xff0c;用于Web頁面布局?&#xff1b;view是微信小程序專…

【C++軟件實戰問題排查經驗分享】UI界面卡頓 | CPU占用高 | GDI對象泄漏 | 線程堵塞 系列問題排查總結

目錄 1、UI界面卡頓問題排查 2、軟件CPU占用高問題排查 3、UI界面顯示異常&#xff08;GDI對象泄漏導致窗口繪制異常&#xff09;問題排查 4、軟件線程堵塞&#xff08;包含線程死鎖&#xff09;問題排查 5、最后 C軟件異常排查從入門到精通系列教程&#xff08;核心精品專…

管理雜談——采石磯大捷的傳奇與啟示

南宋抗金史上&#xff0c;岳飛與岳家軍的鐵血傳奇家喻戶曉&#xff0c;但另一位力挽狂瀾的“文官戰神”卻常被忽視——他從未掌兵&#xff0c;卻在南宋存亡之際整合潰軍&#xff0c;以少勝多&#xff0c;締造采石磯大捷。此人正是虞允文。一介書生何以扭轉乾坤&#xff1f;他的…

動態規劃-零錢兌換

332.零錢兌換 給你一個整數數組 coins &#xff0c;表示不同面額的硬幣&#xff1b;以及一個整數 amount &#xff0c;表示總金額。計算并返回可以湊成總金額所需的 最少的硬幣個數 。如果沒有任何一種硬幣組合能組成總金額&#xff0c;返回 -1 。你可以認為每種硬幣的數量是無…

SpringAI+DeepSeek大模型應用開發——4 對話機器人

目錄??????? ??????????????項目初始化 pom文件 配置模型 ChatClient 同步調用 流式調用 日志功能 對接前端 解決跨域 會話記憶功能 ChatMemory 添加會話記憶功能 會話歷史 管理會話id 保存會話id 查詢會話歷史 完善會話記憶 定義可序列…

Java 關鍵字

本章列出了Java 語言的所有關鍵字和“類關鍵字的單詞”。 “受限關鍵字”是指&#xff0c;它們旨在模塊聲明中是關鍵字&#xff0c;在其他情況下則是標識符。 “受限標識符”是指&#xff0c;除非用在某些特定位置&#xff0c;否則他們只是標識符。例如&#xff0c;var一般都…

AI重塑網絡安全:機遇與威脅并存的“雙刃劍”時代

一、引言 人工智能&#xff08;AI&#xff09;技術的迅猛發展&#xff0c;正在深刻改變網絡安全行業的格局。從ChatGPT生成釣魚郵件到AI驅動的漏洞挖掘&#xff0c;從零信任架構的普及到安全大模型的實戰應用&#xff0c;AI既是攻擊者的“新武器”&#xff0c;也是防御者的“新…

網絡原理——UDP

1、 與TCP的關鍵區別 特性UDPTCP連接方式無連接面向連接可靠性不可靠可靠數據順序不保證順序保證順序傳輸速度更快相對較慢頭部開銷8字節20-60字節流量控制無有擁塞控制無有適用場景實時應用、廣播/多播可靠性要求高的應用 2、UDP 報文結構 報文結構大致可以分為首部和載荷&a…

STM32——新建工程并使用寄存器以及庫函數進行點燈

本文是根據江協科技提供的教學視頻所寫&#xff0c;旨在便于日后復習&#xff0c;同時供學習嵌入式的朋友們參考&#xff0c;文中涉及到的所有資料也均來源于江協科技&#xff08;資料下載&#xff09;。 新建工程并使用寄存器以及庫函數進行點燈操作 新建工程步驟1.建立工程2.…

Unocss 類名基操, tailwindcss 類名

這里只列出 unocss 的可實現類名&#xff0c;tailwindcss 可以拿去試試用 1. 父元素移入&#xff0c;子元素改樣式 <!-- 必須是 group 類名 --> <div class"group"><div class"group-hover:color-red">Text</div> </div>2…

深度學習入門(一)

一、簡介 深度學習是機器學習領域新興且關鍵的研究方向。機器學習重點在于讓計算機從數據中挖掘規律以預測未知&#xff0c;而深度學習借助構建多層神經網絡&#xff0c;自動學習數據的復雜特征&#xff0c;從而實現更精準的模式識別&#xff0c;在圖像、語音等眾多領域廣泛應…

element-plus中,Steps 步驟條組件的使用

目錄 一.基本使用 1.代碼 2.效果展示 3.代碼解讀 二.案例&#xff1a;修改用戶的密碼 1.期望效果 2.代碼 3.展示效果 結語 一.基本使用 1.代碼 從官網復制如下代碼&#xff1a; <template><div><el-stepsstyle"max-width: 600px":space&quo…

jax 備忘錄

https://zhuanlan.zhihu.com/p/532504225 https://docs.jax.dev/en/latest/index.html

NLTK 基礎入門:用 Python 解鎖自然語言處理

自然語言處理&#xff08;NLP&#xff09;是人工智能領域的重要分支&#xff0c;它讓計算機能夠理解、處理和生成人類語言。而 NLTK&#xff08;Natural Language Toolkit&#xff09; 作為 Python 生態中最經典的 NLP 庫之一&#xff0c;提供了豐富的工具和資源&#xff0c;是…

ElementUI中checkbox v-model綁定值為布爾、字符串或數字類型

這篇博客介紹了在Vue.js中使用El-Checkbox組件時&#xff0c;如何設置和處理v-model的布爾值和類型轉換。通過示例代碼展示了如何設置true-label和false-label屬性來改變選中狀態的值&#xff0c;適用于需要特定類型&#xff08;如字符串或整數&#xff09;的場景。v-model不能…

JBoss 項目修復筆記:繞開 iframe 安全問題,JSF 與 Angular 最小代價共存方案

JBoss 項目修復筆記&#xff1a;繞開 iframe 安全問題&#xff0c;JSF 與 Angular 最小代價共存方案 本篇筆記銜接的內容為&#xff1a;JBoss WildFly 本地開發環境完全指南&#xff0c;里面簡單的描述了一下怎么配置 docker&#xff0c;在本地啟動一個可以運行的 JBoss 和 W…

Linux文件時間戳詳解:Access、Modify、Change時間的區別與作用

在 Linux 系統中&#xff0c;文件的這三個時間戳&#xff08;Access、Modify、Change&#xff09;分別表示不同的文件狀態變更時間&#xff0c;具體含義如下&#xff1a; 1. Access Time (Access) 含義&#xff1a;文件最后一次被訪問的時間&#xff08;讀取內容或執行&#xf…

SpringBoot項目打包為window安裝包

SpringBoot項目打包為window安裝包 通過jpackage及maven插件的方式將springboot項目打包為exe或msi pom.xml 添加插件 <plugin><groupId>org.codehaus.mojo</groupId><artifactId>exec-maven-plugin</artifactId><version>3.1.0</vers…

pip永久換鏡像地址

要將 pip 永久設置為阿里云鏡像源&#xff0c;可以通過修改 pip 的全局配置文件來實現。以下是具體步驟&#xff1a; 步驟 1&#xff1a;創建或修改 pip 配置文件 根據你的操作系統&#xff0c;配置文件的路徑略有不同&#xff1a; Linux/macOS 配置文件路徑&#xff1a;~/.…