应用层协议
一、协议定制---以网络计算器为例
网络计算机功能---进行+-*/^&|的运算并返回结果
请求和响应的结构体如下
// Protocol.hpp
#pragma once
#include <iostream>
#include <memory>
class Request
{
public:
Request()
{
}
Request(int data_x, int data_y, char op)
: _data_x(data_x), _data_y(data_y), _op(op)
{
}
private:
int _data_x;
int _data_y;
char _op;
};
class Response
{
public:
Response()
{
}
Response(int result, int code)
: _result(result), _code(code)
{
}
private:
int _result;
int _code;
};
// 建造类 --- 设计模式 --- 用来创建类对象
class Factory
{
public:
std::shared_ptr<Request> BuiltRequest()
{
std::shared_ptr<Request> req = std::make_shared<Request>();
return req;
}
std::shared_ptr<Request> BuiltRequest(int data_x, int data_y, char op)
{
std::shared_ptr<Request> req = std::make_shared<Request>(data_x, data_y, op);
return req;
}
std::shared_ptr<Response> BuiltResponse()
{
std::shared_ptr<Response> resp = std::make_shared<Response>();
return resp;
}
std::shared_ptr<Response> BuiltResponse(int result, int code)
{
std::shared_ptr<Response> resp = std::make_shared<Response>(result, code);
return resp;
}
};
将请求的数据按照怎样的方式序列化成为字符串呢?
我们选择将数据转换成 "len\nx op y\n"
- len是"x op y"的字符串长度,"len"后面的\n用来将len和后面的有效数据分开
- ”x op y“是要计算的表达式,比如”5 + 6“,中间加空格间隔开,它后面的\n是为了调试打印,可以不加
这里我们也可以直接选择将数据转化成 "x op y\n",用\n作为数据之间的间隔,但是如果我们要传递的数据中就包含\n,这样做就会出现问题,但是加"len\n"就不会,因为我们能保证len这个字符串中不可能出现\n,更具有通用性
二、序列化和反序列化
所以,序列化和反序列化本质就是字符串相关的操作,具体的代码如下
//新增 给字符串数据,加报头和去报头的函数
const std::string BlankSep = " ";
const std::string LineSep = "\n";
// 将"有效数据" -> "len\n有效数据\n"
std::string Encode(const std::string &message)
{
return std::to_string(message.size()) + LineSep + message + LineSep;
}
// 将"len\n有效数据\n" -> "有效数据",这里要注意判断数据是否完整,即是否有一个完整的请求
bool Decode(std::string &package, std::string *message)
{
auto pos = package.find(LineSep);
if (pos == std::string::npos)
return false;
int len = std::stoi(package.substr(0, pos));
int total = len + pos + 2 * LineSep.size();
if (total > package.size())
return false;
*message = package.substr(pos + LineSep.size(), len);
package.erase(0, total);
return true;
}
class Request
{
//...
public:
// 新增 --- 序列化和反序列化两个函数
// 将数据序列化为 "x op y", 其中BlankSep是" "
void Serialize(std::string *out)
{
*out = std::to_string(_data_x) + BlankSep + _op + BlankSep + std::to_string(_data_y);
}
// 将"x op y"反序列化结构化字段, 其中BlankSep是" "
bool Deserialize(const std::string &in)
{
auto l = in.find(BlankSep);
if (l == std::string::npos)
return false;
_data_x = std::stoi(in.substr(0, l));
auto r = in.rfind(BlankSep);
if (r == std::string::npos)
return false;
_data_y = std::stoi(in.substr(r + BlankSep.size()));
if (r - l - (int)BlankSep.size() != 1)
return false;
_op = in[l + BlankSep.size()];
return true;
}
};
class Response
{
//...
public:
// 新增 --- 序列化和反序列化两个函数
// 将数据转序列化为"result code", 其中BlankSep是" "
void Serialize(std::string *out)
{
*out = std::to_string(_result) + BlankSep + std::to_string(_code);
}
// 将"result code"反序列化为结构化字段, 其中BlankSep是" "
bool Deserialize(const std::string &in)
{
auto pos = in.find(BlankSep);
if (pos == std::string::npos)
return false;
_result = std::stoi(in.substr(0, pos));
_code = std::stoi(in.substr(pos + BlankSep.size()));
return true;
}
};
当然这些字符串相关的序列化和反序列化操作过于繁琐,我们可以不用手写,可以直接用一些工具帮助我们完成这一过程,如json,protobuf等。
用json举个例子(注意要下载json相关的第三方库)
class Request
{
//...
public:
void Serialize(std::string *out)
{
Json::Value root;
root["data_x"] = _data_x;
root["data_y"] = _data_y;
root["op"] = _op;
Json::FastWriter writer;
*out = writer.write(root);
}
bool Deserialize(const std::string &in)
{
Json::Value root;
Json::Reader reader;
bool res = reader.parse(in, root);
if (res)
{
_data_x = root["data_x"].asInt();
_data_y = root["data_y"].asInt();
_op = root["op"].asInt();
}
return res;
}
};
class Response
{
//...
public:
void Serialize(std::string *out)
{
Json::Value root;
root["result"] = _result;
root["code"] = _code;
Json::FastWriter writer;
*out = writer.write(root);
}
bool Deserialize(const std::string &in)
{
Json::Value root;
Json::Reader reader;
bool res = reader.parse(in, root);
if (res)
{
_result = root["result"].asInt();
_code = root["code"].asInt();
}
return res;
}
};
Json序列化后的表达式如下
三、完整代码(包含对socket的封装)
// Calculate.hpp
#pragma once
#include <iostream>
#include "Protocol.hpp"
namespace Calcu
{
enum
{
Success,
DivZero,
ModZero,
Unknown
};
class Calculate
{
public:
std::shared_ptr<Protocol::Response> cal(std::shared_ptr<Protocol::Request> req)
{
auto resp = factory.BuiltResponse();
switch (req->GetOp())
{
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(DivZero);
}
else
{
resp->SetResult(req->GetX() / req->GetY());
}
}
break;
case '%':
{
if (req->GetY() == 0)
{
resp->SetCode(ModZero);
}
else
{
resp->SetResult(req->GetX() % req->GetY());
}
}
break;
case '^':
resp->SetResult(req->GetX() ^ req->GetY());
break;
case '|':
resp->SetResult(req->GetX() | req->GetY());
break;
case '&':
resp->SetResult(req->GetX() & req->GetY());
break;
default:
resp->SetCode(Unknown);
break;
}
return resp;
}
private:
Protocol::Factory factory;
};
}
//Protocol.hpp
#pragma once
#include <iostream>
#include <memory>
#include <string>
#include <jsoncpp/json/json.h>
// #define SelfDefine 1
namespace Protocol
{
const std::string BlankSep = " ";
const std::string LineSep = "\n";
std::string Encode(const std::string &message)
{
return std::to_string(message.size()) + LineSep + message + LineSep;
}
bool Decode(std::string &package, std::string *message)
{
auto pos = package.find(LineSep);
if (pos == std::string::npos)
return false;
int len = std::stoi(package.substr(0, pos));
int total = len + pos + 2 * LineSep.size();
if (total > package.size())
return false;
*message = package.substr(pos + LineSep.size(), len);
package.erase(0, total);
return true;
}
class Request
{
public:
Request() : _data_x(0), _data_y(0), _op(0)
{
}
Request(int data_x, int data_y, char op)
: _data_x(data_x), _data_y(data_y), _op(op)
{
}
void Serialize(std::string *out)
{
#ifdef SelfDefine
*out = std::to_string(_data_x) + BlankSep + _op + BlankSep + std::to_string(_data_y);
#else
Json::Value root;
root["data_x"] = _data_x;
root["data_y"] = _data_y;
root["op"] = _op;
Json::FastWriter writer;
*out = writer.write(root);
#endif
}
bool Deserialize(const std::string &in)
{
#ifdef SelfDefine
auto l = in.find(BlankSep);
if (l == std::string::npos)
return false;
_data_x = std::stoi(in.substr(0, l));
auto r = in.rfind(BlankSep);
if (r == std::string::npos)
return false;
_data_y = std::stoi(in.substr(r + BlankSep.size()));
if (r - l - (int)BlankSep.size() != 1)
return false;
_op = in[l + BlankSep.size()];
return true;
#else
Json::Value root;
Json::Reader reader;
bool res = reader.parse(in, root);
if (res)
{
_data_x = root["data_x"].asInt();
_data_y = root["data_y"].asInt();
_op = root["op"].asInt();
}
return res;
#endif
}
void Inc()
{
_data_x++;
_data_y++;
}
void DeBug()
{
std::cout << "data_x :" << _data_x << std::endl;
std::cout << "data_y :" << _data_y << std::endl;
std::cout << "op :" << _op << std::endl;
}
int GetX() { return _data_x; }
int GetY() { return _data_y; }
char GetOp() { return _op; }
private:
// 序列化
// len\nx op y\n
int _data_x;
int _data_y;
char _op;
};
class Response
{
public:
Response() : _result(0), _code(0)
{
}
Response(int result, int code)
: _result(result), _code(code)
{
}
void Serialize(std::string *out)
{
#ifdef SelfDefine
*out = std::to_string(_result) + BlankSep + std::to_string(_code);
#else
Json::Value root;
root["result"] = _result;
root["code"] = _code;
Json::FastWriter writer;
*out = writer.write(root);
#endif
}
bool Deserialize(const std::string &in)
{
#ifdef SelfDefine
auto pos = in.find(BlankSep);
if (pos == std::string::npos)
return false;
_result = std::stoi(in.substr(0, pos));
_code = std::stoi(in.substr(pos + BlankSep.size()));
return true;
#else
Json::Value root;
Json::Reader reader;
bool res = reader.parse(in, root);
if (res)
{
_result = root["result"].asInt();
_code = root["code"].asInt();
}
return res;
#endif
}
void SetResult(int res)
{
_result = res;
}
void SetCode(int code)
{
_code = code;
}
int GetResult()
{
return _result;
}
int GetCode()
{
return _code;
}
private:
int _result;
int _code;
};
// 建造类 --- 设计模式
class Factory
{
public:
std::shared_ptr<Request> BuiltRequest()
{
std::shared_ptr<Request> req = std::make_shared<Request>();
return req;
}
std::shared_ptr<Request> BuiltRequest(int data_x, int data_y, char op)
{
std::shared_ptr<Request> req = std::make_shared<Request>(data_x, data_y, op);
return req;
}
std::shared_ptr<Response> BuiltResponse()
{
std::shared_ptr<Response> resp = std::make_shared<Response>();
return resp;
}
std::shared_ptr<Response> BuiltResponse(int result, int code)
{
std::shared_ptr<Response> resp = std::make_shared<Response>(result, code);
return resp;
}
};
}
// 对sockfd进行封装
//对网络套接字进行封装
// Socket.hpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <memory>
#include <string>
#include <unistd.h>
const int defaultsockfd = -1;
const int defaultbacklog = 5;
#define CONV(addr) ((struct sockaddr *)addr)
namespace zxws
{
enum
{
SockError = 1,
BindError,
ListenError,
AcceptError,
ConnectError
};
// 模板方法 --- 设计模式的一种
// 对网络套接字进行封装 --- 屏蔽内部建立连接的过程
class Socket
{
public:
virtual ~Socket() {}
virtual void CreateSocket() = 0;
virtual void BindSocket(uint16_t port) = 0;
virtual void ListenSocketOrDie(int backlog) = 0;
virtual Socket *AcceptConnection(std::string *peerip, uint16_t *peerport) = 0;
virtual bool ConnectServer(const std::string &peerip, uint16_t peerport) = 0;
virtual int GetSockfd() = 0;
virtual void SetSockFd(int sockfd) = 0;
virtual void CloseSockfd() = 0;
public:
void BuildListenSocket(uint16_t port, int backlog)
{
CreateSocket();
BindSocket(port);
ListenSocketOrDie(backlog);
}
bool BuildConnectSocket(const std::string &peerip, uint16_t peerport)
{
CreateSocket();
return ConnectServer(peerip, peerport);
}
void BuildNormalSocket(int sockfd)
{
SetSockFd(sockfd);
}
};
class TcpSocket : public Socket
{
public:
TcpSocket(int sockfd = defaultsockfd) : _sockfd(sockfd)
{
}
virtual ~TcpSocket() {}
virtual void CreateSocket() override
{
_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
exit(SockError);
std::cout << "create success , sockfd : " << _sockfd << std::endl;
}
virtual void BindSocket(uint16_t port) override
{
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
int n = bind(_sockfd, CONV(&local), sizeof(local));
if (n < 0)
exit(BindError);
std::cout << "bing success , sockfd: " << _sockfd << std::endl;
}
virtual void ListenSocketOrDie(int backlog) override
{
int n = listen(_sockfd, backlog);
if (n < 0)
exit(ListenError);
std::cout << "listen success , sockfd: " << _sockfd << std::endl;
}
virtual Socket *AcceptConnection(std::string *peerip, uint16_t *peerport)
{
struct sockaddr_in server;
socklen_t len = sizeof(server);
int sockfd = accept(_sockfd, CONV(&server), &len);
if (sockfd < 0)
return nullptr;
char buffer[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &server.sin_addr, buffer, sizeof(buffer));
*peerip = buffer;
*peerport = ntohs(server.sin_port);
return new TcpSocket(sockfd);
}
virtual bool ConnectServer(const std::string &peerip, uint16_t peerport) override
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(peerport);
inet_pton(_sockfd, peerip.c_str(), &server.sin_addr);
int n = connect(_sockfd, CONV(&server), sizeof(server));
return n == 0;
}
virtual int GetSockfd()
{
return _sockfd;
}
virtual void SetSockFd(int sockfd)
{
_sockfd = sockfd;
}
void CloseSockfd()
{
if (_sockfd >= 0)
close(_sockfd);
}
private:
int _sockfd;
};
}
//TcpServer.hpp
#pragma once
#include "Socket.hpp"
#include <pthread.h>
#include <functional>
using func_t = std::function<std::string(std::string&,bool*)>;
class TcpServer;
class ThreadData
{
public:
ThreadData(TcpServer* tcp_this,zxws::Socket* sockp)
:_this(tcp_this),_sockp(sockp)
{}
public:
TcpServer*_this;
zxws::Socket* _sockp;
};
class TcpServer
{
public:
TcpServer(int port, func_t task)
: _port(port), _listensockfd(new zxws::TcpSocket()), _task(task)
{
}
static void *ThreadRoutine(void *args)
{
pthread_detach(pthread_self());
ThreadData *td = static_cast<ThreadData *>(args);
std::string inbuffer;
while (1)
{
bool ok = true;
// 1、接收
if(!td->_sockp->Recv(&inbuffer,1024))
break;
// 2、处理
std::string send_str = td->_this->_task(inbuffer,&ok);
if(ok)
{
if(!send_str.empty())
{
// 3、发送
td->_sockp->Send(send_str);
}
}
else
{
break;
}
}
//注意顺序
td->_sockp->CloseSockfd();
delete td->_sockp;
delete td;
return nullptr;
}
void Loop()
{
_listensockfd->BuildListenSocket(_port, defaultbacklog);
std::string ip;
uint16_t port;
while (true)
{
zxws::Socket *sockfd = _listensockfd->AcceptConnection(&ip, &port);
if (sockfd == nullptr)
continue;
std::cout << "accept success , [" << ip << ":" << port << "]"
<< " sockfd " << sockfd->GetSockfd() << std::endl;
// sockfd->CloseSockfd();
ThreadData *td = new ThreadData(this,sockfd);
pthread_t tid;
pthread_create(&tid, nullptr, ThreadRoutine, td);
}
}
~TcpServer()
{
delete _listensockfd;
}
private:
int _port;
zxws::Socket *_listensockfd;
func_t _task;
};
//TcpServer.cpp
#include "Socket.hpp"
#include "TcpServer.hpp"
#include "Protocol.hpp"
#include "Calculate.hpp"
using namespace Protocol;
using namespace zxws;
using namespace Calcu;
std::string Handler(std::string &inbuffer, bool *error_code)
{
*error_code = true;
std::unique_ptr<Factory> factory(new Factory);
auto req = factory->BuiltRequest();
Calculate calculate;
// 1、分析字节流
std::string message;
std::string ret;
while (Decode(inbuffer, &message))
{
// 2、反序列化
if (!req->Deserialize(message))
{
*error_code = false;
return std::string();
}
// 3、业务逻辑
auto resp = calculate.cal(req);
// 4、序列化
std::string send_str;
resp->Serialize(&send_str);
// 5、添加报头
send_str = Encode(send_str);
ret += send_str;
}
return ret;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cout << "Usage : " << argv[0] << " port" << std::endl;
return 0;
}
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<TcpServer> svr(new TcpServer(port, Handler));
svr->Loop();
return 0;
}
//TcpClient.cpp
#include "Socket.hpp"
#include "Protocol.hpp"
using namespace Protocol;
const std::string opers = "+-/*%=&^|";
int main(int argc, char *argv[])
{
if (argc != 3)
{
std::cout << "Usage : " << argv[0] << " ip port" << std::endl;
return 0;
}
uint16_t port = std::stoi(argv[2]);
std::string ip = argv[1];
zxws::Socket *sock = new zxws::TcpSocket();
if (!sock->BuildConnectSocket(ip, port))
{
std::cout << "connect failed" << std::endl;
return 0;
}
std::cout << "connect success" << std::endl;
std::unique_ptr<Factory> factory(new Factory);
srand(time(nullptr));
std::string response;
while (1)
{
// 1、创建消息
int x = rand() % 100;
int y = rand() % 100;
char op = opers[rand() % opers.size()];
usleep(1000);
std::shared_ptr<Request> req = factory->BuiltRequest(x, y, op);
req->DeBug();
// 2、序列化
std::string requires;
req->Serialize(&requires);
std::cout<<requires<<std::endl;
// 3、添加报头
std::string res = Encode(requires);
// 4、发送
sock->Send(res);
// 5、接收
sock->Recv(&response, 1024);
// 6、分析字节流
std::string message;
if (!Decode(response, &message))
break;
// 7、反序列化
auto resp = factory->BuiltResponse();
if (!resp->Deserialize(message))
break;
// 打印
std::cout << x << op << y << " = " << resp->GetResult() << " [" << resp->GetCode() << "]" << std::endl;
}
sock->CloseSockfd();
return 0;
}
四、总结和扩展
当然这只是一个协议,我们以后如果想要增加功能,可以再去定义协议,只要在序列化数据的头部加上该请求想要访问哪个服务的即可,其他操作步骤和我们制定网络计算器一样。