【Linux后端服务器开发】Reactor模式实现网络计算器

目录

一、Reactor模式概述

二、日志模块:Log.hpp

三、TCP连接模块:Sock.hpp

四、非阻塞通信模块:Util.hpp

五、多路复用I/O模块:Epoller.hpp

六、协议定制模块:Protocol.hpp

七、服务器模块:Server.hpp    server.cc

八、客户端模块:Client.hpp    client.cc


前情提示:在学习Reactor模式之前,需要熟悉socket套接字及TCP网络通信,需要熟悉 select / poll / epoll 三种多路转接IO,需要理解Linux文件系统的文件描述符与基础IO,需要理解服务器server与客户端client的设计。

【Linux后端服务器开发】基础IO与文件系统_命运on-9的博客-CSDN博客

【Linux后端服务器开发】TCP通信设计_命运on-9的博客-CSDN博客

【Linux后端服务器开发】协议定制(序列化与反序列化)_命运on-9的博客-CSDN博客

【Linux后端服务器开发】select多路转接IO服务器_命运on-9的博客-CSDN博客

【Linux后端服务器开发】poll/epoll多路转接IO服务器_命运on-9的博客-CSDN博客

一、Reactor模式概述

Reactor是什么?reactor的英文翻译是【反应堆】,reactor设计模式是一种事件处理模式。

如何让一个server服务器连接多个client客户端并处理业务?我们可以采用多进程 / 多线程的方法,但是无论是进程还是线程,对系统资源的消耗都是较大的,于是我们可以采用 select / poll / epoll 的多路复用IO方法,而在三种不同的多路复用方法中,性能最优的是epoll。

epoll的LT模式和ET模式该如何选择?LT和ET是不同的事件通知策略,LT水平触发是阻塞式通知,ET边缘触发是非阻塞式通知,ET模式会倒逼程序员一次性将就绪的数据读写完毕,这样也就使得一般情况下ET模式的效率更高,所以在Reactor模式的设计中,采用ET模式。

Reactor模式也叫Dispatcher(分派器)模式,它的工作原理是什么呢? I/O多路复用监听事件,当有事件就绪时,根据事件类型分配给某个进程/线程。

Reactor模式主要由Reactor分派器和资源处理这两个部分组成:

  1. Reactor分派器负责监听和分发事件,事件类型包含连接、读写、异常
  2. 资源处理负责业务处理,通常流程是 read -> 业务逻辑 -> send

Reactor模式是灵活多变的,根据不同的业务场景有不同的设计,可以是【单Reactor】也可以是【多Reactor】,可以是【单进程/线程】 也可以是【多进程/线程】,不过此文中的Reactor网络计算器设计采用的是【单Reactor 单进程/线程】模式。

【单Reactor 单进程/线程】模式设计

初始化TcpServer,创建listensock,并将listensock添加进epoller模型中进行监听,listensock会生成第一个Connection对象(注册_Accepter接口处理读取连接任务),之后的TcpClient的连接请求都是由这个绑定了_Accepter接口的listensock进行监听。

epoller模型Wait()等待TcpClient的请求,如果是连接请求,则TcpServer会派发任务给listensock对象,让其调用Sock对象的Accept接口创建新的套接字sock进行IO,sock会创建一个新的Connection对象(注册_Reader、_Sender、_Excepter接口处理读/写/异常任务)。

Connection进行业务处理的时候,根据TCP通信协议的通信流程是 read -> 业务逻辑 -> send,若是在通信中遇到异常,则会调用_Excepter接口关闭连接。

在TCP通信设计中,我们需要设计通信数据的序列化和反序列化,这便是在Connection对象读取到数据之后的业务处理逻辑,通过序列化和反序列化将数据发送给TcpClient,TcpClient收到服务器发送数据后再通过序列化和反序列化拿到想要的数据。

在服务器设计的时候,日志功能是可以省略的,但是加上日志功能的服务器功能更完整并且方便调试和服务器维护。

二、日志模块:Log.hpp

日志模块里面将日志分为(DEBUG、NORMAL、WARNING、ERROR、FATAL)五个记录等级,并且定义了不同的错误类型,日志记录需要记录进程的IP和端口号以及记录时间。

