【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");// 序列化responsestd::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;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/14880.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基金/证券项目如何进行非交易日数据补全(实战)

一些大数据开发的项目&#xff0c;特别是基金/证券公司的项目&#xff0c;都经常会涉及到交易日与非交易日的概念。 如果要让你对一张交易日跑批的主表&#xff0c;怎么去补全非交易日的数据呢&#xff1f; 在遇到这种情况的时候&#xff0c;我们要去怎么处理&#xff1f;来&…

webSocket+Node+Js实现在线聊天(包含所有代码)

这篇文章主要介绍了如何使用 webSocket、Node 和 Js 实现在线聊天功能。 重要亮点 &#x1f4bb; 技术选型&#xff1a;使用 Node.js 搭建服务器&#xff0c;利用 Express 框架和 Socket.io 库实现 WebSocket 通信。 &#x1f4c4; 实现思路&#xff1a;通过建立数组存储聊天…

掌握RESTful API:从入门到精通,全面解析Web开发的基石!

在现代Web开发中&#xff0c;API&#xff08;应用程序编程接口&#xff09;已经成为不同系统之间通信的重要手段。其中&#xff0c;RESTful API是一种基于HTTP协议的设计风格&#xff0c;它简洁、易用且高效。作为一个资深的技术人员&#xff0c;本文将全面详细地介绍RESTful A…

等保建设:打造MySQL数据库审计系统

1、建设目标 在等级保护三级->应用安全->安全审计中强制需要有审计平台(满足对操作系统、数据库、网络设备的审计&#xff0c;在条件不允许的情况下&#xff0c;至少要使用数据库审计) 数据库审计服务符合等级保护三级标准&#xff0c;帮助您满足合规性要求&#xff0c;…

软考之信息系统管理知识点(2)

计算机系统中提高并行性的措施多种多样&#xff0c;就其基本思想而言&#xff0c;可归纳为如下3条途径&#xff1a; &#xff08;1&#xff09;时间重叠。在并行性概念中引入时间因素&#xff0c;即多个处理过程在时间上相互错开&#xff0c;轮流重叠地使用同一套硬件设备的各个…

VsCode CMake调试QT无法查看源码问题处理

遇到的问题 当我们在VsCode使用CMake来调试QT程序时&#xff0c;想F11进入到QT源码时&#xff0c;发现进不去&#xff0c;无法查看源码。 原因 这种情况一般都是安装目录下没有pdb文件导致的。 PDB文件&#xff1a;是一个包含调试信息的数据库&#xff0c;它由编译器和链接器…

用UDP写一个回显服务器和一个字典服务器

回显服务器 操作系统提供了一些网络通信的api&#xff08;socket&#xff09;。 如&#xff1a; 回显服务器&#xff1a;请求是啥&#xff1f;响应就是啥。 一个正常的服务器要做三件事情&#xff1a; 1. 读取请求并解析。 2. 根据请求计算响应。 3. 把响应写回给客户端。…

settimeout和setinterval有什么区别

settimeout和setinterval的区别&#xff1a; 1、触发时间&#xff0c;settimeout是一次性的&#xff0c;它在设定延迟时间之后执行一次函数&#xff0c;而setinterval是重复性的&#xff0c;它会以设定的时间间隔重复执行函数&#xff1b;2、执行次数&#xff0c;settimeout只执…

【ETAS CP AUTOSAR工具链】ARXML文件详解

本篇文章首先对ARXML这种文件格式做了一个概述&#xff0c;叙述了这种标签语言的基本语法&#xff08;如果您用HTML做过网页&#xff0c;那么这种格式您一定不会陌生&#xff09;&#xff0c;然后对ARXML文件都会包含的一些基本信息做了详细的解读&#xff0c;最后基于使用ISOL…

Android:使用Kotlin搭建MVP架构模式

一、简介Android MVP架构模式 MVP全称&#xff1a;Model、View、Presenter&#xff1b; View&#xff1a;负责视图部分展示Model&#xff1a;负责数据的请求、解析、过滤等数据层操作。Presenter&#xff1a;View和Model交互的桥梁。对应MVC中的C&#xff08;controller&#x…

01.爬虫---初识网络爬虫

01.初识网络爬虫 1.什么是网络爬虫2.网络爬虫的类型3.网络爬虫的工作原理4.网络爬虫的应用场景5.网络爬虫的挑战与应对策略6.爬虫的合法性总结 1.什么是网络爬虫 网络爬虫&#xff0c;亦称网络蜘蛛或网络机器人&#xff0c;是一种能够自动地、系统地浏览和收集互联网上信息的程…

路由聚合和VRRP技术

实验拓扑图&#xff1a; 实验需求 1、内网IP地址使用172.16.0.0/16 2、SW1和SW2之间互为备份&#xff1b; 3、VRRP/stp/vlan/eth-trunk均使用&#xff1b; 4、所有pc均通过DHCP获取IP地址&#xff1b; 5、ISP只配置IP地址&#xff1b; 6、所有电脑可以正常访问ISP路由器环…

【学习笔记】Windows GDI绘图(五)图形路径GraphicsPath详解(上)

文章目录 图形路径GraphicsPath填充模式FillMode构造函数GraphicsPath()GraphicsPath(FillMode)GraphicsPath(Point[],Byte[])和GraphicsPath(PointF[], Byte[])GraphicsPath(Point[], Byte[], FillMode)和GraphicsPath(PointF[], Byte[], FillMode)PathPointType 属性FillMode…

在Linux系统上使用Nginx

在Linux系统上使用Nginx&#xff0c;通常涉及安装、配置和启动Nginx服务器等步骤。以下是详细的步骤说明&#xff1a; 一、安装Nginx 首先&#xff0c;你需要安装编译Nginx所需的依赖库和工具。这通常包括GCC编译器、PCRE库&#xff08;用于支持重写模块&#xff09;、zlib库…

分区4K对齐那些事,你想知道的都在这里

在对磁盘进行分区时,有一个很重要的注意事项,就是要将分区对齐,不对齐可能会造成磁盘性能的下降。尤其是固态硬盘SSD,基本上都要求4K对齐。磁盘读写速度慢还找不到原因?可能就是4K对齐的锅。那么分区对齐究竟是怎么回事?为什么要对齐?如何才能对齐?如何检测是否对齐呢?…

[LLM-Agent]万字长文深度解析规划框架:HuggingGPT

HuggingGPT是一个结合了ChatGPT和Hugging Face平台上的各种专家模型&#xff0c;以解决复杂的AI任务&#xff0c;可以认为他是一种结合任务规划和工具调用两种Agent工作流的框架。它的工作流程主要分为以下几个步骤&#xff1a; 任务规划&#xff1a;使用ChatGPT分析用户的请求…

成犬必备!福派斯鲜肉狗粮,亮毛祛泪痕的神奇功效!

对于成犬来说&#xff0c;选择一款合适且高质量的狗粮至关重要。成犬时期的狗狗正处于身体和生理机能逐渐稳定的阶段&#xff0c;因此&#xff0c;需要选择能够满足其日常营养需求、维持健康状态并有助于长寿的狗粮。理想的狗粮应当包含狗狗所需的各种营养物质&#xff0c;如高…

齐护K210系列教程(三十一)_视觉小车

视觉小车 齐护编程小车端程序动作说明联系我们 在经常做小车任务项目时会用的K210的视觉与巡线或其它动作结合&#xff0c;这就关系到要将K210的识别结果传送给小车的主控制器&#xff0c;K210为辅助传感器&#xff08;视觉采集&#xff09;。 这节课我们用K210识别图像&#x…

Java 解决 古典问题

1 问题 编写一个Java程序&#xff0c;解决以下问题&#xff1a; 2 方法 再导入java.util包下的Scanner类&#xff0c;构建Scanner对象&#xff0c;以便输入。通过对问题的分析&#xff0c;我们可以得到&#xff0c;当位数为1时&#xff0c;其返回值为1&#xff1b;当位数为2时&…

IDEA 将多个微服务Springboot项目Application启动类添加到services标签,统一启动、关闭服务

IDEA 将多个微服务Springboot项目Application启动类添加到services标签&#xff0c;统一启动、关闭服务 首先在Views > Tool Windows > Services 添加services窗口 点击services窗口&#xff0c;首次需要添加配置类型&#xff0c;我们选择Springboot 默认按照运行状态分…