当前位置: 首页 > news >正文

【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服务器与字典翻译系统 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

相关文章:

【Linux网络】构建UDP服务器与字典翻译系统

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…...

DeepSeek 大模型 + LlamaIndex + MySQL 数据库 + 知识文档 实现简单 RAG 系统

DeepSeek 大模型 LlamaIndex MySQL 数据库 知识文档 实现简单 RAG 系统 以下是一个使用 DeepSeek 大模型&#xff08;假设为一个高性能的中文大模型&#xff09;、LlamaIndex、MySQL 数据库 和 知识文档 实现简单 RAG&#xff08;检索增强生成&#xff09;系统的完整示例。该…...

【FFmpeg从入门到精通】第四章-FFmpeg转码

1 FFmpeg 软编码H.264与H.265 当前网络中常见的视频编码格式要数H.264最为火热&#xff0c;支持H.264的封装格式有很多&#xff0c;如FLV、MP4、HLS(M3U8)、MKV、TS等格式;FFmpeg本身并不支持H.264的编码器&#xff0c;而是由FFmpeg的第三方模块对其进行支持&#xff0c;例如x…...

爱普生RX8130CE实时时钟成为智能家居系统的理想解决方案

智能家居的本质是让生活更便捷、舒适与智能&#xff0c;而精准的时间管理是实现这一目标的重要基础。爱普生 RX8130CE 实时时钟&#xff08;RTC&#xff09;以其卓越的性能和丰富的功能&#xff0c;成为智能家居系统的理想时间解决方案&#xff0c;为用户打造更加智能化、人性化…...

Discuz!与DeepSeek的深度融合:打造智能网址导航新标杆

引言 在数字化信息爆炸的时代&#xff0c;网址导航网站作为用户获取优质资源、高效浏览互联网的重要入口&#xff0c;其信息筛选能力、用户体验和商业化潜力成为了决定其竞争力的核心要素。Discuz!作为国内应用广泛的社区论坛系统&#xff0c;以其强大的功能扩展性和用户管理能…...

23种设计模式-结构型模式之代理模式(Java版本)

Java 代理模式&#xff08;Proxy Pattern&#xff09;详解 &#x1f9ed; 什么是代理模式&#xff1f; 代理模式是结构型设计模式之一&#xff0c;为其他对象提供一个代理以控制对这个对象的访问。 就像生活中的“经纪人”&#xff0c;你无法直接联系明星&#xff0c;但可以…...

网络不可达network unreachable问题解决过程

问题&#xff1a;访问一个环境中的路由器172.16.1.1&#xff0c;发现ssh无法访问&#xff0c;ping发现回网络不可达 C:\Windows\System32>ping 172.16.1.1 正在 Ping 172.16.1.1 具有 32 字节的数据: 来自 172.16.81.1 的回复: 无法访问目标网。 来自 172.16.81.1 的回复:…...

@RefreshScope 和@nacosvalue 的区别

文章目录 1. RefreshScope定义与作用工作原理适用场景示例代码 2. NacosValue定义与作用工作原理适用场景示例代码 3. 主要区别4. 如何选择&#xff1f;5. 注意事项 在 Spring 框架中&#xff0c; RefreshScope 和 NacosValue 是两个不同的注解&#xff0c;分别用于不同的场景…...

Oracle EBS R12.2 安装 -- Step by Step

一、引言 在计算机应用已经非常普及的今天,对于绝大部分个人来说,学习并掌握ORACLE ERP系统是一件“实践性”很强的事情,仅仅“纸上谈兵”而不在系统中进行具体的操作,犹如捧着一本“驾驶手册”苦读,但却没有一辆车进行上路演练,是肯定无法学会开车的道理一样,能够为自…...

【JavaEE】计算机的工作原理

计算机系统的组成 一台完整的计算机包含硬件和软件两部分&#xff0c;另外还有一部分固化的软件称为固件&#xff08;兼具软件和硬件的特性&#xff09;&#xff0c;硬件和软件结合才能使计算机正常运行并发挥作用&#xff0c;所以对计算机的理解应该把它看作一个包含软件系统…...

