当前位置: 首页 > news >正文

Linux网络——应用层序列化反序列化

一、协议

1.1应用层&序列化和反序列化

之前实现的TCPservercommon中对于client端的接收直接使用recv是有问题的,tcp是面向字节流的,如果传的命令ls -a -l它不能保证client传的这行命令一定是完整的,也许它只传了一半而另一半在下一行呢?

就像文件读取一样,对于向文件中写入不同的数据类型,我们很轻易就能做到,但是如果将文件中的数据类型准确的读取出来就十分困难了

要想解决这个问题,首先要对协议再一步了解。

之前我们提到协议是一种"约定",计算机与计算机之间的约定,通过这种约定来减少计算机的通信成本。

socket api接口我们传输的都是字符串,如果我们要传一些结构化数据,怎么办呢?

其实协议就是是双方约定好的结构化数据,C语言是结构体,C++是类。

通过实现一个服务器版本的加法器来讲解,客户端要把两个数据发过来,server计算后,把结果返回去。

方案一:

客户端发送一个形如"1+1"的字符串

有两个操作数都是整型

两个数字之间有一个字符是运算符且只能是+

数字和运算符之间没有空格

UDP这个方案是可行的,但是如果是TCP你(server端)怎么确定收到的是1+1,而不是1+10,1+100,1+1000...后面的0可能还没有收到和之前tcpserver的recv一样

方案二:序列化和反序列化

定义结构体来表示我们需要交互的信息

发送数据时,将这个结构体按照一个规则转换成字符串,接收到数据的时候再将字符串按照相同的规则把字符串转化为结构体

以上过程叫序列化和反序列化

无论是采用方案一还是方案二都是可以的,

只要保证,一端发送时构造的数据,
在另一端能够正确的进行解析,就是ok.这种约定,就是应用层协议

2.2readwriterecvsend tcp 为什么支持全双工

当前进程调write所以当前进程pcb能够找到,找到后通过pcb找fd文件描述符表,通过struct file将用户层内容拷贝到缓冲区(文件有自己的内核级缓冲区),缓冲区再刷新进硬盘的文件中

Linux下一切皆文件,网络的设计中,网络套接字本质也是文件,也有类似的原理,只不过目标不是磁盘外设,而是目标主机或者干脆是网络

实际上write或send其实压根没有经过网络,只是将buffer(用户层缓冲区)的内容拷贝到发送缓冲区,而read和recv只是将传输层缓冲区的内容拷贝到用户层缓冲区。这些函数本质是拷贝函数,拷到缓冲区后等待OS去发送。

至于什么时候发?

怎么发?

出错时怎么办?完全由TCP协议(传输层)自己决定——所以Tcp叫做传输控制协议 

read、recv会阻塞的原因是缓冲区中没有数据

阻塞的本质是用户层在同步

网络通信本质是两台主机双方的操作系统在通信

所以Tcp发送数据的本质——将自己发送缓冲区的内容拷贝到接收方的接受缓冲区

所以通信的本质就是拷贝

主机A发消息给主机B的同时,主机B也可以给主机A发消息;他们的发送缓冲区和接受缓冲区互不干扰——这也是Tco支持全双工的根本原因

这也是一个fd(文件描述符)既可以读又可以写的原因

tcp不是面向字节流的吗?

之前说的recv有bug的原因:实际上主机A发的数据就一定是主机B收的数据吗?换句话说,主机B能不能保证自己收到的数据一定是完整的呢?不能吧,万一主机B的接受缓冲区就剩一点了呢。毕竟write只是将数据拷贝到缓冲区,而没有权利去发送,发送时间由tcp自己决定。

在tcp眼中只看到了一串字节流,并不关心发送的是什么

二、网络版本计算器

2.1封装套接字

将TcpServer.hpp中的InetServer函数的创建套接字、绑定套接字、监听套接字封装到Socket中

套接字还要实现accept和connect

先整个socket类

namespace socket_ns
{class Socket;const static int gbacklog = 8;using socket_ptr = std::shared_ptr<Socket>;//智能指针enum{SOCKET_ERROR=1,BIND_ERROR,USAGE_ERROR,LISTEN_ERROR};class Socket{public:virtual void CreateSocketOrDie()=0; virtual void BindSocketOrDie(InetAddr &addr)=0;virtual void ListenSocketOrDie()=0;virtual socket_ptr Accepter(InetAddr *addr)=0;//accept接受的也是一个socket对象virtual bool Connector(InetAddr &addr)=0;public:void BuildListenSocket(InetAddr &addr){CreateSocketOrDie();BindSocketOrDie(addr);ListenSocketOrDie();}bool BulidClientSocket(InetAddr &addr){CreateSocketOrDie();return Connector(addr);}};}

在整个tcpsocket继承socket,套接字创建、绑定、监听...都在TcpSocket定义

class TcpSocket : public Socket{public:TcpSocket(int sockfd):_sockfd(sockfd){}private:int _sockfd;};

创建

        void CreateSocketOrDie() override{//创建套接字_sockfd = socket(AF_INET,SOCK_STREAM,0);if(_sockfd<0)//创建失败{LOG(FATAL,"socket error\n");exit(SOCKET_ERROR);}LOG(DEBUG,"socket create success,sockfd is: %d\n",_sockfd);}

绑定

        void BindSocketOrDie(InetAddr &addr) override{struct sockaddr_in local;bzero(&local,sizeof(local));local.sin_family = AF_INET;//网络通讯方式为ipv4local.sin_port = htons(addr.Port());//主机序列转网络序列local.sin_addr.s_addr=inet_addr(addr.Ip().c_str());//0.0.0.0,绑定所有网络接口,无论client通过哪个ip进来都可以访问//bindint n = bind(_sockfd,(struct sockaddr *)&local,sizeof(local));if(n<0){LOG(FATAL,"bind error\n");exit(BIND_ERROR);}LOG(DEBUG,"bind success,sockfd is: %d\n",_sockfd);}

监听

        void ListenSocketOrDie(){int n = ::listen(_sockfd,gbacklog);if(n<0){//监听失败报错退出LOG(FATAL,"listen error\n");exit(LISTEN_ERROR);}LOG(DEBUG, "listen success,sockfd is : %d\n", _sockfd);}

tcp继承了socket的,accept最终返回的是对应的基类的指针

    using socket_ptr = std::shared_ptr<Socket>;//智能指针

这个指针它自己不能定义对象,但是它可以指向具体的socket,所以此时我们一旦获取了新的套接字我们就可以直接返回这个share_ptr 的TcpSocket

 为什么要返回一个新的套接字呢?未来增加接口(如:recv、send可以直接->调用)

        socket_ptr Accepter(InetAddr *addr){struct sockaddr_in peer;socklen_t len = sizeof(peer); int sockfd =::accept(_sockfd,(struct sockaddr*)&peer,&len);if(sockfd<0){LOG(WARNING,"accept error\n");return nullptr;}*addr = peer;socket_ptr sock = std::make_shared<TcpSocket>(sockfd);return sock;}

连接

        bool Connector(InetAddr &addr){struct sockaddr_in server;// 构建目标主机的socket信息memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(addr.Port());server.sin_addr.s_addr = inet_addr(addr.Ip().c_str());int n = connect(_sockfd, (struct sockaddr *)&server, sizeof(server));if (n < 0){std::cerr << "connect error" << std::endl;return false;}return true;}

Socket.hpp

#include <iostream>
#include <cstring>
#include <functional>
#include <strings.h>
#include <unistd.h>
#include <sys/wait.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "InetAddr.hpp"
#include "LOG.hpp"
namespace socket_ns
{class Socket;const static int gbacklog = 8;using socket_ptr = std::shared_ptr<Socket>; // 智能指针enum{SOCKET_ERROR = 1,BIND_ERROR,USAGE_ERROR,LISTEN_ERROR};class Socket{public:virtual void CreateSocketOrDie() = 0;virtual void BindSocketOrDie(InetAddr &addr) = 0;virtual void ListenSocketOrDie() = 0;virtual socket_ptr Accepter(InetAddr *addr) = 0; // accept接受的也是一个socket对象virtual bool Connector(InetAddr &addr) = 0;virtual int SockFd() = 0;virtual int Recv(std::string *out) = 0;virtual int Send(const std::string &in) = 0;public:void BuildListenSocket(InetAddr &addr){CreateSocketOrDie();BindSocketOrDie(addr);ListenSocketOrDie();}bool BulidClientSocket(InetAddr &addr){CreateSocketOrDie();return Connector(addr);}};class TcpSocket : public Socket{public:TcpSocket(int sockfd = -1) : _sockfd(sockfd){}void CreateSocketOrDie() override{// 创建套接字_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0) // 创建失败{LOG(FATAL, "socket error\n");exit(SOCKET_ERROR);}LOG(DEBUG, "socket create success,sockfd is: %d\n", _sockfd);}// bind需要自己的网络地址void BindSocketOrDie(InetAddr &addr) override{struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;          // 网络通讯方式为ipv4local.sin_port = htons(addr.Port()); // 主机序列转网络序列local.sin_addr.s_addr = inet_addr(addr.Ip().c_str());// 0.0.0.0,绑定所有网络接口,无论client通过哪个ip进来都可以访问// bindint n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(FATAL, "bind error\n");exit(BIND_ERROR);}LOG(DEBUG, "bind success,sockfd is: %d\n", _sockfd);}void ListenSocketOrDie() override{int n = ::listen(_sockfd, gbacklog);if (n < 0){// 监听失败报错退出LOG(FATAL, "listen error\n");exit(LISTEN_ERROR);}LOG(DEBUG, "listen success,sockfd is : %d\n", _sockfd);}// 为什么要返回一个新的套接字呢?未来增加接口(如:recv、send可以直接->调用)socket_ptr Accepter(InetAddr *addr) override{struct sockaddr_in peer;socklen_t len = sizeof(peer);int sockfd = ::accept(_sockfd, (struct sockaddr *)&peer, &len);if (sockfd < 0){LOG(WARNING, "accept error\n");return nullptr;}*addr = peer;socket_ptr sock = std::make_shared<TcpSocket>(sockfd);return sock;}bool Connector(InetAddr &addr) override{struct sockaddr_in server;// 构建目标主机的socket信息memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(addr.Port());server.sin_addr.s_addr = inet_addr(addr.Ip().c_str());int n = connect(_sockfd, (struct sockaddr *)&server, sizeof(server));if (n < 0){std::cerr << "connect error" << std::endl;return false;}return true;}int Recv(std::string *out) override{char inbuffer[1024];ssize_t n = recv(_sockfd, inbuffer, sizeof(inbuffer) - 1,0);if (n > 0){inbuffer[n] = 0;*out += inbuffer;}return n;}int Send(const std::string &in) override{int n = send(_sockfd,in.c_str(),in.size(),0);return n;}int SockFd() override{return _sockfd;}private:int _sockfd;};
}

对TcpServer.hpp进行修改

#pragma once
#include<iostream>
#include<string>
#include<functional>
#include<strings.h>
#include<unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <arpa/inet.h>
#include"LOG.hpp"
#include"InetAddr.hpp"
#include"Socket.hpp"using namespace socket_ns;
class TcpServer;
using io_service_t = std::function<void (socket_ptr sockfd,InetAddr client)>;class ThreadData
{
public:ThreadData(socket_ptr fd,InetAddr addr,TcpServer *s):sockfd(fd),clientaddr(addr),self(s){}
public:socket_ptr sockfd;InetAddr clientaddr;TcpServer *self;
};
class TcpServer
{
public://构造TcpServer(int port,io_service_t service):_localaddr("0",port),_service(service),_listensock(std::make_unique<TcpSocket>()),_isrunning(false){//直接套接字构建绑定监听_listensock->BuildListenSocket(_localaddr);}static void *HandlerSock(void *args){pthread_detach(pthread_self());ThreadData *td = static_cast<ThreadData *>(args);td->self->_service(td->sockfd, td->clientaddr);::close(td->sockfd->SockFd()); delete td;return nullptr;}void Loop(){_isrunning = true;while(_isrunning){InetAddr peeraddr;socket_ptr normalsock =_listensock->Accepter(&peeraddr);if(normalsock == nullptr)   continue;pthread_t t;ThreadData *td = new ThreadData(normalsock, peeraddr, this);pthread_create(&t, nullptr, HandlerSock, td);}_isrunning = false;}//析构~TcpServer(){}
private:bool _isrunning;std::unique_ptr<Socket> _listensock;InetAddr _localaddr;io_service_t _service;
};

2.2序列化和反序列化实现

创建Protocal.hpp,对数据进行序列化和反序列化,我们协议的样子:报文=报头+有效载荷

"有效载荷的长度"\r\n"有效载荷"\r\n

也就是"len"\r\n"_x _oper _y"\r\n  ->  len:有效载荷的长度,\r\n是分隔符,不参与统计

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
namespace protocol_ns
{class Request
{
public:Request(int x,int y,char oper):_x(x),_y(y),_oper(oper){}bool Serialize(std:: string *out){}bool DeSerialize(const std::string &in){}~Request(){}
private:int _x;int _y;char _oper;};
class Response
{
public:Response(){}bool Serialize(std:: string *out){}bool DeSerialize(const std::string &in){}~Response(){}
private:int _code;int _result;
};
}

序列化反序列化可以调用Jsoncpp库来实现

简单介绍一下Jsoncpp

Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。它提供了将 JSON 数据序列化为字 符串以及从字符串反序列化为 C++ 数据结构的功能。Jsoncpp 是开源的,广泛用于各 种需要处理 JSON 数据的 C++ 项目中。

特性

1. 简单易用:Jsoncpp 提供了直观的 API,使得处理 JSON 数据变得简单。

2. 高性能:Jsoncpp 的性能经过优化,能够高效地处理大量 JSON 数据。

3. 全面支持:支持 JSON 标准中的所有数据类型,包括对象、数组、字符串、数 字、布尔值和 null。

4. 错误处理:在解析 JSON 数据时,Jsoncpp 提供了详细的错误信息和位置,方便 开发者调试。

安装

ubuntu:sudo apt-get install libjsoncpp-dev

Centos: sudo yum install jsoncpp-devel

json的使用,可以写入文件,也可以打印出来

#include <iostream>
#include <string>
#include <fstream>
#include <jsoncpp/json/json.h>struct stu
{std::string name;int age;double weight;public:void debug(){std::cout << name << std::endl;std::cout << age << std::endl;std::cout << weight << std::endl;}
};int main()
{// 结构化数据struct stu zs = {"张三", 18, 70};// 转换成为字符串Json::Value root;root["name"] = zs.name;root["age"] = zs.age;root["weight"] = zs.weight;// root["self"] = root;// Json::FastWriter writer;Json::StyledWriter writer;std::string str = writer.write(root);std::ofstream out("out.txt");if(!out.is_open()){std::cout << str;return 1;}out << str;out.close();// std::ifstream in("out.txt");// if(!in.is_open()) return 1;// char buffer[1024];// in.read(buffer, sizeof(buffer));// in.close();// std::string json_string = buffer;// Json::Value root;// Json::Reader reader;// bool res = reader.parse(json_string, root);// (void)res;// struct stu zs;// zs.name = root["name"].asString();// zs.age = root["age"].asInt();// zs.weight =root["weight"].asDouble();// zs.debug();return 0;
}

实现Serialize和Deserialize函数

request的

    class Request
{
public:Request(int x,int y,char oper):_x(x),_y(y),_oper(oper){}bool Serialize(std:: string *out){Json::Value root;root["x"] = _x;root["y"] = _y;root["oper"] = _oper;Json::FastWriter writer;*out = writer.write(root);return true;}bool DeSerialize(const std::string &in){Json::Value root;Json::Reader reader;bool res = reader.parse(in,root);if(!res)return false;_x = root["x"].asInt();_y = root["y"].asInt();_oper = root["oper"].asInt();return res;}~Request(){}
private:int _x;int _y;char _oper;};

response的

class Response
{
public:Response(){}bool Serialize(std:: string *out){Json::Value root;root["code"] = _code;root["result"] = _result;Json::FastWriter writer;*out = writer.write(root);return true;}bool DeSerialize(const std::string &in){Json::Value root;Json::Reader reader;bool res = reader.parse(in,root);if(!res)return false;_code = root["code"].asInt();_result = root["result"].asInt();return res;}~Response(){}
private:int _code;int _result;
};

反序列化中传到的in怎么保证读到的是一个完整的请求呢?

我们并不清楚读到的是不是一个完整的请求,所以Service(现在写在TcpServermain.cc中)要改一下跟协议连接起来

在socket.hpp增加Recv函数

        int Recv(std::string *out) override{char inbuffer[1024];ssize_t n = recv(_sockfd, inbuffer, sizeof(inbuffer) - 1,0);if (n > 0){inbuffer[n] = 0;*out += inbuffer;}return n;}

为什么*out+=inbuffer?待会再说

在TcpServermain.cc修改recv

using namespace protocol_ns;
void Service(socket_ptr sockptr, InetAddr client)
{int sockfd = sockptr->SockFd(); LOG(DEBUG, "get a new link, info %s:%d, fd : %d\n", client.Ip().c_str(), client.Port(), sockfd);std::string clientaddr = "[" + client.Ip() + ":" + std::to_string(client.Port()) + "]# ";std::string inbuffer;while (true){Request req;int n = sockptr->Recv(&inbuffer);if(n>0){//读取成功req.DeSerialize(inbuffer);}else{LOG(DEBUG,"client %s quit\n",clientaddr.c_str());break;}}
}

现在依旧保证不了读到的是个完整的request,我只不过是把Recv封装了一下

如果它没有读到一个完整的请求就一直循环然它读取,Recv中+=就继续把字符串拼接到out的buffer里,inbuffer的内容本质是接受缓冲区中的一份拷贝。+=的作用就是拼接未完整的请求。

所以怎么保证呢?传的不止只是json串还要传长度

"len"\r\n"{ ...... }"\r\n,先读取到长度,再看这个类到底满不满足长度要求

protocol.hpp中

namespace protocol_ns
{const std::string SEP = "\r\n";//json串变成"len"\r\n"{ ...... }"\r\nstd::string Encode(const std::string &json_str){int json_str_len = json_str.size();std::string proto_str = std::to_string(json_str_len);proto_str += SEP;proto_str += json_str;proto_str += SEP;return proto_str;}//看inbuffer中有没有完整的请求std::string Decode(std::string &inbuffer){auto pos = inbuffer.find(SEP);//找不到说明报文一定不完整if(pos == std::string::npos)return std::string();//返回空串//(0~/r之前)std::string len_str = inbuffer.substr(0,pos);if(len_str.empty())return std::string();int pagelen = std::stoi(len_str);//总长度=json串的长度+json串长度的长度+\r\n的长度int total = pagelen+len_str.size()+2*SEP.size();//如果inbuffer的长度>=total说明一定有一个完整请求if(inbuffer.size()<total)   return std::string();//返回json串  len\r\n{         }\r\n 从\n后面度串的长度std::string package = inbuffer.substr(pos+SEP.size(),pagelen);inbuffer.erase(total);//移除数据return package;}
...

protocol.hpp全部代码

#pragma once
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
namespace protocol_ns
{const std::string SEP = "\r\n";//json串变成"len"\r\n"{ ...... }"\r\nstd::string Encode(const std::string &json_str){int json_str_len = json_str.size();std::string proto_str = std::to_string(json_str_len);proto_str += SEP;proto_str += json_str;proto_str += SEP;return proto_str;}//看inbuffer中有没有完整的请求std::string Decode(std::string &inbuffer){auto pos = inbuffer.find(SEP);//找不到说明报文一定不完整if(pos == std::string::npos)//返回空串return std::string();//(0~/r之前)std::string len_str = inbuffer.substr(0,pos);if(len_str.empty())return std::string();int pagelen = std::stoi(len_str);//总长度=json串的长度+json串长度的长度+\r\n的长度int total = pagelen + len_str.size() + 2 * SEP.size();//如果inbuffer的长度>=total说明一定有一个完整请求if(inbuffer.size() < total)   return std::string();//返回json串  len\r\n{         }\r\n 从\n后面度串的长度std::string package = inbuffer.substr(pos + SEP.size(),pagelen);inbuffer.erase(total);return package;}
class Request
{
public:Request(){}Request(int x,int y,char oper):_x(x),_y(y),_oper(oper){}bool Serialize(std:: string *out){Json::Value root;root["x"] = _x;root["y"] = _y;root["oper"] = _oper;Json::FastWriter writer;*out = writer.write(root);return true;}bool DeSerialize(const std::string &in){Json::Value root;Json::Reader reader;bool res = reader.parse(in,root);if(!res)return false;_x = root["x"].asInt();_y = root["y"].asInt();_oper = root["oper"].asInt();return res;}~Request(){}
public:int _x;int _y;char _oper;};
class Response
{
public:Response(){}Response(int result,int code):_result(result),_code(code){}bool Serialize(std:: string *out){Json::Value root;root["code"] = _code;root["result"] = _result;Json::FastWriter writer;*out = writer.write(root);return true;}bool DeSerialize(const std::string &in){Json::Value root;Json::Reader reader;bool res = reader.parse(in,root);if(!res)return false;_code = root["code"].asInt();_result = root["result"].asInt();return res;}~Response(){}
public:int _result;int _code;
};
}

2.3关于服务函数的处理

Service

void Service(socket_ptr sockptr, InetAddr client)
{int sockfd = sockptr->SockFd();LOG(DEBUG, "get a new link, info %s:%d, fd : %d\n", client.Ip().c_str(), client.Port(), sockfd);std::string clientaddr = "[" + client.Ip() + ":" + std::to_string(client.Port()) + "]# ";std::string inbuffer;while (true){Request req;//读取数据int n = sockptr->Recv(&inbuffer);if (n < 0){LOG(DEBUG, "client %s quit\n", clientaddr.c_str());break;}//分析数据是否完整std::string package = Decode(inbuffer);if (package.empty())continue;// 一定获得了完整json串//反序列化req.DeSerialize(package);//业务处理Response resp = _cb(req);//处理完发回去(响应)之前要序列化std::string send_str;resp.Serialize(&send_str);//保证响应完整(添加报头)send_str = Encode(send_str);sockptr->Send(send_str);}
}

创建Calculate,hpp用来提供计算器服务,Excute 收到Request类型,返回Response

这里我能直接访问req._x或者是resp._result是我把Response和Request的成员变量改为pubilc了

#pragma once
#include<iostream>
#include"Protocol.hpp"
using namespace protocol_ns;
class Calculate
{
public:Calculate(){}Response Excute(const Request &req){Response resp(0,0);switch (req._oper){case '+':resp._result = req._x + req._y;break;case '-':resp._result = req._x - req._y;break;case '*':resp._result = req._x * req._y;break;case '/':if(req._y==0){resp._code=1;}else{resp._result = req._x / req._y;}break;case '%':if(req._y == 0){resp._code = 2;}else{resp._result = req._x % req._y;}break;default:resp._code = 3;break;}return resp;}~Calculate(){}
private:
};

TcpMain.cc中将service改为ServiceHelper写入类中

using namespace protocol_ns;
using callback_t = std::function<Response(const Request &req)>;void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << "local_port\n"<< std::endl;
}class Service
{
public:Service(callback_t cb):_cb(cb){}void ServiceHelper(socket_ptr sockptr, InetAddr client){int sockfd = sockptr->SockFd();LOG(DEBUG, "get a new link, info %s:%d, fd : %d\n", client.Ip().c_str(), client.Port(), sockfd);std::string clientaddr = "[" + client.Ip() + ":" + std::to_string(client.Port()) + "] ";std::string inbuffer;while (true){Request req;//读取数据int n = sockptr->Recv(&inbuffer);if (n < 0){LOG(DEBUG, "client %s quit\n", clientaddr.c_str());break;}//分析数据是否完整std::string package = Decode(inbuffer);if (package.empty())continue;// 一定获得了完整json串//反序列化req.DeSerialize(package);//业务处理Response resp = _cb(req);//处理完发回去(响应)之前要序列化std::string send_str;resp.Serialize(&send_str);//保证响应完整(添加报头)send_str = Encode(send_str);sockptr->Send(send_str);}}
private:callback_t _cb;
};int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);return 1;}EnableScreen();uint16_t port = std::stoi(argv[1]);Calculate cal;Service calservice(std::bind(&Calculate::Excute,&cal,std::placeholders::_1));io_service_t service = std::bind(&Service::ServiceHelper,&calservice,std::placeholders::_1,std::placeholders::_2);std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port, service);tsvr->Loop();return 0;
}

创建cal对象将计算器类的Excute函数和cal绑定,绑定后的函数对象作为参数传递给Service的构造函数。
通过bind将Service类的ServiceHelper成员函数与calservice对象绑定

大概逻辑是这样的

TcpServer接收连接 → _service(sock, client) → ServiceHelper(sock, client) 
→ 读取并解析数据 → _cb(req) → Calculate::Excute(req) 

如果出现以下报错

那么可能是你的makefile为连接jsoncpp库

运行一下

2.4client端

没啥问题,下面我们完成客户端的代码

#include <iostream>
#include<string>
#include<memory>
#include"Protocol.hpp"
#include"Socket.hpp"
#include"InetAddr.hpp"
using namespace socket_ns;
using namespace protocol_ns;void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " serverip serverport\n"<< std::endl;
}
int main(int argc,char *argv[])
{if(argc!=3){Usage(argv[0]);exit(1);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);std::unique_ptr<Socket> client = std::make_unique<TcpSocket>();InetAddr serveraddr(serverip,serverport);bool res = client->BulidClientSocket(serveraddr);if(res){}return 0;
}

可以写一个简单的工厂来为客户端不断发送request(在namespace protocol_ns中实现)

class Factory
{
public:Factory(){srand(time(nullptr) ^ getpid());opers = "+-*/%";}//通过随机数来获取数字和运算符std::shared_ptr<Request> BulidRequest(){int x = rand()%10+1;usleep(20*x);int y = rand()%5;usleep(10*y);char oper = opers[rand()%opers.size()];std::shared_ptr<Request> req = std::make_shared<Request>(x,y,oper);return req;}std::shared_ptr<Response> BulidResponse(){return std::make_shared<Response>();}~Factory(){}
private:std::string opers;
};

测试一下,正常运行

http://www.dtcms.com/a/520680.html

相关文章:

  • EWCCTF2025 Tacticool Bin wp
  • 【Trae+AI】和Trae学习搭建App_01(附加可略过):测试Trae的后端功能
  • 网站源码 下载查域名价格
  • 上海做网站联系电话山东兴华建设集团有限公司网站
  • 使用 Vue3 和 Element Plus 实现选择新增用户集下拉选项框,切换类型,有物业,网格,电子围栏,行政区划管理
  • Vue项目页面间,页面中跳转及刷新规划,何时使用router-view,router-link,iframe,slots ,使用场景,及对应场景的完整使用示例
  • 【Qt】VS Code配置Qt UI插件,vscode打开Qt Designer ,vscode打开Qt*.ui文件
  • 服务网站建设的公司安装网站系统
  • 直播做网站数字广东网络建设有限公司介绍
  • 宇树科技:决定更名
  • 2025年MathorCup 大数据竞赛明日开赛,注意事项!论文提交规范、模板、承诺书正确使用!2025年第六届MathorCup数学应用挑战赛——大数据竞赛
  • 【案例实战】鸿蒙智能日程应用性能优化实战:从卡顿到丝滑的完整历程
  • 创建网站商城电子商务企业网站建设前期规划方案
  • php租车网站源码营销型网站规划
  • Universal Extractors (万能解压器) 支持500+格式
  • 网站策划岗位要求wordpress htaccess文件
  • Google Play多区测试与真机复现:用纯净IP重现真实流量(含技术方案)
  • Lombok是什么?
  • 淘客网站做单品类wordpress词汇插件
  • 内网穿透的应用-从崩溃到流畅!Web-Check+cpolar的站点优化实战
  • opencv模版匹配
  • Cython 出现‘Failed to Map Segment from Shared Object‘错误
  • 公司做网站要多久网站建设需要到哪些知识
  • 网站制作模板图片html5 爱情网站模板
  • YARP 全面详解
  • 唐山网站建设汉狮怎么样需要自己的网站需要怎么做
  • Flutter:启动动画Lottie
  • C#模拟鼠标键盘操作的多种实现方案
  • 中国热门网站wordpress中英双语选择
  • DDD(三)领域模型关键词解释、领域模型分类、关系图