目录
一、应用层协议概述
二、序列化与反序列化
Protocal.h头文件
Server.h头文件
Client.h头文件
server.cpp源文件
client.cpp源文件
一、应用层协议概述
什么是应用层?我们通过编写程序解决一个个实际问题、满足我们日常需求的网络程序,都是应用层程序。
协议是一种“约定”,socket的api接口,在读写数据时,都是按“字符串”的方式发送数据,那么如果我们要传输一些“结构化的数据”怎么办?
这时就需要用到应用层协议,一端将结构化数据转化成字符串格式,另一端通过协议的“约定”格式,将其解析成结构化数据。
应用层协议的本质,就是对传输层的字符串数据进行序列化和反序列化,使结构化数据可以进行网络通信。
虽然应用层协议是程序员根据不同的程序进行定制的,但实际上,已经有大佬定义了很多现成又非常好用的应用层协议,供我们直接参考使用,例如 HTTP / HTTPS(超文本传输协议)。
URL:我们俗称的“网址”,其实就是URL,例如https://blog.csdn.net/phoenixFlyzzz?type=blog
这是我的博客主页网址的url,每个url都是有固定格式的,通过特殊符号分隔:
- https:// —— 协议方案名
- blog.csdn.net —— 服务器域名
- /phoenixFlyzzz —— 带层次的文件路径
- ?type=blog —— 参数
在这个URL中,并没有完全展示一个网址的全部结构,但一个URL也不是一定有全部结构的,有些结构可以省略,有些结构不写会有默认值。
urlencode和urldecode:url的编码和解码,像 / ? : 这些字符,已经被URL当作特殊字符处理了,用于区分一个URL中不同的结构字段,因此这些字符不能随意出现。如果某个参数中需要用到这种特殊字符,比必须先对特殊字符进行转义。
转义的规则:将需要转码的字符转为16进制数,然后从左到右,取4位(不足4位直接处理)每2位做一位,前面加上%,编码成%XY格式。
例如,“+”被转义成“%2B”:
urldecode就是urlencode的逆过程,将转义过的字符进行解码。
二、序列化与反序列化
设计:通过TCP协议定制一个网络计算器,客户端发送算式,服务器返回结果
算法:复杂算式的运算规则、字符串数据序列化与反序列化、Json数据解析
由于用到了Json库,需要在编译的时候加上 -ljsoncpp
Protocal.h头文件
网络计算器的协议定制(序列化与反序列化),定义请求体和响应体的对象,请求体与响应体对象在数据传输过程中都需要进行序列化和反序列化操作
- 序列化:将输入的数据转换成规定格式的字符串
- 反序列化:将规定格式的字符串转化成结构化数据
#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;
}
// 通信协议不止一种,需要将协议进行编号,以供os分辨
// "content_len"\r\n"协议编号"\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 = "";
// 结构化 -> "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" -> 结构化
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;
};
// 读取数据包
// "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 << "输入不符合协议规定" << endl;
continue;
}
*text = inbuf.substr(0, total_len);
inbuf.erase(0, total_len);
cout << "处理后#inbuf:\n" << inbuf << endl;
break;
}
else
{
return false;
}
}
return true;
}
// 计算任务
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头文件
网络计算器的服务器,读取请求体报文,执行计算任务,生成响应体报文
#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反序列化,得到结构化请求对象
Request req;
if (!req.Deserialiaze(req_str))
return;
// 3. 业务逻辑, 生成结构化响应
Response resp;
func(req, resp); // 处理req,生成resp
// 4. 对相应的Response序列化
string resp_str;
resp.Serialize(&resp_str);
cout << "计算完成,序列化响应:\n" << resp_str << endl;
// 5. 构建完整报文,发送响应
string send_str = En_Length(resp_str);
cout << "构建完整的响应报文:\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头文件
客户端头文件,通过输入数据生产请求体,将服务器返回的响应体序列化和反序列化
#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);
// 将服务器的返回结果序列化与反序列化
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;
}
}
}
// 将输入转化为Request结构
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;
}