【网络版本计算器的实现】

news2024/11/26 16:38:30

本章重点

  • 理解应用层的作用, 初识HTTP协议
  • 理解传输层的作用, 深入理解TCP的各项特性和机制
  • 对整个TCP/IP协议有系统的理解
  • 对TCP/IP协议体系下的其他重要协议和技术有一定的了解
  • 学会使用一些分析网络问题的工具和方法

注意!! 注意!! 注意!!

  • 本课是网络编程的理论基础.
  • 是一个服务器开发程序员的重要基本功.
  • 是整个Linux课程中的重点和难点.
  • 也是各大公司笔试面试的核心考点

一、应用层

我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层.

1.再谈 "协议"

协议是一种 "约定". socket api的接口,在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果此时字符串的内容比较多,而我们的缓冲区大小又有限,那么此时读上来的字符串可能会不完整,所以上一章的我们在应用层写的代码其实是有bug的,要想解决就要在应用层我们是需要协议定制、序列化和反序列化。

其实上一章我们也进行了协议的定制,只不过非常草率,我们客户端发送一个英语单词,而服务器进处理,将该英文单词的意思返回给客户端,我们来看一下真正协议定制的过程。

在网络传输时,序列化目的是为了方便网络数据的发送和接收,无论是何种类型的数据,经过序列化后都变成了二进制序列,此时底层在进行网络数据传输时看到的统一都是二进制序列。序列化后的二进制序列只有在网络传输时能够被底层识别,上层应用是无法识别序列化后的二进制序列的,因此需要将从网络中获取到的数据进行反序列化,将二进制序列的数据转换成应用层能够识别的数据格式。

二、网络版计算器

1.协议的定制封装

例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端.

约定方案一:

  • 客户端发送一个形如"1+1"的字符串;
  • 这个字符串中有两个操作数, 都是整形;
  • 两个数字之间会有一个字符是运算符, 运算符只能是 + ;
  • 数字和运算符之间没有空格;
  • ...

⭐约定方案二:

  • 定义结构体来表示我们需要交互的信息;
  • 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;
  • 这个过程叫做 "序列化" 和 "反序列化"

我们根据上面的结构体先来将我们的信息进行序列化。

#pragma once

#include <iostream>
#include <string>

using namespace std;

const string blank_space_sep = " ";

class Request
{
public:
    Request(int data1, int data2, char oper)
        : x(data1), y(data2), op(oper)
    {
    }
    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;
    }
public:
    int x;
    int y;
    char op;
};

class Response
{
public:
    Response(int res, int c)
        : result(res), code(c)
    {
    }
    bool Serialize(string *out)
    {
        // 构建报文的有效载荷
        // struct => string, "result code"
        string s = to_string(result);
        s += blank_space_sep;
        s += to_string(code);

        *out = s;

        return true;
    }
public:
    int result;
    int code; // 错误码  0-可信
};

我们来测试一下:

随后我们就要向该报文添加一些报头信息。

#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;
}
class Request
{
public:
    Request(int data1, int data2, char oper)
        : x(data1), y(data2), op(oper)
    {
    }
    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;

        // 协议的模样: "len\nx op y\n"
        return true;
    }
public:
    int x;
    int y;
    char op;
};

class Response
{
public:
    Response(int res, int c)
        : result(res), code(c)
    {
    }
    bool Serialize(string *out)
    {
        // 构建报文的有效载荷
        // struct => string, "result code"
        string s = to_string(result);
        s += blank_space_sep;
        s += to_string(code);

        *out = s;

        // 协议的模样: "len\nresult code\n"
        return true;
    }
public:
    int result;
    int code; // 错误码  0-可信
};

我们再来测试一下哈:

未来服务器收到这个报文,就要将报头信息去掉,拿到有效载荷。

// 解包报文
// 9\n123 + 456\n
bool Decode(string &package, std::string *content)
{
    size_t pos = package.find(protocol_sep); // 找\n
    if (pos == string::npos)
        return false;
    string len_str = package.substr(0, pos); // 找到"9"
    size_t len = stoi(len_str); // 取出9
    // 总的报文长度
    // 换行字符是一个字符哟!
    size_t total_len = len_str.size() + len + 1; // "9"的长度 + 9 + "\n"
    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;
}

