0


【Linux网络编程】第四弹---构建UDP服务器与字典翻译系统:源码结构与关键组件解析

✨个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】【Linux网络编程】

上一弹我们能够完成客户端与服务端的正常通信,但是我们在实际生活中不仅仅是要进行通信,还需要完成某种功能,此弹实现一个英文翻译成中文版服务端!!

注意:此弹内容的实现是在上一弹的原始代码基础上修改的!!

1、UdpServer.hpp

Server类的基本实现没变,但是要增加实现中英文翻译成中文功能的执行函数(即函数类型成员变量),并适当调整构造函数和启动函数!!!

1.1、函数对象声明

此处需要实现一个英文翻译成中文的执行函数,因此函数参数是一个字符串,返回值也是一个字符串!!!

  1. // 声明函数对象
  2. using func_t = std::function<std::string(std::string)>;

1.2、Server类基本结构

基本结构与上一弹的Server类基本一致,只增加了函数对象类型!!!

  1. class UdpServer : public nocopy
  2. {
  3. public:
  4. UdpServer(func_t func,uint16_t localport = glocalport);
  5. void InitServer();
  6. void Start();
  7. ~UdpServer();
  8. private:
  9. int _sockfd; // 文件描述符
  10. uint16_t _localport; // 端口号
  11. bool _isrunning;
  12. func_t _func; // 执行相应函数
  13. };

1.3、构造函数

构造函数只需增加一个函数对象参数,初始化列表初始化变量即可!!!

  1. UdpServer(func_t func,uint16_t localport = glocalport)
  2. : _func(func), _sockfd(gsockfd), _localport(localport), _isrunning(false)
  3. {
  4. }

1.4、Start()

上一弹的Start()函数是先接收客户端的消息,然后将客户端的消息发送回去此弹接收客户端的英文单词,然后服务端翻译成中文,然后将中文发送回去!!!

  1. void Start()
  2. {
  3. _isrunning = true;
  4. char inbuffer[1024];
  5. while (_isrunning)
  6. {
  7. // sleep(1);
  8. struct sockaddr_in peer;
  9. socklen_t len = sizeof(peer);
  10. // 接收客户端的英文单词
  11. ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&peer, &len);
  12. if (n > 0)
  13. {
  14. InetAddr addr(peer);
  15. inbuffer[n] = 0;
  16. // 一个一个的单词
  17. std::cout << "[" << addr.Ip() << ":" << addr.Port() << "]# " << inbuffer << std::endl;
  18. std::string result = _func(inbuffer); // 执行翻译功能
  19. // 将翻译的中文结果发回客户端
  20. sendto(_sockfd,result.c_str(),result.size(),0,(struct sockaddr *)&peer,len);
  21. }
  22. }
  23. }

2、Dict.hpp

Dict类执行加载字典文件和执行翻译的功能!!

2.1、基本结构

Dict成员包含一个存储字典的KV结构和一个文件路径

  1. class Dict
  2. {
  3. private:
  4. // 加载字典文件
  5. void LoadDict(const std::string& path);
  6. public:
  7. // 构造
  8. Dict(const std::string& dict_path);
  9. // 英语翻译为中文
  10. std::string Translate(std::string word);
  11. ~Dict()
  12. {}
  13. private:
  14. std::unordered_map<std::string, std::string> _dict; // 字典结构
  15. std::string _dict_path; // 文件路径
  16. };

2.2、加载字典文件

注意:此处的字典文件是以冒号 + 空格来分割英文与中文的!

加载字典文件的本质是以KV的形式将英文单词和中文翻译插入到_dict哈希表中

** 加载文件包含3个大的步骤:**

  • 1、读方式打开文件
  • 2、按行读取内容[需要考虑中间有空格情况,一行中没找到分隔符情况]
  • 3、关闭文件
  1. const static std::string sep = ": "; // 分隔符 冒号+空格
  2. // 加载字典文件
  3. void LoadDict(const std::string &path)
  4. {
  5. // 1.读方式打开文件
  6. std::ifstream in(path);
  7. // 判断是否打开成功
  8. if (!in.is_open())
  9. {
  10. LOG(FATAL, "open %s failed\n", path.c_str());
  11. exit(1);
  12. }
  13. std::string line;
  14. // 2.按行读取内容
  15. while (std::getline(in, line))
  16. {
  17. LOG(DEBUG, "load info : %s ,success\n", line.c_str());
  18. if (line.empty())
  19. continue; // 中间有空格情况
  20. auto pos = line.find(sep); // 使用find找到分割符位置,返回迭代器位置
  21. if (pos == std::string::npos)
  22. continue; // 一行中没找到分隔符
  23. // apple: 苹果
  24. std::string key = line.substr(0, pos); // [) 前闭后开
  25. if (key.empty())
  26. continue;
  27. std::string value = line.substr(pos + sep.size()); // 从pos + 分隔符长度开始到结尾
  28. if (value.empty())
  29. continue;
  30. _dict.insert(std::make_pair(key, value));
  31. }
  32. LOG(INFO, "load %s done\n", path.c_str());
  33. in.close(); // 3.关闭文件
  34. }

