应用层与协议
我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层.
协议是一种 "约定". socket api 的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果我们要传输一些 "结构化的数据" 怎么办呢?
可以自定义协议!
网络版计算器(应用层)
例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端。
约定方案(大体框架):
cpp
struct request
{
int x;//计算数1
int y;//计算数2
char op;//计算符号
}
cpp
struct response
{
int result;//计算结果
int code;//表示结果是否正确,正确为0等等
}
这就是一种约定。
假设两个人在聊天,发送的消息经过这个协议会形成字符串数据,这个就叫序列化,然后通过网络发送到对面手里,拿到之后把里面的字符串数据通过协议依次拿到手中。
同理,上面的计算器结构也是一样的。
x+y的时候计算符号两边规定添加一个空格。
\n作为一个有效字符串的结束符,这样就不会导致两个字符串合在一起无法区分的问题。
我们可以添加一个报头,报头里面装字符串的长度。(不算\n)
"9"\n"100 + 200"\n
如果是多个报文就是这种格式
"9"\n"100 + 200"\n"9"\n"100 + 200"\n"9"\n"100 + 200"\n
代码实现
打印模块
cpp
//log.hpp
#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 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;
};
Log lg;
计算协议
cpp
//Protocol.hpp
#pragma once
#include <iostream>
#include <string>
using namespace std;
const string blank_space_sep = " ";
const string protocol_sep = "\n";//有效字符串数据的分隔符,防止很多字符串数据叠加在一起难以分辨
string Encode(string &content)//添加字符串长度报文
{
string package = 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(string &package,string *content)
{
size_t pos = package.find(protocol_sep);
if(pos == string::npos) return false;
string len_str = package.substr(0, pos);
size_t len = stoi(len_str);
// package = len_str + content_str + 2
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;
}
class Request
{
public:
Request(int data1,int data2,char oper):x(data1),y(data2),op(oper)
{}
Request()
{}
public:
bool Serialize(string *out)//序列化
{
// 构建报文的有效载荷
// struct => string, "x op y"
string s = to_string(x);
s += blank_space_sep;
s += op;
s += blank_space_sep;
s += to_string(y);
*out = s;
return true;
}
bool Deserialize(const string &in)//反序列化
{
size_t left = in.find(blank_space_sep);
if (left == string::npos)
return false;
string part_x = in.substr(0, left);
size_t right = in.rfind(blank_space_sep);
if (right == string::npos)
return false;
string part_y = in.substr(right + 1);
if (left + 2 != right)
return false;
op = in[left + 1];
x = stoi(part_x);
y = stoi(part_y);
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(string *out)
{
// "result code"
// 构建报文的有效载荷
string s = to_string(result);
s += blank_space_sep;
s += to_string(code);
*out = s;
return true;
}
bool Deserialize(const std::string &in)
{
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具体是几,表明对应错误的原因
};
创建套接字模块
cpp
//Socket.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 "log.hpp"
using namespace std;
enum
{
SocketErr = 2,
BindErr,
ListenErr,
};
const int backlog = 10;
class Sock
{
public:
Sock()
{}
~Sock()
{}
void Socket()
{
sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd_ < 0)
{
lg(Fatal, "socker error, %s: %d", strerror(errno), 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(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0)
{
lg(Fatal, "bind error, %s: %d", strerror(errno), errno);
exit(BindErr);
}
}
void Listen()
{
if (listen(sockfd_, backlog) < 0)
{
lg(Fatal, "listen error, %s: %d", strerror(errno), errno);
exit(ListenErr);
}
}
int Accept(string *clientip, uint16_t *clientport)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int newfd = accept(sockfd_, (struct sockaddr*)&peer, &len);
if(newfd < 0)
{
lg(Warning, "accept error, %s: %d", strerror(errno), 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;
}
bool Connect(const string &ip, const uint16_t &port)
{
struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_port = htons(port);
inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));
int n = connect(sockfd_, (struct sockaddr*)&peer, sizeof(peer));
if(n == -1)
{
std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;
return false;
}
return true;
}
void Close()
{
close(sockfd_);
}
int Fd()
{
return sockfd_;
}
private:
int sockfd_;
};
TCP服务器
cpp
//TcpServer.hpp
#pragma once
#include <functional>
#include <string>
#include <signal.h>
#include "log.hpp"
#include "Socket.hpp"
using func_t = function<string(string &package)>;
class TcpServer
{
public:
TcpServer(uint16_t port, func_t callback) : port_(port), callback_(callback)
{}
bool InitServer()
{
listensock_.Socket();
listensock_.Bind(port_);
listensock_.Listen();
lg(Info,"init server...done");
return true;
}
void Start()
{
signal(SIGCHLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
while(true)
{
string clientip;
uint16_t clientport;
int sockfd = listensock_.Accept(&clientip,&clientport);
if(sockfd < 0) continue;
//提供服务
lg(Info, "accept a new link, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);
if(fork()==0)
{
listensock_.Close();
string inbuffer_stream;
// 数据计算
while (true)
{
char buffer[1280];
ssize_t n = read(sockfd, buffer, sizeof(buffer));
if (n > 0)
{
buffer[n] = 0;
inbuffer_stream += buffer;
lg(Debug, "debug:\n%s", inbuffer_stream.c_str());
while(true)//可以处理多个报文
{
string info = callback_(inbuffer_stream);
if (info.empty()) break;//如果没有报文了就退出
write(sockfd, info.c_str(), info.size());
}
}
else if (n == 0)
break;
else
break;
}
exit(0);
}
close(sockfd);
}
}
~TcpServer()
{}
private:
uint16_t port_;
Sock listensock_;
func_t callback_;
};
服务端
cpp
//ServerCal.hpp
#pragma once
#include <iostream>
#include "Protocol.hpp"
enum
{
Div_Zero = 1,
Mod_Zero,
Other_Oper
};
class ServerCal
{
public:
ServerCal()
{
}
Response CalculatorHelper(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;
}
// "len"\n"10 + 20"\n
string Calculator(string &package)
{
string content;
bool r = Decode(package, &content); // "len"\n"10 + 20"\n
if (!r)
return "";
// "10 + 20"
Request req;
r = req.Deserialize(content); // "10 + 20" ->x=10 op=+ y=20
if (!r)
return "";
content = ""; //
Response resp = CalculatorHelper(req); // result=30 code=0;
resp.Serialize(&content); // "30 0"
content = Encode(content); // "len"\n"30 0"
return content;
}
~ServerCal()
{
}
};
cpp
//ServerCal.cc
#include <unistd.h>
// #include "Daemon.hpp"
static void Usage(const std::string &proc)
{
std::cout << "\nUsage: " << proc << " port\n" << std::endl;
}
// ./servercal 8080
int main(int argc, char *argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(0);
}
uint16_t port = std::stoi(argv[1]);
ServerCal cal;
TcpServer *tsvp = new TcpServer(port, std::bind(&ServerCal::Calculator, &cal, std::placeholders::_1));
tsvp->InitServer();
// Daemon();
daemon(0, 0);
tsvp->Start();
// Response resp(1000, 0);
// std::string content;
// resp.Serialize(&content);
// std::cout << content << std::endl;
// std::string package = Encode(content);
// std::cout << package;
// content = "";
// bool r = Decode(package, &content);
// std::cout << content << std::endl;
// Response temp;
// temp.Deserialize(content);
// std::cout << temp.result << std::endl;
// std::cout << temp.code << std::endl;
// Request req(12364566, 43454356, '+');
// std::string s;
// req.Serialize(&s);
// s = Encode(s);
// std::cout << s;
// std::string content;
// bool r = Decode(s, &content);
// std::cout << content << std::endl;
// Request temp;
// temp.Deserialize(content);
// std::cout << temp.x << std::endl;
// std::cout << temp.op << std::endl;
// std::cout << temp.y << std::endl;
return 0;
}
客户端
cpp
//ClientCal.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;
}
// ./clientcal ip port
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(0);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
Sock sockfd;
sockfd.Socket();
bool r = sockfd.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);
write(sockfd.Fd(), 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.Fd(), 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;
}
json
JSON(JavaScript Object Notation,JavaScript对象表示法)是基于ECMAScript的一个子集设计的,是一种开放标准的文件格式和数据交换格式,它易于人阅读和编写,同时也易于机器解析和生成。JSON独立于语言设计,很多编程语言都支持JSON格式的数据交换。JSON是一种常用的数据格式,在电子数据交换中有多种用途,包括与服务器之间的Web应用程序的数据交换。其简洁和清晰的层次结构有效地提升了网络传输效率,使其成为理想的数据交换语言。其文件通常使用扩展名.json。(百度介绍)
安装json的命令:
sudo yum install epel-releaseyum install -y jq
安装之后库的名字是jsoncpp。
cpp
#include <iostream>
#include <jsoncpp/json/json.h>
using namespace std;
int main()
{
Json::Value root;
root["x"] = 100;
root["y"] = 200;
root["op"] = '+';
root["dect"] = "this is a + oper";
Json::FastWriter w;
string res = w.write(root);
cout << res <<endl;
return 0;
}

打印出来的结果是按风格序列化之后的。
cpp
#include <iostream>
#include <jsoncpp/json/json.h>
using namespace std;
int main()
{
Json::Value root;
root["x"] = 100;
root["y"] = 200;
root["op"] = '+';
root["dect"] = "this is a + oper";
//Json::FastWriter w;
Json::StyledWriter w;
string res = w.write(root);
cout << res <<endl;
return 0;
}

这个是按风格增加可读性的序列化。
cpp
#include <iostream>
#include <jsoncpp/json/json.h>
#include <unistd.h>
using namespace std;
int main()
{
Json::Value root;//这里面能放任何类型的数据包括Json
root["x"] = 100;
root["y"] = 200;
root["op"] = '+';
root["dect"] = "this is a + oper";
Json::FastWriter w;
//Json::StyledWriter w;
string res = w.write(root);
cout << res <<endl;
Json::Value v;
Json::Reader r;
r.parse(res,v);//第一个参数是要反序列化的字符串,第二个参数是要放在哪里,第三个默认缺省
int x = v["x"].asInt();//提取x的整数
int y = v["y"].asInt();//提取y的整数
char op = v["op"].asInt();//提取op的字符
string dect = v["dect"].asString();//提取dect的字符串
cout << x <<endl;
cout << y <<endl;
cout << op <<endl;
cout << dect <<endl;
return 0;
}

这是反序列化。
总结
上述代码说明,OSI七层中的会话层(决定什么时候连接,连接多久,连接什么时候断开),表示层(接收图像文字等信息),应用层(针对每个应用的协议),其实没办法在内核实现,刚刚实现的网络版本计算器说明这些都不是固定的,每个应用和用户需要是不一样的。