我们再来测试一下:

现在我们就已经拿到了有效载荷,但是我们还要进行反序列化才可以。

#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;
}
// 解包报文
// 9\n123 + 456\n
bool Decode(string &package, std::string *content)
{
    size_t pos = package.find(protocol_sep); // 找\n
    if (pos == string::npos)
        return false;
    string len_str = package.substr(0, pos); // 找到"9"
    size_t len = stoi(len_str);              // 取出9
    // 总的报文长度
    size_t total_len = len_str.size() + len + 2; // "9"的长度 + 9 + 2个"\n"
    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()
    {
    }
    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;

        // 协议的模样: "len\nx op y\n"
        return true;
    }
    bool Deserialize(const string &in) // 反序列化
    {
        // "x op y"
        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()
    {
    }
    bool Serialize(string *out)
    {
        // 构建报文的有效载荷
        // struct => string, "result code"
        string s = to_string(result);
        s += blank_space_sep;
        s += to_string(code);

        *out = s;

        // 协议的模样: "len\nresult code\n"
        return true;
    }
    bool Deserialize(const string &in) // 反序列化
    {
        // "result code"
        size_t pos = in.find(blank_space_sep);
        if (pos == string::npos)
            return false;
        string part_left = in.substr(0, pos);
        string part_right = in.substr(pos + 1);

        result = stoi(part_left);
        code = stoi(part_right);

        return true;
    }

    void DebugPrint()
    {
        std::cout << "结果响应完成, result: " << result << ", code: " << code << std::endl;
    }

public:
    int result;
    int code; // 错误码  0-可信
};

我们来测试一下:

 此时我们的协议就算制定完成了,既然是网络版本的服务器,那我们直接写服务器的代码呗。

2.套接字相关接口封装

#pragma once

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

using namespace std;

Log lg;
enum
{
    SocketErr = 2,
    BindErr,
    ListenErr,
};

// TODO
const int backlog = 10;

class Sock
{
public:
    Sock()
    {
    }
    ~Sock()
    {
    }

public:
    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(std::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 std::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_;
};

3.服务器的搭建封装

#pragma once

#include "Socket.hpp"
#include <signal.h>
#include <functional>

using func_t = function<string(string &package)>;

class TcpServer
{
public:
    TcpServer(uint16_t port, func_t callback) : port_(port), callback_(callback)
    {
    }
    bool Init()
    {
        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)
        {
            std::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();
                std::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)
                        {
                            // 将发过来的多个请求一次性处理完
                            std::string info = callback_(inbuffer_stream);
                            if (info.empty())
                                break;
                            //lg(Debug, "debug, response:\n%s", info.c_str());
                            //lg(Debug, "debug:\n%s", inbuffer_stream.c_str());
                            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_;
};

4.计数器功能封装

#pragma once

#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
    std::string Calculator(std::string &package)
    {
        std::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()
    {
    }
};

5.日志信息的封装

#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;
};

6.服务器的启动

#include "Socket.hpp"
#include "ServerCal.hpp"
#include "TcpServer.hpp"

using namespace std;

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, bind(&ServerCal::Calculator, &cal, placeholders::_1));
    tsvp->Init();
    tsvp->Start();

    return 0;
}

此时我们来查看一下结果:

7.客户端的启动

#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 = "+-*/%=-=&^";

    while (cnt <= 2)
    {
        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();

        string content;
        req.Serialize(&content);

        string package = Encode(content);
        int n1 = write(sockfd.Fd(), package.c_str(), package.size());
        cout << "这是最新的发出去的请求: " << n1 << "\n"
             << package;

        std::string inbuffer_stream;
        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;
}

我们来看一下运行结果:

我们再来测试一下,如果同时多个请求发送我们的服务器能不能处理,此时我们将客户端的从服务器读取代码的信息先屏蔽掉。

我们一次性发送两个请求:

我们来看一下结果:

8.协议定制的改善

