当前位置: 首页 > news >正文

网站空间到期提示推广公司有哪些

网站空间到期提示,推广公司有哪些,上海的广告公司有哪些,网络基础知识#x1f30e; 实现网络版计算器【上】 文章目录#xff1a; 实现网络版计算器【上】 自定义协议       制定自定义协议 Jsoncpp序列化反序列化       Json::Value类       Jsoncpp序列化       Jsoncpp反序列化 自定义协议序列化反序列化      … 实现网络版计算器【上】 文章目录 实现网络版计算器【上】 自定义协议       制定自定义协议 Jsoncpp序列化反序列化       Json::Value类       Jsoncpp序列化       Jsoncpp反序列化 自定义协议序列化反序列化       Service 服务改写       服务器端完结 自定义协议 ✈️制定自定义协议 在上一篇我们说了两台机器想要通过网络进行通信那么通信双方所遵循的协议必须相同应用层也是如此大部分情况双方首先约定好数据传输格式那么一端计算机发送的数据另外一端也就能相应的解析。 那为什么我们需要序列化和反序列化呢 我们既然定义好了双方的通信协议那么我们直接按照通信协议进行发送不就行了这是因为在很多时候大型项目里一般底层代码都不会改变但是上层就说不准了双方的结构化数据很容易会因为业务需求的变动而变动每一次变动都可能需要去处理跨平台问题。   比如Linux x64平台与Linux x32平台的内存对齐方式就不同如果双方协议一直在改变那么就必须要一同处理这种平台差异是一种费时费力不讨好的表现。   但是我们在双发发数据之前进行了序列化进行了字符串式的处理相当于添加了一层软件层。这样上层无论怎么变底层收到的代码都是字符串把业务逻辑的改变抛给上层解决这样就不用因为上层的变动而更改底层代码逻辑。 所以序列化与反序列化问题显得尤为重要就上一篇所谈论的计算问题进行序列化与反序列化处理。处理两个模块一个是客户端发来的请求一个是服务器端处理请求后返回的响应。所以在这里设计两个类 Request 和 Response。 我们目的是实现网络版计算器客户端发送Request其中包含左操作数右操作数以及运算符。服务器端需要对Request进行处理但是不排除客户端发送的数据是非法运算所以Response类不仅记录结果还需要记录运算错误方式。同时双方都需要进行序列化和反序列化操作 namespace protocol_ns {class Request{public:Request(){}Request(int x, int y, char oper):_x(x), _y(y), _oper(oper){}bool Serialize(const std::string *message)// 序列化{}bool Deserialize(const std::string in)// 反序列化{}private:int _x;int _y;char _oper;// - * / %, _x _oper _y};class Response{public:Response(){}Response(int result, int code):_result(result), _code(code){}bool Serialize(const std::string *message)// 序列化{}bool Deserialize(const std::string in)// 反序列化{}private:int _result;int _code;// 0: success, 1: 除0, 2: 非法操作}; } // namespace protocol_ns上述的Request与Response就是双方约定的协议那么具体的协议内容如何进行规定呢我们知道我们发送的数据很可能会积压在发送缓冲区而Tcp一旦发送有可能一次发送的是多个序列化之后的字符串那么服务器端在收到这些数据之后需要对每一条完整的数据进行区分。甚至发送过去的数据不完整需要等待下一次发送哪些能处理哪些需要延迟处理都是我们需要考虑的。 既然是协议我们就采用其他网络协议那样定义为 报文 报头 有效载荷我们进行如下定义有效载荷长度\r\n有效载荷 如果你愿意你也可以在报头部分加上类型等判定比如 有效载荷长度数据类型\r\n有效载荷 不过这里我们不搞这么麻烦了就采用前面一种报文方式。 其中 \r\n 代表分隔符将报头部分与 有效载荷进行分离。比如一个客户端发来请求的格式就是有效载荷长度\r\n_x _oper _y\r\n有效载荷长度不记录分隔符只记录引号内有效载荷的长度。 那么如果服务器端收到的字符串进行解析报头部分显示有效载荷长度是100但是现在只有50所以我们就需要在等数据完整再进行处理。 Jsoncpp序列化反序列化 Jsoncpp 是一个用于处理 JSON 数据的 C 库。它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C 数据结构的功能。Jsoncpp 是开源的广泛用于各种需要处理 JSON 数据的 C 项目中。 Jsoncpp特性 简单易用Jsoncpp 提供了直观的 API使得处理 JSON 数据变得简单。高性能Jsoncpp 的性能经过优化能够高效地处理大量 JSON 数据。全面支持支持 JSON 标准中的所有数据类型包括对象、数组、字符串、数字、布尔值和 null。错误处理在解析 JSON 数据时Jsoncpp 提供了详细的错误信息和位置方便开发者调试。 其中在Linux环境下安装Jsoncpp库的命令如下 ubuntu: sudo apt-get install libjsoncpp-dev Centos: sudo yum install jsoncpp-devel✈️Json::Value类 Json::Value 是 Jsoncpp 库中的一个重要类用于表示和操作 JSON 数据结构。以下是一些常用的 Json::Value 操作列表: 构造函数 Json::Value()默认构造函数创建一个空的 Json::Value 对象。Json::Value(ValueType type, bool allocated false)根据给定的ValueType如 nullValue, intValue, stringValue 等创建一个 Json::Value 对象。 访问元素: 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类型的键。 类型检查 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()检查值是否为对象即键值对的集合。 赋值和类型转换: 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()将值转换为字符串类型如果可能。 数组和对象操作: 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::ValuedefaultValue Json::nullValue)在对象中插入或访问一个元素如果键不存在则使用默认值。Json::Value operator[](const std::string key, const Json::Value defaultValue Json::nullValue)同上但使用 std::string类型。 ✈️Jsoncpp序列化 序列化指的是将数据结构或对象转换为一种格式以便在网络上传输或存储到文件中。Jsoncpp 提供了多种方式进行序列化这里不再做详细解释直接使用最简单的两种展示给大家 使用 Json::FastWriter 进行Json格式序列化 首先我们先定义结构化数据Stu结构体内记录的是nameageweight首先我们需要使用 Json::Value 对象将结构化数据转化为字符串 #include iostream #include jsoncpp/json/json.hstruct stu {std::string name;int age;double weight; };int main() {// 结构化数据struct stu zs {阿熊, 20, 80};// 转换为字符串Json::Value root;root[name] zs.name;root[age] zs.age;root[weight] zs.weight;// to do ...return 0; }接着使用 Json::Writer 对象将root的各个分散字符串转化为一个字符串 int main() {// 结构化数据struct stu zs {阿熊, 20, 80};// 转换为字符串Json::Value root;root[name] zs.name;root[age] zs.age;root[weight] zs.weight;Json::FastWriter writer;std::string str writer.write(root);std::cout str;return 0; }这里还有一个需要注意的点当我们在Linux下进行编译的时候直接编译会报如下错误 这是因为Jsoncpp库属于第三方库要想使用Jsoncpp库就必须在编译时带上 -ljsoncpp 选项表示链接到Jsoncpp库 上面的数据实际上就是结构化数据进行序列化之后的字符串其原本应该是{age:20,name:阿熊,weight:80}。 使用 Json::StyleWriter 进行Json格式序列化 代码还是上述的代码只是把Json::FastWriter类型替换为 Json::StyleWriter 类型 #include iostream #include jsoncpp/json/json.hstruct stu {std::string name;int age;double weight; };int main() {// 结构化数据struct stu zs {阿熊, 20, 80};// 转换为字符串Json::Value root;root[name] zs.name;root[age] zs.age;root[weight] zs.weight;// Json::FastWriter writer;Json::StyledWriter writer;std::string str writer.write(root);std::cout str;return 0; }这两种序列化方式我们采用任何一种即可。 ✈️Jsoncpp反序列化 反序列化指的是将序列化后的数据重新转换为原来的数据结构或对象。Jsoncpp 提供了以下方法进行反序列化 首先我们预先将Jsoncpp序列化后的字符串信息放在了一个txt文件当中将来只需要从文件中读取信息并进行反序列化即可向out.txt文件中读取信息 #include iostream #include fstream #include jsoncpp/json/json.hstruct stu {std::string name;int age;double weight;public:void Debug(){std::cout name std::endl;std::cout age std::endl;std::cout weight std::endl;} };int main() {// 读取字符串信息std::ifstream in(out.txt);if(!in.is_open()) return 1;char buffer[1024];in.read(buffer, sizeof(buffer));in.close();return 0; }接下来就是进行反序列化的过程我们使用 Json::Reader 对象调用 parse() 接口把序列化的字符串进行分割到 Json::Value 的对象当中最后再将Stu对象的各个值拷贝。 #include iostream #include fstream #include jsoncpp/json/json.hstruct stu {std::string name;int age;double weight;public:void Debug(){std::cout name std::endl;std::cout age std::endl;std::cout weight std::endl;} };int main() {std::ifstream in(out.txt);if(!in.is_open()) return 1;char buffer[1024];in.read(buffer, sizeof(buffer));in.close();std::string json_str buffer;Json::Value root;Json::Reader reader;bool ret reader.parse(json_str, root);(void)ret;struct stu zs;zs.name root[name].asString();zs.age root[age].asInt();zs.weight root[weight].asDouble();zs.Debug();return 0; }自定义协议序列化反序列化 经过上述的json序列化和反序列化的过程我们可以将此应用到我们自定义协议 Request 和 Response类当中的序列化和反序列化 #pragma once#include iostream #include string #include fstream #include jsoncpp/json/json.hnamespace protocol_ns {class Request{public:Request(){}Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper){}bool Serialize(std::string *out){// 转换成为字符串Json::Value root;root[x] _x;root[y] _y;root[oper] _oper;Json::FastWriter writer;// Json::StyledWriter writer;*out writer.write(root);return true;}bool Deserialize(const std::string in) // 你怎么知道你读到的in 就是完整的一个请求呢{Json::Value root;Json::Reader reader;bool res reader.parse(in, root);if (!res)return false;_x root[x].asInt();_y root[y].asInt();_oper root[oper].asInt();return true;}public:int _x;int _y;char _oper; // -*/% _x _oper _y}; // --- 字符串class Response{public:Response(){}Response(int result, int code) : _result(result), _code(code){}bool Serialize(std::string *out){// 转换成为字符串Json::Value root;root[result] _result;root[code] _code;Json::FastWriter writer;// Json::StyledWriter writer;*out writer.write(root);return true;}bool Deserialize(const std::string in){Json::Value root;Json::Reader reader;bool res reader.parse(in, root);if (!res)return false;_result root[result].asInt();_code root[code].asInt();return true;}public:int _result; // 结果int _code; // 0:success 1: 除0 2: 非法操作 3. 4. 5}; // --- 字符串 }序列化之后的字符串还不够我们还需要给字符串添加报头以及分隔符组成一个网络报文发送给对端所以在制定协议当中我们需要添加Encode()接口对有效载荷进行封装 const std::string SEP \r\n; // 分隔符std::string Encode(const std::string json_str) {int json_str_len json_str.size();std::string proto_str std::to_string(json_str_len);proto_str SEP;proto_str json_str;proto_str SEP;return proto_str; }那么既然有对有效载荷的封装就一定存在对网络报文的解包所以Decode()接口也是必须的接口用来对我们自定义网络报文进行解包首先我们需要寻找分隔符如果连报头都找不到就说明这批数据并不是自己的数据直接返回一个空串。那么接着就一定会带有\r\n。 除了完整的分隔符以外我们还必须得收到报头部分也就是有效载荷长度信息如果没有找到报头部分直接返回空串。这个时候往后执行就必定能拿到报头信息接下来就是有效载荷部分我们知道有效载荷两边都有分隔符如果想要Decode()接口确认一个完整的请求应该至少有 初始分隔符长度 有效载荷的长度 两个分隔符的长度这样才能保证Decode的数据至少有一个完整报文 td::string Decode(std::string inbuffer) {auto pos inbuffer.find(SEP);if (pos std::string::npos)// 未发现分隔符的位置直接返回一个空串return std::string();std::string len_str inbuffer.substr(0, pos);if (len_str.empty())return std::string();int packlen std::stoi(len_str);int total packlen len_str.size() 2 * SEP.size();// 一个完整报文长度if (inbuffer.size() total)// 如果没有一个完整报文直接返回空串return std::string();std::string package inbuffer.substr(pos SEP.size(), packlen);// 有效载荷进行分离inbuffer.erase(0, total);// 报文已经处理完成将处理完成后的报文删除return package; }这样一个简单的序列化和反序列化过程我们就已经完成了。 ✈️Service 服务改写 那么简单的协议框架我们就已经搭建完毕接着将视角转回到服务器端TcpServer 我们已经改写完毕那么就需要再main函数中将Service 接口进行完善并编写启动服务器的demo。 #include iostream #include functional #include memory#include Log.hpp #include Protocol.hpp #include TcpServer.hpp #include CalCulate.hppusing namespace protocol_ns;void Usage(std::string proc) {std::cout Usage:\n\t proc local_port\n std::endl; }void Service(socket_sptr sockptr, InetAddr client) {LOG(DEBUG, get a new link, info %s:%d, fd : %d, client.IP().c_str(), client.Port(), sockfd);std::string clientaddr [ client.IP() : std::to_string(client.Port()) ]# ;while(true){char inbuffer[1024];ssize_t n read(sockfd, inbuffer, sizeof(inbuffer) - 1);if(n 0){inbuffer[n] 0;std::cout clientaddr inbuffer std::endl;std::string echo_string [server echo]# ;echo_string inbuffer;write(sockfd, echo_string.c_str(), echo_string.size());}else if(n 0){//client 退出 关闭链接LOG(INFO, %s quit, clientaddr.c_str());break;}else{LOG(ERROR, read error, clientaddr.c_str());break;}}::close(sockfd); }// ./tcpserver port int main(int argc, char *argv[]) {if (argc ! 2){Usage(argv[0]);return 1;}uint16_t port std::stoi(argv[1]);std::unique_ptrTcpServer tsvr std::make_uniqueTcpServer(port, Service);tsvr-Loop();return 0; }我们将Service 接口中的close放在线程回调当中具体的服务不用管是否需要关闭文件描述符而在 HandlerSock 中没有具体的sockfd虽然 ThreadData类内有构造 Socket 的智能指针但是我们并没有对应的Get函数将私有成员变量返回出来所以在模版方法模式中我们应该添加一些常用的接口 class Socket {public:virtual void CreateSocketOrDie() 0; // 创建套接字virtual void BindSocketOrDie(InetAddr addr) 0; // 绑定套接字virtual void ListenSocketOrDie() 0; // 监听套接字virtual socket_sptr Accepter(InetAddr *addr) 0;virtual bool Connector(InetAddr addr) 0;virtual int SockFd() 0;// 返回sockfdvirtual int Recv(std::string *out) 0;// 接收消息virtual int Send(std::string in) 0; // 发送消息// to do... };父类构建了抽象函数那么子类 TcpSocket 必须对父类抽象函数进行重写 int SockFd() override {return _sockfd; }int Recv(std::string *out) override {char inbuffer[1024];ssize_t n ::recv(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0);if (n 0){inbuffer[n] 0;*out inbuffer;// 接收文件采用的是 表示在out中追加数据}return n; }int Send(std::string in) override {int n ::send(_sockfd, in.c_str(), in.size(), 0);return n; }注意在Recv()接口中我们将读取的数据追加到out中这是因为我们每次读取的并不一定是完整的序列化字符串所以我们需要对每一次来的数据进行追加尽量组成完整的请求。那么线程的回调函数就可以通过ThreadData 对象调用 TcpSocket 的 SockFd() 接口了 static void* HandlerSock(void* args) {pthread_detach(pthread_self());ThreadData* td static_castThreadData*(args);td-self-_service(td-sockfd, td-clientaddr);::close(td-sockfd-SockFd());// 不关闭会导致 文件描述符泄漏的问题(文件描述符不归还)delete td;return nullptr; }这个时候Service 服务我们不再直接使用原生 recv接口了所以我们要对Service 进行改写我们需要思考一个问题我们怎么能保证自己读取的是一个完整的客户端请求(在原本的Service接口中我们并没有做这样的处理也没关心过这样的问题所以改写是必不可少的)尽管在Recv()中我们是进行追加接收信息的但是发送信息的是Tcp不一定会一次性发送一次完整的报文所以我们无法保证每一次都是完整的请求。那么我们检测到如果当前的报文不完整我们进行循环等待新的数据直到报文完整 void ServiceHelper(socket_sptr sockptr, InetAddr client) {int sockfd sockptr-SockFd();LOG(DEBUG, get a new link, info %s:%d, fd : %d, client.IP().c_str(), client.Port(), sockfd);std::string clientaddr [ client.IP() : std::to_string(client.Port()) ]# ;std::string inbuffer;while (true){sleep(5);Request req;// 1. 读取数据int n sockptr-Recv(inbuffer);if (n 0){LOG(DEBUG, client %s quit, clientaddr.c_str());break;}// 2. 分析数据std::string package Decode(inbuffer);// 使用了Decode()接口那么就一定能保证读取到一个完整的json串if(package.empty()) continue;req.Deserialize(package);// 反序列化有效载荷就能够得到完整的信息了 }我们将Service封装为一个类这样方便将来进行回调而回调函数就是具体的对已经反序列化的结果进行算术运算参数应当是收到Request返回一个Response: using callback_t std::functionResponse(const Request req);// 我们发送一个Request返回一个Responseclass Service { public:Service(callback_t cb): _cb(cb){}void ServiceHelper(socket_sptr sockptr, InetAddr client){int sockfd sockptr-SockFd();LOG(DEBUG, get a new link, info %s:%d, fd : %d, client.IP().c_str(), client.Port(), sockfd);std::string clientaddr [ client.IP() : std::to_string(client.Port()) ]# ;std::string inbuffer;while (true){sleep(5);Request req;// 1. 读取数据int n sockptr-Recv(inbuffer);if (n 0){LOG(DEBUG, client %s quit, clientaddr.c_str());break;}// 2. 分析数据std::string package Decode(inbuffer);// 使用了Decode()接口那么就一定能保证读取到一个完整的json串if(package.empty()) continue;// 3. 反序列化req.Deserialize(package);// 反序列化有效载荷就能够得到完整的信息了}}private:callback_t _cb;// 回调 };以上属于读取与分析数据的部分以及反序列化获取完整报文。获取了完整的报文之后我们就可以拿着客户端发来的数据做业务处理我们的业务是模拟简单计算器我们设置的回调就是本次的业务代码我们单独将业务代码封装为一个简单的类 #pragma once#include iostream #include Protocol.hppusing namespace protocol_ns;class Calculate { public:Calculate(){}Response Excute(const Request req)// 参数为 Request类型返回值为Response类型的服务{Response resp(0, 0);switch (req._oper){case :resp._result req._x req._y;break;case -:resp._result req._x - req._y;break;case *:resp._result req._x * req._y;break;case /:{if(req._y 0){resp._code 1;}else{resp._result req._x / req._y;}break;} case %:{if(req._y 0){resp._code 2;}else{resp._result req._x % req._y;}break;} default:resp._code 3;break;}return resp;}~Calculate(){} private:};反序列化之后就需要处理客户端发来的请求处理完请求我们就可以得到一个Response也就是处理之后得到的结果接着服务器端就需要把这个结果返回给客户端所以对Response进行序列化并添加报头最后再发送到对端服务器端这次的工作就完成了 while (true) {sleep(5);Request req;// 1. 读取数据int n sockptr-Recv(inbuffer);if (n 0){LOG(DEBUG, client %s quit, clientaddr.c_str());break;}// 2. 分析数据std::string package Decode(inbuffer);// 使用了Decode()接口那么就一定能保证读取到一个完整的json串if(package.empty()) continue;// 3. 反序列化req.Deserialize(package);// 反序列化有效载荷就能够得到完整的信息了// 4. 业务处理Response resp _cb(req);// 5. 对应答进行序列化std::string send_str;resp.Serialize(send_str);std::cout send_str std::endl;// 6. 添加长度报头send_str Encode(send_str);// 7. 发送到对端sockptr-Send(send_str); }✈️服务器端完结 这样我们将Service服务改写完成而在main函数当中我们需要运行我们的服务器并且创建线程去处理相关的任务 int main(int argc, char *argv[]) {if (argc ! 2){Usage(argv[0]);return 1;}uint16_t port std::stoi(argv[1]);EnableScreen();Calculate cal;// 构造计算服务对象Service calservice(std::bind(Calculate::Excute, cal, std::placeholders::_1));// Calservice类内实现的Excute是我们用来对客户端请求处理的函数但是属于类内函数带有隐藏this指针所以我们需要对其进行绑定将this 指针绑定进来io_service_t service std::bind(Service::ServiceHelper, calservice, std::placeholders::_1, std::placeholders::_2);// 同样service也是封装为了一个类线程想要对其进行回调每次都得传Service类的构造当做第一个参数为了避免这种麻烦使用bind将this绑定std::unique_ptrTcpServer tsvr std::make_uniqueTcpServer(port, service);// 正常创建对象tsvr-Loop();// 进行循环return 0; }这样服务器端的工作我们就准备完毕。
http://www.hkea.cn/news/14440864/

相关文章:

  • 讨债公司网站建设服务平台入口
  • 如何分析网站建设知名企业官网
  • 深圳网站制作联系兴田德润亚马逊的网站建设分析
  • 手机端网站怎么制作必应网站建设
  • 有什么做视频的素材网站网站项目需求表
  • 郑州专业网页模板制作公司宿迁seo公司
  • 大浪网站建设哪些行业做网站多
  • 扬州专业做网站wordpress手机顶部菜单
  • 电商 网站 设计上海网站开发月薪多少钱
  • 网站建设销售开场拓网手机版网站管理系统
  • 网站开发和前端和数据媒体建立网站的信息集成过程
  • 网站建设咋做上海娱乐场所关闭
  • 集团公司网站源码下载dedecms_v5.6室内装饰设计公司企业网站模板.rar
  • 中国百强企业百度热搜seo
  • 网站最初的索引量从何而来wordpress怎么安装?
  • 网站开发 职位晋升路线泰安深度网络科技有限公司
  • 丢了么网站软件ui设计培训学校
  • wordpress++分页宁波关键词优化平台
  • 莆田网站建设费用数字logo创意设计
  • 广东建设局网站首页购物平台有哪些比较火
  • 电影采集网站流量室内装饰公司网站模板
  • 番禺建设网站专家运维需要掌握哪些知识
  • 谷歌网站推广优化百度网站怎么做的赚钱吗
  • 做外贸网站的好处深圳网架制作
  • 临清网站建设费用wordpress淘宝客免费版
  • 什么是小手机型网站百度一下官网网址
  • 免费建站个人网站产品详情页面设计
  • 全屏类网站建设公司入口网站app
  • 东莞设计网站公司WordPress博客定制化首页
  • 追波设计网站安卓集成wordpress