【网络版本计算器】

Tcpserver.hpp

#include "Socket.hpp"
#include <functional>
using func_t = std::function<string(string &, bool *)>;
// 相当于#define就是取别名
class Tcpserver;
class ThreadData
{
public:
    ThreadData(Tcpserver *tcp_this, Net_work::Socket *sockp)
        : _this(tcp_this), _sockp(sockp)
    {
    }

public:
    Tcpserver *_this;
    Net_work::Socket *_sockp; // 监听得到的新的套接字
};
class Tcpserver
{
public:
    Tcpserver(uint16_t port, func_t handler_request) : _port(port),
                                                       _listensocket(new Net_work::Tcpsocket()), _handler_request(handler_request)
    {
        _listensocket->BuidListenSocketMethod(_port, 3);
    }

    static void *ThreadRun(void *argc)
    {
        pthread_detach(pthread_self()); // 创建完自己关闭
        // 强制类型转换是有一定风险的,有的转换并不一定安全,如把整型数值转换成指针,把基类指针转换成派生类指针,
        // static_cast 用于进行比较“自然”和低风险的转换,如整型和浮点型、字符型之间的互相转换。
        // static_cast 不能用于在不同类型的指针之间互相转换,
        // 也不能用于整型和指针之间的互相转换。因为这些属于风险比较高的转换。

        // ThreadData *data = (ThreadData *)argc;
        ThreadData *data = static_cast<ThreadData *>(argc);
        // 1.读取数据
        // 新线程执行的方法不由线程决定,而由外部的方法传入
        // 只读取数据,不关心数据是什么,是否是完整的,都不关心
        string inbuffer;
        while (true)
        {
            if (!data->_sockp->Recv(&inbuffer, 1024))
                break;

            // 读取成功,报文处理,外部去做
            bool ok = true;
            string send_message = data->_this->_handler_request(inbuffer, &ok);
            if (ok)
            {
                if (!send_message.empty())
                    data->_sockp->Send(send_message);
            }
            else
            {

                break;
            }
            
        }

        data->_sockp->CloseSocket();
        delete data->_sockp;
        delete data;
        return nullptr;
    }
    void Loop()
    {
        // 一直监听套接字,等待连接就行
        while (true)
        {
            string peerip;
            uint16_t peerport;
            Net_work::Socket *newscocket = _listensocket->AcceptConnection(&peerip, &peerport);

            if (newscocket == nullptr)
                continue;
            // 创建新线程去处理读写 io
            pthread_t tid;
            ThreadData *td = new ThreadData(this, newscocket);
            pthread_create(&tid, nullptr, ThreadRun, td);
            // pthread_create(&tid,nullptr,ThreadRun,nullptr);报错原因,类内方法,自带this指针
        }
    }
    ~Tcpserver()
    {
        delete _listensocket;
    }

private:
    int _port;
    Net_work::Socket *_listensocket; // 防止命名冲突,加一个命名空间
public:
    func_t _handler_request;
};

Socket.hpp

#pragma once
// 网络版本计算器,
#include <iostream>
#include <string>
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
using namespace std;
// 服务器端:
// socket()-->bind( )-->listen()-->accept()-->read()/write()--->close()
// socket()//创建套接字
// bind()//分配套接字地址
// listen()//等待连接请求状态
// accept()//允许连接
// read()/write()//进行数据交换
// close()//断开连接

// 客户端:
// socket()-->connect()-->read()/write()-->close()
// socket()//创建套接字
// connect()//请求连接
// read()/write()//进行数据交换
// close()//断开连接
// 缺省套接字
namespace Net_work
{

