运用自界说协议计划与实现“跨网络盘算器”

打印 上一主题 下一主题

主题 539|帖子 539|积分 1617

一、计划方案

1. 日志模块 (Log.hpp)

日志模块提供了一个简朴的日志记录功能,允许将日志输出到控制台、单个文件或按日志级别分类的文件中。它界说了差别级别的日志(Info, Debug, Warning, Error, Fatal),并允许通过Enable方法切换日志输出方式。
2. 协议模块 (Protocol.hpp)

协议模块界说了请求和响应的数据格式。Request类封装了盘算请求的数据,包罗操作数和操作符。Response类封装了盘算效果和错误代码。这两个类都提供了序列化和反序列化的方法,以便将数据转换为网络传输的格式。
3. 服务器模块 (ServerCal.hpp、Socket.hpp 和 TcpServer.hpp )

服务器模块由ServerCal、Socket.hpp和TcpServer类构成。

  • ServerCal类负责处理盘算请求,它界说了一个Calculator方法来执行实际的算术运算,并返回效果和错误代码。
  • Socket类封装了根本的socket操作,如毗连、读取和写入。
  • TcpServer类负责网络通讯,它监听指定端口,继续客户端毗连,并利用回调函数来处理吸收到的数据。
4. 客户端模块 (ClientCal.cpp)

客户端模块提供了与服务器通讯的能力,ClientCal类利用Socket类来向服务器发送盘算请求,并吸收响应。
客户端程序首先检查下令行参数是否精确,然后创建一个套接字并毗连到服务器。程序将随机生成两个数字和一个操作符,创建一个请求对象,并将其序列化为字符串。然后,程序将这个字符串编码为网络字节流,并通过套接字发送给服务器。吸收到服务器的响应后,程序将其解码并反序列化为响应对象,然后打印出请求和响应的详细信息。这个过程将重复10次,每次请求后程序会停息1秒。最后,程序关闭套接字并退出。
二、日志模块、makefile文件

✅Log.hpp

  1. // 预处理指令,确保头文件只被包含一次
  2. #pragma once
  3. // 引入必要的头文件
  4. #include <iostream> // 标准输入输出流
  5. #include <time.h>    // 时间函数
  6. #include <stdarg.h>  // 可变参数列表
  7. #include <sys/types.h> // 文件系统类型
  8. #include <sys/stat.h> // 文件状态
  9. #include <fcntl.h>   // 文件控制
  10. #include <unistd.h>   // UNIX标准函数
  11. #include <stdlib.h>   // 标准库
  12. // 定义常量SIZE,用于缓冲区大小
  13. #define SIZE 1024
  14. // 定义日志级别,分别对应不同的日志重要性
  15. #define Info 0
  16. #define Debug 1
  17. #define Warning 2
  18. #define Error 3
  19. #define Fatal 4
  20. // 定义日志输出方式,分别对应控制台输出和文件输出
  21. #define Screen 1
  22. #define Onefile 2
  23. #define Classfile 3
  24. // 定义日志文件名
  25. #define LogFile "log.txt"
  26. // 日志类Log的声明
  27. class Log
  28. {
  29. public:
  30.     // 构造函数,初始化日志输出方式为控制台输出,日志路径为当前目录下的log文件夹
  31.     Log()
  32.     {
  33.         printMethod = Screen; // 默认输出方式为控制台输出
  34.         path = "./log/";     // 默认日志路径
  35.     }
  36.     // 设置日志输出方式的函数
  37.     void Enable(int method)
  38.     {
  39.         printMethod = method; // 根据传入的参数设置输出方式
  40.     }
  41.     // 将日志级别转换为字符串的函数
  42.     std::string levelToString(int level)
  43.     {
  44.         // 使用switch语句根据日志级别返回对应的字符串
  45.         switch (level)
  46.         {
  47.             case Info:
  48.                 return "Info";
  49.             case Debug:
  50.                 return "Debug";
  51.             case Warning:
  52.                 return "Warning";
  53.             case Error:
  54.                 return "Error";
  55.             case Fatal:
  56.                 return "Fatal";
  57.             default:
  58.                 return "None";
  59.         }
  60.     }
  61.     // 打印日志的函数
  62.     void printLog(int level, const std::string &logtxt)
  63.     {
  64.         // 根据输出方式选择不同的打印方法
  65.         switch (printMethod)
  66.         {
  67.             case Screen:
  68.                 std::cout << logtxt << std::endl; // 控制台输出
  69.                 break;
  70.             case Onefile:
  71.                 printOneFile(LogFile, logtxt); // 单文件输出
  72.                 break;
  73.             case Classfile:
  74.                 printClassFile(level, logtxt); // 分类文件输出
  75.                 break;
  76.             default:
  77.                 break;
  78.         }
  79.     }
  80.     // 单文件日志输出的实现
  81.     void printOneFile(const std::string &logname, const std::string &logtxt)
  82.     {
  83.         // 拼接完整的日志文件路径
  84.         std::string _logname = path + logname;
  85.         // 打开文件,如果失败则直接返回
  86.         int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);
  87.         if (fd < 0)
  88.             return;
  89.         // 将日志文本写入文件
  90.         write(fd, logtxt.c_str(), logtxt.size());
  91.         // 关闭文件描述符
  92.         close(fd);
  93.     }
  94.     // 分类文件日志输出的实现
  95.     void printClassFile(int level, const std::string &logtxt)
  96.     {
  97.         // 根据日志级别创建对应的文件名
  98.         std::string filename = LogFile;
  99.         filename += ".";
  100.         filename += levelToString(level);
  101.         // 调用单文件输出函数
  102.         printOneFile(filename, logtxt);
  103.     }
  104.     // 析构函数,目前为空
  105.     ~Log()
  106.     {
  107.     }
  108.     // 重载函数调用运算符,用于格式化输出日志
  109.     void operator()(int level, const char *format, ...)
  110.     {
  111.         // 获取当前时间
  112.         time_t t = time(nullptr);
  113.         struct tm *ctime = localtime(&t);
  114.         // 格式化时间字符串
  115.         char leftbuffer[SIZE];
  116.         snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]",
  117.                  levelToString(level).c_str(),
  118.                  ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
  119.                  ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
  120.         // 使用可变参数列表进行格式化字符串
  121.         va_list s;
  122.         va_start(s, format);
  123.         char rightbuffer[SIZE];
  124.         vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
  125.         va_end(s);
  126.         // 拼接完整的日志文本
  127.         char logtxt[SIZE * 2];
  128.         snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);
  129.         // 调用打印日志函数
  130.         printLog(level, logtxt);
  131.     }
  132. private:
  133.     // 日志输出方式
  134.     int printMethod;
  135.     // 日志文件路径
  136.     std::string path;
  137. };
  138. // 定义一个Log类的全局对象lg,用于输出日志
  139. Log lg;
