Linux笔记---网络计算器

发布于:2025-09-11 ⋅ 阅读:(18) ⋅ 点赞:(0)

1. 网络程序分层

我们说过,OSI7层模型十分完美,但是因特网实际上采用的是TCP/IP五层模型:

实际上,对比可以发现,TCP/IP模型实际上就是将OSI的前三层模型合并为了应用层。

这就提示我们,我们设计的应用程序应当存在三个层次:

  • 应用层:人机交互层,应用程序可以访问网络服务。
  • 表示层:确保数据采用可用形式,并且是数据加密的形式。
  • 会话层:维护连接,并负责控制端口和会话。

简单来说,在服务器端,这三层就是提供实际服务的顶层、规定数据传输协议的协议层、负责建立连接与断开连接的服务器层。

#include <pthread.h>
#include "Socket.hpp"
#include "Server.hpp"
#include "Protocol.hpp"
#include "Calculator.hpp"
#include "Daemon.hpp"
using namespace SocketModule;

void Usage(const std::string &name)
{
    std::cout << "usage: " << name << " + port" << std::endl;
}

int main(int argc, char* args[])
{
    if (argc != 2)
    {
        Usage(args[0]);
        exit(USAGE_ERROR);
    }
    in_port_t port = std::stoi(args[1]);

    // 日志切换为文件策略
    USE_FILE_STRATEGY();

    // 设置为守护进程
    if(Daemon(0, 0) == -1)
    {
        LOG(LogLevel::FATAL) << "Deamon: 设置守护进程失败! " << strerror(errno);
        exit(DAEMON_ERROR);
    }
    // daemon(0, 0)

    // 顶层, 计算器
    Calculator calculator;

    // 协议层
    Protocol protocol([&calculator](const Request& request){
        return calculator.Calculate(request);
    });

    // 服务器层
    Server server(port, [&protocol](const std::shared_ptr<TCPConnectSocket>& socket){
        protocol.ServerService(socket);
    });
    server.Run();

    return 0;
}

2. 协议

为简单起见,我们定制如下的协议:

  • 服务器端只处理类似" x oper y "的简单表达式,所以客户端用于发起请求的结构化数据只包含三个变量:" int x "、" int y "、" char oper "
  • 服务器端处理完表达式后需要返回结果与状态码,所以服务器端用于返回响应的结构化数据需要包含两个变量:" int result "、" int status "
  • 数据在网络上的传输格式为JSON串
  • 一个完整的请求/响应报文包含四个部分:JSON串的长度 + sep + JSON串 + sep。其中sep = " \n\r "

于是就有下面的协议类(Protocol.hpp):

#pragma once
#include <jsoncpp/json/json.h>
#include "Socket.hpp"
using namespace SocketModule;

class Request
{
public:
    Request(){}
    Request(int x, int y, char oper)
    {
        serialize(x, y, oper);
    }
    Request(const std::string& json_str)
    {
        if(!deserialize(json_str))
            LOG(LogLevel::ERROR) << "Response: 反序列化失败! ";
    }
    // 序列化
    std::string serialize(int x, int y, char oper)
    {
        Json::Value root;
        root["x"] = _x = x;
        root["y"] = _y = y;
        root["oper"] = _oper = oper;
        Json::FastWriter writer;
        _json_str = writer.write(root);
        return _json_str;
    }
    // 反序列化
    bool deserialize(const std::string& json_ptr)
    {
        Json::Value root;
        Json::Reader reader;
        if(reader.parse(json_ptr, root))
        {
            _json_str = json_ptr;
            _x = root["x"].asInt();
            _y = root["y"].asInt();
            _oper = root["oper"].asInt();
            return true;
        }
        return false;
    }
    int X() const {return _x;}
    int Y() const {return _y;}
    int Oper() const {return _oper;}
    std::string JsonStr() const {return _json_str;}
private:
    int _x, _y;
    char _oper;
    std::string _json_str;
};

