当前位置: 首页 > news >正文

网络版本计算器(再谈“协议“)

                        

        终于考完学校的课程了,大概有1个多月没有再深入学习编程了,博主堕落了堕落哩。这次博主真的要发力嘞,大家一起学习,即使在今年大背景不好的情况下也能逆流而上,拿到好offer。

目录

再谈"协议"

json工具

json工具安装

json序列化演示 

json反序列化演示

网络版本计算器

CalClient.cc

CalServer.cc

Protocol.hpp

Sock.hpp

Makefile

总结 


再谈"协议"

      协议是一种 "约定",socket api的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的。如果我们要传输一些"结构化的数据" 怎么办呢?

        网络中传输数据时,是以字符串的形式来发送接收,如果我们客户端发送的数据是一个结构化的数据,那就需要先将数据"序列化"成字符串,服务端接收到数据时,根据协议把序列化的数据转换成结构化的数据。

        是不是感觉上面一段话还是很不理解?没关系,我们来设计一个场景来帮助大家理解,最后我们再来谈一遍这个"协议"。我们谈之前,先来学习一个工具json。

json工具

json工具安装

[cyq@VM-0-7-centos test_json]$ sudo yum install jsoncpp-devel

json序列化演示 

#include<iostream>
#include<jsoncpp/json/json.h>

using namespace std;

typedef struct request
{
    int x;
    int y;
    char op;
}request_t;

int main()
{
    //序列化的过程
    request_t req = {10, 20, '+'};
    Json::Value root; //可以承装任何对象,json是一个kv式的序列化方案
    root["datax"] = req.x;
    root["datay"] = req.y;
    root["operator"] = req.op;

    //FastWriter
    //Json::FastWriter writer;

    //StyledWriter
    Json::StyledWriter writer;

    string json_string = writer.write(root); //kv转换成字符串
    cout << json_string << endl;
    return 0;
}

    Json::Value root; //可以承装任何对象,json是一个kv式的序列化方案

    root["datax"] = req.x;

    root["datay"] = req.y;

    root["operator"] = req.op; //这几步骤就相当于把req中结构化的数据放入类似于map/哈希表的root容器里面。

  下面是两个不同的字符串序列化风格,我们分别来打印一下就知道风格的区别了。

  //FastWriter

  Json::FastWriter writer; 

   

  //StyledWriter

  Json::StyledWriter writer;

     

        总结:通过上面的两种输出结果就知道了FastWriter和StyledWriter的区别了。实际使用中,我们在写代码的时候会优先使用StyledWriter,因为它方便调试,等到项目上线后,再改为FastWriter。

string json_string = writer.write(root); //kv转换成字符串,这里write的返回值就是序列化好后的字符串。

json反序列化演示

int main()
{
    //反序列化的过程
    string json_string = R"({"datax":10,"datay":20,"operator":43})";

    Json::Value root;
    Json::Reader reader;

    request_t req;
    reader.parse(json_string, root);
    req.x = root["datax"].asInt();
    req.y = root["datay"].asInt();
    req.op = (char)root["operator"].asInt();
    cout << req.x << " " << req.y << " " << req.op << endl;
    return 0;
}

int main()

{

    //反序列化的过程

    string json_string = R"({"datax":10,"datay":20,"operator":43})";//json序列化后的字符串

    Json::Value root; //我们反序列化也借助了这个root容器。

    Json::Reader reader;

    request_t req;

    reader.parse(json_string, root); //把序列化的数据读取到容器里面。

    req.x = root["datax"].asInt(); //这时候我们再把容器里面的数据读入到结构体里面。

    req.y = root["datay"].asInt();

    req.op = (char)root["operator"].asInt();

    cout << req.x << " " << req.y << " " << req.op << endl;

    return 0;

}

我们来输出一下拿到的数据:

是不是感觉Josn使用还是比较简单的?我们画个图来进一步理解:

Makefile

test:test.cc
	g++ -o $@ $^ -std=c++11 -ljsoncpp
.PHONY:clean
clean:
	rm -f test

注意:我们在编译的时候,使用了jsoncpp第三方库,所以我们需要加上-ljsoncpp。

网络版本计算器

例如, 我们需要实现一个服务器版的加法器。我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端。

约定方案一:
客户端发送一个形如"1+1"的字符串;
这个字符串中有两个操作数, 都是整形;
两个数字之间会有一个字符是运算符, 运算符只能是 + ;
数字和运算符之间没有空格;
...

约定方案二:
定义结构体来表示我们需要交互的信息;
发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;
这个过程叫做 "序列化" 和 "反序列化"

CalClient.cc

#include "Protocol.hpp"
#include"Sock.hpp"
#include<unistd.h>