DAY8:Oracle高可用架构深度解析与Data Guard单节点搭建实战

引言 在数据库领域&#xff0c;高可用性&#xff08;High Availability&#xff09;是保障业务连续性的核心要求。Oracle作为企业级数据库的领导者&#xff0c;提供了RAC、Data Guard、GoldenGate三大核心方案。本文将深入剖析这些技术的实现原理&#xff0c;并手把手指导搭建…...

程序的编译(预处理操作)+链接

程序的编译环境和执行环境 翻译环境&#xff1a;在这个环境中源代码被转换成可执行的机器指令 执行环境&#xff1a;用于实际执行代码 详解编译链接 翻译环境 注意&#xff1a; 1.组成一个程序的每个源文件通过编译过程分别转换成目标代码。 2.每个目标文件由链接器捆绑在一…...

Java 实现桌面共享-简单案例

服务器端&#xff08;共享桌面&#xff09; import java.awt.AWTException; import java.awt.Rectangle; import java.awt.Robot; import java.awt.Toolkit; import java.awt.image.BufferedImage; import java.io.DataOutputStream; import java.io.IOException; import java…...

Idea中实用设置和插件

目录 一、Idea使用插件 1.Fitten Code智能提示 2.MyBatisCodeHelperPro 3.HighlightBracketPair‌ 4.Rainbow Brackets Lite 5.GitToolBox(存在付费) 6.MavenHelperPro 7.Search In Repository 8.VisualGC(存在付费) 9.vo2dto 10.Key Promoter X 11.CodeGlance…...

获取电脑信息(登录电脑的进程、C盘文件信息、浏览器信息、IP)

电脑的进程信息 // 获取登录电脑的进程信息String os System.getProperty("os.name").toLowerCase();String command;if (os.contains("win")) {command "tasklist";} else {command "ps -ef";}try {Process process new ProcessB…...

单例模式(线程安全)

1.什么是单例模式 单例模式&#xff08;Singleton Pattern&#xff09;是一种创建型设计模式&#xff0c;旨在确保一个类只有一个实例&#xff0c;并提供一个全局访问点来访问该实例。这种模式涉及到一个单一的类&#xff0c;该类负责创建自己的对象&#xff0c;同时确保只有单…...

Sentinel源码—7.参数限流和注解的实现二

大纲 1.参数限流的原理和源码 2.SentinelResource注解的使用和实现 2.SentinelResource注解的使用和实现 (1)SentinelResource注解的使用 (2)SentinelResource注解和实现 (1)SentinelResource注解的使用 一.引入Sentinel Spring Boot Starter依赖 <dependency><…...

【单片机 C语言】单片机学习过程中常见C库函数(学习笔记)

memset() ​C 标准库 - <string.h> string .h 头文件定义了一个变量类型、一个宏和各种操作字符数组的函数。 <string.h> 是 C 标准库中的一个头文件&#xff0c;提供了一组用于处理字符串和内存块的函数。这些函数涵盖了字符串复制、连接、比较、搜索和内存操作…...

聚类算法(K-means、DBSCAN)

聚类算法 K-means 算法 算法原理 K-means 是一种基于类内距离最小化的划分式聚类算法&#xff0c;其核心思想是通过迭代优化将数据划分为 K 个簇。目标函数为最小化平方误差&#xff08;SSE&#xff09;&#xff1a; S S E ∑ i 1 K ∑ x ∈ C i ∣ ∣ x − μ i ∣ ∣ 2…...

Spring AI Alibaba Graph基于 ReAct Agent 的天气预报查询系统

1、在本示例中&#xff0c;我们仅为 Agent 绑定了一个天气查询服务&#xff0c;接收到用户的天气查询服务后&#xff0c;流程会在 AgentNode 和 ToolNode 之间循环执行&#xff0c;直到完成用户指令。示例中判断指令完成的条件&#xff08;即 ReAct 结束条件&#xff09;也很简…...

C++初阶——模板