class Response
{
public:
    Response(){}
    Response(int result, int status)
    {
        serialize(result, status);
    }
    Response(const std::string& json_str)
    {
        if(!deserialize(json_str))
            LOG(LogLevel::ERROR) << "Response: 反序列化失败! ";
    }
    // 序列化
    std::string serialize(int result, int status)
    {
        Json::Value root;
        root["result"] = _result = result;
        root["status"] = _status = status;
        Json::FastWriter writer;
        _json_str = writer.write(root);
        return _json_str;
    }
    // 反序列化
    bool deserialize(const std::string& json_str)
    {
        Json::Value root;
        Json::Reader reader;
        if(reader.parse(json_str, root))
        {
            _json_str = json_str;
            _result = root["result"].asInt();
            _status = root["status"].asInt();
            return true;
        }
        return false;
    }
    int Result() const {return _result;}
    int Status() const {return _status;}
    std::string JsonStr() const {return _json_str;}
private:
    int _result, _status;
    std::string _json_str;
};

using request_handler = std::function<Response(const Request&)>;
class Protocol
{
public:
    Protocol(request_handler handler)
        : _handler(handler)
    {}
    // 封装
    // package = size + esp + json_ptr + esp
    void encapsulate(const std::string& json_str, std::string& package)
    {
        int size = json_str.size();
        package = std::to_string(size) + esp + json_str + esp;
    }
    // 解封装
    bool decapsulate(std::string& package, std::string& json_str)
    {
        auto pos = package.find(esp);
        if(pos == std::string::npos)
        {
            return false;
        }

        std::string size_str = package.substr(0, pos);
        int size = std::stoi(size_str.c_str());
        int total_len = size_str.size() + 2 * esp.size() + size;
        if(package.size() >= total_len)
        {
            json_str = package.substr(pos + esp.size(), size);
            package.erase(0, total_len);
            return true;
        }
        return false;
    }

    void ClientService()
    {
        // todo
    }

    // 服务器端服务
    void ServerService(const std::shared_ptr<TCPConnectSocket>& socket)
    {
        std::string package, buffer, json_str, send_msg;
        while(socket->Receive(buffer))
        {
            package += buffer;
            if(decapsulate(package, json_str))
            {
                Request request(json_str);
                // 使用顶层提供的回调函数来处理请求
                Response response = _handler(request);
                encapsulate(response.JsonStr(), send_msg);
                socket->Send(send_msg);
            }
        }
        LOG(LogLevel::INFO) << "[" << socket->addr().Info() << "]连接已断开! ";
    }
private:
    static const std::string esp;
    // 顶层提供的用于处理请求的回调方法
    request_handler _handler;
};
const std::string Protocol::esp = "/n/r";

3. 项目代码

3.1 Calculator.hpp

#pragma once
#include "Protocol.hpp"
#include "Log.hpp"
using namespace LogModule;

class Calculator
{
public:
    Response Calculate(const Request &request)
    {
        int result = 0, status = 0;
        int x = request.X(), y = request.Y();
        char oper = request.Oper();
        switch (oper)
        {
        case '+':
            result = x + y;
            break;
        case '-':
            result = x - y;
            break;
        case '*':
            result = x * y;
            break;
        case '/':
            if(y == 0)
            {
                LOG(LogLevel::ERROR) << "Calculator: 除零错误! ";
                status = 1;
            }
            else
                result = x / y;
            break;
        case '%':
            if(y == 0)
            {
                LOG(LogLevel::ERROR) << "Calculator: 模零错误! ";
                status = 2;
            }
            else
                result = x % y;
            break;
        case '^':
            result = x ^ y;
            break;
        case '&':
            result = x & y;
            break;
        case '|':
            result = x | y;
            break;
        default:
            LOG(LogLevel::ERROR) << "Calculator: 未知类型的运算符[" << oper << "]";
            status = 2;
            break;
        }
        Response response(result, status);
        return response;
    }
};

当然,这个项目只是用作对套接字编程的学习,因此协议的定制与顶层服务的实现都较为简单。

3.2 Server.hpp

#pragma once
#include "Socket.hpp"
#include "Common.hpp"
using namespace SocketModule;
using func_t = std::function<void(const std::shared_ptr<TCPConnectSocket>&)>;

class Server : public NoCopy
{
public:
    Server(in_port_t port, func_t service)
        : _lis_socket(port)
        , _service(service)
    {}

