我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层.
一,再次理解协议
思考1: 我们究竟是如何将数据发送出去的?
(1)我们都知道tcp是双加工的,所以在内核中存在着发送缓冲区和接受缓冲区,而我们write是写入发送缓冲区的,read是从接受缓冲区里读的,所以我们会发现这两个函数其实就是拷贝函数!!write将数据从用户层拷贝到内核层就返回,read将数据从内核层拷贝到用户层就返回,意思就是我用户不管,反正我把数据都交给你os了,发送过程中的可靠性由你来维护。(就像当年我们只需要将内容写到文件内核缓冲区,而由os来决定什么时候,以什么方式刷新到磁盘上)
(2)所以tcp协议是属于操作系统的网络模块部分,他之所以叫做传输控制协议,就是因为他需要确保数据传输过程中的可靠性,比方说我什么时候应该发给对方?要发多少?万一出错了怎么办?
思考2:回忆管道和文件系统
(1)以往我们往管道文件里写了很多次的时候,可能我们一次就全部读上来了
(2)而在文件系统中,我们的写很容易,可以分很多次写,各种类型比如整数/字符串…… 但是读的时候就很难去读,同时不同的文件的读取方式可能也不一样,比如按行读也仅仅只是读取文件的一种方式而已。
思考3:tcp是面向字节流的,你怎么保证你读上来的数据一定是一个"完整的报文"呢?
(1)早期的时候我们可能会想到比方说我们约定必须要凑齐多少字节才往上读,但是这个其实只适用于一些固定类型的报文,比方说我们规定读的是int,而int对应的就是一个错误码或者是某一个固定的任务。但是如果是一些长度不固定的报文,比如说我们在聊天的时候发送的字符串长度都是不一样的,那么这个时候就很容易读到不完整的报文
(2)所以为了应对这种情况,我们就需要在报文直接加入一下分割符,或者是标识这个报文有多长,以确保能够读到完整的报文, 而协议要添加报头就会有很多新得问题出来-比如序列化和反序列化。
二,序列化和反序列化
问题1:协议是一种 "约定". socket api的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果我们要传输一些"结构化的数据" 怎么办呢?
(1)同一个结构体在不同的编译器下编译的大小不一定一样(结构体的内存对齐),其实linux的协议就是传结构体做到的,但是他底层可以把所有方方面面的情况都考虑到了,但是我们用户去定的时候很难考虑得这么周全。
(2) 可以如果不用结构体的话,我们如果用字符串呢?举个例子,我们聊天的时候我发了一句哈哈,但是发送的时候还会带上昵称以及发送时间,所以我们肯定不能把这三个字符串分开发,因为这也服务端就不知道你这话是谁说的,所以我肯定希望把三个字符串打包成一个字符串发过去,然后服务的把消息广播给所有客户端的时候,会再把这个包根据一定的方法解析分成三个字符串然后再给你显现出来!------------->也就是说我们需要在类里面定义两种方法,一种是把类内的数据打包成一个字符串发送过去,另一种是解析的时候将这个字符串再拆分出来。 而这个过程就是序列化和反序列化
问题2 :我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最 后再把结果返回给客户端.
约定方案一:
- 客户端发送一个形如"1+1"的字符串;
- 这个字符串中有两个操作数, 都是整形;
- 两个数字之间会有一个字符是运算符, 运算符只能是 + ;
- 数字和运算符之间没有空格;
- ...
约定方案二:
定义结构体来表示我们需要交互的信息;
发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转 化回结构体; 这个过程叫做 "序列化" 和”反序列化“
三,实现网络计算器
3.1 日志文件
#pragma once #include <iostream> #include <time.h> #include <stdarg.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #define size 1024 #define info 0 #define debug 1 #define warning 2 #define error 3 #define fatal 4 #define screen 1 #define onefile 2 #define classfile 3 #define logfile "log.txt" class log { public: log() { printmethod = screen; path = "./log/"; } void enable(int method) { printmethod = method; } std::string leveltostring(int level) { switch (level) { case info: return "info"; case debug: return "debug"; case warning: return "warning"; case error: return "error"; case fatal: return "fatal"; default: return "none"; } } // void logmessage(int level, const char *format, ...) // { // time_t t = time(nullptr); // struct tm *ctime = localtime(&t); // char leftbuffer[size]; // snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", leveltostring(level).c_str(), // ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, // ctime->tm_hour, ctime->tm_min, ctime->tm_sec); // // va_list s; // // va_start(s, format); // char rightbuffer[size]; // vsnprintf(rightbuffer, sizeof(rightbuffer), format, s); // // va_end(s); // // 格式:默认部分+自定义部分 // char logtxt[size * 2]; // snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer); // // printf("%s", logtxt); // 暂时打印 // printlog(level, logtxt); // } void printlog(int level, const std::string &logtxt) { switch (printmethod) { case screen: std::cout << logtxt << std::endl; break; case onefile: printonefile(logfile, logtxt); break; case classfile: printclassfile(level, logtxt); break; default: break; } } void printonefile(const std::string &logname, const std::string &logtxt) { std::string _logname = path + logname; int fd = open(_logname.c_str(), o_wronly | o_creat | o_append, 0666); // "log.txt" if (fd < 0) return; write(fd, logtxt.c_str(), logtxt.size()); close(fd); } void printclassfile(int level, const std::string &logtxt) { std::string filename = logfile; filename += "."; filename += leveltostring(level); // "log.txt.debug/warning/fatal" printonefile(filename, logtxt); } ~log() { } void operator()(int level, const char *format, ...) { time_t t = time(nullptr); struct tm *ctime = localtime(&t); char leftbuffer[size]; snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", leveltostring(level).c_str(), ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec); va_list s; va_start(s, format); char rightbuffer[size]; vsnprintf(rightbuffer, sizeof(rightbuffer), format, s); va_end(s); // 格式:默认部分+自定义部分 char logtxt[size * 2]; snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer); // printf("%s", logtxt); // 暂时打印 printlog(level, logtxt); } private: int printmethod; std::string path; }; // int sum(int n, ...) // { // va_list s; // char* // va_start(s, n); // int sum = 0; // while(n) // { // sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123); // n--; // } // va_end(s); //s = null // return sum; // } log lg; // 命令对象 用来打印日志信息
3.2socket.hpp
我们可以写个套接字的小组件,这样未来我们就可以直接去使用
#pragma once //写一个套接字的小组件 这样我们未来就可以直接用 #include <iostream> #include <string> #include <unistd.h> #include <cstring> #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> #include "log.hpp" enum { socketerror = 1, binderror =2, listenerror = 3, accepterror = 4 }; const int defaultbacklog = 10;//监听队列默认长度 class sock { public: sock(){} ~sock(){} void socket()//创建套接字 { _sockfd = socket(af_inet, sock_stream, 0); if (_sockfd<0) { lg(fatal,"socket error,%s:%d",strerror(errno),errno); exit(socketerror); } } void bind(uint16_t port)//绑定套接字 { struct sockaddr_in addr; bzero(&addr, sizeof(struct sockaddr_in)); addr.sin_family = af_inet; addr.sin_port = htons(port); addr.sin_addr.s_addr = inaddr_any; if(bind(_sockfd, (struct sockaddr*)&addr, sizeof(addr))<0) { lg(fatal,"bind error,%s:%d",strerror(errno),errno); exit(binderror); } } void listen(int backlog=defaultbacklog)//监听套接字 { if(listen(_sockfd, backlog)<0) { lg(fatal,"listen error,%s:%d",strerror(errno),errno); exit(listenerror); } } int accept(std::string *clientip,uint16_t *clientport)//接受套接字并把客户端的ip和端口返回(输出型参数) { struct sockaddr_in client; socklen_t len = sizeof(client); int connfd = accept(_sockfd, (struct sockaddr*)&client, &len); if(connfd<0) { lg(error,"accept error,%s:%d",strerror(errno),errno);//如果接受失败 打印错误信息并退出程序 exit(accepterror); } char ipstr[64]; inet_ntop(af_inet, &client.sin_addr,ipstr, sizeof(ipstr)); *clientip = ipstr; *clientport = ntohs(client.sin_port); lg(info,"accept a client %s:%d",*clientip,*clientport);//表示接受成功了并打印客户端的ip和端口 return connfd; } int getsockfd()//获取套接字 { return _sockfd; } void close()//关闭套接字 { close(_sockfd); } bool connect(const std::string &ip,const uint16_t &port)//连接套接字 { struct sockaddr_in addr; bzero(&addr, sizeof(struct sockaddr_in)); addr.sin_family = af_inet; addr.sin_port = htons(port); inet_pton(af_inet, ip.c_str(), &addr.sin_addr) ;//将ip地址转换为网络字节序 if(connect(_sockfd, (struct sockaddr*)&addr, sizeof(addr))<0) { lg(warning,"connect error,%s:%d",strerror(errno),errno); return false; } return true; } private: int _sockfd; };
3.3 tcpserver.hpp
#pragma once #include <functional> #include <signal.h> #include "socket.hpp" using func_t = std::function<std::string(std::string &package)>; // 回调函数 class tcpserver { public: tcpserver(uint16_t port, func_t callback) : _port(port), _callback(callback), _isrunning(false) { } ~tcpserver() {} void initserver() { _listensock.socket(); _listensock.bind(_port); _listensock.listen(10); lg(info, "server is running on port %d", _port); // 看看服务器在哪个端口上运行 } void start() // 启动服务器 { _isrunning = true; signal(sigchld, sig_ign); // 忽略子进程结束信号 因为子进程结束时会产生sigchld信号 signal(sigpipe, sig_ign); // 忽略管道错误信号 因为当网络连接中某个套接字被关闭或者重启时,该套接字已经发送缓冲区中的数据都发送完毕了,但是它仍然可以接收数据 此时该套接字就会产生sigpipe信号 while (_isrunning) { std::string clientip; uint16_t clientport; int sockfd = _listensock.accept(&clientip, &clientport); // 接受客户端连接并返回套接字描述符 if (sockfd < 0) continue; // 连接失败就继续尝试重连 pid_t pid = fork(); // 创建子进程 帮助我们处理每个客户端的请求 if (pid < 0) // 出错 { lg(error, "fork error"); continue; } if (pid == 0) // 子进程 { _listensock.close(); // 关闭监听套接字 防止accept阻塞 std::string inbuffer_stream; // 用来获取客户端发来的所有数据 while (true) { char buffer[1024]; ssize_t n = read(sockfd, buffer, sizeof(buffer)); // 读取客户端数据 if (n == 0) // 客户端断开了 { lg(info, "client %s:%d disconnected", clientip.c_str(), clientport); break; } if (n < 0) // 读取出错 { lg(error, "read error"); break; } // 拼接所有数据 buffer[n]=0; inbuffer_stream+=buffer; lg(debug, "debug:\n%s", inbuffer_stream.c_str());// 调试看看整个流的信息 //有可能一个流里面有多个报文,所以我们要循环去处理 while(1) { std::string info =_callback(inbuffer_stream);// 调用回调函数获取服务端需要的数据 //看看剩余的报文信息 if(info.empty()) break;//说明读不到完整报文了 lg(debug, "debug:\n%s", inbuffer_stream.c_str());// 调试看看整个流的信息 lg(debug, "debug,response:\n%s",info.c_str());// 调试看看发给客户端数据 write(sockfd, info.c_str(), info.size()); // 发送数据给客户端 } } exit(0); // 子进程结束 } close(sockfd); // 关闭套接字 } } private: uint16_t _port; // 端口号 sock _listensock; // 监听套接字 func_t _callback; // 回调函数 用来提供服务 // ip模式是0 bool _isrunning; // 是否运行 };
1、tcpserver只负责网络通信读到了那些数据,但是关于数据的解析 全部交给callback回调函数去处理这样就是将网络通信和协议解析进行了解耦
2、因为我们并不确定读到的是否是一个完整报文,所以我们要将读到的内容加到inbuffer-stream里
3.4 protocol.hpp
协议其实就是我们双方约定好的结构化字段
为了确保读到完整的报文,在前面加个有关报文长度的字段是是一种方案,在报文的后面加个分割符\其实也是一个方案
甚至我们可以在前面增加protocol select字段,表示我们选择的不同的协议类型!
- "len"/n"x op y"/n就是我们约定request的协议,里面要提供序列化和反序列化的方法
- ”len“/n"result code"/n 就是我们约定的respose的协议里面要提供序列化和反序列化的方法
#pragma once #include <iostream> #include <string> // #define myself 1 const std::string blank_space_sep = " "; const std::string protocol_sep = "\n"; std::string encode(std::string &content) { std::string package = std::to_string(content.size()); package += protocol_sep; package += content; package += protocol_sep; return package; } // "len"\n"x op y"\nxxxxxx // "protocolnumber"\n"len"\n"x op y"\nxxxxxx bool decode(std::string &package, std::string *content) { std::size_t pos = package.find(protocol_sep); if(pos == std::string::npos) return false; std::string len_str = package.substr(0, pos); std::size_t len = std::stoi(len_str); // package = len_str + content_str + 2 std::size_t total_len = len_str.size() + len + 2; if(package.size() < total_len) return false; *content = package.substr(pos+1, len); // earse 移除报文 package.erase(0, total_len); package.erase(0, total_len); return true; } // json, protobuf class request { public: request(int data1, int data2, char oper) : _x(data1), _y(data2),_op(oper) { } request() {} public: bool serialize(std::string *out) { // 构建报文的有效载荷 // struct => string, "x op y" std::string s = std::to_string(_x); s += blank_space_sep; s += _op; s += blank_space_sep; s += std::to_string(_y); *out = s; return true; } bool deserialize(const std::string &in) // "x op y" { std::size_t left = in.find(blank_space_sep); if (left == std::string::npos) return false; std::string part_x = in.substr(0, left); std::size_t right = in.rfind(blank_space_sep); if (right == std::string::npos) return false; std::string part_y = in.substr(right + 1); if (left + 2 != right) return false; _op = in[left + 1]; _x = std::stoi(part_x); _y = std::stoi(part_y); return true; } void debugprint() { std::cout << "新请求构建完成: " << _x << _op << _y << "=?" << std::endl; } public: // x op y int _x; int _y; char _op; // + - * / % }; class response { public: response(int res, int c) : _result(res), _code(c) { } response() {} public: bool serialize(std::string *out) { // "result code" // 构建报文的有效载荷 std::string s = std::to_string(_result); s += blank_space_sep; s += std::to_string(_code); *out = s; return true; } bool deserialize(const std::string &in) // "result code" { std::size_t pos = in.find(blank_space_sep); if (pos == std::string::npos) return false; std::string part_left = in.substr(0, pos); std::string part_right = in.substr(pos+1); _result = std::stoi(part_left); _code = std::stoi(part_right); return true; } void debugprint() { std::cout << "结果响应完成, result: " << _result << ", code: "<< _code << std::endl; } public: int _result; int _code; // 0,可信,否则!0具体是几,表明对应的错误原因 };
不仅需要有序列化和反序列化的方法,还要有添加报头和解析报头(要有很多检查 将一个有效的报文提取出来)的方法
3.5 servercal.hpp
// 计算器服务 #include <iostream> #include "protocol.hpp" //协议头文件 必须遵守 enum { div_zero = 1, mod_zero = 2, other_oper = 3 }; class servercal { public: servercal() {} ~servercal() {} response calhelper(const request&req) { response resp(0,0); 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._code = div_zero; else resp._result = req._x / req._y; break; case '%': if (req._y == 0) resp._code = mod_zero; else resp._result = req._x % req._y; break; default: resp._code=other_oper; break; } return resp; } //设计一个回调方法 来帮助我们计算 std::string cal(std::string package) //回调方法 到时传过去 { std::string content;//返回的内容 bool r=decode(package,&content); if(!r) return "";//报文不完整 //我们要将这个报文反序列化拿到数据 然后计算成respond 然后再序列化发给客户端 request req; r=req.deserialize(content); //"10 + 20 " -> if(!r) return ""; //解析失败 content=""; //清空再利用 response resp=calhelper(req); resp.serialize(&content); content=encode(content);//把报头加上去 return content; } };
在这里写一个回调方法,如果解析失败的话就返回空串
3.6 servercal.cc
#include"servercal.hpp" #include"tcpserver.hpp" static void usage(const std::string &proc) { std::cout << "\nusage: " << proc << " port\n" << std::endl; } int main(int argc, char *argv[])//./servercal 8080 { if(argc != 2) { usage(argv[0]); exit(0); } uint16_t port = atoi(argv[1]); servercal cal;//用来做计算请求的对象 tcpserver *tsvp = new tcpserver(port,std::bind(&servercal::cal, &cal, std::placeholders::_1));//bind tsvp->initserver(); // daemon(); daemon(0, 0); tsvp->start(); return 0; }
3.7clientcal.cc
#include <iostream> #include <string> #include <ctime> #include <cassert> #include <unistd.h> #include "socket.hpp" #include "protocol.hpp" //客户端也得知道协议 static void usage(const std::string &proc) { std::cout << "\nusage: " << proc << " serverip serverport\n" << std::endl; } //./client 127.0.0.1 8080 int main(int argc, char *argv[]) { if (argc != 3) { usage(argv[0]); return -1; } std::string serverip = argv[1]; uint16_t serverport = std::stoi(argv[2]); sock sockfd; sockfd.socket(); bool r=sockfd.connect(serverip, serverport);//尝试和服务端连接 if(!r) { std::cout<<"连接失败"<<std::endl; return -1; } //否则就是连接成功 srand(time(nullptr) ^ getpid()); int cnt = 1; const std::string opers = "+-*/%=-=&^"; std::string inbuffer_stream; while(cnt <= 10) { std::cout << "===============第" << cnt << "次测试....., " << "===============" << std::endl; int x = rand() % 100 + 1; usleep(1234); int y = rand() % 100; usleep(4321); char oper = opers[rand()%opers.size()]; request req(x, y, oper); req.debugprint(); std::string package; req.serialize(&package); package = encode(package); write(sockfd.getsockfd(), package.c_str(), package.size()); // std::cout << "这是最新的发出去的请求: " << n << "\n" << package; // n = write(sockfd.fd(), package.c_str(), package.size()); // std::cout << "这是最新的发出去的请求: \n" << n << "\n" << package; // n = write(sockfd.fd(), package.c_str(), package.size()); // std::cout << "这是最新的发出去的请求: \n" << n << "\n" << package; // n = write(sockfd.fd(), package.c_str(), package.size()); // std::cout << "这是最新的发出去的请求: \n" << n << "\n" << package; char buffer[128]; ssize_t n = read(sockfd.getsockfd(), buffer, sizeof(buffer)); // 我们也无法保证我们能读到一个完整的报文 if(n > 0) { buffer[n] = 0; inbuffer_stream += buffer; // "len"\n"result code"\n std::cout << inbuffer_stream << std::endl; std::string content; bool r = decode(inbuffer_stream, &content); // "result code" assert(r); response resp; r = resp.deserialize(content); assert(r); resp.debugprint(); } std::cout << "=================================================" << std::endl; sleep(1); cnt++; } sockfd.close(); return 0; }
3.8 daemon.hpp
#pragma once #include <iostream> #include <cstdlib> #include <unistd.h> #include <signal.h> #include <string> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> const std::string nullfile = "/dev/null"; void daemon(const std::string &cwd = "") { // 1. 忽略其他异常信号 signal(sigcld, sig_ign); signal(sigpipe, sig_ign); signal(sigstop, sig_ign); // 2. 将自己变成独立的会话 if (fork() > 0) exit(0); setsid(); // 3. 更改当前调用进程的工作目录 if (!cwd.empty()) chdir(cwd.c_str()); // 4. 标准输入,标准输出,标准错误重定向至/dev/null int fd = open(nullfile.c_str(), o_rdwr); if(fd > 0) { dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); close(fd); } }
3.9 json简单使用
其实我们有更好的两个工具能帮我们完成序列化和反序列化,一个是json 一个是productor
- 关于json的下载
安装这个json,其实就是将头文件和源文件安装在制定的路径下
- 头文件:
- 库文件:
- 使用第三方库必须要指定链接
测试代码:
#include <iostream> #include <jsoncpp/json/json.h> #include <unistd.h> // {a:120, b:"123"} int main() { json::value part1; part1["haha"] = "haha"; part1["hehe"] = "hehe"; json::value root; root["x"] = 100; root["y"] = 200; root["op"] = '+'; root["desc"] = "this is a + oper"; root["test"] = part1; //json::fastwriter w; json::styledwriter w; std::string res = w.write(root); std::cout << res << std::endl; sleep(3); json::value v; json::reader r; r.parse(res, v); int x = v["x"].asint(); int y = v["y"].asint(); char op = v["op"].asint(); std::string desc = v["desc"].asstring(); json::value temp = v["test"]; std::cout << x << std::endl; std::cout << y << std::endl; std::cout << op << std::endl; std::cout << desc << std::endl; return 0; }
(1)头文件必须要指明路径
(2)json是万能类 value代表对象
(3)fastwrite是快速写stylewrite是风格写
(4)asint表示解析成int类型
3.10protocol.hpp改进版
#pragma once #include <iostream> #include <string> #include<jsoncpp/json/json.h> // #define myself 1 const std::string blank_space_sep = " "; const std::string protocol_sep = "\n"; std::string encode(std::string &content) { std::string package = std::to_string(content.size()); package += protocol_sep; package += content; package += protocol_sep; return package; } // "len"\n"x op y"\nxxxxxx // "protocolnumber"\n"len"\n"x op y"\nxxxxxx bool decode(std::string &package, std::string *content) { std::size_t pos = package.find(protocol_sep); if(pos == std::string::npos) return false; std::string len_str = package.substr(0, pos); std::size_t len = std::stoi(len_str); // package = len_str + content_str + 2 std::size_t total_len = len_str.size() + len + 2; if(package.size() < total_len) return false; *content = package.substr(pos+1, len); // earse 移除报文 package.erase(0, total_len); package.erase(0, total_len); return true; } // json, protobuf class request { public: request(int data1, int data2, char oper) : _x(data1), _y(data2),_op(oper) { } request() {} public: bool serialize(std::string *out) { #ifdef myself // 构建报文的有效载荷 // struct => string, "x op y" std::string s = std::to_string(_x); s += blank_space_sep; s += _op; s += blank_space_sep; s += std::to_string(_y); *out = s; return true; #else json::value root; root["x"] = _x; root["y"] = _y; root["op"] = _op; // json::fastwriter w; json::styledwriter w; *out = w.write(root); return true; #endif } bool deserialize(const std::string &in) // "x op y" { #ifdef myself std::size_t left = in.find(blank_space_sep); if (left == std::string::npos) return false; std::string part_x = in.substr(0, left); std::size_t right = in.rfind(blank_space_sep); if (right == std::string::npos) return false; std::string part_y = in.substr(right + 1); if (left + 2 != right) return false; _op = in[left + 1]; _x = std::stoi(part_x); _y = std::stoi(part_y); return true; #else json::value root; json::reader r; r.parse(in, root); _x = root["x"].asint(); _y = root["y"].asint(); _op = root["op"].asint(); return true; #endif } void debugprint() { std::cout << "新请求构建完成: " << _x << _op << _y << "=?" << std::endl; } public: // x op y int _x; int _y; char _op; // + - * / % }; class response { public: response(int res, int c) : _result(res), _code(c) { } response() {} public: bool serialize(std::string *out) { #ifdef myself // "result code" // 构建报文的有效载荷 std::string s = std::to_string(_result); s += blank_space_sep; s += std::to_string(_code); *out = s; return true; #else json::value root; root["result"] = _result; root["code"] = _code; // json::fastwriter w; json::styledwriter w; *out = w.write(root); return true; #endif } bool deserialize(const std::string &in) // "result code" { #ifdef myself std::size_t pos = in.find(blank_space_sep); if (pos == std::string::npos) return false; std::string part_left = in.substr(0, pos); std::string part_right = in.substr(pos+1); _result = std::stoi(part_left); _code = std::stoi(part_right); return true; #else json::value root; json::reader r; r.parse(in, root); _result = root["result"].asint(); _code = root["code"].asint(); return true; #endif } void debugprint() { std::cout << "结果响应完成, result: " << _result << ", code: "<< _code << std::endl; } public: int _result; int _code; // 0,可信,否则!0具体是几,表明对应的错误原因 };
3.11 makefile
.phony:all all:servercal clientcal flag=#-dmyself=1 lib=-ljsoncpp servercal:servercal.cc g++ -o $@ $^ -std=c++11 $(lib) $(flag) clientcal:clientcal.cc g++ -o $@ $^ -std=c++11 -g $(lib) $(flag) .phony:clean clean: rm -f clientcal servercal
四,再谈七层协议
- 会话层: 由服务端解决获取新链接,维护整个链接的使用情况,创建子进程来对外提供服务,相当于没访问一次服务我就会创建一个新的会话,然后去处理新的连接,不需要就关掉
- 表示层:相当于协议的序列化和反序列化
- 应用层:处理数据,不同的数据需要有不同的协议
为什么要压成一层呢??因为以上都是在用户层去实现的,因为方法由很多但是谁也说服不了谁,所以无法统一把他搞到os模块而必须由用户根据不同的场景去定制
每次服务都要自己去定自定义协议吗??其实是不需要的,有人想就会有人去做,所以应用层的协议大部分不需要自己写,可以直接用就可以了,因为他全都考虑到了。 一般公司产品比较严格不会让你使用第三方库。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。