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

网站怎么做实名认证吗正规网站建设网站制作

网站怎么做实名认证吗,正规网站建设网站制作,网站界面设计要求,上海市建设安全协会 - 网站首页#x1f52d;个人主页#xff1a; 北 海 #x1f6dc;所属专栏#xff1a; Linux学习之旅、神奇的网络世界 #x1f4bb;操作环境#xff1a; CentOS 7.6 阿里云远程服务器 文章目录 #x1f324;️前言#x1f326;️正文1.协议的重要性2.什么是序列化与反序列化… 个人主页 北 海 所属专栏 Linux学习之旅、神奇的网络世界 操作环境 CentOS 7.6 阿里云远程服务器 文章目录 ️前言️正文1.协议的重要性2.什么是序列化与反序列化3.实现相关程序4.封装socket相关操作5.服务器6.序列化与反序列7.工具类8.业务处理9.报头处理10.客户端11.测试12.使用库 ️总结 ️前言 本文将介绍如何使用C实现简单的服务器和客户端通信并重点讲解序列化与反序列化的概念和实现。这篇文章将深入探究数据在网络传输中的转换过程以及如何在C中应用这些技术 ️正文 1.协议的重要性 假设张三在路上遇到了一位外国人 Jack这位外国朋友急于寻找厕所对张三进行了一波 英语 输出这可难到了张三因为他 英语 可谓是十分差劲母语的差异导致双方无法正常交流信息也无法传达张三急中生智打开了手机上的 同声传译功能可以将信息转换为对方能听懂的语言在工具的帮助之下外国友人最终知晓了厕所的位置 在这个故事中张三和外国人 Jack 就是两台主机同声传译 这个功能可以看做一种 协议可以确保对端能理解自己传达的信息协议 的出现解决了主机间的交流问题 对于网络来说协议是双方通信的基石如果没有协议那么即使数据传输的再完美也无法使用比如下面这个就是一个简单的 两正整数运算协议 协议要求发送的数据必须由两个操作数正整数和一个运算符组成并且必须遵循 x op y 这样的运算顺序 int x; int y; char op; // 运算符主机A在发送消息时需要将 操作数x、操作数y和运算符op 进行传递只要主机A和主机B都遵循这个 协议那么主机B在收到消息后一定清楚这是两个操作数和一个运算符 现在的问题是如何传递 方案一将两个操作数和一个运算符拼接在一起直接传递方案二将两个操作数和一个运算符打包成一个结构体传递 方案一直接拼接 xopy方案二封装成结构体 struct Mssage {int x;int y;char op; };无论是方案一还是方案二都存在问题前者是对端接收到消息后无法解析后者则是存在平台兼容问题不同平台的结构体内存规则可能不同会导致读取数据出错 要想确保双方都能正确理解 协议还需要进行 序列化与反序列化 处理 2.什么是序列化与反序列化 序列化是指 将一个或多个需要传递的数据按照一定的格式拼接为一条数据反序列化则是 将收到的数据按照格式解析 比如主机A想通过 两正整数运算协议 给主机B发送这样的消息 //11 int x 1; int y 1; char op ;可以根据格式这里使用 空格进行 序列化序列化后的数据长这样 // 经过序列化后得到 string msg 1 1;在经过网络传输后主机B收到了消息并根据 空格进行 反序列化成功获取了主机A发送的信息 string msg 1 1;// 经过反序列化后得到 int x 1; int y 1; char op ;这里可以将需要传递的数据存储在结构体中传递/接收 时将数据填充至类中类中提供 序列化与反序列化 的相关接口即可 class Request { public:void Serialization(string* str){}void Deserialization(const sting str){}public:int _x;int _y;char _op; };以上就是一个简单的 序列化和反序列化 流程简单来说就是 协议 定制后不能直接使用需要配合 序列化与反序列化 这样的工具理解接下来我们就基于 两正整数运算协议 编写一个简易版的网络计算器重点在于 理解协议、序列化和反序列化 3.实现相关程序 我们接下来要编写的程序从实现功能来看是十分简单的客户端给出两个正整数和一个运算符服务器计算出结果后返回 整体框架为客户端获取正整数与运算符 - 将这些数据构建出 Request 对象 - 序列化 - 将结果数据包传递给服务器 - 服务器进行反序列化 - 获取数据 - 根据数据进行运算 - 将运算结果构建出 Response 对象 - 序列化 - 将结果数据包传递给客户端 - 客户端反序列后获取最终结果 既然这是一个基于网络的简易版计算器必然离不开网络相关接口在编写 服务器 与 客户端 的逻辑之前需要先将 socket 接口进行封装方面后续的使用 4.封装socket相关操作 关于 socket 的相关操作可以看看这两篇博客《网络编程『socket套接字 ‖ 简易UDP网络程序』》、《网络编程『简易TCP网络程序』》 注当前实现的程序是基于 TCP 协议的 简单回顾下服务器需要 创建套接字、绑定IP地址和端口号、进入监听连接状态、等待客户端连接至于客户端需要 创建套接字、由操作系统绑定IP地址和端口号、连接服务器等客户端成功连上服务器后双方就可以正常进行网络通信了 为了让客户端和服务器都能使用同一个头文件我们可以把客户端和服务器需要的所有操作都进行实现各自调用即可 Sock.hpp 套接字相关接口头文件 #pragma once#include Log.hpp #include Err.hpp#include iostream #include string #include cstring #include cstdlib #include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include unistd.hclass Sock {const static int default_sock -1;const static int default_backlog 32; public:Sock():sock(default_sock){}// 创建套接字void Socket(){sock socket(AF_INET, SOCK_STREAM, 0);if(sock -1){logMessage(Fatal, Creater Socket Fail! [%d]-%s, errno, strerror(errno));exit(SOCKET_ERR);}logMessage(Debug, Creater Socket Success);}// 绑定IP与端口号void Bind(const uint16_t port){struct sockaddr_in local;memset(local, 0, sizeof(local));local.sin_family AF_INET;local.sin_port htons(port);local.sin_addr.s_addr INADDR_ANY;if(bind(sock, (struct sockaddr*)local, sizeof(local)) -1){logMessage(Fatal, Bind Socket Fail! [%d]-%s, errno, strerror(errno));exit(BIND_ERR);}logMessage(Debug, Bind Socket Success);}// 进入监听状态void Listen(){if(listen(sock, default_backlog) -1){logMessage(Fatal, Listen Socket Fail! [%d]-%s, errno, strerror(errno));exit(LISTEN_ERR);}}// 尝试处理连接请求int Accept(std::string* ip, uint16_t* port){struct sockaddr_in client;socklen_t len sizeof(client);int retSock accept(sock, (struct sockaddr*)client, len) ;if(retSock 0)logMessage(Warning, Accept Fail! [%d]-%s, errno, strerror(errno));else{*ip inet_ntoa(client.sin_addr);*port ntohs(client.sin_port);logMessage(Debug, Accept [%d - %s:%d] Success, retSock, ip-c_str(), *port);}return retSock;}// 尝试进行连接int Connect(const std::string ip, const uint16_t port){struct sockaddr_in server;memset(server, 0, sizeof(server));server.sin_family AF_INET;server.sin_port htons(port);server.sin_addr.s_addr inet_addr(ip.c_str());return connect(sock, (struct sockaddr*)server, sizeof(server));}// 获取sockint GetSock(){return sock;}// 关闭sockvoid Close(){if(sock ! default_sock)close(sock);logMessage(Debug, Close Sock Success);}~Sock(){} private:int sock; // 既可以是监听套接字也可以是连接成功后返回的套接字 };这里还需要用到之前编写的错误码和日志输出 Err.hpp 错误码头文件 #pragma onceenum {USAGE_ERR 1,SOCKET_ERR,BIND_ERR,LISTEN_ERR,CONNECT_ERR,FORK_ERR,SETSID_ERR,CHDIR_ERR,OPEN_ERR,READ_ERR, };Log.hpp 日志输出头文件 #pragma once#include iostream #include string #include vector #include cstdio #include time.h #include sys/types.h #include unistd.h #include stdarg.husing namespace std;enum {Debug 0,Info,Warning,Error,Fatal };static const string file_name log/TcpServer.log;string getLevel(int level) {vectorstring vs {Debug, Info, Warning, Error, Fatal, Unknown};//避免非法情况if(level 0 || level vs.size() - 1)return vs[vs.size() - 1];return vs[level]; }string getTime() {time_t t time(nullptr); //获取时间戳struct tm *st localtime(t); //获取时间相关的结构体char buff[128];snprintf(buff, sizeof(buff), %d-%d-%d %d:%d:%d, st-tm_year 1900, st-tm_mon 1, st-tm_mday, st-tm_hour, st-tm_min, st-tm_sec);return buff; }//处理信息 void logMessage(int level, const char* format, ...) {//日志格式日志等级 [时间] [PID] {消息体}string logmsg getLevel(level); //获取日志等级logmsg getTime(); //获取时间logmsg [ to_string(getpid()) ]; //获取进程PID//截获主体消息char msgbuff[1024];va_list p;va_start(p, format); //将 p 定位至 format 的起始位置vsnprintf(msgbuff, sizeof(msgbuff), format, p); //自动根据格式进行读取va_end(p);logmsg { string(msgbuff) }; //获取主体消息// 直接输出至屏幕上cout logmsg endl;// //持久化。写入文件中// FILE* fp fopen(file_name.c_str(), a); //以追加的方式写入// if(fp nullptr) return; //不太可能出错// fprintf(fp, %s\n, logmsg.c_str());// fflush(fp); //手动刷新一下// fclose(fp);// fp nullptr; } 有了 Sock.hpp 头文件后服务器/客户端就可以专注于逻辑编写了 5.服务器 首先准备好 TcpServer.hpp 头文件其中实现了服务器初始化、服务器启动、序列化与反序列化等功能 TcpServer.hpp 服务器头文件 #pragma once#include Sock.hpp#include iostream #include string #include pthread.hnamespace CalcServer {class TcpServer;// 线程所需要的信息类class ThreadDate{public:ThreadDate(int sock, std::string ip, uint16_t port, TcpServer* ptsvr):_sock(sock), _ip(ip), _port(port), _ptsvr(ptsvr){}~ThreadDate(){}int _sock;std::string _ip;uint16_t _port;TcpServer* _ptsvr; // 回指指针};class TcpServer{const static uint16_t default_port 8888;private:// 线程的执行函数static void* threadRoutine(void* args){// 线程剥离pthread_detach(pthread_self());ThreadDate* td static_castThreadDate*(args);td-_ptsvr-ServiceIO(td-_sock, td-_ip, td-_port);delete td;return nullptr;}// 进行IO服务的函数void ServiceIO(const int sock, const std::string ip, const uint16_t port){// TODO}public:TcpServer(const uint16_t port default_port):_port(port){}// 初始化服务器void Init(){_listen_sock.Socket();_listen_sock.Bind(_port);_listen_sock.Listen();}// 启动服务器void Start(){while(true){std::string ip;uint16_t port;int sock _listen_sock.Accept(ip, port);if(sock -1)continue;// 创建子线程执行业务处理pthread_t tid;ThreadDate* td new ThreadDate(sock, ip, port, this);pthread_create(tid, nullptr, threadRoutine, td);}}~TcpServer(){_listen_sock.Close();}private:Sock _listen_sock; // 监听套接字uint16_t _port; // 服务器端口号}; }上面这份代码我们之前在 《网络编程『简易TCP网络程序』》 中已经写过了本文的重点在于实现 ServiceIO() 函数现在可以先尝试编译并运行程序看看代码是否有问题 CalcServer.cc 简易计算器服务器源文件 #include iostream #include memory #include TcpServer.hppusing namespace std;int main() {unique_ptrCalcServer::TcpServer tsvr(new CalcServer::TcpServer());tsvr-Init();tsvr-Start();return 0; }Makefile 自动编译脚本 .PHONY:all all:CalcServer CalcClientCalcServer:CalcServer.ccg -o $ $^ -stdc11 -lpthreadCalcClient:CalcClient.ccg -o $ $^ -stdc11.PHONY:clean clean:rm -rf CalcServer CalcClient编译并运行程序同时查看网络使用情况 netstat -nltp此时就证明前面写的代码已经没有问题了接下来是填充 ServiceIO() 函数 6.序列化与反序列 ServiceIO() 函数需要做这几件事 读取数据反序列化业务处理序列化发送数据 除了 序列化和反序列化 外其他步骤之前都已经见过了所以我们先来看看如何实现 序列化与反序列化 ServiceIO() 函数 — 位于 TcpServer.hpp 头文件中的 TcpServer 类中 // 进行IO服务的函数 void ServiceIO(const int sock, const std::string ip, const uint16_t port) {// 1.读取数据// 2.反序列化// 3.业务处理// 4.序列化// 5.发送数据 }需要明白我们当前的 协议 为 两正整数运算分隔符为 空格客户端传给服务器两个操作数和一个运算符服务器在计算完成后将结果返回为了方便数据的读写可以创建两个类Request 和 Response类中的成员需要遵循协议要求并在其中支持 序列化与反序列化 Protocol.hpp 协议处理相关头文件 #pragma once #include stringnamespace my_protocol { // 协议的分隔符 #define SEP #define SEP_LEN strlen(SEP)class Request{public:Request(int x 0, int y 0, char op ): _x(x), _y(y), _op(op){}// 序列化bool Serialization(std::string *outStr){}// 反序列化bool Deserialization(const std::string inStr){}~Request(){}public:int _x;int _y;char _op;};class Response{public:Response(int result 0, int code 0):_result(result), _code(code){}// 序列化bool Serialization(std::string *outStr){}// 反序列化bool Deserialization(const std::string inStr){}~Response(){}public:int _result; // 结果int _code; // 错误码}; }接下来就是实现 Serialization() 和 Deserialization() 这两个接口 Serialization()将类中的成员根据协议要求拼接成一个字符串Deserialization()将字符串根据格式进行拆解 Request 类 — 位于 Protocol.hpp 协议相关头文件中 class Request { public:Request(int x 0, int y 0, char op ): _x(x), _y(y), _op(op){}// 序列化bool Serialization(std::string *outStr){*outStr ; // 清空std::string left Util::IntToStr(_x);std::string right Util::IntToStr(_y);*outStr left SEP _op SEP right;return true;}// 反序列化bool Deserialization(const std::string inStr){std::vectorstd::string result;Util::StringSplit(inStr, SEP, result);// 协议规定只允许存在两个操作数和一个运算符if(result.size() ! 3)return false;// 规定运算符只能为一个字符if(result[1].size() ! 1)return false;_x Util::StrToInt(result[0]);_y Util::StrToInt(result[2]);_op result[1][0];return true;}~Request(){}public:int _x;int _y;char _op; };其中涉及 IntToStr()、StringSplit()、StrToInt() 等接口等后面实现 Response 类时也需要使用所以我们可以直接将其放入 Util 工具类中 7.工具类 工具类中包含了常用的工具函数 Util.hpp 工具类 #pragma once #include string #include vectorclass Util { public:static std::string IntToStr(int val){// 特殊处理if(val 0)return 0;std::string str;while(val){str (val % 10) 0;val / 10;}int left 0;int right str.size() - 1;while(left right)std::swap(str[left], str[right--]);return str;}static int StrToInt(const std::string str){int ret 0;for(auto e : str)ret (ret * 10) (e - 0);return ret;}static void StringSplit(const std::string str, const std::string sep, std::vectorstd::string* result){size_t left 0;size_t right 0;while(right str.size()){right str.find(sep, left);if(right std::string::npos)break;result-push_back(str.substr(left, right - left));left right sep.size();}if(left str.size())result-push_back(str.substr(left));} };接下来就可以顺便把 Response 中的 Serialization() 和 Deserialization() 这两个接口给实现了逻辑和 Request 类中的差不多当然结果也要符合 协议 的规定使用 空格进行分隔 Response 类 — 位于 Protocol.hpp 协议相关头文件中 class Response { public:Response(int result 0, int code 0):_result(result), _code(code){}// 序列化bool Serialization(std::string *outStr){*outStr ; // 清空std::string left Util::IntToStr(_result);std::string right Util::IntToStr(_code);*outStr left SEP right;return true;}// 反序列化bool Deserialization(const std::string inStr){std::vectorstd::string result;Util::StringSplit(inStr, SEP, result);if(result.size() ! 2)return false;_result Util::StrToInt(result[0]);_code Util::StrToInt(result[1]);return true;}~Response(){}public:int _result; // 结果int _code; // 错误码 };现在 ServiceIO() 中可以进行 序列化和反序列化 了 ServiceIO() 函数 — 位于 TcpServer.hpp 头文件中的 TcpServer 类中 // 进行IO服务的函数 void ServiceIO(const int sock, const std::string ip, const uint16_t port) {while(true){// 1.读取数据std::string package; // 假设这是已经读取到的数据包格式为 1 1// 2.反序列化my_protocol::Request req;if(req.Deserialization(package) false){logMessage(Warning, Deserialization fail!);continue;}// 3.业务处理// TODOmy_protocol::Response resp; // 业务处理完成后得到的响应对象// 4.序列化std::string sendMsg;resp.Serialization(sendMsg);// 5.发送数据} }至于业务处理函数如何实现交给上层决定也就是 CalcServer.cc 8.业务处理 TcpServer 中的业务处理函数由 CalcServer.cc 传递规定业务处理函数的类型为 void(Request, Response*) Calculate() 函数 — 位于 CalcServer.cc #include TcpServer.hpp #include Protocol.hpp#include iostream #include memory #include functional #include unordered_mapusing namespace std;void Calculate(my_protocol::Request req, my_protocol::Response* resp) {// 这里只是简单的计算而已int x req._x;int y req._y;char op req._op;unordered_mapchar, functionint() hash {{, [](){ return x y; }},{-, [](){ return x - y; }},{*, [](){ return x * y; }},{/, [](){if(y 0){resp-_code 1;return 0;} return x / y; }},{%, [](){ if(y 0){resp-_code 2;return 0;}return x % y;}}};if(hash.count(op) 0)resp-_code 3;elseresp-_result hash[op](); }int main() {unique_ptrCalcServer::TcpServer tsvr(new CalcServer::TcpServer(Calculate));tsvr-Init();tsvr-Start();return 0; }既然 CalcServer 中传入了 Calculate() 函数对象TcpServer 类中就得接收并使用也就是业务处理 TcpServer.hpp 头文件 #pragma once#include Sock.hpp #include Protocol.hpp#include iostream #include string #include functional #include pthread.hnamespace CalcServer {using func_t std::functionvoid(my_protocol::Request, my_protocol::Response*);class TcpServer{const static uint16_t default_port 8888;private:// ...// 进行IO服务的函数void ServiceIO(const int sock, const std::string ip, const uint16_t port){while(true){// 1.读取数据std::string package; // 假设这是已经读取到的数据包格式为 1 1// 2.反序列化my_protocol::Request req;if(req.Deserialization(package) false){logMessage(Warning, Deserialization fail!);continue;}// 3.业务处理my_protocol::Response resp; // 业务处理完成后得到的响应对象_func(req, resp);// 4.序列化std::string sendMsg;resp.Serialization(sendMsg);cout sendMsg endl;// 5.发送数据}}public:// ...private:// ...func_t _func; // 上层传入的业务处理函数}; }这就做好业务处理了ServiceIO() 函数已经完成了 50% 的工作接下来的重点是如何读取和发送数据 TCP 协议是面向字节流的这也就意味着数据在传输过程中可能会因为网络问题分为多次传输这也就意味着我们可能无法将其一次性读取完毕需要制定一个策略来确保数据全部递达 9.报头处理 如何确认自己已经读取完了所以数据答案是提前知道目标数据的长度边读取边判断 数据在发送时是需要在前面添加 长度 这个信息的通常将其称为 报头而待读取的数据称为 有效载荷报头 和 有效载荷 的关系类似于快递单与包裹的关系前者是后者成功递达的保障 最简单的 报头 内容就是 有效载荷 的长度 问题来了如何区分 报头 与 有效载荷 呢 当前可以确定的是我们的报头中只包含了长度这个信息可以通过添加特殊字符如 \r\n 的方式进行区分后续无论有效载荷变成什么内容都不影响我们通过报头进行读取 报头处理属于协议的一部分 所以在正式读写数据前需要解决 报头 的问题收到数据后移除报头发送数据前添加报头 ServiceIO() 函数 — 位于 TcpServer.hpp 头文件中的 TcpServer 类中 // 进行IO服务的函数 void ServiceIO(const int sock, const std::string ip, const uint16_t port) {while(true){// 1.读取数据std::string package; // 假设这是已经读取到的数据包格式为 5\r\n1 1// 2.移除报头// 3.反序列化my_protocol::Request req;if(req.Deserialization(package) false){logMessage(Warning, Deserialization fail!);continue;}// 4.业务处理my_protocol::Response resp; // 业务处理完成后得到的响应对象_func(req, resp);// 5.序列化std::string sendMsg;resp.Serialization(sendMsg);cout sendMsg endl;// 6.添加报头// 7.发送数据} }在 Protocol.hpp 中完成报头的添加和移除 Protocol.hpp 协议相关头文件 #define HEAD_SEP \r\n #define HEAD_SEP_LEN strlen(HEAD_SEP)// 添加报头 void AddHeader(std::string str) {// 先计算出长度size_t len str.size();std::string strLen Util::IntToStr(len);// 再进行拼接str strLen HEAD_SEP str; }// 移除报头 void RemoveHeader(std::string str, size_t len) {// len 表示有效载荷的长度str str.substr(str.size() - len); }报头有效载荷需要通过 read() 或者 recv() 函数从网络中读取并且需要边读取边判断 ReadPackage() 读取函数 — 位于 Protocol.hpp 头文件 #define BUFF_SIZE 1024// 读取数据 int ReadPackage(int sock, std::string inBuff, std::string* package) {// 也可以使用 read 函数char buff[BUFF_SIZE];int n recv(sock, buff, sizeof(buff) - 1, 0);if(n 0)return -1; // 表示读取失败else if(n 0)return 0; // 需要继续读取buff[n] \0;inBuff buff;// 判断 inBuff 中是否存在完整的数据包报头\r\n有效载荷int pos inBuff.find(HEAD_SEP);if(pos std::string::npos)return -1;std::string strLen inBuff.substr(0, pos); // 有效载荷的长度int packLen strLen.size() HEAD_SEP_LEN Util::StrToInt(strLen); // 这是 报头分隔符有效载荷 的总长度if(inBuff.size() packLen)return -1;*package inBuff.substr(0, packLen); // 获取 报头分隔符有效载荷 也就是数据包inBuff.erase(0, packLen); // 从缓冲区中取走字符串return Util::StrToInt(strLen); }此时对于 ServiceIO() 函数来说核心函数都已经准备好了只差拼装了 ServiceIO() 函数 — 位于 TcpServer.hpp 头文件中的 TcpServer 类中 // 进行IO服务的函数 void ServiceIO(const int sock, const std::string ip, const uint16_t port) {std::string inBuff;while(true){// 1.读取数据std::string package; // 假设这是已经读取到的数据包格式为 5\r\n1 1int len my_protocol::ReadPackage(sock, inBuff, package);if(len 0)break;else if(len 0)continue;// 2.移除报头my_protocol::RemoveHeader(package, len);// 3.反序列化my_protocol::Request req;if(req.Deserialization(package) false){logMessage(Warning, Deserialization fail!);continue;}// 4.业务处理my_protocol::Response resp; // 业务处理完成后得到的响应对象_func(req, resp);// 5.序列化std::string sendMsg;resp.Serialization(sendMsg);cout sendMsg endl;// 6.添加报头my_protocol::AddHeader(sendMsg);// 7.发送数据send(sock, sendMsg.c_str(), sendMsg.size(), 0);} }至此服务器编写完毕接下来就是进行客户端的编写了 10.客户端 编写客户端的 TcpCilent.hpp 头文件 TcpClient.hpp 客户端头文件 #pragma once#include Sock.hpp #include Protocol.hpp #include Log.hpp #include Err.hpp#include iostream #include string #include unistd.hnamespace CalcClient {class TcpClient{public:TcpClient(const std::string ip, const uint16_t port):_server_ip(ip), _server_port(port){}void Init(){_sock.Socket();}void Start(){int i 5;while(i 0){if(_sock.Connect(_server_ip, _server_port) ! -1)break;logMessage(Warning, Connect Server Fail! %d, i--);sleep(1);}if(i 0){logMessage(Fatal, Connect Server Fail!);exit(CONNECT_ERR);}// 执行读写函数ServiceIO();}void ServiceIO(){while(true){std::string str;std::cout Please Enter: ;std::getline(std::cin, str);// 1.判断是否需要退出if(str quit)break;// 2.分割输入的字符串my_protocol::Request req;[](){std::string ops -*/%;int pos 0;for(auto e : ops){pos str.find(e);if(pos ! std::string::npos)break;}req._x Util::StrToInt(str.substr(0, pos));req._y Util::StrToInt(str.substr(pos 1));req._op str[pos];}();// 3.序列化std::string sendMsg;req.Serialization(sendMsg);// 4.添加报头my_protocol::AddHeader(sendMsg);// 5.发送数据send(_sock.GetSock(), sendMsg.c_str(), sendMsg.size(), 0);// 6.获取数据std::string inBuff;std::string package;int len 0;while(true){len my_protocol::ReadPackage(_sock.GetSock(), inBuff, package);if(len 0)exit(READ_ERR);else if(len 0)break;}// 7.移除报头my_protocol::RemoveHeader(package, len);// 8.反序列化my_protocol::Response resp;if(resp.Deserialization(package) false){logMessage(Warning, Deserialization fail!);continue;}// 9.获取结果std::cout The Result: resp._result resp._code endl;}}~TcpClient(){_sock.Close();}private:Sock _sock;std::string _server_ip;uint16_t _server_port;}; }注意 客户端也需要边读取边判断确保读取内容的完整性 下面是 CalcClient.cc 的代码 CalcClient.cc 客户端源文件 #include TcpClient.hpp#include iostream #include memoryusing namespace std;int main() {unique_ptrCalcClient::TcpClient tclt(new CalcClient::TcpClient(127.0.0.1, 8888));tclt-Init();tclt-Start();return 0; }11.测试 编译并运行代码 可以在代码中添加一定的输出语句感受 序列化和反序列化 的过程 12.使用库 事实上序列化与反序列化 这种工作轮不到我们来做因为有更好更强的库比如 Json、XML、Protobuf 等 比如我们就可以使用 Json 来修改程序 首先需要安装 json-cpp 库如果是 CentOS7 操作系统的可以直接使用下面这条命令安装 yum install -y jsoncpp-devel安装完成后可以引入头文件 jsoncpp/json/json.h 然后就可以在 Protocol.hpp 头文件中进行修改了如果想保留原来自己实现的 序列化与反序列化 代码可以利用 条件编译 进行区分 Protocol.hpp 协议相关头文件 #pragma once #include Util.hpp#include jsoncpp/json/json.h #include string #include vector #include cstring #include sys/types.h #include sys/socket.hnamespace my_protocol { // 协议的分隔符 #define SEP #define SEP_LEN strlen(SEP) #define HEAD_SEP \r\n #define HEAD_SEP_LEN strlen(HEAD_SEP) #define BUFF_SIZE 1024 // #define USER 1// 添加报头void AddHeader(std::string str){// 先计算出长度size_t len str.size();std::string strLen Util::IntToStr(len);// 再进行拼接str strLen HEAD_SEP str;}// 移除报头void RemoveHeader(std::string str, size_t len){// len 表示有效载荷的长度str str.substr(str.size() - len);}// 读取数据int ReadPackage(int sock, std::string inBuff, std::string* package){// 也可以使用 read 函数char buff[BUFF_SIZE];int n recv(sock, buff, sizeof(buff) - 1, 0);if(n 0)return -1; // 表示什么都没有读到else if(n 0)return 0; // 需要继续读取buff[n] 0;inBuff buff;// 判断 inBuff 中是否存在完整的数据包报头\r\n有效载荷int pos inBuff.find(HEAD_SEP);if(pos std::string::npos)return 0;std::string strLen inBuff.substr(0, pos); // 有效载荷的长度int packLen strLen.size() HEAD_SEP_LEN Util::StrToInt(strLen); // 这是 报头分隔符有效载荷 的总长度if(inBuff.size() packLen)return 0;*package inBuff.substr(0, packLen); // 获取 报头分隔符有效载荷 也就是数据包inBuff.erase(0, packLen); // 从缓冲区中取走字符串return Util::StrToInt(strLen);}class Request{public:Request(int x 0, int y 0, char op ): _x(x), _y(y), _op(op){}// 序列化bool Serialization(std::string *outStr){*outStr ; // 清空 #ifdef USERstd::string left Util::IntToStr(_x);std::string right Util::IntToStr(_y);*outStr left SEP _op SEP right; #else// 使用 JsonJson::Value root;root[x] _x;root[op] _op;root[y] _y;Json::FastWriter writer;*outStr writer.write(root); #endifstd::cout 序列化完成: *outStr std::endl std::endl;return true;}// 反序列化bool Deserialization(const std::string inStr){ #ifdef USERstd::vectorstd::string result;Util::StringSplit(inStr, SEP, result);// 协议规定只允许存在两个操作数和一个运算符if(result.size() ! 3)return false;// 规定运算符只能为一个字符if(result[1].size() ! 1)return false;_x Util::StrToInt(result[0]);_y Util::StrToInt(result[2]);_op result[1][0]; #else// 使用JsonJson::Value root;Json::Reader reader;reader.parse(inStr, root);_x root[x].asInt();_op root[op].asInt();_y root[y].asInt(); #endifreturn true;}~Request(){}public:int _x;int _y;char _op;};class Response{public:Response(int result 0, int code 0):_result(result), _code(code){}// 序列化bool Serialization(std::string *outStr){*outStr ; // 清空 #ifdef USERstd::string left Util::IntToStr(_result);std::string right Util::IntToStr(_code);*outStr left SEP right; #else// 使用 JsonJson::Value root;root[_result] _result;root[_code] _code;Json::FastWriter writer;*outStr writer.write(root); #endifstd::cout 序列化完成: *outStr std::endl std::endl;return true;}// 反序列化bool Deserialization(const std::string inStr){ #ifdef USERstd::vectorstd::string result;Util::StringSplit(inStr, SEP, result);if(result.size() ! 2)return false;_result Util::StrToInt(result[0]);_code Util::StrToInt(result[1]); #else// 使用JsonJson::Value root;Json::Reader reader;reader.parse(inStr, root);_result root[_result].asInt();_code root[_code].asInt(); #endifreturn true;}~Response(){}public:int _result; // 结果int _code; // 错误码}; }注意 因为现在使用了 Json 库所以编译代码时需要指明其动态库 Makefile 自动编译脚本 .PHONY:all all:CalcServer CalcClientCalcServer:CalcServer.ccg -o $ $^ -stdc11 -lpthread -ljsoncppCalcClient:CalcClient.ccg -o $ $^ -stdc11 -ljsoncpp.PHONY:clean clean:rm -rf CalcServer CalcClient使用了 Json 库之后序列化 后的数据会更加直观当然也更易于使用 ️总结 编写网络服务需要注意以下几点 确定协议如何进行序列化和反序列化业务处理 相关文章推荐 网络编程『简易TCP网络程序』 网络编程『socket套接字 ‖ 简易UDP网络程序』 网络基础『发展 ‖ 协议 ‖ 传输 ‖ 地址』
http://www.pierceye.com/news/969835/

