欢迎来到Cefler的博客😁
🕌博客主页:折纸花满衣
🏠个人专栏:题目解析
🌎推荐文章:承接上文内容【Linux】应用层协议序列化和反序列化
目录
- 👉🏻代码实现如下
- Calculate.hpp
- Protocol.hpp
- Socket.hpp
- TcpClientMain.cc
- TcpServer.hpp
- TcpServerMain.cc
- 实现效果
👉🏻代码实现如下
代码目录:
此次代码主要变动如下:
🔥TcpServerMain.cc:
1.HanlderRrequest:
a.读取报文
b.分析是否是一个完整的报文
c.当读到完整的报文后,进行反序列化
d.业务处理(计算器)
e.将计算结果的报文进行序列化
d.将报文再度Encode再度封装加上长度和\n
7.发送报文
🔥Calculate:
🔥TcpServer.hpp:
func_t
1.ThreadRun
a.读取报文
b.报文处理
c.发送处理
🔥TcpClientMain.cc:
1.构建请求,
2.对请求进行序列化
3.添加自描述报头
4.发送请求
5.读取响应
6.报文解析
7.创建response "result code"并进行反序列化
8.输出结果
Calculate.hpp
#pragma once
#include<iostream>
#include<memory>
#include"Protocol.hpp"
namespace CalCulateNS
{
enum
{
Success = 0,
DivZeroErr,
ModZeroErr,
UnKnowOper
};
class CalCulate
{
public:
CalCulate() {}
//该函数对传进来的需求进行计算处理,然后转换为返回格式转出
shared_ptr<Protocol::Response> Cal(shared_ptr<Protocol::Request> req)
{
//1.首先需要创建回应对象指针
shared_ptr<Protocol::Response> resp = factory.BuildResponse();
resp->SetCode(Success);//创建成功
//2.接下来就是对各种操作符进行选择计算
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);//除0错误
}
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:
Protocol::Factory factory;
};
}
Protocol.hpp
#pragma once
#include<iostream>
#include<string>
#include<memory>
using namespace std;
namespace Protocol
{
const string ProtSep = " ";//分隔符
const string LineBreakSep = "\n";//换行符
//封装报文:"len\nx op y\n"
string Encode(const string& message)
{
string len = to_string(message.size());
string package = len+LineBreakSep+message+LineBreakSep;
return package;
}
//解析报文
bool Decode(string& package,string* message)
{
//1.第一步我们要检查该报文是否有边界\n处理
auto pos = package.find(LineBreakSep);
if(pos==string::npos) return false;
//2.第二步我们要判断有效载荷的长度是否如报头len中所说一样长
string len = package.substr(0,pos);
int messagelen = stoi(len);
int totallen = len.size()+messagelen+2*LineBreakSep.size();//这里是整个报文的长度(报头加边界符加有效载荷)
if(package.size()<totallen) return false;
//3.如果上述没有问题,说明此时package内部一定有一个完整的报文了
//我们现在就能对其解析,得到有效载荷
*message = package.substr(pos+LineBreakSep.size(),messagelen);
package.erase(0,totallen);//package已经利用完,清空
return true;
}
class Request
{
public:
Request()
:_data_x(0),_data_y(0),_oper(0)
{}
Request(int x,int y,char op)
:_data_x(x),_data_y(y),_oper(op)
{}
void Inc()
{
_data_x++;
_data_y++;
}
void Debug()
{
cout<<"_data_x: "<<_data_x<<endl;
cout<<"_data_y: "<<_data_y<<endl;
cout<<"_oper: "<<_oper<<endl;
}
bool Serialize(string* out)//序列化
{
*out = to_string(_data_x)+ProtSep+_oper+ProtSep+to_string(_data_y);
return true;
}
bool Deserialize(string& in)//反序列化
{
//1.先找到操作符两边的分隔符
auto left = in.find(ProtSep);
if(left==string::npos) return false;//找不到则说明不是完整报文,失败
auto right = in.rfind(ProtSep);
if(right==string::npos) return false;
//2.将报文中的x和y以及操作符都提取出来赋值
_data_x = stoi(in.substr(0,left));
_data_y = stoi(in.substr(right+ProtSep.size()));
string oper = in.substr(left+ProtSep.size(),right-(left+ProtSep.size()));
if(oper.size()!=1) return false;//操作数长度只能为1
_oper = oper[0];
return true;
}
//获取_data_x、_data_y、_oper的外部接口
int GetX(){return _data_x;}
int GetY(){return _data_y;}
char GetOper() {return _oper;}
private:
// _data_x _oper _data_y
// 报文的自描述字段
// "len\nx op y\n" : \n不属于报文的一部分,约定
// 很多工作都是在做字符串处理!
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(string* out)
{
*out = to_string(_result) + ProtSep + to_string(_code);
return true;
}
bool Deserialize(string& in)
{
//1.找分隔符
auto pos = in.find(ProtSep);
if(pos==string::npos) return false;
//2.提取
_result = stoi(in.substr(0,pos));
_code = stoi(in.substr(pos+ProtSep.size()));
return true;
}
//设置result和code的值
void SetResult(int res) { _result = res; }
void SetCode(int code) {_code = code; }
//提供result和code的外部接口
int GetResult() { return _result; }
int GetCode() { return _code; }
private:
// "_result _code" 序列
int _result;
int _code;
};
//工厂模式,建造类设计模式,直接返回指针对象
class Factory
{
public:
shared_ptr<Request> BuildRequest()
{
shared_ptr<Request> req = make_shared<Request>();
return req;
}
shared_ptr<Request> BuildRequest(int x,int y,char op)
{
shared_ptr<Request> req = make_shared<Request>(x,y,op);
return req;
}
shared_ptr<Response> BuildResponse()
{
shared_ptr<Response> resp = make_shared<Response>();
return resp;
}
shared_ptr<Response> BuildResponse(int result,int code)
{
shared_ptr<Response> resp = make_shared<Response>(result,code);
return resp;
}
};
}
Socket.hpp
#pragma once
#include<iostream>
#include<string>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#define Convert(addrptr) ((struct sockaddr*)addrptr)
using namespace std;
namespace Net_Work
{
const static int defaultsockfd = -1;
const int backlog = 5;
enum
{
SocketError = 1,
BindError,
ListenError,
};
//封装一个基类:Socket接口类
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(string * peerip,uint16_t* peerport)=0;//接收连接,并返回一个新的套接字
virtual bool ConnectServer(string& peerip,uint16_t peerport)=0;//连接服务端
virtual int GetSockFd() = 0;//返回套接字描述符
virtual void SetSockFd(int sockfd) = 0;//
virtual void CloseSocket() = 0;//关闭套接字
virtual bool Recv(string *buffer,int size) = 0;//用来接收信息
virtual void Send(string& send_str) = 0;//用来发送信息
public:
void BuildListenSocketMethod(uint16_t port,int backlog)//创建一个监听服务
{
//1.创建套接字
CreateSocketOrDie();
//2.套接字进行绑定网络信息
BindSocketOrDie(port);
//3.开始监听
ListenSocketOrDie(backlog);
}
bool BuildConnectSocketMethod(string& serverip,uint16_t& serverport)//创建一个连接服务
{
//1.创建套接字
CreateSocketOrDie();
return ConnectServer(serverip,serverport);
}
void BuildNormalSocketMethod(int sockfd)
{
SetSockFd(sockfd);
}
};
//实现Tcp套接字
class TcpSocket:public Socket
{
public:
TcpSocket(int sockfd = defaultsockfd )
:_sockfd(sockfd)
{
}
~TcpSocket(){}
/
void CreateSocketOrDie() override//创建一个套接字
{
_sockfd = socket(AF_INET,SOCK_STREAM,0);
if(_sockfd<0)
exit(SocketError);
}
void BindSocketOrDie(uint16_t port) override//套接字进行绑定网络信息
{
//本地网络信息初始化
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = INADDR_ANY;//服务端的ip由本地随机绑定
local.sin_port = htons(port);
//开始绑定
int n = bind(_sockfd,Convert(&local),sizeof(local));
if(n<0) exit(BindError);
}
void ListenSocketOrDie(int backlog) override//进行监听
{
int n = listen(_sockfd,backlog);
if(n<0) exit(ListenError);
}
Socket* AcceptConnection(string * peerip,uint16_t* peerport)override//接收连接
{
struct sockaddr_in peer;//用来存储客户端的地址信息
socklen_t len = sizeof(peer);
int newsockfd = accept(_sockfd,Convert(&peer),&len);
if(newsockfd<0)
return nullptr;
*peerport = ntohs(peer.sin_port);//网络序列本地化
*peerip = inet_ntoa(peer.sin_addr);
Socket* s = new TcpSocket(newsockfd);
return s;
}
bool ConnectServer(string& serverip,uint16_t serverport)override//连接服务端
{
struct sockaddr_in server;//存储服务端的地址信息
memset(&server,0,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());//ip网络字节序化,4字节
int n = connect(_sockfd,Convert(&server),sizeof(server));
if(n==0)
return true;
else
return false;
}
int GetSockFd() override//返回套接字描述符
{
return _sockfd;
}
void SetSockFd(int sockfd) override//
{
_sockfd = sockfd;
}
void CloseSocket() override//关闭套接字
{
if(_sockfd>defaultsockfd)
close(_sockfd);
}
bool Recv(std::string *buffer, int size) override
{
char inbuffer[size];
ssize_t n = recv(_sockfd, inbuffer, size-1, 0);
if(n > 0)
{
//接收到了
inbuffer[n] = 0;
*buffer += inbuffer; // buffer可能是历史上所有发送来的消息,拼接在buffer后面
return true;
}
else if(n == 0) return false;
else return false;
}
void Send(std::string &send_str) override
{
// 多路转接我们再统一说
send(_sockfd, send_str.c_str(), send_str.size(), 0);
}
private:
int _sockfd;
};
};
TcpClientMain.cc
#include "Protocol.hpp"
#include "Socket.hpp"
#include <iostream>
#include <string>
#include <ctime>
#include <cstdlib>
#include <unistd.h>
using namespace Protocol;
// ./tcpclient serverip serverport
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]);
Net_Work::Socket *conn = new Net_Work::TcpSocket();
if (!conn->BuildConnectSocketMethod(serverip, serverport))
{
std::cerr << "connect " << serverip << ":" << serverport << " failed" << std::endl;
}
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)
{
// 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);
// 2. 对请求进行序列化
std::string requeststr;
req->Serialize(&requeststr); //
std::cout << requeststr << std::endl;
// for test
std::string testreq = requeststr;
testreq += " ";
testreq += "= ";
// 3. 添加自描述报头
requeststr = Encode(requeststr);
std::cout << requeststr << std::endl;
// 4. 发送请求
conn->Send(requeststr);
std::string responsestr;
while (true)
{
// 5. 读取响应
if(!conn->Recv(&responsestr, 1024)) break;
// 6. 报文进行解析
std::string response;
if (!Decode(responsestr, &response))
continue; // 我就不连续的处理了
// 7.response "result code"
auto resp = factory->BuildResponse();
resp->Deserialize(response);
// 8. 得到了计算结果,而且是一个结构化的数据
std::cout << testreq << resp->GetResult() << "[" << resp->GetCode() << "]" << std::endl;
break;
}
sleep(1);
}
conn->CloseSocket();
return 0;
}
TcpServer.hpp
#pragma once
#include"Socket.hpp"
#include<pthread.h>
#include<functional>
using func_t = std::function<std::string(std::string &, bool *error_code)>;
class TcpServer;
class ThreadData
{
public:
ThreadData(TcpServer* tcp_this,Net_Work::Socket* sockp)
:_this(tcp_this),_sockp(sockp)
{}
public:
TcpServer* _this;//TcpServer的指针对象
Net_Work::Socket* _sockp;//套接字指针对象
};
class TcpServer
{
public:
TcpServer(uint16_t port,func_t handler_request)
:_port(port),_listensocket(new Net_Work::TcpSocket()),_hanlder_request(handler_request)
{
_listensocket->BuildListenSocketMethod(_port,Net_Work::backlog);//开启监听事务
}
static void * ThreadRun(void* args)//因为pthread_create要求方法参数中的参数必须只有一个void*
//所以必须变为静态,否则成员函数第一个参数默认隐式为this指针
{
//因为执行的是多线程,这里我们也没有封装线程的自动回收
//所以为了不发生线程阻塞,我们要让当前线程与主线程分离,不影响主线程,并且自己做完任务自己回收
pthread_detach(pthread_self());
ThreadData* td = static_cast<ThreadData*>(args);
string inbufferstream;//用来存储Recv到的报文
while(true)
{
bool ok = true;
//1.读取报文
if(!td->_sockp->Recv(&inbufferstream,1024)) break;//如果读取报文失败,则退出
//2.对报文内容进行处理,并接收处理后的返回值
string send_string = td->_this->_hanlder_request(inbufferstream,&ok);
if(ok)
{
//如果处理的ok,则发送数据给客户端
if(!send_string.empty())
{
td->_sockp->Send(send_string);
}
}
else
break;
}
td->_sockp->CloseSocket();//关闭accept的新套接字
delete td->_sockp;//销毁指针
delete td;
return nullptr;
}
void Loop()
{
while(true)
{
string peerip;
uint16_t peerport;
Net_Work::Socket* newsocket = _listensocket->AcceptConnection(&peerip,&peerport);//接收客户端信息
if(newsocket==nullptr) continue;
cout<<"获取一个新连接,sockfd:"<<newsocket->GetSockFd()<<"client info: "<<peerip<<" "<<peerport<<endl;
//用完后关闭newsocket
//newsocket->CloseSocket();
//使用多线程进行处理任务
pthread_t tid;
ThreadData* td = new ThreadData(this,newsocket);
pthread_create(&tid,nullptr,ThreadRun,td);//线程创建并执行相对应任务
}
}
~TcpServer()
{
delete _listensocket;
}
private:
uint16_t _port;
Net_Work::Socket* _listensocket;
public:
func_t _hanlder_request;//request执行方法
};
TcpServerMain.cc
#include<memory>
#include<unistd.h>
//包含外部头文件
#include"Protocol.hpp"
#include"Socket.hpp"
#include"TcpServer.hpp"
#include"Calculate.hpp"
using namespace Net_Work;
using namespace Protocol;
using namespace CalCulateNS;
//HandlerRequest进行字节流数据解析,和调用业务处理方法得到处理结果返回
string HandlerRequest(string& inbufferstream,bool * error_code)
{
*error_code = true;
//1.创建一个计算器对象,待会用来处理业务
CalCulate calculte;
//2.构建需求对象:用来进行将_data_x _oper _data_y进行序列化和反序列化
unique_ptr<Factory> factory = make_unique<Factory>();//记得后面加()!!!
shared_ptr<Request> req = factory->BuildRequest();
string total_resp_string;//用来存储回应字符串
string message;//用来存储报文解析后格式后的_data_x _oper _data_y
//3.分析字节流,看是否有一个完整的报文
while(Decode(inbufferstream,&message))
{
printf("message[%s] is waiting analysis\n",message.c_str());
//4.此时Decode解析成功,我一定得到了一个完整的报文
//接下来进行反序列化
if (!req->Deserialize(message))
{
std::cout << "Deserialize error" << std::endl;
*error_code = false;
return std::string();
}
std::cout << "Deserialize success" << std::endl;
//5.进行计算业务处理
shared_ptr<Response> resp = calculte.Cal(req);
//6.处理完后,我们要将结果也要序列化,因为我们还要发送回客户端!
std::string send_string;
resp->Serialize(&send_string); // 序列化为"result code"
//7.构建完成的字符串级别的响应报文
send_string = Encode(send_string);
total_resp_string += send_string;
}
return total_resp_string;
}
int main(int argc,char* argv[])
{
if(argc != 2)
{
cout << "Usage : " << argv[0] << " port" << std::endl;
return 0;
}
uint16_t localport = stoi(argv[1]);
unique_ptr<TcpServer> svr (new TcpServer(localport,HandlerRequest));//unique_ptr只能支持移动构造
svr->Loop();//server开始不断获取新连接
return 0;
}
实现效果
如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长