【Linux】使用自定义协议实现网络版计算器

目录

实现思路

主要模块

计算业务类

类成员

方法

运算处理

错误处理

守护进程类

代码

客户端程序

服务端类

成员变量

构造函数

静态成员函数

成员函数

服务端程序

主函数 (main)

请求处理函数 (HandlerRequest)

报头类

常量定义

编码和解码函数

请求和响应类

工厂类

套接字类

主要类和接口

主要函数/方法


实现思路

  1. 客户端负责向服务器发送计算请求,使用随机函数生成两个操作数和一个操作符,先生成一个工厂类对象,使用工厂对象生成请求对象,随后将请求对象序列化,将序列化后的字符串添加报头打包发送给服务器

  2. 服务端负责向服务器发送结果响应,接受客户端发送来的请求,进行解包,随后将解包得到的字符串反序列化为请求对象,调用服务器响应函数来生成结果响应对象,将响应对象进行序列化且添加报头打包发回给客户端。

主要模块

计算业务类

这个类名为 Calculate,是一个用于执行基础数学运算的类。以下是对这个类的详细介绍:

类成员

  1. 工厂对象factory

    • 类型:Factory

    • 作用:用于构建 Response 对象,创建并返回一个 Response 类型的智能指针。

  2. 构造函数与析构函数

    • Calculate():构造函数,用于初始化 Calculate 对象。在这个例子中,构造函数是空的,因为没有需要特别初始化的成员变量。

    • ~Calculate():析构函数,用于清理 Calculate 对象在销毁时可能需要的任何资源。在这个例子中,析构函数也是空的。

方法

  • **Cal(std::shared_ptr<Request> req)**:

    • 参数:接受一个指向 Request 对象的智能指针,这个 Request 对象应该包含了需要执行的运算类型和操作数。

    • 返回值:返回一个指向 Response 对象的智能指针,这个 Response 对象包含了运算的结果和状态码。

    • 功能:根据 Request 对象中包含的运算类型和操作数执行相应的数学运算,并将结果和状态码设置在返回的 Response 对象中。

运算处理

  • 加法(+):如果请求中的运算符是加号,则将请求中的两个操作数相加,并将结果设置在响应中。

  • 减法(-):如果请求中的运算符是减号,则将请求中的第一个操作数减去第二个操作数,并将结果设置在响应中。

  • 乘法(*):如果请求中的运算符是乘号,则将请求中的两个操作数相乘,并将结果设置在响应中。

  • 除法(/):如果请求中的运算符是除号,则需要检查第二个操作数(除数)是否为零。如果为零,则将响应的状态码设置为 DivZeroErr。否则,执行除法运算并将结果设置在响应中。

  • 取模(%):如果请求中的运算符是取模符号,同样需要检查第二个操作数(除数/模数)是否为零。如果为零,则将响应的状态码设置为 ModZeroErr。否则,执行取模运算并将结果设置在响应中。

  • 未知运算符:如果请求中的运算符不是上述任何一种,则将响应的状态码设置为 UnKnowOper

错误处理

  • 通过设置不同的状态码来表示不同的错误情况,如 DivZeroErr 表示除数为零的错误,ModZeroErr 表示模数为零的错误,UnKnowOper 表示未知运算符的错误。这些状态码可以帮助调用者了解运算过程中可能发生的错误情况。

总的来说,Calculate 类是一个封装了基础数学运算功能的类,通过接收一个包含运算类型和操作数的请求对象,执行相应的运算,并返回一个包含运算结果和状态码的响应对象。

#pragma once

#include <iostream>
#include <memory>
#include "Protocol.hpp"

enum
{
  Success = 0,
  DivZeroErr,
  ModZeroErr,
  UnKnowOper
};

class Calculate
{
public:
  Calculate() {}
  std::shared_ptr<Response> Cal(std::shared_ptr<Request> req)
  {
    std::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(UnKnowOper);
      break;
    }

    return resp;
  }
  ~Calculate() {}

private:
  Factory factory;
};

守护进程类

该类的主要目的是将一个进程转化为守护进程(Daemon Process)。守护进程是在后台运行且不受前台用户交互影响的进程。它们通常在系统启动时开始运行,并持续在后台执行任务,直到系统关闭。

  1. 函数定义:

void Daemon(bool ischdir, bool isclose)

这个函数接受两个布尔参数:ischdirisclose。这两个参数控制是否在成为守护进程后更改当前工作目录(CWD)以及是否关闭标准输入、输出和错误流。

  1. 信号处理:

signal(SIGCHLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);

这两行代码设置了两个信号的处理方式:SIGCHLD(子进程结束时发送给父进程的信号)和SIGPIPE(当进程向某个已接收到EOF的socket写数据时发送的信号)都被设置为忽略。这是为了避免这些信号对守护进程的干扰。

  1. 创建子进程:

if (fork() > 0)
  exit(0); // 将父进程退出

通过fork()系统调用创建一个子进程。fork()返回两次:在父进程中返回子进程的PID,在子进程中返回0。如果返回值大于0,说明当前是父进程,于是父进程退出,留下子进程继续运行。这是创建守护进程的第一步,因为守护进程需要是孤儿进程(即其父进程已经退出)。

  1. 创建新的会话:

setsid();

setsid()`系统调用用于创建一个新的会话,并使调用者进程成为会话的领导。这是守护进程创建的关键步骤,因为它使进程摆脱控制终端的关联,从而使其能够在后台独立运行。

  1. 更改当前工作目录:

if (ischdir)
  chdir(root);

如果ischdirtrue,则将当前工作目录更改为根目录(/)。这是为了确保守护进程不会在原始启动目录中保留任何文件描述符,从而避免可能的文件系统挂载问题。

  1. 处理标准输入、输出和错误流: 这部分代码根据isclose参数的值来决定如何处理标准输入(stdin)、标准输出(stdout)和标准错误(stderr)。

    • 如果isclosetrue,则直接关闭这三个文件描述符:

    if (isclose)
    {
      close(0);
      close(1);
      close(2);
    }
    
    • 如果isclosefalse,则将这三个文件描述符重定向到/dev/null

    else
    {
      int fd = open(dev_null, O_RDWR);
      if (fd > 0)
      {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
        close(fd);
      }
    }
    

    这里首先打开/dev/null设备文件,并获取一个文件描述符。然后,使用dup2()系统调用将这个文件描述符复制到stdin、stdout和stderr的文件描述符上。最后,关闭原始打开的/dev/null文件描述符。这样做的好处是,任何写入stdout或stderr的输出,以及从stdin的读取,都会被丢弃,从而确保守护进程不会在控制台上产生任何输出或期待任何输入。

    代码

#pragma once

#include <cstdlib>
#include <fcntl.h>
#include <iostream>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

const char *root = "/";
const char *dev_null = "/dev/null";

void Daemon(bool ischdir, bool isclose) // 守护进程一定要是孤儿进程
{
  signal(SIGCHLD, SIG_IGN);
  signal(SIGPIPE, SIG_IGN);

  if (fork() > 0)
    exit(0); // 将父进程退出

  setsid(); // 设置让自己成为一个新的会话, 后面的代码其实是子进程在走

  if (ischdir)
    chdir(root); // 每一个进程都有自己的CWD,是否将当前进程的CWD更改成为 / 根目录

  if (isclose)
  {
    close(0);
    close(1);
    close(2);
  }
  else
  {
    // 这里一般建议就用这种
    int fd = open(dev_null, O_RDWR);
    if (fd > 0)
    {
      dup2(fd, 0);
      dup2(fd, 1);
      dup2(fd, 2);
      close(fd);
    }
  }
}

客户端程序

这段代码是一个简单的客户端程序,它使用套接字(Socket)与服务器进行通信,发送计算请求并接收服务器的响应。

  1. 命令行参数处理

    • 程序首先检查命令行参数的数量。如果参数数量不正确(不是3个,包括程序名、服务器IP和服务器端口),则打印用法信息并退出。

    • 如果参数数量正确,它将服务器IP和端口从命令行参数中提取出来,并转换为适当的类型。

  2. 套接字连接

    • 创建一个TcpSocket对象(这里假设TcpSocketSocket的一个子类,虽然代码中没有直接显示这一点)。

    • 调用BuildConnectSocketMethod方法尝试连接到指定的服务器IP和端口。如果连接失败,则打印错误消息并退出程序。如果连接成功,则打印成功消息。

  3. 请求构建与发送

    • 使用Factory模式(这里假设Factory是一个用于构建请求和响应对象的工厂类)。创建一个Factory实例。

    • 进入一个无限循环,在每次迭代中:
      • 随机生成两个整数(x和y)和一个运算符(oper)。

      • 使用Factory构建一个Request对象,该对象包含这些随机生成的值。

      • Request对象序列化为一个字符串。

      • 添加一个自描述报头到序列化后的请求字符串(这里假设Encode函数用于添加报头)。

      • 通过套接字发送编码后的请求字符串到服务器。

  4. 接收和解析响应

    • 在发送请求后,程序进入一个内部循环,尝试接收服务器的响应。

    • 调用Recv方法从套接字读取响应数据。如果读取失败,则打印错误消息并继续下一次外部循环的迭代。

    • 如果成功读取响应数据,则尝试对响应进行解码(这里假设Decode函数用于移除报头并返回原始响应数据)。

    • 使用Factory构建一个Response对象,并对解码后的响应数据进行反序列化。

    • 打印出反序列化后的响应数据,包括计算结果和结果代码。

  5. 循环与退出

    • 在每次外部循环的末尾,程序会暂停一段时间(通过sleep函数),然后再次开始新的迭代。

    • 注意,由于代码中没有提供退出循环的条件,因此这个客户端程序将会无限循环地发送请求并接收响应,除非被外部终止。

  6. 清理与退出

    • 在代码的最后(虽然在这个特定的示例中由于无限循环而永远不会达到),程序会关闭套接字并打印一条消息。

    • 然后程序返回0,表示正常退出(虽然在正常情况下这部分代码不会被执行)。

#include "Protocol.hpp"
#include "Socket.hpp"
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <string>
#include <unistd.h>

int main(int argc, char *argv[])
{
  if (argc != 3)
  {
    std::cout << "Usage : " << argv[0] << " serverip serverport" << std::endl;
    return 0;
  }

  std::string serverip = argv[1];
  uint16_t serverport = std::stoi(argv[2]);

  Socket *conn = new TcpSocket(); // 构成多态
  if (!conn->BuildConnectSocketMethod(serverip, serverport))
  {
    std::cerr << "connect " << serverip << ":" << serverport << " failed" << std::endl;
    return 1;
  }
  std::cout << "connect " << serverip << ":" << serverport << " success" << std::endl;

  std::unique_ptr<Factory> factory = std::make_unique<Factory>();

  srand(time(nullptr) ^ getpid());
  const std::string opers = "+-*/%^=&";
  while (true)
  {
    sleep(2);
    // 1. 构建一个请求,遵守协议
    int x = rand() % 100; //[0, 99]
    usleep(rand() % 7777);
    int y = rand() % 100; //[0,99]
    char oper = opers[rand() % opers.size()];
    std::shared_ptr<Request> req = factory->BuildRequest(x, y, oper);
    std::cout << "构建请求" << std::endl;

    // 2. 对请求进行序列化
    std::string requeststr;
    req->Serialize(&requeststr);
    std::cout << "构建序列化" << std::endl;
    std::cout << requeststr << std::endl;

    std::string testreq = std::to_string( req->GetX());
    testreq +=" ";
    testreq += req->GetOper();
    testreq += " ";
    testreq += std::to_string(req->GetY());
    testreq += " ";
    testreq += "= ";

    // 3. 添加自描述报头
    requeststr = Encode(requeststr);
    std::cout << "添加报头成功" << std::endl;
    std::cout << requeststr << std::endl;

    // 4. 发送请求
    conn->Send(requeststr);
    std::cout << "发送请求成功" << std::endl;

    std::string responsestr;
    while (true)
    {
      sleep(2);
      std::cout << "开始接受服务器响应\n";
      // 5. 读取响应
      if(!conn->Recv(&responsestr, 4096))
      {
        std::cout << "读取响应失败" << std::endl;
        break;
      }
      // 6. 报文进行解析
      std::string response;
      if (!Decode(responsestr, &response))
      {
        std::cout << "报文解析失败" << std::endl;

        continue;
      }
      // 7.response "result code"
      auto resp = factory->BuildResponse();
      resp->Deserialize(response);
      std::cout << "响应反序列化成功" << std::endl;

      // 8. 得到了计算结果,而且是一个结构化的数据
            std::cout << testreq << resp->GetResult() << "[" << resp->GetCode() << "]" << std::endl;
      std::cout << "得到结构化数据" << std::endl;
      break;
    }
    sleep(1);
  }

  conn->CloseSocket();
  std::cout << "关闭套接字" << std::endl;

  return 0;
}

服务端类

TcpServer 类是一个简单的多线程 TCP 服务器实现。它监听一个特定的端口,接受客户端的连接,为每个连接创建一个新线程来处理请求,并使用提供的处理函数来响应客户端发送的数据。以下是对这个类的详细介绍:

成员变量

  • _port: 服务器监听的端口号。

  • _listensocket: 一个 Socket 类型的指针,用于监听和接受客户端的连接。

  • _handler_request: 一个函数对象(func_t 类型),用于处理从客户端接收到的数据并生成响应。

构造函数

TcpServer(uint16_t port, func_t handler_request): 构造函数接受一个端口号和一个处理函数作为参数。它创建一个新的 TcpSocket 对象来监听该端口,并使用提供的处理函数来初始化 _handler_request

注意:构造函数中使用了 BuildListenSocketMethod 方法来建立监听套接字,但代码中没有显示 backlog 的定义。通常,backlog 表示系统可以排队等待处理的连接数。

静态成员函数

static void *ThreadRun(void *args): 这是一个静态成员函数,用作新线程的入口点。它接受一个 void* 类型的参数(实际上是 ThreadData 类型的指针),然后进入一个无限循环,在该循环中它尝试从客户端接收数据,并使用 _handler_request 函数来处理这些数据。如果处理成功,它会将响应发送回客户端。如果接收或处理数据时出现错误,或者 _handler_request 返回 false,则线程会退出循环,关闭套接字,并清理资源。

成员函数

  • void Loop(): 这个函数使服务器进入监听模式,等待并接受客户端的连接。对于每个接受的连接,它都会创建一个新的线程来处理该连接。

  • ~TcpServer(): 析构函数,负责清理资源,特别是删除 _listensocket

#pragma once

#include "Log.hpp"
#include "Socket.hpp"
#include <functional>
#include <iostream>
#include <pthread.h>

using func_t = std::function<std::string(std::string &, bool *error_code)>;

class TcpServer;
class ThreadData
{
public:
  ThreadData(TcpServer *tcp_this, Socket *sockp) : _this(tcp_this), _sockp(sockp)
  {
  }

public:
  TcpServer *_this;
  Socket *_sockp;
};

class TcpServer
{
public:
  TcpServer(uint16_t port, func_t handler_request)
      : _port(port),
        _listensocket(new TcpSocket()),
        _handler_request(handler_request)
  {
    _listensocket->BuildListenSocketMethod(_port, backlog);
  }

  static void *ThreadRun(void *args) // 要传递给线程的任务
  {
    std::cout << "HandlerRequest\n";
    pthread_detach(pthread_self());
    ThreadData *td = static_cast<ThreadData *>(args);

    std::string inbufferstream;
    while (true)
    {
      bool ok = true;
      while (true)
      {
        sleep(2);
        // 接受报文
        if (!td->_sockp->Recv(&inbufferstream, 1024))
        {
          std::cout << "接受报文失败" << std::endl;
          break;
        }
        // 2. 报文处理
        std::cout << "报文处理" << std::endl;
        std::string send_string = td->_this->_handler_request(inbufferstream, &ok);

        // 3. 发送数据
        if (ok)
        {
          if (!send_string.empty())
          {
            td->_sockp->Send(send_string);
          }
        }
        else
        {
          break;
        }
      }
      td->_sockp->CloseSocket();
      delete td->_sockp;
      delete td;
      return nullptr;
    }
  }

  void Loop()
  {
    std::cout << "执行Loop" << std::endl;
    while (true)
    {
      std::string peerip;
      uint16_t peerport;
      Socket *newsock = _listensocket->AcceptConnection(&peerip, &peerport);

      if (newsock == nullptr)
      {
        std::cout << "创建newsock失败,在tcpserver。hpp" << std::endl;
        continue;
      }
      lg.LogMessage(Info, "获取一个新连接, sockfd:%d peerip:%s peerport:%d\n", newsock->GetSockFd(), peerip, peerport);

      pthread_t tid;
      ThreadData *td = new ThreadData(this, newsock);
      pthread_create(&tid, nullptr, ThreadRun, td);
    }
  }

  ~TcpServer()
  {
    delete _listensocket;
  }

public:
  func_t _handler_request;

private:
  int _port;
  Socket *_listensocket;
};

服务端程序

程序使用了前面提到的TcpServer类来创建一个TCP服务器,该服务器监听指定的端口,接受客户端连接,并处理客户端发送的请求。

主函数 (main)

  1. 参数检查: 程序首先检查命令行参数的数量。如果参数数量不正确(应该是程序名和端口号两个参数),则打印用法信息并退出。

  2. 端口转换: 如果参数数量正确,程序将第二个参数(命令行传入的端口号)从字符串转换为uint16_t类型的整数,并存储在localport变量中。

  3. 服务器创建: 使用std::unique_ptr<TcpServer>来管理TcpServer对象的生命周期。这样做的好处是,当unique_ptr超出作用域时,它会自动删除其所指向的对象,从而防止内存泄漏。TcpServer的构造函数接受端口号和请求处理函数(HandlerRequest)作为参数。

  4. 事件循环: 调用svr->Loop()来启动服务器的事件循环。在这个循环中,服务器将监听指定的端口,接受客户端的连接,并为每个连接创建一个新线程来处理请求。

请求处理函数 (HandlerRequest)

这个函数是服务器处理客户端请求的核心逻辑。每当服务器接受到一个新的连接并接收到数据时,它都会调用这个函数来处理数据。

  1. 初始化: 函数首先设置error_codetrue,表示当前没有错误。然后,它创建一个Calculate对象和一个Factory对象来构建响应。

  2. 解码和反序列化: 函数使用一个名为Decode的函数(未在代码中给出)来从输入的字节流(inbufferstream)中解码出消息。然后,它尝试使用req->Deserialize方法来反序列化消息。如果反序列化失败,函数将记录错误,设置error_codefalse,并返回一个空字符串。

  3. 业务处理: 如果反序列化成功,函数将使用Calculate对象的Cal方法来处理请求,并得到一个响应对象(resp)。

  4. 序列化和编码: 接下来,函数使用resp->Serialize方法来序列化响应对象,将其转换为字符串。然后,它使用Encode函数来对序列化后的响应进行编码,以便发送回客户端。

  5. 发送响应: 最后,函数将编码后的响应字符串添加到total_resp_string中,并返回这个字符串。在TcpServer类的ThreadRun方法中,这个返回的字符串将被发送给客户端。

#include "Calculate.hpp"
#include "Daemon.hpp"
#include "Protocol.hpp"
#include "TcpServer.hpp"
#include <iostream>
#include <memory>
#include <unistd.h>

std::string HandlerRequest(std::string &inbufferstream, bool *error_code)
{
  std::cout<<"在执行HandlerRequest\n";
  *error_code = true;
  // 创建计算器对象
  Calculate calculte;
  lg.LogMessage(Debug, "文件:TcpServerMain,创建计算器对象成功\n");

  // 构建响应对象
  std::unique_ptr<Factory> factory = std::make_unique<Factory>();
  auto req = factory->BuildRequest();
  lg.LogMessage(Debug, "文件:TcpServerMain,创建响应对象成功\n");

  // 分析字节流,看是否有一个完整的报文
  std::string total_resp_string;
  std::string message;
  while (Decode(inbufferstream, &message))
  {
    lg.LogMessage(Debug, "文件:TcpServerMain,添加报头成功\n");
    std::cout << message << "---- messge" << std::endl;

    if (!req->Deserialize(message))
    {
      lg.LogMessage(Debug, "文件:TcpServerMain,反序列化成功\n");

      std::cout << "Deserialize error" << std::endl;
      *error_code = false;
      return std::string();
    }
    std::cout << "Deserialize success" << std::endl;
    // 业务处理了
    auto resp = calculte.Cal(req);
    lg.LogMessage(Debug, "文件:TcpServerMain,业务处理成功\n");

    // 序列化response
    std::string send_string;
    resp->Serialize(&send_string); // "result code"
    lg.LogMessage(Debug, "文件:TcpServerMain,序列化回应成功\n");

    // 构建完成的字符串级别的响应报文
    send_string = Encode(send_string);
    lg.LogMessage(Debug, "文件:TcpServerMain,构建回应报头成功\n");

    // 发送
    total_resp_string += send_string;
  }
  return total_resp_string;
}

int main(int argc, char *argv[])
{
  if (argc != 2)
  {
    std::cout << "Usage : " << argv[0] << " port" << std::endl;
    return 0;
  }
  uint16_t localport = std::stoi(argv[1]);

  std::unique_ptr<TcpServer> svr(new TcpServer(localport, HandlerRequest));

  svr->Loop();

  return 0;
}

报头类

这段代码定义了几个关键类和函数,它们共同构成了一个简单的网络通信框架。下面是对这些类和函数的详细介绍:

常量定义

  • ProtSepLineBreakSep:分别定义了协议分隔符和行分隔符。在这个例子中,协议分隔符是一个空格(" "),而行分隔符是一个换行符("\n")。

编码和解码函数

  • Encode(const std::string &message):这个函数接受一个字符串消息作为输入,并在其前面添加一个表示消息长度的数字(以换行符分隔),然后在消息末尾也添加一个换行符。这个函数用于将消息打包成一种特定格式,便于网络传输。

  • Decode(std::string &package, std::string *message):这个函数用于解码由Encode函数编码的消息。它首先找到第一个换行符来确定消息长度的位置,然后提取出消息长度,并根据这个长度来提取出实际的消息内容。

请求和响应类

  • Request 类:表示一个网络请求。它包含三个私有成员变量(_data_x_data_y_oper),分别用于存储两个整数值和一个字符值。这个类提供了序列化和反序列化方法(SerializeDeserialize),用于将请求对象转换为字符串格式或从字符串格式恢复请求对象。此外,还提供了获取这些私有成员变量的方法(GetXGetYGetOper)。

  • Response 类:表示一个网络响应。它包含两个私有成员变量(_result_code),分别用于存储一个整数值和一个表示响应代码的整数值。与Request类类似,它也提供了序列化和反序列化方法,以及设置和获取这些私有成员变量的方法。

工厂类

  • Factory 类:这是一个工厂类,用于创建RequestResponse对象。它提供了几个BuildRequestBuildResponse方法,这些方法可以创建具有不同初始化参数的请求和响应对象。使用工厂模式可以更容易地管理和扩展对象的创建过程。

#pragma once

#include <iostream>
#include <jsoncpp/json/json.h>
#include <memory>

const std::string ProtSep = " ";
const std::string LineBreakSep = "\n";

std::string Encode(const std::string &message)
{
  std::string len = std::to_string(message.size());
  std::string package = len + LineBreakSep + message + LineBreakSep;
  std::cout<<"package  构建成功\n";
  return package;
}

bool Decode(std::string &package, std::string *message)
{
  auto pos = package.find(LineBreakSep);
  if (pos == std::string::npos)
    return false;

  std::string lens = package.substr(0, pos);
  int messagelen = std::stoi(lens);

  int total = lens.size() + messagelen + 2 * LineBreakSep.size();
  if (package.size() < total)
    return false;

  *message = package.substr(pos + LineBreakSep.size(), messagelen);
  package.erase(0, total);
  return true;
}

class Request
{
public:
  Request()
      : _data_x(0),
        _data_y(0),
        _oper(0)
  {
  }

  Request(int x, int y, char oper)
      : _data_x(x),
        _data_y(y),
        _oper(oper)
  {
  }

  bool Serialize(std::string *out)
  {
    Json::Value root;
    root["datax"] = _data_x;
    root["datay"] = _data_y;
    root["oper"] = _oper;

    Json::FastWriter fastwriter;
    *out = fastwriter.write(root);
    return true;
  }

  bool Deserialize(std::string &in)
  {
    Json::Value root;
    Json::Reader reader;
    bool res = reader.parse(in, root);
    if (res)
    {
      _data_x = root["datax"].asInt();
      _data_y = root["datay"].asInt();
      _oper = root["oper"].asInt();
    }

    return res;
  }
  int GetX()
  {
    return _data_x;
  }
  int GetY()
  {
    return _data_y;
  }
  char GetOper()
  {
    return _oper;
  }

private:
  int _data_x;
  int _data_y;
  char _oper;
};

class Response
{
public:
  Response() : _result(0), _code(0)
  {
  }

  Response(int result, int code) : _result(result), _code(code)
  {
  }

  bool Serialize(std::string *out)
  {
    Json::Value root;
    root["Code"] = _code;
    root["Result"] = _result;

    Json::FastWriter writer;
    *out = writer.write(root);
    return true;
  }

  bool Deserialize(std::string &in)
  {
    Json::Value root;
    Json::Reader reader;

    bool res = reader.parse(in, root);
    if (res)
    {
      _code = root["Code"].asInt();
      _result = root["Result"].asInt();
    }

    return res;
  }

  void SetResult(int res)
  {
    _result = res;
  }

  void SetCode(int code)
  {
    _code = code;
  }

  int GetCode()
  {
    return _code;
  }

  int GetResult()
  {
    return _result;
  }

private:
  int _code;
  int _result;
};

class Factory
{
public:
  std::shared_ptr<Request> BuildRequest()
  {
    std::shared_ptr<Request> req = std::make_shared<Request>();
    return req;
  }
  std::shared_ptr<Request> BuildRequest(int x, int y, char op)
  {
    std::shared_ptr<Request> req = std::make_shared<Request>(x, y, op);
    return req;
  }

  std::shared_ptr<Response> BuildResponse()
  {
    std::shared_ptr<Response> resp = std::make_shared<Response>();
    return resp;
  }
  std::shared_ptr<Response> BuildResponse(int result, int code)
  {
    std::shared_ptr<Response> req = std::make_shared<Response>(result, code);
    return req;
  }
};

套接字类

这是一个C++网络编程的代码片段,定义了一个基于TCP协议的Socket类及其子类。以下是对这段代码的详细介绍:

主要类和接口

  1. Socket 类

    • 这是一个抽象基类,定义了网络编程中常用的一些操作接口,如创建Socket、绑定、监听、接受连接、连接服务器、获取和设置Socket文件描述符,以及关闭Socket等。

    • 它还提供了两个构建方法:BuildListenSocketMethodBuildConnectSocketMethod,分别用于构建监听Socket和连接Socket。

    • 此类中的方法大多是纯虚函数,需要在子类中实现。

  2. TcpSocket 类

    • 这是Socket类的子类,专门用于TCP通信。

    • 它实现了Socket类中定义的所有纯虚函数,提供了具体的TCP Socket操作实现。

    • 此类中使用了一个私有成员变量_sockfd来存储Socket的文件描述符。

主要函数/方法

  1. CreateSocketOrDie

    • 创建一个新的Socket,并设置其文件描述符到_sockfd

    • 如果创建失败,则记录错误信息并退出程序。

  2. BindSocketOrDie

    • 将Socket绑定到指定的端口上。

    • 如果绑定失败,则记录错误信息并退出程序。

  3. ListenSocketOrDie

    • 将Socket设置为监听模式,等待客户端的连接。

    • 如果监听设置失败,则记录错误信息并退出程序。

  4. AcceptConnection

    • 接受一个客户端的连接请求,并返回一个新的Socket对象,该对象与客户端进行通信。

    • 同时返回客户端的IP地址和端口号。

  5. ConnectServer

    • 尝试连接到指定的服务器IP和端口。

    • 如果连接失败,返回false;否则返回true。

  6. GetSockFd 和 SetSockFd

    • 获取或设置Socket的文件描述符。

  7. CloseSocket

    • 关闭Socket。

  8. Recv 和 Send

    • 接收和发送数据。

    • Recv方法接收数据并将其添加到提供的字符串缓冲区中。

    • Send方法发送提供的字符串数据。

#pragma once

#include "Log.hpp"
#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include <netinet/in.h>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

const static int DefaultSockfd = -1;
const int backlog = 5;

enum
{
  SocketError = 1,
  BindError,
  ListenError,
  AcceptError
};

class Socket
{
public:
  virtual ~Socket() {}
  virtual void CreateSocketOrDie() = 0;
  virtual void BindSocketOrDie(uint16_t port) = 0;
  virtual void ListenSocketOrDie(int backlog) = 0;
  virtual Socket *AcceptConnection(std::string *peerip, uint16_t *peerport) = 0;
  virtual bool ConnectServer(std::string &serverip, uint16_t serverport) = 0;
  virtual int GetSockFd() = 0;
  virtual void SetSockFd(int sockfd) = 0;
  virtual void CloseSocket() = 0;
  virtual bool Recv(std::string *buffer, int size) = 0;
  virtual void Send(std::string &send_str) = 0;

public:
  void BuildListenSocketMethod(uint16_t port, int backlog)
  {
    std::cout << "调用: BuildListenSocketMethod\n";

    CreateSocketOrDie();
    BindSocketOrDie(port);
    ListenSocketOrDie(backlog);
  }

  bool BuildConnectSocketMethod(std::string &serverip, uint16_t serverport)
  {
    std::cout << "调用: BuildConnectSocketMethod\n";

    CreateSocketOrDie();
    return ConnectServer(serverip, serverport);
  }

  void BuildNormalSocketMethod(int sockfd)
  {
    std::cout << "调用: BuildNormalSocketMethod\n";

    SetSockFd(sockfd);
  }
};

class TcpSocket : public Socket
{
public:
  TcpSocket(int sockfd = DefaultSockfd)
      : _sockfd(sockfd)
  {
  }

  ~TcpSocket()
  {
  }

  void CreateSocketOrDie() override
  {
    std::cout << "调用:CreateSocketOrDie\n";
    _sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (_sockfd < 0)
    {
      lg.LogMessage(Fatal, "CreateSocketOrDie Error! Error code:%d Error information:%s \n", errno, strerror(errno));
      exit(SocketError);
    }

    lg.LogMessage(Info, "CreateSocketOrDie Sucess! Socket:%d\n", _sockfd);
  }

  void BindSocketOrDie(uint16_t port) override
  {
    std::cout << "调用: BindSocketOrDie\n";

    sockaddr_in local;
    memset(&local, 0, sizeof(local));
    local.sin_addr.s_addr = INADDR_ANY;
    local.sin_port = htons(port);
    local.sin_family = AF_INET;

    int n = bind(_sockfd, (sockaddr *)&local, sizeof(local));
    if (n < 0)
    {
      lg.LogMessage(Fatal, "BindSocketOrDie Error! Error code:%d Error information:%s \n", errno, strerror(errno));
      exit(BindError);
    }

    lg.LogMessage(Info, "BindSocketOrDie Sucess!\n");
  }

  void ListenSocketOrDie(int backlog) override
  {
    std::cout << "调用: ListenSocketOrDie\n";

    int n = listen(_sockfd, backlog);
    if (n < 0)
    {
      lg.LogMessage(Fatal, "ListenSocketOrDie Error! Error code:%d Error information:%s \n", errno, strerror(errno));
      exit(ListenError);
    }
    lg.LogMessage(Info, "ListenSocketOrDie Sucess!\n");
  }

  Socket *AcceptConnection(std::string *peerip, uint16_t *peerport) override
  {
    std::cout << "调用: AcceptConnection\n";

    sockaddr_in peer;
    socklen_t len = sizeof(peer);
    int newSocket = accept(_sockfd, (sockaddr *)&peer, &len);
    if (newSocket < 0)
    {
      lg.LogMessage(Fatal, "AcceptConnection Error! Error code:%d Error information:%s \n", errno, strerror(errno));
      return nullptr;
    }
    *peerip = inet_ntoa(peer.sin_addr);
    *peerport = ntohs(peer.sin_port);
    lg.LogMessage(Info, "AcceptConnection Sucess!\n");
    Socket *s = new TcpSocket(newSocket);
    return s;
  }

  bool ConnectServer(std::string &serverip, uint16_t serverport) override
  {
    std::cout << "调用: ConnectServer\n";

    sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_addr.s_addr = inet_addr(serverip.c_str());
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);

    int n = connect(_sockfd, (sockaddr *)&server, sizeof(server));
    if (n < 0)
    {
      lg.LogMessage(Fatal, "ConnectServer Error! Error code:%d Error information:%s \n", errno, strerror(errno));
      return false;
    }

    lg.LogMessage(Info, "ConnectServer Sucess!\n");
    return true;
  }

  int GetSockFd() override
  {
    std::cout << "调用: GetSockFd\n";

    return _sockfd;
  }

  void SetSockFd(int sockfd) override
  {
    std::cout << "调用: GetSockFd\n";

    _sockfd = sockfd;
  }

  void CloseSocket() override
  {
    std::cout << "调用: CloseSocket\n";

    if (_sockfd > DefaultSockfd)
      close(_sockfd);
  }

  bool Recv(std::string *buffer, int size)override
  {
    std::cout << "调用: Recv\n";

    char inbuffer[size];
    ssize_t n = recv(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0);
    if (n > 0)
    {
      inbuffer[n] = 0;
      *buffer += inbuffer;
      return true;
    }
    return false;
  }
  void Send(std::string &send_str) override
  {
    std::cout << "调用: Send\n";

    send(_sockfd, send_str.c_str(), send_str.size(), 0);
  }

private:
  int _sockfd;
};


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值