初始“协议”
一、认识协议
(一)协议的概念
- 定义:协议是网络通信中计算机双方必须共同遵守的一组约定。这些约定规定了通信的规则,包括如何建立连接、如何互相识别、如何传输数据等。
- 重要性:协议是网络通信的基础。只有当通信双方都遵循相同的协议时,数据才能正确地从源端传输到目的端。如果没有协议,计算机之间将无法进行有效的通信。
- 实现方式:协议需要通过计算机语言的方式表示出来。这意味着协议的具体规则需要被编码到软件或硬件中,以便计算机能够理解和执行这些规则。
(二)协议的作用
- 确保数据传输的准确性:协议规定了数据的格式和传输方式,确保数据在传输过程中不会被误解或丢失。
- 支持多种通信方式:不同的协议可以支持不同的通信方式,例如有线通信、无线通信、点对点通信或广播通信等。
- 实现互操作性:协议使得不同厂商的设备和软件能够相互兼容和通信。例如,TCP/IP协议使得不同操作系统的计算机可以在互联网上进行通信。
(三)协议的层次结构
- OSI七层模型:协议通常按照OSI七层模型进行分类,每一层都有其特定的功能和协议。例如,物理层负责物理介质的传输,传输层负责端到端的数据传输等。
- TCP/IP协议栈:在实际的网络通信中,TCP/IP协议栈是最常用的协议体系。它包括应用层、传输层、网络层和链路层等。
(四)协议的示例
- HTTP协议:用于浏览器和服务器之间的通信,支持网页的请求和响应。
- TCP协议:用于保证数据在传输过程中的可靠性,通过三次握手建立连接,通过四次挥手关闭连接。
- IP协议:负责将数据包从源地址传输到目的地址,是互联网的基础协议。
二、网络通信中的数据传输方式
(一)简单字符串传输
- 直接传输:如果需要传输的数据是一个简单的字符串,可以直接将字符串发送到网络中。接收端可以直接从网络中获取到这个字符串。
- 代码演示:
//客户端
std::string message = "Hello, World!";
send(socket, message.c_str(), message.size(), 0);
//服务端
char buffer[1024];
recv(socket, buffer, sizeof(buffer), 0);
std::cout << buffer << std::endl;
(二)结构化数据传输
- 复杂性:如果需要传输的是结构化的数据(例如一个包含多个字段的对象),不能直接将这些数据逐个发送到网络中。因为接收端需要知道如何将这些分散的数据重新组合成原始的结构化数据。
示例:在实现一个网络版计算器时,客户端需要向服务端发送以下结构化的数据:左操作数、右操作数、操作符(如 +
, -
, *
, /
等),这些数据不能简单地逐个发送,因为服务端需要知道如何将这些数据组合起来。
解决方案:序列化与反序列化
序列化(Serialization)
定义:序列化是将对象或数据结构转换为字节序列(或其他格式)的过程,以便可以将其存储到文件、数据库或通过网络传输。
目的:
- 数据传输:在网络通信中,数据需要以字节流的形式传输。序列化将复杂的数据结构(如对象、结构体等)转换为字节序列,使其能够通过网络传输。
- 数据存储:将数据序列化后存储到文件或数据库中,便于后续的读取和恢复。
- 跨平台兼容性:不同平台可能有不同的数据表示方式(如字节序、对齐方式等),序列化可以将数据转换为统一的格式,确保跨平台传输和存储的一致性。
反序列化(Deserialization)
定义:反序列化是将字节序列(或其他格式)还原为原始对象或数据结构的过程。
目的:
- 数据恢复:接收端从网络中接收到字节序列后,需要将其还原为原始的数据结构,以便进行进一步的处理。
- 数据一致性:确保接收端能够正确解析和使用发送端的数据。
OSI七层模型中表示层的作用就是,实现设备固有数据格式和网络标准数据格式的转换。其中设备固有的数据格式指的是数据在应用层上的格式,而网络标准数据格式则指的是序列化之后可以进行网络传输的数据格式。TCP/IP模型中的应用层也承担了类似OSI模型中表示层的功能。虽然TCP/IP模型没有明确划分出一个独立的“表示层”,但其应用层的功能范围更广泛,涵盖了OSI模型中表示层的职责。
约定方案一:将结构化的数据组合成一个字符串
- 客户端发送一个形如“1+1”的字符串。
- 这个字符串中有两个操作数,都是整型。
- 两个数字之间会有一个字符是运算符。
- 数字和运算符之间没有空格。
客户端可以按某种方式将这些结构化的数据组合成一个字符串,然后将这个字符串发送到网络当中,此时服务端每次从网络当中获取到的就是这样一个字符串,然后服务端再以相同的方式对这个字符串进行解析,此时服务端就能够从这个字符串当中提取出这些结构化的数据。
存在问题
1、解析复杂性:
- 问题:服务端需要解析字符串以提取操作数和操作符。如果字符串格式稍有变化(如多出空格、使用不同的分隔符等),解析逻辑就需要相应调整,容易出错。
- 示例:如果客户端发送的是“1 + 1”(带空格),而服务端解析逻辑假设字符串格式为“1+1”(无空格),则可能导致解析失败。
2、缺乏灵活性:
- 问题:这种格式难以扩展。如果需要支持更多功能(如添加更多操作符、支持浮点数、添加括号等),字符串格式需要进行复杂调整。
- 示例:如果要支持浮点数运算(如“1.5+2.3”),需要额外处理浮点数的解析,而整数和浮点数的解析逻辑可能不同。
3、缺乏类型安全
- 问题:字符串格式无法保证数据的类型安全。服务端需要手动将字符串解析为整数或其他类型,这可能导致类型转换错误。
- 示例:如果客户端发送“a+1”,服务端在尝试将“a”解析为整数时会失败。
4、缺乏标准
- 问题:这种字符串格式缺乏统一的标准,不同的系统或开发者可能会采用不同的格式,导致兼容性问题。
- 示例:如果一个系统使用“1+1”格式,而另一个系统使用“1,+,1”格式,两个系统之间将无法直接通信。
也就是说当使用简单的字符串格式来传输结构化数据时,不同的系统或开发者可能会选择不同的字符串格式来表示相同的数据内容,从而导致兼容性问题。换句话说,如果没有统一的标准来规定数据的格式,不同的实现可能会产生冲突,使得系统之间无法直接通信。
5、性能问题
- 问题:字符串解析通常涉及大量的字符串操作,这些操作在性能上可能不如直接操作字节序列高效。特别是在数据量较大或传输频率较高的场景下,性能问题会更加明显。
- 示例:如果需要传输大量的计算请求,字符串解析可能会成为性能瓶颈。
约定方案二:二进制序列化和反序列化
- 定制结构体来表示需要交互的信息。
- 发送数据时将这个结构体按照一个规则转换成网络标准数据格式,接收数据时再按照相同的规则把接收到的数据转化为结构体。
(三)实现一个网络版本计算器
(1)实现一个简单的网络编程库,封装TCP/IP套接字操作,提供更高级别接口,简化网络编程
#pragma once
#include <iostream>
#include <stdint.h>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
namespace Net_Work
{
#define Convert(addrptr) ((struct sockaddr *)addrptr)const static int defaultsockfd = -1;const int backlog = 5;enum ErrorCode{SocketError = 1,BindError,ListenError};// 封装一个基类,Socket接口类// 设计模式,模板方法类class Socket{public:// 如果基类的析构函数不是虚的,当通过基类指针删除派生类对象时,// 只会调用基类的析构函数,而不会调用派生类的析构函数,导致资源泄漏virtual ~Socket() {}// 封装创建监听 Socket 的完整流程使用模板方法模式,将创建、绑定、监听的步骤固定为一个流程。// 子类可以重写具体步骤,但流程本身保持一致。void BuildListenSocketMethod(uint16_t port, int backlog){CreateSocketOrDie();BindSocketOrDie(port);ListenSocketOrDie(backlog);}// 封装创建连接 Socket 的完整流程。类似模板方法模式,固定创建和连接的流程。// 子类可以重写具体步骤(如 CreateSocketOrDie 或 ConnectServer),但流程保持一致。bool BuildConnectSocketMethod(std::string &serverip, uint16_t serverport){CreateSocketOrDie();return ConnectServer(serverip, serverport);}// 装创建普通 Socket 的流程。用于接受连接后的新 Socket 初始化。// 子类可以通过重写 SetSockFd 来实现具体的初始化逻辑void BuildNormalSockerMethod(int sockfd){SetSockFd(sockfd);}// 获取当前 Socket 的文件描述符virtual int GetSockFd() = 0;// 关闭当前 Socketvirtual void CloseSocket() = 0;// 从 Socket 接收数据,并存储到缓冲区中virtual bool Recv(std::string *buffer, int size) = 0;// 通过 Socket 发送数据virtual void Send(std::string &send_str) = 0;// 接受一个客户端连接,并返回一个新的 Socket 对象virtual Socket *AcceptConnection(std::string *peerip, uint16_t *peerport) = 0;private:// 创建一个 Socket,并在失败时终止程序virtual void CreateSocketOrDie() = 0;// 将 Socket 绑定到指定的端口,并在失败时终止程序virtual void BindSocketOrDie(uint16_t port) = 0;// 将 Socket 设置为监听模式,并在失败时终止程序virtual void ListenSocketOrDie(int backlog) = 0;// 连接到指定的服务器地址和端口virtual bool ConnectServer(std::string &serverip, uint16_t serverport) = 0;// 设置当前 Socket 的文件描述符virtual void SetSockFd(int sockfd) = 0;};class TcpSocket : public Socket{public:TcpSocket(int sockfd = defaultsockfd) : _sockfd(sockfd) {}~TcpSocket() {}// 获取当前 Socket 的文件描述符int GetSockFd() override{return _sockfd;}// 关闭当前 Socketvoid CloseSocket() override{if (_sockfd > defaultsockfd)::close(_sockfd);}// 接受一个客户端连接,并返回一个新的 Socket 对象Socket *AcceptConnection(std::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 *acceptsocket = new TcpSocket(newsockfd);return acceptsocket;}// 从 Socket 接收数据,并存储到缓冲区中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; // 拼接return true;}return false;}// 通过 Socket 发送数据void Send(std::string &send_str) override{::send(_sockfd, send_str.c_str(), send_str.size(), 0);}private:// 创建一个 Socket,并在失败时终止程序void CreateSocketOrDie() override{_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0)exit(ErrorCode::SocketError);}// 将 Socket 绑定到指定的端口,并在失败时终止程序void BindSocketOrDie(uint16_t port) override{struct sockaddr_in local;::bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = ::htons(port);local.sin_addr.s_addr = INADDR_ANY;if (::bind(_sockfd, Convert(&local), sizeof(local)) < 0)exit(ErrorCode::BindError);}// 将 Socket 设置为监听模式,并在失败时终止程序void ListenSocketOrDie(int backlog) override{if (::listen(_sockfd, backlog) < 0)exit(ErrorCode::ListenError);}// 连接到指定的服务器地址和端口bool ConnectServer(std::string &serverip, uint16_t serverport) override{struct sockaddr_in server;::bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_port = ::htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());if (::connect(_sockfd, Convert(&server), sizeof(server)) == 0)return true;return false;}// 设置当前 Socket 的文件描述符void SetSockFd(int sockfd) override{_sockfd = sockfd;}private:int _sockfd;};
}
1、代码整体功能与用途:这段代码实现了一个网络通信相关的Socket类库,主要用于封装TCP Socket通信的操作,方便进行网络编程,包括创建监听Socket、建立连接、接收和发送数据等功能,通过面向对象的方式对底层Socket API进行封装
2、设计思路与结构解析
Socket基类 :
纯虚函数与多态 :基类 Socket
中声明了一系列纯虚函数,如 GetSockFd
、CloseSocket
、Recv
、Send
、AcceptConnection
等,这些函数在派生类中被具体实现,使得基类指针可以指向不同类型的Socket对象,并调用相应的方法,体现了多态性,便于后续对不同类型Socket进行统一管理和操作。
模板方法模式 :BuildListenSocketMethod
和 BuildConnectSocketMethod
是模板方法,它们定义了一个操作中的算法骨架,而将一些步骤延迟到子类中实现,使得子类可以在不改变算法结构的情况下,重新定义算法的某些特定步骤。比如在创建监听Socket的流程中,固定了创建、绑定、监听这几个主要步骤的顺序,但具体的创建、绑定、监听等操作由子类去实现,保证了流程的一致性和可扩展性。
私有虚函数 :CreateSocketOrDie
、BindSocketOrDie
、ListenSocketOrDie
、ConnectServer
、SetSockFd
等私有虚函数,将具体的Socket创建、绑定、监听、连接以及设置文件描述符等核心操作封装起来,仅在类内部提供接口,一方面隐藏了实现细节,另一方面也便于后续根据不同需求在子类中重写这些函数来实现不同的功能。
(2)实现一个简单的网络通信协议框架,用于在网络应用程序中高效、规范地传输和处理请求与响应数据
#pragma once
#include <iostream>
#include <string>
#include <memory>
namespace Protocol
{const std::string ProSep = " ";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;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;// 到这一步说明package内部至少有一个完整报文*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(const int data_x, const int data_y, const char oper): _data_x(data_x), _data_y(data_y), _oper(oper){}// 序列化bool Serialize(std::string *out){*out = std::to_string(_data_x) + ProSep + _oper + ProSep + std::to_string(_data_y);return true;}// 反序列化bool DeSerialize(std::string &in){auto left = in.find(ProSep);if (left == std::string::npos)return false;auto right = in.rfind(ProSep);if (right == std::string::npos)return false;_data_x = std::stoi(in.substr(0, left));_data_y = std::stoi(in.substr(right + ProSep.size()));std::string oper = in.substr(left + ProSep.size(), right - (left + ProSep.size()));if (oper.size() != 1)return false;_oper = oper[0];return true;}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){*out = std::to_string(_result) + ProSep + std::to_string(_code);return true;}// 反序列化bool Deserialize(std::string &in){auto pos = in.find(ProSep);if (pos = std::string::npos)return false;_result = std::stoi(in.substr(pos + ProSep.size()));_code = std::stoi(in.substr(pos + ProSep.size()));return true;}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> BuildRequest(){std::shared_ptr<Request> req = std::make_shared<Request>();return req;}std::shared_ptr<Request> BuildRequest(int x, int y, char oper){std::shared_ptr<Request> req = std::make_shared<Request>(x, y, oper);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;}};
}
1、代码整体功能与用途:实现了一个用于网络通信的消息协议相关功能,主要用于将数据编码为自定义的协议格式进行传输,以及在接收端对消息进行解码还原数据,同时对请求和响应数据进行了封装,方便在网络应用程序中进行数据交互,提高了数据传输的结构化和规范化程度,便于开发和维护。
2、设计思路与结构解析
常量定义 :ProSep
和 LineBreakSep
定义了协议中用于分隔数据和消息的特殊字符串,分别作为消息内部数据的分隔符和消息整体的结束标志,使得消息在传输过程中具有明确的结构,方便发送端编码和接收端解码。
消息编码与解码 :
Encode
函数 :将传入的消息按照协议格式进行编码,先将消息长度转换为字符串,然后依次添加消息长度、换行符、消息内容和换行符,形成一个完整的协议包。这种设计可以确保接收端能够准确地识别和解析消息的长度和内容,即使消息内容中包含换行符等特殊字符,也能保证数据的完整性。Decode
函数 :对编码后的消息进行解码,首先查找消息中第一个换行符的位置,获取消息长度部分,然后根据消息长度计算整个消息的总长度,最后提取消息内容并更新传入的消息字符串,以便处理后续消息。这种逐步解析的方式可以有效处理粘包等情况,确保每个完整消息都能被正确提取。
请求和响应类 :
Request
类 :用于封装客户端发送的请求数据,包含两个操作数和一个操作符。Serialize
方法将这三个数据项按照协议的分隔符拼接成字符串,DeSerialize
方法则从字符串中提取数据并还原为对象的内部状态。这种封装方式使得请求数据在网络传输过程中具有统一的格式,便于发送和接收。Response
类 :用于封装服务器返回的响应数据,包含运算结果和运行状态代码。同样提供了序列化和反序列化方法,方便在网络通信中传输数据,并且可以根据需要设置和获取结果及状态码。
工厂类 Factory
:提供了创建 Request
和 Response
对象的接口,使用智能指针 std::shared_ptr
来管理对象的生命周期,方便在程序中动态创建和传递请求和响应对象,避免了手动管理内存的繁琐和风险。
(3)实现了一个简单但功能相对完整的计算器类,通过封装运算逻辑、处理错误状态以及与协议类的配合使用,提供了一个易于理解和使用的基础框架
#include <iostream>
#include <memory>
#include "Protocol.hpp"namespace CalculateNS
{enum Code{Success = 0,DivZeroErr,ModZeroErr,UnKnowOper};class Calculate{public:Calculate() {}~Calculate() {}std::shared_ptr<Protocol::Response> Cal(std::shared_ptr<Protocol::Request> req){std::shared_ptr<Protocol::Response> resp = factory.BuildResponse();resp->SetCode(Code::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(Code::ModZeroErr);}else{resp->SetResult(req->GetX() / req->GetY());}break;}case '%':{if (req->GetY() == 0){resp->SetCode(Code::DivZeroErr);}else{resp->SetResult(req->GetX() % req->GetY());}break;}}return resp;}private:Protocol::Factory factory;};
}
(4)实现了一个多线程的 TCP 服务器。它的主要用途是监听指定端口,接受客户端的连接请求,并为每个客户端连接创建一个线程来处理请求和响应。
#pragma once
#include <iostream>
#include <pthread.h>
#include <functional>
#include <string>
#include "Socket.hpp"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;Net_Work::Socket *_sockp;
};class TcpServer
{
public:TcpServer(uint16_t port, func_t handler_request): _port(port), _listensocket(new Net_Work::TcpSocket()), _handler_request(handler_request){_listensocket->BuildListenSocketMethod(_port, Net_Work::backlog);}~TcpServer(){delete _listensocket;}static void *ThraedRun(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);if (ok){// 发送数据-不关心数据是什么,只发送if (!send_string.empty())td->_sockp->Send(send_string);}elsebreak;}td->_sockp->CloseSocket();delete td->_sockp;delete td;return nullptr;}void Loop(){while (true){std::string peerip;uint16_t peerport;Net_Work::Socket *newsock = _listensocket->AcceptConnection(&peerip, &peerport);if (newsock == nullptr)continue;std::cout << "获取一个新连接, sockfd: " << newsock->GetSockFd()<< " client info: " << peerip << ":" << peerport << std::endl;pthread_t tid;ThreadData* td=new ThreadData(this,newsock);pthread_create(&tid,nullptr,ThraedRun,td);}}private:uint16_t _port;Net_Work::Socket *_listensocket;public:func_t _handler_request;
};
设计思路与结构解析
- 高效地处理并发请求 :通过为每个客户端连接创建一个线程,服务器可以同时处理多个客户端的请求。在一个线程中处理一个客户端的请求时,不会阻塞其他客户端的请求处理,从而提高了服务器的吞吐量和并发处理能力。
- 灵活的请求处理机制 :使用回调函数
_handler_request
,允许用户根据自己的需求自定义请求处理逻辑。这样可以方便地扩展服务器的功能,例如可以轻松地添加对不同协议的支持、数据处理方式、业务逻辑等。
ThreadData
类:
这个类用于在线程之间传递数据。_this
指向创建该线程的 TcpServer
对象实例,使得线程可以访问 TcpServer
的成员函数和数据。 _sockp
指向与客户端通信的套接字对象,用于在该线程中处理客户端的请求和响应。
静态成员函数 ThraedRun(void *args)
:
- 这是线程的入口函数。它接收一个指向
ThreadData
类型的指针args
作为参数。 - 首先调用
pthread_detach
将当前线程与主线程分离,这样主线程不需要等待该线程结束。 - 然后从
args
中提取出ThreadData
对象指针td
。
进入一个无限循环,用于处理客户端的请求。在循环中:
- 读取客户端发送的数据,调用
td->_sockp->Recv
方法,将接收到的数据存储在inbufferstream
中。如果不成功,跳出循环。 - 调用回调函数
_handler_request
对接收到的数据进行处理,生成响应数据send_string
,并判断处理是否成功(通过ok
)。 - 如果处理成功且有响应数据,调用
td->_sockp->Send
方法将响应数据发送回客户端。 - 如果处理失败,跳出循环。
- 当循环结束时,关闭套接字
td->_sockp
,释放套接字对象和ThreadData
对象所占用的内存。
(5)实现了一个基于 TCP 的服务器,用于处理客户端发送的请求,并根据请求内容进行计算后返回响应
#include"Protocol.hpp"
#include"TcpServer.hpp"
#include"Calculate.hpp"
#include<iostream>
#include<memory>
#include<unistd.h>std::string HandlerRequest(std::string& inbufferstream,bool* error_code)
{*error_code=true;//构造函数CalculateNS::Calculate calculate;//构建请求客户端对象std::unique_ptr<Protocol::Factory> factory=std::make_unique<Protocol::Factory>();auto req=factory->BuildRequest();//分析字节流是否存在一个完整报文std::string total_resp_string;std::string message;while(Protocol::Decode(inbufferstream,&message)){if(!req->DeSerialize(message)){//反序列化失败*error_code=false;return std::string();}//反序列化成功,进行业务处理auto resp=calculate.Cal(req);//序列号回应std::string send_string;resp->Serialize(&send_string);//构建完成字符串级别的响应报文send_string=Protocol::Encode(send_string);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;
}
请求处理逻辑:
- 接收客户端发送的字节流数据,解析出完整的请求报文。
- 对请求报文进行反序列化,提取请求内容。
- 调用
Calculate
类进行业务逻辑处理(如计算等)。 - 将处理结果序列化并封装成响应报文,发送回客户端。
(6)实现了一个基于 TCP 的客户端,用于向服务器发送数学运算请求,并接收服务器返回的计算结果
#include "Protocol.hpp"
#include "Socket.hpp"
#include <iostream>
#include <string>
#include <ctime>
#include <cstdlib>
#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]);Net_Work::Socket *conn = new Net_Work::TcpSocket();if (!conn->BuildConnectSocketMethod(serverip, serverport)){std::cerr << "connect " << serverip << ":" << serverport << " failed" << std::endl;return 0;}std::cout << "connect " << serverip << ":" << serverport << " success" << std::endl;std::unique_ptr<Protocol::Factory> factory = std::make_unique<Protocol::Factory>();srand(time(nullptr) ^ getpid());const std::string opers = "+-*/%";while (true){// 构建一个请求int x = rand() % 100;usleep(rand() % 7777);int y = rand() % 100;char oper = opers[rand() % opers.size()];std::shared_ptr<Protocol::Request> req = factory->BuildRequest(x, y, oper);// 对请求进行序列化std::string request_str;req->Serialize(&request_str);std::string testreq = request_str;testreq += " ";testreq += "=";// 添加自描述报头request_str = Protocol::Encode(request_str);// 发送请求conn->Send(request_str);// 接收回响std::string response_ptr;while (true){// 读取回响if (!conn->Recv(&response_ptr, 1024))break;// 报文解析std::string response;if (!Protocol::Decode(response_ptr, &response))continue;// 反序列化auto resp = factory->BuildResponse();resp->Deserialize(response);// 8. 得到了计算结果,而且是一个结构化的数据std::cout << testreq << resp->GetResult() << "[" << resp->GetCode() << "]" << std::endl;break;}}conn->CloseSocket();return 0;
}