自定义协议与序列化

目录

自定义协议与序列化

一、应用层

1.1.应用层协议

1.2.序列化和反序列化

1.3.重新理解read和write

二、Jsoncpp

2.1.Jsoncpp概念

2.2.Jsoncpp安装

2.3.序列化

2.3.1.toStyledString

2.3.2.FastWriter

2.3.3.StyledWriter

2.3.4.StreamWriter

2.4.反序列化

2.4.1.Reader

2.5.手写序列化和反序列化

三、进程间关系与守护进程

3.1.进程组

3.2.组长进程

3.3.会话

3.4.任务

3.5.控制终端

3.6.作业控制

3.7.守护进程

3.8.setsid函数

3.9.null文件

3.10.daemon函数

四、网络版计算器

2.1.socket封装

2.2.自定义协议

2.3.设计服务器

2.4.设计客户端

2.5.设计计算器

2.6.设计主函数

2.7.守护进程化

2.8.日志

2.9.网络地址转换

2.10.共有类

2.11.互斥锁

2.12.Makefile

2.13.安装卸载脚本


一、应用层

1.1.应用层协议

应用层:解决实际问题,满足日常需求的网络程序

协议:一种约定,套接字的API接口以双方约定好的结构化数据读写传输

1.2.序列化和反序列化

序列化:保证报文完整性,方便网络传输

反序列化:方便上层处理

序列化与反序列化的本质:对软件进行解耦,在应用层进行扩展

客户端和服务器都认识约定好的结构化数据,所以原则上可以直接发送二进制对象

但是客户端和服务器的结构体大小必须相同,可能会因为内存对齐问题而各不相同

同时客户端和服务端的语言种类也必须相同,语言种类不同类的类型大小也不相同

而在操作系统的内部,协议全部是传递结构体对象,因为操作系统都是C语言写的

进行网络协议的通信,在应用层主要使用序列化和反序列化方案

1.3.重新理解read和write

write函数将数据从用户层拷贝到了操作系统内核中的TCP发送缓冲区中

由TCP(传输控制协议)决定什么时候发送,要发送多少,发生错误如何处理

操作系统把数据拷贝到网络中,对方网卡接收数据,将数据拷贝到接收缓冲区

由TCP(传输控制协议)决定什么时候接收,要接收多少,发生错误如何处理

read函数将数据从操作系统内核中TCP的接收缓冲区中拷贝到了用户层

计算机通信的本质:拷贝

主机间通信的本质:把发送方的发送缓冲区数据拷贝到接收方的接收缓冲区

TCP全双工的本质:有两对发送和接收缓冲区

  • 发送缓冲区写满后,应用层会在write函数内阻塞
  • 接收缓冲区无数据,应用层会在read函数内阻塞

本质是为了让用户与操作系统之间实现同步机制

来维护用户与操作系统之间的生产者消费者模型

UDP面向数据报:发送方发送一次报文,接收方接收到一个完整的报文

TCP面向字节流:发送方发送一次报文,接收方接收到报文的一部分(粘包问题)或者多条报文

定制协议

  • 结构化字段,提供序列化和反序列化方案
  • 解决字节流, 导致读取报文不完整的问题

二、Jsoncpp

2.1.Jsoncpp概念

一个用于处理JSON数据的C++库

功能:将JSON数据序列化为字符串,将字符串反序列化为C++数据结构

相似的第三方库:xml,protobuf(二进制级别的序列化与反序列化)

2.2.Jsoncpp安装

ubuntu:sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel

2.3.序列化

2.3.1.toStyledString
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>

int main()
{
    Json::Value root;
    root["name"] = "张三";
    root["sex"] = "男";
    root["age"] = 18;

    std::string s = root.toStyledString();
    std::cout << s << std::endl;

    return 0;
}

2.3.2.FastWriter
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>

int main()
{
    Json::Value root;
    root["name"] = "张三";
    root["sex"] = "男";
    root["age"] = 18;

    Json::FastWriter writer;
    std::string s = writer.write(root);
    std::cout << s << std::endl;

    return 0;
}

2.3.3.StyledWriter
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>

int main()
{
    Json::Value root;
    root["name"] = "张三";
    root["sex"] = "男";
    root["age"] = 18;

    // 用\n按行设置, 可读性好
    Json::StyledWriter writer;
    std::string s = writer.write(root);
    std::cout << s << std::endl;

    return 0;
}

2.3.4.StreamWriter
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
#include <sstream>
#include <memory>

int main()
{
    Json::Value root;
    root["name"] = "张三";
    root["sex"] = "男";
    root["age"] = 18;

    Json::StreamWriterBuilder sbuilder;
    std::unique_ptr<Json::StreamWriter> writer(sbuilder.newStreamWriter());
    
    std::stringstream ss;
    writer->write(root, &ss);

    std::string s = ss.str();
    std::cout << s << std::endl;
}

2.4.反序列化

2.4.1.Reader
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>

int main()
{
    std::string json_string = "{\"name\":\"张三\", \"age\":30, \"city\":\"北京\"}";

    Json::Value root;
    Json::Reader reader;

    bool ok = reader.parse(json_string, root);
    (void)ok;

    std::string name = root["name"].asString();
    int age = root["age"].asInt();
    std::string city = root["city"].asString();
    
    std::cout << name << std::endl;
    std::cout << age<< std::endl;
    std::cout << city << std::endl;
}

2.5.手写序列化和反序列化

  • Protocol.hpp
#pragma once
#include <iostream>
#include <string>
#include <memory>
#include "Socket.hpp"
#include <jsoncpp/json/json.h>
#include <functional>

// #define SELF 1 
// 等价于Makefile的-D

// 自定义网络版本的计算器

using namespace SocketModule;

const std::string ProtSep = " ";

// client → server
class Request
{
public:
    Request()
    {
    }

    Request(int x, int y, char oper)
        : _x(x), _y(y), _oper(oper)
    {
    }

