网络版计算器

news2024/11/28 20:37:10

        本次我们实现一个服务器版本的简单的计算器,通过自己在应用层定制协议来将数据传输出去。

协议代码

        此处我们为了解耦,采用了两个类,分别表示客户端的请求和服务端的响应。

Request

class Request
{
public:
    Request()
    {
    }
    Request(int x, int y, char op)
        : _x(x), _y(y), _op(op)
    {
    }

    // 序列化
    bool serialize(string &out)
    {
        out += to_string(_x);
        out += SEP;
        out += _op;
        out += SEP;
        out += to_string(_y);
        return true;
    }
    // 反序列化 x + y
    bool deserialize(const string &in)
    {
        string x_str, y_str;
        int left = in.find(SEP);
        if (left == string::npos)
            return false;

        int right = in.rfind(SEP);
        if (right == string::npos)
            return false;

        x_str = in.substr(0, left);
        y_str = in.substr(right + 1);
        _x = stoi(x_str);
        _y = stoi(y_str);
        _op = in[left + SEP_LEN];
        return true;
    }

    int _x;
    int _y;
    char _op;
};

Response

class Response
{
public:
    Response()
        : exitcode(0), result(0)
    {
    }
    // 序列化
    bool serialize(string &out)
    {
        out.clear();
        out += to_string(exitcode);
        out += SEP;
        out += to_string(result);
    }
    // 反序列化
    bool deserialize(const string &in)
    {
        // exitcode result
        string exit_str, result_str;
        int pos = in.find(SEP);

        if (pos == string::npos)
            return false;

        exit_str = in.substr(0, pos);
        result_str = in.substr(pos + SEP_LEN);
        if (exit_str.empty() || result_str.empty())
            return false;

        exitcode = stoi(exit_str);
        result = stoi(result_str);
    }

    int exitcode;
    int result;
};

        而对于两个类来说,它们最重要的就是序列化和反序列化的过程。

        对于请求类,它需要将所输入的数据序列化转化成为字符串转化的字符串就是一整个报文,便于后续添加报头;而它的反序列化就需要将报文重新转化为数据,便于后续的计算任务进行。

        对于响应类,它需要将所得到的结果序列化转化为字符串;反序列化则是将报文转化为数据。

        当数据序列化后就需要添加报头,而反序列化的任务则需要在去除报头后进行。

添加和去除报头

#define SEP " "
#define SEP_LEN strlen(SEP)
#define SEP_LINE "\\r\\n"
#define SEP_LINE_LEN strlen(SEP_LINE)

// 报头格式 text_len/r/ntext/r/n
// 添加报头

bool Enlength(const string &in, string &out)
{
    out.clear();
    out += to_string(in.size());
    out += SEP_LINE;
    out += in;
    out += SEP_LINE;
    return true;
}
// 去掉报头
// 去掉后 只剩 text
bool Delength(const string &in, string &out)
{
    int pos = in.find(SEP_LINE);
    if (pos == string::npos)
        return false;
    int text_len = stoi(in.substr(0, pos));
    out = in.substr(pos + SEP_LINE_LEN, text_len);
    return true;
}

        此处我们的报头的格式就是 "text_len\r\ntext\r\n" 的格式。 

        这Enlength函数可以将报文转化为数据包,Delength可以将数据包转化为报文

 接收函数

// 接收的是一个数据 是 text_len/r/ntext/r/n的形式
bool Recv(int socket, string &inbuffer, string &pacakge)
{
    char buffer[1024];
    while (true)
    {
        int n = recv(socket, buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            // 开始接收
            buffer[n] = 0;
            inbuffer += buffer;
            int pos = inbuffer.find(SEP_LINE);
            if (pos == string::npos)
                continue;
            int text_len = stoi(inbuffer.substr(0, pos));
            string len_string = inbuffer.substr(0, pos);
            int total_len = text_len + len_string.size() + SEP_LINE_LEN * 2;
            if (inbuffer.size() < total_len)
                continue; // 说明没有一个完整的报文

            // 此时就说明至少有一个完整的报文
            pacakge = inbuffer.substr(0, total_len + 1);
            inbuffer.erase(total_len);

            break;
        }
        else
        {
            return false;
        }
    }
    return true;
}

        此外,由于TCP是字节流的协议,我们需要自定义函数来保证收到了至少一个完整的报文。 

计算函数 

void CalHandler(const Request &rq, Response &rp)
{
    switch (rq._op)
    {
    case '+':
        rp.result = rq._x + rq._y;
        break;
    case '-':
        rp.result = rq._x - rq._y;
        break;
    case '*':
        rp.result = rq._x * rq._y;
        break;
    case '/':
        if (rq._y == 0)
        {
            rp.exitcode = 1;
        }
        else
        {
            rp.result = rq._x / rq._y;
        }
        break;
    case '%':
        if (rq._y == 0)
        {
            rp.exitcode = 1;
        }
        else
        {
            rp.result = rq._x % rq._y;
        }
        break;
    }
}

服务器

        首先直接看看服务器的代码。

calsever.hpp

#include <iostream>
#include <string>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include "protocol.hpp"

using namespace std;

#define gbacklog 5

class Sever;

class Sever
{
public:
    Sever(const uint16_t &port)
        : _port(port), _listensocket(-1)
    {
    }

    void InitSever()
    {
        _listensocket = socket(AF_INET, SOCK_STREAM, 0); // TCP是面向字节流的协议

        // bind该socket
        struct sockaddr_in peer;
        peer.sin_family = AF_INET;
        peer.sin_port = htons(_port);
        peer.sin_addr.s_addr = INADDR_ANY;
        if (bind(_listensocket, (sockaddr *)&peer, sizeof(peer)) < 0)
        {
            cout << "bind err!" << endl;
            exit(-1);
        }
        cout << "bind success!" << endl;

        // 监听该socket
        if (listen(_listensocket, gbacklog) < 0)
        {
            cout << "listen err!" << endl;
            exit(-1);
        }
        cout << "listen success!" << endl;
    }

    void start()
    {
        while (true)
        {
            struct sockaddr_in peer;
            bzero(&peer, sizeof(peer));
            socklen_t len = sizeof(peer);
            int socket = accept(_listensocket, (sockaddr *)&peer, &len);

            if (socket < 0)
            {
                cout << "socket err" << endl;
                exit(-1);
            }

            cout << "accept success socket : " << socket << endl;

            // version 2.1 多进程版
            pid_t id = fork();
            if (id == 0) // 子进程内部
            {
                close(_listensocket); // 子进程不用监听,父进程监听即可
                if (fork() > 0)
                    exit(-1); // 直接让子进程创建孙子进程,然后将子进程退出,让孙子进程被领养
                handlerEnter(socket);
                close(socket);

            } // 但是父进程不用等待,否则会造成串行
        }
    }

    void handlerEnter(int socket)
    {
        string inbuffer;
        while (true)
        {

            // 读取数据
            string recv_text,recv_pacakge;
            while(!Recv(socket,recv_text,recv_pacakge))
            {
            }
            //去报头
            if(!Delength(recv_pacakge,recv_text))
            return;
            Request rq;
            // 反序列化
            if(!rq.deserialize(recv_text))
            {
                return;
            }
            // 计算公式
            Response rp;
            CalHandler(rq,rp);
            // 将结果序列化
            string send_text,send_pacakge;
            rp.serialize(send_text);
            //添加报头
            Enlength(send_text,send_pacakge);
            // 发送结果
            send(socket,send_pacakge.c_str(),send_pacakge.size(),0);
        }
    }

private:
    uint16_t _port;
    int _listensocket;
};

calsever.cc 

#include "calSever.hpp"
#include <memory>

using namespace std;

int main(int args, char *argv[])
{
    if (args != 2) // 在运行时必须带有端口号
    {
        cout << " ./Sever port" << endl;
        exit(-1);
    }

    uint16_t port = atoi(argv[1]);

    unique_ptr<Sever> TCPSever(new Sever(port));
    
    TCPSever->InitSever();
    TCPSever->start();

    return 0;
}

        对于服务器的代码,并没有太大的改动,主要是服务器所需要进行的任务的代码需要了解。

        

         首先是接收客户端发过来的数据,由于此处使用的是TCP协议进行传输,所以可能接受的数据包不是一个完整的数据包,因此需要利用循环和自定义的函数进行检测。

        而该函数的逻辑大致为 接收到数据包——去掉报头——对报文进行反序列化——对获取的数据进行计算——将结果序列化——对报文添加报头——发送数据包。

        整体逻辑十分简单,但前提是函数需要正确使用。

客户端

calclient.hpp

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include"protocol.hpp"