    const static int defaultsocket = 1;
    enum
    {
        SocketError = 1,
        BindError,
        ListenError,
    };
    class Socket
    {
    public:
        virtual ~Socket() {}
        virtual void CreatSocketOrdie() = 0; // 纯虚函数的声明。这里的= 0是使函数成为纯虚函数的关键部分。
        // 纯虚函数本身在基类中是不会有实现的,它们必须在派生类中被重写。
        // 如果基类试图为纯虚函数提供实现,那么它就不再是纯虚函数,而是一个虚函数了。
        virtual void BindSocketOrdie(uint16_t port) = 0;
        virtual void ListenSocketOrdie(int backlog) = 0;
        virtual Socket *AcceptConnection(string *peerip, uint16_t *peerport) = 0;
        virtual bool ConnectServer(string &peerip, uint16_t &peerport) = 0;
        virtual int Getsickfd() = 0;
        virtual void Setsockfd(int socketfd) = 0;
        virtual void CloseSocket() = 0;
        virtual bool Recv(string *buffer, int size) = 0;
        virtual bool Send(string &buffer) = 0;

    public:
        void BuidListenSocketMethod(uint16_t port, int backlog)
        {
            CreatSocketOrdie();
            BindSocketOrdie(port);
            ListenSocketOrdie(backlog);
        }
        bool BuildConnectSocketMethod(string &peerip, uint16_t &peerport)
        {
            CreatSocketOrdie();
            return ConnectServer(peerip, peerport);
        }
        void BuildnomalSocketMethod(int socketfd)
        {
            Setsockfd(socketfd);
        }
    };
    class Tcpsocket : public Socket
    {
    private:
        int _sockfd;

    public:
        // 构建一个缺省套接字
        Tcpsocket(int tcpsocket = defaultsocket) : _sockfd(tcpsocket) {}
        ~Tcpsocket() {}
        void CreatSocketOrdie() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0)
                exit(SocketError);
            // 创建套接字文件,返回文件描述符号
        }
        void BindSocketOrdie(uint16_t port) override
        {
            // 绑定套接字
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_addr.s_addr = INADDR_ANY;
            local.sin_port = htons(port);
            // 将套接字文件ip.port建立固定对应关系
            int n = ::bind(_sockfd, (sockaddr *)&local, sizeof(local));
            if (n < 0)
                exit(BindError);
        }
        void ListenSocketOrdie(int backlog) override
        {
            // backlog队列的容量
            // 将套接字文件描述符变为被动描述符
            int n = listen(_sockfd, backlog);
            if (n < 0)
                exit(ListenError);
        }
        Socket *AcceptConnection(string *peerip, uint16_t *peerport) override
        { // 服务端监听客户端的请求,三次握手成功后返回一个通信的文件描述符
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int newsockfd = ::accept(_sockfd, (sockaddr *)&peer, &len);
            if (newsockfd < 0)
                return nullptr;
            *peerip = inet_ntoa(peer.sin_addr);
            *peerport = ntohs(peer.sin_port);
            Socket *s = new Tcpsocket(newsockfd);
            return s;
        }
        bool ConnectServer(string &peerip, uint16_t &peerport) override
        {
            // 主动向服务器发送三次握手请求
            // 先有一个服务器的ip,port的结合体
            struct sockaddr_in server;
            server.sin_addr.s_addr = inet_addr(peerip.c_str());
            server.sin_family = AF_INET;
            server.sin_port = htons(peerport);
            int n = ::connect(_sockfd, (sockaddr *)&server, sizeof(server));
            if (n == 0)
                return true;
            else
                return false;
        }
        int Getsickfd() override
        {
            return _sockfd;
        }
        void Setsockfd(int socketfd) override
        {
            _sockfd = socketfd;
        }
        void CloseSocket() override
        {
            if (_sockfd > defaultsocket)
                ::close(_sockfd);
        }
        bool Recv(string *buffer, int size) override
        {
            // 这里读取,一直往inbuffer里面添加
            // read是覆盖式的读取,因此不能直接读到buffer里面
            // 我可能读上来的不是一个完整的报文,还要接着读取添加到刚才读取的后面
            char message[size];
            int a = recv(_sockfd, &message, size-1,0);
            if (a > 0)
            {
                message[a] = 0; // 结尾添加\0的原因,+=从\0的位置开始
                *buffer += message;
                return true;
            }
            else
                return false;
        }
        bool Send(string &buffer) override
        {
            // write(_sockfd,&buffer,buffer.size()); 发送的是字符串,不是结构化数据
            write(_sockfd,buffer.c_str(),buffer.size());
            return true;
        }
    };
} // namespace Net_work

Protocol.hpp


```cpp

```cpp

```cpp
#pragma once
#include <iostream>
#include <memory>
using namespace std;
// 序列和反序列化的问题
const string ProtSep = " ";
const string LineSep = "\n";
// 添加报头,然后返回一个新的字符串
// "len\nx op y\n"
string Encode(const string &message)
{
    string len = std::to_string(message.size());
    string package = len + LineSep + message + LineSep;
    return package;
}
// "l
// "len
// "len\n
// "len\nx
// "len\nx op
// "len\nx op y
// "len\nx op y\n"
// "len\nx op y\n""len
// "len\nx op y\n" 变为x op y
// 无法保证package的完整性,所以先判断
bool Decode(string &package, string *message)
{
    // 提取len
    int pos = package.find(LineSep);
    //有些错误判断还是必须要写的,不仅找错误方便,程序也更稳定
    if (pos == string::npos)
        return false;
    string len = package.substr(0, pos);
    int messagelen = stoi(len);
    // 判断是否是一个完整的报文
    int total = len.size() + messagelen + 2 * LineSep.size();
    if (package.size() < total)
        return false;
    // 是一个完整的报文开始处理
    *message = package.substr(pos + LineSep.size(), messagelen);
    // *message=package.substr(len.size()-1+LineSep.size(),messagelen);
    package.erase(0, total);
    return true;
}
class Request
{
public:
    Request() {}
    Request(int data_x, int data_y, char oper) : _data_y(data_y), _data_x(data_x), _oper(oper)
    {
    }
    void debug()
    {
        cout << _data_x << "  " << _data_y << "  " << _oper << endl;
    }
    void add()
    {
        _data_x++;
        _data_y++;
    }
    // 序列化
    // 添加报头的工作分开
    bool Serialize(string *out)
    {
        *out = to_string(_data_x) + ProtSep + _oper + ProtSep + to_string(_data_y);
        return true;
    }
    bool Deserialize(string &in)
    {
        size_t left = in.find(ProtSep);
        // static const size_typenpos = static_cast<size_type>(-1);
        if (left == string::npos)
            return false;
        size_t right = in.rfind(ProtSep);
        if (right == string::npos)
            return false;
        // (size_t pos = 0, size_t len = npos)(从哪里开始拷贝,拷贝多少个)
        // 反回值: string,包含s中从pos开始的len个字符的拷贝
        //  若pos的值超过了sting的大小,则substr函数会抛出一个out ofrange异常;
        //  若pos+n的值超过了string的大小,则substr会调整n内值,只拷贝到string的末尾
        _data_x = std::stoi(in.substr(0, left));
        string oper = in.substr(left + ProtSep.size(), right - left - ProtSep.size());
        if (oper.size() != 1)
            return false;
        _oper = oper[0];
        _data_y = std::stoi(in.substr(right + ProtSep.size(), string::npos));
        return true;
    }
    int GetX() { return _data_x; }
    int GetY() { return _data_y; }
    char GetOper() { return _oper; }

private:
    // "len\nx op y\n"   第一个\n保证读到一个len  第二个\n报文边界,方便打印
    // len报文的长度:万一字符串中有\n的话就不行了,所以要加len字段
    int _data_x;
    int _data_y;
    char _oper;
};

class Response
{
public:
    Response()
    {
    }
    Response(int result, int code) : _result(result), _code(code) {}
    bool Serialize(string *out)
    {
        *out = to_string(_result) + ProtSep + to_string(_code);
        return true;
    }
    // 不需要输出的就用引用,
    bool Deserialize(string &in)
    {
        size_t pos = in.find(ProtSep);
        if (pos == string::npos)
            return false;
        _result = stoi(in.substr(0, pos));
        _code = stoi(in.substr(pos + ProtSep.size(), string::npos));
        return true;
    }
    //"result code"
    void SetResult(int res) { _result = res; }
    void SetCode(int code) { _code = code; }
    int GetResult() { return _result; }
    int GetCode() { return _code; }

private:
    int _result;
    int _code;
};

class Factory
{
public:
    shared_ptr<Request> BuildRequest()
    {
        shared_ptr<Request> req = make_shared<Request>();
        return req;
    }
    shared_ptr<Request> BuildRequest(int x, int y, int op)
    {
        shared_ptr<Request> req = make_shared<Request>(x, y, op);
        return req;
    }
    shared_ptr<Response> BuildResponse()
    {
        shared_ptr<Response> resp = make_shared<Response>();
        return resp;
    }
    shared_ptr<Response> BuildResponse(int result, int code)
    {
        shared_ptr<Response> resp = make_shared<Response>(result, code);
        return resp;
    }
};
// make_shared的实现原理源码没看懂,一会去b站上找找
//  make_shared和shared_ptr的区别
//  make_shared只会申请一次内存,这块内存会大于int所占用的内存,多出的部分被用于智能指针引用计数。这样就避免了直接使用shared_ptr带来的问题。
//  而shared_ptr会申请两次内存,一次是指针指向要管理的对象,一次是引用计数
//  智能指针的实现原理,也就是一个模板类,跟vector,list的使用是一样的
//  我里面就两个在栈上开辟的空间,我走了之后,也会把开辟的空间带走
//   template<typename T>
//   class shared_ptr {
//   public:
//   // constructor
//   shared_ptr(T* ptr = nullptr) : m_ptr(ptr), m_refCount(new int(1)) {}

// // copy constructor
// shared_ptr(const shared_ptr& other) : m_ptr(other.m_ptr), m_refCount(other.m_refCount) {
//     // increase the reference count
//     (*m_refCount)++;
// }

// // destructor
// ~shared_ptr() {
//     // decrease the reference count
//     (*m_refCount)--;
//     // if the reference count is zero, delete the pointer
//     if (*m_refCount == 0) {
//         delete m_ptr;
//         delete m_refCount;
//     }
// }

// // overload operator=()
// shared_ptr& operator=(const shared_ptr& other) {
//     // check self-assignment
//     if (this != &other) {
//         // decrease the reference count for the old pointer
//         (*m_refCount)--;
//         // if the reference count is zero, delete the pointer
//         if (*m_refCount == 0) {
//             delete m_ptr;
//             delete m_refCount;
//         }
//         // copy the data and reference pointer and increase the reference count
//         m_ptr = other.m_ptr;
//         m_refCount = other.m_refCount;
//         // increase the reference count
//         (*m_refCount)++;
//     }
//     return *this;
// }

// private:
//     T* m_ptr;            // points to the actual data
//     int* m_refCount;     // reference count
// };

Calculate.hpp

#include "Protocol.hpp"
enum
{
    Success = 0,
    Divzeroerr,
    Modzeroerr,
    Unknowcode
};
class Calculate
{
private:
    Factory factory;

public:
    Calculate(){};
    std::shared_ptr<Response> Cal(std::shared_ptr<Request> req)
    {
        // 构建响应   //为啥智能指针不能用.来用成员函数
        shared_ptr<Response> resp = factory.BuildResponse();
        resp->SetCode(Success);
        switch (req->GetOper())
        {
        case '+':
            resp->SetResult(req->GetX() + req->GetY());
            break;
        case '-':
            resp->SetResult(req->GetX() - req->GetY());
            break;
        case '*':
            resp->SetResult(req->GetX() * req->GetY());

            break;
        case '/':
        {
            if (req->GetY() == 0)
                resp->SetCode(Divzeroerr);
            else
                resp->SetResult(req->GetX() / req->GetY());
        }
        break;
        case '%':
            if (req->GetY() == 0)
                resp->SetCode(Modzeroerr);
            else
                resp->SetResult(req->GetX() % req->GetY());
            break;
        default:
            resp->SetCode(Unknowcode);
            break;
        }
        return resp;
    }
    ~Calculate(){};
};

Tcpservermain.cc


```cpp
#include "Protocol.hpp"
#include "Tcpserver.hpp"
#include "Socket.hpp"
#include "Calculate.hpp"
string handle(string &inbuffer, bool *error_code)
{
    // 只处理报文
    *error_code = true;
    Calculate calculate;
    unique_ptr<Factory> factory = make_unique<Factory>();
    auto req = factory->BuildRequest();
    auto resp = factory->BuildResponse();

    // 优化为一次处理多个报文,构建多个有效载荷,发送一个报文
    //  2.分析是否有一个完整的报文
    string message;
    string total_send_message;
    while (Decode(inbuffer, &message))
    {
        // 3.decode已经提取了一个有效的载荷
        // 接下来放到req里面反序列化提取x y op的值
        if (!req->Deserialize(message))
        {
            // 报文没问题,但是发送的数据没按照协议来
            *error_code = false;
            return string();
        }
        // 4.业务处理req,里面已经有值,计算构建resp
        resp = calculate.Cal(req);
        // 5.格式化构建报文
        string send_string;
        resp->Serialize(&send_string);
        // 6.构建报头
        string send_massage = Encode(send_string);
        total_send_message += send_massage;
    }
    // 如果循环没进去没有一个完整的报文处理不了
    // 进去了就发送
    return total_send_message;
}
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Usage : " << argv[0] << " port" << endl;
    }
    // 不加换行符,打印的一直在缓冲区里,所以以后排错都加endl
    uint16_t localport = stoi(argv[1]);
    Tcpserver server(localport, handle);
    server.Loop();
    return 0;
}

TcpClientmain.cc

#include "Socket.hpp"
#include "Protocol.hpp"
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include "Socket.hpp"
// 在调用rand函数之前,我们要使用srand函数设置生成随机数的起点。起点不同生成的就不同
// 只要在每次执行程序的时候给srand函数传入一个与上一次不同的数即可.时间一直在变化
//  time_t time(time_t *timer)第二个是输出型参数,timer的值和返回值一样(有病的设计)
int main(int argc, char *argv[])
{
    srand(time(NULL) ^ getpid()); // 不加的话每次从新运行的数据都一样
    if (argc != 3)
    {
        cout << "Usage: " << argv[0] << " ip" << " port" << endl;
    }
    Net_work::Socket *client = new Net_work::Tcpsocket();
    string serverip = argv[1];
    uint16_t serverport = stoi(argv[2]);
    // 不是输出型参数就不用引用
    bool n = client->BuildConnectSocketMethod(serverip, serverport);
    if (n == false)
        cout << "connect is error" << endl;
    else
    {
        cout << "connect success " << endl;
        cout << "serverip:" << serverip << " serverport" << serverport << endl;
    }
    // 发消息
    unique_ptr<Factory> factory = make_unique<Factory>();
    const string opers = "+-*/^!=";
    // 智能指针解引用是啥
    while (true)
    {
        int x = rand() % 100;
        int y = rand() % 100;
        char opar = opers[rand() % (opers.size())];
        shared_ptr<Request> req = factory->BuildRequest(x, y, opar);
        // 1.序列化
        string message;
        req->Serialize(&message);
        // 2.构建响应
        string send_message = Encode(message);
        // 3.发送
        client->Send(send_message);
        cout<<send_message;
        while (true)
        {
            // 4.接受
            string resp_messsage;
            client->Recv(&resp_messsage, 1024);
            // 5.反序列化
            string re_mess;
            if (!Decode(resp_messsage, &re_mess))
                continue; // 没有完整的报文
            // 6.解析报文
            auto resp = factory->BuildResponse();
            resp->Deserialize(re_mess);
            // 7.打印
            cout << resp->GetResult() << "  " << resp->GetCode() << endl;
            break;
        }
        sleep(1);
    }
    client->CloseSocket();
    return 0;
}


  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值