目录
[V2 版本 - DictServer](#V2 版本 - DictServer)
[V3 版本 - DictServer封装版](#V3 版本 - DictServer封装版)
UDP网络编程
V2 版本 - DictServer
Dict.hpp
#pragma once #include <iostream> #include <fstream> #include <string> #include <unordered_map> #include "Log.hpp" #include "InetAddr.hpp" const std::string defaultdict = "./dictionary.txt"; const std::string sep = ": "; using namespace LogModule; class Dict { public: Dict(const std::string &path = defaultdict) : _dict_path(path) { } //加载字典 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)) { // "apple: 苹果" 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; } 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; } ~Dict() { } private: std::string _dict_path; // 路径+文件名 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: 冬天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: InetAddr(struct sockaddr_in &addr) : _addr(addr) { _port = ntohs(_addr.sin_port); // 从网络中拿到的!网络序列 _ip = inet_ntoa(_addr.sin_addr); // 4字节网络风格的IP -> 点分十进制的字符串风格的IP } uint16_t Port() {return _port;} std::string Ip() {return _ip;} ~InetAddr() {} private: struct sockaddr_in _addr; std::string _ip; uint16_t _port; };Log.hpp
#ifndef __LOG_HPP__ #define __LOG_HPP__ #include <iostream> #include <cstdio> #include <string> #include <filesystem> //C++17 #include <sstream> #include <fstream> #include <memory> #include <ctime> #include <unistd.h> #include "Mutex.hpp" namespace LogModule { using namespace MutexModule; const std::string gsep = "\r\n"; // 策略模式,C++多态特性 // 2. 刷新策略 a: 显示器打印 b:向指定的文件写入 // 刷新策略基类 class LogStrategy { public: ~LogStrategy() = default; virtual void SyncLog(const std::string &message) = 0; }; // 显示器打印日志的策略 : 子类 class ConsoleLogStrategy : public LogStrategy { public: ConsoleLogStrategy() { } void SyncLog(const std::string &message) override { LockGuard lockguard(_mutex); std::cout << message << gsep; } ~ConsoleLogStrategy() { } private: Mutex _mutex; }; // 文件打印日志的策略 : 子类 const std::string defaultpath = "./log"; const std::string defaultfile = "my.log"; class FileLogStrategy : public LogStrategy { public: FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile) : _path(path), _file(file) { LockGuard lockguard(_mutex); if (std::filesystem::exists(_path)) { return; } try { std::filesystem::create_directories(_path); } catch (const std::filesystem::filesystem_error &e) { std::cerr << e.what() << '\n'; } } void SyncLog(const std::string &message) override { LockGuard lockguard(_mutex); std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file; // "./log/" + "my.log" std::ofstream out(filename, std::ios::app); // 追加写入的 方式打开 if (!out.is_open()) { return; } out << message << gsep; out.close(); } ~FileLogStrategy() { } private: std::string _path; // 日志文件所在路径 std::string _file; // 日志文件本身 Mutex _mutex; }; // 形成一条完整的日志&&根据上面的策略,选择不同的刷新方式 // 1. 形成日志等级 enum class LogLevel { DEBUG, INFO, WARNING, ERROR, FATAL }; std::string Level2Str(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 "UNKNOWN"; } } std::string GetTimeStamp() { time_t curr = time(nullptr); struct tm curr_tm; localtime_r(&curr, &curr_tm); char timebuffer[128]; snprintf(timebuffer, sizeof(timebuffer),"%4d-%02d-%02d %02d:%02d:%02d", curr_tm.tm_year+1900, curr_tm.tm_mon+1, curr_tm.tm_mday, curr_tm.tm_hour, curr_tm.tm_min, curr_tm.tm_sec ); return timebuffer; } // 1. 形成日志 && 2. 根据不同的策略,完成刷新 class Logger { public: Logger() { EnableConsoleLogStrategy(); } void EnableFileLogStrategy() { _fflush_strategy = std::make_unique<FileLogStrategy>(); } void EnableConsoleLogStrategy() { _fflush_strategy = std::make_unique<ConsoleLogStrategy>(); } // 表示的是未来的一条日志 class LogMessage { public: LogMessage(LogLevel &level, std::string &src_name, int line_number, Logger &logger) : _curr_time(GetTimeStamp()), _level(level), _pid(getpid()), _src_name(src_name), _line_number(line_number), _logger(logger) { // 日志的左边部分,合并起来 std::stringstream ss; ss << "[" << _curr_time << "] " << "[" << Level2Str(_level) << "] " << "[" << _pid << "] " << "[" << _src_name << "] " << "[" << _line_number << "] " << "- "; _loginfo = ss.str(); } // LogMessage() << "hell world" << "XXXX" << 3.14 << 1234 template <typename T> LogMessage &operator<<(const T &info) { // a = b = c =d; // 日志的右半部分,可变的 std::stringstream ss; ss << info; _loginfo += ss.str(); return *this; } ~LogMessage() { if (_logger._fflush_strategy) { _logger._fflush_strategy->SyncLog(_loginfo); } } private: std::string _curr_time; LogLevel _level; pid_t _pid; std::string _src_name; int _line_number; std::string _loginfo; // 合并之后,一条完整的信息 Logger &_logger; }; // 这里故意写成返回临时对象 LogMessage operator()(LogLevel level, std::string name, int line) { return LogMessage(level, name, line, *this); } ~Logger() { } private: std::unique_ptr<LogStrategy> _fflush_strategy; }; // 全局日志对象 Logger logger; // 使用宏,简化用户操作,获取文件名和行号 #define LOG(level) logger(level, __FILE__, __LINE__) #define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy() #define Enable_File_Log_Strategy() logger.EnableFileLogStrategy() } #endifMakefile
.PHONY:all all:udpclient udpserver udpclient:UdpClient.cc g++ -o $@ $^ -std=c++17 #-static udpserver:UdpServer.cc g++ -o $@ $^ -std=c++17 .PHONY:clean clean: rm -f udpclient udpserverMutex.hpp
#pragma once #include <iostream> #include <pthread.h> namespace MutexModule { class Mutex { public: Mutex() { pthread_mutex_init(&_mutex, nullptr); } void Lock() { int n = pthread_mutex_lock(&_mutex); (void)n; } void Unlock() { int n = pthread_mutex_unlock(&_mutex); (void)n; } ~Mutex() { pthread_mutex_destroy(&_mutex); } pthread_mutex_t *Get() { return &_mutex; } private: pthread_mutex_t _mutex; }; class LockGuard { public: LockGuard(Mutex &mutex):_mutex(mutex) { _mutex.Lock(); } ~LockGuard() { _mutex.Unlock(); } private: Mutex &_mutex; }; }
#include <iostream> #include <string> #include <cstring> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h> // ./udpclient server_ip server_port int main(int argc, char *argv[]) { if (argc != 3) { std::cerr << "Usage:" << argv[0] << " server_ip server_port" << std::endl; return 1; } std::string server_ip = argv[1]; uint16_t server_port = std::stoi(argv[2]); // 1.创建socket int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { std::cerr << "socket error" << std::endl; return 2; } // 2.本地的ip和端口是什么?要不要和上面的"文件"关联呢? // 问题:client要不要bind?需要bind // client要不要显式地bind?不要,首次发送消息,OS会自动给client进行bind,OS知道IP,端口号采用随机端口号的方式 // 为什么?一个端口号,只能被一个进程bind,为了避免client端口冲突 // client的端口号是几,不重要,只要是唯一的就行 // 那为什么服务器端需要显式地bind呢?IP和端口必须是众所周知且不能轻易改变的! // 填写服务器信息 struct sockaddr_in server; memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(server_port); server.sin_addr.s_addr = inet_addr(server_ip.c_str()); 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; std::cout<<buffer<<std::endl; } } return 0; }
#include <iostream> #include <memory> #include "Dict.hpp" // 翻译的功能 #include "UdpServer.hpp" // 网络通信的功能 // 仅仅是用来进行测试的 std::string defaulthandler(const std::string &message) { std::string hello = "hello, "; hello += message; return hello; } // 需求 // 1.翻译系统,字符串当成英文单词,把英文单词翻译成汉语 // 2.基于文件来做 // ./udpserver port int main(int argc, char *argv[]) { if (argc != 2) { std::cerr << "Usage:" << argv[0] << " port" << std::endl; return 1; } // std::string ip = argv[1]; uint16_t port = std::stoi(argv[1]); Enable_Console_Log_Strategy(); //1.字典对象提供翻译功能 Dict dict; dict.LoadDict(); //2.网络服务器对象,提供通信功能 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; }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" using namespace LogModule; using func_t = std::function<std::string(const std::string&, InetAddr&)>; const int defaultfd = -1; // 你是为了进行网络通信的! class UdpServer { public: UdpServer(uint16_t port, func_t func) : _sockfd(defaultfd), //_ip(ip), _port(port), _isrunning(false), _func(func) { } // 初始化 void Init() { // 1. 创建套接字 _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. 绑定socket信息,ip和端口, ip(比较特殊,后续解释) // 2.1 填充sockaddr_in结构体 struct sockaddr_in local; bzero(&local, sizeof(local)); local.sin_family = AF_INET; // 我会不会把我的IP地址和端口号发送给对方? // IP信息和端口信息,一定要发送到网络! // 本地格式->网络序列 local.sin_port = htons(_port); // IP也是如此,1. IP转成4字节 2. 4字节转成网络序列 -> in_addr_t inet_addr(const char *cp); // local.sin_addr.s_addr = inet_addr(_ip.c_str());//TODO local.sin_addr.s_addr = INADDR_ANY; // INADDR_ANY这个宏值就是0 // 完成将本地套接字进行绑定 // 那为什么服务器端需要显式地bind呢?IP和端口必须是众所周知且不能轻易改变的! 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.收消息,client为什么要整个服务器发送消息啊?不就是让服务端处理数据。 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); // LOG(LogLevel::DEBUG)<<"["<<peer_ip<<":"<<peer_port<<"]# "<<buffer;//1.消息内容 2.谁发的 // 2.发消息 // std::string echo_string = "server echo@ "; // echo_string += buffer; sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr *)&peer, len); } } } ~UdpServer() { } private: int _sockfd; uint16_t _port; // std::string _ip; // 用的是字符串风格,点分十进制 "192.168.1.1" bool _isrunning; func_t _func; // 服务器的回调函数,用来进行对数据进行处理 }; root@iZ5waahoxw3q2bZ:~/linux-learning/linux/26-7-19# ./udpclient 121.40.196.90 8080 Please Enter# ni^H^H None Please Enter# 你好 None Please Enter# 苹果 None Please Enter# apply None Please Enter# pen 笔 Please Enter# ^[[A^H None Please Enter# apple 苹果 Please Enter# 苹果 None Please Enter# root@iZ5waahoxw3q2bZ:~/linux-learning/linux/26-7-19# make g++ -o udpclient UdpClient.cc -std=c++17 #-static g++ -o udpserver UdpServer.cc -std=c++17 root@iZ5waahoxw3q2bZ:~/linux-learning/linux/26-7-19# ./udpserver 8080 [2026-07-19 18:27:48] [DEBUG] [449692] [Dict.hpp] [49] - 加载: apple: 苹果 [2026-07-19 18:27:48] [DEBUG] [449692] [Dict.hpp] [49] - 加载: banana: 香蕉 [2026-07-19 18:27:48] [DEBUG] [449692] [Dict.hpp] [49] - 加载: cat: 猫 [2026-07-19 18:27:48] [DEBUG] [449692] [Dict.hpp] [49] - 加载: dog: 狗 [2026-07-19 18:27:48] [DEBUG] [449692] [Dict.hpp] [49] - 加载: book: 书 [2026-07-19 18:27:48] [DEBUG] [449692] [Dict.hpp] [49] - 加载: pen: 笔 [2026-07-19 18:27:48] [DEBUG] [449692] [Dict.hpp] [49] - 加载: happy: 快乐的 [2026-07-19 18:27:48] [DEBUG] [449692] [Dict.hpp] [49] - 加载: sad: 悲伤的 [2026-07-19 18:27:48] [WARNING] [449692] [Dict.hpp] [44] - 没有有效内容: hello: [2026-07-19 18:27:48] [WARNING] [449692] [Dict.hpp] [44] - 没有有效内容: : 你好 [2026-07-19 18:27:48] [WARNING] [449692] [Dict.hpp] [37] - 解析: 失败 [2026-07-19 18:27:48] [WARNING] [449692] [Dict.hpp] [37] - 解析: 失败 [2026-07-19 18:27:48] [WARNING] [449692] [Dict.hpp] [37] - 解析: 失败 [2026-07-19 18:27:48] [DEBUG] [449692] [Dict.hpp] [49] - 加载: run: 跑 [2026-07-19 18:27:48] [DEBUG] [449692] [Dict.hpp] [49] - 加载: jump: 跳 [2026-07-19 18:27:48] [DEBUG] [449692] [Dict.hpp] [49] - 加载: teacher: 老师 [2026-07-19 18:27:48] [DEBUG] [449692] [Dict.hpp] [49] - 加载: student: 学生 [2026-07-19 18:27:48] [DEBUG] [449692] [Dict.hpp] [49] - 加载: car: 汽车 [2026-07-19 18:27:48] [DEBUG] [449692] [Dict.hpp] [49] - 加载: bus: 公交车 [2026-07-19 18:27:48] [DEBUG] [449692] [Dict.hpp] [49] - 加载: love: 爱 [2026-07-19 18:27:48] [DEBUG] [449692] [Dict.hpp] [49] - 加载: hate: 恨 [2026-07-19 18:27:48] [DEBUG] [449692] [Dict.hpp] [49] - 加载: hello: 你好 [2026-07-19 18:27:48] [DEBUG] [449692] [Dict.hpp] [49] - 加载: goodbye: 再见 [2026-07-19 18:27:48] [DEBUG] [449692] [Dict.hpp] [49] - 加载: summer: 夏天 [2026-07-19 18:27:48] [DEBUG] [449692] [Dict.hpp] [49] - 加载: winter: 冬天 [2026-07-19 18:27:48] [INFO] [449692] [UdpServer.hpp] [41] - socket success, sockfd : 3 [2026-07-19 18:27:48] [INFO] [449692] [UdpServer.hpp] [65] - bind success, sockfd : 3 你好 [2026-07-19 18:28:20] [DEBUG] [449692] [Dict.hpp] [60] - 进入到了翻译模块, [121.40.196.90 : 58997]# ->None [2026-07-19 18:28:23] [DEBUG] [449692] [Dict.hpp] [60] - 进入到了翻译模块, [121.40.196.90 : 58997]# 你好->None [2026-07-19 18:28:28] [DEBUG] [449692] [Dict.hpp] [60] - 进入到了翻译模块, [121.40.196.90 : 58997]# 苹果->None [2026-07-19 18:28:31] [DEBUG] [449692] [Dict.hpp] [60] - 进入到了翻译模块, [121.40.196.90 : 58997]# apply->None [2026-07-19 18:28:50] [DEBUG] [449692] [Dict.hpp] [63] - 进入到了翻译模块, [121.40.196.90 : 58997]# pen->笔 [2026-07-19 18:28:53] [DEBUG->None692] [Dict.hpp] [60] - 进入到了翻译模 [2026-07-19 18:28:56] [DEBUG] [449692] [Dict.hpp] [63] - 进入到了翻译模块, [121.40.196.90 : 58997]# apple->苹果 [2026-07-19 18:29:00] [DEBUG] [449692] [Dict.hpp] [60] - 进入到了翻译模块, [121.40.196.90 : 58997]# 苹果->None


我们所对应的文件描述符,在线程间是共享的
udp是支持全双工的---多线程同时读写
线程将来要执行的任务是---消息转发的任务
route
V3 版本 - DictServer封装版
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: InetAddr(struct sockaddr_in &addr) : _addr(addr) { _port = ntohs(_addr.sin_port); // 从网络中拿到的!网络序列 _ip = inet_ntoa(_addr.sin_addr); // 4字节网络风格的IP -> 点分十进制的字符串风格的IP } uint16_t Port() {return _port;} std::string Ip() {return _ip;} const struct sockaddr_in &NetAddr() { return _addr; } bool operator==(const InetAddr &addr) { return addr._ip == _ip && addr._port == _port; } std::string StringAddr() { return _ip + ":" + std::to_string(_port); } ~InetAddr() {} private: struct sockaddr_in _addr; std::string _ip; uint16_t _port; };Log.hpp
#ifndef __LOG_HPP__ #define __LOG_HPP__ #include <iostream> #include <cstdio> #include <string> #include <filesystem> //C++17 #include <sstream> #include <fstream> #include <memory> #include <ctime> #include <unistd.h> #include "Mutex.hpp" namespace LogModule { using namespace MutexModule; const std::string gsep = "\r\n"; // 策略模式,C++多态特性 // 2. 刷新策略 a: 显示器打印 b:向指定的文件写入 // 刷新策略基类 class LogStrategy { public: ~LogStrategy() = default; virtual void SyncLog(const std::string &message) = 0; }; // 显示器打印日志的策略 : 子类 class ConsoleLogStrategy : public LogStrategy { public: ConsoleLogStrategy() { } void SyncLog(const std::string &message) override { LockGuard lockguard(_mutex); std::cout << message << gsep; } ~ConsoleLogStrategy() { } private: Mutex _mutex; }; // 文件打印日志的策略 : 子类 const std::string defaultpath = "./log"; const std::string defaultfile = "my.log"; class FileLogStrategy : public LogStrategy { public: FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile) : _path(path), _file(file) { LockGuard lockguard(_mutex); if (std::filesystem::exists(_path)) { return; } try { std::filesystem::create_directories(_path); } catch (const std::filesystem::filesystem_error &e) { std::cerr << e.what() << '\n'; } } void SyncLog(const std::string &message) override { LockGuard lockguard(_mutex); std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file; // "./log/" + "my.log" std::ofstream out(filename, std::ios::app); // 追加写入的 方式打开 if (!out.is_open()) { return; } out << message << gsep; out.close(); } ~FileLogStrategy() { } private: std::string _path; // 日志文件所在路径 std::string _file; // 日志文件本身 Mutex _mutex; }; // 形成一条完整的日志&&根据上面的策略,选择不同的刷新方式 // 1. 形成日志等级 enum class LogLevel { DEBUG, INFO, WARNING, ERROR, FATAL }; std::string Level2Str(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 "UNKNOWN"; } } std::string GetTimeStamp() { time_t curr = time(nullptr); struct tm curr_tm; localtime_r(&curr, &curr_tm); char timebuffer[128]; snprintf(timebuffer, sizeof(timebuffer),"%4d-%02d-%02d %02d:%02d:%02d", curr_tm.tm_year+1900, curr_tm.tm_mon+1, curr_tm.tm_mday, curr_tm.tm_hour, curr_tm.tm_min, curr_tm.tm_sec ); return timebuffer; } // 1. 形成日志 && 2. 根据不同的策略,完成刷新 class Logger { public: Logger() { EnableConsoleLogStrategy(); } void EnableFileLogStrategy() { _fflush_strategy = std::make_unique<FileLogStrategy>(); } void EnableConsoleLogStrategy() { _fflush_strategy = std::make_unique<ConsoleLogStrategy>(); } // 表示的是未来的一条日志 class LogMessage { public: LogMessage(LogLevel &level, std::string &src_name, int line_number, Logger &logger) : _curr_time(GetTimeStamp()), _level(level), _pid(getpid()), _src_name(src_name), _line_number(line_number), _logger(logger) { // 日志的左边部分,合并起来 std::stringstream ss; ss << "[" << _curr_time << "] " << "[" << Level2Str(_level) << "] " << "[" << _pid << "] " << "[" << _src_name << "] " << "[" << _line_number << "] " << "- "; _loginfo = ss.str(); } // LogMessage() << "hell world" << "XXXX" << 3.14 << 1234 template <typename T> LogMessage &operator<<(const T &info) { // a = b = c =d; // 日志的右半部分,可变的 std::stringstream ss; ss << info; _loginfo += ss.str(); return *this; } ~LogMessage() { if (_logger._fflush_strategy) { _logger._fflush_strategy->SyncLog(_loginfo); } } private: std::string _curr_time; LogLevel _level; pid_t _pid; std::string _src_name; int _line_number; std::string _loginfo; // 合并之后,一条完整的信息 Logger &_logger; }; // 这里故意写成返回临时对象 LogMessage operator()(LogLevel level, std::string name, int line) { return LogMessage(level, name, line, *this); } ~Logger() { } private: std::unique_ptr<LogStrategy> _fflush_strategy; }; // 全局日志对象 Logger logger; // 使用宏,简化用户操作,获取文件名和行号 #define LOG(level) logger(level, __FILE__, __LINE__) #define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy() #define Enable_File_Log_Strategy() logger.EnableFileLogStrategy() } #endifMakefile
.PHONY:all all:udpclient udpserver udpclient:UdpClient.cc g++ -o $@ $^ -std=c++17 -lpthread -static udpserver:UdpServer.cc g++ -o $@ $^ -std=c++17 .PHONY:clean clean: rm -f udpclient udpserverMutex.hpp
#pragma once #include <iostream> #include <pthread.h> namespace MutexModule { class Mutex { public: Mutex() { pthread_mutex_init(&_mutex, nullptr); } void Lock() { int n = pthread_mutex_lock(&_mutex); (void)n; } void Unlock() { int n = pthread_mutex_unlock(&_mutex); (void)n; } ~Mutex() { pthread_mutex_destroy(&_mutex); } pthread_mutex_t *Get() { return &_mutex; } private: pthread_mutex_t _mutex; }; class LockGuard { public: LockGuard(Mutex &mutex):_mutex(mutex) { _mutex.Lock(); } ~LockGuard() { _mutex.Unlock(); } private: Mutex &_mutex; }; }Route.hpp
#pragma once #include <iostream> #include <string> #include <vector> #include "InetAddr.hpp" #include "Log.hpp" using namespace LogModule; class Route { private: bool IsExist(InetAddr &peer) { for (auto &user : _online_user) { if (user == peer) { return true; } } return false; } void AddUser(InetAddr &peer) { LOG(LogLevel::INFO) << "新增一个在线用户: " << peer.StringAddr(); _online_user.push_back(peer); } void DeleteUser(InetAddr &peer) { for (auto iter = _online_user.begin(); iter != _online_user.end(); iter++) { if (*iter == peer) { LOG(LogLevel::INFO) << "删除一个在线用户:" << peer.StringAddr() << "成功"; _online_user.erase(iter); break; } } } public: Route() { } void MessageRoute(int sockfd, const std::string &message, InetAddr &peer) { if (!IsExist(peer)) { AddUser(peer); } std::string send_message = peer.StringAddr() + "# " + message; // 127.0.0.1:8080# 你好 // TODO for (auto &user : _online_user) { sendto(sockfd, send_message.c_str(), send_message.size(), 0, (const struct sockaddr *)&(user.NetAddr()), sizeof(user.NetAddr())); } // 这个用户一定已经在线了 if (message == "QUIT") { LOG(LogLevel::INFO) << "删除一个在线用户: " << peer.StringAddr(); DeleteUser(peer); } } ~Route() { } private: // 首次给我发消息,等同于登录 std::vector<InetAddr> _online_user; // 在线用户 };Thread.hpp
#ifndef _THREAD_H_ #define _THREAD_H_ #include <iostream> #include <string> #include <pthread.h> #include <cstdio> #include <cstring> #include <functional> namespace ThreadModlue { static uint32_t number = 1; // bug class Thread { using func_t = std::function<void()>; // 暂时这样写,完全够了 private: void EnableDetach() { _isdetach = true; } void EnableRunning() { _isrunning = true; } static void *Routine(void *args) // 属于类内的成员函数,默认包含this指针! { Thread *self = static_cast<Thread *>(args); self->EnableRunning(); if (self->_isdetach) self->Detach(); pthread_setname_np(self->_tid, self->_name.c_str()); self->_func(); // 回调处理 return nullptr; } // bug public: Thread(func_t func) : _tid(0), _isdetach(false), _isrunning(false), res(nullptr), _func(func) { _name = "thread-" + std::to_string(number++); } void Detach() { if (_isdetach) return; if (_isrunning) pthread_detach(_tid); EnableDetach(); } bool Start() { if (_isrunning) return false; int n = pthread_create(&_tid, nullptr, Routine, this); if (n != 0) { return false; } else { return true; } } bool Stop() { if (_isrunning) { int n = pthread_cancel(_tid); if (n != 0) { return false; } else { _isrunning = false; return true; } } return false; } void Join() { if (_isdetach) { return; } int n = pthread_join(_tid, &res); if (n != 0) { } else { } } pthread_t Id() { return _tid; } ~Thread() { } private: pthread_t _tid; std::string _name; bool _isdetach; bool _isrunning; void *res; func_t _func; }; } #endif
#include <iostream> #include <string> #include <cstring> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h> #include "Thread.hpp" int sockfd = 0; std::string server_ip; uint16_t server_port = 0; pthread_t id; using namespace ThreadModlue; void Recv() { while (true) { 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; std::cerr << buffer << std::endl; // 2 } } } void Send() { struct sockaddr_in server; memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(server_port); server.sin_addr.s_addr = inet_addr(server_ip.c_str()); const std::string online = "inline"; sendto(sockfd, online.c_str(), online.size(), 0, (struct sockaddr *)&server, sizeof(server)); while (true) { std::string input; std::cout << "Please Enter# "; // 1 std::getline(std::cin, input); // 0 int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server)); (void)n; if (input == "QUIT") { pthread_cancel(id); break; } } } // client 我们也要做多线程改造 // ./udpclient server_ip server_port int main(int argc, char *argv[]) { if (argc != 3) { std::cerr << "Usage:" << argv[0] << " server_ip server_port" << std::endl; return 1; } server_ip = argv[1]; server_port = std::stoi(argv[2]); // 1.创建socket sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { std::cerr << "socket error" << std::endl; return 2; } // 2. 创建线程 Thread recver(Recv); Thread sender(Send); recver.Start(); sender.Start(); id = recver.Id(); recver.Join(); sender.Join(); // 2. 本地的ip和端口是什么?要不要和上面的"文件"关联呢? // 问题:client要不要bind?需要bind. // client要不要显式的bind?不要!!首次发送消息,OS会自动给client进行bind,OS知道IP,端口号采用随机端口号的方式 // 为什么?一个端口号,只能被一个进程bind,为了避免client端口冲突 // client端的端口号是几,不重要,只要是唯一的就行! // 填写服务器信息 return 0; }
#include <iostream> #include <memory> #include "Route.hpp" #include "UdpServer.hpp" // 网络通信的功能 // 仅仅是用来进行测试的 std::string defaulthandler(const std::string &message) { std::string hello = "hello, "; hello += message; return hello; } // 需求 // 1.翻译系统,字符串当成英文单词,把英文单词翻译成汉语 // 2.基于文件来做 using task_t = std::function<void()>; // ./udpserver port int main(int argc, char *argv[]) { if (argc != 2) { std::cerr << "Usage:" << argv[0] << " port" << std::endl; return 1; } // std::string ip = argv[1]; uint16_t port = std::stoi(argv[1]); Enable_Console_Log_Strategy(); //1.路由服务 Route r; // 2. 网络服务器对象,提供通信功能 std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&r](int sockfd, const std::string &message, InetAddr&peer){ r.MessageRoute(sockfd, message, peer); }); usvr->Init(); usvr->Start(); return 0; }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<void(int sockfd, const std::string&, InetAddr&)>; const int defaultfd = -1; // 你是为了进行网络通信的! class UdpServer { public: UdpServer(uint16_t port, func_t func) : _sockfd(defaultfd), //_ip(ip), _port(port), _isrunning(false), _func(func) { } // 初始化 void Init() { // 1. 创建套接字 _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. 绑定socket信息,ip和端口, ip(比较特殊,后续解释) // 2.1 填充sockaddr_in结构体 struct sockaddr_in local; bzero(&local, sizeof(local)); local.sin_family = AF_INET; // 我会不会把我的IP地址和端口号发送给对方? // IP信息和端口信息,一定要发送到网络! // 本地格式->网络序列 local.sin_port = htons(_port); // IP也是如此,1. IP转成4字节 2. 4字节转成网络序列 -> in_addr_t inet_addr(const char *cp); // local.sin_addr.s_addr = inet_addr(_ip.c_str());//TODO local.sin_addr.s_addr = INADDR_ANY; // INADDR_ANY这个宏值就是0 // 完成将本地套接字进行绑定 // 那为什么服务器端需要显式地bind呢?IP和端口必须是众所周知且不能轻易改变的! 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.收消息,client为什么要整个服务器发送消息啊?不就是让服务端处理数据。 ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len); if (s > 0) { InetAddr client(peer); buffer[s] = 0; // TODO _func(_sockfd, buffer, client); // LOG(LogLevel::DEBUG)<<"["<<peer_ip<<":"<<peer_port<<"]# "<<buffer;//1.消息内容 2.谁发的 // 2.发消息 // std::string echo_string = "server echo@ "; // echo_string += buffer; //sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr *)&peer, len); } } } ~UdpServer() { } private: int _sockfd; uint16_t _port; // std::string _ip; // 用的是字符串风格,点分十进制 "192.168.1.1" bool _isrunning; func_t _func; // 服务器的回调函数,用来进行对数据进行处理 }; root@iZ5waahoxw3q2bZ:~/linux-learning/linux/26-7-19/3.ChatServer# make g++ -o udpclient UdpClient.cc -std=c++17 -lpthread -static g++ -o udpserver UdpServer.cc -std=c++17 root@iZ5waahoxw3q2bZ:~/linux-learning/linux/26-7-19/3.ChatServer# ./udpserver 8080 [2026-07-20 09:35:59] [INFO] [454000] [UdpServer.hpp] [42] - socket success, sockfd : 3 [2026-07-20 09:35:59] [INFO] [454000] [UdpServer.hpp] [66] - bind success, sockfd : 3 [2026-07-20 09:36:29] [INFO] [454000] [Route.hpp] [25] - 新增一个在线用户: 127.0.0.1:46808 root@iZ5waahoxw3q2bZ:~/linux-learning/linux/26-7-19/3.ChatServer# ./udpclient 127.0.0.1 8080 Please Enter# 127.0.0.1:46808# inline hjdsh Please Enter# 127.0.0.1:46808# hjdsh dsjdjsajkxzcmn Please Enter# 127.0.0.1:46808# dsjdjsajkxzcmn Please Enter# Please Enter# Please Enter# Please Enter# Please Enter# 你好 Please Enter# 127.0.0.1:46808# 你好 789 Please Enter# 127.0.0.1:46808# 789
感谢你的观看,期待我们下次再见!