    // 序列化
    std::string Serialize()
    {
#ifdef SELF
        std::string _x_str = std::to_string(_x);
        std::string _y_str = std::to_string(_y);
        std::string s = _x_str + ProtSep + _oper + ProtSep + _y_str;
        return s;
#else
        // _x = 10 _y = 20 _oper = '+'
        // "10" "20" '+': 用空格作为分隔符
        Json::Value root;
        root["x"] = _x;
        root["y"] = _y;
        root["oper"] = _oper; // _oper是char, 也是整数(ASCII码)

        Json::FastWriter writer;
        std::string s = writer.write(root);
        return s;
#endif
    }

    // 反序列化
    bool Deserialize(std::string &in)
    {
#ifdef SELF
        // "x op y"
        auto left = in.find(ProtSep);
        if (left == std::string::npos)
        {
            return false;
        }
        auto right = in.rfind(ProtSep);
        if (right == std::string::npos)
        {
            return false;
        }
        if (left + ProtSep.size() + 1 != right)
        {
            return false;
        }

        std::string left_data = in.substr(0, left);
        if (left_data.empty())
        {
            return false;
        }
        std::string right_data = in.substr(right + ProtSep.size());
        if (right_data.empty())
        {
            return false;
        }

        _oper = in[right - 1];
        _x = std::stoi(left_data);
        _y = std::stoi(right_data);

        return true;
#else
        // "10" "20" '+': 用空格作为分隔符
        // 10 20 '+'
        Json::Value root;
        Json::Reader reader;
        bool ok = reader.parse(in, root);
        if (ok)
        {
            _x = root["x"].asInt();
            _y = root["y"].asInt();
            _oper = root["oper"].asInt();
        }
        return ok;
#endif
    }

    ~Request()
    {
    }
    int X()
    {
        return _x;
    }
    int Y()
    {
        return _y;
    }
    char Oper()
    {
        return _oper;
    }

private:
    int _x;
    int _y;
    char _oper;
};

// server → client
class Response
{
public:
    Response()
    {
    }

    Response(int result, int code)
        : _result(result), _code(code)
    {
    }

    // 序列化
    std::string Serialize()
    {
#ifdef SELF
        std::string _result_str = std::to_string(_result);
        std::string _code_str = std::to_string(_code);
        std::string s = _result_str + ProtSep + _code_str;
        return s;
#else
        Json::Value root;
        root["result"] = _result;
        root["code"] = _code;

        Json::FastWriter writer;
        return writer.write(root);
#endif
    }

    // 反序列化
    bool Deserialize(std::string &in)
    {
#ifdef SELF
        // "result code"
        auto mid = in.find(ProtSep);
        if(mid == std::string::npos)
        {
            return false;
        }

        std::string left_data = in.substr(0, mid);
        if(left_data.empty())
        {
            return false;
        }
        std::string right_data = in.substr(mid + ProtSep.size());
        if(right_data.empty())
        {
            return false;
        }

        _result = std::stoi(left_data);
        _code = std::stoi(right_data);

        return true;
#else
        Json::Value root;
        Json::Reader reader;
        bool ok = reader.parse(in, root);
        if (ok)
        {
            _result = root["result"].asInt();
            _code = root["code"].asInt();
        }
        return ok;
#endif
    }

    ~Response()
    {
    }

    void SetResult(int res)
    {
        _result = res;
    }

    void SetCode(int code)
    {
        _code = code;
    }

    void ShowResult()
    {
        std::cout << "计算结果是: " << _result << "[ " << _code << " ]" << std::endl;
    }

private:
    int _result; // 运算结果
    int _code;   // 0: success 1,2,3,4: 不同异常
};

// 基于TCP的协议要解决两个问题
// 1. request和response必须有序列化和反序列化功能(使用C++的Jsoncpp库)
// 2. 读取时, 读到完整的请求(仅TCP, UDP不用考虑)
// 因为TCP是面向字节流的, 当读取方读取的时候, 可能读到一个完整的Json请求, 也可能读到半个或一个半(粘报问题)
// read函数本身并不保证读取到的报文完整性, 只要有数据, 就进行读取
// 由应用层的程序员自己控制, 需要进一步定制协议
// 在Json字符串前面加Json字符串长度(应用层报头)
// content_len jsonstring
// 50\r\n{"x" : 10, "y" : 20, "oper" : '+'}\r\n
// 50
// {"x" : 10, "y" : 20, "oper" : '+'}
//
const std::string sep = "\r\n";
using func_t = std::function<Response(Request &req)>; // 计算业务回调
class Protocol
{
public:
    Protocol()
    {
    }
    Protocol(func_t func)
        : _func(func)
    {
    }

    // 编码
    std::string Encode(const std::string jsonstr)
    {
        // 50\r\n{"x" : 10, "y" : 20, "oper" : '+'}\r\n
        std::string len = std::to_string(jsonstr.size());
        return len + sep + jsonstr + sep; // 应用层封装
    }

    // 解码
    // 1. 判断报文完整性
    // 2. 如果包含至少一个完整请求, 就进行提取, 并从缓冲区移除,便于处理下一个
    bool Decode(std::string &buffer, std::string *package)
    {
        ssize_t pos = buffer.find(sep);
        if (pos == std::string::npos)
        {
            return false; // 让调用方从内核继续读取数据
        }
        // 获取报文长度
        std::string package_len_str = buffer.substr(0, pos);
        int package_len_int = std::stoi(package_len_str);
        // 计算一个完整报文长度
        int target_len = package_len_str.size() + sep.size() + package_len_int + sep.size();
        // 保证buffer里至少有一个完整报文
        if (buffer.size() < target_len)
        {
            return false;
        }
        // 提取一个完整报文
        *package = buffer.substr(pos + sep.size(), package_len_int);
        // 将报文移除缓冲区
        buffer.erase(0, target_len);
        return true;
    }

