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

本章重点

  • 理解应用层的作用, 初识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提供无连接的、不可靠的、无序的数据传输服务。而每次当有客户端给向服务器发送请求的时候,此时服务器在获取客户端的链接后,会创建一个子进程来专门为这个客户端服务,这个相当于上面的会话层,我们上面进行协议的定制、序列化和反序列化,就是我们的表示层,上面的应用层呢?它在我们的代码表现的就是计数器计算的功能,它是负责网络计算的。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

那些网络安全上的事实,很多人不见得知道!

明月发现不少小白对网络安全的认知几乎为零&#xff0c;甚至明月还碰到一个说 VPN 能彻底隐匿自己的&#xff0c;至于现在这帮动不动就利用 DDos/CC 攻击被人网站来推销境外高防服务器、高防 CDN 的老鼠屎们更是网络安全知识白痴的水平&#xff0c;破坏和攻击的水平完全取决于能…

零一万物Yi-1.5开源,34B/9B/6B多尺寸,34B超Qwen1.5-72B

前言 近年来&#xff0c;大型语言模型&#xff08;LLM&#xff09;在各个领域展现出惊人的能力&#xff0c;为人们的生活和工作带来了巨大的改变。然而&#xff0c;大多数开源 LLM 的性能仍然无法与闭源模型相媲美&#xff0c;这限制了 LLM 在科研和商业领域的进一步应用。为了…

详细分析crontab定时执行任务(附Demo | 定时清空Tomcat的实战)

目录 前言1. 基本知识2. Demo3. 实战3.1 错误版本3.2 正确版本 前言 由于用户量大&#xff0c;且导出的日志以及缓存特别多&#xff0c;急需定期删除文件 1. 基本知识 crontab 是一个用于定时执行任务的命令行工具&#xff0c;通常在 Unix 和类 Unix 系统中可用&#xff0c;表…

MCF-Microbial Cell Factories

文章目录 一、期刊简介二、征稿信息三、期刊表现四、投稿须知五、投稿咨询 一、期刊简介 Microbial Cell Factories 是一份开放的同行评审期刊&#xff0c;涵盖了与微生物细胞作为重组蛋白和天然产物的生产者或作为工业兴趣的生物转化的催化剂的开发、使用和研究相关的任何主题…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-17讲 定时器按键消抖

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

抖音跳转微信卡片制作教程 小白也能搞

实测可以正常跳转&#xff0c;很牛逼&#xff0c;给大家分享一下~ 这是我做出来抖音发出去的效果&#xff0c;大家会制作了可以去卖钱&#xff0c;市场上一个这个卡片都要卖50-200&#xff0c;很不错的&#xff01;&#xff01; https://pan.baidu.com/s/1xPmGAWPcbAp7eXg7Dc…

VMware 和 VirtualBox开机自启指定虚拟机详细教程

VMware上虚拟机随宿主机开机自启 1. 设置自动启动虚拟机 网上教程旧版的&#xff0c;界面和新版有所差异。17版本设置如下&#xff1a;VMware Workstation工作台 -> 文件 -> 配置自动启动虚拟机 -> 按顺序选择需要启动的虚拟机 VMWare17配置自动启动虚拟机提示&…

当前API面临的安全风险,有什么安全措施

在当今信息化高速发展的时代&#xff0c;API&#xff08;应用程序编程接口&#xff09;技术已成为企业数字化转型的基石&#xff0c;它连接着各种服务、传输数据并控制系统&#xff0c;成为现代数字业务环境不可或缺的一部分。然而&#xff0c;随着API的广泛应用&#xff0c;其…

【B站 heima】小兔鲜Vue3 项目学习笔记

系列文章目录 Day 01 目录 系列文章目录前言Day011.项目使用相关技术栈2. 项目规模和亮点3. Vue2和Vue3实现一个小案例4. vue3的优势5. create-vue脚手架工具6. 熟悉我们的项目目录和文件7. 组合式API-setup选项8. 组合式API-reactive和ref函数9. 组合式API-computed计算属性…

C++ | Leetcode C++题解之第110题平衡二叉树

题目&#xff1a; 题解&#xff1a; class Solution { public:int height(TreeNode* root) {if (root NULL) {return 0;}int leftHeight height(root->left);int rightHeight height(root->right);if (leftHeight -1 || rightHeight -1 || abs(leftHeight - rightH…

【蓝桥杯选拔赛真题76】python找出元素 第十四届青少年组蓝桥杯python选拔赛真题 算法思维真题解析

目录 python找出元素 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、 推荐资料 1、蓝桥杯比赛 2、考级资料 3、其它资料 python找出元素 第十四届蓝桥杯青少年组python比赛选拔赛真题 一、题目要…

一文了解 FileBeat:诞生背景、发展历程与定义

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《洞察之眼&#xff1a;ELK监控与可视化》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、什么是ELK 2、FileBeat在ELK中的角色 3、File…

数据结构顺序表实现通讯录

目录 1. 前言&#xff1a; 2.通讯录项目的创建 3. 通讯录的实现 3.1 通讯录的初始化 3.2 通讯录的销毁 3.3 通讯录添加数据 3.4 通讯录查找数据 3.5 通讯录展示数据 3.6 通讯录删除数据 3.7 通讯录修改数据 4. 通讯录完整代码 4.1 test.c 4.2 SeqList.h 4.3 SeqLis…

OneAPI接入本地大模型+FastGPT调用本地大模型

将Ollama下载的本地大模型配置到OneAPI中&#xff0c;并通过FastGPT调用本地大模型完成对话。 OneAPI配置 新建令牌 新建渠道 FastGPT配置 配置docker-compose 配置令牌和OneAPI部署地址 配置config.json 配置调用的渠道名称和大模型名称 {"systemEnv": {&qu…

【虚拟机软件】 VMware Workstation Pro 17 新建 Linux 虚拟机教程(CentOS 7 版本)

文章目录 下载安装 VMware Workstation Pro 17 软件下载 Linux 的 ISO 映像文件Linux版本选择 新建虚拟机准备配置新建安装 后续设置文章导航 我是一名立志把细节说清楚的博主&#xff0c;欢迎【关注】&#x1f389; ~ 原创不易&#xff0c; 如果有帮助 &#xff0c;记得【点赞…

【全开源】填表统计预约打卡表单系统FastAdmin+ThinkPHP+UniApp

简化流程&#xff0c;提升效率 一、引言&#xff1a;传统表单处理的局限性 在日常工作和生活中&#xff0c;我们经常会遇到需要填写表单、统计数据和预约打卡等场景。然而&#xff0c;传统的处理方式往往效率低下、易出错&#xff0c;且不利于数据的统计和分析。为了解决这些…

报名倒计时两周|2024 OpenTiny 开源之夏项目直播解读回顾

5月16日&#xff0c;OpenTiny 开源社区成功举办了以《OpenTiny 开源之夏项目解读直播》为主题的直播活动。此次直播中&#xff0c;华为云的高级前端工程师曾令卡、华为云的高级前端工程师伍其和与10位开源之夏技术专家携手组成项目导师团&#xff0c;面向广大开发者一同深入探讨…

如何在go语言中调用c语言代码

1.安装c语言编译器 要使用cgo&#xff0c;需要安装c语言编译器 gcc 2.检查CGO_ENABLED时候开启 使用以下命令查看&#xff1a; go env CGO_ENABLED 如果go env CGO_ENABLED被禁用(为0),需要将其设置为开启(为1) 3.编写c语言程序&#xff0c;并用go语言调用c语言程序 1&#xff…

Design to code(2)

【碎碎念】从七点到十一点&#xff0c;累计用时4个小时完成的代码翻译Σ(&#xffe3;。&#xffe3;ノ)ノ DCDS图 顺序图&#xff08;支付过程&#xff09; 交互图&#xff08;订单&#xff09; 我的代码 Payment public class Payment { //定义支付订单金额 private…

k8s集群部署成功后某个节点突然出现notready状态解决办法

通过&#xff1a; kubectl get nodes 查看master1节点为not ready 通过查看日志&#xff1a; journalctl -f -u kubelet.service 看到这里 查看状态&#xff1a; systemctl status kubelet.service 重启一样会报错 执行&#xff1a; swapoff -a 执行后&#xff0c;重启…