跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 网络编程实战:基于 TCP 协议的简易计算器

C++ 网络编程实战项目展示 TCP 协议下的简易计算器实现。项目包含服务器端与客户端代码,利用 Socket 封装网络通信,通过 Fork 多进程处理并发请求。数据传输采用序列化与自定义编码格式,支持加减乘除及取余运算,并包含除零错误处理。代码结构清晰,涵盖日志记录、构建配置及协议定义,适合学习 Linux 网络编程基础。

并发大师发布于 2026/3/30更新于 2026/10/2489 浏览
C++ 网络编程实战:基于 TCP 协议的简易计算器

前言

根据前面的经验:网络套接字,序列化与反序列化,守护进程等等,写出一个小项目。

文件组成

.vscode/
log.hpp // 记录日志的头文件,用于定义日志功能的类和函数
Makefile // 项目的构建文件,定义如何使用 make 编译、链接代码
Protocol.hpp // 定义通信协议的头文件,包含数据格式、消息结构、序列化与反序列化规则等
ServerCal.hpp // 服务器计算相关的头文件,包含服务器端任务计算、数据处理等的声明
Socket.hpp // 定义套接字操作的头文件,包含与网络连接、数据传输等相关的类和函数
TcpClient/ // 存放与 TCP 客户端相关的文件夹
TcpClient.cc // TCP 客户端实现源文件,处理与服务器的连接、数据发送和接收
TcpClient.hpp // TCP 客户端头文件,声明客户端与服务器进行交互的类和方法
TcpServer/ // 存放与 TCP 服务器相关的文件夹
TcpServer.cc // TCP 服务器实现源文件,处理客户端的连接、接收和处理数据
TcpServer.hpp // TCP 服务器头文件,声明用于启动和管理服务器端的类和函数

TCP 服务端

TcpServer.hpp

#include <functional>
#include "Socket.hpp"      // 引入套接字操作相关的类
#include "ServerCal.hpp"   // 引入服务器端计算相关的类

// 定义回调函数类型,接受一个字符串引用并返回一个字符串
using func_t = std::function<std::string(std::string& package)>;

// 定义 TcpServer 类
class TcpServer {
public:
    // 构造函数,接受端口号和回调函数作为参数
    TcpServer(uint16_t port, func_t callback) : port_(port), callback_(callback) {}

    // 初始化函数,设置监听套接字
    void Init() {
        listensock_.Socket();       // 创建套接字
        listensock_.Bind(port_);    // 绑定端口
        listensock_.Listen();       // 开始监听
        lg(INFO, "Server Init");    // 输出日志,表示服务器已初始化
    }

    // 启动服务器函数,开始接收客户端连接
    void Start() {
        while (true) {
            std::string ip;
            uint16_t port;
            // 接受客户端连接,返回客户端的文件描述符
            int fd = listensock_.Accept(&ip, &port);
            lg(INFO, "Server Accept"); // 输出日志,表示有客户端连接

            // 创建子进程处理客户端请求
            if (fork() == 0) {
                lg(INFO, "fork success"); // 子进程成功创建
                listensock_.Close();      // 子进程关闭监听套接字
                std::string inbuffer_stream; // 缓存接收的数据
                while (true) {
                    char buffer[1024];
                    // 从客户端读取数据
                    int n = read(fd, &buffer, sizeof(buffer));
                    if (n > 0) {
                        lg(INFO, "read success"); // 输出日志,表示读取成功
                        buffer[n] = 0;            // 添加字符串结束符
                        inbuffer_stream += buffer;// 将读取的内容添加到缓冲区
                        while (true) {
                            // 调用回调函数处理接收到的包,并返回响应信息
                            std::string info = callback_(inbuffer_stream);
                            if (info.empty()) break; // 如果回调返回空字符串,退出循环
                            // 将处理后的数据写回客户端
                            write(fd, info.c_str(), info.size());
                        }
                    } else if (n == 0)
                        break; // 如果读取到 0,表示客户端断开连接
                    else
                        break; // 读取失败,退出循环
                }
                exit(0); // 子进程结束
            }
            // 关闭客户端连接的文件描述符,在父进程中继续等待下一个连接
            close(fd);
        }
    }

private:
    uint16_t port_;          // 服务器端口号
    Socket listensock_;      // 监听套接字对象
    func_t callback_;        // 回调函数,用于处理客户端发送的数据
};
代码说明:
  • 构造函数 TcpServer(uint16_t port, func_t callback):
    • 初始化服务器的端口和回调函数。回调函数 callback_ 将在收到客户端请求时被调用,用于处理数据。
  • Init():
    • 该方法用于初始化服务器,创建套接字、绑定端口并开始监听客户端连接。
  • Start():
    • 该方法是服务器的主循环,用于接受客户端的连接请求。
    • 每当有新的客户端连接时,服务器会通过 fork() 创建子进程来处理该客户端的请求。
    • 在子进程中,读取客户端发送的数据,并通过回调函数处理数据。处理结果将通过套接字返回给客户端。
    • 子进程处理完毕后会退出,而父进程继续等待新的客户端连接。
  • Socket listensock_:
    • Socket 类的对象,用于处理底层套接字操作,包括创建、绑定、监听、接收连接等。
  • 回调函数 func_t callback_:
    • 回调函数类型,负责处理客户端发来的数据包,并返回处理后的结果。