2.3、构造函数

构造函数****初始化字典文件和加载字典文件(将字典文件以KV格式插入到_dict中)。

  1. // 构造
  2. Dict(const std::string &dict_path) : _dict_path(dict_path)
  3. {
  4. LoadDict(_dict_path);
  5. }

2.4、翻译函数

翻译函数即**在_dict中查找是否有该单词,有该单词则返回_dict的value值(没找到返回None)**!

  1. // 英语翻译为中文
  2. std::string Translate(std::string word)
  3. {
  4. if (word.empty())
  5. return "None";
  6. auto iter = _dict.find(word);
  7. if (iter == _dict.end())
  8. return "None";
  9. else
  10. return iter->second;
  11. }

2.5、dict.txt

dict.txt文件****存储对应的英文单词和翻译结果

  1. apple: 苹果
  2. banana: 香蕉
  3. cat:
  4. dog:
  5. book:
  6. pen:
  7. happy: 快乐的
  8. sad: 悲伤的
  9. run:
  10. jump:
  11. teacher: 老师
  12. student: 学生
  13. car: 汽车
  14. bus: 公交车
  15. love:
  16. hate:
  17. hello: 你好
  18. goodbye: 再见
  19. summer: 夏天
  20. winter: 冬天

3、UdpServerMain.cc

服务端主函数基本结构没变,但是构造指针对象时需要传递翻译函数对象,但是这个函数对象的参数是字符串类型,返回值是字符串类型,而Dict类中的翻译函数有this指针,因此我们需要使用bind()绑定函数

  1. // .udp_client local-port
  2. // .udp_client 8888
  3. int main(int argc, char *argv[])
  4. {
  5. if (argc != 2)
  6. {
  7. std::cerr << "Usage: " << argv[0] << " server-port" << std::endl;
  8. exit(0);
  9. }
  10. uint16_t port = std::stoi(argv[1]);
  11. EnableScreen();
  12. Dict dict("./dict.txt"); // 构造字典类
  13. func_t translate = std::bind(&Dict::Translate, &dict, std::placeholders::_1); // 绑定翻译函数
  14. std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(translate, port); // C++14标准
  15. usvr->InitServer();
  16. usvr->Start();
  17. return 0;
  18. }

运行结果

4、完整源码

4.1、Dict.hpp

  1. #pragma once
  2. #include <iostream>
  3. #include <fstream>
  4. #include <string>
  5. #include <unordered_map>
  6. #include "Log.hpp"
  7. using namespace log_ns;
  8. const static std::string sep = ": "; // 分隔符 冒号+空格
  9. class Dict
  10. {
  11. private:
  12. // 加载字典文件
  13. void LoadDict(const std::string &path)
  14. {
  15. // 1.读方式打开文件
  16. std::ifstream in(path);
  17. // 判断是否打开成功
  18. if (!in.is_open())
  19. {
  20. LOG(FATAL, "open %s failed\n", path.c_str());
  21. exit(1);
  22. }
  23. std::string line;
  24. // 2.按行读取内容
  25. while (std::getline(in, line))
  26. {
  27. LOG(DEBUG, "load info : %s ,success\n", line.c_str());
  28. if (line.empty())
  29. continue; // 中间有空格情况
  30. auto pos = line.find(sep); // 使用find找到分割符位置,返回迭代器位置
  31. if (pos == std::string::npos)
  32. continue; // 一行中没找到分隔符
  33. // apple: 苹果
  34. std::string key = line.substr(0, pos); // [) 前闭后开
  35. if (key.empty())
  36. continue;
  37. std::string value = line.substr(pos + sep.size()); // 从pos + 分隔符长度开始到结尾
  38. if (value.empty())
  39. continue;
  40. _dict.insert(std::make_pair(key, value));
  41. }
  42. LOG(INFO, "load %s done\n", path.c_str());
  43. in.close(); // 3.关闭文件
  44. }
  45. public:
  46. // 构造
  47. Dict(const std::string &dict_path) : _dict_path(dict_path)
  48. {
  49. LoadDict(_dict_path);
  50. }
  51. // 英语翻译为中文
  52. std::string Translate(std::string word)
  53. {
  54. if (word.empty())
  55. return "None";
  56. auto iter = _dict.find(word);
  57. if (iter == _dict.end())
  58. return "None";
  59. else
  60. return iter->second;
  61. }
  62. ~Dict()
  63. {
  64. }
  65. private:
  66. std::unordered_map<std::string, std::string> _dict; // 字典结构
  67. std::string _dict_path; // 文件路径
  68. };