复制代码
✅makefile

  1. .PHONY:all
  2. all:servercal clientcal
  3. Flag=#-DMySelf=1
  4. Lib=-ljsoncpp
  5. servercal:ServerCal.cc
  6.         g++ -o $@ $^ -std=c++11 $(Lib) $(Flag)
  7. clientcal:ClientCal.cc
  8.         g++ -o $@ $^ -std=c++11 -g $(Lib) $(Flag)
  9. .PHONY:clean
  10. clean:
  11.         rm -f clientcal servercal
复制代码
三、协议模块(Protocol.hpp)

✅Protocol.hpp

  1. #pragma once // 确保头文件在整个程序中只被包含一次
  2. #include <iostream> // 包含标准输入输出流
  3. #include <string> // 包含字符串类
  4. #include <jsoncpp/json/json.h> // 包含JSONCPP库,用于JSON数据的处理
  5. // 宏定义,用于序列化和反序列化过程中的数据分隔
  6. const std::string blank_space_sep = " "; // 空格分隔符
  7. const std::string protocol_sep = "\n"; // 换行符作为协议的分隔符
  8. // 序列化函数,将内容字符串包装成网络传输的格式
  9. std::string Encode(std::string &content)
  10. {
  11.     std::string package; // 创建一个字符串用于存储包装后的数据
  12.     package = std::to_string(content.size()); // 将内容的长度转换为字符串
  13.     package += protocol_sep; // 添加协议分隔符
  14.     package += content; // 添加内容本身
  15.     package += protocol_sep; // 再次添加协议分隔符,表示数据结束
  16.     return package; // 返回包装后的字符串
  17. }
  18. // 反序列化函数,将接收到的网络数据解析为内容字符串
  19. bool Decode(std::string &package, std::string *content)
  20. {
  21.     std::size_t pos = package.find(protocol_sep); // 查找协议分隔符的位置
  22.     if(pos == std::string::npos) return false; // 如果找不到分隔符,解析失败
  23.     std::string len_str = package.substr(0, pos); // 提取长度字符串
  24.     std::size_t len = std::stoi(len_str); // 将长度字符串转换为数字
  25.     std::size_t total_len = len_str.size() + len + 2; // 计算总长度(长度字符串 + 内容 + 分隔符)
  26.     if(package.size() < total_len) return false; // 如果实际数据长度小于预期,解析失败
  27.     *content = package.substr(pos+1, len); // 提取内容字符串
  28.     package.erase(0, total_len); // 从原始数据中移除已解析的部分
  29.     return true; // 解析成功
  30. }
  31. // 请求数据结构
  32. class Request
  33. {
  34. public:
  35.     // 构造函数,初始化请求的参数
  36.     Request(int data1, int data2, char oper) : x(data1), y(data2), op(oper) {}
  37.     // 默认构造函数
  38.     Request() {}
  39. public:
  40.     // 序列化方法,将请求对象转换为字符串
  41.     bool Serialize(std::string *out)
  42.     {
  43. #ifdef MySelf
  44.         // 使用简单的字符串拼接方式
  45.         std::string s = std::to_string(x); // 将操作数x转换为字符串
  46.         s += blank_space_sep; // 添加空格分隔符
  47.         s += op; // 添加操作符
  48.         s += blank_space_sep; // 再次添加空格分隔符
  49.         s += std::to_string(y); // 将操作数y转换为字符串
  50.         *out = s; // 将拼接后的字符串赋值给输出参数
  51.         return true; // 返回成功
  52. #else
  53.         // 使用JSON格式
  54.         Json::Value root; // 创建JSON值对象
  55.         root["x"] = x; // 添加操作数x
  56.         root["y"] = y; // 添加操作数y
  57.         root["op"] = op; // 添加操作符
  58.         Json::StyledWriter w; // 创建JSON格式化写入器
  59.         *out = w.write(root); // 将JSON对象转换为格式化的字符串
  60.         return true; // 返回成功
  61. #endif
  62.     }
  63.     // 反序列化方法,将字符串转换为请求对象
  64.     bool Deserialize(const std::string &in)
  65.     {
  66. #ifdef MySelf
  67.         // 使用简单的字符串拼接方式
  68.         std::size_t left = in.find(blank_space_sep); // 查找第一个空格分隔符
  69.         if (left == std::string::npos) return false; // 如果找不到分隔符,解析失败
  70.         std::string part_x = in.substr(0, left); // 提取操作数x的字符串
  71.         std::size_t right = in.rfind(blank_space_sep); // 查找最后一个空格分隔符
  72.         if (right == std::string::npos) return false; // 如果找不到分隔符,解析失败
  73.         std::string part_y = in.substr(right + 1); // 提取操作数y的字符串
  74.         if (left + 2 != right) return false; // 如果分隔符之间的长度不符合预期,解析失败
  75.         op = in[left + 1]; // 提取操作符
  76.         x = std::stoi(part_x); // 将操作数x的字符串转换为数字
  77.         y = std::stoi(part_y); // 将操作数y的字符串转换为数字
  78.         return true; // 返回成功
  79. #else
  80.         // 使用JSON格式
  81.         Json::Value root;
  82.         Json::Reader r;
  83.         if (!r.parse(in, root)) return false; // 解析JSON字符串,如果失败则返回
  84.         x = root["x"].asInt(); // 提取操作数x
  85.         y = root["y"].asInt(); // 提取操作数y
  86.         op = root["op"].asInt(); // 提取操作符
  87.         return true; // 返回成功
  88. #endif
  89.     }
  90.     // 调试方法,打印请求对象的内容
  91.     void DebugPrint()
  92.     {
  93.         std::cout << "新请求构建完成:  " << x << op << y << "=?" << std::endl; // 打印操作数和操作符
  94.     }
  95. public:
  96.     // 请求的数据成员
  97.     int x; // 操作数x
  98.     int y; // 操作数y
  99.     char op; // 操作符,可以是 + - * / %
  100. };
  101. // 响应数据结构
  102. class Response
  103. {
  104. public:
  105.     // 构造函数,初始化响应的参数
  106.     Response(int res, int c) : result(res), code(c) {}
  107.     // 默认构造函数
  108.     Response() {}
  109. public:
  110.     // 序列化方法,将响应对象转换为字符串
  111.     bool Serialize(std::string *out)
  112.     {
  113. #ifdef MySelf
  114.         // 使用简单的字符串拼接方式
  115.         std::string s = std::to_string(result); // 将结果转换为字符串
  116.         s += blank_space_sep; // 添加空格分隔符
  117.         s += std::to_string(code); // 将错误代码转换为字符串
  118.         *out = s; // 将拼接后的字符串赋值给输出参数
  119.         return true; // 返回成功
  120. #else
  121.         // 使用JSON格式
  122.         Json::Value root; // 创建JSON值对象
  123.         root["result"] = result; // 添加结果
  124.         root["code"] = code; // 添加错误代码
  125.         Json::StyledWriter w; // 创建JSON格式化写入器
  126.         *out = w.write(root); // 将JSON对象转换为格式化的字符串
  127.         return true; // 返回成功
  128. #endif
  129.     }
  130.     // 反序列化方法,将字符串转换为响应对象
  131.     bool Deserialize(const std::string &in)
  132.     {
  133. #ifdef MySelf
  134.         // 使用简单的字符串拼接方式
  135.         std::size_t pos = in.find(blank_space_sep); // 查找空格分隔符
  136.         if (pos == std::string::npos) return false; // 如果找不到分隔符,解析失败
  137.         std::string part_left = in.substr(0, pos); // 提取结果字符串
  138.         std::string part_right = in.substr(pos + 1); // 提取错误代码字符串
  139.         result = std::stoi(part_left); // 将结果字符串转换为数字
  140.         code = std::stoi(part_right); // 将错误代码字符串转换为数字
  141.         return true; // 返回成功
  142. #else
  143.         // 使用JSON格式
  144.         Json::Value root;
  145.         Json::Reader r;
  146.         if (!r.parse(in, root)) return false; // 解析JSON字符串,如果失败则返回
  147.         result = root["result"].asInt(); // 提取结果
  148.         code = root["code"].asInt(); // 提取错误代码
  149.         return true; // 返回成功
  150. #endif
  151.     }
  152.     // 调试方法,打印响应对象的内容
  153.     void DebugPrint()
  154.     {
  155.         std::cout << "结果响应完成, result: " << result << ", code: " << code << std::endl; // 打印结果和错误代码
  156.     }
  157. public:
  158.     int result; // 计算结果
  159.     int code; // 错误代码,0表示成功,非0表示错误
  160. };