TcpServer.cc

#include <iostream>
#include <string>
#include <functional>
#include "log.hpp"     // 引入日志记录功能
#include "TcpServer.hpp" // 引入 TCP 服务器类
#include "Protocol.hpp"  // 引入协议相关类(用于数据格式、协议处理等)

// 测试请求函数的声明
void RequestTest();
// 测试响应函数的声明
void ResponseTest();

// 用法说明函数,输出程序的使用方式
void Usage() {
    std::cout << "\n\r" << "[Usage]:prot" << "\n" << std::endl;
}

// 主程序入口函数
int main(int argc, char* argv[]) {
    // 检查命令行参数的数量是否正确
    if (argc != 2) {
        // 如果参数数量不为 2,则调用 Usage 函数输出使用方法
        Usage();
        return -1; // 返回错误代码
    }
    // 输出服务器启动的日志
    lg(INFO, "Server start");
    ServerCal cal; // 创建一个 ServerCal 对象,用于计算请求数据
    // 获取命令行参数中的端口号,并将其转换为整数
    std::string port_1 = argv[1];
    uint16_t port = std::stoi(port_1); // 将字符串端口号转换为无符号短整型
    // 创建一个 TcpServer 对象,绑定回调函数
    // 使用 std::bind 绑定 ServerCal::Calculator 函数与 ServerCal 对象,作为回调函数
    TcpServer* tsur = new TcpServer(port, std::bind(&ServerCal::Calculator, &cal, std::placeholders::_1));
    // 初始化服务器
    tsur->Init();
    // 启动服务器
    tsur->Start();
    return 0; // 程序执行成功,返回 0
}
代码说明:
  • 头文件引入:
    • #include "log.hpp":用于记录日志,lg(INFO, "message") 可以在程序中输出日志信息。
    • #include "TcpServer.hpp":引入定义 TcpServer 类的头文件,用于创建 TCP 服务器。
    • #include "Protocol.hpp":引入协议相关的头文件,可能包含协议的定义和数据格式的处理方法。
  • Usage() 函数:
    • 该函数在命令行参数不正确时,输出程序的使用方式,提示用户如何正确传递命令行参数。
  • main() 函数:
    • 程序的入口点,首先检查命令行参数的数量。如果参数不正确,则调用 Usage() 输出帮助信息,并返回错误。
    • 如果参数正确,程序继续执行:
      • 创建 ServerCal 对象 cal,该对象负责计算接收到的数据。
      • 通过 argv[1] 获取并转换端口号。
      • 创建 TcpServer 对象 tsur,并将 ServerCal::Calculator 函数作为回调函数绑定到 TcpServer 中。此回调函数会处理客户端请求的数据。
      • 调用 tsur->Init() 初始化服务器,tsur->Start() 启动服务器,开始监听和处理客户端连接。

TCP 客户端

#include <iostream>      // 引入标准输入输出流,用于控制台输出
#include <unistd.h>      // 引入 UNIX 标准库,提供 sleep 函数和系统调用等功能
#include <time.h>        // 引入时间相关的库,用于生成随机数种子
#include <string>        // 引入字符串类
#include "log.hpp"       // 引入日志记录功能
#include "Socket.hpp"    // 引入套接字操作相关的类
#include "Protocol.hpp"  // 引入协议相关类(用于数据序列化、反序列化、编码、解码等)

// 用法说明函数,输出程序的使用方法
void Usage() {
    std::cout << "\n\r" << "[Usage]:port" << "\n" << std::endl;
}

// 主程序入口
int main(int argc, char* argv[]) {
    // 检查命令行参数的数量是否正确
    if (argc != 3) {
        Usage(); // 如果参数数量不为 3,调用 Usage 函数输出使用方法
        return -1; // 返回错误代码
    }
    srand(time(nullptr)); // 使用当前时间作为随机数生成的种子
    Socket sock;          // 创建一个套接字对象
    sock.Socket();        // 初始化套接字
    // 获取命令行参数中的 IP 地址和端口号
    std::string ip = argv[1];
    std::string port_1 = argv[2];
    std::cout << "port: " << port_1 << std::endl; // 输出连接的端口号
    // 将端口号从字符串转换为无符号短整型
    uint16_t port = std::stoi(port_1);
    // 连接到服务器
    sock.Connect(ip, port);
    lg(INFO, "connect successful"); // 输出连接成功的日志信息
    std::string op = "+-*/%"; // 运算符集合
    int cnt = 1;               // 计算请求的次数
    // 持续进行请求
    while (true) {
        cnt++; // 创建一个请求对象,生成随机数据
        Request req;
        req.x_ = rand() % 100 + 1; // 随机生成 1 到 100 之间的整数作为 x
        req.y_ = rand() % 100;     // 随机生成 0 到 99 之间的整数作为 y
        req.op_ = op[rand() % op.size()]; // 随机从运算符集合中选择一个运算符
        std::string con;             // 序列化后的字符串
        std::string out_stream;      // 编码后的数据流
        // 序列化请求对象
        bool r = req.Serialize(&con);
        if (!r) lg(WARNING, "Serialize failure"); // 如果序列化失败,输出警告日志
        out_stream = Encode(con);                 // 对序列化后的数据进行编码
        // 输出请求的详细信息
        std::cout << "============" << " The " << cnt << " Request " << "============" << std::endl;
        std::cout << "x: " << req.x_ << std::endl;
        std::cout << "op: " << req.op_ << std::endl;
        std::cout << "y: " << req.y_ << std::endl;
        // 发送请求数据给服务器
        write(sock.GetFd(), out_stream.c_str(), out_stream.size());
        // 从服务器接收响应数据
        std::string in_stream;
        char inbuffer[1024];
        int n = read(sock.GetFd(), &inbuffer, sizeof(inbuffer));
        if (n > 0) {
            inbuffer[n] = 0; // 添加字符串结束符
        }
        in_stream += inbuffer; // 将接收到的数据追加到输入流中
        std::string bon;       // 解码后的数据
        Response resp;         // 响应对象
        Decode(in_stream, &bon); // 解码接收到的数据
        resp.Deserialize(bon);   // 反序列化响应数据
        resp.Print();            // 打印响应结果
        sleep(1);                // 每次请求之间暂停 1 秒钟
        std::cout << "========================================" << std::endl; // 输出分隔线
    }
    return 0; // 程序结束
}
代码说明:
  • Usage():
    • 用于输出程序的使用说明,提示用户需要传递端口号作为参数。
  • main():
    • 主程序入口,首先检查命令行参数是否正确。如果参数不正确,调用 Usage() 输出帮助信息。
    • 使用 srand(time(nullptr)) 设置随机数种子,确保每次运行时生成不同的随机数。
    • 创建一个 Socket 对象,初始化并连接到指定的 IP 地址和端口号。
    • 在 while 循环中,程序持续进行请求:
      • 生成随机的请求数据,包括 x_、y_ 和运算符 op_。
      • 使用 Serialize() 方法将请求数据序列化,调用 Encode() 方法进行编码。
      • 发送请求数据到服务器。
      • 接收来自服务器的响应数据,使用 Decode() 进行解码,并使用 Deserialize() 方法将响应数据反序列化。
      • 最后,输出计算结果。
  • 日志记录:
    • 使用 lg(INFO, "message") 输出程序的日志信息,帮助调试和跟踪程序的状态。

计算器

#pragma once
#include <iostream>
#include "log.hpp"       // 引入日志功能,用于输出日志
#include "Protocol.hpp"  // 引入协议相关的类和功能

// 定义错误符号的枚举类型
enum err_symbol {
    div_zero = 1, // 除法为零错误
    mod_zero      // 取余为零错误
};