4.2、dict.txt

  1. apple: 苹果
  2. banana: 香蕉
  3. cat:
  4. dog:
  5. book:
  6. pen:
  7. happy: 快乐的
  8. sad: 悲伤的
  9. run:
  10. jump:
  11. teacher: 老师
  12. student: 学生
  13. car: 汽车
  14. bus: 公交车
  15. love:
  16. hate:
  17. hello: 你好
  18. goodbye: 再见
  19. summer: 夏天
  20. winter: 冬天

4.3、InetAddr.hpp

  1. #pragma once
  2. #include <iostream>
  3. #include <string>
  4. #include <sys/types.h>
  5. #include <sys/socket.h>
  6. #include <arpa/inet.h>
  7. #include <netinet/in.h>
  8. class InetAddr
  9. {
  10. private:
  11. // 网络地址转本地地址
  12. void ToHost(const struct sockaddr_in& addr)
  13. {
  14. _port = ntohs(addr.sin_port); // 网络转主机
  15. _ip = inet_ntoa(addr.sin_addr); // 结构化转字符串
  16. }
  17. public:
  18. InetAddr(const struct sockaddr_in& addr):_addr(addr)
  19. {
  20. ToHost(addr);
  21. }
  22. std::string Ip()
  23. {
  24. return _ip;
  25. }
  26. uint16_t Port()
  27. {
  28. return _port;
  29. }
  30. ~InetAddr()
  31. {}
  32. private:
  33. std::string _ip;
  34. uint16_t _port;
  35. struct sockaddr_in _addr;
  36. };

4.4、LockGuard.hpp

  1. #pragma once
  2. #include <pthread.h>
  3. class LockGuard
  4. {
  5. public:
  6. LockGuard(pthread_mutex_t* mutex):_mutex(mutex)
  7. {
  8. pthread_mutex_lock(_mutex);
  9. }
  10. ~LockGuard()
  11. {
  12. pthread_mutex_unlock(_mutex);
  13. }
  14. private:
  15. pthread_mutex_t* _mutex;
  16. };

