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

专业网站推荐刚察县公司网站建设

专业网站推荐,刚察县公司网站建设,开发一款app需要多少钱?,建设职业注册中心网站文章目录 1. TCP协议的通信流程2. 应用层协议定制3. 通过“网络计算器”的实现来实现应用层协议定制和序列化3.1 protocol3.2 序列化和反序列化3.2.1 手写序列化和反序列化3.2.2 使用Json库 3.3 数据包读取3.4 服务端设计3.5 最后的源代码和运行结果 1. TCP协议的通信流程 在之… 文章目录 1. TCP协议的通信流程2. 应用层协议定制3. 通过“网络计算器”的实现来实现应用层协议定制和序列化3.1 protocol3.2 序列化和反序列化3.2.1 手写序列化和反序列化3.2.2 使用Json库 3.3 数据包读取3.4 服务端设计3.5 最后的源代码和运行结果 1. TCP协议的通信流程 在之前的代码中相信大家对TCP的通信过程的代码已经有了一定了了解。在很早之前就了解到过一些网络通信的相关描述比如TCP的三次握手和四次挥手。那么什么是三次握手和四次挥手呢 在介绍之前我们首先看一个图通过这个图来了解接下来我们讲解这张图 在最开始的时候客户端和服务器都是处于关闭状态的。 1. 开始前的准备 服务端和客户端在任意时刻在应用层调用socket函数分配一个文件描述符服务端显示bind指定端口和任意IP地址服务端调用listen使对应的文件描述符成为一个监听描述符服务端调用accept阻塞等待客户端的连接至此服务端在通信钱的准备已经完成 2. 三次握手 客户端调用connect函数向服务器发起连接请求然后阻塞自己等待完成 服务端收到客户端的连接请求之后由OS完成连接然后accept调用完成 这里connect是三次握手的开始accept调用完成时三次握手一定已经结束了三次握手是OS内部自己完成的在TCP层我们感知不到 3. 四次挥手 四次挥手的工作都是由双方的OS完成而我们决定什么时候挥手一旦调用系统调用close应用层就不用管了 2. 应用层协议定制 我们在第一次谈到协议的时候就说协议其实就是一种约定。在此之前我们也写过一些UDP和TCP的通信代码使用过一些socket API我们可以发现socket API在发送数据的时候都是按照“字符串”的形式来发送和接收的那如果我们要传输一些结构化的数据该怎么办呢 比如在发送一条QQ消息的时候需要带上发消息的人的昵称、QQ号、消息本身等等这些消息必须要一次性绑定的发送那么我们在发送的时候就需要把这些内容打包成一个“字符串”来发送 为什么不直接发送一个结构体对象 网络通信涉及到不同的机器可能出现大小段问题和内存对齐问题等等所以不能直接发送结构体 这个打包成一个字符串的过程就是序列化将收到的一个字符串转化为多个信息的过程就是反序列化 那么最终我们发送的消息就可以看作是一个完整的Content但是TCP通信是面向字节流的所以在通信的过程中我们也没有办法知道一次发送过来的数据里面有几个完整的Content这就需要在应用层定制一些“协议”来保证能区分每个数据包一般来说我们有以下几种方法 1. 确保每个数据包是定长的 2. 用特殊符号来表示结尾 3. 自描述 注意这里序列化反序列化和协议定制是两码事。序列化反序列化的作用是将要发送的信息变成一整条消息协议定制的作用是保证每次读取一整个数据包这个数据包里面会包含包头和有效载荷这个有效载荷就是我们所说的“一整条消息” 3. 通过“网络计算器”的实现来实现应用层协议定制和序列化 3.1 protocol 设计思想实现两个类request用于存储对应的运算请求存放算式包括两个操作数和一个操作符。response表示对应请求的响应也就是运算的结果状态和运算结果。最终经过系列化和反序列化之后形成一个字符串形式的有效载荷我们在这个有效载荷前面加上报头信息这里我们**约定报头的内容是一个字符串格式的数据存放的是有效载荷的长度有效载荷和报头之间存在一个分隔符** 这里的约定就是我们的协议 既然有了应用层的通信协议那么我们就要实现对应的为有效载荷添加报头和去除报头 std::string enLength(const std::string text) // 在text上加报头 {// content_len\r\ttext\r\tstd::string send_string std::to_string(text.size());send_string LINE_SEP;send_string text;send_string LINE_SEP;return send_string; } bool deLength(const std::string package, std::string *text) // 从package上去报头 {auto pos package.find(LINE_SEP);if (pos std::string::npos)return false;std::string text_len_string package.substr(0, pos);int text_len std::stoi(text_len_string);*text package.substr(pos LINE_SEP_LEN, text_len);return true; }3.2 序列化和反序列化 3.2.1 手写序列化和反序列化 按照我们的约定我们希望发送的结构化的数据就是Request和Response里面有一些特定的字段 enum // 协议定义的相关错误枚举 {OK 0,DIV_ZERO,MOD_ZERO,OP_ERROR }; class Request // 客户端请求数据 { public:int x;int y;char op; }; class Response // 服务器响应数据 { public:int exitcode;int result; };那么对于结构化的数据我们要首先将其序列化才能够作为有效载荷去添加报头然后发送。接收到发送的数据去除报头之后的有效载荷同样需要进行反序列化才能拿到结构化的数据进行操作 #define SEP // 分隔符 #define SEP_LEN strlen(SEP) // 分隔符长度 #define LINE_SEP \r\n // 行分隔符分隔报头和有效载荷 #define LINE_SEP_LEN strlen(LINE_SEP) // 行分隔符长度 // class Request // 客户端请求数据 bool serialize(std::string *out) // 序列化 - x op y {std::string x_string std::to_string(x);std::string y_string std::to_string(y);*out x_string;*out SEP;*out op;*out SEP;*out y_string;return true; } // x op y bool deserialize(std::string in) // 反序列化 {auto left in.find(SEP);auto right in.rfind(SEP);if (left std::string::npos || right std::string::npos)return false; // 出现了不合法的待反序列化数据if (left right)return false; // 出现了不合法的待反序列化数据if (right - SEP_LEN - left ! 1)return false; // op的长度不为1std::string left_str in.substr(0, left);std::string right_str in.substr(right SEP_LEN);if (left_str.empty() || right_str.empty())return false;x std::stoi(left_str);y std::stoi(right_str);op in[left SEP_LEN];return true; } // class Response // 服务器响应数据 bool serialize(std::string *out) // 序列化 {// exitcode result*out ;std::string ec_string std::to_string(exitcode);std::string res_string std::to_string(result);*out ec_string;*out SEP;*out res_string;return true; } bool deserialize(std::string in) // 反序列化 exitcode result {auto pos in.find(SEP);if (pos std::string::npos)return false;std::string ec_string in.substr(0, pos);std::string res_string in.substr(pos SEP_LEN);if (ec_string.empty() || res_string.empty())return false;exitcode std::stoi(ec_string);result std::stoi(res_string);return true; }3.2.2 使用Json库 我们会发现手写序列化好麻烦 那么实际上有人已经帮我们做过这件事情了提供了一些可以使用的组件我们只需要按照规则使用即可。常用的序列化和反序列化工具有1. Json 2. protobuf 3. xml。这里我们为了使用的方便采用Json来写。protobuf在之后的博文会更新使用方式 // class Request // 客户端请求数据 bool serialize(std::string *out) // 序列化 {Json::Value root; // Json::Value 是一个KV结构。首先定义出这个结构root[first] x; // 按照KV结构的模式为每个字段添加一个Key给这个字段赋值root[second] y;root[oper] op;Json::FastWriter writer; // FastWriter是一个序列化的类里面提供了write方法这个方法可以将Value的对象转成std::string*out writer.write(root); // 转换后的字符串就是序列化后的结果return true; } bool deserialize(std::string in) // 反序列化 {Json::Value root; // 序列化后的结果需要被存放Json::Reader reader; // Reader类是用作读取的里面提供了parse解析方法可以将对应的序列化结果string转化成Value对象reader.parse(in, root);x root[first].asInt();// 按照KV结构的模式将存放的内容提取出来提取出来的结果的类型是Json内部的要使用的时候需要指定类型y root[second].asInt();op root[oper].asInt();return true; }// class Response // 服务器响应数据 bool serialize(std::string *out) // 序列化 {Json::Value root;root[first] exitcode;root[second] result;Json::FastWriter writer;*out writer.write(root);return true; } bool deserialize(std::string in) // 反序列化 exitcode result {Json::Value root;Json::Reader reader;reader.parse(in, root);exitcode root[first].asInt();result root[second].asInt();return true; }Json库不是标准库的内容所以在使用之前需要安装在cent OS下的安装命令 sudo yum install -y jsoncpp-devel # 安装json安装之后编译我们的代码会报错么当然会因为我们没有链接 ccg.PHONY:all all:Server ClientServer:calServer.cc$(cc) -o $ $^ -lpthread -ljsoncpp -stdc11 # 这里加上-ljsoncppClient:calClient.cc$(cc) -o $ $^ -ljsoncpp -stdc11 # 这里加上-ljsoncpp.PHONY:clean clean:rm -f Server Client3.3 数据包读取 首先明确一点TCP协议是面向字节流的不能确定是否当前收到的就是一个完整的报文所以需要进行判断与读取 这里我们采用的方法是如果读取到一个完整的报文就进行后续处理如果没有读取到一个完整的报文那就继续读取直到遇到完整报文再处理 /*** sock:读取对应套接字的报文* inbuffer:接收缓冲区这里存放接收到的所有数据* req_text:输出型参数如果读到完整报文就将报文内容存放到req_text中* 返回值:读取成功返回true失败返回false */ bool recvPackage(int sock, std::string inbuffer, std::string *req_text) {char buffer[1024];while (true){ssize_t n recv(sock, buffer, sizeof(buffer) - 1, 0); // 接收数据if (n 0){buffer[n] 0; // 当前本次接收的数据inbuffer buffer; // 放在inbuffer后面处理整个inbufferauto pos inbuffer.find(LINE_SEP);if (pos std::string::npos)continue; // 还没有接收完一个完整的报头// 走到当前位置确定能接收到一个完整的报头std::string text_len_string inbuffer.substr(0, pos); // 报头拿完了报头就是这个有效载荷的长度int text_len std::stoi(text_len_string); // 有效载荷的长度int total_len text_len 2 * LINE_SEP_LEN text_len_string.size(); // 报文总长度if (inbuffer.size() total_len){// 收到的信息不是一个完整的报文continue;}// 到这里就拿到了一个完整的报文*req_text inbuffer.substr(0, total_len);inbuffer.erase(0, total_len); // 在缓冲区中删除拿到的报文return true;}elsereturn false;} }3.4 服务端设计 按照我们在上一篇博文的多进程版本设计这里服务端将会让一个孙子进程来执行相关的操作其中孙子进程需要执行的任务分为5个步骤 1. 读取报文读取到一个完整报文之后去掉报头 2. 将有效载荷反序列化 3. 进行业务处理回调 4. 将响应序列化 5. 将徐姐话的响应数据构建成一个符合协议的报文发送回去 void handleEntery(int sock, func_t func) // 服务端调用 {std::string inbuffer;// 接收缓冲区while(true){// 1. 读取数据std::string req_text, req_str;// 1.1 读到一个完整的请求带报头req_text content_len\r\tx op y\r\tif(!recvPackage(sock, inbuffer, req_text)) return;// 1.2 将req_text解析成req_str不带报头x op yif(!deLength(req_text, req_str)) return;// 2. 数据反序列化Request req;if(!req.deserialize(req_str)) return;// 3. 业务处理Response resp;func(req, resp);// 4. 数据序列化std::string send_str;if(!resp.serialize(send_str)) return;// 5. 发送响应数据// 5.1 构建一个完整的报文std::string resp_str enLength(send_str);// 5.2 发送send(sock, resp_str.c_str(), resp_str.size(), 0);} }对应需要执行的内容我们就在业务逻辑层来处理 bool cal(const Request req, Response resp) {// 此时结构化的数据就在req中可以直接使用resp.exitcode OK;switch (req.op){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.exitcode DIV_ZERO;elseresp.result req.x / req.y;}break;case %:{if (req.y 0)resp.exitcode MOD_ZERO;elseresp.result req.x % req.y;}break;default:resp.exitcode OP_ERROR;break;} }3.5 最后的源代码和运行结果 /*calServer.hpp*/ #pragma once#include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include sys/wait.h #include pthread.h#include string #include functional#include log.hpp #include protocol.hppnamespace Server {enum{USAGE_ERR 1,SOCKET_ERR,BIND_ERR,LISTEN_ERR};static const uint16_t gport 8080;static const int gbacklog 5;typedef std::functionbool(const Request req, Response resp) func_t;void handleEntery(int sock, func_t func) // 服务端调用{std::string inbuffer;// 接收缓冲区while(true){// 1. 读取数据std::string req_text, req_str;// 1.1 读到一个完整的请求带报头req_text content_len\r\tx op y\r\tif(!recvPackage(sock, inbuffer, req_text)) return;// 1.2 将req_text解析成req_str不带报头x op yif(!deLength(req_text, req_str)) return;// 2. 数据反序列化Request req;if(!req.deserialize(req_str)) return;// 3. 业务处理Response resp;func(req, resp);// 4. 数据序列化std::string send_str;if(!resp.serialize(send_str)) return;// 5. 发送响应数据// 5.1 构建一个完整的报文std::string resp_str enLength(send_str);// 5.2 发送send(sock, resp_str.c_str(), resp_str.size(), 0);}}class tcpServer;class ThreadData // 封装线程数据用于传递给父进程{public:ThreadData(tcpServer *self, int sock) : _self(self), _sock(sock) {}public:tcpServer *_self;int _sock;};class tcpServer{public:tcpServer(uint16_t port) : _port(port){}void initServer(){// 1. 创建socket文件套接字对象_listensock socket(AF_INET, SOCK_STREAM, 0);if (_listensock -1){logMessage(FATAL, create socket error);exit(SOCKET_ERR);}logMessage(NORMAL, create socket success:%d, _listensock);// 2.bind自己的网络信息sockaddr_in local;local.sin_family AF_INET;local.sin_port htons(_port);local.sin_addr.s_addr INADDR_ANY;int n bind(_listensock, (struct sockaddr *)local, sizeof local);if (n -1){logMessage(FATAL, bind socket error);exit(BIND_ERR);}logMessage(NORMAL, bind socket success);// 3. 设置socket为监听状态if (listen(_listensock, gbacklog) ! 0) // listen 函数{logMessage(FATAL, listen socket error);exit(LISTEN_ERR);}logMessage(NORMAL, listen socket success);}void start(func_t func){while (true){struct sockaddr_in peer;socklen_t len sizeof peer;int sock accept(_listensock, (struct sockaddr *)peer, len);if (sock 0){logMessage(ERROR, accept error, next);continue;}// version 2:多进程版本pid_t id fork();if (id 0){close(_listensock); // 子进程不会使用监听socket但是创建子进程的时候写时拷贝会拷贝这里先关掉// 子进程再创建子进程if (fork() 0)exit(0); // 父进程退出// 走到当前位置的就是子进程handleEntery(sock, func); // 使用close(sock); // 关闭对应的通信socket这里也可以不关闭因为此进程在下个语句就会退出exit(0); // 孙子进程退出}// 走到这里的是监听进程爷爷进程pid_t n waitpid(id, nullptr, 0);if (n 0){logMessage(NORMAL, wait success pid:%d, n);}close(sock);}}~tcpServer() {}private:uint16_t _port;int _listensock;};} // namespace Server /*calServer.cc*/ #include iostream #include memory#include calServer.hpp #include protocol.hppusing namespace Server;static void Usage(const char *proc) {std::cout \n\tUsage: proc local_port\n; }bool cal(const Request req, Response resp) {// 此时结构化的数据就在req中可以直接使用resp.exitcode OK;switch (req.op){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.exitcode DIV_ZERO;elseresp.result req.x / req.y;}break;case %:{if (req.y 0)resp.exitcode MOD_ZERO;elseresp.result req.x % req.y;}break;default:resp.exitcode OP_ERROR;break;} }int main(int argc, char *argv[]) {if (argc ! 2){Usage(argv[0]);exit(USAGE_ERR);}uint16_t port atoi(argv[1]);std::unique_ptrtcpServer tsvr(new tcpServer(port));tsvr-initServer();tsvr-start(cal);return 0; } /*protocol.hpp*/ #pragma once#include cstring #include string #include jsoncpp/json/json.h#define SEP // 分隔符 #define SEP_LEN strlen(SEP) // 分隔符长度 #define LINE_SEP \r\n // 行分隔符分隔报头和有效载荷 #define LINE_SEP_LEN strlen(LINE_SEP) // 行分隔符长度enum // 协议定义的相关错误枚举 {OK 0,DIV_ZERO,MOD_ZERO,OP_ERROR };std::string enLength(const std::string text) // 在text上加报头 {// content_len\r\ttext\r\tstd::string send_string std::to_string(text.size());send_string LINE_SEP;send_string text;send_string LINE_SEP;return send_string; } bool deLength(const std::string package, std::string *text) // 从package上去报头 {auto pos package.find(LINE_SEP);if (pos std::string::npos)return false;std::string text_len_string package.substr(0, pos);int text_len std::stoi(text_len_string);*text package.substr(pos LINE_SEP_LEN, text_len);return true; }class Request // 客户端请求数据 { public:Request() {}Request(int x_, int y_, char op_) : x(x_), y(y_), op(op_) {}bool serialize(std::string *out) // 序列化 - x op y{ #ifdef MYSELFstd::string x_string std::to_string(x);std::string y_string std::to_string(y);*out x_string;*out SEP;*out op;*out SEP;*out y_string; #elseJson::Value root; // Json::Value 是一个KV结构。首先定义出这个结构root[first] x; // 按照KV结构的模式为每个字段添加一个Key给这个字段赋值root[second] y;root[oper] op;Json::FastWriter writer; // FastWriter是一个序列化的类里面提供了write方法这个方法可以将Value的对象转成std::string*out writer.write(root); // 转换后的字符串就是序列化后的结果 #endifreturn true;}// x op ybool deserialize(std::string in) // 反序列化{ #ifdef MYSELFauto left in.find(SEP);auto right in.rfind(SEP);if (left std::string::npos || right std::string::npos)return false; // 出现了不合法的待反序列化数据if (left right)return false; // 出现了不合法的待反序列化数据if (right - SEP_LEN - left ! 1)return false; // op的长度不为1std::string left_str in.substr(0, left);std::string right_str in.substr(right SEP_LEN);if (left_str.empty() || right_str.empty())return false;x std::stoi(left_str);y std::stoi(right_str);op in[left SEP_LEN]; #elseJson::Value root; // 序列化后的结果需要被存放Json::Reader reader; // Reader类是用作读取的里面提供了parse解析方法可以将对应的序列化结果string转化成Value对象reader.parse(in, root);x root[first].asInt();// 按照KV结构的模式将存放的内容提取出来提取出来的结果的类型是Json内部的要使用的时候需要指定类型y root[second].asInt();op root[oper].asInt(); #endifreturn true;}public:int x;int y;char op; };class Response // 服务器响应数据 { public:bool serialize(std::string *out) // 序列化{ #ifdef MYSELF// exitcode result*out ;std::string ec_string std::to_string(exitcode);std::string res_string std::to_string(result);*out ec_string;*out SEP;*out res_string; #elseJson::Value root;root[first] exitcode;root[second] result;Json::FastWriter writer;*out writer.write(root); #endifreturn true;}bool deserialize(std::string in) // 反序列化 exitcode result{ #ifdef MYSELFauto pos in.find(SEP);if (pos std::string::npos)return false;std::string ec_string in.substr(0, pos);std::string res_string in.substr(pos SEP_LEN);if (ec_string.empty() || res_string.empty())return false;exitcode std::stoi(ec_string);result std::stoi(res_string); #elseJson::Value root;Json::Reader reader;reader.parse(in, root);exitcode root[first].asInt();result root[second].asInt(); #endifreturn true;}public:int exitcode;int result; };/*** sock:读取对应套接字的报文* inbuffer:接收缓冲区这里存放接收到的所有数据* req_text:输出型参数如果读到完整报文就将报文内容存放到req_text中* 返回值:读取成功返回true失败返回false */ bool recvPackage(int sock, std::string inbuffer, std::string *req_text) {char buffer[1024];while (true){ssize_t n recv(sock, buffer, sizeof(buffer) - 1, 0); // 接收数据if (n 0){buffer[n] 0; // 当前本次接收的数据inbuffer buffer; // 放在inbuffer后面处理整个inbufferauto pos inbuffer.find(LINE_SEP);if (pos std::string::npos)continue; // 还没有接收完一个完整的报头// 走到当前位置确定能接收到一个完整的报头std::string text_len_string inbuffer.substr(0, pos); // 报头拿完了报头就是这个有效载荷的长度int text_len std::stoi(text_len_string); // 有效载荷的长度int total_len text_len 2 * LINE_SEP_LEN text_len_string.size(); // 报文总长度if (inbuffer.size() total_len){// 收到的信息不是一个完整的报文continue;}// 到这里就拿到了一个完整的报文*req_text inbuffer.substr(0, total_len);inbuffer.erase(0, total_len); // 在缓冲区中删除拿到的报文return true;}elsereturn false;} } /*calClient.hpp*/ #pragma once #include sys/types.h #include sys/socket.h #include arpa/inet.h #include netinet/in.h#include string#include log.hpp #include protocol.hppnamespace Client {class tcpClient{public:tcpClient(uint16_t port, std::string IP) : _serverPort(port), _serverIP(IP), _sockfd(-1) {}void initClient(){// 1. 创建socket_sockfd socket(AF_INET, SOCK_STREAM, 0);if (_sockfd -1){std::cerr create socket error std::endl;exit(2);}}void run(){struct sockaddr_in server;server.sin_family AF_INET;server.sin_port htons(_serverPort);server.sin_addr.s_addr inet_addr(_serverIP.c_str());if (connect(_sockfd, (struct sockaddr *)server, sizeof server) ! 0){// 链接失败std::cerr socket connect error std::endl;}else{std::string line;std::string inbuffer;while (true){std::cout mycal ;std::getline(std::cin, line);Request req ParseLine(line);std::string content;req.serialize(content); // 序列化结果存放的content中std::string send_string enLength(content); // 添加报头send(_sockfd, send_string.c_str(), send_string.size(), 0);std::string package, text;if (!recvPackage(_sockfd, inbuffer, package))continue;if (!deLength(package, text))continue;// text中的结果就是 exitcode resultResponse resp;resp.deserialize(text); // 反序列化std::cout exitCode: resp.exitcode std::endl;std::cout result: resp.result std::endl;}}}Request ParseLine(const std::string line){int status 0; // 0 操作符之前 1 操作符 2 操作符之后int i 0, size line.size();char op;std::string left, right;while (i size){switch (status){case 0:if(!isdigit(line[i])){// 遇到字符op line[i];status 1;}else left.push_back(line[i]);break;case 1:i;status 2;break;case 2:right.push_back(line[i]);break;}}return Request(std::stoi(left), std::stoi(right), op);}~tcpClient(){if (_sockfd 0)close(_sockfd); // 使用完关闭防止文件描述符泄露当然这里也可以不写当进程结束之后一切资源都将被回收}private:uint16_t _serverPort;std::string _serverIP;int _sockfd;};} // namespace Client /*calClient.cc*/ #include memory #include string#include calClient.hpp using namespace Client;static void Usage(const char *proc) {std::cout \n\tUsage: proc server_ip server_port\n; }int main(int argc, char* argv[]) {if(argc ! 3){Usage(argv[0]);exit(1);}std::string IP argv[1];uint16_t port atoi(argv[2]);std::unique_ptrtcpClient tclt(new tcpClient(port, IP));tclt-initClient();tclt-run();return 0; } /*log.hpp*/ #include unistd.h #include iostream #include cstdio #include ctime #include cstdarg// 这里是日志等级对应的宏 #define DEBUG (1 0) #define NORMAL (1 1) #define WARNING (1 2) #define ERROR (1 3) #define FATAL (1 4)#define NUM 1024 // 日志行缓冲区大小 #define LOG_NORMAL log.normal // 日志存放的文件名 #define LOG_ERR log.errorconst char *logLevel(int level) // 把日志等级转变为对应的字符串 {switch (level){case DEBUG:return DEBUG;case NORMAL:return NORMAL;case WARNING:return WARNING;case ERROR:return ERROR;case FATAL:return FATAL;default:return UNKNOW;} } //[日志等级][时间][pid]日志内容 void logMessage(int level, const char *format, ...) // 核心调用 {char logprefix[NUM]; // 存放日志相关信息time_t now_ time(nullptr);struct tm *now localtime(now_);snprintf(logprefix, sizeof(logprefix), [%s][%d年%d月%d日%d时%d分%d秒][pid:%d],logLevel(level), now-tm_year 1900, now-tm_mon 1, now-tm_mday, now-tm_hour, now-tm_min, now-tm_sec, getpid());char logcontent[NUM];va_list arg; // 声明一个变量arg指向可变参数列表的对象va_start(arg, format); // 使用va_start宏来初始化arg将它指向可变参数列表的起始位置。// format是可变参数列表中的最后一个固定参数用于确定可变参数列表从何处开始vsnprintf(logcontent, sizeof(logcontent), format, arg); // 将可变参数列表中的数据格式化为字符串并将结果存储到logcontent中FILE *log fopen(LOG_NORMAL, a);FILE *err fopen(LOG_ERR, a);if(log ! nullptr err ! nullptr){FILE *curr nullptr;if(level DEBUG || level NORMAL || level WARNING) curr log;if(level ERROR || level FATAL) curr err;if(curr) fprintf(curr, %s%s\n, logprefix, logcontent);fclose(log);fclose(err);} }ccg.PHONY:all all:Server ClientServer:calServer.cc$(cc) -o $ $^ -lpthread -ljsoncpp -stdc11Client:calClient.cc$(cc) -o $ $^ -ljsoncpp -stdc11.PHONY:clean clean:rm -f Server Client.PHONY:cleanlog cleanlog:rm -f log.error log.normal本节完…
文章转载自:
http://www.morning.dwfzm.cn.gov.cn.dwfzm.cn
http://www.morning.wfzlt.cn.gov.cn.wfzlt.cn
http://www.morning.rckdq.cn.gov.cn.rckdq.cn
http://www.morning.nbrkt.cn.gov.cn.nbrkt.cn
http://www.morning.pjwfs.cn.gov.cn.pjwfs.cn
http://www.morning.ksggr.cn.gov.cn.ksggr.cn
http://www.morning.jjzbx.cn.gov.cn.jjzbx.cn
http://www.morning.bwfsn.cn.gov.cn.bwfsn.cn
http://www.morning.xfxnq.cn.gov.cn.xfxnq.cn
http://www.morning.qbmjf.cn.gov.cn.qbmjf.cn
http://www.morning.hkpn.cn.gov.cn.hkpn.cn
http://www.morning.cgtrz.cn.gov.cn.cgtrz.cn
http://www.morning.ptwrz.cn.gov.cn.ptwrz.cn
http://www.morning.kwqt.cn.gov.cn.kwqt.cn
http://www.morning.xqjh.cn.gov.cn.xqjh.cn
http://www.morning.rqfnl.cn.gov.cn.rqfnl.cn
http://www.morning.xnlj.cn.gov.cn.xnlj.cn
http://www.morning.sskkf.cn.gov.cn.sskkf.cn
http://www.morning.yqndr.cn.gov.cn.yqndr.cn
http://www.morning.mrcpy.cn.gov.cn.mrcpy.cn
http://www.morning.zrnph.cn.gov.cn.zrnph.cn
http://www.morning.rnnq.cn.gov.cn.rnnq.cn
http://www.morning.wmqrn.cn.gov.cn.wmqrn.cn
http://www.morning.hbywj.cn.gov.cn.hbywj.cn
http://www.morning.mcwgn.cn.gov.cn.mcwgn.cn
http://www.morning.ptqpd.cn.gov.cn.ptqpd.cn
http://www.morning.qtnmp.cn.gov.cn.qtnmp.cn
http://www.morning.rpkg.cn.gov.cn.rpkg.cn
http://www.morning.tpxgm.cn.gov.cn.tpxgm.cn
http://www.morning.tbqbd.cn.gov.cn.tbqbd.cn
http://www.morning.sggzr.cn.gov.cn.sggzr.cn
http://www.morning.mkhwx.cn.gov.cn.mkhwx.cn
http://www.morning.wgkz.cn.gov.cn.wgkz.cn
http://www.morning.hxhrg.cn.gov.cn.hxhrg.cn
http://www.morning.rzczl.cn.gov.cn.rzczl.cn
http://www.morning.nkbfc.cn.gov.cn.nkbfc.cn
http://www.morning.pwdrc.cn.gov.cn.pwdrc.cn
http://www.morning.hcqd.cn.gov.cn.hcqd.cn
http://www.morning.sftpg.cn.gov.cn.sftpg.cn
http://www.morning.wschl.cn.gov.cn.wschl.cn
http://www.morning.srbsr.cn.gov.cn.srbsr.cn
http://www.morning.fpbj.cn.gov.cn.fpbj.cn
http://www.morning.gkdhf.cn.gov.cn.gkdhf.cn
http://www.morning.grfhd.cn.gov.cn.grfhd.cn
http://www.morning.wdlg.cn.gov.cn.wdlg.cn
http://www.morning.zcsch.cn.gov.cn.zcsch.cn
http://www.morning.ghxkm.cn.gov.cn.ghxkm.cn
http://www.morning.cyyhy.cn.gov.cn.cyyhy.cn
http://www.morning.ymtbr.cn.gov.cn.ymtbr.cn
http://www.morning.jpbpc.cn.gov.cn.jpbpc.cn
http://www.morning.dnbkz.cn.gov.cn.dnbkz.cn
http://www.morning.bqrd.cn.gov.cn.bqrd.cn
http://www.morning.sdhmn.cn.gov.cn.sdhmn.cn
http://www.morning.mkyxp.cn.gov.cn.mkyxp.cn
http://www.morning.zdfrg.cn.gov.cn.zdfrg.cn
http://www.morning.xgchm.cn.gov.cn.xgchm.cn
http://www.morning.zcxjg.cn.gov.cn.zcxjg.cn
http://www.morning.ypzr.cn.gov.cn.ypzr.cn
http://www.morning.mnlk.cn.gov.cn.mnlk.cn
http://www.morning.chehb.com.gov.cn.chehb.com
http://www.morning.qsmdd.cn.gov.cn.qsmdd.cn
http://www.morning.bkfdf.cn.gov.cn.bkfdf.cn
http://www.morning.rnrwq.cn.gov.cn.rnrwq.cn
http://www.morning.jntdf.cn.gov.cn.jntdf.cn
http://www.morning.wyfpc.cn.gov.cn.wyfpc.cn
http://www.morning.lhwlp.cn.gov.cn.lhwlp.cn
http://www.morning.khpx.cn.gov.cn.khpx.cn
http://www.morning.lfcfn.cn.gov.cn.lfcfn.cn
http://www.morning.hmxrs.cn.gov.cn.hmxrs.cn
http://www.morning.qnhpq.cn.gov.cn.qnhpq.cn
http://www.morning.gcxfh.cn.gov.cn.gcxfh.cn
http://www.morning.jqwpw.cn.gov.cn.jqwpw.cn
http://www.morning.xdhcr.cn.gov.cn.xdhcr.cn
http://www.morning.ykmtz.cn.gov.cn.ykmtz.cn
http://www.morning.fpzpb.cn.gov.cn.fpzpb.cn
http://www.morning.dxhdn.cn.gov.cn.dxhdn.cn
http://www.morning.rywn.cn.gov.cn.rywn.cn
http://www.morning.rgxf.cn.gov.cn.rgxf.cn
http://www.morning.wqcz.cn.gov.cn.wqcz.cn
http://www.morning.crrjg.cn.gov.cn.crrjg.cn
http://www.tj-hxxt.cn/news/268508.html