上面我们的协议就算制定完成了,但是以后每次我们都要自己来写协议吗?幸运的是,现代开发中广泛采用了一些高级的数据交换格式和协议,使得开发者不必从零开始设计通信协议。json就是其中一种非常流行的数据交换格式,首先我们就需要安装这个第三方库,安装第三方库首先就会给我安装头文件,随后就会安装这个库。

sudo apt-get install libjsoncpp-dev

此时我们就能发现安装成功了,现在我们来使用一下它。

#include <iostream>
#include <jsoncpp/json/json.h>
#include <unistd.h>

// {a:120, b:"123"}
int main()
{
    // 封装格式化数据
    Json::Value root;
    root["x"] = 100;
    root["y"] = 200;
    root["op"] = '+';
    root["desc"] = "this is a + oper";

    // 序列化
    Json::FastWriter w;
    std::string res = w.write(root);

    std::cout << res << std::endl;
    return 0;
}

然后我们现在来编译一下,此时需要指定第三方库才能链接成功。

g++ test.cc -ljsoncpp

我们来看一下运行结果:

我们再来看一下反序列化:

#include <iostream>
#include <jsoncpp/json/json.h>
#include <unistd.h>

// {a:120, b:"123"}
int main()
{
    // 封装格式化数据
    Json::Value root;
    root["x"] = 100;
    root["y"] = 200;
    root["op"] = '+';
    root["desc"] = "this is a + oper";

    // 序列化
    // Json::FastWriter w;
    Json::StyledWriter w;
    std::string res = w.write(root);
    std::cout << res << std::endl;

    Json::Value v;
    Json::Reader r;
    r.parse(res, v);
	
    // 反序列化
    int x = v["x"].asInt();
    int y = v["y"].asInt();
    char op = v["op"].asInt();
    std::string desc = v["desc"].asString();

    std::cout << x << std::endl;
    std::cout << y << std::endl;
    std::cout << op << std::endl;
    std::cout << desc << std::endl;

    return 0;
}

我们来看一下运行结果:

同时我们的json里面可以再套json,可以进行嵌套。

#include <iostream>
#include <jsoncpp/json/json.h>
#include <unistd.h>

// {a:120, b:"123"}
int main()
{
    Json::Value part1;
    part1["haha"] = "haha";
    part1["hehe"] = "hehe";


    Json::Value root;
    root["x"] = 100;
    root["y"] = 200;
    root["op"] = '+';
    root["desc"] = "this is a + oper";
    root["test"] = part1;

    //Json::FastWriter w;
    Json::StyledWriter w;
    std::string res = w.write(root);

    std::cout << res << std::endl;

    sleep(3);

    Json::Value v;
    Json::Reader r;
    r.parse(res, v);

    int x = v["x"].asInt();
    int y = v["y"].asInt();
    char op = v["op"].asInt();
    std::string desc = v["desc"].asString();
    Json::Value temp = v["test"];
    std::cout << x << std::endl;
    std::cout << y << std::endl;
    std::cout << op << std::endl;
    std::cout << desc << std::endl;

    return 0;
}

现在我们就来使用它来改善我们的协议。

#pragma once

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

using namespace std;

// #define MySelf 1

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;
}
// 解包报文
// 9\n123 + 456\n
bool Decode(string &package, std::string *content)
{
    size_t pos = package.find(protocol_sep); // 找\n
    if (pos == string::npos)
        return false;
    string len_str = package.substr(0, pos); // 找到"9"
    size_t len = stoi(len_str);              // 取出9
    // 总的报文长度
    size_t total_len = len_str.size() + len + 2; // "9"的长度 + 9 + 2个"\n"
    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;
}