using namespace std;

class Client
{

public:
    Client(const uint16_t &port, const string &ip)
        : _severport(port), _severip(ip), _socket(-1)
    {
    }

    void InitClient()
    {
        _socket = socket(AF_INET, SOCK_STREAM, 0);
        if (_socket < 0)
        {
            cout << "socket failed" << endl;
        }
    }

    void run()
    {
        struct sockaddr_in peer;
        bzero(&peer, sizeof(peer));
        peer.sin_family = AF_INET;
        peer.sin_port = htons(_severport);
        peer.sin_addr.s_addr = inet_addr(_severip.c_str());

        if (connect(_socket, (sockaddr *)&peer, sizeof(peer)) == -1)
        {
            cout << "connect failed" << endl;
            exit(-1);
        }
        else
        {
            // 输入所需要计算的公式
            string inbuffer;
            while (true)
            {
                int x, y;
                char op;
                cout << "请输入第一个数据 :";
                cin >> x;
                cout << "请输入第二个数据 :";
                cin >> y;
                cout << "请输入计算方式 :";
                cin >> op;

                string send_text,send_pacakge;
                Request rq(x,y,op);

                // 进行序列化以及添加报头
                rq.serialize(send_text);
                Enlength(send_text,send_pacakge);

                // 发送公式
                send(_socket,send_pacakge.c_str(),send_pacakge.size(),0);

                // 接收结果

                string recv_text,recv_pacakge;
                while(!Recv(_socket,inbuffer,recv_pacakge))
                {
                    //若是接收失败就重试
                }

                //此时已经接收到结果
                // 对结果进行反序列化并输出

                Response rp;
                //去掉报头
                Delength(recv_pacakge,recv_text);
                rp.deserialize(recv_text);

                if(rp.exitcode != 0)
                cout<<"err ! exitcode : "<<rp.exitcode<<endl;
                else 
                {
                    cout<<"The result : "<<rp.result<<endl;
                }
            }
        }
    }

private:
    uint16_t _severport;
    string _severip;
    int _socket;
};

calclient.cc

#include"calClient.hpp"
#include<memory>
using namespace std;

void Remind()
{
    cout<<"./Client Severport Severip"<<endl;
}

int main(int args,char* argv[])
{

    if(args!=3)
    {
        Remind();
        exit(-1);
    }

    uint16_t port = atoi(argv[1]);
    string ip = argv[2];

    unique_ptr<Client> TCPClient(new Client(port,ip));

    TCPClient->InitClient();
    TCPClient->run();

    return 0;
}

        对于客户端而言,它的工作和服务器大差不差。 

         具体逻辑是 获取数据——序列化——添加报头——传输数据包——接收数据包——去掉报头——反序列化——获取结果。       

        接下来就来直接看看结果。

        

 

         能看到我们确实成功的运行并计算出结果。

        但是我们可以看到,由我们自己定义协议十分麻烦,这里是协议很小,当我们的项目很大的时候,也许协议就不止这么点了,因此我们有更简单的方法来定制协议。 

更简单的方案:

  • json
  • protobuf
  • xml

json方案的序列化和反序列化 

class Request
{
public:
    Request()
    {
    }
    Request(int x, int y, char op)
        : _x(x), _y(y), _op(op)
    {
    }

    // 序列化
    bool serialize(string &out)
    {
        #ifdef MYSELF
        out += to_string(_x);
        out += SEP;
        out += _op;
        out += SEP;
        out += to_string(_y);
        #else
            Json::Value root;
            root["first"] = _x;
            root["second"] = _y;
            root["operator"] = _op;

            Json::FastWriter w;
            out = w.write(root);  
        #endif
        return true;
    }
    // 反序列化 x + y
    bool deserialize(const string &in)
    {
        #ifdef MYSELF
        string x_str, y_str;
        int left = in.find(SEP);
        if (left == string::npos)
            return false;

        int right = in.rfind(SEP);
        if (right == string::npos)
            return false;

        x_str = in.substr(0, left);
        y_str = in.substr(right + 1);
        _x = stoi(x_str);
        _y = stoi(y_str);
        _op = in[left + SEP_LEN];
        #else 
        Json::Value root;
        Json::Reader r;
        r.parse(in,root);
        _x = root["first"].asInt();
        _y = root["second"].asInt();
        _op = root["operator"].asInt();

        
        #endif
        return true;

    }

