✨个人主页: 熬夜学编程的小林
系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】【Linux网络编程】
目录
1、 TcpService.hpp
1.1、TcpServer类基本结构
1.2、构造析构函数
1.3、Loop()
1.3.1、内部类
1.3.2、Execute()
2、Service.hpp
2.1、IOService类基本结构
2.2、构造析构函数
2.3、IOExcute()
3、ServerMain.cc
4、Jsoncpp
4.1、特性
4.2、安装
4.3、序列化
4.4、反序列化
4.5、总结
5、Json::Value
5.1、构造函数
5.2、访问元素
5.3. 类型检查
5.4、赋值和类型转换
5.5、数组和对象操作
上一弹简单的将Socket类进行了封装,封装的目的是为了我们后序直接使用的,因此这弹使用封装好的Socket类实现网络计算器(加协议版本)的一部分轮廓,此处我们将执行方法单独放在一个文件!
TcpService.hpp 用于封装TcpServer类,此处需要调用执行方法!
执行方法声明:
using service_io_t = std::function;
TcpServer类成员变量有端口号,监听套接字(封装的智能指针类型),运行状态和执行方法,构造函数初始化成员变量即可!
// 面向字节流
class TcpServer
{
public:
TcpServer(service_io_t service,uint16_t port = gport);
void Loop();
~TcpServer();
private:
uint16_t _port;
SockSPtr _listensock;
bool _isrunning;
service_io_t _service; // 执行方法
};
构造函数初始化成员变量并创建监听套接字(调用父类方法),析构函数无需处理!
TcpServer(service_io_t service,uint16_t port = gport)
:_port(port),
_listensock(std::make_shared()),
_isrunning(false),
_service(service)
{
_listensock->BuildListenSocket(_port);
}
~TcpServer()
{}
Loop()函数使用多线程的版本执行长服务,让新线程去执行主函数传递的执行方法!
void Loop()
{
_isrunning = true;
while(_isrunning)
{
InetAddr client;
SockSPtr newsock = _listensock->Accepter(&client); // 获取连接
if(newsock == nullptr)
continue;
LOG(INFO,"get a new link,client info: %s,sockfd:%d\n",client.AddrStr().c_str(),newsock->Sockfd());
// 获取成功
// version 2 -- 多线程版 -- 不能关闭fd了,也不需要
pthread_t tid;
ThreadData *td = new ThreadData(newsock, this,client);
pthread_create(&tid,nullptr,Execute,td); // 新线程分离
}
_isrunning = false;
}
为了解决新线程函数能够看到TcpServer类的成员变量,此处传内部类的地址,内部类包含TcpServer类的指针变量,套接字和网络地址类!
// 内部类
class ThreadData
{
public:
SockSPtr _sockfd;
TcpServer* _self;
InetAddr _addr;
public:
ThreadData(SockSPtr sockfd,TcpServer* self,const InetAddr &addr)
:_sockfd(sockfd),_self(self),_addr(addr)
{}
};
Execute()为新线程的执行函数,调用执行方法!
// 无法调用类内成员 无法看到sockfd
static void *Execute(void *args)
{
ThreadData *td = static_cast(args);
pthread_detach(pthread_self()); // 分离新线程,无需主线程回收
td->_self->_service(td->_sockfd,td->_addr); // 执行回调
td->_sockfd->Close();
delete td;
return nullptr;
}
该文件设计一个类,其中一个成员函数实现执行方法,暂时先让服务端代码编译过即可,后序再加协议!
该类没有成员变量,主要是实现执行函数,为了进一步的解耦因此单独放在一个文件!
class IOService
{
public:
IOService();
void IOExcute(SockSPtr sock, InetAddr &addr);
~IOService()
{}
};
因为该类没有成员变量,因此构造析构函数也无需处理!
IOService()
{}
~IOService()
{}
该函数是TcpServer类中执行方法的具体实现,此处暂时只进行IO操作,保证编译通过!
void IOExcute(SockSPtr sock, InetAddr &addr)
{
while (true)
{
std::string message;
ssize_t n = sock->Recv(&message);
if(n > 0)
{
LOG(INFO, "get message from client [%s],message: %s\n", addr.AddrStr().c_str(), message.c_str());
std::string hello = "hello";
sock->Send(hello);
}
else if(n == 0)
{
LOG(INFO, "client %s quit\n", addr.AddrStr().c_str());
break;
}
else
{
LOG(ERROR, "read error\n", addr.AddrStr().c_str());
break;
}
}
}
该文件用户创建TcpServer类对象,并调用执行函数运行服务端!
// ./calserver 8888
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]);
IOService service;
std::unique_ptr tsvr = std::make_unique(
std::bind(&IOService::IOExcute, &service, std::placeholders::_1, std::placeholders::_2),
port);
tsvr->Loop();
return 0;
}
运行结果
前面已经实现了通信的代码了,因此现在我们需要自己定义协议,协议中包含两个类,一个用于发送消息的类,一个用于接受消息的类,两个类的核心操作是序列化和反序列化!
序列化操作和反序列操作可以自己实现,也可以使用库,此处介绍jsoncpp库!
Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能。Jsoncpp 是开源的,广泛用于各种需要处理 JSON 数据的 C++ 项目中。
当使用 Jsoncpp 库进行 JSON 的序列化和反序列化时,确实存在不同的做法和工具类可供选择。
以下是对 Jsoncpp 中序列化和反序列化操作的详细介绍:
ubuntu:sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel
注意:在使用g++编译时需要加库文件编译,否则会报错!
g++ 文件名 -ljsoncpp # 编译文件
序列化指的是将数据结构或对象转换为一种格式,以便在网络上传输或存储到文件中。Jsoncpp 提供了多种方式进行序列化:
使用 Json::Value 的 toStyledString 方法:
优点:将 Json::Value 对象直接转换为格式化的 JSON 字符串。
示例:
#include
#include
#include
int main()
{
Json::Value root;
root["name"] = "joe";
root["sex"] = "男";
std::string s = root.toStyledString();
std::cout << s << std::endl;
return 0;
}
$ ./test.exe
{
"name" : "joe",
"sex" : "男"
}
使用 Json::StreamWriter:
优点:提供了更多的定制选项,如缩进、换行符等。
示例:
#include
#include
#include
#include
#include
int main()
{
Json::Value root;
root["name"] = "joe";
root["sex"] = "男";
Json::StreamWriterBuilder wbuilder; // StreamWriter 的工厂
std::unique_ptr
writer(wbuilder.newStreamWriter());
std::stringstream ss;
writer->write(root, &ss);
std::cout << ss.str() << std::endl;
return 0;
}
$ ./test.exe
{
"name" : "joe",
"sex" : "男"
}
使用 Json::FastWriter:
优点:比 StyledWriter 更快,因为它不添加额外的空格和换行符。
示例一:
#include
#include
#include
#include
#include
int main()
{
Json::Value root;
root["name"] = "joe";
root["sex"] = "男";
Json::FastWriter writer;
std::string s = writer.write(root);
std::cout << s << std::endl;
return 0;
}
$ ./test.exe
{"name":"joe","sex":"男"}
示例二:
#include
#include
#include
#include
#include
int main()
{
Json::Value root;
root["name"] = "joe";
root["sex"] = "男";
// Json::FastWriter writer;
Json::StyledWriter writer;
std::string s = writer.write(root);
std::cout << s << std::endl;
return 0;
}
$ ./test.exe
{
"name" : "joe",
"sex" : "男"
}
反序列化指的是将序列化后的数据重新转换为原来的数据结构或对象。Jsoncpp 提供了以下方法进行反序列化:
使用 Json::Reader:
优点:提供详细的错误信息和位置,方便调试。
示例:
#include
#include
#include
int main() {
// JSON 字符串
std::string json_string = "{\"name\":\"张三\",\"age\":30, \"city\":\"北京\"}";
// 解析 JSON 字符串
Json::Reader reader;
Json::Value root;
// 从字符串中读取 JSON 数据
bool parsingSuccessful = reader.parse(json_string,root);
if (!parsingSuccessful) {
// 解析失败,输出错误信息
std::cout << "Failed to parse JSON: " <<
reader.getFormattedErrorMessages() << std::endl;
return 1;
}
// 访问 JSON 数据
std::string name = root["name"].asString();
int age = root["age"].asInt();
std::string city = root["city"].asString();
// 输出结果
std::cout << "Name: " << name << std::endl;
std::cout << "Age: " << age << std::endl;
std::cout << "City: " << city << std::endl;
return 0;
}
$ ./test.exe
Name: 张三
Age: 30
City: 北京
使用 Json::CharReader 的派生类(不推荐了,上面的足够了):
Json::Value 是 Jsoncpp 库中的一个重要类,用于表示和操作 JSON 数据结构。以下是一些常用的 Json::Value 操作列表: