TCP定制协议,序列化和反序列化

目录

前言

1.理解协议

2.网络版本计算器

2.1设计思路

2.2接口设计

2.3代码实现:

2.4编译测试

总结


前言

        在之前的文章中,我们说TCP是面向字节流的,但是可能对于面向字节流这个概念,其实并不理解的,今天我们要介绍的是如何理解TCP是面向字节流的,通过编码的方式,自己定制协议,实现序列化和反序列化,相信看完这篇文章之后,关于TCP面向字节流的这个概念,你将会有一个清晰的认识,下面我们就一起来看看。

1.理解协议

        前面,我们通俗的介绍过协议,在网络中协议是属于一种约定,今天要说的是,数据在网络中传输的时候,协议又是如何体现的。

根据我们之前写的TCP服务器实现数据通信知道socket api的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果我们要传输一些"结构化的数据" 怎么办呢?

什么是结构化的数据呢?

举个简单的例子,比如在微信上发送信息的时候,除了有发送的信息之外还包含有昵称,时间,头像这些信息,这些合起来就称为是结构化的数据。

所以我们将结构化的数据打包形成一个字符串的过程就称为是序列化,将打包形成的一个字符串转化为结构化数据的过程就称为是反序列化

如图所示:

TCP发送和接受数据的流程

如图所示:

作为程序员,在应用层定义一个缓冲区,然后send接口将数据发送,在这里发送不是将数据直接发送到网络里了,而是调用send接口将数据拷贝到传输层操作系统维护的缓冲区中,而read是将传输层的数据拷贝到应用层,当数据拷贝到传输层之后,剩下数据如何继续发送是由操作系统进行维护的,所以将TCP协议称为是传输控制协议,又因为TCP协议既可以是客户端向服务端发送信息,也可以是服务端向客户端发送信息,所以TCP是全双工的。

了解了TCP协议发送和接受数据的流程之后,因为TCP是面向字节流的,思考当数据由客户端发送给服务端的时候,有没有可能服务端的接受缓冲区中不足一个报文,有没有可能上层来不及处理导致服务端传输层接受缓冲区中有多个报文的情况,此时如何正确的拿到一个完整的报文呢?

因为这些问题的存在,所以我们要定制协议,明确一个完整报文大小,明确一个报文和一个报文的边界,所以我们要采取定制协议的方案获取到一个正确的报文。

一般采取的策略有三种:

1.定长

2.特殊符号

3.自描述的方式

下面我们按照上述的三种方式实现编码上的协议定制。

2.网络版本计算器

说明:为了演示协议定制和序列化以及反序列化在编码上如何实现,以及如何在编码上体现TCP面向字节流的特性,我们通过实现一个网络版本计算器为大家进行介绍

实现网络版本计算机约定:

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

2.1设计思路

        客户端将想要计算的请求按照序列化的方式打包成一个字符串,然后发送给服务端,服务端按照定制协议的方式准确收到客户端的请求,然后服务端进行反序列化获取到结构化的数据,然后进行处理业务逻辑计算结果,结果计算完成之后,服务端将计算结果序列化打包形成一个字符串发送给客户端,然后客户端按照定制协议的方式准确获取到服务端发送过来的一个完整报文,至此就基于TCP协议实现了一个网络版本的计算器

2.2接口设计

要向完成上述的要求,就必须要包含几个接口:

a.请求的序列化和反序列化

b.响应的序列化和反序列化

c.协议定制

d.计算业务逻辑

e.准确获取一个报文

f.客户端和服务端编写

2.3代码实现:

1.请求的序列化和反序列化

class Request
{
public:
    Request()
    :x(0),y(0),op(char()){}
    Request(int x_, int y_, char op_) : x(x_), y(y_), op(op_)
    {}
    bool serialize(std::string *out)
    {
        *out = "";
        // 结构化 -> "x op y";
        std::string x_string = std::to_string(x);
        std::string y_string = std::to_string(y);

        *out = x_string;
        *out += SEP;
        *out += op;
        *out += SEP;
        *out += y_string;
        return true;
    }

    // "x op yyyy";
    bool deserialize(const std::string &in)
    {
        // "x op y" -> 结构化
        auto left = in.find(SEP);
        auto right = in.rfind(SEP);
        if (left == std::string::npos || right == std::string::npos)
            return false;
        if (left == right)
            return false;
        if (right - (left + SEP_LEN) != 1)
            return false;

        std::string x_string = in.substr(0, left); // [0, 2) [start, end) , start, end - start
        std::string y_string = in.substr(right + SEP_LEN);

        if (x_string.empty())
            return false;
        if (y_string.empty())
            return false;
        x = stoi(x_string);
        y = stoi(y_string);
        op = in[left + SEP_LEN];
        return true;
    }

public:
    int x;
    int y;
    char op;
};

序列化结果:将x,y,op - > 转化为 "x y op\r\n"

反序列化结果:"x y op\r\n" - > 转化为 x,y,op

2.响应的序列化和反序列化

#define SEP " "
#define SEP_LEN strlen(SEP) // 不敢使用sizeof()
#define LINE_SEP "\r\n"
#define LINE_SEP_LEN strlen(LINE_SEP) // 不敢使用sizeof()
class Response
{
public:
    Response()
    :exitcode(0),result(0) {}
    Response(int exitcode_, int result_) : exitcode(exitcode_), result(result_)
    {}
    bool serialize(std::string *out)
    {
        *out = "";
        std::string ec_string = std::to_string(exitcode);
        std::string res_string = std::to_string(result);

        *out = ec_string;
        *out += SEP;
        *out += res_string;
        return true;
    }
    bool deserialize(const std::string &in)
    {
        // "exitcode result"
        auto mid = in.find(SEP);
        if (mid == std::string::npos)
            return false;
        std::string ec_string = in.substr(0, mid);
        std::string res_string = in.substr(mid + SEP_LEN);
        if (ec_string.empty() || res_string.empty())
            return false;

        exitcode = std::stoi(ec_string);
        result = std::stoi(res_string);
        return true;
    }
public:
    int exitcode;
    int result;
};

序列化结果:将exitcode,result - > 转化为 "exitcode result\r\n"

反序列化结果: "exitcode result\r\n" - > 转化为 exitcode,result

3.协议定制

说明:采用自描述的方式+特殊符号,给一个报文头部加上报文的长度,特殊符号"\r\n"用来区分报文长度和报文数据

#define SEP " "
#define SEP_LEN strlen(SEP) // 不敢使用sizeof()
#define LINE_SEP "\r\n"
#define LINE_SEP_LEN strlen(LINE_SEP) // 不敢使用sizeof()
//enLength 和 deLength:打包和解包,解决服务端和客户端准确拿到数据
// "x op y" -> "content_len"\r\n"x op y"\r\n
// "exitcode result" -> "content_len"\r\n"exitcode result"\r\n
std::string enLength(const std::string &text)
{
    std::string send_string = std::to_string(text.size());
    send_string += LINE_SEP;
    send_string += text;
    send_string += LINE_SEP;

    return send_string;
}

// "content_len"\r\n"exitcode result"\r\n
bool deLength(const std::string &package, std::string *text)
{
    auto pos = package.find(LINE_SEP);
    if (pos == std::string::npos)
        return false;
    std::string text_len_string = package.substr(0, pos);
    int text_len = std::stoi(text_len_string);
    *text = package.substr(pos + LINE_SEP_LEN, text_len);
    return true;
}

4.计算业务逻辑