由于Linux系统的gdb调试是很复杂的,通过在代码中添加DEBUG的打印日志更方便调试。

#pragma once

#include <iostream>
#include <string>
#include <cstdarg>
#include <ctime>
#include <cstdarg>
#include <unistd.h>

#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4

#define NUM 1024

enum
{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    EPOLL_CREATE_ERR
};

const char* To_Level_Str(int level)
{
    switch (level)
    {
    case DEBUG:
        return "DEBUG";
    case NORMAL:
        return "NORMAL";
    case WARNING:
        return "WARNING";
    case ERROR:
        return "ERROR";
    case FATAL:
        return "FATAL";
    default:
        return nullptr;
    }
}

std::string To_Time_Str(long int t)
{
    // 将时间戳转化为tm结构体
    struct tm* cur;
    cur = gmtime(&t);
    cur->tm_hour = (cur->tm_hour + 8) % 24; // 东八区

    char tmp[NUM];
    std::string my_format = "%Y-%m-%d %H:%M:%S";
    strftime(tmp, sizeof(tmp), my_format.c_str(), cur);
    
    std::string cur_time = tmp;
    return cur_time;
}

void Log_Message(int level, const char *format, ...)
{
    char logprefix[NUM];
    std::string cur_time = To_Time_Str((long int)time(nullptr));
    snprintf(logprefix, sizeof(logprefix), "[%s][%s][pid: %d]",
        To_Level_Str(level), cur_time.c_str(), getpid());

    char logcontent[NUM];
    va_list arg;
    va_start(arg, format);
    vsnprintf(logcontent, sizeof(logcontent), format, arg);

    std::cout << logprefix << logcontent << std::endl;
}

三、TCP连接模块:Sock.hpp

Sock对象里面将TCP网络连接的底层接口进行了封装,更方便其他模块对于TCP连接的调用。

Sock对象不再对Accept()的连接失败做处理,将处理权交给了TcpServer。

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "Log.hpp"

const static int g_defaultfd = -1;
const static int g_backlog = 32;

class Sock
{
public:
    Sock()
        : _listensock(g_defaultfd)
    {}

    Sock(int listensock)
        : _listensock(listensock)
    {}

    int Fd()
    {
        return _listensock;
    }

    void Socket()
    {
        // 1. 创建socket文件套接字对象
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            Log_Message(FATAL, "create socket error");
            exit(SOCKET_ERR);
        }
        Log_Message(NORMAL, "create socket success: %d", _listensock);

        int opt = 1;
        setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
    }

    void Bind(int port)
    {
        // 2. bind绑定自己的网络信息
        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(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            Log_Message(FATAL, "bind socket error");
            exit(BIND_ERR);
        }
        Log_Message(NORMAL, "bind socket success");
    }

    void Listen()
    {
        // 3. 设置socket 为监听状态
        if (listen(_listensock, g_backlog) < 0) // 第二个参数backlog后面在填这个坑
        {
            Log_Message(FATAL, "listen socket error");
            exit(LISTEN_ERR);
        }
        Log_Message(NORMAL, "listen socket success");
    }

    int Accept(std::string *clientip, uint16_t *clientport, int* err)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
        *err = errno;
        if (sock >= 0)
        {
            *clientip = inet_ntoa(peer.sin_addr);
            *clientport = ntohs(peer.sin_port);
        }
            
        return sock;
    }

    void Close()
    {
        if (_listensock != g_defaultfd)
            close(_listensock);
    }

    ~Sock()
    {
        this->Close();
    }

private:
    int _listensock;
};

四、非阻塞通信模块:Util.hpp

Util.hpp设置静态成员函数Set_Noblock(),将ET通知策略的套接字sock设置为非阻塞模式。

本次网络计算器的设计是ET模式,故所有的连接在新建连接时都需要设置为非阻塞模式。

#pragma once

#include <iostream>
#include <fcntl.h>
#include <unistd.h>

class Util
{
public:
    static bool Set_Nonblock(int fd)
    {
        int fl = fcntl(fd, F_GETFL);
        if (fl < 0)
            return false;
        fcntl(fd, F_SETFL, fl | O_NONBLOCK);
        return true;
    }
};

五、多路复用I/O模块:Epoller.hpp

epoll模型是一个操作系统层面的模型,我们将控制epoll的系统接口封装在Epoller对象中方便TcpServer的调用。

无论是TcpClient的连接请求还是业务请求,从epoll的角度来看,都是一个对文件描述符的读事件,epoll只需要做事件通知即可。

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <sys/epoll.h>
#include <unistd.h>

#include "Log.hpp"

const static int g_default_epfd = -1;
const static int g_size = 64;

class Epoller
{
public:
    Epoller()
        : _epfd(g_default_epfd)
    {}

    void Create()
    {
        _epfd = epoll_create(g_size);
        if (_epfd < 0)
        {
            Log_Message(FATAL, "epoll create error: %s", strerror(errno));
            exit(EPOLL_CREATE_ERR);
        }
    }

    // user -> kernel
    bool Add_Event(int sock, uint32_t events)
    {
        struct epoll_event ev;
        ev.events = events;
        ev.data.fd = sock;

        int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, sock, &ev);
        return n == 0;
    }

    // kernel -> user
    int Wait(struct epoll_event revs[], int num, int timeout)
    {
        return epoll_wait(_epfd, revs, num, timeout);
    }

    bool Control(int sock, uint32_t event, int action)
    {
        int n = 0;
        if (action == EPOLL_CTL_MOD)
        {
            struct epoll_event ev;
            ev.events = event;
            ev.data.fd = sock;
            n = epoll_ctl(_epfd, action, sock, &ev);
        }
        else if (action == EPOLL_CTL_DEL)
        {
            n = epoll_ctl(_epfd, action, sock, nullptr);
        }
        else
        {
            n = -1;
        }

        return n == 0;
    }

    void Close()
    {
        if (_epfd != g_default_epfd)
            close(_epfd);
    }

    ~Epoller()
    {
        this->Close();
    }

private:
    int _epfd;
};

六、协议定制模块:Protocol.hpp

TCP通信的序列化与反序列化就是网络服务器的业务逻辑,因为TCP通信是字节流传输,无法传输结构化数据,所有我们需要自定义协议做序列化与反序列化处理,进行字符串数据与结构化数据的转换。

序列化与反序列化可以完全编写函数做字符串数据处理,也可以通过调用Json库做字符串数据处理,调用Json库需要加上 -ljsoncpp 动态链接库。

这里的序列化与反序列化,包含了客户端Client和服务器Serer双端的业务逻辑。

#pragma once
 
#include <iostream>
#include <cstring>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <jsoncpp/json/json.h>

#include "Log.hpp"

using namespace std;
 
#define SEP " "
#define LINE_SEP "\r\n"
 
enum
{
    OK = 0,
    DIV_ZERO,
    MOD_ZERO,
    OP_ERR
};
 
// "x op y" -> "content_len"\r\n"x op y"\r\n
string En_Length(const string& text)
{
    string send_str = to_string(text.size());
    send_str += LINE_SEP;
    send_str += text;
    send_str += LINE_SEP;
 
    return send_str;
}
 
// "content_len"\r\n"x op y"\r\n
bool De_Length(const string& package, string* text)
{
    auto pos = package.find(LINE_SEP);
    if (pos == string::npos)
        return false;
    string text_len_str = package.substr(0, pos);
    int text_len = stoi(text_len_str);
    *text = package.substr(pos + strlen(LINE_SEP), text_len);
    return true;
}

// 通信协议不止一种,需要将协议进行编号,以供os分辨
// "content_len"\r\n"协议编号"\r\n"x op y"\r\n
 
class Request
{
public:
    Request(int x = 0, int y = 0, char op = 0)
        : _x(x), _y(y), _op(op)
    {}
 
    // 序列化
    bool Serialize(string* out)
    {
        Json::Value root;
        root["first"] = _x;
        root["second"] = _y;
        root["oper"] = _op;
 
        Json::FastWriter write;
        *out = write.write(root);
        return true;
    }
 
    // 反序列化
    bool Deserialiaze(const string& in)
    {
        Json::Value root;
        Json::Reader reader;
        reader.parse(in, root);
 
        _x = root["first"].asInt();
        _y = root["second"].asInt();
        _op = root["oper"].asInt();
        return true;
    }
 
public:
    int _x, _y;
    char _op;
};
 
class Response
{
public:
    Response(int exitcode = 0, int res = 0)
        : _exitcode(exitcode), _res(res)
    {}
 
    bool Serialize(string* out)
    {
        Json::Value root;
        root["exitcode"] = _exitcode;
        root["result"] = _res;
 
        Json::FastWriter writer;
        *out = writer.write(root);
        return true;
    }
 
    bool Deserialize(const string& in)
    {
        Json::Value root;
        Json::Reader reader;
        reader.parse(in, root);
 
        _exitcode = root["exitcode"].asInt();
        _res = root["result"].asInt();
        return true;
    }
 
public:
    int _exitcode;
    int _res;
};
 
// 读取数据包
// "content_len"\r\n"x op y"\r\n
bool Parse_One_Package(string& inbuf, string* text)
{
    *text = "";
    
    // 分析处理
    auto pos = inbuf.find(LINE_SEP);
    if (pos == string::npos)
        return false;

    string text_len_string = inbuf.substr(0, pos);
    int text_len = stoi(text_len_string);
    int total_len = text_len_string.size() + 2 * strlen(LINE_SEP) + text_len;

    if (inbuf.size() < total_len)
        return false;

    // 至少有一个完整的报文
    *text = inbuf.substr(0, total_len);
    inbuf.erase(0, total_len);

    return true;
}

bool Recv_Package(int sock, string& inbuf, string* text)
{
    char buf[1024];
    while (true)
    {
        ssize_t n = recv(sock, buf, sizeof(buf) - 1, 0);
        if (n > 0)
        {
            buf[n] = 0;
            inbuf += buf;
 
            auto pos = inbuf.find(LINE_SEP);
            if (pos == string::npos)
                continue;
            string text_len_str = inbuf.substr(0, pos);
            int text_len = stoi(text_len_str);
            int total_len = text_len_str.size() + 2 * strlen(LINE_SEP) + text_len;
            cout << "\n收到响应报文:\n" << inbuf;
 
            if (inbuf.size() < total_len)
            {
                cout << "输入不符合协议规定" << endl;
                continue;
            }
 
            *text = inbuf.substr(0, total_len);
            inbuf.erase(0, total_len);
 
            break;
        }
        else
        {
            return false;
        }
    }
 
    return true;
}

七、服务器模块:Server.hpp    server.cc

Server初始化创建listensock、创建epoll模型、创建事件就绪队列(struct epoll_event* _recv),listensock套接字会创建一个注册了_Accepter接口的Connection对象,负责新建Client的连接。

所有的Connection对象通过哈希表管理,极大的提高了效率。每一个Connection对象都有读写缓冲区(_inbuffer / _outbuffer),可以绑定回调的_Recver、_Sender、_Excepter函数,以对事件做读、写、异常处理。

服务器的本质就是一个死循环,循环的等待epoll模型的事件通知然后再Dispatch分派任务做读写处理,若遇到异常问题,将其转化为读写问题再去分派任务。

我们将网络计算服务器的计算任务放入了server.cc中进行函数定义,为了解耦我们也可以再单独创建一个Task()对象,但是此处由于计算任务比较简单,我们就直接在server.cc源文件中定义了。

服务器中的所有监听的Connection对象,我们将其读监听设置为一直开启,将其写监听设置为按需开启,当写任务完成后即关闭写监听。

Server.hpp

#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <unordered_map>
#include <cassert>
#include <unistd.h>

#include "Sock.hpp"
#include "Epoller.hpp"
#include "Log.hpp"
#include "Util.hpp"
#include "Protocol.hpp"

using namespace std;

class Connection;
class TcpServer;

static const uint16_t g_defaultport = 8080;
static const int g_num = 64;

using func_t = function<void(Connection*)>;

class Connection
{
public:
    Connection(int sock, TcpServer* tcp)
        : _sock(sock), _tcp(tcp)
    {}

    void Register(func_t recver, func_t sender, func_t excepter)
    {
        _recver = recver;
        _sender = sender;
        _excepter = excepter;
    }

    void Close()
    {
        close(_sock);
    }

public:
    int _sock;
    string _inbuffer;   // 输入缓冲区
    string _outbuffer;  // 输出缓冲区

    func_t _recver;   // 读
    func_t _sender;   // 写
    func_t _excepter; // 异常

    TcpServer *_tcp; // 可以省略

    // uint64_t last_time;     // 记录最近访问时间,可用于主动关闭某时间段内未访问的连接
};

class TcpServer
{
public:
    TcpServer(func_t service, uint16_t port = g_defaultport)
        : _service(service), _port(port), _revs(nullptr)
    {}

    void InitServer()
    {
        // 1. 创建socket
        _sock.Socket();
        _sock.Bind(_port);
        _sock.Listen();

        // 2. 创建epoller
        _epoller.Create();

        // 3. 将目前唯一的一个sock,添加到Epoller中
        Add_Connection(_sock.Fd(), EPOLLIN | EPOLLET,
                       bind(&TcpServer::Accepter, this, placeholders::_1), nullptr, nullptr);

        _revs = new struct epoll_event[g_num];
        _num = g_num;
    }

    void Enable_Read_Write(Connection* conn, bool readable, bool writeable)
    {
        uint32_t event = (readable ? EPOLLIN : 0) | (writeable ? EPOLLOUT : 0) | EPOLLET;
        _epoller.Control(conn->_sock, event, EPOLL_CTL_MOD);
    }

    // 事件派发
    void Dispatch()
    {
        int timeout = -1;
        while (1)
        {
            Loop(timeout);
            // Log_Message(DEBUG, "timeout ...");

            // 遍历_conn_map,计算每一个节点的最近访问时间做节点控制
        }
    }

    ~TcpServer()
    {
        _sock.Close();
        _epoller.Close();
        if (nullptr == _revs)
            delete[] _revs;
    }

private:
    void Add_Connection(int sock, uint32_t events, func_t recver, func_t sender, func_t excepter)
    {
        // 1. 首先为该sock创建Connection并初始化,并添加到_conn_map
        if (events & EPOLLET)
            Util::Set_Nonblock(sock);
        Connection *conn = new Connection(sock, this);

        // 2. 给对应的sock设置对应的回调方法
        conn->Register(recver, sender, excepter);

        // 3. 其次将sock与它要关心的时间"写透式"注册到epoll中,让epoll帮我们关心
        bool f = _epoller.Add_Event(sock, events);
        assert(f);

        // 4. 将kv添加到_conn_map中
        _conn_map.insert(pair<int, Connection*>(sock, conn));

        Log_Message(DEBUG, "Add_Connection: add new sock: %d in epoll and unordered_map", sock);
    }

    void Recver(Connection *conn)
    {
        char buffer[1024];
        while (1)
        {
            ssize_t s = recv(conn->_sock, buffer, sizeof(buffer) - 1, 0);
            if (s > 0)
            {
                buffer[s] = 0;
                conn->_inbuffer += buffer;      // 将读到的数据入队列
                Log_Message(DEBUG, "\n收到client[%d]请求报文:\n%s", conn->_sock, conn->_inbuffer.c_str());

                _service(conn);
            }
            else if (s == 0)
            {
                // 异常回调
                if (conn->_excepter)
                {
                    conn->_excepter(conn);
                    return;
                }
            }
            else
            {
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                {
                    break;
                }
                else if (errno == EINTR)
                {
                    continue;
                }
                else
                {
                    if (conn->_excepter)
                    {
                        conn->_excepter(conn);
                        return;
                    }
                }
            }
        }
    }

    void Sender(Connection *conn)
    {
        while (1)
        {
            ssize_t s = send(conn->_sock, conn->_outbuffer.c_str(), conn->_outbuffer.size(), 0);
            if (s >= 0)
            {
                if (conn->_outbuffer.empty())
                    break;
                else
                    conn->_outbuffer.erase(0, s);
            }
            else
            {
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                {
                    break;
                }
                else if (errno == EINTR)
                {
                    continue;
                }
                else
                {
                    if (conn->_excepter)
                    {
                        conn->_excepter(conn);
                        return;
                    }
                }
            }
        }

        // 如果没有发送完毕,需要对对应的sock开启写事件的关心,发完了,关闭对写事件的关心
        if (!conn->_outbuffer.empty())
            conn->_tcp->Enable_Read_Write(conn, true, true);
        else
            conn->_tcp->Enable_Read_Write(conn, true, false);
    }

    void Excepter(Connection *conn)
    {
        _epoller.Control(conn->_sock, 0, EPOLL_CTL_DEL);
        conn->Close();
        _conn_map.erase(conn->_sock);

        Log_Message(DEBUG, "关闭 %d 文件描述符的所有资源", conn->_sock);
        delete conn;
    }

    void Accepter(Connection *conn)
    {
        while (1)
        {
            string clientip;
            uint16_t clientport;
            int err = 0;
            int sock = _sock.Accept(&clientip, &clientport, &err);
            if (sock > 0)
            {
                Add_Connection(sock, EPOLLIN | EPOLLET, 
                               bind(&TcpServer::Recver, this, placeholders::_1),
                               bind(&TcpServer::Sender, this, placeholders::_1), 
                               bind(&TcpServer::Excepter, this, placeholders::_1));
                Log_Message(DEBUG, "get a new link, info: [%s : %d]", clientip.c_str(), clientport);
            }
            else
            {
                if (err == EAGAIN || err == EWOULDBLOCK)
                    break;
                else if (err == EINTR)
                    continue;
                else
                    break;
            }
        }
    }

    bool Is_Connection_Exists(int sock)
    {
        auto iter = _conn_map.find(sock);
        return iter != _conn_map.end();
    }

    void Loop(int timeout)
    {
        int n = _epoller.Wait(_revs, _num, timeout); // 获取已经就绪的事件
        for (int i = 0; i < n; ++i)
        {
            int sock = _revs[i].data.fd;
            uint32_t events = _revs[i].events;

            // 将所有异常问题,转化成读写问题
            if (events & EPOLLERR)
                events |= (EPOLLIN | EPOLLOUT);
            if (events & EPOLLHUP)
                events |= (EPOLLIN | EPOLLOUT);

            // 读写事件就绪
            if ((events & EPOLLIN) && Is_Connection_Exists(sock) && _conn_map[sock]->_recver)
                _conn_map[sock]->_recver(_conn_map[sock]);
            if ((events & EPOLLOUT) && Is_Connection_Exists(sock) && _conn_map[sock]->_sender)
                _conn_map[sock]->_sender(_conn_map[sock]);
        }
    }

private:
    uint16_t _port;
    Sock _sock;
    Epoller _epoller;
    unordered_map<int, Connection*> _conn_map;
    struct epoll_event* _revs;
    int _num;
    func_t _service;
};

server.cc

#include "Server.hpp"
#include <memory>

using namespace std;

// 计算任务
bool Cal(const Request& req, Response& resp)
{
    resp._exitcode = OK;
    resp._res = 0;
 
    if (req._op == '/' && req._y == 0)
    {
        resp._exitcode = DIV_ZERO;
        return false;
    }
    if (req._op == '%' && req._y == 0)
    {
        resp._exitcode = MOD_ZERO;
        return false;
    }
 
    switch (req._op)
    {
    case '+':
        resp._res = req._x + req._y;
        break;
    case '-':
        resp._res = req._x - req._y;
        break;
    case '*':
        resp._res = req._x * req._y;
        break;
    case '/':
        resp._res = req._x / req._y;
        break;
    case '%':
        resp._res = req._x % req._y;
        break;
    default:
        resp._exitcode = OP_ERR;
        break;
    }
 
    return true;
}

void Calculate(Connection* conn)
{
    string one_package;
    while (Parse_One_Package(conn->_inbuffer, &one_package))
    {
        string req_str;
        if (!De_Length(one_package, &req_str))
            return;

        // 对请求体Request反序列化,得到一个结构化的请求对象
        Request req;
        if (!req.Deserialiaze(req_str))
            return;
        
        Response resp;
        Cal(req, resp);

        string resp_str;
        resp.Serialize(&resp_str);

        conn->_outbuffer += En_Length(resp_str);
        cout << "构建完整的响应报文: \n" << conn->_outbuffer << endl;
    }

    // 直接发
    if (conn->_sender)
        conn->_sender(conn);
}

static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " port" << "\n\n";
    exit(1);
}

string Transaction(const string &request)
{
    return request;
}

// ./select_server 8080
int main(int argc, char *argv[])
{
    // if(argc != 2)
    //     Usage();

    // unique_ptr<SelectServer> svr(new SelectServer(atoi(argv[1])));

    // std::cout << "test: " << sizeof(fd_set) * 8 << std::endl;
    unique_ptr<TcpServer> svr(new TcpServer(Calculate));

    svr->InitServer();
    svr->Dispatch();

    return 0;
}

八、客户端模块:Client.hpp    client.cc

Client.hpp

#pragma once
 
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
 
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
 
#include "Protocol.hpp"
 
using namespace std;
 
class Client
{
public:
    Client(const std::string& server_ip, const uint16_t& server_port)
        : _sock(-1), _server_ip(server_ip), _server_port(server_port)
    {}
 
    void Init()
    {
        _sock = socket(AF_INET, SOCK_STREAM, 0);
        if (_sock < 0)
        {
            std::cerr << "socket error" << std::endl;
            exit(1);
        }
    }
 
    void Run()
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(_server_port);
        server.sin_addr.s_addr = inet_addr(_server_ip.c_str());
 
        if (connect(_sock, (struct sockaddr*)&server, sizeof(server)) < 0)
        {
            std::cerr << "connect error" << std::endl;
            exit(1);
        }
        else
        {
            string line;
            string inbuf;
            while (true)
            {
                cout << "mycal>>> ";
                getline(cin, line);
                Request req = Parse_Line(line);     // 输入字符串,生成Request对象
 
                string content;
                req.Serialize(&content);                // Request对象序列化
                string send_str = En_Length(content);   // 序列化字符串编码 -> "content_len"\r\n"x op y"\r\n

                send(_sock, send_str.c_str(), send_str.size(), 0);
 
                // 将服务器的返回结果序列化与反序列化
                string package, text;
                if (!Recv_Package(_sock, inbuf, &package))
                    continue;
                if (!De_Length(package, &text))
                    continue;
                
                Response resp;
                resp.Deserialize(text);
                cout << "计算结果: " << endl;
                cout << "exitcode: " << resp._exitcode << ", ";
                cout << "result: " << resp._res << endl << endl;
            }
        }
    }
 
    // 将输入转化为Request结构
    Request Parse_Line(const string& line)
    {
        int status = 0;     // 0:操作符之前    1:遇到操作符    2:操作符之后
        int cnt = line.size();
        string left, right;
        char op;
        int i = 0;
        while (i < cnt)
        {
            switch (status)
            {
            case 0:
                if (!isdigit(line[i]))
                {
                    if (line[i] == ' ')
                    {
                        i++;
                        break;
                    }
                    op = line[i];
                    status = 1;
                }
                else
                {
                    left.push_back(line[i++]);
                }
                break;
            case 1:
                i++;
                if (line[i] == ' ')
                    break;
                status = 2;
                break;
            case 2:
                right.push_back(line[i++]);
                break;
            }
        }
        return Request(stoi(left), stoi(right), op);
    }
 
    ~Client()
    {
        if (_sock >= 0)
            close(_sock);
    }
 
private:
    int _sock;
    string _server_ip;
    uint16_t _server_port;
};

client.cc

#include "Client.hpp"
#include <memory>
 
using namespace std;
 
static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << " local_port\n\n";
    exit(1);
}
 
int main(int argc, char* argv[])
{
    if (argc != 3)
        Usage(argv[0]);
 
    string server_ip = argv[1];
    uint16_t server_port = atoi(argv[2]);
 
    unique_ptr<Client> tcli(new Client(server_ip, server_port));
    tcli->Init();
    tcli->Run();
 
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AllinTome

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值