    void GetRequest(std::shared_ptr<Socket> &sock, InetAddr &client)
    {
        // 读取请求
        std::string buffer_queue;
        while (true)
        {
            int n = sock->Recv(&buffer_queue);
            if (n > 0)
            {
                std::cout << "--------------- request_buffer --------------" << std::endl;
                std::cout << buffer_queue << std::endl;
                std::cout << "---------------------------------------------" << std::endl;
                // 1. 解析报文, 提取完整的Json请求, 如果不完整则继续读取
                std::string json_package;
                while (Decode(buffer_queue, &json_package))
                {
                    std::cout << "--------------- request json --------------" << std::endl;
                    std::cout << json_package << std::endl;
                    std::cout << "---------------------------------------------" << std::endl;

                    std::cout << "--------------- request_buffer --------------" << std::endl;
                    std::cout << buffer_queue << std::endl;
                    std::cout << "---------------------------------------------" << std::endl;
                    // 2. 请求Json报文的反序列化
                    LOG(LogLevel::DEBUG) << client.StringAddr() << " 请求: " << json_package;
                    Request req;
                    bool ok = req.Deserialize(json_package);
                    if (!ok)
                    {
                        continue;
                    }
                    // 3. 完成计算功能(业务)
                    Response resp = _func(req);
                    // 4. 序列化
                    std::string json_str = resp.Serialize();
                    // 5. 编码, 添加自定义长度
                    std::string send_str = Encode(json_str);
                    // 6. 发送
                    sock->Send(send_str);
                }
            }
            else if (n == 0)
            {
                LOG(LogLevel::INFO) << "client: " << client.StringAddr() << " Quit!";
                break;
            }
            else
            {
                LOG(LogLevel::WARNING) << "client: " << client.StringAddr() << " , recv error";
                break;
            }
        }
    }

    bool GetResponse(std::shared_ptr<Socket> &client, std::string &resp_buff, Response *resp)
    {
        // 面向字节流
        while (true)
        {
            int n = client->Recv(&resp_buff);
            if (n > 0)
            {
                // std::cout << "---------------- resp_buffer ----------------" << std::endl;
                // std::cout << resp_buff << std::endl;
                // std::cout << "---------------------------------------------" << std::endl;
                // 解析报文, 提取完整的Json报文
                std::string json_package;
                // bool ret = Decode(resp_buff, &json_package);
                // if (!ret)
                // {
                //     continue;
                // }
                while (Decode(resp_buff, &json_package))
                {
                    // std::cout << "--------------- response json ---------------" << std::endl;
                    // std::cout << json_package << std::endl;
                    // std::cout << "---------------------------------------------" << std::endl;

                    // std::cout << "---------------- resp_buffer ----------------" << std::endl;
                    // std::cout << resp_buff << std::endl;
                    // std::cout << "---------------------------------------------" << std::endl;
                    // 反序列化
                    resp->Deserialize(json_package);
                }
                return true;
            }
            else if (n == 0)
            {
                std::cout << "server quit" << std::endl;
                return false;
            }
            else
            {
                std::cout << "recv error" << std::endl;
                return false;
            }
        }
    }

    std::string BuildRequestString(int x, int y, char oper)
    {
        // 构建一个完整的请求
        Request req(x, y, oper);

        // 序列化
        std::string json_req = req.Serialize();

        // std::cout << "-------------- json_req string --------------" << std::endl;
        // std::cout << json_req << std::endl;
        // std::cout << "---------------------------------------------" << std::endl;

        // 编码 添加自定义长度
        return Encode(json_req);
    }

    ~Protocol()
    {
    }

private:
    // 多进程(可以写时拷贝)
    // Request _req;
    // Response _resp;
    func_t _func; // 计算业务回调
};
  • Makefile
.PHONY:all
all: ServerNetCald client_netcal

LDFLAG=-DSELF=1

ServerNetCald:Main.cc
	g++ -o $@ $^ $(LDFLAG) -std=c++17 -ljsoncpp -lpthread 
client_netcal:TcpClient.cc
	g++ -o $@ $^ $(LDFLAG) -std=c++17 -ljsoncpp -lpthread

.PHONY:output
output:
	@mkdir output
	@mkdir -p output/bin
	@mkdir -p output/conf
	@mkdir -p output/log
	@cp ServerNetCald output/bin
	@cp client_netcal output/bin
	@cp test.conf output/conf
	@cp install.sh output/
	@cp uninstall.sh output/
	@tar czf output.tgz output

.PHONY:clean
clean:
	rm -rf ServerNetCald client_netcal output output.tgz

三、进程间关系与守护进程

3.1.进程组

进程组:一个或者多个进程的集合

进程是以进程组的形式完成对应任务的

单个进程执行任务独立成为一个进程组

  • PGID:进程组
  • TTY:终端号
  • TPGID:终端与进程的关联
  • UID:创建进程的用户
  • TIME:时间
  • COMMAND:命令

3.2.组长进程

每一个进程组都有一个组长进程,组长进程的ID等于其进程的ID

组长进程的作用:创建一个进程组或者创建该组中的进程

进程组生命周期:从进程组创建开始到最后一个进程离开

注意:某个进程组有一个进程存在,进程组就存在,与组长进程是否终止无关

3.3.会话

SID(Session ID):会话ID

会话:一个或多个进程组的集合(进程组一定属于某一个会话)

xshell登录linux系统后,linux系统会为用户新建一个会话

会话内部默认有一个进程组Bash,将该会话的ID设置为Bash进程的ID

3.4.任务

任务:由进程来完成的某种工作

进程组和任务是一个硬币的两面

键盘只有一个,输入的数据必须明确地给一个进程组

在会话内部,进程组必须区分成为前台和后台进程组

一次会话中,只能有一个前台进程组,后台可以多个

一次会话中,只能有一个前台任务,后台可以有多个

所以键盘输入的数据,只是给前台进程组

3.5.控制终端

控制终端:终端文件

用户通过终端登录系统后得到的一个Shell(Bash)进程

3.6.作业控制

作业:用户为了完成某项任务而启动的进程组

作业号:作业的编号

  • (+):该作业号是默认作业
  • (-):该作业号即将成为默认作业
  • (无符号):其他作业

作业控制:Shell同时运行一个前台作业和任意多个后台作业

  • jobs:查看系统当前的后台任务
  • fg(front ground)任务号: 把指定任务提到前台
  • bg(back ground) 任务号:运行后台任务
  • ctrl + c:只能终止前台任务,后台任务不受键盘输入影响
  • ctrl + z:暂停前台进程组,自动被切换到后台,Bash重新回到前台

3.7.守护进程

用户登录是建立会话的过程,关闭终端是销毁会话的过程

用户的登录和注销都会影响到服务器的运行,导致不稳定

需要为服务器新建一个会话,将服务器进程组迁移新会话

3.8.setsid函数

作业:创建一个会话

前提:调用的进程不能是一个进程组的组长

创建子进程,终止父进程,子进程继承父进程的进程组ID,守护进程也是孤儿进程的一种

3.9.null文件

凡是写入该文件的内容都丢弃

凡是该文件读取的内容都是空

3.10.daemon函数

作用:守护进程化

参数1:0 - 将当前进程的工作目录改为根目录

参数2:0 - 关闭标准输出,标准输入,标准错误

四、网络版计算器

2.1.socket封装

设计模式:模板方法类

  • Socket.hpp
#pragma once

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>
#include "Log.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"

namespace SocketModule
{
    using namespace LogModule;
    const static int gbacklog = 16;
    // 设计模式: 模板方法模式
    // 基类socket
    // 大部分方法为纯虚方法
    class Socket
    {
    public:
        virtual ~Socket() {}                                                  // 析构函数
        virtual void SocketOrDie() = 0;                                       // 创建套接字
        virtual void BindOrDie(uint16_t port) = 0;                            // 绑定IP和端口号
        virtual void ListenOrDie(int backlog) = 0;                            // 监听
        virtual std::shared_ptr<Socket> Accept(InetAddr *client) = 0;         // 获取
        virtual void Close() = 0;                                             // 关闭
        virtual int Recv(std::string *out) = 0;                               // 读取
        virtual int Send(const std::string &message) = 0;                     // 写入
        virtual int Connect(const std::string &server_ip, uint16_t port) = 0; // 连接

    public:
        void BuildListenSocketMethod(uint16_t port, int backlog = gbacklog)
        {
            SocketOrDie();
            BindOrDie(port);
            ListenOrDie(backlog);
        }
        void BuildTcpClientSocketMethod()
        {
            SocketOrDie();
        }
    };

    const static int defaultfd = -1;
    class TcpSocket : public Socket
    {
    public:
        TcpSocket()
            : _sockfd(defaultfd)
        {
        }
        TcpSocket(int fd)
            : _sockfd(fd)
        {
        }

        void SocketOrDie() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0)
            {
                LOG(LogLevel::FATAL) << "socket error";
                exit(SOCKET_ERR);
            }
            LOG(LogLevel::INFO) << "socket success";
        }

        void BindOrDie(uint16_t port) override
        {
            InetAddr localaddr(port);
            int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen());
            if (n < 0)
            {
                LOG(LogLevel::FATAL) << "bind error";
                exit(BIND_ERR);
            }
            LOG(LogLevel::INFO) << "bind success";
        }

        void ListenOrDie(int backlog) override
        {
            int n = ::listen(_sockfd, backlog);
            if (n < 0)
            {
                LOG(LogLevel::FATAL) << "listen error";
                exit(LISTEN_ERR);
            }
            LOG(LogLevel::INFO) << "listen success";
        }

        std::shared_ptr<Socket> Accept(InetAddr *client) override
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int fd = ::accept(_sockfd, CONV(peer), &len);
            if (fd < 0)
            {
                LOG(LogLevel::WARNING) << "accept warning...";
                return nullptr;
            }
            client->SetAddr(peer);
            // return fd;
            return std::make_shared<TcpSocket>(fd);
        }

        void Close() override
        {
            if (_sockfd >= 0)
            {
                ::close(_sockfd);
            }
        }

        int Recv(std::string *out) override
        {
            // 流式读取(不用关心读到的是什么)
            char buffer[1024];
            ssize_t n = ::recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
            if (n > 0)
            {
                buffer[n] = 0;
                *out += buffer;
            }
            return n;
        }

        int Send(const std::string &message) override
        {
            return ::send(_sockfd, message.c_str(), message.size(), 0);
        }

        int Connect(const std::string &server_ip, uint16_t port) override
        {
            InetAddr server(server_ip, port);
            return ::connect(_sockfd, server.NetAddrPtr(), server.NetAddrLen());
        }

        ~TcpSocket()
        {
        }

    private:
        int _sockfd;
    };
};

2.2.自定义协议

  • Protocol.hpp
#pragma once
#include <iostream>
#include <string>
#include <memory>
#include "Socket.hpp"
#include <jsoncpp/json/json.h>
#include <functional>

// 自定义网络版本的计算器

using namespace SocketModule;

// client → server
class Request
{
public:
    Request()
    {
    }

    Request(int x, int y, char oper)
        : _x(x), _y(y), _oper(oper)
    {
    }

    // 序列化
    std::string Serialize()
    {
        // _x = 10 _y = 20 _oper = '+'
        // "10" "20" '+': 用空格作为分隔符
        Json::Value root;
        root["x"] = _x;
        root["y"] = _y;
        root["oper"] = _oper; // _oper是char, 也是整数(ASCII码)

        Json::FastWriter writer;
        std::string s = writer.write(root);
        return s;
    }

    // 反序列化
    bool Deserialize(std::string &in)
    {
        // "10" "20" '+': 用空格作为分隔符
        // 10 20 '+'
        Json::Value root;
        Json::Reader reader;
        bool ok = reader.parse(in, root);
        if (ok)
        {
            _x = root["x"].asInt();
            _y = root["y"].asInt();
            _oper = root["oper"].asInt();
        }
        return ok;
    }

    ~Request()
    {
    }
    int X()
    {
        return _x;
    }
    int Y()
    {
        return _y;
    }
    char Oper()
    {
        return _oper;
    }

private:
    int _x;
    int _y;
    char _oper;
};

// server → client
class Response
{
public:
    Response()
    {
    }

    Response(int result, int code)
        : _result(result), _code(code)
    {
    }

    // 序列化
    std::string Serialize()
    {
        Json::Value root;
        root["result"] = _result;
        root["code"] = _code;

        Json::FastWriter writer;
        return writer.write(root);
    }

    // 反序列化
    bool Deserialize(std::string &in)
    {
        Json::Value root;
        Json::Reader reader;
        bool ok = reader.parse(in, root);
        if (ok)
        {
            _result = root["result"].asInt();
            _code = root["code"].asInt();
        }
        return ok;
    }

    ~Response()
    {
    }

    void SetResult(int res)
    {
        _result = res;
    }

    void SetCode(int code)
    {
        _code = code;
    }

    void ShowResult()
    {
        std::cout << "计算结果是: " << _result << "[ " << _code << " ]" << std::endl;
    }

private:
    int _result; // 运算结果
    int _code;   // 0: success 1,2,3,4: 不同异常
};

// 基于TCP的协议要解决两个问题
// 1. request和response必须有序列化和反序列化功能(使用C++的Jsoncpp库)
// 2. 读取时, 读到完整的请求(仅TCP, UDP不用考虑)
// 因为TCP是面向字节流的, 当读取方读取的时候, 可能读到一个完整的Json请求, 也可能读到半个或一个半(粘报问题)
// read函数本身并不保证读取到的报文完整性, 只要有数据, 就进行读取
// 由应用层的程序员自己控制, 需要进一步定制协议
// 在Json字符串前面加Json字符串长度(应用层报头)
// content_len jsonstring
// 50\r\n{"x" : 10, "y" : 20, "oper" : '+'}\r\n
// 50
// {"x" : 10, "y" : 20, "oper" : '+'}
//
const std::string sep = "\r\n";
using func_t = std::function<Response(Request &req)>; // 计算业务回调
class Protocol
{
public:
    Protocol()
    {
    }
    Protocol(func_t func)
        : _func(func)
    {
    }

    // 编码
    std::string Encode(const std::string jsonstr)
    {
        // 50\r\n{"x" : 10, "y" : 20, "oper" : '+'}\r\n
        std::string len = std::to_string(jsonstr.size());
        return len + sep + jsonstr + sep; // 应用层封装
    }

    // 解码
    // 1. 判断报文完整性
    // 2. 如果包含至少一个完整请求, 就进行提取, 并从缓冲区移除,便于处理下一个
    bool Decode(std::string &buffer, std::string *package)
    {
        ssize_t pos = buffer.find(sep);
        if (pos == std::string::npos)
        {
            return false; // 让调用方从内核继续读取数据
        }
        // 获取报文长度
        std::string package_len_str = buffer.substr(0, pos);
        int package_len_int = std::stoi(package_len_str);
        // 计算一个完整报文长度
        int target_len = package_len_str.size() + sep.size() + package_len_int + sep.size();
        // 保证buffer里至少有一个完整报文
        if (buffer.size() < target_len)
        {
            return false;
        }
        // 提取一个完整报文
        *package = buffer.substr(pos + sep.size(), package_len_int);
        // 将报文移除缓冲区
        buffer.erase(0, target_len);
        return true;
    }

    void GetRequest(std::shared_ptr<Socket> &sock, InetAddr &client)
    {
        // 读取请求
        std::string buffer_queue;
        while (true)
        {
            int n = sock->Recv(&buffer_queue);
            if (n > 0)
            {
                std::cout << "--------------- request_buffer --------------" << std::endl;
                std::cout << buffer_queue << std::endl;
                std::cout << "---------------------------------------------" << std::endl;
                // 1. 解析报文, 提取完整的Json请求, 如果不完整则继续读取
                std::string json_package;
                while (Decode(buffer_queue, &json_package))
                {
                    std::cout << "--------------- request json --------------" << std::endl;
                    std::cout << json_package << std::endl;
                    std::cout << "---------------------------------------------" << std::endl;

                    std::cout << "--------------- request_buffer --------------" << std::endl;
                    std::cout << buffer_queue << std::endl;
                    std::cout << "---------------------------------------------" << std::endl;
                    // 2. 请求Json报文的反序列化
                    LOG(LogLevel::DEBUG) << client.StringAddr() << " 请求: " << json_package;
                    Request req;
                    bool ok = req.Deserialize(json_package);
                    if (!ok)
                    {
                        continue;
                    }
                    // 3. 完成计算功能(业务)
                    Response resp = _func(req);
                    // 4. 序列化
                    std::string json_str = resp.Serialize();
                    // 5. 编码, 添加自定义长度
                    std::string send_str = Encode(json_str);
                    // 6. 发送
                    sock->Send(send_str);
                }
            }
            else if (n == 0)
            {
                LOG(LogLevel::INFO) << "client: " << client.StringAddr() << " Quit!";
                break;
            }
            else
            {
                LOG(LogLevel::WARNING) << "client: " << client.StringAddr() << " , recv error";
                break;
            }
        }
    }

    bool GetResponse(std::shared_ptr<Socket> &client, std::string &resp_buff, Response *resp)
    {
        // 面向字节流
        while (true)
        {
            int n = client->Recv(&resp_buff);
            if (n > 0)
            {
                // std::cout << "---------------- resp_buffer ----------------" << std::endl;
                // std::cout << resp_buff << std::endl;
                // std::cout << "---------------------------------------------" << std::endl;
                // 解析报文, 提取完整的Json报文
                std::string json_package;
                // bool ret = Decode(resp_buff, &json_package);
                // if (!ret)
                // {
                //     continue;
                // }
                while (Decode(resp_buff, &json_package))
                {
                    // std::cout << "--------------- response json ---------------" << std::endl;
                    // std::cout << json_package << std::endl;
                    // std::cout << "---------------------------------------------" << std::endl;

                    // std::cout << "---------------- resp_buffer ----------------" << std::endl;
                    // std::cout << resp_buff << std::endl;
                    // std::cout << "---------------------------------------------" << std::endl;
                    // 反序列化
                    resp->Deserialize(json_package);
                }
                return true;
            }
            else if (n == 0)
            {
                std::cout << "server quit" << std::endl;
                return false;
            }
            else
            {
                std::cout << "recv error" << std::endl;
                return false;
            }
        }
    }

    std::string BuildRequestString(int x, int y, char oper)
    {
        // 构建一个完整的请求
        Request req(x, y, oper);

        // 序列化
        std::string json_req = req.Serialize();

        // std::cout << "-------------- json_req string --------------" << std::endl;
        // std::cout << json_req << std::endl;
        // std::cout << "---------------------------------------------" << std::endl;

        // 编码 添加自定义长度
        return Encode(json_req);
    }

    ~Protocol()
    {
    }

private:
    // 多进程(可以写时拷贝)
    // Request _req;
    // Response _resp;
    func_t _func; // 计算业务回调
};

2.3.设计服务器

  • TcpServer.hpp
#include "Socket.hpp"
#include <iostream>
#include <memory>
#include <sys/wait.h>
#include <functional>

using namespace SocketModule;
using namespace LogModule;

// IO服务
using ioservice_t = std::function<void(std::shared_ptr<Socket> &sock, InetAddr &client)>;

// TCP服务器: 实现连接, 不需要关心传递的数据
// 网络版本的计算器: 长服务

class TcpServer
{
public:
    TcpServer(uint16_t port, ioservice_t service)
        : _port(port), _listensockptr(std::make_unique<TcpSocket>()), _isrunning(false), _service(service)
    {
        _listensockptr->BuildListenSocketMethod(_port);
    }

    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            InetAddr client;
            auto sock = _listensockptr->Accept(&client);
            if (sock == nullptr)
            {
                continue;
            }
            LOG(LogLevel::DEBUG) << "accept success..." << client.StringAddr();

            // 多进程实现
            pid_t id = fork();
            if (id < 0)
            {
                LOG(LogLevel::FATAL) << "fork error...";
                exit(FORK_ERR);
            }
            else if (id == 0)
            {
                // 子进程
                // 关闭listensockfd
                _listensockptr->Close();
                if (fork() > 0)
                {
                    exit(0);
                }
                // 孙子进程
                _service(sock, client); // 执行IO服务
                sock->Close();
                exit(OK);
            }
            else
            {
                // 父进程
                // 关闭sockfd
                sock->Close();
                pid_t rid = ::waitpid(id, nullptr, 0);
                (void)rid;
            }
        }
        _isrunning = false;
    }

    ~TcpServer()
    {
    }

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listensockptr;
    bool _isrunning;
    ioservice_t _service;
};

2.4.设计客户端

  • TcpClient.cc
#include "Socket.hpp"
#include "Common.hpp"
#include "Protocol.hpp"
#include <iostream>
#include <string>
#include <memory>

using namespace SocketModule;

void Usage(std::string proc)
{
    std::cerr << "Usage: " << proc << " server_ip server_port" << std::endl;
}

// 获取数据
void GetDataFromStdin(int *x, int *y, char *oper)
{
    std::cout << "Please Enter x: ";
    std::cin >> *x;
    std::cout << "Please Enter y: ";
    std::cin >> *y;
    std::cout << "Please Enter oper: ";
    std::cin >> *oper;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }

    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);

    std::shared_ptr<Socket> client = std::make_shared<TcpSocket>();
    client->BuildTcpClientSocketMethod();

    if (client->Connect(server_ip, server_port) != 0)
    {
        // 连接失败
        std::cerr << "connect error" << std::endl;
        exit(CONNECT_ERR);
    }

    std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>();
    std::string resp_buffer;

    // 服务器连接成功
    while (true)
    {
        // 获取数据
        int x, y;
        char oper;
        GetDataFromStdin(&x, &y, &oper);
        // 构建请求
        std::string req_str = protocol->BuildRequestString(x, y, oper);
        // std::cout << "--------------encode req string--------------" << std::endl;
        // std::cout << req_str << std::endl;
        // std::cout << "---------------------------------------------" << std::endl;
        // 发送请求
        client->Send(req_str);
        // 获取应答
        Response resp;
        bool res = protocol->GetResponse(client, resp_buffer, &resp);
        if (res == false)
        {
            break;
        }
        // 显示结果
        resp.ShowResult();
    }
    client->Close();
    return 0;
}

2.5.设计计算器

  • NetCal.hpp
#pragma once

#include "Protocol.hpp"
#include <iostream>

class Cal
{
public:
    Response Execute(Request &req)
    {
        Response resp(0, 0);
        switch (req.Oper())
        {
        case '+':
            resp.SetResult(req.X() + req.Y());
            break;
        case '-':
            resp.SetResult(req.X() - req.Y());
            break;
        case '*':
            resp.SetResult(req.X() * req.Y());
            break;
        case '/':
        {
            if (req.Y() == 0)
            {
                resp.SetCode(1); // 1: 除0错误
            }
            else
            {
                resp.SetResult(req.X() / req.Y());
            }
        }
        break;
        case '%':
        {
            if (req.Y() == 0)
            {
                resp.SetCode(2); // 2: 模0错误
            }
            else
            {
                resp.SetResult(req.X() % req.Y());
            }
        }
        break;
        default:
            resp.SetCode(3); // 3: 非法操作
            break;
        }

        return resp;
    }
};

2.6.设计主函数

  • Main.cc
#include "NetCal.hpp"
#include "Protocol.hpp"
#include "TcpServer.hpp"
#include "Daemon.hpp"
#include <memory>

void Usage(std::string proc)
{
    std::cerr << "Usage: " << proc << " port" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }

    std::cout << "服务器已经启动, 已守护进程化" << std::endl;

    // 守护进程化
    Daemon(0, 0);
    // daemon(0, 0);

    // 采用文件方式显示日志
    Enbale_File_Log_Strategy();

    // 受用户的需求影响, OSI的上三层无法设计到内核中
    // 只能由程序员进行编写
    
    // 业务层(应用层)
    std::unique_ptr<Cal> cal = std::make_unique<Cal>();

    // 协议层(表示层)
    std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>(
        [&cal](Request &req)
        ->Response
        {
            return cal->Execute(req);
        });

    // 网络通信层(会话层)
    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(std::stoi(argv[1]), 
        [&protocol](std::shared_ptr<Socket> &sock, InetAddr &client)
        {
            protocol->GetRequest(sock, client);
        });
    tsvr->Start();
    return 0;
}

2.7.守护进程化

  • Daemon.hpp
#pragma once

#include <iostream>
#include <cstdio>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <sys/stat.h>
#include <fcntl.h>
#include "Log.hpp"
#include "Common.hpp"

using namespace LogModule;

const std::string dev = "/dev/null";

// 将服务器守护进程化

void Daemon(int nochdir, int noclose)
{
    // 忽略IO, 子进程退出等相关信号
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);

    // 销毁父进程 创建子进程
    if (fork() > 0)
    {
        exit(0);
    }

    // 孤儿进程
    setsid(); // 成为独立的会话

    if(nochdir == 0)
    {
        chdir("/");
    }

    // 守护进程不从键盘输入, 也不需向显示器打印
    // 方法一: 关闭与stdin、stdout、stderr(不推荐)
    // 方法二: 打开/dev/null, 重定向stdin、stdout、stderr到/dev/null
    if (noclose == 0)
    {
        int fd = ::open(dev.c_str(), O_RDWR);
        if (fd < 0)
        {
            LOG(LogLevel::FATAL) << "open " << dev << " error";
            exit(OPEN_ERR);
        }
        else
        {
            dup2(fd, 0);
            dup2(fd, 1);
            dup2(fd, 2);
            close(fd);
        }
    }
}

2.8.日志

  • Log.hpp
#ifndef __LOG_HPP__
#define __LOG_HPP__

#include <iostream>
#include <string>
#include <filesystem> // C++17中 文件操作的相关封装
#include <fstream>
#include "Mutex.hpp"
#include <memory>
#include <unistd.h>
#include <sstream>
#include <ctime>
#include <cstdio>

namespace LogModule
{
    using namespace MutexModule;
    const std::string gsep = "\r\n";

    // 2. 刷新策略(策略模式: C++多态)

    // 策略基类
    class LogStrategy
    {
    public:
        ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 策略a: 显示器打印
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy()
        {
        }

        void SyncLog(const std::string &message) override
        {
            // 加锁
            LockGuard lockguard(_mutex);

            // 打印日志
            std::cout << message << gsep;
        }

        ~ConsoleLogStrategy()
        {
        }

    private:
        Mutex _mutex;
    };

    // 缺省参数
    const std::string defaultpath = "/var/log/";
    const std::string defaultfile = "my.log";

    // 策略b: 指定文件写入
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
            : _path(path), _file(file)
        {
            // 加锁
            LockGuard lockguard(_mutex);
            // 如果当前路径存在
            if (std::filesystem::exists(_path))
            {
                return;
            }
            // 如果当前路径不存在
            try
            {
                std::filesystem::create_directories(_path);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << "\n";
            }
        }

        void SyncLog(const std::string &message) override
        {
            // 加锁
            LockGuard lockguard(_mutex);
            // "./log" + "/" + "my.log"
            std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
            // 以追加的方式打开文件
            std::ofstream out(filename, std::ios::app);
            if (!out.is_open())
            {
                return;
            }
            // 写入日志
            out << message << gsep;
            // 关闭文件
            out.close();
        }

        ~FileLogStrategy()
        {
        }

    private:
        std::string _path; // 日志文件所在路径
        std::string _file; // 日志文件名称

