1. 网络程序分层
我们说过,OSI7层模型十分完美,但是因特网实际上采用的是TCP/IP五层模型:
实际上,对比可以发现,TCP/IP模型实际上就是将OSI的前三层模型合并为了应用层。
这就提示我们,我们设计的应用程序应当存在三个层次:
- 应用层:人机交互层,应用程序可以访问网络服务。
- 表示层:确保数据采用可用形式,并且是数据加密的形式。
- 会话层:维护连接,并负责控制端口和会话。
简单来说,在服务器端,这三层就是提供实际服务的顶层、规定数据传输协议的协议层、负责建立连接与断开连接的服务器层。
#include <pthread.h>
#include "Socket.hpp"
#include "Server.hpp"
#include "Protocol.hpp"
#include "Calculator.hpp"
#include "Daemon.hpp"
using namespace SocketModule;
void Usage(const std::string &name)
{
std::cout << "usage: " << name << " + port" << std::endl;
}
int main(int argc, char* args[])
{
if (argc != 2)
{
Usage(args[0]);
exit(USAGE_ERROR);
}
in_port_t port = std::stoi(args[1]);
// 日志切换为文件策略
USE_FILE_STRATEGY();
// 设置为守护进程
if(Daemon(0, 0) == -1)
{
LOG(LogLevel::FATAL) << "Deamon: 设置守护进程失败! " << strerror(errno);
exit(DAEMON_ERROR);
}
// daemon(0, 0)
// 顶层, 计算器
Calculator calculator;
// 协议层
Protocol protocol([&calculator](const Request& request){
return calculator.Calculate(request);
});
// 服务器层
Server server(port, [&protocol](const std::shared_ptr<TCPConnectSocket>& socket){
protocol.ServerService(socket);
});
server.Run();
return 0;
}
2. 协议
为简单起见,我们定制如下的协议:
- 服务器端只处理类似" x oper y "的简单表达式,所以客户端用于发起请求的结构化数据只包含三个变量:" int x "、" int y "、" char oper "。
- 服务器端处理完表达式后需要返回结果与状态码,所以服务器端用于返回响应的结构化数据需要包含两个变量:" int result "、" int status "。
- 数据在网络上的传输格式为JSON串。
- 一个完整的请求/响应报文包含四个部分:JSON串的长度 + sep + JSON串 + sep。其中sep = " \n\r "。
于是就有下面的协议类(Protocol.hpp):
#pragma once
#include <jsoncpp/json/json.h>
#include "Socket.hpp"
using namespace SocketModule;
class Request
{
public:
Request(){}
Request(int x, int y, char oper)
{
serialize(x, y, oper);
}
Request(const std::string& json_str)
{
if(!deserialize(json_str))
LOG(LogLevel::ERROR) << "Response: 反序列化失败! ";
}
// 序列化
std::string serialize(int x, int y, char oper)
{
Json::Value root;
root["x"] = _x = x;
root["y"] = _y = y;
root["oper"] = _oper = oper;
Json::FastWriter writer;
_json_str = writer.write(root);
return _json_str;
}
// 反序列化
bool deserialize(const std::string& json_ptr)
{
Json::Value root;
Json::Reader reader;
if(reader.parse(json_ptr, root))
{
_json_str = json_ptr;
_x = root["x"].asInt();
_y = root["y"].asInt();
_oper = root["oper"].asInt();
return true;
}
return false;
}
int X() const {return _x;}
int Y() const {return _y;}
int Oper() const {return _oper;}
std::string JsonStr() const {return _json_str;}
private:
int _x, _y;
char _oper;
std::string _json_str;
};
class Response
{
public:
Response(){}
Response(int result, int status)
{
serialize(result, status);
}
Response(const std::string& json_str)
{
if(!deserialize(json_str))
LOG(LogLevel::ERROR) << "Response: 反序列化失败! ";
}
// 序列化
std::string serialize(int result, int status)
{
Json::Value root;
root["result"] = _result = result;
root["status"] = _status = status;
Json::FastWriter writer;
_json_str = writer.write(root);
return _json_str;
}
// 反序列化
bool deserialize(const std::string& json_str)
{
Json::Value root;
Json::Reader reader;
if(reader.parse(json_str, root))
{
_json_str = json_str;
_result = root["result"].asInt();
_status = root["status"].asInt();
return true;
}
return false;
}
int Result() const {return _result;}
int Status() const {return _status;}
std::string JsonStr() const {return _json_str;}
private:
int _result, _status;
std::string _json_str;
};
using request_handler = std::function<Response(const Request&)>;
class Protocol
{
public:
Protocol(request_handler handler)
: _handler(handler)
{}
// 封装
// package = size + esp + json_ptr + esp
void encapsulate(const std::string& json_str, std::string& package)
{
int size = json_str.size();
package = std::to_string(size) + esp + json_str + esp;
}
// 解封装
bool decapsulate(std::string& package, std::string& json_str)
{
auto pos = package.find(esp);
if(pos == std::string::npos)
{
return false;
}
std::string size_str = package.substr(0, pos);
int size = std::stoi(size_str.c_str());
int total_len = size_str.size() + 2 * esp.size() + size;
if(package.size() >= total_len)
{
json_str = package.substr(pos + esp.size(), size);
package.erase(0, total_len);
return true;
}
return false;
}
void ClientService()
{
// todo
}
// 服务器端服务
void ServerService(const std::shared_ptr<TCPConnectSocket>& socket)
{
std::string package, buffer, json_str, send_msg;
while(socket->Receive(buffer))
{
package += buffer;
if(decapsulate(package, json_str))
{
Request request(json_str);
// 使用顶层提供的回调函数来处理请求
Response response = _handler(request);
encapsulate(response.JsonStr(), send_msg);
socket->Send(send_msg);
}
}
LOG(LogLevel::INFO) << "[" << socket->addr().Info() << "]连接已断开! ";
}
private:
static const std::string esp;
// 顶层提供的用于处理请求的回调方法
request_handler _handler;
};
const std::string Protocol::esp = "/n/r";
3. 项目代码
3.1 Calculator.hpp
#pragma once
#include "Protocol.hpp"
#include "Log.hpp"
using namespace LogModule;
class Calculator
{
public:
Response Calculate(const Request &request)
{
int result = 0, status = 0;
int x = request.X(), y = request.Y();
char oper = request.Oper();
switch (oper)
{
case '+':
result = x + y;
break;
case '-':
result = x - y;
break;
case '*':
result = x * y;
break;
case '/':
if(y == 0)
{
LOG(LogLevel::ERROR) << "Calculator: 除零错误! ";
status = 1;
}
else
result = x / y;
break;
case '%':
if(y == 0)
{
LOG(LogLevel::ERROR) << "Calculator: 模零错误! ";
status = 2;
}
else
result = x % y;
break;
case '^':
result = x ^ y;
break;
case '&':
result = x & y;
break;
case '|':
result = x | y;
break;
default:
LOG(LogLevel::ERROR) << "Calculator: 未知类型的运算符[" << oper << "]";
status = 2;
break;
}
Response response(result, status);
return response;
}
};
当然,这个项目只是用作对套接字编程的学习,因此协议的定制与顶层服务的实现都较为简单。
3.2 Server.hpp
#pragma once
#include "Socket.hpp"
#include "Common.hpp"
using namespace SocketModule;
using func_t = std::function<void(const std::shared_ptr<TCPConnectSocket>&)>;
class Server : public NoCopy
{
public:
Server(in_port_t port, func_t service)
: _lis_socket(port)
, _service(service)
{}
void Run()
{
while(true)
{
auto socket = _lis_socket.Accept();
int id = fork();
if(id < 0)
{
LOG(LogLevel::FATAL) << "fork: 创建子进程失败! " << strerror(errno);
throw std::runtime_error("fork failed");
}
else if( id == 0)
{
_lis_socket.Close();
// 子进程
if(fork() > 0) exit(NORMAL);
// 孙子进程
_service(socket);
exit(NORMAL);
}
else
{
// 父进程
socket->Close();
}
}
}
~Server()
{
_lis_socket.Close();
}
private:
TCPListenSocket _lis_socket;
func_t _service;
};
3.3 Server.cpp
#include <pthread.h>
#include "Socket.hpp"
#include "Server.hpp"
#include "Protocol.hpp"
#include "Calculator.hpp"
#include "Daemon.hpp"
using namespace SocketModule;
void Usage(const std::string &name)
{
std::cout << "usage: " << name << " + port" << std::endl;
}
int main(int argc, char* args[])
{
if (argc != 2)
{
Usage(args[0]);
exit(USAGE_ERROR);
}
in_port_t port = std::stoi(args[1]);
// 日志切换为文件策略
USE_FILE_STRATEGY();
// 设置为守护进程
if(Daemon(0, 0) == -1)
{
LOG(LogLevel::FATAL) << "Deamon: 设置守护进程失败! " << strerror(errno);
exit(DAEMON_ERROR);
}
// daemon(0, 0)
// 顶层
Calculator calculator;
// 协议层
Protocol protocol([&calculator](const Request& request){
return calculator.Calculate(request);
});
// 服务器层
Server server(port, [&protocol](const std::shared_ptr<TCPConnectSocket>& socket){
protocol.ServerService(socket);
});
server.Run();
return 0;
}
3.4 Client.cpp
#include "Socket.hpp"
#include "Protocol.hpp"
using namespace SocketModule;
void Usage(const std::string &name)
{
std::cout << "usage: " << name << " + ip" << " + port" << std::endl;
}
int main(int argc, char* args[])
{
if (argc != 3)
{
Usage(args[0]);
exit(USAGE_ERROR);
}
in_port_t port = std::stoi(args[2]);
TCPClientSocket cli_socket(args[1], port);
int x, y;
char oper;
Request request;
Response response;
Protocol protocol([](const Request&){return Response();});
std::string send_msg, recv_msg, json_str;
InetAddr server = cli_socket.addr();
while(true)
{
std::cout << "Enter expression(x oper y)# ";
std::cin >> x >> oper >> y;
request.serialize(x, y, oper);
protocol.encapsulate(request.JsonStr(), send_msg);
cli_socket.Send(send_msg);
std::string tmp;
while(!protocol.decapsulate(recv_msg, json_str))
{
cli_socket.Receive(tmp);
recv_msg += tmp;
}
response.deserialize(json_str);
std::cout << "Recive from Client# " << response.Result() << "[" << response.Status() << "]" << std::endl;
}
return 0;
}
3.5 Common.hpp
#pragma once
#include <functional>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
enum SocketExitCode
{
NORMAL = 0,
SOCKET_ERROR,
BIND_ERROR,
LISTEN_ERROR,
FORK_ERROR,
READ_ERROR,
WRITE_ERROR,
USAGE_ERROR,
CONNECT_ERROR,
DAEMON_ERROR
};
class NoCopy
{
public:
NoCopy(){}
NoCopy(const NoCopy&) = delete;
NoCopy& operator=(const NoCopy&) = delete;
};
3.6 Socket.hpp
https://blog.csdn.net/2302_80372340/article/details/151293752?spm=1011.2415.3001.5331
4. 演示
日志内容(/var/log/NetCal.log):
关闭服务器: