> 技术文档 > 【Linux网络编程】第九弹---深入解析TCP服务、IOService与Jsoncpp的应用与实现

【Linux网络编程】第九弹---深入解析TCP服务、IOService与Jsoncpp的应用与实现

个人主页: 熬夜学编程的小林

💗系列专栏: 【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类实现网络计算器(加协议版本)的一部分轮廓,此处我们将执行方法单独放在一个文件

1、 TcpService.hpp

TcpService.hpp 用于封装TcpServer类,此处需要调用执行方法!

执行方法声明:

using service_io_t = std::function;

1.1、TcpServer类基本结构

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; // 执行方法};

1.2、构造析构函数 

构造函数初始化成员变量并创建监听套接字(调用父类方法),析构函数无需处理!

TcpServer(service_io_t service,uint16_t port = gport) :_port(port), _listensock(std::make_shared()), _isrunning(false), _service(service){ _listensock->BuildListenSocket(_port);}~TcpServer(){}

1.3、Loop()

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;}

1.3.1、内部类

为了解决新线程函数能够看到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) {}};

1.3.2、Execute()

Execute()为新线程的执行函数,调用执行方法

// 无法调用类内成员 无法看到sockfdstatic 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;}

2、Service.hpp

该文件设计一个类,其中一个成员函数实现执行方法,暂时先让服务端代码编译过即可,后序再加协议!

2.1、IOService类基本结构

该类没有成员变量,主要是实现执行函数,为了进一步的解耦因此单独放在一个文件!

class IOService{public: IOService(); void IOExcute(SockSPtr sock, InetAddr &addr); ~IOService() {}};

2.2、构造析构函数

因为该类没有成员变量,因此构造析构函数也无需处理!

IOService(){}~IOService(){}

2.3、IOExcute()

该函数是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; } }}

3、ServerMain.cc

该文件用户创建TcpServer类对象,并调用执行函数运行服务端

// ./calserver 8888int 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;}

运行结果 

4、Jsoncpp

前面已经实现了通信的代码了,因此现在我们需要自己定义协议,协议中包含两个类,一个用于发送消息的类,一个用于接受消息的类,两个类的核心操作是序列化和反序列化

序列化操作和反序列操作可以自己实现,也可以使用库,此处介绍jsoncpp库!

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

4.1、特性

  • 1.简单易用:Jsoncpp 提供了直观的 API,使得处理 JSON 数据变得简单。
  • 2.高性能:Jsoncpp 的性能经过优化,能够高效地处理大量 JSON 数据。
  • 3.全面支持:支持 JSON 标准中的所有数据类型,包括对象、数组、字符串、数字、布尔值和 null。
  • 4.错误处理:在解析 JSON 数据时,Jsoncpp 提供了详细的错误信息和位置,方便开发者调试。

当使用 Jsoncpp 库进行 JSON 的序列化和反序列化时,确实存在不同的做法和工具类可供选择。

以下是对 Jsoncpp 中序列化和反序列化操作的详细介绍:

4.2、安装

ubuntu:sudo apt-get install libjsoncpp-devCentos: sudo yum install jsoncpp-devel

注意:在使用g++编译时需要加库文件编译,否则会报错!

g++ 文件名 -ljsoncpp # 编译文件

4.3、序列化

序列化指的是将数据结构或对象转换为一种格式,以便在网络上传输或存储到文件中。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\" : \"男\"}

4.4、反序列化

反序列化指的是将序列化后的数据重新转换为原来的数据结构或对象。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.exeName: 张三Age: 30City: 北京

使用 Json::CharReader 的派生类(不推荐了,上面的足够了):

  • 在某些情况下,你可能需要更精细地控制解析过程,可以直接使用Json::CharReader 的派生类。
  • 但通常情况下,使用 Json::parseFromStream 或 Json::Reader 的 parse方法就足够了。

