阅读量:0
应用层协议(自定义协议)(序列和反序列化)
一、引言–应用层的使用
二、应用层
协议是一种 “约定”. socket api的接口, 在读写数据时, 都是按 “字符串” 的方式来发送接收的. 如果我们要传输一些"结构化的数据" 怎么办呢?
1、网络版本计算器
(1)协议定制和序列反序列化
结构化数据,所有主机都要是一样的结构体类型(协议的定制),再通过网络的序列和反序列化方便数据的收发。
(2)网络版计算器协议定制
i、封装有效载荷(默认上面图是Request的,下面图是Response的)
其实就是将这个结构体转化成为"x op y"!和"result code"!
ii、封装报头(单独出来的函数)
“len”\n"s"
iii、分离有效载荷(默认上面图是Request的,下面图是Response的)
将string s 进行分离出来,分离成整型或者char类型的。
iv、解析报文(单独出来的函数)
v、测试结果
(3)计算器函数
CalHelper函数是进行完加减乘除取模后返回结果的函数,Calculator是将计算结果进行封装完报头的函数。
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 = DIVERRO; } else { resp.result = req.x / req.y; } break; } case '%': { if (req.y == 0) { resp.code = MOERROR; } else { resp.result = req.x % req.y; } break; } default: resp.code = OTHERERROR; break; } return resp; } // "len"\n"10 + 20"\n std::string Calculator(std::string &package) { std::string content; bool r = Decode(package, &content); // 分离出有效载荷 // "len"\n"10 + 20"\n if (!r) { return ""; } // 反序列化 -- 分离开每一个元素 Request req; r = req.Deserialize(content); // x=10 op=+ y=20 if (!r) { return ""; } // 处理结果 content = ""; Response resp = CalHelper(req); // result=30 code=0 // 序列化 resp.Serialize(&content); // "30 0" // 封装报头 content = Encode(content); // "len"\n"30 0"\n return content; }
(4)计算器与网络连接(网络服务代码)
代码逻辑为:InitTcpServer函数是用来进行监听套接字的设置绑定和监听的,RunTcpServer函数是用来将底层的监听套接字拿到上层来使用并进行提供服务的。
#pragma once #include "Log.hpp" #include "Socket.hpp" #include <signal.h> #include <functional> using func_t = std::function<std::string(std::string &package)>; class TcpServer { public: TcpServer(uint16_t port, func_t callback) : _port(port), _callback(callback) { } bool InitTcpServer() { _listensocketfd.Socket(); _listensocketfd.Bind(_port); _listensocketfd.Listen(); lg(Info, "init servercal..."); return true; } void RunTcpServer() { signal(SIGCHLD, SIG_IGN); signal(SIGPIPE, SIG_IGN); while (true) { std::string clientip; uint16_t clientport; int socketfd = _listensocketfd.Accept(&clientip, &clientport); if (socketfd < 0) { continue; } lg(Info, "accept a new link..., sockfd:%d, clientip:%s, clientport:%d", socketfd, clientip.c_str(), clientport); // 提供服务 // pid_t id = fork(); if (fork() == 0) { _listensocketfd.Close(); std::string inbuffer_stream; // 数据计算 while (true) { char buffer[128]; ssize_t n = read(socketfd, buffer, sizeof(buffer)); if (n > 0) { buffer[n] = 0; inbuffer_stream += buffer; lg(Debug, "debug:%s", inbuffer_stream.c_str()); // while (true) //{ std::string info = _callback(inbuffer_stream); if (info.empty()) continue; // break; // lg(Debug, "debug, response:\n%s", info.c_str()); // lg(Debug, "debug:\n%s", inbuffer_stream.c_str()); write(socketfd, info.c_str(), info.size()); //} } else if (n == 0) { break; } else break; } exit(0); } close(socketfd); } } ~TcpServer() { } private: Sock _listensocketfd; uint16_t _port; func_t _callback; };
回调函数示意:
(5)附加:Socket封装
#pragma once #include <iostream> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <netinet/in.h> #include <sys/socket.h> #include <arpa/inet.h> #include <cstring> #include <string> #include "Log.hpp" //extern Log lg; enum { SOCKETERR = 2, BINDERR, LISTENERR }; const int backlog = 10; class Sock { public: Sock() { } ~Sock() { } public: void Socket() { _socketfd = socket(AF_INET, SOCK_STREAM, 0); if (_socketfd < 0) { lg(Fatal, "socket create err, %d:%s", errno, strerror(errno)); exit(SOCKETERR); } } void Bind(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(_socketfd, (struct sockaddr *)&local, sizeof(local)) < 0) { lg(Fatal, "bind error, %d:%s", errno, strerror(errno)); exit(BINDERR); } } void Listen() { if (listen(_socketfd, backlog) < 0) { lg(Fatal, "listen error, %d:%s", errno, strerror(errno)); exit(LISTENERR); } } int Accept(std::string *clientip, uint16_t *clientport) { struct sockaddr_in peer; socklen_t len = sizeof(peer); int newfd = accept(_socketfd, (struct sockaddr *)&peer, &len); if (newfd < 0) { lg(Warning, "accept error, %d:%s", errno, strerror(errno)); return -1; } char ipstr[64]; inet_ntop(AF_INET, &(peer.sin_addr), ipstr, sizeof(ipstr)); *clientip = ipstr; *clientport = ntohs(peer.sin_port); return newfd; } int Connect() { return 0; } void Close() { close(_socketfd); } private: int _socketfd; };
(6)服务端代码
#include "TcpServer.hpp" #include "Protocol.hpp" #include "Servercal.hpp" static void Usage(const std::string &proc) { std::cout << "\nUsage" << proc << " port\n\n" << std::endl; } int main(int argc, char *argv[]) { if (argc != 2) { Usage(argv[0]); exit(0); } uint16_t clientport = std::stoi(argv[1]); ServerCal cal; TcpServer *tsvp = new TcpServer(clientport, std::bind(&ServerCal::Calculator, &cal, std::placeholders::_1)); tsvp->InitTcpServer(); tsvp->RunTcpServer(); return 0; }
(7)测试结果
测试结果Debug
解决方法:每一次都删掉规定好的报文
(8)客户端编写
Socket.hpp:
ClientCal.cc:
#include <iostream> #include <unistd.h> #include <cassert> #include "Socket.hpp" #include "Protocol.hpp" static void Usage(const std::string &proc) { std::cout << "\nUsage" << proc << " serverip: serverport\n\n" << std::endl; } // ./clientcal 127.0.0.1 8888 int main(int argc, char *argv[]) { if (argc != 3) { Usage(argv[0]); exit(0); } uint16_t serverport = std::stoi(argv[2]); std::string serverip = argv[1]; Sock socketfd; socketfd.Socket(); bool r = socketfd.Connect(serverip, serverport); if (!r) 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); std::cout << "这是新的发出去的请求:\n" << package; write(socketfd.Fd(), package.c_str(), package.size()); char buffer[128]; ssize_t n = read(socketfd.Fd(), buffer, sizeof(buffer)); // 保证不了读到的是一个完整的报文 if (n > 0) { buffer[n] = 0; inbuffer_stream += buffer; std::string content; bool rqs = Decode(inbuffer_stream, &content); assert(rqs); Response resp; rqs = resp.Deserialize(content); assert(rqs); resp.DebugPrint(); } std::cout << "=================================================" << std::endl; sleep(1); cnt++; } socketfd.Close(); return 0; }
测试结果:
(9)客户端多个请求同时发送
2、json(用来自动支持序列反序列化)
安装命令:sudo yum install -y jsoncpp-devel
安装成功:
头文件:
#include <jsoncpp/json/json.h>
使用:
FastWriter:
测试用例一(序列化):
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; std::string res = w.write(root); std::cout << res << std::endl; return 0; }
编译:
g++ test.cc -ljsoncpp
测试用例二(反序列化):
FastWriter:
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; std::string res = w.write(root); 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; }
测试用例三(序列化):
StyleWriter:
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::StyleWriter w; std::string res = w.write(root); std::cout << res << std::endl; return 0; }
3、用json进行序列反序列化结果
Makefile:
.PHONY:all all:servercal clientcal Flag=-DMySelf=1 Lib=-ljsoncpp servercal:ServerCalculator.cc g++ -o $@ $^ -std=c++11 $(Lib) #$(Flag) clientcal:ClientCalculator.cc g++ -o $@ $^ -std=c++11 $(Lib) #$(Flag) .PHONY:clean clean: rm -f servercal clientcal
Protocol.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; } bool Decode(std::string &package, std::string *content) // "len"\n"x op y"\n { // package = len_str + content_str + 2 总长度等于长度字符串长度+正文长度+两个\n的长度 size_t pos = package.find(protocol_sep); if (pos == std::string::npos) { return false; } std::string len_str = package.substr(0, pos); // 分理出长度 size_t len = std::stoi(len_str); size_t total_len = len_str.size() + len + 2; if (package.size() < total_len) { return false; } // 完整报文--提取有效载荷 *content = package.substr(pos + 1, len); // 移除报文,防止多余报文影响 package.erase(0, total_len); return true; } 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" => "len"\n"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; #else Json::Value root; root["x"] = x; root["y"] = y; root["op"] = op; Json::FastWriter w; *out = w.write(root); #endif return true; } bool Deserialize(const std::string &in) // "x op y" { #ifdef MySelf size_t leftpos = in.find(blank_space_sep); if (leftpos == std::string::npos) { return false; } std::string part_x = in.substr(0, leftpos); size_t rightpos = in.rfind(blank_space_sep); if (rightpos == std::string::npos) { return false; } std::string part_y = in.substr(rightpos + 1); if (leftpos + 1 != rightpos - 1) { return false; } op = in[leftpos + 1]; x = std::stoi(part_x); y = std::stoi(part_y); #else Json::Value root; Json::Reader r; r.parse(in, root); x = root["x"].asInt(); y = root["y"].asInt(); op = root["op"].asInt(); #endif return true; } void DebugPrint() { std::cout << "新请求构建完成:" << x << op << y << "=?" << std::endl; } public: 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 // 构建有效载荷 // "len"\n"result code" std::string s = std::to_string(result); s += blank_space_sep; s += std::to_string(code); *out = s; #else Json::Value root; root["result"] = result; root["code"] = code; Json::FastWriter w; *out = w.write(root); #endif return true; } bool Deserialize(const std::string &in) // "result code" { #ifdef MySelf size_t pos = in.find(blank_space_sep); if (pos == std::string::npos) { return false; } std::string res = in.substr(0, pos); std::string cod = in.substr(pos + 1); result = std::stoi(res); code = std::stoi(cod); #else Json::Value root; Json::Reader r; r.parse(in, root); result = root["result"].asInt(); code = root["code"].asInt(); #endif return true; } void DebugPrint() { std::cout << "结果响应完成, 结果是:" << "result:" << result << " " << "code:" << code << std::endl; } public: int result; int code; // 0表示可信,非零表示不可信,相对应的数字表示相对应的错误原因 };
三、重谈OSI7层模型
这三层不在内核中实现,是我们手动实现。