目录
实现思路
主要模块
计算业务类
类成员
方法
运算处理
错误处理
守护进程类
代码
客户端程序
服务端类
成员变量
构造函数
静态成员函数
成员函数
服务端程序
主函数 (main)
请求处理函数 (HandlerRequest)
报头类
常量定义
编码和解码函数
请求和响应类
工厂类
套接字类
主要类和接口
主要函数/方法
实现思路
-
客户端负责向服务器发送计算请求,使用随机函数生成两个操作数和一个操作符,先生成一个工厂类对象,使用工厂对象生成请求对象,随后将请求对象序列化,将序列化后的字符串添加报头打包发送给服务器
-
服务端负责向服务器发送结果响应,接受客户端发送来的请求,进行解包,随后将解包得到的字符串反序列化为请求对象,调用服务器响应函数来生成结果响应对象,将响应对象进行序列化且添加报头打包发回给客户端。
主要模块
计算业务类
这个类名为 Calculate
,是一个用于执行基础数学运算的类。以下是对这个类的详细介绍:
类成员
-
工厂对象factory:
-
类型:
Factory
-
作用:用于构建
Response
对象,创建并返回一个Response
类型的智能指针。
-
-
构造函数与析构函数:
-
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)。守护进程是在后台运行且不受前台用户交互影响的进程。它们通常在系统启动时开始运行,并持续在后台执行任务,直到系统关闭。
-
函数定义:
void Daemon(bool ischdir, bool isclose)
这个函数接受两个布尔参数:ischdir
和 isclose
。这两个参数控制是否在成为守护进程后更改当前工作目录(CWD)以及是否关闭标准输入、输出和错误流。
-
信号处理:
signal(SIGCHLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
这两行代码设置了两个信号的处理方式:SIGCHLD
(子进程结束时发送给父进程的信号)和SIGPIPE
(当进程向某个已接收到EOF的socket写数据时发送的信号)都被设置为忽略。这是为了避免这些信号对守护进程的干扰。
-
创建子进程:
if (fork() > 0)exit(0); // 将父进程退出
通过fork()
系统调用创建一个子进程。fork()
返回两次:在父进程中返回子进程的PID,在子进程中返回0。如果返回值大于0,说明当前是父进程,于是父进程退出,留下子进程继续运行。这是创建守护进程的第一步,因为守护进程需要是孤儿进程(即其父进程已经退出)。
-
创建新的会话:
setsid();
setsid()`系统调用用于创建一个新的会话,并使调用者进程成为会话的领导。这是守护进程创建的关键步骤,因为它使进程摆脱控制终端的关联,从而使其能够在后台独立运行。
-
更改当前工作目录:
if (ischdir)chdir(root);
如果ischdir
为true
,则将当前工作目录更改为根目录(/
)。这是为了确保守护进程不会在原始启动目录中保留任何文件描述符,从而避免可能的文件系统挂载问题。
-
处理标准输入、输出和错误流: 这部分代码根据
isclose
参数的值来决定如何处理标准输入(stdin)、标准输出(stdout)和标准错误(stderr)。-
如果
isclose
为true
,则直接关闭这三个文件描述符:
if (isclose) {close(0);close(1);close(2); }
-
如果
isclose
为false
,则将这三个文件描述符重定向到/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)与服务器进行通信,发送计算请求并接收服务器的响应。
-
命令行参数处理:
-
程序首先检查命令行参数的数量。如果参数数量不正确(不是3个,包括程序名、服务器IP和服务器端口),则打印用法信息并退出。
-
如果参数数量正确,它将服务器IP和端口从命令行参数中提取出来,并转换为适当的类型。
-
-
套接字连接:
-
创建一个
TcpSocket
对象(这里假设TcpSocket
是Socket
的一个子类,虽然代码中没有直接显示这一点)。 -
调用
BuildConnectSocketMethod
方法尝试连接到指定的服务器IP和端口。如果连接失败,则打印错误消息并退出程序。如果连接成功,则打印成功消息。
-
-
请求构建与发送:
-
使用
Factory
模式(这里假设Factory
是一个用于构建请求和响应对象的工厂类)。创建一个Factory
实例。 - 进入一个无限循环,在每次迭代中:
-
随机生成两个整数(x和y)和一个运算符(oper)。
-
使用
Factory
构建一个Request
对象,该对象包含这些随机生成的值。 -
将
Request
对象序列化为一个字符串。 -
添加一个自描述报头到序列化后的请求字符串(这里假设
Encode
函数用于添加报头)。 -
通过套接字发送编码后的请求字符串到服务器。
-
-
-
接收和解析响应:
-
在发送请求后,程序进入一个内部循环,尝试接收服务器的响应。
-
调用
Recv
方法从套接字读取响应数据。如果读取失败,则打印错误消息并继续下一次外部循环的迭代。 -
如果成功读取响应数据,则尝试对响应进行解码(这里假设
Decode
函数用于移除报头并返回原始响应数据)。 -
使用
Factory
构建一个Response
对象,并对解码后的响应数据进行反序列化。 -
打印出反序列化后的响应数据,包括计算结果和结果代码。
-
-
循环与退出:
-
在每次外部循环的末尾,程序会暂停一段时间(通过
sleep
函数),然后再次开始新的迭代。 -
注意,由于代码中没有提供退出循环的条件,因此这个客户端程序将会无限循环地发送请求并接收响应,除非被外部终止。
-
-
清理与退出:
-
在代码的最后(虽然在这个特定的示例中由于无限循环而永远不会达到),程序会关闭套接字并打印一条消息。
-
然后程序返回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
)
-
参数检查: 程序首先检查命令行参数的数量。如果参数数量不正确(应该是程序名和端口号两个参数),则打印用法信息并退出。
-
端口转换: 如果参数数量正确,程序将第二个参数(命令行传入的端口号)从字符串转换为
uint16_t
类型的整数,并存储在localport
变量中。 -
服务器创建: 使用
std::unique_ptr<TcpServer>
来管理TcpServer
对象的生命周期。这样做的好处是,当unique_ptr
超出作用域时,它会自动删除其所指向的对象,从而防止内存泄漏。TcpServer
的构造函数接受端口号和请求处理函数(HandlerRequest
)作为参数。 -
事件循环: 调用
svr->Loop()
来启动服务器的事件循环。在这个循环中,服务器将监听指定的端口,接受客户端的连接,并为每个连接创建一个新线程来处理请求。
请求处理函数 (HandlerRequest
)
这个函数是服务器处理客户端请求的核心逻辑。每当服务器接受到一个新的连接并接收到数据时,它都会调用这个函数来处理数据。
-
初始化: 函数首先设置
error_code
为true
,表示当前没有错误。然后,它创建一个Calculate
对象和一个Factory
对象来构建响应。 -
解码和反序列化: 函数使用一个名为
Decode
的函数(未在代码中给出)来从输入的字节流(inbufferstream
)中解码出消息。然后,它尝试使用req->Deserialize
方法来反序列化消息。如果反序列化失败,函数将记录错误,设置error_code
为false
,并返回一个空字符串。 -
业务处理: 如果反序列化成功,函数将使用
Calculate
对象的Cal
方法来处理请求,并得到一个响应对象(resp
)。 -
序列化和编码: 接下来,函数使用
resp->Serialize
方法来序列化响应对象,将其转换为字符串。然后,它使用Encode
函数来对序列化后的响应进行编码,以便发送回客户端。 -
发送响应: 最后,函数将编码后的响应字符串添加到
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;
}
报头类
这段代码定义了几个关键类和函数,它们共同构成了一个简单的网络通信框架。下面是对这些类和函数的详细介绍:
常量定义
-
ProtSep
和LineBreakSep
:分别定义了协议分隔符和行分隔符。在这个例子中,协议分隔符是一个空格(" "),而行分隔符是一个换行符("\n")。
编码和解码函数
-
Encode(const std::string &message)
:这个函数接受一个字符串消息作为输入,并在其前面添加一个表示消息长度的数字(以换行符分隔),然后在消息末尾也添加一个换行符。这个函数用于将消息打包成一种特定格式,便于网络传输。 -
Decode(std::string &package, std::string *message)
:这个函数用于解码由Encode
函数编码的消息。它首先找到第一个换行符来确定消息长度的位置,然后提取出消息长度,并根据这个长度来提取出实际的消息内容。
请求和响应类
-
Request
类:表示一个网络请求。它包含三个私有成员变量(_data_x
、_data_y
和_oper
),分别用于存储两个整数值和一个字符值。这个类提供了序列化和反序列化方法(Serialize
和Deserialize
),用于将请求对象转换为字符串格式或从字符串格式恢复请求对象。此外,还提供了获取这些私有成员变量的方法(GetX
、GetY
和GetOper
)。 -
Response
类:表示一个网络响应。它包含两个私有成员变量(_result
和_code
),分别用于存储一个整数值和一个表示响应代码的整数值。与Request
类类似,它也提供了序列化和反序列化方法,以及设置和获取这些私有成员变量的方法。
工厂类
-
Factory
类:这是一个工厂类,用于创建Request
和Response
对象。它提供了几个BuildRequest
和BuildResponse
方法,这些方法可以创建具有不同初始化参数的请求和响应对象。使用工厂模式可以更容易地管理和扩展对象的创建过程。
#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类及其子类。以下是对这段代码的详细介绍:
主要类和接口
-
Socket 类
-
这是一个抽象基类,定义了网络编程中常用的一些操作接口,如创建Socket、绑定、监听、接受连接、连接服务器、获取和设置Socket文件描述符,以及关闭Socket等。
-
它还提供了两个构建方法:
BuildListenSocketMethod
和BuildConnectSocketMethod
,分别用于构建监听Socket和连接Socket。 -
此类中的方法大多是纯虚函数,需要在子类中实现。
-
-
TcpSocket 类
-
这是Socket类的子类,专门用于TCP通信。
-
它实现了Socket类中定义的所有纯虚函数,提供了具体的TCP Socket操作实现。
-
此类中使用了一个私有成员变量
_sockfd
来存储Socket的文件描述符。
-
主要函数/方法
-
CreateSocketOrDie
-
创建一个新的Socket,并设置其文件描述符到
_sockfd
。 -
如果创建失败,则记录错误信息并退出程序。
-
-
BindSocketOrDie
-
将Socket绑定到指定的端口上。
-
如果绑定失败,则记录错误信息并退出程序。
-
-
ListenSocketOrDie
-
将Socket设置为监听模式,等待客户端的连接。
-
如果监听设置失败,则记录错误信息并退出程序。
-
-
AcceptConnection
-
接受一个客户端的连接请求,并返回一个新的Socket对象,该对象与客户端进行通信。
-
同时返回客户端的IP地址和端口号。
-
-
ConnectServer
-
尝试连接到指定的服务器IP和端口。
-
如果连接失败,返回false;否则返回true。
-
-
GetSockFd 和 SetSockFd
-
获取或设置Socket的文件描述符。
-
-
CloseSocket
-
关闭Socket。
-
-
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;
};