相关文章:

  • 网站建设需要每年交钱吗如何选择宣传片制作
  • 建设网站为网站网站做广告芜湖市网站建设
  • 网站建设和维护怎么学android开发编辑wordpress
  • 有哪些学做衣服的网站生产管理软件app
  • 网站换域名 蜘蛛不来广告宣传片制作公司
  • 百度做个网站要多少钱如何在淘宝网做自己的网站
  • 网站建设属于营业范围里的哪一项深圳外贸建站网络推广联客易
  • 网站开发公司 郑州wordpress 服务器环境
  • 网站搭建什么意思砀山做网站
  • 营销型网站服务长沙做网站费用
  • 提供信息门户网站定制怎样做wordpress模板
  • 做爰小视频网站如何制作淘宝客网站
  • 公司架设网站费用怎么做分录linux网站开发软件
  • 网站可信图标精品网站建设费用 地址磐石网络
  • 朝阳住房和城乡建设厅网站学佛网站开发项目需求分析
  • 做快递单的网站会不会是骗人的网站推广营销收费
  • 网站设计师需要学什么wordpress focus
  • 查询网网站十大求职招聘app排行
  • 百度 搜索到手机网站wordpress百科汉化
  • 自己做的网站点击赚钱徐州万网网站建设
  • 网站定制生成器网页制作需要会哪些
  • 最重要的网站官方网站手机 优帮云
  • 建一个展示网站下班多少钱怎样给一个公司做网站改版
  • wordpress 网站死机php7.0 wordpress 设置
  • 免版权费自建网站自考本科官网
  • 使用ai做网站设计长沙建设网站哪家好
  • 建设行业网站价格公共服务标准化建设
  • 电商网站开发发展和前景网站建设案例多少钱
  • 网站建设特效代码做销售用什么网站
  • 如何做中英版网站上海到北京机票