复制代码
四、服务端模块

✅ServerCal.hpp

  1. // 预处理指令,确保头文件只被包含一次
  2. #pragma once
  3. // 引入必要的头文件
  4. #include <iostream>
  5. // 引入自定义的协议头文件
  6. #include "Protocol.hpp"
  7. // 定义枚举类型,用于表示不同类型的操作和错误
  8. enum
  9. {
  10.     Div_Zero = 1, // 除数为零的错误代码
  11.     Mod_Zero,    // 模数为零的错误代码
  12.     Other_Oper     // 其他操作或者错误
  13. };
  14. // 声明ServerCal类,用于处理客户端的计算请求
  15. class ServerCal
  16. {
  17. public:
  18.     // 构造函数
  19.     ServerCal()
  20.     {
  21.     }
  22.     // 计算助手函数,根据请求计算结果并返回响应对象
  23.     Response CalculatorHelper(const Request &req)
  24.     {
  25.         Response resp(0, 0); // 创建一个响应对象,初始化结果和错误代码为0
  26.         // 根据请求中的操作符进行计算
  27.         switch (req.op) // 检查操作符
  28.         {
  29.             case '+': // 加法
  30.                 resp.result = req.x + req.y;
  31.                 break;
  32.             case '-': // 减法
  33.                 resp.result = req.x - req.y;
  34.                 break;
  35.             case '*': // 乘法
  36.                 resp.result = req.x * req.y;
  37.                 break;
  38.             case '/': // 除法
  39.             {
  40.                 if (req.y == 0) // 如果除数为0,设置错误代码
  41.                     resp.code = Div_Zero;
  42.                 else // 否则进行除法运算
  43.                     resp.result = req.x / req.y;
  44.             }
  45.             break;
  46.             case '%':
  47.             {
  48.                 if (req.y == 0) // 如果模数为0,设置错误代码
  49.                     resp.code = Mod_Zero;
  50.                 else // 否则进行模运算
  51.                     resp.result = req.x % req.y;
  52.             }
  53.             break;
  54.             default: // 如果操作符不是预定义的几种
  55.                 resp.code = Other_Oper; // 设置错误代码为其他操作
  56.                 break;
  57.         }
  58.         return resp; // 返回计算结果和错误代码
  59.     }
  60.     // 计算函数,解析请求字符串,并返回计算结果
  61.     std::string Calculator(std::string &package)
  62.     {
  63.         std::string content; // 用于存储解码后的内容
  64.         // 解析请求包的长度和内容
  65.         bool r = Decode(package, &content);
  66.         if (!r)
  67.             return ""; // 如果解码失败,返回空字符串
  68.         // 从解码后的内容中反序列化请求对象
  69.         Request req;
  70.         r = req.Deserialize(content);
  71.         if (!r)
  72.             return ""; // 如果反序列化失败,返回空字符串
  73.         // 清空content,准备存储响应内容
  74.         content = "";
  75.         // 调用助手函数进行计算
  76.         Response resp = CalculatorHelper(req);
  77.         // 将计算结果和错误代码序列化到content
  78.         resp.Serialize(&content);
  79.         // 编码响应内容,添加长度前缀
  80.         content = Encode(content);
  81.         return content; // 返回响应字符串
  82.     }
  83.     // 析构函数
  84.     ~ServerCal()
  85.     {
  86.     }
  87. };