// json, protobuf
class Request
{
public:
    Request(int data1, int data2, char oper) : x(data1), y(data2), op(oper)
    {
    }
    Request()
    {}
public:
    bool Serialize(std::string *out)
    {
#ifdef MySelf
        // 构建报文的有效载荷
        // struct => string, "x op y"
        std::string s = std::to_string(x);
        s += blank_space_sep;
        s += op;
        s += blank_space_sep;
        s += std::to_string(y);
        *out = s;
        return true;
#else
        Json::Value root;
        root["x"] = x;
        root["y"] = y;
        root["op"] = op;
        // Json::FastWriter w;
        Json::StyledWriter w;
        *out = w.write(root);
        return true;
#endif
    }
    bool Deserialize(const std::string &in) // "x op y"
    {
#ifdef MySelf
        std::size_t left = in.find(blank_space_sep);
        if (left == std::string::npos)
            return false;
        std::string part_x = in.substr(0, left);

        std::size_t right = in.rfind(blank_space_sep);
        if (right == std::string::npos)
            return false;
        std::string part_y = in.substr(right + 1);

        if (left + 2 != right)
            return false;
        op = in[left + 1];
        x = std::stoi(part_x);
        y = std::stoi(part_y);
        return true;
#else
        Json::Value root;
        Json::Reader r;
        r.parse(in, root);

        x = root["x"].asInt();
        y = root["y"].asInt();
        op = root["op"].asInt();
        return true;
#endif
    }
    void DebugPrint()
    {
        std::cout << "新请求构建完成:  " << x << op << y << "=?" << std::endl;
    }
public:
    // x op y
    int x;
    int y;
    char op; // + - * / %
};

class Response
{
public:
    Response(int res, int c) : result(res), code(c)
    {
    }

    Response()
    {}
public:
    bool Serialize(std::string *out)
    {
#ifdef MySelf
        // "result code"
        // 构建报文的有效载荷
        std::string s = std::to_string(result);
        s += blank_space_sep;
        s += std::to_string(code);
        *out = s;
        return true;
#else
        Json::Value root;
        root["result"] = result;
        root["code"] = code;
        // Json::FastWriter w;
        Json::StyledWriter w;
        *out = w.write(root);
        return true;
#endif
    }
    bool Deserialize(const std::string &in) // "result code"
    {
#ifdef MySelf
        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;
#else
        Json::Value root;
        Json::Reader r;
        r.parse(in, root);

        result = root["result"].asInt();
        code = root["code"].asInt();
        return true;
#endif

    }
    void DebugPrint()
    {
        std::cout << "结果响应完成, result: " << result << ", code: "<< code << std::endl;
    }
public:
    int result;
    int code; // 0,可信,否则!0具体是几,表明对应的错误原因
};

此时我们来看一下makefile文件,我们可以通过编译的时候带上D来定义宏,并不是只能在代码上定义。

.PHONY:all
all:servercal clientcal
servercal:ServerCal.cc
	g++ -o $@ $^ -std=c++11 -D MySelf=1
clientcal:ClientCal.cc
	g++ -o $@ $^ -std=c++11 -D MySelf=1

.PHONY:clean
clean:
	rm -f servercal clientcal

此时就是使用我们自己定义的协议,如果要使用json,我们就不能带-D选项,此时我们要携带我们的第三方库,这样才能进行链接。

9.服务器守护进程化

这个函数接受两个整型参数:

  1. nochdir:

    • 如果 nochdir 参数为0,daemon() 函数将会把当前工作目录更改为根目录("/")。这是守护进程的标准行为,避免因当前工作目录被卸载而导致的问题。
    • 如果 nochdir 为非0值,则不改变当前工作目录。
  2. noclose:

    • 如果 noclose 参数为0,daemon() 函数会关闭标准输入、标准输出和标准错误,并将它们都重定向到 /dev/null。这可以防止守护进程因为试图写入终端而阻塞或产生不必要的输出。
    • 如果 noclose 为非0值,标准输入、输出和错误保持不变。但通常情况下,为了确保守护进程的无终端运行,我们会选择关闭它们。

使用 daemon() 函数的基本步骤通常包括:

  • 调用 fork() 创建子进程,父进程退出,这样新进程就不再与终端关联。
  • 在子进程中调用 setsid() 成为新的会话领导并脱离控制终端。
  • 调用 umask() 设置合适的权限掩码。
  • 根据需要调用 chdir("/") 更改当前工作目录到根目录。
  • 重定向标准输入、输出和错误流,或者通过 daemon() 函数自动处理。
  • 继续执行守护进程的具体任务。

