应用层协议
一、协议定制---以网络计算器为例
网络计算机功能---进行+-*/^&|的运算并返回结果
请求和响应的结构体如下
// 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 1namespace 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);
#elseJson::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 SelfDefineauto 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;
#elseJson::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\nint _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);
#elseJson::Value root;root["result"] = _result;root["code"] = _code;Json::FastWriter writer;*out = writer.write(root);
#endif}bool Deserialize(const std::string &in){
#ifdef SelfDefineauto 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;
#elseJson::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;
}
四、总结和扩展
当然这只是一个协议,我们以后如果想要增加功能,可以再去定义协议,只要在序列化数据的头部加上该请求想要访问哪个服务的即可,其他操作步骤和我们制定网络计算器一样。