// 服务器计算类
class ServerCal {
public:
    // 构造函数,初始化 ServerCal 对象
    ServerCal() {}

    // 计算函数,根据传入的请求计算结果
    Response Calculate(Request &req) {
        Response resp(0, 0); // 创建一个默认响应对象,初始值为 0
        // 根据请求的运算符执行相应的数学运算
        switch (req.op_) {
        case '+': // 加法
            resp.result_ = req.Getx() + req.Gety();
            break;
        case '-': // 减法
            resp.result_ = req.Getx() - req.Gety();
            break;
        case '*': // 乘法
            resp.result_ = req.Getx() * req.Gety();
            break;
        case '/': // 除法
            if (req.Gety() == 0) { // 检查除数是否为零
                resp.code_ = div_zero; // 如果除数为零,设置错误代码为 `div_zero`
                break;
            }
            resp.result_ = req.Getx() / req.Gety(); // 执行除法运算
            break;
        case '%': // 取余
            if (req.Gety() == 0) { // 检查除数是否为零
                resp.code_ = mod_zero; // 如果除数为零,设置错误代码为 `mod_zero`
                break;
            }
            resp.result_ = req.Getx() % req.Gety(); // 执行取余运算
            break;
        default:
            break; // 如果运算符不匹配,直接退出
        }
        return resp; // 返回计算结果
    }

    // 计算器函数,处理客户端发送的请求数据包
    std::string Calculator(std::string &package) {
        // 移除报头部分,解码数据包
        std::string context;
        bool rDecode = Decode(package, &context); // 解码包头
        if (!rDecode) return "";                  // 解码失败时返回空字符串
        // 反序列化请求数据
        Request req;
        req.Deserialize(context);
        // 根据请求数据进行计算
        Response resp;
        resp = Calculate(req);
        sleep(3); // 模拟计算延时(可能用于测试)
        // 序列化计算结果并返回
        std::string in;
        bool r = resp.Serialize(&in);
        if (!r) return ""; // 如果序列化失败,返回空字符串
        // 编码处理后的结果
        context = "";
        context = Encode(in); // 编码计算结果并准备返回
        return context;       // 返回最终的编码结果
    }

    // 析构函数,销毁 ServerCal 对象
    ~ServerCal() {}
};
代码说明:
1. ServerCal 类:
  • ServerCal 类包含了处理数学计算的逻辑,通过接收客户端发送的请求并计算结果,然后返回计算结果给客户端。
2. Calculate(Request &req) 函数:
  • 该函数接收一个 Request 对象,表示客户端发送的请求。
  • 根据请求的操作符(op_),执行相应的数学运算(加、减、乘、除、取余)。如果请求中存在除数为零的情况(除法和取余),则返回相应的错误代码(div_zero 或 mod_zero)。
  • 最终返回一个 Response 对象,包含计算结果。
3. Calculator(std::string &package) 函数:
  • 该函数接收客户端发送的包含请求数据的字符串 package。
  • 首先解码数据包头部,去除冗余信息并获取有效数据。
  • 然后将解码后的数据反序列化为 Request 对象。
  • 使用 Calculate() 方法进行实际的计算。
  • 计算结果通过 Serialize() 序列化,之后进行编码。
  • 返回最终的结果给客户端。

请求和响应服务

#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <jsoncpp/json/json.h> // 引入 JSON 库,用于数据的序列化和反序列化
#include "log.hpp"             // 引入日志功能

// 定义协议分隔符常量
const std::string space_sep = " ";   // 空格分隔符
const std::string protocal_sep = "\n"; // 协议分隔符(换行符)

// 编码函数:将数据进行编码,格式为 "len/n x + y/n"
std::string Encode(std::string &context) {
    std::string s = std::to_string(context.size()); // 获取数据长度并转换为字符串
    s += protocal_sep;                              // 添加协议分隔符
    s += context;                                   // 添加实际数据内容
    s += protocal_sep;                              // 添加协议分隔符
    return s;                                       // 返回编码后的数据
}

// 解码函数:将协议数据解码,移除头部长度并提取有效数据
bool Decode(std::string &package, std::string *context) {
    auto pos = package.find(protocal_sep); // 查找协议分隔符位置
    if (pos == std::string::npos) return false; // 如果没有找到协议分隔符,返回解码失败
    std::string len_str = package.substr(0, pos); // 获取数据长度部分
    std::size_t len = std::stoi(len_str);         // 将长度字符串转换为整数
    int total_len = len + len_str.size() + 2;     // 总长度 = 数据长度 + 协议头的长度(包括分隔符)
    if (package.size() < total_len) return false; // 检查数据包是否完整
    *context = package.substr(pos + 1, len);      // 提取有效数据
    // 移除解码数据,剩余部分为下一个包的内容
    package.erase(0, total_len);
    return true; // 解码成功
}