        Mutex _mutex; // 互斥锁
    };

    // 形成完整日志 && 根据策略选择不同刷新方式

    // 1. 形成日志等级
    enum class LogLevel
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };
    std::string LeveltoStr(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        default:
            return "UNKNOW";
        }
    }

    // 2. 获取时间方法
    std::string GetTimeStamp()
    {
        time_t curr = time(nullptr);
        struct tm curr_tm;
        localtime_r(&curr, &curr_tm);
        char timebuffer[128];
        snprintf(timebuffer, sizeof(timebuffer), "%4d-%02d-%02d %02d-%02d-%02d",
                 curr_tm.tm_year + 1900, curr_tm.tm_mon + 1, curr_tm.tm_mday,
                 curr_tm.tm_hour, curr_tm.tm_min, curr_tm.tm_sec);
        return timebuffer;
    }

    // 日志类
    class Logger
    {
    public:
        Logger()
        {
            // 默认使用显示器
            EnableConsoleLogStrategy();
        }

        void EnableFileLogStrategy()
        {
            _fflush_strategy = std::make_unique<FileLogStrategy>();
        }

        void EnableConsoleLogStrategy()
        {
            _fflush_strategy = std::make_unique<ConsoleLogStrategy>();
        }

        // 内部类: 表示未来的一条日志
        class LogMessage
        {
        public:
            LogMessage(LogLevel &level, std::string &src_name, int line_number, Logger &logger)
                : _curr_time(GetTimeStamp()), _level(level), _pid(getpid()), _src_name(src_name), _line_number(line_number), _logger(logger)
            {
                // 日志左半部分
                std::stringstream ss;
                ss << "[" << _curr_time << "] "
                   << "[" << LeveltoStr(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _src_name << "] "
                   << "[" << _line_number << "] "
                   << "- ";
                _loginfo = ss.str();
            }
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                // 日志右半部分
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }
            ~LogMessage()
            {
                if (_logger._fflush_strategy)
                {
                    _logger._fflush_strategy->SyncLog(_loginfo);
                }
            }

        private:
            std::string _curr_time; // 时间
            LogLevel _level;        // 等级
            pid_t _pid;             // 进程PID
            std::string _src_name;  // 文件名
            int _line_number;       // 行号
            std::string _loginfo;   // 一条完整的日志信息
            Logger &_logger;
        };

        LogMessage operator()(LogLevel level, std::string name, int line)
        {
            return LogMessage(level, name, line, *this);
        }

        ~Logger()
        {
        }

    private:
        std::unique_ptr<LogStrategy> _fflush_strategy;
    };

    // 全局日志对象
    Logger logger;

// 使用宏简化用户操作, 获取文件名和行号
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
#define Enbale_File_Log_Strategy() logger.EnableFileLogStrategy()
};

#endif

2.9.网络地址转换

  • InetAddr.hpp
#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"

// 网络地址 <=> 主机地址

class InetAddr
{
public:
    InetAddr()
    {
    }
    InetAddr(struct sockaddr_in &addr)
    {
       SetAddr(addr);
    }
    InetAddr(const std::string &ip, uint16_t port)
        : _ip(ip), _port(port)
    {
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        // 主机序列 → 网络序列(端口号)
        _addr.sin_port = htons(_port);
        // 点分十进制 → 网络序列(IP地址)
        inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
    }
    InetAddr(uint16_t port)
        : _port(port), _ip("0")
    {
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        _addr.sin_port = htons(_port);
        _addr.sin_addr.s_addr = INADDR_ANY;
    }
    void SetAddr(struct sockaddr_in &addr)
    {
        _addr = addr;
        // 网络序列 → 主机序列(端口号)
        _port = ntohs(_addr.sin_port);
        // 网络序列 → 点分十进制(IP地址)
        char ipbuffer[64];
        inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(_addr));
        _ip = ipbuffer;
    }
    uint16_t Port()
    {
        return _port;
    }
    std::string Ip()
    {
        return _ip;
    }
    const struct sockaddr_in &NetAddr()
    {
        return _addr;
    }
    const struct sockaddr *NetAddrPtr()
    {
        return CONV(_addr);
    }
    socklen_t NetAddrLen()
    {
        return sizeof(_addr);
    }
    bool operator==(const InetAddr &addr)
    {
        return addr._ip == _ip && addr._port == _port;
    }
    std::string StringAddr()
    {
        return _ip + " : " + std::to_string(_port);
    }
    ~InetAddr()
    {
    }

private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};

2.10.共有类

  • Common.hpp
#pragma once

#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

enum ExitCode
{
    OK = 0,
    USAGE_ERR,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    CONNECT_ERR,
    FORK_ERR,
    OPEN_ERR,
};

// 禁止拷贝
class NoCopy
{
public:
    NoCopy()
    {
    }
    ~NoCopy()
    {
    }
    NoCopy(const NoCopy &) = delete;
    const NoCopy &operator=(const NoCopy &) = delete;
};

#define CONV(addr) ((struct sockaddr *)&addr)

2.11.互斥锁

  • Mutex.hpp
#pragma once
#include <iostream>
#include <pthread.h>

namespace MutexModule
{
    class Mutex
    {
    public:
        Mutex()
        {
            pthread_mutex_init(&_mutex, nullptr);
        }
        void Lock()
        {
            int n = pthread_mutex_lock(&_mutex);
            (void)n;
        }
        void unLock()
        {
            int n = pthread_mutex_unlock(&_mutex);
            (void)n;
        }
        ~Mutex()
        {
            pthread_mutex_destroy(&_mutex);
        }
        pthread_mutex_t *Get()
        {
            return &_mutex;
        }
    private:
        pthread_mutex_t _mutex;
    };

    class LockGuard
    {
    public:
        LockGuard(Mutex &mutex)
            :_mutex(mutex)
        {
            _mutex.Lock();
        }
        ~LockGuard()
        {
            _mutex.unLock();
        }
    private:
        Mutex &_mutex;
    };
};

2.12.Makefile

.PHONY:all
all: ServerNetCald client_netcal

ServerNetCald:Main.cc
	g++ -o $@ $^ -std=c++17 -ljsoncpp -lpthread -static
client_netcal:TcpClient.cc
	g++ -o $@ $^ -std=c++17 -ljsoncpp -lpthread -static

.PHONY:output
output:
	@mkdir output
	@mkdir -p output/bin
	@mkdir -p output/conf
	@mkdir -p output/log
	@cp ServerNetCald output/bin
	@cp client_netcal output/bin
	@cp test.conf output/conf
	@cp install.sh output/
	@cp uninstall.sh output/
	@tar czf output.tgz output

.PHONY:clean
clean:
	rm -rf ServerNetCald client_netcal output output.tgz

2.13.安装卸载脚本

  • install.sh
#!/usr/bin/bash

cp -f ./bin/ServerNetCald /usr/bin
cp -f ./bin/client_netcal /usr/bin
  • uninstall.sh
#!/usr/bin/bash

rm -rf /usr/bin/ServerNetCald
rm -rf /usr/bin/client_netcal 

更多推荐