    int _x;
    int _y;
    char _op;
};

class Response
{
public:
    Response()
        : exitcode(0), result(0)
    {
    }
    // 序列化
    bool serialize(string &out)
    {
        #ifdef MYSELF
        out.clear();
        out+=to_string(exitcode);
        out+=SEP;
        out+=to_string(result);
        #else 
        Json::Value root;
        root["exitcode"] = exitcode;
        root["result"] = result;
        Json::FastWriter w;
        out = w.write(root);
        #endif  
        return true;
    }
    // 反序列化
    bool deserialize(const string &in)
    {
        #ifdef MYSELF
        //exitcode result
        string exit_str,result_str;
        int pos = in.find(SEP);

        if(pos == string::npos)
            return false;

        exit_str = in.substr(0,pos);
        result_str = in.substr(pos+SEP_LEN);
        if(exit_str.empty()||result_str.empty())
            return false;
        
        exitcode = stoi(exit_str);
        result = stoi(result_str);
        #else
        Json::Value root;
        Json::Reader r;
        r.parse(in,root);
        exitcode = root["exitcode"].asInt();
        result = root["result"].asInt();
 

        #endif
        return true;

    }

    int exitcode;
    int result;
};

         我们能够看到确实成功了。

        不过使用json需要一点小小的准备工作。

  • 下载对应的工具

        可以用root账号或者sudo进行下载。

        yun install -y jsoncpp-devel

        
  •  包含头文件

  •  编译添加新指令

        只要是使用了json的文件,就需要添加 -ljsoncpp 指令才行。

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/763724.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

复习单例模式

单例模式 单例模式&#xff08;Singleton Pattern&#xff09;是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式。 这种模式涉及到一个单一的类&#xff0c;该类负责创建自己的对象&#xff0c;同时确保只有单个…

在SpringBoot中实现文件上传

1.创建一个SpringBoot的项目&#xff0c;需要导入spring-boot-starter-web的依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency> 2.编写文件的核心配置applica…

ASEMI二极管MURF2080CTR封装, MURF2080CTR大小

编辑-Z MURF2080CTR参数描述&#xff1a; 型号&#xff1a;MURF2080CTR 最大峰值反向电压(VRRM)&#xff1a;800V 最大RMS电压(VRMS)&#xff1a;430V 最大直流阻断电压VR(DC)&#xff1a;800V 平均整流正向电流(IF)&#xff1a;20A 非重复峰值浪涌电流(IFSM)&#xff1…

亚马逊会员日结束了,如何防止销量和排名“断崖式”下跌?

令人瞩目的2023亚马逊Prime会员日落下了帷幕&#xff0c;据官方数据显示&#xff0c;48小时售出商品超3.75亿件&#xff0c;再一次创造了历史新纪录&#xff01; 好不容易因为亚马逊会员日提升了销售额和曝光了品牌&#xff0c;那么会员日结束了&#xff0c;如何稳住您的销量和…

【网站建设】HTTP/HTTPS 是什么?有什么区别?

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;陈童学哦&#xff0c;目前学习C/C、算法、Java等方向&#xff0c;一个正在慢慢前行的普通人。 &#x1f3c0;系列专栏&#xff1a;陈童学的日记 &#x1f4a1;其他专栏&#xff1a;CSTL&#xff0c;感…

STM32H743 W25Q128 Keil烧录算法的制作、内存映射代码、分散加载文件源码分享

一、W25Q128烧录算法的制作 1、前言 最近做项目用到STM32H743这款芯片&#xff0c;其内部FLash有2M&#xff0c;但是项目中用到touchgfx&#xff0c;如果资源放到内部Flash中会造成两个问题&#xff0c;一是图片过多会导致内部Flash不够用&#xff0c;二是每次修改一下程序都…

一文了解近端边缘 IT 基础架构技术需求

随着边缘计算相关技术的逐渐成熟&#xff0c;边缘应用的种类也呈现多样化发展。IDC 2023 年发布的《全球边缘支出指南》归纳出 400 多种跟边缘相关的应用——这是在从 9 个地理区域、17 个技术市场、6 个技术领域和 19 个行业当中汇总出来的。 边缘计算的复杂和边缘应用的多样…

Leetcode-每日一题【24.两两交换链表中的节点】

题目 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4]输出&#xff1a;[…

【玩转Linux操作】详细讲解 Linux分区磁盘 操作以及相关的命令