我们直接将这个调用加载服务器的初始化和启动之间即可。

come up,我们来运行一下哈。

无论我们采用方案一, 还是方案二, 还是其他的方案, 只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解 析, 就是ok的. 这种约定, 就是 应用层协议

三、重谈OSI七层模型

传输层在我们上面的代码体现为创建套接字的代码,它负责端到端的通信,确保数据可靠或尽力而为地传输。在TCP/IP模型中,TCP和UDP是传输层的两个主要协议。TCP提供面向连接的、可靠的、有序的数据传输服务;而UDP提供无连接的、不可靠的、无序的数据传输服务。而每次当有客户端给向服务器发送请求的时候,此时服务器在获取客户端的链接后,会创建一个子进程来专门为这个客户端服务,这个相当于上面的会话层,我们上面进行协议的定制、序列化和反序列化,就是我们的表示层,上面的应用层呢?它在我们的代码表现的就是计数器计算的功能,它是负责网络计算的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1688913.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IT廉连看——UniApp——事件绑定

IT廉连看——UniApp——事件绑定 这是我们上节课最终的样式&#xff1b; 一、现在我有这样一个需求&#xff0c;当我点击“生在国旗下&#xff0c;长在春风里”它的颜色由红色变为蓝色&#xff0c;该怎么操作&#xff1f; 这时候我们需要一个事件的绑定&#xff0c;绑定一个单…

设计模式—23种设计模式重点 表格梳理

设计模式的核心在于提供了相关的问题的解决方案&#xff0c;使得人们可以更加简单方便的复用成功的设计和体系结构。 按照设计模式的目的可以分为三大类。创建型模式与对象的创建有关&#xff1b;结构型模式处理类或对象的组合&#xff1b;行为型模式对类或对象怎样交互和怎样…

pytorch-20_1 LSTM在股价数据集上的预测实战

LSTM在股价数据集上的预测实战 使用完整的JPX赛题数据&#xff0c;并向大家提供完整的lstm流程。 导包 import numpy as np #数据处理 import pandas as pd #数据处理 import matplotlib as mlp import matplotlib.pyplot as plt #绘图 from sklearn.preprocessing import M…

Unreal Engine5 Landscape地形材质无法显示加载

UE5系列文章目录 文章目录 UE5系列文章目录前言一、解决办法 前言 在使用ue5做地形编辑的时候&#xff0c;明明刚才就保存的Landscape地形完全消失不见&#xff0c;或者是地形的材质不见了。重新打开UE5发现有时候能解决&#xff0c;但大多数时候还是没有解决&#xff0c;我下…

有效的变位词

如果哈希表的键的取值范围是固定的&#xff0c;并且范围不是很大&#xff0c;则可以用数组来模拟哈希表。数组的下标和哈希表的键相对应&#xff0c;而数组的值和哈希表的值相对应。 英文小写字母只有26个&#xff0c;因此可以用一个数组来模拟哈希表。 class Solution {publi…

中国主要城市房价指数数据集(2011-2024)

数据来源&#xff1a;东方财富网 时间跨度&#xff1a;2011年1月 - 2024年4月 数据范围&#xff1a;中国主要城市 包含指标&#xff1a; 日期、城市 新建商品住宅价格指数-同比 新建商品住宅价格指数-环比 新建商品住宅价格指数-定基 二手住宅价格指数-环比 二手住宅价格指…

CS西电高悦计网课设——校园网设计

校园网设计 一&#xff0c;需求分析 所有主机可以访问外网 主机可以通过域名访问Web服务器 为网络配置静态或者动态路由 图书馆主机通过DHCP自动获取IP参数 为办公楼划分VLAN 为所有设备分配合适的IP地址和子网掩码&#xff0c;IP地址的第二个字节使用学号的后两位。 二…

学习平台|基于Springboot+vue的学习平台系统的设计与实现(源码+数据库+文档)

学习平台系统 目录 基于Springboot&#xff0b;vue的学习平台系统的设计与实现 一、前言 二、系统设计 三、系统功能设计 1系统功能模块 2管理员功能模块 3学生功能模块 4教师功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八…