C初阶——模板 一、概念引入 1.如何实现一个通用的交换函数&#xff0c;使它既可以用来交换各种类型的数据呢&#xff1f; 通过前面的学习&#xff0c;我们知道函数重载可以帮我们实现这一功能&#xff0c;代码如下&#xff1a; 运行结果如图&#xff1a; 使用函数重载虽然…...

【技术派后端篇】技术派中基于 Redis 的缓存实践

在互联网应用追求高并发和高可用的背景下&#xff0c;缓存对于提升程序性能至关重要。相较于本地缓存 Guava Cache 和 Caffeine&#xff0c;Redis 具有显著优势。Redis 支持集群和分布式部署&#xff0c;能横向扩展缓存容量和负载能力&#xff0c;适应大型分布式系统的缓存需求…...

系统安装及应用

重点 账号安全控制 系统引导和登陆控制 弱口令检测 端口扫描 前言 随着信息技术的快速发展,系统安全成为我们日常生活和工作中不可或缺的一部分。本章节主要探讨系统安全及应用,涵盖了账号安全控制、系统引导和登录控制、弱口令检测以及端口扫描等多个方面,为我们提供了一…...

发布事件和Insert数据库先后顺序

代码解释 csharp await PublishCreatedAsync(entity).ConfigureAwait(false); await Repository.InsertAsync(entity).ConfigureAwait(false);PublishCreatedAsync(entity)&#xff1a;这是一个异步方法&#xff0c;其功能是发布与实体创建相关的事件。此方法或许会通知其他组…...

【英语语法】词法---冠词

目录 冠词一、不定冠词&#xff1a;a / an1. 基本用法2. 主要使用场景3. 特殊情况 二、定冠词&#xff1a;the1. 基本用法2. 主要使用场景3. 特殊情况 三、零冠词1. 基本规则2. 特殊情况 四、冠词对比五、常见错误总结 冠词 冠词是英语中用于限定名词的一类虚词&#xff0c;分…...

android的 framework 有哪些知识点和应用场景

Android Framework 知识点 1. 四大组件 Activity&#xff08;活动&#xff09; 是 Android 应用中最基本的组件&#xff0c;用于实现用户界面。一个 Activity 通常对应一个屏幕的内容。有自己的生命周期&#xff0c;包括 onCreate、onStart、onResume、onPause、onStop、onDe…...

Prompt 攻击与防范:大语言模型安全的新挑战

随着大语言模型&#xff08;LLM&#xff09;在企业服务、智能助手、搜索增强等领域的广泛应用&#xff0c;围绕其"Prompt"机制的安全问题也逐渐引起关注。其中最具代表性的&#xff0c;就是所谓的 Prompt Injection&#xff08;提示词注入&#xff09;攻击。 本文将…...

Ubuntu20.04安装Pangolin遇到的几种报错的解决方案

1.添加两个编译选项 /usr/include/OpenEXR/half.h:121:13: note: because ‘half’ has user-provided ‘half& half::operator(half)’121 | half & operator (half h);| ^~~~~~~~ 解决方案&#xff1a; 在CMakeList中添加以下两句&#xff1a; …...

软考 中级软件设计师 考点知识点笔记总结 day14 关系代数 数据库完整性约束

文章目录 6.5 关系代数6.5.1 关系代数—七种基本运算 6.6 数据库完整性约束6.7 关系型数据库SQL简介 6.5 关系代数 候选码&#xff08;键&#xff09;&#xff1a;若关系中的某一属性或属性组的值能唯一标识一个元组&#xff0c;则称该属性或属性组为候选码。 主码&#xff0…...

前端vue监听 -watch

前端vue监听 -watch 前言基本用法监听简单数据属性监听对象属性 高级用法深度监听对象即时触发监听监听计算属性 注意事项 前言 在 Vue.js 里&#xff0c;watch 选项可用于响应式地监听数据的变化&#xff0c;当被监听的数据发生改变时&#xff0c;就会触发相应的回调函数来执…...

Linux之信号

目录 一、预备知识 二、信号的产生 一、键盘产生信号 二、系统调用 三、调用系统命令向进程发信号 kill 四、硬件异常 五、软件条件 三、信号的保存 四、信号的处理 一、预备知识 1.信号&#xff01;信号量。两者没有任何关系 2.什么是信号&#xff1f; 定义一&…...