//req是反序列化后的结果,根据res业务处理填充req即可
bool cal(const Request& req,Response& res)
{
    //req是结构化的数据,可以直接使用
    // req已经有结构化完成的数据啦,你可以直接使用
    res.exitcode = OK;
    res.result = OK;

    switch (req.op)
    {
    case '+':
        res.result = req.x + req.y;
        break;
    case '-':
        res.result = req.x - req.y;
        break;
    case '*':
        res.result = req.x * req.y;
        break;
    case '/':
    {
        if (req.y == 0)
            res.exitcode = DIV_ZERO;
        else
            res.result = req.x / req.y;
    }
    break;
    case '%':
    {
        if (req.y == 0)
            res.exitcode = MOD_ZERO;
        else
            res.result = req.x % req.y;
    }
    break;
    default:
        res.exitcode = OP_ERROR;
        break;
    }

    return true;
}

5.准确获取一个报文

//从sock中读取数据保存到text中
//continue是因为tcp协议是面向字节流的,传输数据的时候可能不完整
bool recvPackage(int sock,string &inbuffer,string *text)
{
    char buffer[1024];
    while (true)
    {
        ssize_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            buffer[n] = 0;
            inbuffer += buffer;
            // 分析处理
            auto pos = inbuffer.find(LINE_SEP);
            if (pos == std::string::npos)
                continue;
            std::string text_len_string = inbuffer.substr(0, pos);
            int text_len = std::stoi(text_len_string);
            int total_len = text_len_string.size() + 2 * LINE_SEP_LEN + text_len;
            // text_len_string + "\r\n" + text + "\r\n" <= inbuffer.size();
            std::cout << "处理前#inbuffer: \n" << inbuffer << std::endl;

            if (inbuffer.size() < total_len)
            {
                std::cout << "你输入的消息,没有严格遵守我们的协议,正在等待后续的内容, continue" << std::endl;
                continue;
            }

            // 至少有一个完整的报文
            *text = inbuffer.substr(0, total_len);
            inbuffer.erase(0, total_len);

            std::cout << "处理后#inbuffer:\n " << inbuffer << std::endl;

            break;
        }
        else
            return false;
    }
    return true;
}

注:看到这里我们就可以理解了TCP是面向字节流的概念了。

6.客户端和服务端实现:

calServer.hpp:

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include "log.hpp"
#include "protocol.hpp" //按照协议约定读取请求
using namespace std;
namespace server
{
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR
    };
    static const uint16_t gport = 8080;
    static const int gbacklog = 5;
    typedef function<bool(const Request& req,Response& res)> func_t;
    //读取请求,保证解耦
    void handlerEnter(int sock,func_t fun)
    {
       string inbuffer;
       while(true)
       {
            //1. 读取:"content_len"\r\n"x op y"\r\n
            // 1.1 你怎么保证你读到的消息是 【一个】完整的请求
            string req_text, req_str;
            if (!recvPackage(sock,inbuffer,&req_text))
                return;
            std::cout << "带报头的请求:\n" << req_text << std::endl;
            //req_str:获取报文
            if (!deLength(req_text, &req_str))
                return;
            std::cout << "去掉报头的正文:\n" << req_str << std::endl;
            // 2. 对请求Request,反序列化
            // 2.1 得到一个结构化的请求对象
            Request req;
            if(!req.deserialize(req_str))
                return;
            // 3. 计算机处理,req.x, req.op, req.y --- 业务逻辑
            // 3.1 得到一个结构化的响应
            Response res;
            fun(req,res);//req处理的结果放到res中,采用回调的方式保证上层业务逻辑和服务器的解耦
            // 4.对响应Response,进行序列化
            // 4.1 得到了一个"字符串"
            string resp_str;
            if(!res.serialize(&resp_str))
                return;
             std::cout << "计算完成, 序列化响应: " <<  resp_str << std::endl;
            // 5. 然后我们在发送响应
            // 5.1 构建成为一个完整的报文
            std::string send_string = enLength(resp_str);
            std::cout << "构建完成完整的响应\n" <<  send_string << std::endl;
            send(sock, send_string.c_str(), send_string.size(), 0); // 其实这里的发送也是有问题的,不过后面再说
       }
    }
    class CalServer
    {
    public:
        CalServer(const uint16_t &port = gport) : _listensock(-1), _port(port)
        {}
        void initServer()
        {
            // 1. 创建socket文件套接字对象
            _listensock = socket(AF_INET, SOCK_STREAM, 0);
            if (_listensock < 0)
            {
                logMessage(FATAL, "create socket error");
                exit(SOCKET_ERR);
            }
            logMessage(NORMAL, "create socket success: %d", _listensock);

            // 2. bind绑定自己的网络信息
            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(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                logMessage(FATAL, "bind socket error");
                exit(BIND_ERR);
            }
            logMessage(NORMAL, "bind socket success");

            // 3. 设置socket 为监听状态
            if (listen(_listensock, gbacklog) < 0) // 第二个参数backlog后面在填这个坑
            {
                logMessage(FATAL, "listen socket error");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL, "listen socket success");
        }
        void start(func_t fun)
        {
            for (;;)
            {
                // 4. server 获取新链接
                // sock, 和client进行通信的fd
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accept error, next");
                    continue;
                }
                logMessage(NORMAL, "accept a new link success, get new sock: %d", sock); // ?

                // version 2 多进程版(2)
                pid_t id = fork();
                if (id == 0) // child
                {
                    close(_listensock);
                    handlerEnter(sock,fun);
                    close(sock);
                    exit(0);
                }
                close(sock);

                // father
                pid_t ret = waitpid(id, nullptr, 0);
                if (ret > 0)
                {
                    logMessage(NORMAL, "wait child success"); // ?
                }
            }
        }
        ~CalServer() {}

    private:
        int _listensock; // 不是用来进行数据通信的,它是用来监听链接到来,获取新链接的!
        uint16_t _port;
    };

} // namespace server

calClient.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 <unistd.h>
#include "protocol.hpp"

#define NUM 1024

class CalClient
{
public:
    CalClient(const std::string &serverip, const uint16_t &serverport)
        : _sock(-1), _serverip(serverip), _serverport(serverport)
    {}
    void initClient()
    {
        // 1. 创建socket
        _sock = socket(AF_INET, SOCK_STREAM, 0);
        if (_sock < 0)
        {
            std::cerr << "socket create error" << std::endl;
            exit(2);
        }
    }
    void start()
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(_serverport);
        server.sin_addr.s_addr = inet_addr(_serverip.c_str());

        if (connect(_sock, (struct sockaddr *)&server, sizeof(server)) != 0)
        {
            std::cerr << "socket connect error" << std::endl;
        }
        else
        {
            std::string line;
            std::string inbuffer;
            while (true)
            {
                std::cout << "mycal>>> ";
                std::getline(std::cin, line);  // 1+1
                Request req = ParseLine(line); // "1+1"
                std::string content;
                req.serialize(&content);
                std::string send_string = enLength(content);
                send(_sock, send_string.c_str(), send_string.size(), 0); // bug?? 不管

                std::string package, text;
                //  "content_len"\r\n"exitcode result"\r\n
                if (!recvPackage(_sock, inbuffer, &package))
                    continue;
                if (!deLength(package, &text))
                    continue;
                // "exitcode result"
                Response resp;
                resp.deserialize(text);
                std::cout << "exitCode: " << resp.exitcode << std::endl;
                std::cout << "result: " << resp.result << std::endl;
            }
        }
    }
    Request ParseLine(const std::string &line)
    {
        // 建议版本的状态机!
        //"1+1" "123*456" "12/0"
        int status = 0; // 0:操作符之前,1:碰到了操作符 2:操作符之后
        int i = 0;
        int cnt = line.size();
        std::string left, right;
        char op;
        while (i < cnt)
        {
            switch (status)
            {
            case 0:
            {
                if(!isdigit(line[i]))
                {
                    op = line[i];
                    status = 1;
                }
                else left.push_back(line[i++]);
            }
            break;
            case 1:
                i++;
                status = 2;
                break;
            case 2:
                right.push_back(line[i++]);
                break;
            }
        }
        std::cout << std::stoi(left)<<" " << std::stoi(right) << " " << op << std::endl;
        return Request(std::stoi(left), std::stoi(right), op);
    }
    ~CalClient()
    {
        if (_sock >= 0)
            close(_sock);
    }

