欢迎光临
我们一直在努力

网络计算器:理解序列化与反序列化(中)

网络计算器:理解序列化与反序列化(上)-CSDN博客


一、Socket.hpp:模板方法模式 + TCP 实现

1.1 文件存在的意义

Socket 操作有固定流程创建 → 绑定 → 监听 → 接受连接。但 TCP 和 UDP 的具体实现不同。模板方法模式把"流程"固化在基类,把"变化"留给子类。

1.2 抽象基类 Socket

class 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; // 连接服务器

// 模板方法:固化流程
void BuildTcpSocketMethod(uint16_t port, int backlog = gbacklog) {
SocketOrDie(); // 第1步
BindOrDie(port); // 第2步
ListenOrDie(backlog); // 第3步
}

void BuildTcpClientSocketMethod() {
SocketOrDie(); // 客户端只需要创建
}
};

模板方法的好处

  • 服务器端不可能"先 Listen 再 Bind",流程错误在编译期就避免

  • 新增 UDP 时,只需继承 Socket,实现 7 个纯虚函数,流程自动正确

1.3 TcpSocket 实现详解

class TcpSocket : public Socket {
int _sockfd; // 核心资源:文件描述符
public:
TcpSocket() : _sockfd(defaultfd) {} // 默认构造(监听套接字)
TcpSocket(int fd) : _sockfd(fd) {} // 从已有 fd 构造(连接套接字)

为什么需要两个构造函数?

  • 服务器:TcpSocket() → 创建新的监听 fd

  • Accept() 后:TcpSocket(fd) → 包装系统返回的连接 fd

void SocketOrDie() override {
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0); // :: 表示系统调用
if (_sockfd < 0) {
LOG(LogLevel::FATAL) << "socket error";
exit(SOCKET_ERR);
}
}

注意 ::socket加 :: 明确调用全局命名空间的系统函数,避免与类成员函数混淆。

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); // 返回新的连接套接字
}

  • 多态:上层代码只认识 Socket 接口,不知道具体是 TCP 还是 UDP

  • 自动内存管理:不用手动 delete

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

+= 是核心设计!

  • TCP 是流式协议,一次 recv 可能读不完一个完整请求

  • buffer_queue += buffer 把新读到的数据追加到累积缓冲区

  • 后续 Protocol::Decode() 从累积缓冲区中提取完整报文

int Send(const std::string &message) override {
return send(_sockfd, message.c_str(), message.size(), 0);
}

为什么 Send 不用 +=?

  • 发送方知道自己要发什么,直接发完整报文即可

  • Encode() 已经把长度+JSON 拼好了,不需要累积

#pragma once

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"

namespace SocketModule
{
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:
void BuildTcpSocketMethod(uint16_t port, int backlog = gbacklog)
{
SocketOrDie();
BindOrDie(port);
ListenOrDie(backlog);
}
void BuildTcpClientSocketMethod()
{
SocketOrDie();
}
// void BuildUdpSocketMethod()
// {
// SocketOrDie();
// BindOrDie();
// }
};

const static int defaultfd = -1;

class TcpSocket : public Socket
{
public:
TcpSocket() : _sockfd(defaultfd)
{
}
TcpSocket(int fd) : _sockfd(fd)
{
}
~TcpSocket() {}
void SocketOrDie() override
{
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error";
exit(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; // TODO
}
client->SetAddr(peer);
return std::make_shared<TcpSocket>(fd);
}
// n == read的返回值
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; // _sockfd , listensockfd, sockfd;
};

// class UdpSocket : public Socket
// {
// };
}


二、Protocol.hpp:项目的灵魂

2.1 文件存在的意义

这个文件解决了网络编程的两大核心问题:

  • 结构化数据 ↔ 字符串:序列化/反序列化

  • 字节流 ↔ 消息:自定义协议解决粘包

  • 2.2 Request 类:客户端 → 服务器

    class Request {
    int _x;
    int _y;
    char _oper;
    public:
    Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper) {}

    std::string Serialize() {
    Json::Value root;
    root["x"] = _x;
    root["y"] = _y;
    root["oper"] = _oper; // char 自动转为 ASCII 码整数

    Json::FastWriter writer;
    return writer.write(root); // 返回如 {"x":10,"y":20,"oper":43}
    }

    bool Deserialize(std::string &in) {
    Json::Value root;
    Json::Reader reader;
    bool ok = reader.parse(in, root);
    if (ok) {
    _x = root["x"].asInt();
    _y = root["y"].asInt();
    _oper = root["oper"].asInt(); // 转回 char
    }
    return ok;
    }
    };

    为什么 _oper 存的是 ASCII 码?

    • JSON 没有 char 类型,'+' 的 ASCII 是 43,序列化后变成 {"oper":43}

    • 反序列化时用 asInt() 取出 43,使用时直接当 char 用(C++ 会自动转换)

    2.3 Response 类:服务器 → 客户端

    class Response {
    int _result;
    int _code; // 0=成功, 1=除零, 2=模零, 3=非法操作
    public:
    Response(int result, int code) : _result(result), _code(code) {}

    std::string Serialize() {
    Json::Value root;
    root["result"] = _result;
    root["code"] = _code;
    Json::FastWriter writer;
    return writer.write(root);
    }

    bool Deserialize(std::string &in) { … }
    void ShowResult() {
    std::cout << "计算结果是: " << _result << "[" << _code << "]" << std::endl;
    }
    };

    _code 的设计是点睛之笔!

    • 如果没有 code,result=0 时客户端不知道是真的结果为 0,还是出错了

    • code 让业务错误与计算结果彻底分离

    2.4 Encode / Decode:自定义协议核心

    const std::string sep = "\\r\\n";

    std::string Encode(const std::string &jsonstr) {
    std::string len = std::to_string(jsonstr.size());
    return len + sep + jsonstr + sep;
    }

    协议格式可视化

    为什么用 \\r\\n 而不用 \\n

    bool Decode(std::string &buffer, std::string *package) {
    // 步骤1:找第一个 \\r\\n,确定"长度字段"的边界
    ssize_t pos = buffer.find(sep);
    if (pos == std::string::npos) return false; // 数据不够,继续读

    // 步骤2:提取长度值
    std::string package_len_str = buffer.substr(0, pos);
    int package_len_int = std::stoi(package_len_str);

    // 步骤3:计算"一个完整报文"需要的总长度
    // 总长度 = 长度字段自身长度 + \\r\\n(2字节) + JSON内容长度 + \\r\\n(2字节)
    int target_len = package_len_str.size() + package_len_int + 2 * sep.size();

    // 步骤4:判断 buffer 中是否有足够数据
    if (buffer.size() < target_len) return false; // 数据不够,继续读

    // 步骤5:提取完整 JSON 内容
    *package = buffer.substr(pos + sep.size(), package_len_int);

    // 步骤6:从 buffer 中移除已处理的报文(关键!)
    buffer.erase(0, target_len);

    return true;
    }

    buffer 为什么用引用 &

    • Recv() 是流式读取,可能一次读到多个报文,也可能只读到半个报文

    • buffer 作为累积缓冲区,保留未处理完的残留数据

    • 下次 Recv() 时,新数据追加到 buffer 尾部,继续尝试 Decode

    循环解析的妙处

    while (Decode(buffer_queue, &json_package)) {
    // 处理第一个完整请求
    // 处理第二个完整请求(如果粘包了)
    // …
    }

    2.5 GetRequest:服务器的"主循环"

    void GetRequest(std::shared_ptr<Socket> &sock, InetAddr &client) {
    std::string buffer_queue; // 累积缓冲区

    while (true) {
    int n = sock->Recv(&buffer_queue); // 追加读取
    if (n > 0) {
    std::string json_package;

    // 循环提取所有完整报文
    while (Decode(buffer_queue, &json_package)) {
    LOG(LogLevel::DEBUG) << client.StringAddr() << " 请求: " << json_package;

    // 1. 反序列化
    Request req;
    bool ok = req.Deserialize(json_package);
    if (!ok) continue;

    // 2. 业务处理(通过回调函数 _func)
    Response resp = _func(req);

    // 3. 序列化响应
    std::string json_str = resp.Serialize();

    // 4. 编码(加长度报头)
    std::string send_str = Encode(json_str);

    // 5. 发送
    sock->Send(send_str);
    }
    }
    else if (n == 0) {
    LOG(LogLevel::INFO) << "client:" << client.StringAddr() << "Quit!";
    break; // 客户端断开
    }
    else {
    LOG(LogLevel::WARNING) << "client:" << client.StringAddr() << ", recv error";
    break; // 出错
    }
    }
    }

    2.6 GetResponse:客户端的接收

    bool GetResponse(std::shared_ptr<Socket> &client, std::string &resp_buff, Response *resp) {
    while (true) {
    int n = client->Recv(&resp_buff);
    if (n > 0) {
    std::string json_package;
    while (Decode(resp_buff, &json_package)) {
    resp->Deserialize(json_package); // 填充 resp 对象
    }
    return true;
    }
    else if (n == 0) {
    std::cout << "server quit " << std::endl;
    return false;
    }
    else {
    std::cout << "recv error" << std::endl;
    return false;
    }
    }
    }

    GetRequest 的区别

    • GetRequest 是服务器端,需要循环处理多个请求(长连接)

    • GetResponse 是客户端,一次请求对应一次响应,返回 bool 表示成功/失败

    2.7 BuildRequestString:客户端的"打包工厂"

    std::string BuildRequestString(int x, int y, char oper) {
    Request req(x, y, oper); // 1. 构造请求对象
    std::string json_req = req.Serialize(); // 2. 序列化为 JSON
    return Encode(json_req); // 3. 加长度报头,返回可直接发送的字符串
    }

    存在价值:把"构造→序列化→编码"三步封装成一步,客户端代码更简洁。

    #pragma once
    #include "Socket.hpp"
    #include <iostream>
    #include <string>
    #include <memory>
    #include <jsoncpp/json/json.h>
    #include <functional>

    // 实现一个自定义的网络版本的计算器

    using namespace SocketModule;

    // 约定好各个字段的含义,本质就是约定好协议!
    // client -> server
    // 如何要做序列化和反序列化:
    // 1. 我们自己写(怎么做) —> 往往不具备很好的扩展性
    // 2. 使用现成的方案(这个是我们要写的) —> json -> jsoncpp

    // content_len jsonstring
    // 50\\r\\n协议号\\r\\n{"x": 10, "y" : 20, "oper" : '+'}\\r\\n
    // 50
    // {"x": 10, "y" : 20, "oper" : '+'}
    class Request
    {
    public:
    Request()
    {
    }
    Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper)
    {
    }
    std::string Serialize()
    {
    // _x = 10 _y = 20, _oper = '+'
    // "10" "20" '+' : 用空格作为分隔符
    Json::Value root;
    root["x"] = _x;
    root["y"] = _y;
    root["oper"] = _oper; // _oper是char,也是整数,阿斯克码值

    Json::FastWriter writer;
    std::string s = writer.write(root);
    return s;
    }

    // {"x": 10, "y" : 20, "oper" : '+'}
    bool Deserialize(std::string &in)
    {
    // "10" "20" '+' -> 以空格作为分隔符 -> 10 20 '+'
    Json::Value root;
    Json::Reader reader;
    bool ok = reader.parse(in, root);
    if (ok)
    {
    _x = root["x"].asInt();
    _y = root["y"].asInt();
    _oper = root["oper"].asInt(); //?
    }
    return ok;
    }
    ~Request() {}
    int X() { return _x; }
    int Y() { return _y; }
    char Oper() { return _oper; }

    private:
    int _x;
    int _y;
    char _oper; // + – * / % -> _x _oper _y -> 10 + 20
    };

    // server -> client
    class Response
    {
    public:
    Response() {}
    Response(int result, int code) : _result(result), _code(code)
    {
    }
    std::string Serialize()
    {
    Json::Value root;
    root["result"] = _result;
    root["code"] = _code;

    Json::FastWriter writer;
    return writer.write(root);
    }
    bool Deserialize(std::string &in)
    {
    Json::Value root;
    Json::Reader reader;
    bool ok = reader.parse(in, root);
    if (ok)
    {
    _result = root["result"].asInt();
    _code = root["code"].asInt();
    }
    return ok;
    }
    ~Response() {}
    void SetResult(int res)
    {
    _result = res;
    }
    void SetCode(int code)
    {
    _code = code;
    }
    void ShowResult()
    {
    std::cout << "计算结果是: " << _result << "[" << _code << "]" << std::endl;
    }

    private:
    int _result; // 运算结果,无法区分清楚应答是计算结果,还是异常值
    int _code; // 0:sucess, 1,2,3,4->不同的运算异常的情况, 约定!!!!
    };

    const std::string sep = "\\r\\n";

    using func_t = std::function<Response(Request &req)>;

    // 协议(基于TCP的)需要解决两个问题:
    // 1. request和response必须得有序列化和反序列化功能
    // 2. 你必须保证,读取的时候,读到完整的请求(TCP, UDP不用考虑)
    class Protocol
    {
    public:
    Protocol()
    {
    }
    Protocol(func_t func) : _func(func)
    {
    }
    std::string Encode(const std::string &jsonstr)
    {
    // 50\\r\\n{"x": 10, "y" : 20, "oper" : '+'}\\r\\n
    std::string len = std::to_string(jsonstr.size());
    return len + sep + jsonstr + sep; // 应用层封装报头
    }

    // 50\\r\\n{"x": 10, "y" : 20, "oper" : '+'}\\r\\n
    // 5
    // 50
    // 50\\r
    // 50\\r\\n
    // 50\\r\\n{"x": 10, "
    // 50\\r\\n{"x": 10, "y" : 20, "oper" : '+'}\\r\\n
    // 50\\r\\n{"x": 10, "y" : 20, "oper" : '+'}\\r\\n50\\r\\n{"x": 10, "y" : 20, "ope
    //…..
    // packge故意是&
    // 1. 判断报文完整性
    // 2. 如果包含至少一个完整请求,提取他, 并从移除它,方便处理下一个
    bool Decode(std::string &buffer, std::string *package)
    {
    ssize_t pos = buffer.find(sep);
    if (pos == std::string::npos)
    return false; // 让调用方继续从内核中读取数据
    std::string package_len_str = buffer.substr(0, pos);
    int package_len_int = std::stoi(package_len_str);
    // buffer 一定有长度,但是一定有完整的报文吗?
    int target_len = package_len_str.size() + package_len_int + 2 * sep.size();
    if (buffer.size() < target_len)
    return false;

    // buffer一定至少有一个完整的报文!
    *package = buffer.substr(pos + sep.size(), package_len_int);
    buffer.erase(0, target_len);
    return true;
    }
    void GetRequest(std::shared_ptr<Socket> &sock, InetAddr &client)
    {
    // 读取
    std::string buffer_queue;
    while (true)
    {
    int n = sock->Recv(&buffer_queue);
    if (n > 0)
    {
    std::cout << "———–request_buffer————–" << std::endl;
    std::cout << buffer_queue << std::endl;
    std::cout << "————————————" << std::endl;

    std::string json_package;
    // 1. 解析报文,提取完整的json请求,如果不完整,就让服务器继续读取
    while (Decode(buffer_queue, &json_package))
    {
    // 我敢100%保证,我一定拿到了一个完整的报文
    // {"x": 10, "y" : 20, "oper" : '+'} -> 你能处理吗?
    // 2. 请求json串,反序列化
    // std::cout << "———–request_json————–" << std::endl;
    // std::cout << json_package << std::endl;
    // std::cout << "————————————" << std::endl;

    // std::cout << "———–request_buffer————–" << std::endl;
    // std::cout << buffer_queue << std::endl;
    // std::cout << "————————————" << std::endl;

    LOG(LogLevel::DEBUG) << client.StringAddr() << " 请求: " << json_package;
    Request req;
    bool ok = req.Deserialize(json_package);
    if (!ok)
    continue;
    // 3. 我一定得到了一个内部属性已经被设置了的req了.
    // 通过req->resp, 不就是要完成计算功能嘛!!业务
    Response resp = _func(req);

    // 4. 序列化
    std::string json_str = resp.Serialize();

    // 5. 添加自定义长度
    std::string send_str = Encode(json_str); // 携带长度的应答报文了"len\\r\\n{result:XXX, code:XX}\\r\\n"

    // 6. 直接发送
    sock->Send(send_str);
    }
    }
    else if (n == 0)
    {
    LOG(LogLevel::INFO) << "client:" << client.StringAddr() << "Quit!";
    break;
    }
    else
    {
    LOG(LogLevel::WARNING) << "client:" << client.StringAddr() << ", recv error";
    break;
    }
    }
    }
    bool GetResponse(std::shared_ptr<Socket> &client, std::string &resp_buff, Response *resp)
    {
    // 面向字节流,你怎么保证,你的client读到的 一个网络字符串,就一定是一个完整的请求呢??
    while (true)
    {
    int n = client->Recv(&resp_buff);
    if (n > 0)
    {
    // std::cout << "———–resp_buffer————–" << std::endl;
    // std::cout << resp_buff << std::endl;
    // std::cout << "————————————" << std::endl;

    // 成功
    std::string json_package;
    // 1. 解析报文,提取完整的json请求,如果不完整,就让服务器继续读取
    // bool ret = Decode(resp_buff, &json_package);
    // if (!ret)
    // continue;

    while (Decode(resp_buff, &json_package))
    {
    // std::cout << "———-response json—————" << std::endl;
    // std::cout << json_package << std::endl;
    // std::cout << "————————————–" << std::endl;

    // std::cout << "———–resp_buffer————–" << std::endl;
    // std::cout << resp_buff << std::endl;
    // std::cout << "————————————" << std::endl;
    // 2. 走到这里,我能保证,我一定拿到了一个完整的应答json报文
    // 2. 反序列化
    resp->Deserialize(json_package);
    }
    return true;
    }
    else if (n == 0)
    {
    std::cout << "server quit " << std::endl;
    return false;
    }
    else
    {
    std::cout << "recv error" << std::endl;
    return false;
    }
    }
    }
    std::string BuildRequestString(int x, int y, char oper)
    {
    // 1. 构建一个完整的请求
    Request req(x, y, oper);

    // 2. 序列化
    std::string json_req = req.Serialize();

    // // 2.1 debug
    // std::cout << "————json_req string————" << std::endl;
    // std::cout << json_req << std::endl;
    // std::cout << "—————————————" << std::endl;

    // 3. 添加长度报头
    return Encode(json_req);
    }
    ~Protocol()
    {
    }

    private:
    // 因为我们用的是多进程
    // Request _req;
    // Response _resp;
    func_t _func;
    };


    三、Cal.hpp:纯粹的业务层

    3.1 文件存在的意义

    这是唯一不需要关心网络的文件!只负责:

    • 拿到 Request(已经反序列化好的结构体)

    • 计算结果

    • 返回 Response

    3.2 Execute 函数详解

    Response Execute(Request &req) {
    Response resp(0, 0); // result=0, code=0(成功)

    switch (req.Oper()) {
    case '+': resp.SetResult(req.X() + req.Y()); break;
    case '-': resp.SetResult(req.X() – req.Y()); break;
    case '*': resp.SetResult(req.X() * req.Y()); break;
    case '/':
    if (req.Y() == 0) resp.SetCode(1); // 除零错误
    else resp.SetResult(req.X() / req.Y());
    break;
    case '%':
    if (req.Y() == 0) resp.SetCode(2); // 模零错误
    else resp.SetResult(req.X() % req.Y());
    break;
    default: resp.SetCode(3); // 非法操作符
    }
    return resp;
    }

    设计亮点:

    • 零耦合:不知道 JSON、不知道 Socket、不知道 TCP

    • 错误码体系:code=1/2/3 让客户端能给出精确错误提示

    • 异常安全:除零不会崩溃,而是返回错误码

    #pragma once

    #include "Protocol.hpp"
    #include <iostream>

    class Cal
    {
    public:
    Response Execute(Request &req)
    {
    Response resp(0, 0); // code: 0表示成功
    switch (req.Oper())
    {
    case '+':
    resp.SetResult(req.X() + req.Y());
    break;
    case '-':
    resp.SetResult(req.X() – req.Y());

    break;
    case '*':
    resp.SetResult(req.X() * req.Y());
    break;
    case '/':
    {
    if (req.Y() == 0)
    {
    resp.SetCode(1); // 1除零错误
    }
    else
    {
    resp.SetResult(req.X() / req.Y());
    }
    }
    break;
    case '%':
    {
    if (req.Y() == 0)
    {
    resp.SetCode(2); // 2 mod 0 错误
    }
    else
    {
    resp.SetResult(req.X() % req.Y());
    }
    }
    break;
    default:
    resp.SetCode(3); // 非法操作
    break;
    }

    return resp;
    }
    };

    赞(0)
    未经允许不得转载:171主机测评 » 网络计算器:理解序列化与反序列化(中)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址