微软Edge浏览器字体设置

前言 时间&#xff1a;2025年4月 自2025年4月起&#xff0c;微软Edge浏览器的默认字体被微软从微软雅黑替换成了Noto Sans&#xff0c;如下图。Noto Sans字体与微软雅黑风格差不多&#xff0c;但在4K以下分辨率的显示器上较微软雅黑更模糊&#xff0c;因此低分辨率的显示器建议…...

Java中 关于编译(Compilation)、类加载(Class Loading) 和 运行(Execution)的详细区别解析

以下是Java中 编译&#xff08;Compilation&#xff09;、类加载&#xff08;Class Loading&#xff09; 和 运行&#xff08;Execution&#xff09; 的详细区别解析&#xff1a; 1. 编译&#xff08;Compilation&#xff09; 定义 将Java源代码&#xff08;.java文件&#x…...

[python] set

1.添加元素 在 Python 中&#xff0c;向 set 添加一个元素可以使用 add() 方法。如果添加的元素已经存在于 set 中&#xff0c;add() 不会重复添加&#xff08;因为 set 具有自动去重的特性&#xff09;。 方法 1&#xff1a;add(element)&#xff08;添加单个元素&#xff0…...

转化率提升47%?亚马逊用户行为预测模型深度解读

在亚马逊运营的战场上&#xff0c;谁能更精准地读懂用户行为&#xff0c;谁就更可能赢得转化率的胜利。近年来&#xff0c;越来越多卖家借助“用户行为预测模型”来优化Listing布局、广告投放策略、甚至库存管理&#xff0c;而这些数据驱动的决策也确确实实地带来了质的提升。 …...

C++计算 n! 中末尾零的数量

* 详细说明* 给定一个整数作为输入。目标是找出该数的阶乘结果中末尾零的数量。 一个数 N 的阶乘是范围 [1, N] 内所有数的乘积。* * 我们知道&#xff0c;只有当一个数是 10 的倍数或者有因数对 (2, 5) 时&#xff0c;才会产生末尾零。 在任何大于 5 的数的阶乘中&#xff0c;…...

大模型中超参数TopK是什么

大模型中的超参数Top-K是文本生成过程中的关键控制参数,主要用于平衡生成结果的确定性与多样性。以下从定义、工作原理、应用场景及与其他参数的协同关系进行详细阐述: 一、Top-K的定义与核心机制 基本定义 Top-K(Top-K Sampling)是一种基于概率采样的文本生成策略。其核心…...

NetApp ONTAP 9 故障磁盘更换操作指南

以前写过一篇7-mode的磁盘更换文档&#xff0c;好几个朋友反馈说命令都没有&#xff0c;都不对。主要原因是客户现在的环境都是ontap 9的cluster-mode环境了&#xff0c;所以很多命令都不一样了。为此&#xff0c;这里专门就ontap 9的cluster-mode写一篇磁盘更换操作指南&#…...

leetcode day 35 01背包问题 416+1049

0-1背包问题 &#xff08;1&#xff09;第一种情况&#xff1a;二维dp[i][j]数组 dp[i][j]表示[0,i]的物品放入容量为j背包的最大价值 不放物品i,dp[i][j]dp[i-1][j] 放物品i,dp[i][j]dp[i-1][j-w[i]]v[i] 递推公式为&#xff1a; dp[i][j]dp[i-1][j];//不放 if(w[i]<j)dp…...

MySQL的基本操作

显示所有数据库&#xff1a; SHOW DATABASES; 系统默认数据库&#xff1a; 数据库名用途information_schema存储 MySQL 服务器元数据&#xff08;如数据库、表、列信息&#xff09;&#xff0c;只读mysql存储用户权限、密码、日志等核心数据&#xff08;不要随意修改&#xff…...

CSS伪类、clip-path实现三角形、箭头绘制