基于STM32实现智能风扇控制系统

目录 文章主题环境准备智能风扇控制系统基础代码示例&#xff1a;实现智能风扇控制系统 PWM控制风扇速度温度传感器数据读取串口通信控制应用场景&#xff1a;智能家居与环境调节问题解决方案与优化收尾与总结 1. 文章主题与命名 文章主题 本教程将详细介绍如何在STM32嵌入式…

layui扩展件(xm-select)实现下拉框

layui扩展件&#xff08;xm-select&#xff09;实现下拉框 扩展组件 xm-select 效果图 html代码 <div class"layui-inline"><label class"layui-form-label">职位</label><div class"layui-input-inline" style"wid…

你以为的私域是真正的私域嘛??你的私域流量真的属于你嘛?

大家好 我是一个软件开发公司的产品经理 专注私域电商行业7年有余 您的私域流量是真正的属于你自己嘛&#xff1f; 私域的定义 私域的界定&#xff1a;一个互联网私有数据&#xff08;资产&#xff09;积蓄的载体。这个载体的数据权益私有&#xff0c;且具备用户规则制定权…

法那科机器人M-900iA维修主要思路

发那科工业机器人是当今制造业中常用的自动化设备之一&#xff0c;而示教器是发那科机器人操作和维护的重要组成部分。 一、FANUC机械手示教器故障分类 1. 硬件故障 硬件故障通常是指发那科机器人M-900iA示教器本身的硬件问题&#xff0c;如屏幕损坏、按键失灵、电源故障等。 2…

脆皮之“字符函数与字符串函数”宝典

hello&#xff0c;大家好呀&#xff0c;感觉我之前有偷偷摸鱼了&#xff0c;今天又开始学习啦。加油&#xff01;&#xff01;&#xff01; 文章目录 1. 字符分类函数2. 字符转换函数3. strlen的使用和模拟实现3.1 strlen 的使用3.1 strlen 的模拟1.计算器方法2.指针-指针的方…

【Spring Security + OAuth2】身份认证

Spring Security OAuth2 第一章 Spring Security 快速入门 第二章 Spring Security 自定义配置 第三章 Spring Security 前后端分离配置 第四章 Spring Security 身份认证 第五章 Spring Security 授权 第六章 OAuth2 1、用户认证信息 1.1、基本概念 在Spring Security框架中…

Axure RP 9 for Mac/win:重新定义交互原型设计的未来

在当今数字化时代&#xff0c;交互原型设计已成为产品开发中不可或缺的一环。Axure RP 9作为一款功能强大的交互原型设计软件&#xff0c;凭借其出色的性能和用户友好的界面&#xff0c;赢得了广大设计师的青睐。 Axure RP 9不仅支持Mac和Windows两大主流操作系统&#xff0c;…

PMP 学习笔记(增量更新中)

PMP 作为最流行的项目管理方法论&#xff0c;是项目管理领域的对话基础&#xff0c;了解它能帮助我理解术语和规范的管理过程&#xff0c;也许后面会考一个认证。感谢 B 站视频《 PMP 认证考试课程最新完整免费课程零基础一次通过项目管理 PMP 考试》的作者&#xff0c;我通过它…

【简单介绍下深度神经网络】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

人工智能应用-实验7-胶囊网络分类minst手写数据集

文章目录 &#x1f9e1;&#x1f9e1;实验内容&#x1f9e1;&#x1f9e1;&#x1f9e1;&#x1f9e1;代码&#x1f9e1;&#x1f9e1;&#x1f9e1;&#x1f9e1;分析结果&#x1f9e1;&#x1f9e1;&#x1f9e1;&#x1f9e1;实验总结&#x1f9e1;&#x1f9e1; &#x1f9…

vue3+ts实战

目录 一、ts语法练习 1.1、安装 1.2、语法 二、vue3ts 2.1、项目创建 2.1.1、项目创建(建议node版本在16.及以上) 2.1.2、下载路由、axios 2.1.3、引入element-plus 2.1.4、报错解决 (1)文件路径下有红色波浪 (2)组件名称下有红色波浪 (3)引入模块下有红色波浪 2.…