    void Run()
    {
        while(true)
        {
            auto socket = _lis_socket.Accept();
            int id = fork();
            if(id < 0)
            {
                LOG(LogLevel::FATAL) << "fork: 创建子进程失败! " << strerror(errno);
                throw std::runtime_error("fork failed");
            }
            else if( id == 0)
            {
                _lis_socket.Close();
                // 子进程
                if(fork() > 0) exit(NORMAL);
                // 孙子进程
                _service(socket);
                exit(NORMAL);
            }
            else
            {
                // 父进程
                socket->Close();
            }
        }
    }

    ~Server()
    {
        _lis_socket.Close();
    }
private:
    TCPListenSocket _lis_socket;
    func_t _service;
};

3.3 Server.cpp

#include <pthread.h>
#include "Socket.hpp"
#include "Server.hpp"
#include "Protocol.hpp"
#include "Calculator.hpp"
#include "Daemon.hpp"
using namespace SocketModule;

void Usage(const std::string &name)
{
    std::cout << "usage: " << name << " + port" << std::endl;
}

int main(int argc, char* args[])
{
    if (argc != 2)
    {
        Usage(args[0]);
        exit(USAGE_ERROR);
    }
    in_port_t port = std::stoi(args[1]);

    // 日志切换为文件策略
    USE_FILE_STRATEGY();

    // 设置为守护进程
    if(Daemon(0, 0) == -1)
    {
        LOG(LogLevel::FATAL) << "Deamon: 设置守护进程失败! " << strerror(errno);
        exit(DAEMON_ERROR);
    }
    // daemon(0, 0)

    // 顶层
    Calculator calculator;

    // 协议层
    Protocol protocol([&calculator](const Request& request){
        return calculator.Calculate(request);
    });

    // 服务器层
    Server server(port, [&protocol](const std::shared_ptr<TCPConnectSocket>& socket){
        protocol.ServerService(socket);
    });
    server.Run();

    return 0;
}

3.4 Client.cpp

#include "Socket.hpp"
#include "Protocol.hpp"
using namespace SocketModule;

void Usage(const std::string &name)
{
    std::cout << "usage: " << name << " + ip" << " + port" << std::endl;
}

int main(int argc, char* args[])
{
    if (argc != 3)
    {
        Usage(args[0]);
        exit(USAGE_ERROR);
    }

    in_port_t port = std::stoi(args[2]);
    TCPClientSocket cli_socket(args[1], port);

    int x, y;
    char oper;
    Request request;
    Response response;
    Protocol protocol([](const Request&){return Response();});
    std::string send_msg, recv_msg, json_str;
    InetAddr server = cli_socket.addr();
    while(true)
    {
        std::cout << "Enter expression(x oper y)# ";
        std::cin >> x >> oper >> y;
        request.serialize(x, y, oper);
        protocol.encapsulate(request.JsonStr(), send_msg);
        cli_socket.Send(send_msg);

        std::string tmp;
        while(!protocol.decapsulate(recv_msg, json_str))
        {
            cli_socket.Receive(tmp);
            recv_msg += tmp;
        }
        response.deserialize(json_str);
        std::cout << "Recive from Client# " << response.Result() << "[" << response.Status() << "]" << std::endl;
    }

    return 0;
}

3.5 Common.hpp

#pragma once
#include <functional>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;

enum SocketExitCode
{
    NORMAL = 0,
    SOCKET_ERROR,
    BIND_ERROR,
    LISTEN_ERROR,
    FORK_ERROR,
    READ_ERROR,
    WRITE_ERROR,
    USAGE_ERROR,
    CONNECT_ERROR,
    DAEMON_ERROR
};

class NoCopy
{
public:
    NoCopy(){}
    NoCopy(const NoCopy&) = delete;
    NoCopy& operator=(const NoCopy&) = delete;
};

3.6 Socket.hpp

https://blog.csdn.net/2302_80372340/article/details/151293752?spm=1011.2415.3001.5331

4. 演示

日志内容(/var/log/NetCal.log):

关闭服务器:


网站公告

今日签到

点亮在社区的每一天
去签到