【Linux网络】打造初级网络计算器 - 从协议设计到服务实现

📢博客主页:https://blog.csdn.net/2301_779549673

📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson

📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!

📢本文由 JohnKi 原创,首发于 CSDN🙉

📢未来很长,值得我们全力奔赴更美好的生活✨

文章目录

  • 🏳️‍🌈一、protocol.hpp
    • [1.1 Request类](#1.1 Request类)
      • [1.1.1 基本结构](#1.1.1 基本结构)
      • [1.1.2 构造、析构函数](#1.1.2 构造、析构函数)
      • [1.1.3 序列化函数](#1.1.3 序列化函数)
      • [1.1.4 反序列化函数](#1.1.4 反序列化函数)
      • [1.1.5 其他函数](#1.1.5 其他函数)
    • 1.2、Response类
      • [1.2.1 基本结构](#1.2.1 基本结构)
      • [1.2.2 构造析构函数](#1.2.2 构造析构函数)
      • [1.2.3 序列化函数](#1.2.3 序列化函数)
      • [1.2.4 反序列化函数](#1.2.4 反序列化函数)
      • [1.2.5 打印结果](#1.2.5 打印结果)
    • [1.3 Factory类](#1.3 Factory类)
    • [1.4 报头](#1.4 报头)
      • [1.4.1 添加报头](#1.4.1 添加报头)
      • [1.4.2 解析报头](#1.4.2 解析报头)
  • 🏳️‍🌈二、Service.hpp
    • [2.1 方法回调](#2.1 方法回调)
    • [2.2 成员变量 + 构造](#2.2 成员变量 + 构造)
    • [2.3 IOExcute](#2.3 IOExcute)
  • 🏳️‍🌈三、NetCal.hpp
  • 🏳️‍🌈四、TcpClient.cpp
  • 🏳️‍🌈五、整体代码
    • [5.1 protocol.hpp](#5.1 protocol.hpp)
    • [5.2 Service.hpp](#5.2 Service.hpp)
    • [5.3 Socket.hpp](#5.3 Socket.hpp)
    • [5.4 TcpServer.cpp](#5.4 TcpServer.cpp)
    • [5.5 TcpServer.hpp](#5.5 TcpServer.hpp)
    • [5.6 NetCal.hpp](#5.6 NetCal.hpp)
    • [5.7 TcpClient.cpp](#5.7 TcpClient.cpp)
    • [5.8 TcpClient.hpp](#5.8 TcpClient.hpp)
    • [5.9 Makefile](#5.9 Makefile)
  • 👥总结

🏳️‍🌈一、protocol.hpp

该文件实现序列化与反序列使用到的类和相关函数(加报头解报头)!

1.1 Request类

1.1.1 基本结构

该类有三个成员变量,_x,_y,_oper(运算符号),序列化,反序列化,构造,析构及其他获取成员变量与打印的函数!

复制代码
namespace protocol{
    class Request{
        public:
            Request(){}
            // 序列化 将结构化转成字符串
            bool Serialize(std::string& out);
            // 反序列化 将字符串转成结构化
            bool Deserialize(const std::string& in);
            void Print();
            int X();
            int Y();
            char Oper();
            ~Request(){}
        private:
            int _x;
            int _y;
            char _oper; // 计算符号
    };
}

1.1.2 构造、析构函数

为了方便后面的使用,此处实现两个构造函数,一个无参,一个带参函数,析构函数无需处理!

复制代码
// 构造函数 - 无参
Request() {}
// 构造函数 - 有参
Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper) {}
// 析构函数
~Request(){}

1.1.3 序列化函数

序列化即将结构化转成字符串,并将字符串以输出型参数输出

复制代码
// 序列化 将结构化转成字符串
            bool Serialize(std::string* out){
                // 使用现成的库,xml,json,protobuf等
                // 这里使用json库
                Json::Value root;
                root["x"] = _x;
                root["y"] = _y;
                root["oper"] = _oper;
                Json::FastWriter writer;
                std::string s = writer.write(root);
                *out = s;
                return true;
            }

1.1.4 反序列化函数

反序列化即将字符串转成结构化,参数传入字符串!

复制代码
// 反序列化 将字符串转成结构化
bool Deserialize(const std::string& in) {
    Json::Value root;
    Json::Reader reader;
    bool res = reader.parse(in, root);

    _x = root["x"].asInt();
    _y = root["y"].asInt();
    _oper = root["oper"].asInt();

    return true;
}

1.1.5 其他函数

复制代码
void Print() {
    std::cout << _x << std::endl;
    std::cout << _y << std::endl;
    std::cout << _oper << std::endl;
}

int X() { return _x; }
int Y() { return _y; }
char Oper() { return _oper; }

void SetValue(int x, int y, char oper) {
    _x = x;
    _y = y;
    _oper = oper;
}

1.2、Response类

1.2.1 基本结构

这个类我们需要组织发送给客户端的响应,需要三个成员变量,_result(计算结果),_code(自定义错误码),_desc(错误码描述)

内部主要是 序列化(发送)反序列化(接受) 函数!

复制代码
class Response {
public:
    Response() : _result(0), _code(0), _desc("success") {}
    bool Serialize(std::string* out);
    bool Deserialize(const std::string& in);
    ~Response() {}

public:
    int _result;
    int _code;         // 错误码 0 success, 1 fail, 2 fatal
    std::string _desc; // 错误码描述
};

1.2.2 构造析构函数

构造函数 直接手动初始化(结果和错误码初始化为0,描述默认初始化为success)
析构函数无需处理!

复制代码
Response() : _result(0), _code(0), _desc("success") {}
~Response() {}

1.2.3 序列化函数

这里和 request 类如出一辙,只需要构造相应的JSON结果,然后利用紧凑方法,构造出JSON风格的字符串就行了

复制代码
bool Serialize(std::string* out) {
    Json::Value root;
    root["result"] = _result;
    root["code"] = _code;
    root["desc"] = _desc;
    Json::FastWriter writer;
    std::string s = writer.write(root);
    *out = s;
    return true;
}

1.2.4 反序列化函数

反序列化即将字符串转成结构化,参数传入字符串!

复制代码
// 反序列化 - 将JSON字符串反序列化成成员变量
bool Deserialize(const std::string& in) {
    Json::Value root;
    Json::Reader reader;

    // parse 的作用是将 JSON 字符串解析成 Json::Value 对象
    bool res = reader.parse(in, root);

    _result = root["result"].asInt();
    _code = root["code"].asInt();
    _desc = root["desc"].asString();

    return true;
}

1.2.5 打印结果

将成员变量以字符串形式打印出来即可!

复制代码
void PrintResult(){
    std::cout << "result: " << _result << ", code: " << _code 
    << ", desc: " << _desc << std::endl;
}

1.3 Factory类

  • 因为 Request类Response类 可能频繁创建,因此我们可以设计一个工厂类内部设计两个创建类的静态函数(没有this指针,外部直接调用函数即可)!

    class Factory {
    public:
    static std::shared_ptr<Request> BuildRequestDefault() {
    return std::make_shared<Request>();
    }
    static std::shared_ptr<Response> BuildResponseDefault() {
    return std::make_shared<Response>();
    }
    };

1.4 报头

在实际的网络通信中,传的不仅仅是序列化后的字符串,还有报头信息,此处我们也设计一下报头信息

  1. "len"\r\n"{json}"\r\n -- 完整的报文
  2. len 有效荷载长度
  3. \r\n(第一个):区分 len 和 json 串
  4. \r\n(第二个):暂时没用

1.4.1 添加报头

复制代码
// 添加报头
std::string AddHeader(const std::string& jsonstr) {
    int len = jsonstr.size();
    std::string lenstr = std::to_string(len);
    return lenstr + sep + jsonstr + sep;
}

1.4.2 解析报头

注意:可能没有一个有效信息或者有多个有效信息!

复制代码
// 解析报头
// 去掉前面的长度和分隔符与有效信息后面的分隔符
std::string ParseHeader(std::string& jsonstr) {
    // 分析
    auto pos = jsonstr.find(sep); // 在json风格字符串中找第一个分隔符
    if (pos == std::string::npos)
        return std::string(); // 找不到分隔符,返回空字符串

    // 获取 len
    std::string lenstr = jsonstr.substr(0, pos); // 取出长度字符串
    int len = std::stoi(lenstr);                 // 转成整数

    // 计算一个完整的报文应该是多长
    int totallen = lenstr.size() + len + 2 * sep.size();
    // 若传进来的字符串长度小于报文总长,说明没有一个完整的有效信息,返回空
    if (jsonstr.size() < totallen)
        return std::string();

    // 取出有效信息
    std::string validstr = jsonstr.substr(pos + sep.size(), len);
    // 去掉最后的分隔符
    jsonstr.erase(0, totallen);

    return validstr;
}

🏳️‍🌈二、Service.hpp

我们需要在这里处理响应的请求和进行响应

2.1 方法回调

因此我们需要提供一个方法回调,来处理json化的请求和响应

参数是请求类的指针,返回值是应答类的指针!

复制代码
// 参数是请求类的指针,返回值是应答类的指针!    
using process_t = std::function<std::shared_ptr<Response>(std::shared_ptr<Request>)>;

2.2 成员变量 + 构造

所以我们就需要添加一个方法回调的成员变量,并且在构造中赋值

2.3 IOExcute

IOExcute()函数进行客户端与服务端 的通信,并处理发送过来的信息(调用执行方法),

1、接收消息

2、报文解析(保证获取至少获得一条有效信息,没有则继续接受消息)

3、反序列化(将字符串转成结构化)

4、业务处理(调用构造函数传入的回调函数)

5、序列化应答

6、添加len长度(报头)

7、发送回去

复制代码
// 处理请求并给出响应
void IOExcute(SockPtr sock, InetAddr& addr) {
    std::string message; // 写在while外,存储信息
    while (true) {
        // 1. 负责读取
        ssize_t n = sock->Recv(&message);
        if (n == 0) {
            LOG(LogLevel::INFO)
                << "client " << addr.AddrStr().c_str() << " disconnected";
            break;
        } else if (n < 0) {
            LOG(LogLevel::ERROR)
                << "recv error for client: " << addr.AddrStr().c_str();
            break;
        }

        std::cout << "----------------------------------------" << std::endl;
        std::cout << "client " << addr.AddrStr().c_str()
                  << " send message: " << message << std::endl
                  << std::endl;

        // 2. 负责解析,提取报头和有效荷载
        // 但此时我们仍然无法保证读到的是完整报文
        std::string package = ParseHeader(message);
        if (package.empty())
            continue;
        auto req = Factory::BuildRequestDefault();
        std::cout << "package:  " << package << std::endl;

        // 3. 反序列化
        req->Deserialize(package);

        // 4. 业务处理
        auto resp = _process(req);

        // 5. 序列化
        std::string respjson;
        resp->Serialize(&respjson);
        std::cout << "respjson: " << respjson << std::endl;

        // 6. 添加报头
        std::string respstr = AddHeader(respjson);
        std::cout << "respstr:  " << respstr << std::endl;

        // 7. 负责写回
        sock->Send(respstr);
    }
}

因为我们是循环获取报文,所以可能一次获取的报文不完整,因此我们需要保证recv过来的message能够保留

复制代码
ssize_t Recv(std::string* out) override {
    char inbuffer[4096];
    ssize_t n = ::recv(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0);
    if (n > 0) {
        inbuffer[n] = 0;
        *out += inbuffer; // 可能一次读取不成功
    }
    return n;
}

🏳️‍🌈三、NetCal.hpp

这个类就是来处理计算机的具体过程的那个回调函数的类要传进一个请求类,返回一个响应类

他不需要成员变量,构造、析构为空即可

std::shared_ptr<Response> Calculator(std::shared_ptr<Request> req){}

复制代码
std::shared_ptr<Response> Calculator(std::shared_ptr<Request> req) {
    auto rsp = Factory::BuildResponseDefault();
    switch (req->Oper()) {
    case '+':
        rsp->_result = req->X() + req->Y();
        break;
    case '-':
        rsp->_result = req->X() - req->Y();
        break;
    case '*':
        rsp->_result = req->X() * req->Y();
        break;
    case '/':
        if (req->Y() == 0) {
            rsp->_result = -1;
            rsp->_desc = "division by zero";
        } else {
            rsp->_result = req->X() / req->Y();
        }
        break;
    case '%':
        if (req->Y() == 0) {
            rsp->_result = -1;
            rsp->_desc = "mod by zero";
        } else {
            rsp->_result = req->X() % req->Y();
        }
    default:
        rsp->_result = -1;
        rsp->_desc = "unknown operator";
        break;
    }
    return rsp;
}

🏳️‍🌈四、TcpClient.cpp

该文件用户创建TcpServer类对象,并调用执行函数运行客户端!

通信操作主要包括以下七步:

1、序列化

2、添加长度报头字段

3、发送数据

4、读取应答,response

5、报文解析,提取报头和有效载荷

6、反序列化

7、打印结果

复制代码
#include "TcpServer.hpp"
#include "Service.hpp"
#include "NetCal.hpp"

using namespace TcpServerModule;

int main(int argc, char* argv[]){
    if(argc != 2){
        std::cerr << "Usage: " << argv[0] << " port" << std::endl;
        Die(1);
    }
    uint16_t port = std::stoi(argv[1]);

    NetCal netcal;
    IOService service(
        [&netcal](std::shared_ptr<Request> req) {
            return netcal.Calculator(req);
        }
    );

    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(
        std::bind(&IOService::IOExcute, &service, std::placeholders::_1, std::placeholders::_2), port);

    tsvr->Loop();


    return 0;
}

🏳️‍🌈五、整体代码

5.1 protocol.hpp

复制代码
#include <iostream>
#include <jsoncpp/json/json.h>


namespace protocol{
    // `"len"\r\n"{json}"\r\n`  -- 完整的报文
    static const std::string sep = "\r\n";  // 分隔符

    // 添加报头
    std::string AddHeader(const std::string& jsonstr){
        int len = jsonstr.size();
        std::string lenstr = std::to_string(len);
        return lenstr + sep + jsonstr + sep;
    }

    // 解析报头
    // 去掉前面的长度和分隔符与有效信息后面的分隔符
    std::string ParseHeader(std::string& jsonstr){
        // 分析
        auto pos = jsonstr.find(sep);       // 在json风格字符串中找第一个分隔符
        if(pos == std::string::npos)
            return std::string();           // 找不到分隔符,返回空字符串

        // 获取 len
        std::string lenstr = jsonstr.substr(0, pos);  // 取出长度字符串
        int len = std::stoi(lenstr);                  // 转成整数

        // 计算一个完整的报文应该是多长
        int totallen = lenstr.size() + len + 2 * sep.size();
        // 若传进来的字符串长度小于报文总长,说明没有一个完整的有效信息,返回空
        if(jsonstr.size() < totallen)
            return std::string();

        // 取出有效信息
        std::string validstr = jsonstr.substr(pos + sep.size(), len);
        // 去掉最后的分隔符
        jsonstr.erase(0, totallen);

        return validstr;
    }


    class Request{
        public:
            // 构造函数 - 无参
            Request(){}
            // 构造函数 - 有参
            Request(int x, int y, char oper): _x(x), _y(y), _oper(oper){}

            // 序列化 将结构化转成字符串
            bool Serialize(std::string* out){
                // 使用现成的库,xml,json,protobuf等
                // 这里使用json库
                Json::Value root;
                root["x"] = _x;
                root["y"] = _y;
                root["oper"] = _oper;
                Json::FastWriter writer;
                std::string s = writer.write(root);
                *out = s;
                return true;
            }
            // 反序列化 将字符串转成结构化
            bool Deserialize(const std::string& in){
                Json::Value root;
                Json::Reader reader;

                // parse 的作用是将 JSON 字符串解析成 Json::Value 对象
                bool res = reader.parse(in, root);

                _x = root["x"].asInt();
                _y = root["y"].asInt();
                _oper = root["oper"].asInt();

                return true;
            }
            void Print(){
                std::cout << _x << std::endl;
                std::cout << _y << std::endl;
                std::cout << _oper << std::endl;
            }

            int X(){ return _x;}
            int Y(){ return _y;}
            char Oper(){ return _oper;}

            void SetValue(int x, int y, char oper){
                _x = x;
                _y = y;
                _oper = oper;
            }

            ~Request(){}
        private:
            int _x;
            int _y;
            char _oper; // 计算符号
    };


    class Response{
        public:
            Response():_result(0), _code(0), _desc("success"){}
            
            // 序列化 - 将成员变量结构化成JSON字符串
            bool Serialize(std::string* out){
                Json::Value root;
                root["result"] = _result;
                root["code"] = _code;
                root["desc"] = _desc;

                // 使用 FastWriter 快速生成紧凑的 JSON 字符串
                Json::FastWriter writer;
                std::string s = writer.write(root);

                // 将结果通过指针参数返回
                *out = s;
                return true;    
            }

            // 反序列化 - 将JSON字符串反序列化成成员变量
            bool Deserialize(const std::string& in){
                Json::Value root;
                Json::Reader reader;

                // parse 的作用是将 JSON 字符串解析成 Json::Value 对象
                bool res = reader.parse(in, root);

                _result = root["result"].asInt();
                _code = root["code"].asInt();
                _desc = root["desc"].asString();

                return true;
            }
            
            void PrintResult(){
                std::cout << "result: " << _result << ", code: " << _code << ", desc: " << _desc << std::endl;
            }

            ~Response(){}
        public:
            int _result;
            int _code;  // 错误码 0 success, 1 fail, 2 fatal
            std::string _desc; // 错误码描述
    };

    class Factory{
        public:
            static std::shared_ptr<Request> BuildRequestDefault(){
                return std::make_shared<Request>();
            }
            static std::shared_ptr<Response> BuildResponseDefault(){
                return std::make_shared<Response>();
            }
    };
}

5.2 Service.hpp

复制代码
#pragma once

#include <iostream>
#include <functional>

#include "Log.hpp"
#include "InetAddr.hpp"
#include "Socket.hpp"
#include "protocol.hpp"

using namespace LogModule;
using namespace SocketModule;
using namespace protocol;


class IOService{

    // 参数是请求类的指针,返回值是应答类的指针!
    using process_t = std::function<std::shared_ptr<Response>(std::shared_ptr<Request>)>;
    public:
        IOService(process_t process) : _process(process){}
        // 处理请求并给出响应
        void IOExcute(SockPtr sock, InetAddr& addr){
            std::string message;    // 写在while外,存储信息
            while(true){
                // 1. 负责读取
                ssize_t n = sock->Recv(&message);
                if(n == 0){
                    LOG(LogLevel::INFO) << "client " << addr.AddrStr().c_str() << " disconnected";
                    break;
                }
                else if(n < 0){
                    LOG(LogLevel::ERROR) << "recv error for client: " << addr.AddrStr().c_str();
                    break;
                }

                std::cout << "----------------------------------------" << std::endl;
                std::cout << "client " << addr.AddrStr().c_str() << " send message: " << message << std::endl;
                

                // 2. 负责解析,去掉报头和有效荷载
                    // 但此时我们仍然无法保证读到的是完整报文
                std::string package = ParseHeader(message);
                if(package.empty()) continue;
                auto req = Factory::BuildRequestDefault();
                std::cout << "package:  " << package << std::endl;

                // 3. 反序列化
                    // 此时 req 里面存储着 x,y,oper
                req->Deserialize(package);
                std::cout << "x: " << req->X() << " y: " << req->Y() << " oper: " << req->Oper() << std::endl;

                // 4. 业务处理
                auto resp = _process(req);

                // 5. 序列化
                std::string respjson;
                resp->Serialize(&respjson);
                std::cout << "respjson: " << respjson << std::endl;

                // 6. 添加报头
                std::string respstr = AddHeader(respjson);
                std::cout << "respstr:  " << respstr << std::endl;

                // 7. 负责写回
                sock->Send(respstr);
            }
        }
        ~IOService(){}

    private:
        process_t _process;
};

5.3 Socket.hpp

复制代码
#pragma once 

#include <iostream>
#include <cstring>
#include <memory>

#include <netinet/in.h>

#include "InetAddr.hpp"
#include "Log.hpp" 
#include "Common.hpp"

using namespace LogModule;

const int gbacklog = 8;


namespace SocketModule{
    class Socket;
    using SockPtr = std::shared_ptr<Socket>;

    class Socket{
        public:
            virtual void CreateSocketOrDie() = 0;                                       // 创建套接字
            virtual void BindOrDie(uint16_t port) = 0;                                  // 绑定套接字
            virtual void ListenOrDie(int backlog = gbacklog) = 0;                       // 监听套接字
            virtual SockPtr Accepter(InetAddr* cli) = 0;                                // 获取链接
            virtual bool Connector(const std::string& serverip, uint16_t serverport) = 0;   // 简历连接
            virtual int Sockfd() = 0;
            virtual void Close() = 0;
            virtual ssize_t Recv(std::string* out) = 0;                                  // 接收数据
            virtual ssize_t Send(const std::string& in) = 0;                             // 发送数据 
    
        public:
            // 创建监听套接字
            void BuildListenSocket(uint16_t port){
                CreateSocketOrDie();        // 创建
                BindOrDie(port);            // 绑定
                ListenOrDie();              // 监听
            }
    
            // 创建客户端套接字
            void BuildConnectorSocket(const std::string& serverip, uint16_t serverport){
                CreateSocketOrDie();        // 创建
                Connector(serverip, serverport); // 连接
            }
    }; 



    class TcpSocket : public Socket{
        public:
            TcpSocket(){}
            TcpSocket(int sockfd) : _sockfd(sockfd){ }

            // 创建套接字
            void CreateSocketOrDie() override{
                _sockfd = socket(AF_INET, SOCK_STREAM, 0);
                if(_sockfd < 0){
                    LOG(LogLevel::ERROR) << "create socket error";
                    exit(SOCKET_ERR);
                }
                LOG(LogLevel::DEBUG) << "sockfd create success : " << _sockfd;
            }
            // 绑定套接字
            void BindOrDie(uint16_t port) override{
                // sockaddr_in 的头文件是 #include <netinet/in.h>
                struct sockaddr_in local;
                memset(&local, 0, sizeof(local));
                local.sin_family = AF_INET;
                local.sin_port = htons(port);
                local.sin_addr.s_addr = htonl(INADDR_ANY);
                
                int n = ::bind(_sockfd, CONV(&local), sizeof(local));
                if(n < 0){
                    LOG(LogLevel::ERROR) << "bind socket error";
                    exit(BIND_ERR);
                }
                LOG(LogLevel::DEBUG) << "bind success";
            }                                  
            // 监听套接字
            void ListenOrDie(int backlog = gbacklog) override{
                int n = ::listen(_sockfd, backlog);
                if(n < 0){
                    LOG(LogLevel::ERROR) << "listen socket error";
                    exit(LISTEN_ERR);
                }
                LOG(LogLevel::DEBUG) << "listen success";
            }
            // 获取链接
            SockPtr Accepter(InetAddr* cli) override{
                struct sockaddr_in client;
                socklen_t clientlen = sizeof(client);
                // accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
                // 返回一个新的套接字,该套接字与调用进程间接地建立了连接。
                int sockfd = ::accept(_sockfd, CONV(&client), &clientlen);
                if(sockfd < 0){
                    LOG(LogLevel::ERROR) << "accept socket error";
                    return nullptr;
                }
                *cli = InetAddr(client);
                LOG(LogLevel::DEBUG) << "get a new connection from " << cli->AddrStr().c_str() << ", sockfd : " << sockfd;

                return std::make_shared<TcpSocket>(sockfd);
            }
            // 建立连接
            bool Connector(const std::string& serverip, uint16_t serverport) override{
                struct sockaddr_in server;
                memset(&server, 0, sizeof(server));
                server.sin_family = AF_INET;    // IPv4协议
                server.sin_port = htons(serverport); // 端口号
                // 这句话表示将字符串形式的IP地址转换为网络字节序的IP地址
                // inet_pton函数的作用是将点分十进制的IP地址转换为网络字节序的IP地址
                // 这里的AF_INET表示IPv4协议
                // 这里的serverip.c_str()表示IP地址的字符串形式
                // &server.sin_addr表示将IP地址存储到sin_addr成员变量中
                ::inet_pton(AF_INET, serverip.c_str(), &server.sin_addr);   // IP地址

                int n = ::connect(_sockfd, CONV(&server), sizeof(server));
                if(n < 0){
                    LOG(LogLevel::ERROR) << "connect socket error" ;
                    return false;
                }
                LOG(LogLevel::DEBUG) << "connect success";
                return true;
            }
            
            // 获取套接字描述符
            int Sockfd() override{ return _sockfd; }
            // 关闭套接字
            void Close() override{ if(_sockfd >= 0) ::close(_sockfd); }
            // 接收数据
            ssize_t Recv(std::string* out) override{
                char inbuffer[4096];
                ssize_t n = ::recv(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0);
                if(n > 0){
                    inbuffer[n] = 0;
                    *out += inbuffer;   // 可能一次读取不成功
                }
                return n;
            }           
            // 发送数据                        
            ssize_t Send(const std::string& in) override{
                return ::send(_sockfd, in.c_str(), in.size(), 0);
            }

            ~TcpSocket(){}
        private:
            int _sockfd;
    };

}

5.4 TcpServer.cpp

复制代码
#include "TcpServer.hpp"
#include "Service.hpp"
#include "NetCal.hpp"

using namespace TcpServerModule;

int main(int argc, char* argv[]){
    if(argc != 2){
        std::cerr << "Usage: " << argv[0] << " port" << std::endl;
        Die(1);
    }
    uint16_t port = std::stoi(argv[1]);

    NetCal netcal;
    IOService service(
        [&netcal](std::shared_ptr<Request> req) {
            return netcal.Calculator(req);
        }
    );

    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(
        std::bind(&IOService::IOExcute, &service, std::placeholders::_1, std::placeholders::_2), port);

    tsvr->Loop();


    return 0;
}

5.5 TcpServer.hpp

复制代码
#pragma once

#include <iostream>
#include <memory>
#include <functional>
#include <sys/wait.h>

#include "Thread.hpp"
#include "InetAddr.hpp"
#include "Socket.hpp"

namespace TcpServerModule{
    using namespace SocketModule;
    using namespace LogModule;

    using service_t = std::function<void(SocketModule::SockPtr, InetAddr&)>;

    class TcpServer{
        public:
            TcpServer(service_t service, uint16_t port)
                : _port(port), _listensock(std::make_shared<TcpSocket>()),
                _isrunning(false), _service(service)
            {
                _listensock->BuildListenSocket(port);
            }

            
            void Loop(){
                _isrunning = true;
                while(_isrunning){
                    InetAddr client;
                    
                    // 获取客户端连接
                    SockPtr cli = _listensock->Accepter(&client);
                    if(cli == nullptr) continue;
                    LOG(LogLevel::DEBUG) << "get a new connection from " << client.AddrStr().c_str();

                    // 获取成功
                    pthread_t tid;
                    // ThreadData 的头文件是 
                    ThreadData* td = new ThreadData(cli, this, client);
                    pthread_create(&tid, nullptr, Execute, td);  // 新线程分离
                }
            }
            // 线程函数参数对象
            class ThreadData{
                public:
                    SockPtr _sockfd;
                    TcpServer* _self;
                    InetAddr _addr;
                public:
                    ThreadData(SockPtr sockfd, TcpServer* self, const InetAddr& addr)
                        : _sockfd(sockfd), _self(self), _addr(addr)
                    {}
            };
            // 线程函数
            static void* Execute(void* args){
                ThreadData* td = static_cast<ThreadData*>(args);
                // 子线程结束后由系统自动回收资源,无需主线程调用 pthread_join
                pthread_detach(pthread_self()); // 分离新线程,无需主线程回收
                td->_self->_service(td->_sockfd, td->_addr);
                delete td;
                return nullptr;
            }

            

            ~TcpServer(){}
        private:
            uint16_t _port;
            SockPtr _listensock;
            bool _isrunning;
            service_t _service;
    };
}

5.6 NetCal.hpp

这里头文件可以直接使用 Service.hpp,因为我们要使用 protocol.hpp 类的请求和相应类,如果 NetCal.hpp 中也包含 protocol.hpp 以及 using namespace protocol 就会导致 protocol 命名空间中的 RequestResponse 类定义不清晰

复制代码
#pragma once

#include "Service.hpp"


// 构建处理请求的方法类,接收请求类,返回响应类
class NetCal{
    public:
        NetCal(){}

        std::shared_ptr<Response> Calculator(std::shared_ptr<Request> req){
            auto rsp = Factory::BuildResponseDefault();
            switch(req->Oper()){
                case '+':
                    rsp->_result = req->X() + req->Y();
                    break;
                case '-':
                    rsp->_result = req->X() - req->Y();
                    break;
                case '*':
                    rsp->_result = req->X() * req->Y();
                    break;
                case '/':
                    if(req->Y() == 0){
                        rsp->_result = -1;
                        rsp->_desc = "division by zero";
                    }else{
                        rsp->_result = req->X() / req->Y();
                    }
                    break;
                case '%':
                    if(req->Y() == 0){
                        rsp->_result = -1;
                        rsp->_desc = "mod by zero";
                    }else{
                        rsp->_result = req->X() % req->Y();
                    }
                    break;
                default:
                    rsp->_result = -1;
                    rsp->_desc = "unknown operator";
                    break;
            }
            return rsp;
        }

        ~NetCal(){}
};

5.7 TcpClient.cpp

复制代码
#include "TcpClient.hpp"


int main(int argc, char* argv[]){
    if(argc != 3){
        std::cerr << "Usage: " << argv[0] << " <server_ip> <server_port>" << std::endl;
        Die(1);
    }

    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    // 1. 创建套接字 并 建立连接
    SockPtr sock = std::make_shared<TcpSocket>();
    sock->BuildConnectorSocket(serverip, serverport);

    // srand需要的头文件是 cstdlib
    srand(time(nullptr) ^ getpid());
    const std::string opers = "+-*/%";

    std::string packmessage;
    while(true){
        // 构建数据
        int x = rand() % 10;
        usleep(x * 1000);
        int y = rand() % 10;
        usleep(x * y * 100);
        char oper = opers[rand() % 4];

        // 构建请求
        auto req = Factory::BuildRequestDefault();
        req->SetValue(x, y, oper);

        // 1. 序列化
        std::string reqstr;
        req->Serialize(&reqstr);

        // 2. 添加报头字段
        reqstr = AddHeader(reqstr);

        std::cout << "##################################" << std::endl;
        std::cout << "Request String: " << reqstr << std::endl;

        // 3. 发送请求
        sock->Send(reqstr);

        while(true){
            // 4. 读取响应
            ssize_t n = sock->Recv(&packmessage);
            if(n <= 0)
                break;

            // 5. 解析响应
            std::string package = ParseHeader(packmessage);
            if(package.empty()) 
                continue;
            std::cout << "Response String: " << package << std::endl;

            // 6. 反序列化
            auto rsp = Factory::BuildResponseDefault();
            rsp->Deserialize(package);

            rsp->PrintResult();

            break;
        }
        sleep(10);
    }
    sock->Close();

    return 0;
}

5.8 TcpClient.hpp

复制代码
#pragma once

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <string>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <functional>
#include <time.h>

#include "Log.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
#include "Socket.hpp"
#include "protocol.hpp"

using namespace LogModule;
using namespace SocketModule;
using namespace protocol;

5.9 Makefile

复制代码
.PHONY: all
all:server_tcp client_tcp

server_tcp:TcpServer.cpp
	g++ -o $@ $^ -std=c++17 -lpthread -ljsoncpp
	
client_tcp:TcpClient.cpp 
	g++ -o $@ $^ -std=c++17 -lpthread -ljsoncpp

.PHONY: clean
clean:
	rm -f server_tcp client_tcp

👥总结

本篇博文对 【Linux网络】打造初级网络计算器 - 从协议设计到服务实现 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

相关推荐
老六ip加速器3 分钟前
如何获取静态IP地址?完整教程
网络·网络协议·tcp/ip
riveting38 分钟前
SD2351核心板:重构AI视觉产业价值链的“超级节点”
大数据·linux·图像处理·人工智能·重构·智能硬件
易保山1 小时前
MIT6.S081 - Lab10 mmap(文件&内存映射)
linux·操作系统·c
禅与Bug的修复艺术1 小时前
JAVA后端开发常用的LINUX命令总结
java·linux·面试·java面试·后端开发·java后端·面试经验
Cloud_Air7542 小时前
从零开始使用SSH链接目标主机(包括Github添加SSH验证,主机连接远程机SSH验证)
运维·ssh
Hello.Reader2 小时前
基于 Nginx 的 WebSocket 反向代理实践
运维·websocket·nginx
小吃饱了3 小时前
LSA六种类型
网络·智能路由器
北冥有鱼被烹3 小时前
【微知】/proc中如何查看Linux内核是否允许加载内核模块?(/proc/sys/kernel/modules_disabled)
linux·服务器
游王子3 小时前
springboot3 声明式 HTTP 接口
网络·spring boot·网络协议·http
qq_273900233 小时前
CentOS系统防火墙服务介绍
linux·运维·centos