Linux网络-------2.应⽤层⾃定义协议与序列化
1.什么是序列化和反序列化?
协议是⼀种"约定".socketapi的接⼝,在读写数据时,都是按"字符串"的⽅式来发送接收的.如果我们要传输⼀些"结构化的数据"怎么办呢?
- 其实,协议就是双⽅约定好的结构化的数据------在下文图示就是指双方都能看懂的结构体对象!!!!!!!!
这个时候就需要用到序列化和反序列化了!!!!!!!
2.json库的使用(序列化)
序列化指的是 将数据结构或对象转换为⼀种格式
,以便在⽹络上传输或存储到⽂件中。Jsoncpp提供了多种⽅式进⾏序列化:
1.使⽤ Json::Value 的 toStyledString ⽅法:
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{Json::Value root;//定义一个value类root["name"] = "joe";root["sex"] = "男";std::string s = root.toStyledString();//使用tostyledstring()函数转换为字符串格式std::cout << s << std::endl;return 0;
}
使用g++编译:
-ljsoncpp
告诉编译器在链接阶段需要将 JSONCPP 库(libjsoncpp.so 或 libjsoncpp.a)链接到你的程序中。这样,你的程序才能调用 JSONCPP 提供的函数和类(如 Json::Value、Json::Reader 或 Json::Writer)。
2.使⽤ Json::FastWriter :
它不添加额外的空格和换⾏符:
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
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;
}
3.使⽤使⽤ Json::StreamWriter :
提供了更多的定制选项,如缩进、换⾏符等。
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
int main()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";Json::StreamWriterBuilder wbuilder; // StreamWriter的⼯⼚ std::unique_ptr<Json::StreamWriter>writer(wbuilder.newStreamWriter());std::stringstream ss;writer->write(root, &ss);std::cout << ss.str() << std::endl;return 0;
}
代码分析
这段代码的功能是 使用 JSONCPP 库构造一个 JSON 对象,并将其序列化为字符串输出。以下是逐步解析:
1. 包含头文件
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
<jsoncpp/json/json.h>
:JSONCPP 库的核心头文件,提供 JSON 数据的解析和生成功能。- 其他头文件(
<iostream>
,<sstream>
,<memory>
)用于输入输出、字符串流和智能指针管理。
2. 构造 JSON 对象
Json::Value root; // 创建一个空的 JSON 对象
root["name"] = "joe"; // 添加键值对 "name": "joe"
root["sex"] = "男"; // 添加键值对 "sex": "男"
Json::Value
是 JSONCPP 的核心类,可以表示 JSON 对象、数组、字符串、数字等。- 这里构造了一个 JSON 对象:
{"name": "joe","sex": "男" }
3. 配置 JSON 序列化(写入)
Json::StreamWriterBuilder wbuilder; // StreamWriter 的工厂类
std::unique_ptr<Json::StreamWriter> writer(wbuilder.newStreamWriter());
StreamWriterBuilder
是 JSONCPP 提供的写入器工厂,用于创建StreamWriter
对象。writer
是一个智能指针(std::unique_ptr
),负责将 JSON 数据写入输出流。
4. 将 JSON 对象序列化为字符串
std::stringstream ss; // 创建一个字符串流
writer->write(root, &ss); // 将 JSON 对象写入流
std::cout << ss.str() << std::endl; // 输出序列化后的字符串
writer->write(root, &ss)
:将root
(JSON 对象)写入字符串流ss
。ss.str()
获取流中的字符串内容,即 JSON 格式化后的文本。- 最终输出:
{"name":"joe","sex":"男"}
5. 程序返回值
return 0; // 正常退出
总结
这段代码的主要功能是:
- 构造一个简单的 JSON 对象(包含
name
和sex
字段)。 - 使用 JSONCPP 的
StreamWriter
将其序列化为字符串。 - 输出 JSON 字符串到标准输出。
输出示例
运行程序后,控制台会打印:
{"name":"joe","sex":"男"}
关键点
- JSONCPP 的使用:通过
Json::Value
构造 JSON 数据,再通过StreamWriter
序列化。 - 智能指针管理:
std::unique_ptr
自动释放StreamWriter
资源,避免内存泄漏。 - 流式写入:数据先写入
std::stringstream
,再转换为字符串输出,适合灵活处理。
3.json库的使用(反序列化)
反序列化指的是将序列化后的数据重新转换为原来的数据结构或对象。
1.Json::Reader :
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h> int main() { // JSON 字符串 std::string json_string = "{\"name\":\"张三\", \"age\":30,
\"city\":\"北京\"}";
son::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;
}
3.socket类的封装:
#pragma once // 防止头文件被重复包含#include <iostream> // 标准输入输出流
#include <string> // 字符串操作
#include <unistd.h> // POSIX操作系统API(如close)
#include <sys/socket.h> // 套接字相关函数和数据结构
#include <sys/types.h> // 系统数据类型定义
#include <netinet/in.h> // 互联网地址族定义
#include <arpa/inet.h> // 互联网地址转换函数
#include <cstdlib> // 标准库函数(如exit)
#include "Common.hpp" // 自定义公共头文件
#include "Log.hpp" // 自定义日志模块
#include "InetAddr.hpp" // 自定义网络地址类namespace SocketModule // 定义Socket模块命名空间
{using namespace LogModule; // 使用日志模块命名空间const static int gbacklog = 16; // 默认监听队列长度// Socket抽象基类(模板方法模式)class Socket{public:virtual ~Socket() {} // 虚析构函数(确保派生类正确析构)// 纯虚函数接口(强制派生类实现)virtual void SocketOrDie() = 0; // 创建套接字(失败则退出)virtual void BindOrDie(uint16_t port) = 0; // 绑定端口(失败则退出)virtual void ListenOrDie(int backlog) = 0; // 开始监听(失败则退出)virtual std::shared_ptr<Socket> Accept(InetAddr *client) = 0; // 接受连接virtual void Close() = 0; // 关闭套接字virtual int Recv(std::string *out) = 0; // 接收数据virtual int Send(const std::string &message) = 0; // 发送数据virtual int Connect(const std::string &server_ip, uint16_t port) = 0; // 连接服务器public:// TCP服务端初始化模板方法void BuildTcpSocketMethod(uint16_t port, int backlog = gbacklog){SocketOrDie(); // 1. 创建套接字BindOrDie(port); // 2. 绑定端口ListenOrDie(backlog); // 3. 开始监听}// TCP客户端初始化模板方法void BuildTcpClientSocketMethod(){SocketOrDie(); // 仅需创建套接字}/* UDP版本(注释状态)void BuildUdpSocketMethod(){SocketOrDie();BindOrDie();}*/};const static int defaultfd = -1; // 默认无效文件描述符// TCP套接字实现类class TcpSocket : public Socket{public:TcpSocket() : _sockfd(defaultfd) {} // 默认构造(初始化为-1)TcpSocket(int fd) : _sockfd(fd) {} // 通过现有文件描述符构造~TcpSocket() {} // 析构函数// 创建TCP套接字void SocketOrDie() override{_sockfd = ::socket(AF_INET, SOCK_STREAM, 0); // IPv4 + TCP协议if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket error"; // 致命错误日志exit(SOCKET_ERR); // 退出程序(错误码SOCKET_ERR)}LOG(LogLevel::INFO) << "socket success"; // 成功日志}// 绑定端口void BindOrDie(uint16_t port) override{InetAddr localaddr(port); // 创建本地地址对象int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen());if (n < 0){LOG(LogLevel::FATAL) << "bind error";exit(BIND_ERR); // 绑定失败退出}LOG(LogLevel::INFO) << "bind success";}// 开始监听void ListenOrDie(int backlog) override{int n = ::listen(_sockfd, backlog);if (n < 0){LOG(LogLevel::FATAL) << "listen error";exit(LISTEN_ERR);}LOG(LogLevel::INFO) << "listen success";}// 接受客户端连接std::shared_ptr<Socket> Accept(InetAddr *client) override{struct sockaddr_in peer; // 存储客户端地址socklen_t len = sizeof(peer);int fd = ::accept(_sockfd, CONV(peer), &len); // 接受连接if (fd < 0){LOG(LogLevel::WARNING) << "accept warning ..."; // 非致命警告return nullptr; // 返回空指针}client->SetAddr(peer); // 设置客户端地址信息return std::make_shared<TcpSocket>(fd); // 返回新套接字的智能指针}// 接收数据(追加到字符串)int Recv(std::string *out) override{char buffer[1024]; // 接收缓冲区ssize_t n = ::recv(_sockfd, buffer, sizeof(buffer) - 1, 0); // 接收数据if (n > 0){buffer[n] = 0; // 手动添加字符串结束符*out += buffer; // 追加到输出字符串}return n; // 返回实际接收字节数}// 发送数据int Send(const std::string &message) override{return send(_sockfd, message.c_str(), message.size(), 0); // 发送原始数据}// 关闭套接字void Close() override{if (_sockfd >= 0)::close(_sockfd); // 仅关闭有效描述符}// 连接服务器int Connect(const std::string &server_ip, uint16_t port) override{InetAddr server(server_ip, port); // 创建服务器地址对象return ::connect(_sockfd, server.NetAddrPtr(), server.NetAddrLen()); // 发起连接}private:int _sockfd; // 套接字文件描述符};/* UDP实现类(注释状态)class UdpSocket : public Socket{};*/
}
InetAddr.hpp:
#pragma once
#include "Common.hpp"
// 网络地址和主机地址之间进行转换的类class InetAddr
{
public:InetAddr(){} //参数是ip结构体,初始化的主机的ip和端口InetAddr(struct sockaddr_in &addr)//这是客户端/服务端使用recvfrom函数之类的获取的另一方的ip结构体,保存为主机版/------收取信息并保存{SetAddr(addr);}//参数是ip和端口,初始化的是ip结构体!!!!InetAddr(const std::string &ip, uint16_t port) : _ip(ip), _port(port)//-----------这是服务端/客户端使用sendto之类函数使用的---发送信息{// 主机转网络memset(&_addr, 0, sizeof(_addr));_addr.sin_family = AF_INET;inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);//这行代码的作用是 将人类可读的点分十进制IPv4字符串(如 "192.168.1.1")转换为二进制网络字节序格式,并存储到 _addr.sin_addr 中,以便用于网络通信(如 bind、connect 等函数)。_addr.sin_port = htons(_port);// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // TODO}//初始化的是ip结构体!!!!InetAddr(uint16_t port) :_port(port),_ip()//-----------这是服务端使用的主机转网络----服务端使用bind函数时获取并初始化主机地址使用的函数{// 主机转网络memset(&_addr, 0, sizeof(_addr));//结构体清空内存!!!!_addr.sin_family = AF_INET;//统一为AF_INET_addr.sin_addr.s_addr = INADDR_ANY;//设置为0,-----只要端口号符合,不管ip地址是多少,服务端直接接收!!!!!_addr.sin_port = htons(_port);//使用htons函数转为网络序列!!!!!!}void SetAddr(struct sockaddr_in &addr){_addr = addr;// 网络转主机_port = ntohs(_addr.sin_port); // 从网络中拿到的!网络序列// _ip = inet_ntoa(_addr.sin_addr); // 4字节网络风格的IP -> 点分十进制的字符串风格的IPchar ipbuffer[64];inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(_addr));_ip = ipbuffer;}uint16_t Port() { return _port; }//获取端口号std::string Ip() { return _ip; }//获取ipconst struct sockaddr_in &NetAddr() { return _addr; } //获取sockaddr_in类型的结构体const struct sockaddr *NetAddrPtr()//获取sockaddr类型的结构体------便于bind函数进行绑定!!!!{return CONV(_addr);//#define CONV(addr) ((struct sockaddr*)&addr)------------把CONV(addr)-------翻译为((struct sockaddr*)&addr),就是类型转换的意思}socklen_t NetAddrLen(){return sizeof(_addr);//获取ip结构体的大小,也是为了给bind函数提供参数!!!}bool operator==(const InetAddr &addr){return addr._ip == _ip && addr._port == _port;}std::string StringAddr(){return _ip + ":" + std::to_string(_port); //返回地址名---ip地址加端口号!!!!!}~InetAddr(){}private:struct sockaddr_in _addr;std::string _ip;uint16_t _port;
};
Common.hpp:
#pragma once#include <iostream>
#include <functional>
#include <unistd.h>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>enum ExitCode
{OK = 0,USAGE_ERR,SOCKET_ERR,BIND_ERR,LISTEN_ERR,CONNECT_ERR,FORK_ERR,OPEN_ERR
};//在 C/c++ 中,枚举器会直接暴露在外层作用域(需直接使用 OK 而非 ExitCode::OK)。class NoCopy
{
public:NoCopy(){}~NoCopy(){}NoCopy(const NoCopy &) = delete;const NoCopy &operator = (const NoCopy&) = delete;
};//它的作用是 禁止对象的拷贝构造和拷贝赋值,即让这个类的对象不能被复制。这是 C++11 引入的 = delete 特性的典型应用。#define CONV(addr) ((struct sockaddr*)&addr)