4.5、总结

  • toStyledString、StreamWriter 和 FastWriter 提供了不同的序列化选项,你可以根据具体需求选择使用。
  • Json::Reader 和 parseFromStream 函数是 Jsoncpp 中主要的反序列化工具,它们提供了强大的错误处理机制。
  • 在进行序列化和反序列化时,请确保处理所有可能的错误情况,并验证输入和输出的有效性。

5、Json::Value

Json::Value 是 Jsoncpp 库中的一个重要类,用于表示和操作 JSON 数据结构。以下是一些常用的 Json::Value 操作列表:

5.1、构造函数

  • Json::Value():默认构造函数,创建一个空的 Json::Value 对象。
  • Json::Value(ValueType type, bool allocated = false):根据给定的ValueType(如 nullValue, intValue, stringValue 等)创建一个 Json::Value 对象。

5.2、访问元素

  • Json::Value& operator[](const char* key):通过键(字符串)访问对象中的元素。如果键不存在,则创建一个新的元素。
  • Json::Value& operator[](const std::string& key):同上,但使用std::string 类型的键。
  • Json::Value& operator[](ArrayIndex index):通过索引访问数组中的元素。如果索引超出范围,则创建一个新的元素。
  • Json::Value& at(const char* key):通过键访问对象中的元素,如果键不存在则抛出异常。
  • Json::Value& at(const std::string& key):同上,但使用 std::string类型的键。

5.3、类型检查

  • bool isNull():检查值是否为 null。
  • bool isBool():检查值是否为布尔类型。
  • bool isInt():检查值是否为整数类型。
  • bool isInt64():检查值是否为 64 位整数类型。
  • bool isUInt():检查值是否为无符号整数类型。
  • bool isUInt64():检查值是否为 64 位无符号整数类型。
  • bool isIntegral():检查值是否为整数或可转换为整数的浮点数。
  • bool isDouble():检查值是否为双精度浮点数。
  • bool isNumeric():检查值是否为数字(整数或浮点数)。
  • bool isString():检查值是否为字符串。
  • bool isArray():检查值是否为数组。
  • bool isObject():检查值是否为对象(即键值对的集合)。

5.4、赋值和类型转换

  • Json::Value& operator=(bool value):将布尔值赋给 Json::Value 对象。
  • Json::Value& operator=(int value):将整数赋给 Json::Value 对象。
  • Json::Value& operator=(unsigned int value):将无符号整数赋给Json::Value 对象。
  • Json::Value& operator=(Int64 value):将 64 位整数赋给 Json::Value对象。
  • Json::Value& operator=(UInt64 value):将 64 位无符号整数赋给Json::Value 对象。
  • Json::Value& operator=(double value):将双精度浮点数赋给Json::Value 对象。
  • Json::Value& operator=(const char* value):将 C 字符串赋给Json::Value 对象。
  • Json::Value& operator=(const std::string& value):将 std::string赋给 Json::Value 对象。
  • bool asBool():将值转换为布尔类型(如果可能)。
  • int asInt()将值转换为整数类型(如果可能)。
  • Int64 asInt64()将值转换为 64 位整数类型(如果可能)。
  • unsigned int asUInt():将值转换为无符号整数类型(如果可能)。
  • UInt64 asUInt64():将值转换为 64 位无符号整数类型(如果可能)。
  • double asDouble():将值转换为双精度浮点数类型(如果可能)。
  • std::string asString():将值转换为字符串类型(如果可能)。

5.5、数组和对象操作

  • size_t size():返回数组或对象中的元素数量。
  • bool empty():检查数组或对象是否为空。
  • void resize(ArrayIndex newSize):调整数组的大小。
  • void clear():删除数组或对象中的所有元素。
  • void append(const Json::Value& value)在数组末尾添加一个新元素
  • Json::Value& operator[](const char* key, const Json::Value&
  • defaultValue = Json::nullValue):在对象中插入或访问一个元素,如果键不存在则使用默认值。
  • Json::Value& operator[](const std::string& key, const Json::Value& defaultValue = Json::nullValue):同上,但使用 std::string类型的