文章目录
- 协议
- 什么是“协议”
- 为什么要定制协议
- 如何定制协议
- 网络版本计算器
- 协议定制
- Request
- response
- 客户端
- Request类
- 服务器
- Response类
- 样例演示
协议
什么是“协议”
- 在计算机科学中,协议(Protocol)是对数据格式和计算机之间交换数据时必须遵守的规则的正式描述。例如,网络中的计算机要能够互相顺利通信,就必须遵守相同的协议,如Ethernet、NetBEUI、IPX/SPX以及TCP/IP协议
为什么要定制协议
- 为了使数据在网络上能够从源到达目的,网络通信的参与方必须遵循相同的规则,我们将这套规则称为协议(protocol),而协议最终都需要通过计算机语言的方式表示出来。只有通信计算机双方都遵守相同的协议,计算机之间才能互相通信交流。
如何定制协议
-
定制结构体:用该结构体来表示需要交互的信息
-
序列化:将对象的状态信息转换为可以存储或传输的形式(字节序列)
-
反序列化:把字节序列恢复为结构体对象
网络版本计算器
接下来我们通过自定义协议来实现一个网络版本的计算器,来真正理解一下协议
源码连接:网络版本计算器
协议定制
Request
_data_x
:左操作数_data_y
:右操作数_op
:运算符
response
_result
:计算结果_code
:状态码
class Request{public:Request(int x, int y, char op): _data_x(x), _data_y(y), _op(op){}Request(): _data_x(0), _data_y(0), _op(0){}private: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){}private:int _result;int _code;};
enum{Success = 0, //成功DivZero, //除零错误ModZero, //模零错误UnknownOp //未知错误};
客户端
- 客户端在给服务器发送请求时,会经过以下几个步骤:
- 将请求序列化,即
Serialize
- 添加自描述报头,将响应构建成为一个完整报文,即
Encode
- 发送请求
- 得到响应报文
- 对响应报文解析,即
Decode
- 反序列化,即
Deserialize
,得到结果_result
以及状态码_status
Request类
我们封装另一个Request类,类内成员就是左右操作数以及操作符,类内提供了对请求进行序列化的Serialize
函数以及对请求进行反序列化的函数Deserialize
class Request{public:Request(int x, int y, char op): _data_x(x), _data_y(y), _op(op){}Request(): _data_x(0), _data_y(0), _op(0){}bool Serialize(std::string *out) //"x op y"{
#ifdef SelfDefine*out = std::to_string(_data_x) + ProtSep + _op + ProtSep + std::to_string(_data_y);return true;
#elseJson::Value root;root["data_x"] = _data_x;root["data_y"] = _data_y;root["oper"] = _op;Json::FastWriter writer;*out = writer.write(root);return true;
#endif}bool Deserialize(std::string &in){
#ifdef SelfDefineauto left = in.find(ProtSep);if (left == std::string::npos)return false;auto right = in.rfind(ProtSep);if (right == std::string::npos)return false;_data_x = std::stoi(in.substr(0, left));_data_y = std::stoi(in.substr(right + ProtSep.size()));std::string oper = in.substr(left + ProtSep.size(), right - (left + ProtSep.size()));if (oper.size() != 1)return false;_op = oper[0];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["oper"].asInt();}return true;
#endif}void Debug(){std::cout << "_data_x: " << _data_x << std::endl;std::cout << "_data_y: " << _data_y << std::endl;std::cout << "_op: " << _op << std::endl;}void Inc(){_data_x++;_data_y++;}int GetX(){return _data_x;}int GetY(){return _data_y;}char GetOp(){return _op;}private:int _data_x;int _data_y;char _op;};
#include <iostream>
#include <unistd.h>
#include <string>
#include <memory>
#include <ctime>
#include <stdlib.h>
#include "Socket.hpp"
#include "Protocol.hpp"using namespace ProtocolNS;//./tcpclinet serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){std::cout << "Usage: \n\t" << argv[0] << " serverip serverport\n"<< std::endl;return 0;}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);NetWork::Socket *conn = new NetWork::TcpSocket();if (!conn->BuildConnectionSocketMethod(serverip, serverport)){std::cerr << "connect " << serverip << ":" << serverport << " failed" << std::endl;}std::cerr << "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;usleep(1234);int y = rand()%100;char oper = opers[rand() % opers.size()];std::shared_ptr<Request> req = factory->BuildRequest(x,y,oper);//2.对请求进行序列化std::string requeststr;req->Serialize(&requeststr);std::string testreq = requeststr;testreq += " = ";//3.添加自描述报头requeststr = Encode(requeststr);//4.发送请求conn->Send(requeststr);std::string responsestr;while(true){//5.读取响应conn->Recv(&responsestr,1024);//6.对报文进行解析std::string response;if(!Decode(responsestr,&response))continue;//7.反序列化auto resp = factory->BuildResponse();resp->Deserialize(response);std::cout << testreq << resp->Getresult() << "[" << resp->Getcode() << "]" << std::endl;break;}sleep(1);}conn->CloseFd();return 0;
}
服务器
- 服务器在收到客户端发来的请求后会经过以下几个步骤:
- 对收到的请求报文进行解析,即
Decode
- 反序列化,即
Deserialize
,得到操作数和运算符 - 业务处理(计算)
- 序列化response,即
Serialize
- 构建响应报文,即
Encode
- 发送给客户端
Response类
我们封装另一个Response
类,类内成员就是结果以及状态码,类内提供了对响应进行序列化的Serialize
函数以及对响应进行反序列化的函数Deserialize
class Response{public:Response(): _result(0), _code(0){}Response(int result, int code): _result(result), _code(code){}bool Serialize(std::string *out) //"_result _code"{
#ifdef SelfDefine*out = std::to_string(_result) + ProtSep + std::to_string(_code);return true;
#elseJson::Value root;root["result"] = _result;root["code"] = _code;Json::FastWriter writer;*out = writer.write(root);return true;
#endif}bool Deserialize(std::string &in){
#ifdef SelfDefineauto pos = in.find(ProtSep);if (pos == std::string::npos)return false;_result = std::stoi(in.substr(0, pos));_code = std::stoi(in.substr(pos + ProtSep.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 true;
#endif}void SetResult(int result){_result = result;}void SetCode(int code){_code = code;}int Getresult(){return _result;}int Getcode(){return _code;}private:int _result;int _code;};
#pragma once
#include "Socket.hpp"
#include <iostream>
#include <pthread.h>
#include <functional>using func_t = std::function<std::string(std::string &, bool *)>;class TcpServer;
class ThreadData
{
public:ThreadData(TcpServer *tcp_this, NetWork::Socket *sockp): _this(tcp_this), _sockp(sockp){}public:TcpServer *_this;NetWork::Socket *_sockp;
};class TcpServer
{
public:TcpServer(uint16_t port, func_t handler_request): _port(port), _listensocket(new NetWork::TcpSocket()), _handler_request(handler_request){_listensocket->BuildListenSocketMethod(_port, NetWork::backlog);}static void *ThreadRun(void *args){pthread_detach(pthread_self());ThreadData *td = static_cast<ThreadData *>(args);std::string inbufferstream;while (true){bool ok = true;// 1.读取报文if (!td->_sockp->Recv(&inbufferstream, 1024))break;//2.报文处理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->CloseFd();delete td->_sockp;delete td;return nullptr;}void Loop(){while (true){std::string peerip;uint16_t peerport;NetWork::Socket *newsock = _listensocket->AcceptConnection(&peerip, &peerport);if (newsock == nullptr)continue;std::cout << "获取一个新连接,sockfd: " << newsock->GetSocket() << "客户信息:" << peerip << ":" << peerport << std::endl;pthread_t tid;ThreadData *td = new ThreadData(this, newsock);pthread_create(&tid, nullptr, ThreadRun, td);}}~TcpServer(){delete _listensocket;}private:uint16_t _port;NetWork::Socket *_listensocket;public:func_t _handler_request;
};
#include "Socket.hpp"
#include "TcpServer.hpp"
#include "Protocol.hpp"
#include "Calculate.hpp"
#include <iostream>
#include <string>
#include <memory>using namespace NetWork;
using namespace ProtocolNS;
using namespace CalculateNs;std::string HandlerRequest(std::string &inbufferstream, bool *error_code)
{*error_code = true;Calculate calculate;std::unique_ptr<Factory> factory = std::make_unique<Factory>();auto req = factory->BuildRequest();// 2.查看报文是否完整std::string message;std::string total_resp_messgae;while (Decode(inbufferstream, &message)){// 3.报文一定完整,反序列化reqif (!req->Deserialize(message)){*error_code = false;return std::string();}// 4.业务处理auto resp = calculate.Cal(req);// 5.序列化responsestd::string send_string;resp->Serialize(&send_string);// 6.构建字符串级别的响应报文send_string = Encode(send_string);// 7.发送total_resp_messgae += send_string;}return total_resp_messgae;
}//./tcpserver 8888
int main(int argc, char *argv[])
{if (argc != 2){std::cout << "Usage: \n\t" << argv[0] << " local_port\n"<< std::endl;return 0;}uint16_t port = std::stoi(argv[1]);std::unique_ptr<TcpServer> tsvr(new TcpServer(port, HandlerRequest));tsvr->Loop();return 0;
}
样例演示
服务器启动后会进入死循环,不间断为客户端提供服务,客户端会随机生成操作数以及运算符,将请求发送给服务器后,服务器会将响应发送给客户端,客户端便会把结果打印输出在屏幕上