序列化与反序列化:
并且重新理解了TCP协议:
TCP协议
最重要的是将Socket进行了程序重构,具体的细节在TCP协议中讲解过。这样将通信功能彻底解耦出来:
对应网络计算器的需求,我们设计了Request和Response两个结构体作为通信的协议!并且我们通过JSON库来进行协议内部的序列化与反序列化!为了保证可以获取完整的结构化数据,我们设计了独特的报文结构:
len\r\n{json}\r\n
这样可以保证从数据流中获取完整的报文结构!!!
服务器的框架是基于这样的三层结构实现的:
TcpServer
:负责从Socket文件中获取链接,传输层不需要进行IO,获取到连接就让会话层通过连接获取数据!Service
:根据传输层给的连接,从Sockfd文件中读取数据,解析出报文结构中的数据字符串,然后通过协议分离出结构化数据。该层只负责数据的解析,数据的处理交给应用层进行!Process
:应用层是具有的业务逻辑,根据会话层解析出的数据,进行数据处理!这里使用的是网络计算器的业务逻辑,也就是执行加减乘除运算!基于这样的结构我们上层的服务器代码逻辑是很好写的:
#include "TcpServer.hpp"
#include "Service.hpp"
#include "NetCal.hpp"
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cerr << "Usage: " << argv[0] << " local-port" << std::endl;
exit(0);
}
uint16_t port = std::stoi(argv[1]);
//业务层
NetCal cal;
Service ser(std::bind(&NetCal::Calculator , &cal , std::placeholders::_1));
//IO层
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(std::bind(&Service::IOExecute, &ser,
std::placeholders::_1,
std::placeholders::_2),
port);
//进入通信循环!
tsvr->Loop();
return 0;
}
可以看到我们只是使用了两次的bind绑定就实现了三层结构的实现,十分非简洁明了。只需等待客户端传入数据即可!
客户端的框架和服务端类似:
#include <iostream>
#include "Socket.hpp"
#include "Protocol.hpp"
using namespace socket_ns;
int main(int argc, char *argv[])
{
// 根据参数获取服务器IP 与 端口号
if (argc != 3)
{
std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;
exit(0);
}
std::string ip = argv[1];
uint16_t port = std::stoi(argv[2]);
// 工厂建立TcpSocket链接
SockSPtr sock = std::make_shared<TcpSocket>();
if (!sock->BuildClientSocket(ip, port))
{
std::cerr << "connect error!" << std::endl;
exit(1);
}
std::string packagestream;
//业务逻辑
while (true)
{
}
return 0;
}
接下来我们来进行客户端数据通信的逻辑:
int main(int argc, char *argv[])
{
//...
srand(time(nullptr));
std::string arr = "+-*/%&^!";
std::string packagestream;
int cnt = 3;
while (cnt--)
{
// 传入数据
int x = rand() % 50;
usleep(1000);
int y = rand() % 50;
char oper = arr[y % arr.size()];
// 1. 构建request
auto req = Factory::BuildRequestDefault();
req->SetValue(x, y, oper);
// 2. 进行序列化
std::string jsonstr;
req->Serialize(&jsonstr);
std::cout << "jsonstr: " << jsonstr << std::endl;
// 3. 添加报头
std::string reqstr = Encode(jsonstr);
// 4. 发送数据
sock->Send(reqstr);
// 5. 接收数据
while (true)
{
ssize_t n = sock->Recv(&packagestream);
if (n <= 0)
break;
// 6. 去除报头
std::string resstr = Decode(packagestream);
std::cout << "resstr: " << resstr << std::endl;
if (resstr.empty())
continue;
auto res = Factory::BuildResponseDefault();
// 7. 反序列化
std::cout << "----------------"<<std::endl;
res->Deserialize(resstr);
res->PrintResult();
break;
}
}
return 0;
}
这样客户端逻辑就写好了!!!
我们进行一下简单的测试首先注意因为我们使用JSON库编译时要加入对应的编译动态库选项:
.PHONY:all
all:calserver calclient
calserver:ServerMain.cc
g++ -o $@ $^ -std=c++14 -lpthread -ljsoncpp
calclient:ClientMain.cc
g++ -o $@ $^ -std=c++14 -ljsoncpp
.PHONY:clean
clean:
rm -rf calserver calclient
编译之后我们来看运行效果:
参照对应的ASCII码表:
运算符 | ACSII码 |
---|---|
+ | 43 |
- | 45 |
* | 42 |
/ | 47 |
% | 37 |
& | 38 |
* | 42 |
! | 33 |
可以验证结果是正确的!!! 这样网络计算机的小项目就完成了!!!