&#x1f38a;专栏【玩转Linux操作】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【Counting Stars 】 欢迎并且感谢大家指出小吉的问题&#x1f970; 文章目录 &#x1f354;什么是Linux的分区&#x1f354;Linux分区的作用&a…

没有代码基础如何学习自动化测试

因为最近在群里有一些同学&#xff0c;之前没做过自动化测试&#xff0c;但是限于领导要求&#xff0c;或者自己想提升了&#xff0c;开始研究自动化测试&#xff0c;最近记忆比较深的低级的几个问题是&#xff1a; 1、编写一个python的类将 __init__写成_init_苦于问题一直解…

springboot项目使用mybatis-plus启动报错

在使用最新的mybatis-plus的时候&#xff0c;遇到一个报错 org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name payMentController: Unsatisfied dependency expressed through field payMentService; nested exception is…

mybatis-plus代码生成器使用指南

mybatis-plus代码生成器 官网&#xff1a;mp代码生成器&#xff08;新&#xff09; 1、导入依赖 使用最新版本&#xff1a;3.5.3.1版本&#xff0c;老版本不兼容 <properties><mybatis-plus.version>3.5.3.1</mybatis-plus.version><freemarker.versio…

集成VCO-Cadence ADE相位噪声分析

集成VCO-Cadence ADE相位噪声分析 简介环境软件工艺 参考振荡器原理图相位噪声说明pss设置1234 pnoise设置12345 结果 简介 本文主要是使用Cadence ADE仿真1GHz交叉耦合振荡器&#xff0c;得到相位噪声曲线&#xff0c;主要记录仿真设置过程&#xff0c;仅供参考&#xff0c;如…

零拷贝技术(DMA、MMAP、sendfile)

零拷贝mmap、sendfile 定义 零拷贝技术主要是解决传统网络I/O操作中发送文件的性能问题&#xff1a;如下图表示一次read和write时传统I/O涉及到的CPU操作&#xff1a; 涉及到4次用户态↔内核态上下文切换&#xff0c;其中read切换两次、write切换两次&#xff1b;涉及到4次数…

IntelliJ IDEA 忽略Git提交

在项目下新建 .gitignore 文件 在 .gitignore 文件写入以下内容&#xff0c;从上图可以看到&#xff0c;忽略提交的这些目录/文件变成了黄色

P2372 yyy2015c01挑战算周长

yyy2015c01挑战算周长 题目背景 yyy2015c01 快速的解决了问题&#xff0c;受到邻居们的赞许&#xff0c;高兴的回到家&#xff0c;把糖给了妈妈&#xff0c;吃了香喷喷的午饭&#xff0c;又睡了一个甜甜的午觉&#xff0c;感觉人生真美好。下午爸爸回到家&#xff0c;听说了 …

设计模式 ~ 单例模式

单例模式 单例模式是一种设计模式&#xff0c;指在确保一个类只有一个实例&#xff0c;并提供一个全局访问点来访问该实例&#xff1b; 前端对于单例模式不常用&#xff0c;但是单例的思想无处不在&#xff1b; 创建之后缓存以便继续使用&#xff1b; 如&#xff1a;弹窗、遮罩…

HCIP——HCIA回顾及静态实验

HCIP HCIA一、知识回顾1、OSI七层参考模型&#xff1a;2、重要的几个协议报头格式 二、静态实验1、实验拓扑图2、实验要求3、实验思路4、实验步骤1、给接口划分IP地址以及配置环回地址2、交换机配置划分VLAN3、配置静态路由4、配置DHCP服务5、测试6、优化 HCIA 1、七层参考模型…

遭黑客攻击后反而涨了千百倍?

近期&#xff0c;遭遇黑客攻击后的某数字藏品平台悄悄“复活”&#xff0c;引发业界关注。复出后&#xff0c;该平台上的数字藏品持续暴涨&#xff0c;大部分涨幅均超过百倍&#xff0c;少数藏品甚至超过了千倍。事出反常必有妖。这波涨势难免令人怀疑其中是否存在投机炒作行为…

常用API学习05(Java)

Runtime Runtime表示当前虚拟机的运行环境&#xff0c;Runtime的方法我们不能自己直接去new。 public static Runtime getRuntime() 当前系统的运行环境对象 public void exit(int status) 停止虚拟机 public int availableProcessors() 获得cpu的线程数 publ…