NetCal2.0

CalculatorClient.cc

#include "TcpClient.hpp"
#include "Sock.hpp"
#include "Protocol.hpp"

#include <iostream>
#include <string>

using namespace protocol_ns;

static void usage(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << " serverip serverport\n"
              << std::endl;
}

enum
{
    LEFT,
    OPER,
    RIGHT
};

// 10+20
Request ParseLine(const std::string &line)
{
    std::string left, right;
    char op;
    int status = LEFT;
    int i = 0;
    while(i < line.size())
    {
        // if(isdigit(e)) left.push_back;

        switch (status)
        {
        case LEFT:
            if (isdigit(line[i]))
                left.push_back(line[i++]);
            else
                status = OPER;
            break;
        case OPER:
            op = line[i++];
            status = RIGHT;
            break;
        case RIGHT:
            if (isdigit(line[i]))
                right.push_back(line[i++]);
            break;
        }
    }

    Request req;
    std::cout << "left: " << left << std::endl;
    std::cout << "right: " << right << std::endl;
    std::cout << "op: " << op << std::endl;

    req._x = std::stoi(left);
    req._y = std::stoi(right);
    req._op = op;

    return req;
}

// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }
    std::string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);

    Sock sock;
    sock.Socket();//创建套接字

    int n = sock.Connect(serverip, serverport);//
    if (n != 0)
        return 1;

    std::string buffer;
    while (true)
    {
        std::cout << "Enter# "; // 输入计算表达式1+1,2*9
        std::string line;
        std::getline(std::cin, line);
        // std::cout << "data1# ";
        // std::cin >> req._x;
        // std::cout << "data2# ";
        // std::cin >> req._y;
        // std::cout << "op# ";
        // std::cin >> req._op;
        Request req = ParseLine(line);//将“1+1”这种字符串解析到req当中
        std::cout << "test: " << req._x << req._op << req._y << std::endl;

        // 1. 序列化
        std::string sendString;
        req.Serialize(&sendString);
        // 2. 添加报头
        sendString = AddHeader(sendString);
        // 3. send
        send(sock.Fd(), sendString.c_str(), sendString.size(), 0);

        // 4. 获取响应
        std::string package;
        int n = 0;
    START:
        n = ReadPackage(sock.Fd(), buffer, &package);
        if (n == 0)//读取成功了,但是没有读到一个完整求,所以还要去start继续读取。
            goto START;
        else if (n < 0)//读取错误就直接break退出得了
            break;
        else
        { 
        }

        // 5. 走到这里说明读到了一个完整的的响应,去掉报头
        package = RemoveHeader(package, n);

        // 6. 反序列化
        Response resp;
        resp.Deserialize(package);
 
        std::cout << "result: " << resp._result << "[code: " << resp._code << "]" << std::endl;
    }

    sock.Close();  
    return 0;
}

CalculatorServer.cc

#include "TcpServer.hpp"
#include <memory>
using namespace tcpserver_ns;

// ./calserver 8888
Response calculate(const Request &req)
{
    // 走到这里,一定保证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;
}

Err.hpp

#pragma once

enum
{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    CONNECT_ERR,
    SETSID_ERR,
    OPEN_ERR
};

Log.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <cstdarg>
#include <sys/types.h>
#include <unistd.h>

// 日志是有日志等级的

const std::string filename = "log/tcpserver.log";

enum
{
    Debug = 0,
    Info,
    Warning,
    Error,
    Fatal,
    Uknown
};

static std::string toLevelString(int level)
{
    switch (level)
    {
    case Debug:
        return "Debug";
    case Info:
        return "Info";
    case Warning:
        return "Warning";
    case Error:
        return "Error";
    case Fatal:
        return "Fatal";
    default:
        return "Uknown";
    }
}

static std::string getTime()
{
    time_t curr = time(nullptr);
    struct tm *tmp = localtime(&curr);
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "%d-%d-%d %d:%d:%d", tmp->tm_year + 1900, tmp->tm_mon+1, tmp->tm_mday,
             tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
    return buffer;
}

// 日志格式: 日志等级 时间 pid 消息体
// logMessage(DEBUG, "hello: %d, %s", 12, s.c_str()); // DEBUG hello:12, world
void logMessage(int level, const char *format, ...)
{
    char logLeft[1024];
    std::string level_string = toLevelString(level);
    std::string curr_time = getTime();
    snprintf(logLeft, sizeof(logLeft), "[%s] [%s] [%d] ", level_string.c_str(), curr_time.c_str(), getpid());

    char logRight[1024];
    va_list p;
    va_start(p, format);
    vsnprintf(logRight, sizeof(logRight), format, p);
    va_end(p);

    // 打印
    printf("%s%s\n", logLeft, logRight);

    // 保存到文件中
    // FILE *fp = fopen(filename.c_str(), "a");
    // if(fp == nullptr)return;
    // fprintf(fp,"%s%s\n", logLeft, logRight);
    // fflush(fp); //可写也可以不写
    // fclose(fp);


    // 预备
    //  va_list p; // char *
    //  int a = va_arg(p, int);  // 根据类型提取参数
    //  va_start(p, format); //p指向可变参数部分的起始地址
    //  va_end(p); // p = NULL;
}

Protocol.hpp

#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <cstring>
#include <jsoncpp/json/json.h>//需要通过执行sudo yum install -y jsoncpp-devel来使用该库。系统默认的头文件只能到/usr/include这里。做为第三方库需要在编译的时候-ljsoncpp
#include "Util.hpp"

// #define MYSELF 1//定义了MYSELF就用我们自己的方法,没有定义我们会看到#ifdef MYSELF和#else之间我们自己定义的代码就相当于被注释掉了,采用线程的函数接口来进行序列化。

// 给网络版本计算机定制协议
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"协议号"\r\n""_x _op _y"\r\n,通过协议号我们一份代码可以存在多份协议,也就是对应多对request和response类。

    // "10 + 20" => "7"\r\n""10 + 20"\r\n => 报头 + 有效载荷
    // 请求/响应 = 报头\r\n有效载荷\r\n

    // "10 + 20" => "7"\r\n""10 + 20"\r\n
    std::string AddHeader(const std::string &str)
    {
        std::cout << "AddHeader 之前:\n"
                  << str << std::endl;
        std::string s = std::to_string(str.size());
        s += HEADER_SEP;
        s += str;
        s += HEADER_SEP;

        std::cout << "AddHeader 之后:\n"
                  << s << std::endl;

        return s;
    }
    // "7"\r\n""10 + 20"\r\n => "10 + 20"
    std::string RemoveHeader(const std::string &str, int len)
    {
        std::cout << "RemoveHeader 之前:\n"
                  << str << std::endl;

        std::string res = str.substr(str.size() - HEADER_SEP_LEN - len, len);//从后面来提取有效荷载

        std::cout << "RemoveHeader 之后:\n"
                  << res << std::endl;

        return res;
    }
    // const &: 输入
    // *: 输出
    // &: 输入输出
    int ReadPackage(int sock, std::string &inbuffer, std::string *package)//从sock套接字里面读取报文,有完整报文就向上交付,否则一直读取。读到的报文如果不完整不应该丢弃,而是应该放在inbuffer里面,
    {
        std::cout << "ReadPackage inbuffer 之前:\n"
                  << inbuffer << std::endl;

        // 边读取,将所有的数据都读到buffer里面,然后从buffer里面分析完整的报文放到package里面。
        char buffer[1024];
        ssize_t s = recv(sock, buffer, sizeof(buffer - 1), 0);//第四个参数flag默认为0
        if (s <= 0)
            return -1;//读取出错return-1
        buffer[s] = 0;
        inbuffer += buffer;

        std::cout << "ReadPackage inbuffer 之中:\n"
                  << inbuffer << std::endl;

        // 边分析,从inbuffer中分析提取完整的报文 "7"\r\n""10 + 20"\r\n
        auto pos = inbuffer.find(HEADER_SEP);
        if (pos == std::string::npos)
            return 0;//没有读取到完整的报文return 0                                                    // inbuffer我没看可什么都没有动
        std::string lenStr = inbuffer.substr(0, pos);                    // 获取了头部字符串, 里面以字符串呈现出来了有效荷载的长度,inbuffer我没看可什么都没有动
        int len = Util::toInt(lenStr);                                   //  
        int targetPackageLen = lenStr.size() + len + 2 * HEADER_SEP_LEN; // 完整报文的长度,inbuffer此时什么都没有动
        if (inbuffer.size() < targetPackageLen)//说明inbuffer里面并灭有完整的报文
            return 0;//没有读取到完整的报文return 0                       // inbuffer我没看可什么都没有动
        *package = inbuffer.substr(0, targetPackageLen); // 走到这里说明提inbuffer里面有有效的报文,所以下一步取到了完整的报文包含有效再和和报头,inbuffer我没看可什么都没有动,package保存的是"7"\r\n""10 + 20"\r\n
        inbuffer.erase(0, targetPackageLen);             // 从inbuffer中直接移除整个报文

        std::cout << "ReadPackage inbuffer 之后:\n"
                  << inbuffer << std::endl;

        return len;
    }
    ///
    // Request && Response都要提供序列化和反序列化功能
    // 1. 自己手写 -- done
    // 2. 用别人写的 --- json, xml, protobuf(精品课)
    // class CalRequest
    class Request
    {
    public:
        Request() {}
        Request(int x, int y, char op) : _x(x), _y(y), _op(op)
        {
        }
        // struct->string
        // 目前: "_x _op _y"
        bool Serialize(std::string *outStr)
        {
            *outStr = "";
#ifdef MYSELF
            std::string x_string = std::to_string(_x);
            std::string y_string = std::to_string(_y);

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

            std::cout << "Request Serialize:\n"
                      << *outStr << std::endl;
#else
            //使用json进行序列化的工作
            Json::Value root; // Value: 一种万能对象, 接受任意的kv类型

            root["x"] = _x;
            root["y"] = _y;
            root["op"] = _op;
            // Json::FastWriter writer; // Writer:是用来进行序列化的,将struct -> string
            Json::StyledWriter writer;//和上面本质是一样的,只是采用了一种不一样的序列话方法而已。
            *outStr = writer.write(root);//write对象的write方法
#endif
            return true;
        }
        // string->struct
        bool Deserialize(const std::string &inStr)
        {
#ifdef MYSELF
            // inStr : 10 + 20 => [0]=>10, [1]=>+, [2]=>20
            // string -> vector
            std::vector<std::string> result;
            Util::StringSplit(inStr, SEP, &result);
            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];
#else
            Json::Value root;
            Json::Reader reader; // Reader: 用来进行反序列化的,将string->struct,名字很好,读数据之前一定要进行反序列化。
            reader.parse(inStr, root);//喂给函数一个json串inStr,将结果放到root万能对象里面  
            _x = root["x"].asInt();//jason会把拿到的整数、double都变成字符串,所以这边需要类型转换
            _y = root["y"].asInt();
            _op = root["op"].asInt();//op是char类型,在转的时候也当成整数进行处理就可以了。

#endif
            Print();

            return true;
        }
        void Print()
        {
            std::cout << "_x: " << _x << std::endl;
            std::cout << "_y: " << _y << std::endl;
            std::cout << "_z: " << _op << std::endl;
        }
        ~Request() {}

    public:
        // _x op _y ==> 10 / 0 ? ==> 10 * 9 ?
        int _x;
        int _y;
        char _op;//x、y、op分别是什么变量类型以及一定是x op y这种顺序等等这些要求都是客户端和服务器一定要形成的共识和约定, 是我们定义的协议。 
    };
    // class CalResponse
    class Response
    {
    public:
        Response() {}
        Response(int result, int code) : _result(result), _code(code)
        {
        }
        // struct->string
        bool Serialize(std::string *outStr)
        {
            *outStr = "";
#ifdef MYSELF
            //_result _code
            std::string res_string = std::to_string(_result);
            std::string code_string = std::to_string(_code);

            *outStr = res_string + SEP + code_string;

            std::cout << "Response Serialize:\n"
                      << *outStr << std::endl;
#else
            Json::Value root;
            root["result"] = _result;
            root["code"] = _code;
            // Json::FastWriter writer;
            Json::StyledWriter writer;
            *outStr = writer.write(root);
#endif
            return true;
        }
        // string->struct
        bool Deserialize(const std::string &inStr)
        {
#ifdef MYSELF
            // 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]);
#else
            Json::Value root;
            Json::Reader reader;
            reader.parse(inStr, root);
            _result = root["result"].asInt();
            _code = root["code"].asInt();
#endif
            Print();
            return true;
        }

        void Print()
        {
            std::cout << "_result: " << _result << std::endl;
            std::cout << "_code: " << _code << std::endl;
        }
        ~Response() {}

    public:
        int _result;
        int _code; // 0 success, 1,2,3,4代表不同的错误码
    };
}

Sock.hpp

#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()
    {
        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)
    {
        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;
    }
    void Close()
    {
        if (_sock != defaultfd)
            close(_sock);
    }
    ~Sock()
    {
        
    }

private:
    int _sock;
};

TcpServer.hpp

#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 &)>;

    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);
            }
        }

        static void *ThreadRoutine(void *args)
        {
            pthread_detach(pthread_self());
            ThreadData *td = static_cast<ThreadData *>(args);
            td->_tsvrp->ServiceIO(td->_sock, td->_ip, td->_port);
            logMessage(Debug, "thread quit, client quit ...");
            delete td;
            return nullptr;
        }
        // 我们这个函数是被多线程调用的
        void ServiceIO(int sock, const std::string &ip, const uint16_t &port)
        {
            std::string inbuffer;
            while (true)
            {
                // 0. 你怎么保证你读到了一个完整的字符串报文?"7"\r\n""10 + 20"\r\n
                std::string package;//用来存放一个完整的报文
                int n = ReadPackage(sock, inbuffer, &package);//从socket里面读取一个有效的报文并放入package当中  
                if (n == -1)//读取失败直接退出
                    break;
                else if (n == 0)//读取成功,但是没有读取到完整的报文就继续循环再去读取
                    continue;
                else//读取到一个完整的报文,并返回有效载荷的长度,也只有读到一个完整的请求才会有后续的响应工作
                {
                    // 一定得到了一个"7"\r\n""10 + 20"\r\n
                    // 1. 你需要的只是有效载荷"10 + 20"
                    package = RemoveHeader(package, n);
                    // decode
                    
                    // 2. 假设已经读到了一个完整的string
                    Request req;
                    req.Deserialize(package); // 对读到的request字符串要进行反序列化

                    // 3. 直接提取用户的请求数据啦
                    Response resp = _func(req); // 业务逻辑!

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

                    // 5. 添加报头
                    send_string = AddHeader(send_string);

                    //encode
                    // 6. 发送
                    send(sock, send_string.c_str(), send_string.size(), 0);
                }
            }
            close(sock);
        }
        ~TcpServer()
        {
            _listensock.Close();
        }

    private:
        uint16_t _port;
        Sock _listensock;
        func_t _func;
    };
}

Util.hpp

#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)
    {
        size_t start = 0;
        // + 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));
        return true;
    }

    static int toInt(const std::string &s)
    {
        // std::stoi();
        return atoi(s.c_str());
    }
};

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值