4.5、Log.hpp

  1. #pragma once
  2. #include <iostream>
  3. #include <fstream>
  4. #include <ctime>
  5. #include <cstring>
  6. #include <sys/types.h>
  7. #include <unistd.h>
  8. #include <stdarg.h>
  9. #include <pthread.h>
  10. #include "LockGuard.hpp"
  11. namespace log_ns
  12. {
  13. // 日志等级
  14. enum
  15. {
  16. DEBUG = 1,
  17. INFO,
  18. WARNING,
  19. ERROR,
  20. FATAL
  21. };
  22. std::string LevelToString(int level)
  23. {
  24. switch (level)
  25. {
  26. case DEBUG:
  27. return "DEBUG";
  28. case INFO:
  29. return "INFO";
  30. case WARNING:
  31. return "WARNING";
  32. case ERROR:
  33. return "ERROR";
  34. case FATAL:
  35. return "FATAL";
  36. default:
  37. return "UNKNOW";
  38. }
  39. }
  40. std::string GetCurrTime()
  41. {
  42. time_t now = time(nullptr); // 时间戳
  43. struct tm *curr_time = localtime(&now);
  44. char buffer[128];
  45. snprintf(buffer, sizeof(buffer), "%d-%02d-%02d %02d:%02d:%02d",
  46. curr_time->tm_year + 1900,
  47. curr_time->tm_mon + 1,
  48. curr_time->tm_mday,
  49. curr_time->tm_hour,
  50. curr_time->tm_min,
  51. curr_time->tm_sec);
  52. return buffer;
  53. }
  54. class logmessage
  55. {
  56. public:
  57. std::string _level; // 日志等级
  58. pid_t _id; // pid
  59. std::string _filename; // 文件名
  60. int _filenumber; // 文件行号
  61. std::string _curr_time; // 当前时间
  62. std::string _message_info; // 日志内容
  63. };
  64. #define SCREEN_TYPE 1
  65. #define FILE_TYPE 2
  66. const std::string glogfile = "./log.txt";
  67. pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER;
  68. // log.logMessage(""/*文件名*/,12/*文件行号*/,INFO/*日志等级*/,"this is a %d message,%f,%s,hello world"/*日志内容*/,x,,);
  69. class Log
  70. {
  71. public:
  72. // 默认向显示器打印
  73. Log(const std::string &logfile = glogfile) : _logfile(logfile), _type(SCREEN_TYPE)
  74. {
  75. }
  76. // 打印方式
  77. void Enable(int type)
  78. {
  79. _type = type;
  80. }
  81. // 向屏幕打印
  82. void FlushToScreen(const logmessage &lg)
  83. {
  84. printf("[%s][%d][%s][%d][%s] %s",
  85. lg._level.c_str(),
  86. lg._id,
  87. lg._filename.c_str(),
  88. lg._filenumber,
  89. lg._curr_time.c_str(),
  90. lg._message_info.c_str());
  91. }
  92. // 向文件打印
  93. void FlushToFile(const logmessage &lg)
  94. {
  95. std::ofstream out(_logfile, std::ios::app); // 追加打开文件
  96. if (!out.is_open())
  97. return; // 打开失败直接返回
  98. char logtxt[2048];
  99. snprintf(logtxt, sizeof(logtxt), "[%s][%d][%s][%d][%s] %s",
  100. lg._level.c_str(),
  101. lg._id,
  102. lg._filename.c_str(),
  103. lg._filenumber,
  104. lg._curr_time.c_str(),
  105. lg._message_info.c_str());
  106. out.write(logtxt, strlen(logtxt)); // 写文件
  107. out.close(); // 关闭文件
  108. }
  109. // 刷新日志
  110. void FlushLog(const logmessage &lg)
  111. {
  112. // 加过滤逻辑 --- TODO
  113. // ...
  114. LockGuard lockguard(&glock); // RAII锁
  115. switch (_type)
  116. {
  117. case SCREEN_TYPE:
  118. FlushToScreen(lg);
  119. break;
  120. case FILE_TYPE:
  121. FlushToFile(lg);
  122. break;
  123. }
  124. }
  125. // ... 可变参数(C语言)
  126. // 初始化日志信息
  127. void logMessage(std::string filename, int filenumber, int level, const char *format, ...)
  128. {
  129. logmessage lg;
  130. lg._level = LevelToString(level);
  131. lg._id = getpid();
  132. lg._filename = filename;
  133. lg._filenumber = filenumber;
  134. lg._curr_time = GetCurrTime();
  135. va_list ap; // va_list-> char*指针
  136. va_start(ap, format); // 初始化一个va_list类型的变量
  137. char log_info[1024];
  138. vsnprintf(log_info, sizeof(log_info), format, ap);
  139. va_end(ap); // 释放由va_start宏初始化的va_list资源
  140. lg._message_info = log_info;
  141. // std::cout << lg._message_info << std::endl; // 测试
  142. // 日志打印出来(显示器/文件)
  143. FlushLog(lg);
  144. }
  145. ~Log()
  146. {
  147. }
  148. private:
  149. int _type; // 打印方式
  150. std::string _logfile; // 文件名
  151. };
  152. Log lg;
  153. // 打印日志封装成宏,使用函数方式调用
  154. #define LOG(Level, Format, ...) \
  155. do \
  156. { \
  157. lg.logMessage(__FILE__, __LINE__, Level, Format, ##__VA_ARGS__); \
  158. } while (0)
  159. // 设置打印方式,使用函数方式调用
  160. #define EnableScreen() \
  161. do \
  162. { \
  163. lg.Enable(SCREEN_TYPE); \
  164. } while (0)
  165. // 设置打印方式,使用函数方式调用
  166. #define EnableFile() \
  167. do \
  168. { \
  169. lg.Enable(FILE_TYPE); \
  170. } while (0)
  171. }

4.6、Makefile

  1. .PHONY:all
  2. all:udpserver udpclient
  3. udpserver:UdpServerMain.cc
  4. g++ -o $@ $^ -std=c++14
  5. udpclient:UdpClientMain.cc
  6. g++ -o $@ $^ -std=c++14
  7. .PHONY:clean
  8. clean:
  9. rm -rf udpserver udpclient

4.7、nocopy.hpp

  1. #pragma once
  2. class nocopy
  3. {
  4. public:
  5. nocopy(){}
  6. ~nocopy(){}
  7. nocopy(const nocopy&) = delete;
  8. const nocopy& operator=(const nocopy&) = delete;
  9. };

4.8、UdpClientMain.cc

  1. #include "UdpServer.hpp"
  2. #include <string>
  3. #include <cstring>
  4. #include <sys/types.h>
  5. #include <sys/socket.h>
  6. #include <netinet/in.h>
  7. #include <arpa/inet.h>
  8. // 客户端在未来一定要知道服务器的IP地址和端口号
  9. // .udp_client server-ip server-port
  10. // .udp_client 127.0.0.1 8888
  11. int main(int argc, char *argv[])
  12. {
  13. if (argc != 3)
  14. {
  15. std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;
  16. exit(0);
  17. }
  18. std::string serverip = argv[1];
  19. uint16_t serverport = std::stoi(argv[2]);
  20. // 1.创建套接字
  21. int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
  22. if (sockfd < 0)
  23. {
  24. std::cerr << "create socket eror\n"
  25. << std::endl;
  26. exit(1);
  27. }
  28. // client的端口号,一般不让用户自己设定,而是让client 所在OS随机选择?怎么选择?什么时候?
  29. // client 需要bind它自己的IP和端口,但是client 不需要 "显示" bind它自己的IP和端口
  30. // client 在首次向服务器发送数据的时候,OS会自动给client bind它自己的IP和端口
  31. struct sockaddr_in server;
  32. memset(&server, 0, sizeof(server));
  33. server.sin_family = AF_INET;
  34. server.sin_port = htons(serverport); // 转换重要!!!
  35. server.sin_addr.s_addr = inet_addr(serverip.c_str());
  36. while (true)
  37. {
  38. std::string line;
  39. std::cout << "Please Enter# ";
  40. std::getline(std::cin, line); // 以行读取消息
  41. // 发消息,你要知道发送给谁
  42. int n = sendto(sockfd, line.c_str(), line.size(), 0, (struct sockaddr *)&server, sizeof(server));
  43. if(n > 0)
  44. {
  45. // 收消息
  46. struct sockaddr_in temp;
  47. socklen_t len = sizeof(temp);
  48. char buffer[1024];
  49. int m = recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&temp,&len);
  50. if(m > 0)
  51. {
  52. buffer[m] = 0;
  53. std::cout << buffer << std::endl;
  54. }
  55. else
  56. {
  57. break;
  58. }
  59. }
  60. else
  61. {
  62. break;
  63. }
  64. }
  65. // 关闭套接字
  66. ::close(sockfd);
  67. return 0;
  68. }

4.9、UdpServerMain.cc

  1. #include "UdpServer.hpp"
  2. #include "Dict.hpp"
  3. #include <memory>
  4. // .udp_client local-port
  5. // .udp_client 8888
  6. int main(int argc, char *argv[])
  7. {
  8. if (argc != 2)
  9. {
  10. std::cerr << "Usage: " << argv[0] << " server-port" << std::endl;
  11. exit(0);
  12. }
  13. uint16_t port = std::stoi(argv[1]);
  14. EnableScreen();
  15. Dict dict("./dict.txt"); // 构造字典类
  16. func_t translate = std::bind(&Dict::Translate, &dict, std::placeholders::_1); // 绑定翻译函数
  17. std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(translate, port); // C++14标准
  18. usvr->InitServer();
  19. usvr->Start();
  20. return 0;
  21. }
标签: 网络 linux 运维

本文转载自: https://blog.csdn.net/2201_75584283/article/details/143041518
版权归原作者 小林熬夜学编程 所有, 如有侵权,请联系我们删除。

“【Linux网络编程】第四弹---构建UDP服务器与字典翻译系统:源码结构与关键组件解析”的评论:

还没有评论