void Usage(string proc)
{
    cout << "Usage: " << proc << " server_ip server_port" << endl;
}
// ./CalClient server_ip server_port
int main(int args, char* argv[])
{
    if(args != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    int sock = Sock::Socket();
    Sock::Connect(sock, argv[1], atoi(argv[2]));

    //业务逻辑
    request_t req;
    memset(&req, 0, sizeof(req));
    cout << "Plase Enter datax: ";
    cin >> req.x;
    cout << "Plase Enter datay: ";
    cin >> req.y;
    cout << "Plase Enter operator: ";
    cin >> req.op;

    string json_string = SerializeRequest(req);

    ssize_t s = write(sock, json_string.c_str(), json_string.size());

    //得到响应
    char buffer[1024];
    s = read(sock, buffer, sizeof(buffer)-1);
    if(s > 0)
    {
        buffer[s] = 0;
        responce_t resp;
        string str = buffer; //读到的是序列化的字符串
        DeserializerResponce(str, resp);//反序列化
        cout << "code[0:success]: " << resp.code << endl;
        cout << "request: " << resp.result << endl;
    }
    return 0;
}

CalServer.cc

#include <pthread.h>
#include "Protocol.hpp"
#include "Sock.hpp"
#include <unistd.h>

void Usage(string proc)
{
    cout << "Usage: " << proc << " port" << endl;
}

void *HandlerRequest(void *args)
{
    int sock = *(int *)args;
    delete (int *)args;

    pthread_detach(pthread_self());

    //读取请求
    char buffer[1024];
    request_t req;
    ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
    if (s > 0)
    {
        buffer[s] = 0;
        cout << "get a new request ..." << endl;
        string str = buffer; //有一个隐式构造函数

        DeserializerRequest(str, req); //反序列化

        responce_t resp = {0, 0};
        switch (req.op)
        {
        case '+':
            resp.result = req.x + req.y;
            break;
        case '-':
            resp.result = req.x - req.y;
            break;
        case '*':
            resp.result = req.x * req.y;
            break;
        case '/':
            if (req.y == 0)
            {
                resp.code = -1;
            }
            else
            {
                resp.result = req.x / req.y;
            }
            break;
        case '%':
            if (req.y == 0)
            {
                resp.code = -2;
            }
            else
            {
                resp.result = req.x % req.y;
            }
            break;
        default:
            resp.code = -3; //代表请求方法异常
            break;
        }

        cout << "request: " << req.x << req.op << req.y << endl;
        string send_string = SerializeResponce(resp);
        write(sock, send_string.c_str(), send_string.size());
        cout << "服务结束: " << send_string << endl;
    }
    close(sock);//记住要关闭链接
    return nullptr;
}

// ./CalServer port
int main(int args, char *argv[])
{
    if (args != 2)
    {
        Usage(argv[0]);
        exit(1);
    }
    uint16_t port = atoi(argv[1]);

    int listen_sock = Sock::Socket();
    Sock::Bind(listen_sock, port);
    Sock::Listen(listen_sock);

    while (1)
    {
        int sock = Sock::Accept(listen_sock);
        if (sock >= 0)
        {
            //任务处理
            cout << "get a new client..." << endl;
            int *pram = new int(sock);
            pthread_t tid;
            pthread_create(&tid, nullptr, HandlerRequest, (void *)pram);
        }
    }
    return 0;
}

Protocol.hpp

#pragma once

#include<iostream>
#include<string>
#include<jsoncpp/json/json.h>

using namespace std;

//请求格式
typedef struct request
{
    int x;
    int y;
    char op;
}request_t;

//响应格式
typedef struct responce
{
    int code;
    int result;
}responce_t;

//request_t -> string
string SerializeRequest(const request_t& req)
{
    //序列化的过程
    Json::Value root;
    root["datax"] = req.x;
    root["datay"] = req.y;
    root["operator"] = req.op;

    Json::FastWriter writer;
    string json_string = writer.write(root);
    return json_string;
}

void DeserializerRequest(const string& json_string, request_t& out)
{
    //反序列化
    Json::Reader reader;
    Json::Value root;

    reader.parse(json_string, root);

    out.x = root["datax"].asInt();
    out.y = root["datay"].asInt();
    out.op = (char)root["operator"].asInt();
    
}

string SerializeResponce(const responce_t& resp)
{
    Json::Value root;
    root["code"] = resp.code;
    root["result"] = resp.result;

    Json::FastWriter writer;
    string json_string = writer.write(root);
    return json_string;
}

void DeserializerResponce(const string& json_string, responce_t& out)
{
    Json::Value root;
    Json::Reader reader;

    reader.parse(json_string, root);

    out.code = root["code"].asInt();
    out.result = root["result"].asInt();
}

Sock.hpp

#pragma once
#include<iostream>
#include<string>
#include<string.h>
#include<stdlib.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

using namespace std;

class Sock
{
public:
    static int Socket()
    {
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        if(sock < 0)
        {
            cerr << "socket err" << endl;
            exit(2); 
        }
        return sock;
    }

    static void Bind(int sock, uint16_t port)
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = INADDR_ANY; //服务端 ip地址

        if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
        {
            cerr << "bind error" << endl;
            exit(3);
        }
    }

    static void Listen(int sock)
    {
        if(listen(sock, 5) < 0)
        {
            cerr << ";isten error" << endl;
            exit(4);
        }
    }

    static int Accept(int sock)
    {
        struct sockaddr_in peer; //输出型参数
        socklen_t len = sizeof(peer);
        int fd = accept(sock, (struct sockaddr*)&peer, &len);
        if(fd >= 0)
        {
            return fd;
        }
        return -1;
    }

    static void Connect(int sock, string ip, uint16_t port)
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));

        server.sin_family = AF_INET;
        server.sin_port = htons(port);
        server.sin_addr.s_addr = inet_addr(ip.c_str()); //字符串转整型

        if(connect(sock, (struct sockaddr*)&server, sizeof(server)) == 0)
        {
            cout << "Connect Success" << endl;
        }
        else
        {
            cout << "Connect failed" << endl;
            exit(5);
        }
    }
};

