应用层序列化和反序列化代码_1

Util.h

//存放工具方法
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
using namespace std;
class Util
{
public:
    // 纯输入,在函数内部不会做修改: const &
    // 输出: *
    // 输入输出: &
    static bool StringSplit(const string &str, const string &sep, vector<std::string> *result)//sep是分隔符
    {
        size_t start = 0;
        // 10 + 20
        // "abcd efg" -> for(int i = 0; i < 10; i++) !=  for(int i = 0; i <= 9; i++)
        while (start < str.size())//循环切割
        {
            auto pos = str.find(sep, start);
            if (pos == string::npos) break;
            result->push_back(str.substr(start, pos-start));
            // 位置的重新reload
            start = pos + sep.size();
        }
        if(start < str.size())  result->push_back(str.substr(start));//将最后一个结构化的变量放入vector当中
        return true;
    }

    static int toInt(const std::string &s)
    {
        return atoi(s.c_str());//字符串转整数
    }
};

Sock.h

//对tcpsocket做一下封装
#pragma once
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include "Log.hpp"
#include "Err.hpp"
static const int gbacklog = 32;
static const int defaultfd = -1;
class Sock
{
public:
    Sock() : _sock(defaultfd)
    {
    }
    void Socket()
    {
        _sock = socket(AF_INET, SOCK_STREAM, 0);
        if (_sock < 0)
        {
            logMessage(Fatal, "socket error, code: %d, errstring: %s", errno, strerror(errno));
            exit(SOCKET_ERR);
        }
    }
    void Bind(const 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;

        if (bind(_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            logMessage(Fatal, "bind error, code: %d, errstring: %s", errno, strerror(errno));
            exit(BIND_ERR);
        }
    }
    void Listen()//将套接字设置成为listen状态
    {
        if (listen(_sock, gbacklog) < 0)
        {
            logMessage(Fatal, "listen error, code: %d, errstring: %s", errno, strerror(errno));
            exit(LISTEN_ERR);
        }
    }
    int Accept(std::string *clientip, uint16_t *clientport)//获取连接,第二三个参数是输出型参数,获得客户端的信息。
    {
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        int sock = accept(_sock, (struct sockaddr *)&temp, &len);
        if (sock < 0)
        {
            logMessage(Warning, "accept error, code: %d, errstring: %s", errno, strerror(errno));
        }
        else
        {
            *clientip = inet_ntoa(temp.sin_addr);//网络转主机序列
            *clientport = ntohs(temp.sin_port);//网络转主机序列
        }
        return sock;
    }
    int Connect(const std::string &serverip, const uint16_t &serverport)//为tcp客户端准备的接口
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(serverport);
        server.sin_addr.s_addr = inet_addr(serverip.c_str());

        return connect(_sock, (struct sockaddr *)&server, sizeof(server));
    }
    int Fd()//用来获取套接字
    {
        return _sock;
    }
    ~Sock()
    {
        if (_sock != defaultfd)
            close(_sock);
    }

private:
    int _sock;//这个套接字是用来监听、connect、还是获取连接等等由用户自己来决定
};

Protocol.h

//制定协议,协议就是结构话的数据
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <cstring>
#include "Util.hpp"

// 给网络版本计算机定制协议
namespace protocol_ns
{
    #define SEP " "
    #define SEP_LEN strlen(SEP) //绝对不能写成sizeof
    #define HEADER_SEP "\r\n"
    #define HEADER_SEP_LEN strlen("\r\n")

    // "长度"\r\n""_x _op _y"\r\n
    // "10 + 20" => "7"\r\n""10 + 20"\r\n => 报头 + 有效载荷,添加报头是为了保证用户正确的获得一个完整的报文string
    // 请求/响应 = 报头\r\n有效载荷\r\n

    // "10 + 20" => "7"\r\n""10 + 20"\r\n
    std::string AddHeader(const std::string &str)//添加报头
    {
        std::string s = std::to_string(str.size());
        s += HEADER_SEP;
        s += str;
        s += HEADER_SEP;
    }
    // "7"\r\n""10 + 20"\r\n => "10 + 20"
    std::string RemoveHeader(const std::string &str)//去除报头
    {
        //?
    }

    // Request && Response都要提供序列化和反序列化功能
    // 1. 自己手写
    // 2. 用别人写的
    class Request//客户端发送请求
    {
    public:
        Request(){}
        Request(int x, int y, char op): _x(x), _y(y), _op(op)
        {}
        
        // 序列化之后的字符串构成(用空格做为成员之间的分隔符SEP): "_x _op _y"
        bool Serialize(std::string *outStr)// struct->string将结构话的数据转换成为字符串,这里的outStr是输出型参数,是序列化之后的结果
        {
            *outStr = "";
            std::string x_string = std::to_string(_x);
            std::string y_string = std::to_string(_y);

            //手动序列化
            *outStr = x_string + SEP + _op + SEP + y_string;

            return true;
        }
        // string->struct
        bool Deserialize(const std::string &inStr)
        {
            //inStr : 10 + 20 => [0]=>10, [1]=>+, [2]=>20
            //string -> vector
            std::vector<std::string> result;
            Util::StringSplit(inStr, SEP, &result);//Util::使用类内的某个公共方法,对字符串进行切割成各个结构化的成员变量再放入vector容器里面
            if(result.size() != 3) return false;//可以看成是一种协议规定
            if(result[1].size() != 1) return false; //可以看成是一种协议规定
            _x = Util::toInt(result[0]);
            _y = Util::toInt(result[2]);
            _op = result[1][0];//result[1]是一个string类型,不能直接赋值给一个字符变量
        }
        ~Request() {}
    public:
        // _x op _y ==> 10 / 0 ? ==> 10 * 9 ? 
        int _x;
        int _y;
        char _op;
    };

    class Response//响应
    {
    public:
        Response() {}
        Response(int result, int code):_result(result), _code(code)
        {}
        // struct->string
        bool Serialize(std::string *outStr)
        {
            //_result _code
            *outStr = "";
            std::string res_string = std::to_string(_result);
            std::string code_string = std::to_string(_code);

            *outStr = res_string + SEP + code_string;
            return true;
        }
        // string->struct
        bool Deserialize(const std::string &inStr)
        {
            // 10 0, 10 1
            std::vector<std::string> result;
            Util::StringSplit(inStr, SEP, &result);
            if(result.size() != 2) return false;

            _result = Util::toInt(result[0]);
            _code = Util::toInt(result[1]);
            return true;
        }
        ~Response() {}
    public:
        int _result;//肯定要有结果
        int _code; //但是不能光有结果,还要有任务处理合法性的判断。0 success, 1,2,3,4代表不同的错误码
    };
}

Calserver.h

#pragma once

#include <iostream>
#include <pthread.h>
#include <functional>
#include "Sock.hpp"
#include "Protocol.hpp"

namespace tcpserver_ns
{
    using namespace protocol_ns;
    class TcpServer;

    using func_t = std::function<Response(const Request&)>;//函数返回类型是Response,参数是Request

    class ThreadData
    {
    public:
        ThreadData(int sock, std::string ip, uint16_t port, TcpServer *tsvrp)
            : _sock(sock), _ip(ip), _port(port), _tsvrp(tsvrp)
        {
        }
        ~ThreadData() {}

    public:
        int _sock;
        std::string _ip;
        uint16_t _port;
        TcpServer *_tsvrp;
    };

    class TcpServer
    {
    public:
        TcpServer(func_t func, uint16_t port) : _func(func), _port(port)
        {
        }
        void InitServer()
        {
            // 1. 初始化服务器
            _listensock.Socket();
            _listensock.Bind(_port);
            _listensock.Listen();
            logMessage(Info, "init server done, listensock: %d", _listensock.Fd());
        }

        void Start()
        {
            for (;;)//服务器启动之后就需要周而复始的循环
            {
                std::string clientip;
                uint16_t clientport;
                int sock = _listensock.Accept(&clientip, &clientport);
                if (sock < 0)
                    continue;
                logMessage(Debug, "get a new client, client info : [%s:%d]", clientip.c_str(), clientport);
                //采用多线程的版本
                pthread_t tid;
                ThreadData *td = new ThreadData(sock, clientip, clientport, this);//
                pthread_create(&tid, nullptr, ThreadRoutine, td);//td做为线程执行函数的参数,里面由很多信息,比如说this指针、套接字sock、客户端的ip和端口号等等,this指针里面还有ServiceIO函数用来处理来自客户端的数据
            }
        }
        static void *ThreadRoutine(void *args)
        {
            pthread_detach(pthread_self());//省略了主线程的join操作
            ThreadData *td = static_cast<ThreadData *>(args);
            td->_tsvrp->ServiceIO(td->_sock, td->_ip, td->_port);//该服务器的线程要开始执行处理客户端的任务了
            // logMessage(Debug, "thread running ...");
            delete td;
            return nullptr;
        }
        // 我们这个函数是被多线程调用的,将sock文件描述符的创建工作和sock的读取工作让不同的线程进行。,
        void ServiceIO(int sock, const std::string &ip, const uint16_t &port)//负责从sock读客户端的数据以及将处理号的数据write到sock,第二三个参数是让函数知道我是在和哪个客户端进行通信
        {
            // 1. read/recv - 如何正确的读,继续设计协议,该协议的目的是为了保证用户正确的获得一个完整的报文string!
            // 你怎么保证你读到的string 就是一个完整的string风格的请求呢??不能!
            

            // 我们进行一直循环读取,边读取,边检测,测试
            char buffer[1024];
            ssize_t s = recv(sock, buffer, sizeof(buffer), 0); // ssize_t recv(int sockfd, void *buf, size_t len, int flags)第四个参数flag代表读取方式,默认为零即可。

            // 2. 假设已经读到了一个完整的string
            Request req;
            req.Deserialize(str); // 对读到的request字符串要进行反序列化。将字符串str反序列化成为结构化的数据存储到req的x、y、op成员变量里面。

            // 3. 直接提取用户的请求数据x、y、op啦,处理逻辑是客户端发个我一个request,服务器返回一个response
            Response resp = _func(req); // 业务逻辑!

            // 4. 给用户返回响应 - 序列化
            std::string send_string;
            resp.Serialize(&send_string); // 对计算完毕的response结构要进行序列化,形成可发送字符串

            // 5. 发送到网络
        }
        ~TcpServer()
        {
        }

    private:
        uint16_t _port;//任何一个服务器都需要一个端口号
        Sock _listensock;//创建listen套接字
        func_t _func;
    };
}

CalServer.cc

#include "TcpServer.hpp"
#include <memory>
using namespace tcpserver_ns;
// ./calserver 8888
Response calculate(const Request &req)//服务器对于客户端发送的request的处理逻辑,以后只要将该函数以及Protocol.h文件一改,就当与是一个替换协议的过程。
{
    // 走到这里,一定保证req是有具体数据的!
    // _result(result), _code(code)
    Response 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;
    }

    return resp;
}

int main()
{
    uint16_t port = 8888;
    std::unique_ptr<TcpServer> tsvr(new TcpServer(calculate, port)); // TODO
    tsvr->InitServer();//初始化服务器
    tsvr->Start();//让服务器跑起来

    return 0;
}

 CalClient.cc

//telnet是一个命令,和接口要区分,可以向服务其发起tcp登录入telnet 127.0.0.1 8888
#include "TcpClient.hpp"
int main()
{
    std::cout << "cal client" << std::endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值