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

                        网络版本计算器(再谈“协议“)_第1张图片

        终于考完学校的课程了,大概有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

网络版本计算器(再谈“协议“)_第2张图片

json序列化演示 

#include
#include

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; 

网络版本计算器(再谈“协议“)_第3张图片

   

  //StyledWriter

  Json::StyledWriter writer;

网络版本计算器(再谈“协议“)_第4张图片

     

        总结:通过上面的两种输出结果就知道了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;

}

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

网络版本计算器(再谈“协议“)_第5张图片

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

网络版本计算器(再谈“协议“)_第6张图片

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


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 
#include "Protocol.hpp"
#include "Sock.hpp"
#include 

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
#include
#include

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
#include
#include
#include
#include
#include
#include
#include

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

演示结果:

网络版本计算器(再谈“协议“)_第7张图片

总结 

网络版本计算器(再谈“协议“)_第8张图片

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

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

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

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

                        网络版本计算器(再谈“协议“)_第9张图片

你可能感兴趣的:(计算机网络(Linux),服务器,c++,网络协议)