// 请求类(Client Request)
class Request {
public:
    // 构造函数,初始化请求数据
    Request(int data1, char op, int data2) : x_(data1), op_(op), y_(data2) {}
    Request() {} // 默认构造函数
    ~Request() {} // 析构函数

    void Print() // 打印请求内容
    {
        std::cout << x_ << op_ << y_ << std::endl;
    }

    // 序列化函数:将请求数据(x, op, y)序列化为字符串
    bool Serialize(std::string *out) {
#ifdef Myself
        // 在 Myself 模式下:直接构建字符串格式 "x op y"
        std::string s = std::to_string(x_);
        s += space_sep;
        s += op_;
        s += space_sep;
        s += std::to_string(y_);
        *out = s; // 返回序列化后的数据
        return true;
#else
        // 否则使用 JSON 格式序列化
        Json::Value root;
        root["x"] = x_;
        root["y"] = y_;
        root["op"] = op_;
        Json::FastWriter w;
        *out = w.write(root); // 返回序列化后的 JSON 字符串
        return true;
#endif
    }

    // 反序列化函数:从字符串中提取数据(x, op, y)
    bool Deserialize(const std::string &in) {
#ifdef Myself
        // 在 Myself 模式下:解析有效载荷
        auto pos1 = in.find(space_sep);
        if (pos1 == std::string::npos) return false; // 如果没有找到空格分隔符,返回失败
        std::string part_x = in.substr(0, pos1);     // 提取 x 部分
        auto pos2 = in.rfind(space_sep);
        std::string oper = in.substr(pos1 + 1, pos2); // 提取操作符部分
        std::string part_y = in.substr(pos2 + 1);     // 提取 y 部分
        if (pos2 != pos1 + 2) return false;           // 如果格式不正确,返回失败
        op_ = in[pos1 + 1];                           // 设置操作符
        x_ = std::stoi(part_x);                       // 转换 x 为整数
        y_ = std::stoi(part_y);                       // 转换 y 为整数
        return true;
#else
        // 否则使用 JSON 格式反序列化
        Json::Value root;
        Json::Reader r;
        r.parse(in, root); // 解析 JSON 字符串
        x_ = root["x"].asInt(); // 提取 x
        y_ = root["y"].asInt(); // 提取 y
        op_ = root["op"].asString()[0]; // 提取操作符(假设只有一个字符)
        return true;
#endif
    }

    int Getx() { return x_; } // 获取 x 值
    int Gety() { return y_; } // 获取 y 值
    char Getop() { return op_; } // 获取操作符

private:
    int x_;   // 操作数 x
    char op_; // 运算符
    int y_;   // 操作数 y
};

// 响应类(Server Response)
class Response {
public:
    Response(int ret, int code) : result_(ret), code_(code) {}
    Response() {}
    ~Response() {}

    // 序列化函数:将响应数据(result_, code_)序列化为字符串
    bool Serialize(std::string *out) {
#ifdef Myself
        // 在 Myself 模式下:构建字符串格式 "result code"
        std::string s = std::to_string(result_);
        s += space_sep;
        s += std::to_string(code_);
        *out = s;
        return true;
#else
        // 否则使用 JSON 格式序列化
        Json::Value root;
        root["result"] = result_;
        root["code"] = code_;
        Json::FastWriter w;
        *out = w.write(root); // 返回序列化后的 JSON 字符串
        return true;
#endif
    }

    // 反序列化函数:从字符串中提取响应数据(result_, code_)
    bool Deserialize(const std::string &in) {
#ifdef Myself
        auto pos = in.find(space_sep);
        std::string res = in.substr(0, pos);
        std::string code = in.substr(pos + 1);
        if (pos != in.rfind(space_sep)) return false; // 如果没有找到正确的分隔符,返回失败
        result_ = std::stoi(res);                     // 转换 result_为整数
        code_ = std::stoi(code);                      // 转换 code_为整数
        return true;
#else
        // 否则使用 JSON 格式反序列化
        Json::Value root;
        Json::Reader r;
        r.parse(in, root); // 解析 JSON 字符串
        result_ = root["result"].asInt(); // 提取 result_
        code_ = root["code"].asInt();     // 提取 code_
        return true;
#endif
    }

    void Print() // 打印响应内容
    {
        std::cout << "result_: " << result_ << " code_: " << code_ << std::endl;
    }

private:
    int result_; // 计算结果
    int code_;   // 错误代码(例如除数为零等错误)
};
代码总结:
1. Encode 和 Decode:
  • Encode:将字符串的大小和内容打包为带有协议头的格式,便于传输。
  • Decode:从带有协议头的包中提取有效数据,移除头部信息并返回有效部分。
2. Request 类:
  • 表示客户端的计算请求,包括 x_、op_、y_ 三个字段(操作数和运算符)。
  • Serialize 和 Deserialize 用于数据的序列化和反序列化。
  • 提供 Getx、Gety 和 Getop 获取请求参数的函数。
3. Response 类:
  • 表示服务器的响应,包括计算结果 result_ 和错误代码 code_。
  • Serialize 和 Deserialize 用于数据的序列化和反序列化。
  • 提供 Print 方法输出响应内容。

网络组件

#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <strings.h>     // 用于处理字节操作
#include <sys/types.h>   // 引入系统数据类型
#include <sys/socket.h>  // 引入套接字 API
#include <arpa/inet.h>   // 引入 IP 地址相关函数
#include <netinet/in.h>  // 引入 IPv4 协议结构和常量
#include "log.hpp"       // 引入日志记录功能

int backlog = 10; // 定义监听队列的最大连接数

// 定义错误枚举,表示不同的错误类型
enum err {
    Socketerr = 1,   // 套接字创建失败
    Bindeterr,       // 套接字绑定失败
    Listeneterr,     // 监听失败
    Accepteterr      // 接受连接失败
};

// 套接字类,封装了套接字的创建、绑定、监听、连接和关闭等操作
class Socket {
public:
    // 默认构造函数
    Socket() {}
    // 析构函数
    ~Socket() {}

    // 创建套接字
    void Socket() {
        sockfd_ = socket(AF_INET, SOCK_STREAM, 0); // 创建一个 IPv4 TCP 套接字
        if (sockfd_ < 0) {                         // 检查套接字是否创建成功
            lg(FATAL, "Socket error: %d,%s", errno, strerror(errno)); // 输出日志并退出
            exit(Socketerr);                         // 错误退出
        }
    }

    // 绑定套接字到指定端口
    void Bind(uint16_t port) {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        bzero(&peer, len); // 清空结构体
        peer.sin_port = htons(port); // 设置端口(使用网络字节顺序)
        peer.sin_family = AF_INET;   // 设置地址族为 IPv4
        peer.sin_addr.s_addr = INADDR_ANY; // 绑定到所有本地接口
        // 执行绑定操作
        if (bind(sockfd_, (struct sockaddr*)&(peer), len) < 0) {
            lg(FATAL, "Bind error: %d,%s", errno, strerror(errno)); // 输出日志并退出
            exit(Bindeterr);                                         // 错误退出
        }
    }

    // 开始监听连接请求
    void Listen() {
        if (listen(sockfd_, backlog) < 0) { // 监听套接字,最大连接数为 `backlog`
            lg(FATAL, "Listen error: %d,%s", errno, strerror(errno)); // 输出日志并退出
            exit(Listeneterr);                                         // 错误退出
        }
    }

    // 接受客户端连接
    int Accept(std::string *clientip, uint16_t *clientport) {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        bzero(&peer, len); // 清空结构体
        // 等待并接受一个客户端的连接请求
        int newfd = accept(sockfd_, (struct sockaddr*)&(peer), &len);
        if (newfd < 0) {
            lg(FATAL, "Accept error: %d,%s", errno, strerror(errno)); // 输出日志并退出
            exit(Accepteterr);                                         // 错误退出
        }
        // 获取客户端的 IP 地址
        char ip[64];
        inet_ntop(AF_INET, &peer.sin_addr.s_addr, ip, sizeof(ip));
        *clientip = ip; // 返回客户端 IP
        *clientport = ntohs(peer.sin_port); // 返回客户端端口号(使用主机字节序)
        return newfd; // 返回新的文件描述符
    }

    // 连接到服务器
    bool Connect(const std::string &ip, const uint16_t &port) {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        bzero(&peer, len); // 清空结构体
        peer.sin_addr.s_addr = inet_addr(ip.c_str()); // 设置目标 IP
        peer.sin_port = htons(port);                  // 设置目标端口(使用网络字节顺序)
        peer.sin_family = AF_INET;                    // 设置地址族为 IPv4
        int n = connect(sockfd_, (struct sockaddr*)&(peer), len); // 连接到服务器
        if (n < 0) {
            lg(WARNING, "Connect error: %d,%s", errno, strerror(errno)); // 输出日志并返回失败
            return false;
        }
        return true; // 连接成功
    }

    // 关闭套接字
    void Close() {
        close(sockfd_);
    }

    // 获取套接字的文件描述符
    int GetFd() {
        return sockfd_; // 返回套接字的文件描述符
    }

private:
    int sockfd_; // 套接字的文件描述符
};
代码说明:
1. Socket():
  • 创建一个 IPv4 TCP 套接字,并检查是否成功创建。如果创建失败,输出日志并退出程序。
2. Bind(uint16_t port):
  • 将套接字绑定到指定的端口,并设置为监听来自所有网络接口的请求。如果绑定失败,输出日志并退出程序。
3. Listen():
  • 开始监听客户端连接,backlog 变量定义了最大等待连接队列。如果监听失败,输出日志并退出程序。
4. Accept(std::string *clientip, uint16_t *clientport):
  • 接受一个客户端的连接请求,并返回一个新的文件描述符,用于与客户端进行通信。还会返回客户端的 IP 地址和端口号。
5. Connect(const std::string &ip, const uint16_t &port):
  • 连接到指定的服务器 IP 和端口。如果连接失败,输出日志并返回 false;否则返回 true。
6. Close():
  • 关闭当前套接字,释放资源。
7. GetFd():
  • 返回套接字的文件描述符,这个文件描述符可用于读取或写入数据。

目录

  1. 前言
  2. 文件组成
  3. TCP 服务端
  4. TcpServer.hpp
  5. 代码说明:
  6. TcpServer.cc
  7. 代码说明:
  8. TCP 客户端
  9. 代码说明:
  10. 计算器
  11. 代码说明:
  12. 1. ServerCal 类:
  13. 2. Calculate(Request &req) 函数:
  14. 3. Calculator(std::string &package) 函数:
  15. 请求和响应服务
  16. 代码总结:
  17. 1. Encode 和 Decode:
  18. 2. Request 类:
  19. 3. Response 类:
  20. 网络组件
  21. 代码说明:
  22. 1. Socket():
  23. 2. Bind(uint16_t port):
  24. 3. Listen():
  25. 4. *Accept(std::string clientip, uint16_t clientport)*:
  26. 5. Connect(const std::string &ip, const uint16_t &port):
  27. 6. Close():
  28. 7. GetFd():

更多推荐文章

查看全部
  • 逻辑漏洞挖掘原因与实战案例解析
  • STL map/multimap 深度剖析:接口使用与核心特性详解
  • Python 爬虫基础教程:从原理到实战代码详解
  • QGIS 到 WebGIS:网络瓦片地图离线化与可视化实战
  • Llama-2-7B 昇腾 NPU 测评:性能数据、场景适配与硬件选型
  • Python 数据统计实战指南
  • 数据结构:快速排序进阶优化
  • 前端高频面试题:TypeScript 基础与进阶
  • AI 产品经理全流程工作指南:从需求定义到模型验收
  • Java 中 char、String、StringBuilder 与 StringBuffer 核心解析
  • CCF-GESP 2026 年 3 月五级 C++ 真题:找数
  • OpenClaw 浏览器控制:利用 Chrome Debug 模式实现免登录自动化
  • 基于Python的豆瓣电影数据可视化分析设计与实现
  • 栈结构在算法题中的五种经典应用:去重、退格、计算、解码与验证
  • AI 系统视角下的大模型发展与技术挑战
  • Git 远程仓库同步实战:基础操作与避坑指南
  • Home Assistant 界面美化指南:打造个性化智能家居界面
  • AIGC 技术全景解析:大语言模型、扩散模型与多模态应用指南
  • ToDesk、顺网云与海马云部署 DeepSeek 模型对比评测
  • 使用腾讯 Cloud Studio 免费算力部署 Llama3.1 私有化模型

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online