复制代码
✅Socket.hpp

  1. // 预处理指令,确保头文件只被包含一次
  2. #pragma once
  3. // 引入必要的头文件
  4. #include <iostream> // 标准输入输出流
  5. #include <string>   // 字符串类
  6. #include <unistd.h>  // UNIX标准函数,如sleep等
  7. #include <cstring>   // C字符串处理函数
  8. #include <sys/types.h> // 系统类型定义
  9. #include <sys/stat.h> // 文件状态信息
  10. #include <sys/socket.h> // 套接字相关函数和结构定义
  11. #include <arpa/inet.h> // 网络地址转换
  12. #include <netinet/in.h> // 网络接口定义
  13. #include "Log.hpp"  // 自定义的日志库
  14. // 定义枚举类型,用于表示不同的错误代码
  15. enum
  16. {
  17.     SocketErr = 2, // 套接字创建错误
  18.     BindErr,     // 套接字绑定错误
  19.     ListenErr    // 套接字监听错误
  20. };
  21. // 定义backlog常量,用于listen函数的参数
  22. const int backlog = 10;
  23. // Sock类声明,封装了套接字操作的一系列方法
  24. class Sock
  25. {
  26. public:
  27.     // 构造函数
  28.     Sock()
  29.     {
  30.     }
  31.     // 析构函数
  32.     ~Sock()
  33.     {
  34.     }
  35.     // 创建套接字的方法
  36.     void Socket()
  37.     {
  38.         // 使用socket函数创建一个流式套接字,地址族为IPv4,类型为TCP
  39.         sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
  40.         // 如果套接字创建失败,记录错误日志并退出程序
  41.         if (sockfd_ < 0)
  42.         {
  43.             lg(Fatal, "socket error, %s: %d", strerror(errno), errno);
  44.             exit(SocketErr);
  45.         }
  46.     }
  47.     // 绑定套接字到指定端口的方法
  48.     void Bind(uint16_t port)
  49.     {
  50.         // 创建一个sockaddr_in结构体,用于绑定操作
  51.         struct sockaddr_in local;
  52.         memset(&local, 0, sizeof(local)); // 清零结构体
  53.         local.sin_family = AF_INET;       // 设置地址族为IPv4
  54.         local.sin_port = htons(port);     // 设置端口号,使用网络字节序
  55.         local.sin_addr.s_addr = INADDR_ANY; // 允许绑定到所有可用网络接口
  56.         // 使用bind函数将套接字绑定到指定端口,如果失败记录错误日志并退出程序
  57.         if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0)
  58.         {
  59.             lg(Fatal, "bind error, %s: %d", strerror(errno), errno);
  60.             exit(BindErr);
  61.         }
  62.     }
  63.     // 使套接字监听传入连接的方法
  64.     void Listen()
  65.     {
  66.         // 使用listen函数使套接字监听传入连接,backlog参数指定最大的连接请求队列长度
  67.         if (listen(sockfd_, backlog) < 0)
  68.         {
  69.             lg(Fatal, "listen error, %s: %d", strerror(errno), errno);
  70.             exit(ListenErr);
  71.         }
  72.     }
  73.     // 接受传入连接的方法
  74.     int Accept(std::string *clientip, uint16_t *clientport)
  75.     {
  76.         // 创建一个sockaddr_in结构体,用于存储客户端的地址信息
  77.         struct sockaddr_in peer;
  78.         socklen_t len = sizeof(peer);
  79.         // 使用accept函数接受一个传入连接,返回一个新的套接字描述符
  80.         int newfd = accept(sockfd_, (struct sockaddr*)&peer, &len);
  81.         // 如果接受连接失败,记录警告日志并返回-1
  82.         if(newfd < 0)
  83.         {
  84.             lg(Warning, "accept error, %s: %d", strerror(errno), errno);
  85.             return -1;
  86.         }
  87.         // 将客户端的IP地址和端口号转换为可读格式
  88.         char ipstr[64];
  89.         inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));
  90.         *clientip = ipstr;
  91.         *clientport = ntohs(peer.sin_port); // 转换端口号为主机字节序
  92.         return newfd; // 返回新的套接字描述符
  93.     }
  94.     // 连接到指定的IP地址和端口号的方法
  95.     bool Connect(const std::string &ip, const uint16_t &port)
  96.     {
  97.         // 创建一个sockaddr_in结构体,用于存储目标地址信息
  98.         struct sockaddr_in peer;
  99.         memset(&peer, 0, sizeof(peer)); // 清零结构体
  100.         peer.sin_family = AF_INET;     // 设置地址族为IPv4
  101.         peer.sin_port = htons(port);     // 设置端口号,使用网络字节序
  102.         inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr)); // 将IP地址转换为网络字节序
  103.         // 使用connect函数尝试连接到目标地址,如果失败则记录错误并返回false
  104.         int n = connect(sockfd_, (struct sockaddr*)&peer, sizeof(peer));
  105.         if(n == -1)
  106.         {
  107.             std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;
  108.             return false;
  109.         }
  110.         return true; // 连接成功返回true
  111.     }
  112.     // 关闭套接字的方法
  113.     void Close()
  114.     {
  115.         close(sockfd_); // 使用close函数关闭套接字
  116.     }
  117.     // 获取套接字文件描述符的方法
  118.     int Fd()
  119.     {
  120.         return sockfd_; // 返回套接字文件描述符
  121.     }
  122. private:
  123.     int sockfd_; // 套接字文件描述符
  124. };