<template><div :class"$options.name"><div class"triangle-container1"><!-- 伪类三角形&#xff1a;向右 --><div class"triangle-RM"></div><!-- 伪类三角形&#xff1a;向下 --><div class&q…...

基于大模型的腹股沟疝全流程预测与诊疗方案研究报告

目录 一、引言 1.1 研究背景与目的 1.2 研究方法与创新点 二、大模型在腹股沟疝术前评估中的应用 2.1 腹股沟疝概述与诊断方法 2.2 术前评估指标与数据收集 2.3 大模型预测原理与实现 2.4 预测结果与传统评估对比 三、基于大模型预测的手术方案制定 3.1 手术方式选择…...

零基础上手Python数据分析 (20):Seaborn 统计数据可视化 - 轻松绘制精美统计图表!

写在前面 —— 告别 Matplotlib 繁琐定制,拥抱 Seaborn 便捷之美,让统计可视化更高效 在前面两篇博客中,我们学习了 Python 数据可视化的基石 Matplotlib,掌握了绘制基础图表和进行高级定制的技巧。 Matplotlib 功能强大且灵活,能够满足几乎所有的二维绘图需求。 然而,…...

elasticsearch7.15节点磁盘空间满了迁移数据到新磁盘

一.数据安全迁移 在 Elasticsearch 中设置某个节点临时不可用&#xff08;例如进行维护或升级&#xff09;&#xff0c;可以通过以下步骤安全地操作&#xff0c;避免数据丢失或集群状态异常 1: 排除节点分片分配&#xff0c;触发分片迁移到其他节点 PUT /_cluster/settings {&…...

MCP案例—客户端和服务端

MCP简介 Model Context Protocol (模型上下文协议)&#xff0c;简称MCP&#xff0c;MCP是一种协议&#xff0c;用于LLM与外部拓展资源交互的协议。 想了解具体细节可参考作者本篇文章MCP理论指南 准备 本篇文章将带你通过python创建MCP客户端及服务端&#xff0c;并连接到本…...

排序模型(Learning to Rank)

排序模型&#xff08;Learning to Rank&#xff09; 要解决的问题 排序模型旨在解决信息检索中的排序优化问题。例如&#xff1a; 搜索引擎中对候选网页的排序推荐系统中物品的展示顺序广告系统中广告位的分配 核心挑战&#xff1a;根据上下文特征&#xff0c;将最相关/最有…...

L1-1、Prompt 是什么?为什么它能“控制 AI”?

*Prompt 入门 L1-1 想象一下&#xff0c;你只需输入一句话&#xff0c;AI 就能自动为你写一篇文案、生成一份报告、甚至规划你的创业计划。这种“对话即编程”的背后魔法&#xff0c;就是 Prompt 的力量。 &#x1f50d; 一、Prompt 的定义与由来 Prompt&#xff08;提示词&am…...

RolmOCR重磅开源:基于Qwen2.5-VL,速度提升40%,手写/倾斜文档识别准确率超92%

向大家介绍一款全新的开源OCR模型——RolmOCR&#xff01;这款由Reducto AI团队基于阿里巴巴强大的Qwen2.5-VL-7B-Instruct视觉语言模型微调而来的利器&#xff0c;不仅在速度和效率上实现了显著提升&#xff08;据称处理速度相比其前身olmOCR提升了约40%&#xff09;&#xff…...

系统架构设计(二):基于架构的软件设计方法ABSD

“基于架构的软件设计方法”&#xff08;Architecture-Based Software Design, ABSD&#xff09;是一种通过从软件架构层面出发指导详细设计的系统化方法。它旨在桥接架构设计与详细设计之间的鸿沟&#xff0c;确保系统的高层结构能够有效指导后续开发。 ABSD 的核心思想 ABS…...

[langchain教程]langchain03——用langchain构建RAG应用

RAG RAG过程 离线过程&#xff1a; 加载文档将文档按一定条件切割成片段将切割的文本片段转为向量&#xff0c;存入检索引擎&#xff08;向量库&#xff09; 在线过程&#xff1a; 用户输入Query&#xff0c;将Query转为向量从向量库检索&#xff0c;获得相似度TopN信息将…...