目錄
一、應(yīng)用層協(xié)議概述
二、序列化與反序列化
Protocal.h頭文件
Server.h頭文件
Client.h頭文件
server.cpp源文件
client.cpp源文件
一、應(yīng)用層協(xié)議概述
什么是應(yīng)用層?我們通過編寫程序解決一個個實際問題、滿足我們?nèi)粘P枨蟮木W(wǎng)絡(luò)程序,都是應(yīng)用層程序。
協(xié)議是一種“約定”,socket的api接口,在讀寫數(shù)據(jù)時,都是按“字符串”的方式發(fā)送數(shù)據(jù),那么如果我們要傳輸一些“結(jié)構(gòu)化的數(shù)據(jù)”怎么辦?
這時就需要用到應(yīng)用層協(xié)議,一端將結(jié)構(gòu)化數(shù)據(jù)轉(zhuǎn)化成字符串格式,另一端通過協(xié)議的“約定”格式,將其解析成結(jié)構(gòu)化數(shù)據(jù)。
應(yīng)用層協(xié)議的本質(zhì),就是對傳輸層的字符串數(shù)據(jù)進行序列化和反序列化,使結(jié)構(gòu)化數(shù)據(jù)可以進行網(wǎng)絡(luò)通信。
雖然應(yīng)用層協(xié)議是程序員根據(jù)不同的程序進行定制的,但實際上,已經(jīng)有大佬定義了很多現(xiàn)成又非常好用的應(yīng)用層協(xié)議,供我們直接參考使用,例如?HTTP / HTTPS(超文本傳輸協(xié)議)。
URL:我們俗稱的“網(wǎng)址”,其實就是URL,例如https://blog.csdn.net/phoenixFlyzzz?type=blog
這是我的博客主頁網(wǎng)址的url,每個url都是有固定格式的,通過特殊符號分隔:
- https:// —— 協(xié)議方案名
- blog.csdn.net —— 服務(wù)器域名
- /phoenixFlyzzz —— 帶層次的文件路徑
- ?type=blog —— 參數(shù)
在這個URL中,并沒有完全展示一個網(wǎng)址的全部結(jié)構(gòu),但一個URL也不是一定有全部結(jié)構(gòu)的,有些結(jié)構(gòu)可以省略,有些結(jié)構(gòu)不寫會有默認值。
urlencode和urldecode:url的編碼和解碼,像 / ? : 這些字符,已經(jīng)被URL當作特殊字符處理了,用于區(qū)分一個URL中不同的結(jié)構(gòu)字段,因此這些字符不能隨意出現(xiàn)。如果某個參數(shù)中需要用到這種特殊字符,比必須先對特殊字符進行轉(zhuǎn)義。
轉(zhuǎn)義的規(guī)則:將需要轉(zhuǎn)碼的字符轉(zhuǎn)為16進制數(shù),然后從左到右,取4位(不足4位直接處理)每2位做一位,前面加上%,編碼成%XY格式。
例如,“+”被轉(zhuǎn)義成“%2B”:
urldecode就是urlencode的逆過程,將轉(zhuǎn)義過的字符進行解碼。
二、序列化與反序列化
設(shè)計:通過TCP協(xié)議定制一個網(wǎng)絡(luò)計算器,客戶端發(fā)送算式,服務(wù)器返回結(jié)果
算法:復雜算式的運算規(guī)則、字符串數(shù)據(jù)序列化與反序列化、Json數(shù)據(jù)解析
由于用到了Json庫,需要在編譯的時候加上 -ljsoncpp
Protocal.h頭文件
網(wǎng)絡(luò)計算器的協(xié)議定制(序列化與反序列化),定義請求體和響應(yīng)體的對象,請求體與響應(yīng)體對象在數(shù)據(jù)傳輸過程中都需要進行序列化和反序列化操作
- 序列化:將輸入的數(shù)據(jù)轉(zhuǎn)換成規(guī)定格式的字符串
- 反序列化:將規(guī)定格式的字符串轉(zhuǎn)化成結(jié)構(gòu)化數(shù)據(jù)
#pragma once
#include <iostream>
#include <cstring>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <jsoncpp/json/json.h>
using namespace std;
#define SEP " "
#define LINE_SEP "\r\n"
enum
{
OK = 0,
DIV_ZERO,
MOD_ZERO,
OP_ERR
};
// "x op y" -> "content_len"\r\n"x op y"\r\n
string En_Length(const string& text)
{
string send_str = to_string(text.size());
send_str += LINE_SEP;
send_str += text;
send_str += LINE_SEP;
return send_str;
}
// "content_len"\r\n"x op y"\r\n
bool De_Length(const string& package, string* text)
{
auto pos = package.find(LINE_SEP);
if (pos == string::npos)
return false;
string text_len_str = package.substr(0, pos);
int text_len = stoi(text_len_str);
*text = package.substr(pos + strlen(LINE_SEP), text_len);
return true;
}
// 通信協(xié)議不止一種,需要將協(xié)議進行編號,以供os分辨
// "content_len"\r\n"協(xié)議編號"\r\n"x op y"\r\n
class Request
{
public:
Request(int x = 0, int y = 0, char op = 0)
: _x(x), _y(y), _op(op)
{}
// 序列化
bool Serialize(string* out)
{
#ifdef MYSELF
*out = "";
// 結(jié)構(gòu)化 -> "x op y"
string x_str = to_string(_x);
string y_str = to_string(_y);
*out = x_str;
*out += SEP;
*out += _op;
*out += SEP;
*out += y_str;
#else
Json::Value root;
root["first"] = _x;
root["second"] = _y;
root["oper"] = _op;
Json::FastWriter write;
*out = write.write(root);
#endif
return true;
}
// 反序列化
bool Deserialiaze(const string& in)
{
#ifdef MYSELF
// "x op y" -> 結(jié)構(gòu)化
auto left = in.find(SEP);
auto right = in.rfind(SEP);
if (left == string::npos || right == string::npos)
return false;
if (left == right)
return false;
if (right - (left + strlen(SEP)) != 1)
return false;
string x_str = in.substr(0, left);
string y_str = in.substr(right + strlen(SEP));
if (x_str.empty() || y_str.empty())
return false;
_x = stoi(x_str);
_y = stoi(y_str);
_op = in[left + strlen(SEP)];
#else
Json::Value root;
Json::Reader reader;
reader.parse(in, root);
_x = root["first"].asInt();
_y = root["second"].asInt();
_op = root["oper"].asInt();
#endif
return true;
}
public:
int _x, _y;
char _op;
};
class Response
{
public:
Response(int exitcode = 0, int res = 0)
: _exitcode(exitcode), _res(res)
{}
bool Serialize(string* out)
{
#ifdef MYSELF
*out = "";
string ec_str = to_string(_exitcode);
string res_str = to_string(_res);
*out = ec_str;
*out += SEP;
*out += res_str;
#else
Json::Value root;
root["exitcode"] = _exitcode;
root["result"] = _res;
Json::FastWriter writer;
*out = writer.write(root);
#endif
return true;
}
bool Deserialize(const string& in)
{
#ifdef MYSELF
auto mid = in.find(SEP);
if (mid == string::npos)
return false;
string ec_str = in.substr(0, mid);
string res_str = in.substr(mid + strlen(SEP));
if (ec_str.empty() || res_str.empty())
return false;
_exitcode = stoi(ec_str);
_res = stoi(res_str);
#else
Json::Value root;
Json::Reader reader;
reader.parse(in, root);
_exitcode = root["exitcode"].asInt();
_res = root["result"].asInt();
#endif
return true;
}
public:
int _exitcode;
int _res;
};
// 讀取數(shù)據(jù)包
// "content_len"\r\n"x op y"\r\n
bool Recv_Package(int sock, string& inbuf, string* text)
{
char buf[1024];
while (true)
{
ssize_t n = recv(sock, buf, sizeof(buf) - 1, 0);
if (n > 0)
{
buf[n] = 0;
inbuf += buf;
auto pos = inbuf.find(LINE_SEP);
if (pos == string::npos)
continue;
string text_len_str = inbuf.substr(0, pos);
int text_len = stoi(text_len_str);
int total_len = text_len_str.size() + 2 * strlen(LINE_SEP) + text_len;
cout << "處理前#inbuf:\n" << inbuf << endl;
if (inbuf.size() < total_len)
{
cout << "輸入不符合協(xié)議規(guī)定" << endl;
continue;
}
*text = inbuf.substr(0, total_len);
inbuf.erase(0, total_len);
cout << "處理后#inbuf:\n" << inbuf << endl;
break;
}
else
{
return false;
}
}
return true;
}
// 計算任務(wù)
bool Cal(const Request& req, Response& resp)
{
resp._exitcode = OK;
resp._res = 0;
if (req._op == '/' && req._y == 0)
{
resp._exitcode = DIV_ZERO;
return false;
}
if (req._op == '%' && req._y == 0)
{
resp._exitcode = MOD_ZERO;
return false;
}
switch (req._op)
{
case '+':
resp._res = req._x + req._y;
break;
case '-':
resp._res = req._x - req._y;
break;
case '*':
resp._res = req._x * req._y;
break;
case '/':
resp._res = req._x / req._y;
break;
case '%':
resp._res = req._x % req._y;
break;
default:
resp._exitcode = OP_ERR;
break;
}
return true;
}
Server.h頭文件
網(wǎng)絡(luò)計算器的服務(wù)器,讀取請求體報文,執(zhí)行計算任務(wù),生成響應(yīng)體報文文章來源:http://www.zghlxwxcb.cn/news/detail-602861.html
#pragma once
#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <functional>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Protocal.h"
using namespace std;
using func_t = function<bool(const Request& req, Response& resp)>;
const static uint16_t g_port = 8080;
const static int g_backlog = 5;
void Handler_Entry(int sock, func_t func)
{
string inbuf;
while (true)
{
// 1. 讀取報文: "content_len"\r\n"x op y"\r\n
string req_text, req_str;
if (!Recv_Package(sock, inbuf, &req_text))
return;
cout << "帶報頭的請求:\n" << req_text << endl;
if (!De_Length(req_text, &req_str))
return;
cout << "去報頭的正文:\n" << req_str << endl;
// 2. 對請求Request反序列化,得到結(jié)構(gòu)化請求對象
Request req;
if (!req.Deserialiaze(req_str))
return;
// 3. 業(yè)務(wù)邏輯, 生成結(jié)構(gòu)化響應(yīng)
Response resp;
func(req, resp); // 處理req,生成resp
// 4. 對相應(yīng)的Response序列化
string resp_str;
resp.Serialize(&resp_str);
cout << "計算完成,序列化響應(yīng):\n" << resp_str << endl;
// 5. 構(gòu)建完整報文,發(fā)送響應(yīng)
string send_str = En_Length(resp_str);
cout << "構(gòu)建完整的響應(yīng)報文:\n" << send_str << endl;
send(sock, send_str.c_str(), send_str.size(), 0);
}
}
class Server
{
public:
Server(const int port)
: _port(port), _listenfd(-1)
{}
void Init()
{
_listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listenfd < 0)
exit(1);
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_addr.s_addr = htonl(INADDR_ANY);
local.sin_port = htons(_port);
if (bind(_listenfd, (struct sockaddr*)&local, sizeof(local)) < 0)
exit(1);
if (listen(_listenfd, g_backlog) < 0)
exit(1);
}
void Start(func_t func)
{
while (true)
{
struct sockaddr_in peer;
socklen_t peer_len = sizeof(peer);
int sock = accept(_listenfd, (struct sockaddr*)&peer, &peer_len);
if (sock < 0)
exit(1);
pid_t id = fork();
if (id == 0)
{
close(_listenfd);
Handler_Entry(sock, func);
close(sock);
exit(0);
}
pid_t ret = waitpid(id, nullptr, 0);
}
}
private:
int _listenfd;
uint16_t _port;
};
Client.h頭文件
客戶端頭文件,通過輸入數(shù)據(jù)生產(chǎn)請求體,將服務(wù)器返回的響應(yīng)體序列化和反序列化文章來源地址http://www.zghlxwxcb.cn/news/detail-602861.html
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Protocal.h"
using namespace std;
class Client
{
public:
Client(const std::string& server_ip, const uint16_t& server_port)
: _sock(-1), _server_ip(server_ip), _server_port(server_port)
{}
void Init()
{
_sock = socket(AF_INET, SOCK_STREAM, 0);
if (_sock < 0)
{
std::cerr << "socket error" << std::endl;
exit(1);
}
}
void Run()
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(_server_port);
server.sin_addr.s_addr = inet_addr(_server_ip.c_str());
if (connect(_sock, (struct sockaddr*)&server, sizeof(server)) < 0)
{
std::cerr << "connect error" << std::endl;
exit(1);
}
else
{
string line;
string inbuf;
while (true)
{
cout << "mycal>>> ";
getline(cin, line);
Request req = Parse_Line(line); // 輸入字符串,生成Request對象
string content;
req.Serialize(&content); // Request對象序列化
string send_str = En_Length(content); // 序列化字符串編碼 -> "content_len"\r\n"x op y"\r\n
send(_sock, send_str.c_str(), send_str.size(), 0);
// 將服務(wù)器的返回結(jié)果序列化與反序列化
string package, text;
if (!Recv_Package(_sock, inbuf, &package))
continue;
if (!De_Length(package, &text))
continue;
Response resp;
resp.Deserialize(text);
cout << "exitcode: " << resp._exitcode << endl;
cout << "result: " << resp._res << endl << endl;
}
}
}
// 將輸入轉(zhuǎn)化為Request結(jié)構(gòu)
Request Parse_Line(const string& line)
{
int status = 0; // 0:操作符之前 1:遇到操作符 2:操作符之后
int cnt = line.size();
string left, right;
char op;
int i = 0;
while (i < cnt)
{
switch (status)
{
case 0:
if (!isdigit(line[i]))
{
if (line[i] == ' ')
{
i++;
break;
}
op = line[i];
status = 1;
}
else
{
left.push_back(line[i++]);
}
break;
case 1:
i++;
if (line[i] == ' ')
break;
status = 2;
break;
case 2:
right.push_back(line[i++]);
break;
}
}
cout << left << ' ' << op << ' ' << right << endl;
return Request(stoi(left), stoi(right), op);
}
~Client()
{
if (_sock >= 0)
close(_sock);
}
private:
int _sock;
string _server_ip;
uint16_t _server_port;
};
server.cpp源文件
#include "Server.h"
#include <memory>
void Usage(std::string proc)
{
std::cout << "Usage:\n\t" << proc << " local_port\n\t";
exit(1);
}
// ./server port
int main(int argc, char* argv[])
{
if (argc != 2)
Usage(argv[0]);
uint16_t port = atoi(argv[1]);
std::unique_ptr<Server> tsvr(new Server(port));
tsvr->Init();
tsvr->Start(Cal);
return 0;
}
client.cpp源文件
#include "Client.h"
#include <memory>
using namespace std;
static void Usage(string proc)
{
cout << "\nUsage:\n\t" << proc << " local_port\n\n";
exit(1);
}
int main(int argc, char* argv[])
{
if (argc != 3)
Usage(argv[0]);
string server_ip = argv[1];
uint16_t server_port = atoi(argv[2]);
unique_ptr<Client> tcli(new Client(server_ip, server_port));
tcli->Init();
tcli->Run();
return 0;
}
到了這里,關(guān)于【Linux后端服務(wù)器開發(fā)】協(xié)議定制(序列化與反序列化)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!