相关文章:

  • 如何来做网站德源网站建设
  • 建立企业网站费用建站房的公司
  • 网站做关键词排名有必要吗虚拟服务器价格
  • 银行官方网站陕西华伟建设有限公司网站
  • 做搜狗网站优化点实验室网站模板
  • 传奇网站模板使用泉州网站建设选择讯呢
  • 兰州网站优化公司硬件开发和嵌入式的区别
  • 建网站能上传多少数据深圳网站建设哪家强
  • 丹阳网站建设多少钱网站后台管理优化
  • node.js可以做网站么dw做网站首页代码
  • 找一个网站做搜索引擎分析720全景网站怎么做
  • 企业展厅设计比较好的公司seo排名大概多少钱
  • 还有哪些方法让网站更加利于seo网站图片有什么要求吗
  • 常州网站建设哪家便宜小型网站建设公司价格
  • 临海做网站公司wordpress哪种语言
  • 建设厅网站给领导留言如何查看北京企业聚集
  • 企业做电商网站有哪些找人做个网站建设制作报价方案
  • 住房和城乡建设部网站注册晋城市网站建设管理人员
  • 英文购物网站建设上海地区网站备案
  • 大连模板网站制作公司珠海网站建易搜互联
  • 郑州哪有做网站的建设一个网站需要几个角色
  • 南京一等一网站建设海岸城网站建设
  • 安徽省建设工程质量安全监督总站网站网页设计与制作项目设计思路
  • goland 网站开发企业网站最重要的访问对象是
  • iis 没有右键网站属性长沙竹叶网络科技有限公司
  • 企业网站建设情况汇报项目网站的建设有两种模式
  • 关于机关单位网站的建设typecho客户端wordpress
  • 手机端网站建设大气网络公司网站模板
  • 站长之家网站建设怎么注册公司公众号微信号
  • 做网站做哪个行业好企业网站用哪个cms好