[net 5] udp_dict_server 基于udp的簡單字典翻譯(服務器與業務相分離)

目錄

1. 功能了解

1.1. 啥是 dic_server?

1.2. dic_server 的小目標

2. 基本框架

2.1. 基本文件框架

2.2. 業務與服務器解耦 -> 回調函數

3. 字典

3.1. 字典配置文件

3.2. 構建字典類

3.2.1. 字典類的基本成員

3.2.2. 字典類構造

3.2.2.1. 構造

3.2.2.2. 信息加載

3.2.2.2.1. 先打開文件:

3.2.2.2.2. 如果打開失敗了呢?

3.2.2.2.3. 打開成功呢?

3.2.2.2.4. 最后記得關閉文件描述符 -> 防止資源泄露

3.2.3. dict::translate()

3.2.3.1. 查找一個空單詞?

3.2.3.2. 如果沒查到?

3.2.3.3. 如果查到了?

3.3. 字典服務 與 服務器相關聯

3.4. 測試一下

4. 參考代碼

4.1. 核心代碼

4.2. 其他代碼


目標:?

  • 基于udp的服務器接口基本認識
  • 實現服務器(收消息發消息) 與 業務(翻譯業務)的分離邏輯 -> 通過回調函數實現.?

1. 功能了解

1.1. 啥是 dic_server?

dic_server: 基于 udp 套接字的基本業務 -> 英漢翻譯.

1.2. dic_server 的小目標

  1. 然后我們做一個文件性的詞典, 而非內存級(正常來說稍微大一點的東西都不會做成內存級的).
  1. 一般服務器主要是用來進行網絡數據讀取和寫入的, 所以說很多服務器就是進行 IO 的.
    而我們的服務器希望可以 IO 邏輯 和 業務邏輯進行解耦.

2. 基本框架

2.1. 基本文件框架

為了方便, 我們直接把 udp_echo_server 的一些代碼 CV 過來即可.

2.2. 業務與服務器解耦 -> 回調函數

約定: 客戶端發來的是單詞.

這里為了方便解耦, 我們用一下包裝器包裝函數, 來達到方便類型統一的目的.

注意: 參數是非const參數.

如何用呢? 用戶傳對應的業務給服務器, 這樣就實現解耦了(實現的是業務和服務器之間的邏輯解耦).

回調函數調用完成之后, 我們服務器再返回即可.

3. 字典

3.1. 字典配置文件

3.2. 構建字典類

3.2.1. 字典類的基本成員

3.2.2. 字典類構造
3.2.2.1. 構造

告訴構造配置文件路徑, 然后開始加載:

3.2.2.2. 信息加載
3.2.2.2.1. 先打開文件:

3.2.2.2.2. 如果打開失敗了呢?

直接退出, 這屬于一個 FATAL 錯誤.

3.2.2.2.3. 打開成功呢?

我們開始從文件中讀取信息.

注意: 這個 getline 里面內置了強制類型轉換為 bool 的功能, 因此可以用到 while 當中.

處理"異常":

定義分隔符:

如果沒有找到分隔符: 咱們直接 continue

如果找到了, 咱們就截取子串.

如果 key / value 為空, 我們也要 continue.

我們把 key-val 插入到 _dict 當中.

到最后, 我們再提示一下即可:

3.2.2.2.4. 最后記得關閉文件描述符 -> 防止資源泄露

in.close()

3.2.3. dict::translate()
3.2.3.1. 查找一個空單詞?

3.2.3.2. 如果沒查到?

3.2.3.3. 如果查到了?

3.3. 字典服務 與 服務器相關聯

肯定是在 UdpServerMain.cc 當中完成的.

首先構建一個字典對象:

然后我們服務器當中需要一個什么類型的回調函數?

注意: 這個參數是非 const 的.

但是我們 dict 當中是(this, string)的, 所以我們用 bind 綁定一下.

之后, 我們再把這個函數傳給我們的服務器即可.

3.4. 測試一下

啟動服務端, 發現是 ok 的:

啟動客戶端, 也是 ok 的:

4. 參考代碼

4.1. 核心代碼

#pragma once#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <functional>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "nocopy.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"using namespace log_ns;static const int gsockfd = -1;
static const uint16_t glocalport = 8888;enum
{SOCKET_ERROR = 1,BIND_ERROR
};// 解耦合: 我們約定, 客戶端給我們發過來的是一個一個的單詞(字符串), 對于這些字符串
// 我們不再讓服務器處理了, 而是讓服務器把這些任務派發給另一個函數完成, 從
// 而實現解耦合(業務與服務器通信解耦)! 
// 服務器: 負責 讀數據 + 發數據. 
// 業務: 解耦, 負責處理數據. 
using func_t = std::function<std::string(std::string)>; 
// 這個就算我們的業務函數, 用包裝器進行了類型包裝, 設計為回調函數! // UdpServer user("192.1.1.1", 8899);
// 一般服務器主要是用來進行網絡數據讀取和寫入的。IO的
// 服務器IO邏輯 和 業務邏輯 解耦
class UdpServer : public nocopy
{
public:UdpServer(func_t func, uint16_t localport = glocalport): _func(func),_sockfd(gsockfd),_localport(localport),_isrunning(false){}void InitServer(){// 1. 創建socket文件_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(FATAL, "socket error\n");exit(SOCKET_ERROR);}LOG(DEBUG, "socket create success, _sockfd: %d\n", _sockfd); // 3// 2. bindstruct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_localport);// local.sin_addr.s_addr = inet_addr(_localip.c_str()); // 1. 需要4字節IP 2. 需要網絡序列的IP -- 暫時local.sin_addr.s_addr = INADDR_ANY; // 服務器端,進行任意IP地址綁定int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(FATAL, "bind error\n");exit(BIND_ERROR);}LOG(DEBUG, "socket bind success\n");}void Start(){_isrunning = true;char inbuffer[1024];while (_isrunning){struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&peer, &len);if (n > 0){InetAddr addr(peer);inbuffer[n] = 0;// 一個一個的單詞std::cout << "[" << addr.Ip() << ":" << addr.Port() << "]# " << inbuffer << std::endl;std::string result = _func(inbuffer); // 把任務交給_func函數, 讓_func函數處理, 處理完了我們服務器再給他發過去. sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr *)&peer, len);}else{std::cout << "recvfrom ,  error" << std::endl;}}}~UdpServer(){if (_sockfd > gsockfd)::close(_sockfd);}private:int _sockfd;uint16_t _localport;// std::string _localip; // TODO:后面專門要處理一下這個IPbool _isrunning;func_t _func; // 業務 -> 回調函數. // 這樣寫回調的好處就是服務器不需要關心業務如何處理, 只需要了解服務器需要給// 業務什么東西, 然后服務器需要讓業務返回什么東西即可. 
};
#include "UdpServer.hpp"
#include "Dict.hpp"#include <memory>// ./udp_server local-port
// ./udp_server 8888
int main(int argc, char *argv[])
{if(argc != 2){std::cerr << "Usage: " << argv[0] << " local-port" << std::endl;exit(0);}uint16_t port = std::stoi(argv[1]);EnableScreen();Dict dict("./dict.txt"); // 構建字典 + 配置文件路徑進行配置加載 func_t translate = std::bind(&Dict::Translate, &dict, std::placeholders::_1); // 綁定成為指定類型: string (string). std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(translate, port); //C++14的標準 -> 這樣的好處就是, 業務與服務器端的解耦, 這樣你想換一個業務, 只需要修改一下業務函數指向即可, 其他則不用修改! usvr->InitServer();usvr->Start();return 0;
}
#pragma once
#include <iostream>
#include <string>
#include <fstream>
#include <unordered_map>
#include <unistd.h>
#include "Log.hpp"
/*
* 這個字典類, 是一個業務, 用來把服務器交給我們的單詞, 翻譯成漢語給他返回去. 
* 其中, 我們的字典需要從文件中加載對應的單詞數據, 是一個文件級別的數據, 而非
* 內存級別的數據. 
*/using namespace log_ns;
const static std::string sep = ": "; // 定義分隔符. // sad: 悲傷的class Dict
{
private:void LoadDict(const std::string &path){std::ifstream in(path); // 打開文件if (!in.is_open()) // 打開失敗了就不可能完成任務, 直接exit! {LOG(FATAL, "open %s failed!\n", path.c_str());exit(1);}std::string line;while (std::getline(in, line)) // 注意: cpp中的getline是重載了bool類型哦~ {LOG(DEBUG, "load info: %s , success\n", line.c_str());if (line.empty()) // 避免空行. continue;auto pos = line.find(sep); if (pos == std::string::npos) // 避免沒有": "的情況. continue;std::string key = line.substr(0, pos);if (key.empty()) // 如果發現key值是空, 直接忽略. continue;std::string value = line.substr(pos + sep.size());if (value.empty()) // 如果發現value值是空, 直接忽略. continue;_dict.insert(std::make_pair(key, value)); // 用哈希將數據組織起來! }LOG(INFO, "load %s done\n", path.c_str());in.close();}public:// 構造: 構造的時候 自動 把所有的文件屬性加載到哈希表中去! Dict(const std::string &dict_path) : _dict_path(dict_path){LoadDict(_dict_path); // 自動加載資源到哈希表組織起來! }// 翻譯std::string Translate(std::string word){if(word.empty()) return "None"; // 如果沒有key值, 咱們就返回"None"auto iter = _dict.find(word); if(iter == _dict.end()) return "None"; // 沒有查到, 咱們就返回"None"else return iter->second;}~Dict(){}private:std::unordered_map<std::string, std::string> _dict; // 對于這個字典, 我們加載進來是用哈希表進行映射組織的! std::string _dict_path; // 外界給你文件路徑, 來讀取對應的單詞數據. 
};
apple: 蘋果
banana: 香蕉
cat: 貓
dog: 狗
book: 書
pen: 筆
happy: 快樂的
sad: 悲傷的
run: 跑
jump: 跳
teacher: 老師
student: 學生
car: 汽車
bus: 公交車
love: 愛
hate: 恨
hello: 你好
goodbye: 再見
summer: 夏天
winter: 冬天

4.2. 其他代碼

#pragma once#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>class InetAddr
{
private:void ToHost(const struct sockaddr_in &addr){_port = ntohs(addr.sin_port);_ip = inet_ntoa(addr.sin_addr);}public:InetAddr(const struct sockaddr_in &addr):_addr(addr){ToHost(addr);}std::string Ip(){return _ip;}uint16_t Port(){return _port;}~InetAddr(){}private:std::string _ip;uint16_t _port;struct sockaddr_in _addr;
};
#pragma once#include <pthread.h>class LockGuard
{
public:LockGuard(pthread_mutex_t *mutex):_mutex(mutex){pthread_mutex_lock(_mutex);}~LockGuard(){pthread_mutex_unlock(_mutex);}
private:pthread_mutex_t *_mutex;
};
#pragma once#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <ctime>
#include <cstdarg>
#include <fstream>
#include <cstring>
#include <pthread.h>
#include "LockGuard.hpp"namespace log_ns
{enum{DEBUG = 1,INFO,WARNING,ERROR,FATAL};std::string LevelToString(int level){switch (level){case DEBUG:return "DEBUG";case INFO:return "INFO";case WARNING:return "WARNING";case ERROR:return "ERROR";case FATAL:return "FATAL";default:return "UNKNOWN";}}std::string GetCurrTime(){time_t now = time(nullptr);struct tm *curr_time = localtime(&now);char buffer[128];snprintf(buffer, sizeof(buffer), "%d-%02d-%02d %02d:%02d:%02d",curr_time->tm_year + 1900,curr_time->tm_mon + 1,curr_time->tm_mday,curr_time->tm_hour,curr_time->tm_min,curr_time->tm_sec);return buffer;}class logmessage{public:std::string _level;pid_t _id;std::string _filename;int _filenumber;std::string _curr_time;std::string _message_info;};#define SCREEN_TYPE 1
#define FILE_TYPE 2const std::string glogfile = "./log.txt";pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER;// log.logMessage("", 12, INFO, "this is a %d message ,%f, %s hellwrodl", x, , , );class Log{public:Log(const std::string &logfile = glogfile) : _logfile(logfile), _type(SCREEN_TYPE){}void Enable(int type){_type = type;}void FlushLogToScreen(const logmessage &lg){printf("[%s][%d][%s][%d][%s] %s",lg._level.c_str(),lg._id,lg._filename.c_str(),lg._filenumber,lg._curr_time.c_str(),lg._message_info.c_str());}void FlushLogToFile(const logmessage &lg){std::ofstream out(_logfile, std::ios::app);if (!out.is_open())return;char logtxt[2048];snprintf(logtxt, sizeof(logtxt), "[%s][%d][%s][%d][%s] %s",lg._level.c_str(),lg._id,lg._filename.c_str(),lg._filenumber,lg._curr_time.c_str(),lg._message_info.c_str());out.write(logtxt, strlen(logtxt));out.close();}void FlushLog(const logmessage &lg){// 加過濾邏輯 --- TODOLockGuard lockguard(&glock);switch (_type){case SCREEN_TYPE:FlushLogToScreen(lg);break;case FILE_TYPE:FlushLogToFile(lg);break;}}void logMessage(std::string filename, int filenumber, int level, const char *format, ...){logmessage lg;lg._level = LevelToString(level);lg._id = getpid();lg._filename = filename;lg._filenumber = filenumber;lg._curr_time = GetCurrTime();va_list ap;va_start(ap, format);char log_info[1024];vsnprintf(log_info, sizeof(log_info), format, ap);va_end(ap);lg._message_info = log_info;// 打印出來日志FlushLog(lg);}~Log(){}private:int _type;std::string _logfile;};Log lg;#define LOG(Level, Format, ...)                                        \do                                                                 \{                                                                  \lg.logMessage(__FILE__, __LINE__, Level, Format, ##__VA_ARGS__); \} while (0)
#define EnableScreen()          \do                          \{                           \lg.Enable(SCREEN_TYPE); \} while (0)
#define EnableFILE()          \do                        \{                         \lg.Enable(FILE_TYPE); \} while (0)
};
.PHONY:all
all:udpserver udpclientudpserver:UdpServerMain.ccg++ -o $@ $^ -std=c++14
udpclient:UdpClientMain.ccg++ -o $@ $^ -std=c++14.PHONY:clean
clean:rm -rf udpserver udpclient
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>// 客戶端在未來一定要知道服務器的IP地址和端口號
// ./udp_client server-ip server-port
// ./udp_client 127.0.0.1 8888
int main(int argc, char *argv[])
{if(argc != 3){std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;exit(0);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){std::cerr << "create socket error" << std::endl;exit(1);}// client的端口號,一般不讓用戶自己設定,而是讓client OS隨機選擇?怎么選擇,什么時候選擇呢?// client 需要 bind它自己的IP和端口, 但是client 不需要 “顯示” bind它自己的IP和端口, // client 在首次向服務器發送數據的時候,OS會自動給client bind它自己的IP和端口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());while(1){std::string line;std::cout << "Please Enter# ";std::getline(std::cin, line);// std::cout << "line message is@ " << line << std::endl;int n = sendto(sockfd, line.c_str(), line.size(), 0, (struct sockaddr*)&server, sizeof(server)); // 你要發送消息,你得知道你要發給誰啊!if(n > 0){struct sockaddr_in temp;socklen_t len = sizeof(temp);char buffer[1024];int m = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&temp, &len);if(m > 0){buffer[m] = 0;std::cout << buffer << std::endl;}else{std::cout << "recvfrom error" << std::endl;break;}}else{std::cout << "sendto error" << std::endl;break;}}::close(sockfd);return 0;
}
#pragma onceclass nocopy
{
public:nocopy(){}~nocopy(){}nocopy(const nocopy&) = delete;const nocopy& operator=(const nocopy&) = delete;
};

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

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

相關文章

七種驅動器綜合對比——《器件手冊--驅動器》

九、驅動器 名稱 功能與作用 工作原理 優勢 應用 隔離式柵極驅動器 隔離式柵極驅動器用于控制功率晶體管&#xff08;如MOSFET、IGBT、SiC或GaN等&#xff09;的開關&#xff0c;其核心功能是將控制信號從低壓側傳輸到高壓側的功率器件柵極&#xff0c;同時在輸入和輸出之…

EM儲能網關ZWS智慧儲能云應用(8) — 電站差異化支持

面對不同項目、種類繁多的儲能產品&#xff0c;如何在儲能云平臺上進行電站差異化支持尤為關鍵&#xff0c;ZWS智慧儲能云從多方面支持儲能電站差異化。 簡介 隨著行業發展&#xff0c;市場“內卷”之下&#xff0c;各大儲能企業推陳出新的速度加快。面對不同項目、種類繁多…

圖像預處理-色彩空間補充,灰度化與二值化

一.圖像色彩空間轉換 1.1 HSV顏色空間 HSV顏色空間使用色調&#xff08;Hue&#xff09;、飽和度&#xff08;Saturation&#xff09;和亮度&#xff08;Value&#xff09;三個參數來表示顏色 一般對顏色空間的圖像進行有效處理都是在HSV空間進行的&#xff0c;然后對于基本…

Midnight Flag CTF 2025

周末還是三個比賽&#xff0c;可惜不好弄。不是遠端連不上就是遠端打不開。再有就是太難了。 Crypto ABC 這個題還是不算難的。給了兩個30位數的平方和&#xff0c;并且pu1*baser0,qu2*baser1其中r 都很小&#xff0c;可以copper。 只是sage里的two_squres不管用&#xff0…

深度學習--激活函數

激活函數通過計算加權和并加上偏置來確定神經元是否應該倍激活&#xff0c;它們將輸入信號轉換為輸出的可微運算。大多數激活函數都是非線性的&#xff0c;由于激活函數是深度學習的基礎&#xff0c;下面簡要介紹一些常見的激活函數。 1 RelU函數 最受歡迎的激活函數是修正線性…

深入解析 OrdinalEncoder 與 OneHotEncoder:核心區別與實戰應用

標題&#xff1a;深入解析 OrdinalEncoder 與 OneHotEncoder&#xff1a;核心區別與實戰應用 摘要&#xff1a; 本文詳細探討了機器學習中類別特征編碼的兩種核心方法——OrdinalEncoder 和 OneHotEncoder。通過對比兩者的功能、特點、適用場景及代碼實現&#xff0c;幫助讀者…

CTF web入門之命令執行 完整版

web29 文件名過濾 由于flag被過濾,需要進行文件名繞過,有以下幾種方法: 1.通配符繞過 fla?.* 2.反斜杠繞過 fl\ag.php 3.雙引號繞過 fl’‘ag’.php 還有特殊變量$1、內聯執行等 此外 讀取文件利用cat函數,輸出利用system、passthru 、echo echo `nl flag.php`; ec…

【Linux實踐系列】:用c/c++制作一個簡易的進程池

&#x1f525; 本文專欄&#xff1a;Linux Linux實踐項目 &#x1f338;作者主頁&#xff1a;努力努力再努力wz &#x1f4aa; 今日博客勵志語錄&#xff1a; 人生沒有標準答案&#xff0c;你的錯題本也能寫成傳奇。 ★★★ 本文前置知識&#xff1a; 匿名管道 1.前置知識回顧…

2.2 函數返回值

1.回顧def def sum(x,y): return xy res sum(10,20) #調用函數 print(res) 2.函數的三個重要屬性 -函數的類型&#xff1a;function -函數的ID&#xff1a;16進制的整數數值 -函數的值&#xff1a;封裝在函數中的數據和代碼 # - 函數是一塊內存空間&#xff0c;通過…

【3GPP核心網】【5G】精講5G網絡語音業務系統架構

1. 歡迎大家訂閱和關注,精講3GPP通信協議(2G/3G/4G/5G/IMS)知識點,專欄會持續更新中.....敬請期待! 目錄 1. 音視頻業務 2. 消息類業務 SMS over IMS SMS over NAS 3. 互聯互通架構 3.1 音視頻業務互通場景 3.2 5G 用戶與 5G 用戶互通 3.3 5G 用戶與 4G 用戶的互通…

系統環境變量有什么實際作用,為什么要配置它

系統環境變量有什么實際作用,為什么要配置它 系統環境變量具有以下重要實際作用: 指定程序路徑:操作系統通過環境變量來知曉可執行文件、庫文件等的存儲位置例如,當你在命令提示符或終端中輸入一個命令時,系統會根據環境變量PATH中指定的路徑去查找對應的可執行文件。如果…

qt/C++面試題自用學習(更新中)

最近在找工作…面試中遇到了的問題總以為自己會但回答的時候磕磕巴巴&#xff0c;覺得還是要總結一下&#xff1a; vector和list的區別 vector list 底層數據結構 基于動態數組實現&#xff0c;元素在內存中連續存儲 基于雙向鏈表實現&#xff0c;元素在內存中非連續存儲&…

Day09【基于Tripletloss實現的簡單意圖識別對話系統】

基于Tripletloss實現的表示型文本匹配 目標數據準備參數配置數據處理Triplet Loss目標Triplet Loss計算公式公式說明 模型構建網絡結構設計網絡訓練目標損失函數設計 主程序推理預測類初始化加載問答知識庫文本向量化知識庫查詢主程序main測試測試效果 參考博客 目標 在此之前…

說說什么是冪等性?

大家好&#xff0c;我是鋒哥。今天分享關于【說說什么是冪等性&#xff1f;】面試題。希望對大家有幫助&#xff1b; 說說什么是冪等性&#xff1f; 1000道 互聯網大廠Java工程師 精選面試題-Java資源分享網 冪等性&#xff08;Idempotence&#xff09; 是指在某些操作或請求…

【自相關】全局 Moran’s I 指數

自相關&#xff08;Autocorrelation&#xff09;&#xff0c;也稱為序列相關性&#xff0c;指的是同一變量在不同時間或空間點的值之間的關系。簡而言之&#xff0c;自相關就是一個變量與自身在不同位置或時間點的相關性 自相關&#xff1a;針對同一屬性之間進行分析相關性 本…

【C#】Html轉Pdf,Spire和iTextSharp結合,.net framework 4.8

&#x1f339;歡迎來到《小5講堂》&#x1f339; &#x1f339;這是《C#》系列文章&#xff0c;每篇文章將以博主理解的角度展開講解。&#x1f339; &#x1f339;溫馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不對之處望指正&#xff01;&#…

KrillinAI:視頻跨語言傳播的一站式AI解決方案

引言 在全球內容創作領域&#xff0c;跨語言傳播一直是內容創作者面臨的巨大挑戰。傳統的視頻本地化流程繁瑣&#xff0c;涉及多個環節和工具&#xff0c;不僅耗時耗力&#xff0c;還常常面臨質量不穩定的問題。隨著大語言模型(LLM)技術的迅猛發展&#xff0c;一款名為Krillin…

AllDup:高效管理重復文件

AllDup 是一款免費高效的重復文件管理工具&#xff0c;專為 Windows 系統設計&#xff0c;支持快速掃描并清理冗余文件&#xff0c;優化存儲空間。它通過智能算法識別重復內容&#xff0c;覆蓋文本、圖片、音頻、視頻等常見文件類型?。軟件提供便攜版與安裝版&#xff0c;無需…

C++進程間通信開發實戰:高效解決項目中的IPC問題

C進程間通信開發實戰&#xff1a;高效解決項目中的IPC問題 在復雜的軟件項目中&#xff0c;進程間通信&#xff08;Inter-Process Communication, IPC&#xff09;是實現模塊化、提高系統性能與可靠性的關鍵技術之一。C作為一門高性能的編程語言&#xff0c;廣泛應用于需要高效…