Makefile

.PHONY:all
all:CalClient CalServer

CalClient:CalClient.cc
	g++ -o $@ $^ -std=c++11 -ljsoncpp
CalServer:CalServer.cc
	g++ -o $@ $^ -std=c++11 -lpthread -ljsoncpp

.PHONY:clean
clean:
	rm -f CalClient CalServer

演示结果:

总结 

思考:为什么我们不直接传输结构化的数据?

        我们不能站在上帝视角来看待这个问题,对于主机B,它不知道主机A要发送什么样的结构化的数据,那么它就无法定义响应相应的结构体来存储传过来的数据。这就好像成为了先有鸡还是先有蛋的问题了。 

       如果传输字符串,我们就不必担心这个问题,对于主机B中的接收缓冲区来说,是以字节为单位来读取数据,至于读取字符串出现的问题,还是根据协议来解决,后面的博客博主再来讲解。

看到这里,给博主点个赞吧~

                        

相关文章:

  • C++——string的简单使用与深浅拷贝的理解(建议收藏)
  • BGP综合实验
  • Day4——两两交换链表节点、删除链表第n个绩点、链表相交、环形链表||
  • YOLOv5实现佩戴安全帽检测和识别(含佩戴安全帽数据集+训练代码)
  • H.264 入门篇 - 10 (帧间预测 - 参考帧列表修改/重排)
  • 第三章Linux环境基础开发工具使用(yum+rzsz+vim+g++和gcc+gdb+make和Makefile+进度条+git)
  • 博客系统数据库设计与实现之修改(java)
  • django-设置X-Frame-Options响应头防止点击劫持攻击
  • PostgreSQL自增主键的用法以及在mybatis中的使用
  • app逆向(10)| APP的加固与脱壳
  • 解析IEC 61850通信规约
  • 了解Selenium,看这一篇够了
  • 你就想这样一辈子躺平,还是改变这个世界?
  • Xmake基础----自定义脚本介绍
  • Python 的Tkinter包系列之二:窗口菜单
  • 《深入 React 技术栈》
  • 【162天】黑马程序员27天视频学习笔记【Day02-上】
  • 【comparator, comparable】小总结
  • Angular 4.x 动态创建组件
  • axios 和 cookie 的那些事
  • canvas 绘制双线技巧
  • co.js - 让异步代码同步化
  •  D - 粉碎叛乱F - 其他起义
  • iOS帅气加载动画、通知视图、红包助手、引导页、导航栏、朋友圈、小游戏等效果源码...
  • Linux中的硬链接与软链接
  • mac修复ab及siege安装
  • uva 10370 Above Average
  • Vultr 教程目录
  • 产品三维模型在线预览
  • 翻译--Thinking in React
  • 前端技术周刊 2019-01-14:客户端存储
  • 原生JS动态加载JS、CSS文件及代码脚本
  • 这几个编码小技巧将令你 PHP 代码更加简洁
  • MPAndroidChart 教程:Y轴 YAxis
  • 阿里云重庆大学大数据训练营落地分享
  • 仓管云——企业云erp功能有哪些?
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • ​LeetCode解法汇总2182. 构造限制重复的字符串
  • ​插件化DPI在商用WIFI中的价值
  • #我与Java虚拟机的故事#连载03:面试过的百度,滴滴,快手都问了这些问题
  • $(document).ready(function(){}), $().ready(function(){})和$(function(){})三者区别
  • (2.2w字)前端单元测试之Jest详解篇
  • (39)STM32——FLASH闪存
  • (C语言)fgets与fputs函数详解
  • (Spark3.2.0)Spark SQL 初探: 使用大数据分析2000万KF数据
  • (差分)胡桃爱原石
  • (二) Windows 下 Sublime Text 3 安装离线插件 Anaconda
  • (免费分享)基于springboot,vue疗养中心管理系统
  • (免费领源码)python#django#mysql公交线路查询系统85021- 计算机毕业设计项目选题推荐
  • (四)TensorRT | 基于 GPU 端的 Python 推理
  • (源码版)2024美国大学生数学建模E题财产保险的可持续模型详解思路+具体代码季节性时序预测SARIMA天气预测建模
  • (转)Linq学习笔记
  • (转)nsfocus-绿盟科技笔试题目
  • (转)shell调试方法
  • .Net(C#)常用转换byte转uint32、byte转float等