目录
实现思路
-
客户端负责向服务器发送计算请求,使用随机函数生成两个操作数和一个操作符,先生成一个工厂类对象,使用工厂对象生成请求对象,随后将请求对象序列化,将序列化后的字符串添加报头打包发送给服务器
-
服务端负责向服务器发送结果响应,接受客户端发送来的请求,进行解包,随后将解包得到的字符串反序列化为请求对象,调用服务器响应函数来生成结果响应对象,将响应对象进行序列化且添加报头打包发回给客户端。
主要模块
计算业务类
这个类名为 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");
// 序列化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;
}
报头类
这段代码定义了几个关键类和函数,它们共同构成了一个简单的网络通信框架。下面是对这些类和函数的详细介绍:
常量定义
-
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;
};