复制代码
✅TcpServer.hpp

  1. // 预处理指令,确保头文件只被包含一次
  2. #pragma once
  3. // 引入必要的头文件
  4. #include <functional> // 用于支持 std::function
  5. #include <string>     // 用于支持 std::string
  6. #include <signal.h>   // 用于处理信号
  7. #include "Log.hpp"     // 自定义的日志头文件
  8. #include "Socket.hpp"  // 自定义的套接字头文件
  9. // 定义一个类型别名,用于简化函数参数
  10. using func_t = std::function<std::string(std::string &package)>;
  11. // 声明TcpServer类,用于创建和运行TCP服务器
  12. class TcpServer
  13. {
  14. public:
  15.     // 构造函数,初始化服务器的端口号和回调函数
  16.     TcpServer(uint16_t port, func_t callback) : port_(port), callback_(callback)
  17.     {
  18.     }
  19.    
  20.     // 初始化服务器的函数,创建套接字,绑定端口,开始监听
  21.     bool InitServer()
  22.     {
  23.         // 创建监听套接字
  24.         listensock_.Socket();
  25.         // 绑定端口到套接字
  26.         listensock_.Bind(port_);
  27.         // 开始监听连接请求
  28.         listensock_.Listen();
  29.         // 记录服务器初始化信息
  30.         lg(Info, "init server .... done");
  31.         return true;
  32.     }
  33.     // 启动服务器的主函数,处理客户端连接请求
  34.     void Start()
  35.     {
  36.         // 忽略子进程退出和管道信号
  37.         signal(SIGCHLD, SIG_IGN);
  38.         signal(SIGPIPE, SIG_IGN);
  39.         // 循环处理连接请求
  40.         while (true)
  41.         {
  42.             // 接受客户端的连接请求,获取客户端的IP地址和端口号
  43.             std::string clientip;
  44.             uint16_t clientport;
  45.             int sockfd = listensock_.Accept(&clientip, &clientport);
  46.             // 如果接受连接失败,则继续下一次循环
  47.             if (sockfd < 0)
  48.                 continue;
  49.             // 记录客户端连接信息
  50.             lg(Info, "accept a new link, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);
  51.             // 处理客户端请求,创建子进程提供服务
  52.             if (fork() == 0)
  53.             {
  54.                 // 关闭监听套接字
  55.                 listensock_.Close();
  56.                 std::string inbuffer_stream;
  57.                 // 循环读取客户端发送的数据
  58.                 while (true)
  59.                 {
  60.                     // 读取数据到缓冲区
  61.                     char buffer[1280];
  62.                     ssize_t n = read(sockfd, buffer, sizeof(buffer));
  63.                     // 如果读取到数据
  64.                     if (n > 0)
  65.                     {
  66.                         // 将读取的数据添加到输入缓冲区
  67.                         buffer[n] = 0;
  68.                         inbuffer_stream += buffer;
  69.                         // 记录调试信息
  70.                         lg(Debug, "debug:\n%s", inbuffer_stream.c_str());
  71.                         // 循环处理输入缓冲区中的数据
  72.                         while (true)
  73.                         {
  74.                             // 调用回调函数处理数据,并获取响应信息
  75.                             std::string info = callback_(inbuffer_stream);
  76.                             // 如果没有响应信息,则跳出循环
  77.                             if (info.empty())
  78.                                 break;
  79.                             // 记录调试信息
  80.                             lg(Debug, "debug, response:\n%s", info.c_str());
  81.                             // 记录调试信息
  82.                             lg(Debug, "debug:\n%s", inbuffer_stream.c_str());
  83.                             // 将响应信息发送回客户端
  84.                             write(sockfd, info.c_str(), info.size());
  85.                         }
  86.                     }
  87.                     // 如果客户端关闭连接,则跳出循环
  88.                     else if (n == 0)
  89.                         break;
  90.                     // 如果读取失败,则跳出循环
  91.                     else
  92.                         break;
  93.                 }
  94.                 // 子进程服务结束后退出
  95.                 exit(0);
  96.             }
  97.             // 关闭客户端套接字
  98.             close(sockfd);
  99.         }
  100.     }
  101.     // 析构函数
  102.     ~TcpServer()
  103.     {
  104.     }
  105. private:
  106.     // 服务器的端口号
  107.     uint16_t port_;
  108.     // 监听套接字
  109.     Sock listensock_;
  110.     // 回调函数,用于处理客户端请求
  111.     func_t callback_;
  112. };
复制代码
✅ServerCal.cpp

  1. // 引入必要的头文件
  2. #include "TcpServer.hpp" // 引入自定义的TCP服务器头文件
  3. #include "ServerCal.hpp"  // 引入自定义的计算器逻辑头文件
  4. #include <unistd.h>      // 引入UNIX标准函数库,用于系统调用如sleep等
  5. // #include "Daemon.hpp" // 引入自定义的守护进程头文件(当前被注释)
  6. // 定义Usage函数,用于输出程序的使用方法
  7. static void Usage(const std::string &proc)
  8. {
  9.     std::cout << "\nUsage: " << proc << " port\n" << std::endl; // 输出程序的使用方法
  10. }
  11. // 主函数,程序的入口点
  12. int main(int argc, char *argv[])
  13. {
  14.     // 检查命令行参数数量是否正确
  15.     if(argc != 2)
  16.     {
  17.         Usage(argv[0]); // 如果参数不正确,输出使用方法并退出程序
  18.         exit(0);
  19.     }
  20.     // 将命令行参数转换为端口号
  21.     uint16_t port = std::stoi(argv[1]);
  22.     // 创建计算器逻辑对象
  23.     ServerCal cal;
  24.     // 创建TCP服务器对象,绑定端口号和计算器逻辑对象的Calculator方法
  25.     TcpServer *tsvp = new TcpServer(port, std::bind(&ServerCal::Calculator, &cal, std::placeholders::_1));
  26.     // 初始化服务器
  27.     tsvp->InitServer();
  28.     // 调用守护进程相关函数,将程序转为守护进程运行(当前被注释)
  29.     // Daemon();
  30.     daemon(0, 0); // 调用守护进程函数,参数设置为0表示不进行标准输出和错误输出的重定向
  31.     // 启动服务器,开始监听和处理客户端请求
  32.     tsvp->Start();
  33.     return 0; // 程序正常结束
  34. }
复制代码
五、客户端模块

✅ClientCal.cpp

  1. // 引入所需的头文件
  2. #include <iostream> // 用于输入输出流
  3. #include <string>   // 用于字符串类
  4. #include <ctime>    // 用于时间相关函数
  5. #include <cassert>   // 用于断言检查
  6. #include <unistd.h>  // 用于UNIX标准函数,如sleep
  7. #include "Socket.hpp" // 自定义的套接字操作库
  8. #include "Protocol.hpp" // 自定义的通信协议库
  9. // 定义Usage函数,用于输出程序的使用方法
  10. static void Usage(const std::string &proc)
  11. {
  12.     std::cout << "\nUsage: " << proc << " serverip serverport\n"
  13.               << std::endl;
  14. }
  15. // 主函数,程序的入口点
  16. int main(int argc, char *argv[])
  17. {
  18.     // 检查命令行参数数量是否正确
  19.     if (argc != 3)
  20.     {
  21.         Usage(argv[0]);
  22.         exit(0);
  23.     }
  24.     // 从命令行参数获取服务器的IP地址和端口号
  25.     std::string serverip = argv[1];
  26.     uint16_t serverport = std::stoi(argv[2]);
  27.     // 创建套接字对象
  28.     Sock sockfd;
  29.     // 创建套接字
  30.     sockfd.Socket();
  31.     // 尝试连接到服务器
  32.     bool r = sockfd.Connect(serverip, serverport);
  33.     if(!r) return 1; // 如果连接失败,则退出程序
  34.     // 初始化随机数生成器
  35.     srand(time(nullptr) ^ getpid());
  36.     // 定义测试次数
  37.     int cnt = 1;
  38.     // 定义操作符字符串
  39.     const std::string opers = "+-*/%=-=&^";
  40.     // 定义输入缓冲区流
  41.     std::string inbuffer_stream;
  42.     // 循环发送请求并接收响应,直到发送了10次
  43.     while(cnt <= 10)
  44.     {
  45.         // 输出测试次数信息
  46.         std::cout << "===============第" << cnt << "次测试....., " << "===============" << std::endl;
  47.         // 生成随机数作为请求的参数
  48.         int x = rand() % 100 + 1;
  49.         usleep(1234); // 微秒级的暂停
  50.         int y = rand() % 100;
  51.         usleep(4321); // 微秒级的暂停
  52.         // 随机选择一个操作符
  53.         char oper = opers[rand() % opers.size()];
  54.         // 创建请求对象
  55.         Request req(x, y, oper);
  56.         // 打印请求的详细信息(调试用)
  57.         req.DebugPrint();
  58.         // 序列化请求对象到字符串
  59.         std::string package;
  60.         req.Serialize(&package);
  61.         // 将请求字符串编码为网络字节流
  62.         package = Encode(package);
  63.         // 通过套接字发送请求
  64.         write(sockfd.Fd(), package.c_str(), package.size());
  65.         // 读取服务器的响应
  66.         char buffer[128];
  67.         ssize_t n = read(sockfd.Fd(), buffer, sizeof(buffer)); // 读取响应到缓冲区
  68.         if(n > 0)
  69.         {
  70.             // 确保读取的字符串以空字符结尾
  71.             buffer[n] = 0;
  72.             // 将读取的内容添加到输入缓冲区流
  73.             inbuffer_stream += buffer;
  74.             // 输出接收到的完整响应
  75.             std::cout << inbuffer_stream << std::endl;
  76.             // 从输入缓冲区流中解码出响应内容
  77.             std::string content;
  78.             bool r = Decode(inbuffer_stream, &content); // 解码响应内容
  79.             assert(r); // 断言解码成功
  80.             // 反序列化响应内容到Response对象
  81.             Response resp;
  82.             r = resp.Deserialize(content);
  83.             assert(r); // 断言反序列化成功
  84.             // 打印响应的详细信息(调试用)
  85.             resp.DebugPrint();
  86.         }
  87.         // 输出测试分隔线
  88.         std::cout << "=================================================" << std::endl;
  89.         // 暂停一秒
  90.         sleep(1);
  91.         // 增加测试次数
  92.         cnt++;
  93.     }
  94.     // 关闭套接字
  95.     sockfd.Close();
  96.     // 程序正常退出
  97.     return 0;
  98. }
复制代码
六、计划方案总结


  • 模块化:代码被分为差别的模块,每个模块负责一个特定的功能。这种计划使得代码易于明白和维护。
  • 日志记录:通过Log类,服务器和客户端可以记录操作信息和错误信息,有助于调试和监控。
  • 自界说协议:通过Protocol类,界说了一个简朴的文本协议来传输请求和响应。协议的计划简朴明了,易于明白和实现。
  • 错误处理:服务器和客户端都实现了根本的错误处理逻辑,能够处理一些常见的错误情况。
  • 多进程模型:TcpServer类利用多进程模型来处理并发毗连,每个客户端毗连都在自己的进程中运行。
  • 安全性:固然代码中没有明白提到安全性措施,但在实际部署时,应该考虑利用加密通讯、身份验证等安全措施来掩护数据。
  • 性能优化:在生产情况中,可能需要进一步优化服务器的性能,比如利用多线程或异步I/O代替多进程模型,或者利用专门的高性能网络库。
  • 可扩展性:代码计划允许未来添加新的功能,比如支持更多的操作符或增加新的操作类型。
通过上述计划方案,我们可以得到一个根本的TCP服务器和客户端,它们可以根据自界说协议处理客户端请求,并支持根本的算术运算。这个系统可以作为“跨网络盘算器”的根本,通过界说合适的回调函数和协议格式来实现盘算器的功能。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

王柳

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表