private:
    int _sock;
    std::string _serverip;
    uint16_t _serverport;
};

2.4编译测试

如图所示:我们准确的实现了网络版本计算器

总结

        通过上面代码的编写,包含定制协议,序列化和反序列代码的实现,我们就能够理解协议在网络传输的重要性了,以及理解了TCP是面向字节流的概念。感谢大家的观看,希望能够帮助到大家,我们下次再见。

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

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

相关文章

【C# 基础精讲】LINQ to Objects查询

LINQ to Objects是LINQ技术在C#中的一种应用&#xff0c;它专门用于对内存中的对象集合进行查询和操作。通过使用LINQ to Objects&#xff0c;您可以使用统一的语法来查询、过滤、排序、分组等操作各种.NET对象。本文将详细介绍LINQ to Objects的基本概念、常见的操作和示例&am…

代码随想录算法训练营第四十一天 | 343. 整数拆分,96.不同的二叉搜索树

代码随想录算法训练营第四十一天 | 343. 整数拆分&#xff0c;96.不同的二叉搜索树 343. 整数拆分动态规划贪心 96.不同的二叉搜索树 343. 整数拆分 题目链接 视频讲解 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff…

使用Druid解析SQL,获取SQL中所有使用的表

一、sqlParse组成 Druid SQL Parser分三个模块&#xff1a; - Parser - AST - Visitor 1.1 Parser parser是将输入文本转换为ast&#xff08;抽象语法树&#xff09;&#xff0c;parser有包括两个部分&#xff0c;Parser和Lexer&#xff0c;其中Lexer实现词法分析&#x…

uniapp 微信小程序 绘制海报,长按图片分享,保存海报

uView UI 2.0 dcloud 插件市场地址 弹窗海报源码 <template><!-- 推荐商品弹窗 --><u-popup :show"haibaoShow" mode"center" round26rpx z-index10076 bgColortransparent safeAreaInsetTop close"goodsclose"><image …

chatglm2-6b模型在9n-triton中部署并集成至langchain实践 | 京东云技术团队

一.前言 近期&#xff0c; ChatGLM-6B 的第二代版本ChatGLM2-6B已经正式发布&#xff0c;引入了如下新特性&#xff1a; ①. 基座模型升级&#xff0c;性能更强大&#xff0c;在中文C-Eval榜单中&#xff0c;以51.7分位列第6&#xff1b; ②. 支持8K-32k的上下文&#xff1b…

excel条件格式:不同组对应位置对比标记

问题描述 下图中有两组数据&#xff0c;想要对比两个对应位置的数据并标记 条件格式 选中其中一个单元格&#xff0c;条件格式->新建规则 使用公式确定要设置格式的单元格&#xff0c;自定义需求 格式化剩余同样标准的单元格

MySQL | JDBC连接数据库

一、什么是JDBC 概念&#xff1a; JDBC&#xff0c;即Java Database Connectivity&#xff0c;java数据库连接。是一种用于执行SQL语句的Java API&#xff0c;它是Java中的数据库连接规范。这个API由 java.sql.*,javax.sql.* 包中的一些类和接口组成&#xff0c;它为Java开发…

SpringBoot复习:(56)使用@Transactional注解标记的方法的执行流程

首先&#xff0c;如果在某个类或某个方法被标记为Transactional时&#xff0c;Spring boot底层会在创建这个bean时生成代理对象&#xff08;默认使用cglib) 示例&#xff1a; 当调用studentService的addStudent方法时&#xff0c;会直接跳到CglibAopProxy类去执行intercept方…

Edge浏览器免费使用GPT3.5

搜索sider,安装Sidebar插件 注册账号即可每天免费使用30次。 Sider: ChatGPT侧边栏,GPT-4, 联网, 绘图

30.Netty源码服务端启动主要流程

highlight: arduino-light 服务端启动主要流程 •创建 selector •创建 server socket channel •初始化 server socket channel •给 server socket channel 从 boss group 中选择一个 NioEventLoop •将 server socket channel 注册到选择的 NioEventLoop 的 selector •…

机器学习深度学习——transformer(机器翻译的再实现)

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——自注意力和位置编码&#xff08;数学推导代码实现&#xff09; &#x1f4da;订阅专栏&#xff1a;机器…

Flink 数据集成服务在小红书的降本增效实践

摘要&#xff1a;本文整理自实时引擎研发工程师袁奎&#xff0c;在 Flink Forward Asia 2022 数据集成专场的分享。本篇内容主要分为四个部分&#xff1a; 小红书实时服务降本增效背景Flink 与在离线混部实践实践过程中遇到的问题及解决方案未来展望 点击查看原文视频 & 演…

[oneAPI] 手写数字识别-LSTM

[oneAPI] 手写数字识别-LSTM 手写数字识别参数与包加载数据模型训练过程结果 oneAPI 比赛&#xff1a;https://marketing.csdn.net/p/f3e44fbfe46c465f4d9d6c23e38e0517 Intel DevCloud for oneAPI&#xff1a;https://devcloud.intel.com/oneapi/get_started/aiAnalyticsToolk…

八种架构演进

日升时奋斗&#xff0c;日落时自省 目录 1、单机架构 2、应用数据分离架构 3、应用服务集群架构 4、读写分离/主从分离架构 5、冷热分离架构 6、垂直分库架构 7、微服务架构 8、容器编排架构 9、小结 1、单机架构 特征&#xff1a;应用服务和数据库服务器公用一台服务…

LVS负载均衡群-DR模式

目录 1、lvs-dr数据包流向分析 2、lvs-dr中ARP问题 3、lvs-dr特性 4、lvs-dr集群-构建 1、lvs-dr数据包流向分析 &#xff08;1&#xff09;客户端发送请求到 Director Server&#xff08;负载均衡器&#xff09;&#xff0c;请求的数据报文&#xff08;源 IP 是 CIP,目标 IP …

【校招VIP】CSS校招考点之选择器优先级

考点介绍&#xff1a; 选择器是CSS的基础&#xff0c;也是校招中的高频考点&#xff0c;特别是复合选择器的执行优先级&#xff0c;同时也是实战中样式不生效的跟踪依据。 因为选择器的种类较多&#xff0c;很难直接记忆&#xff0c;可以考虑选择一个相对值&#xff0c;比如id类…

回归预测 | MATLAB实现BO-SVM贝叶斯优化支持向量机多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现BO-SVM贝叶斯优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现BO-SVM贝叶斯优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效果一览基本介绍程序设计…

Java自学到什么程度就可以去找工作了?

引言 Java作为一门广泛应用于软件开发领域的编程语言&#xff0c;对于初学者来说&#xff0c;了解到什么程度才能开始寻找实习和入职机会是一个常见的问题。 本文将从实习和入职这两个方面&#xff0c;分点详细介绍Java学习到什么程度才能够开始进入职场。并在文章末尾给大家安…

k8s集群部署vmalert和prometheusalert实现钉钉告警

先决条件 安装以下软件包&#xff1a;git, kubectl, helm, helm-docs&#xff0c;请参阅本教程。 1、安装 helm wget https://xxx-xx.oss-cn-xxx.aliyuncs.com/helm-v3.8.1-linux-amd64.tar.gz tar xvzf helm-v3.8.1-linux-amd64.tar.gz mv linux-amd64/helm /usr/local/bin…

网络编程(TCP和UDP的基础模型)

一、TCP基础模型&#xff1a; tcp Ser&#xff1a; #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> #include <head.h>#define PORT 88…