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 + espvoid 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;}elseresult = x / y;break;case '%':if(y == 0){LOG(LogLevel::ERROR) << "Calculator: 模零错误! ";status = 2;}elseresult = 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):
关闭服务器: