• [技术干货] linux——自定义协议 -转载
    什么是自定义协议?自定义协议 是指根据应用程序的需求,开发者定义的一种数据格式或传输规则。在网络通信中,协议用于描述双方通信时的数据结构、数据格式、传输顺序等。定义:自定义协议是开发者为特定应用或场景设计的协议,用来满足应用之间的数据交换需求。与标准协议(如HTTP、TCP/IP)不同,自定义协议不依赖于任何标准,而是根据具体需求自行设计。用途:例如,在一个客户端与服务器之间进行通信时,客户端发送特定格式的数据请求,服务器根据预定义的协议格式解析并返回响应数据。自定义协议的设计通常需要考虑数据的序列化、反序列化、传输效率和安全性。示例:比如,设计一个简单的数学计算协议,客户端发送请求(比如两个数字和一个运算符),服务器进行处理,返回结果。自定义协议在此场景中的格式可以是:数字1 运算符 数字2。// 请求结构struct Request {    int num1;    int num2;    char op; // 操作符(如 +,-,*,/)}; // 响应结构struct Response {    int result;    int code; // 0: 成功,1: 错误};AI写代码cpp运行通信双方根据这个格式进行数据的传递和解析,即构成了自定义协议.自定义协议 是属于 应用层 的概念。应用层 是 OSI 模型中的最上层,也是计算机网络协议栈中最接近用户的层。它定义了应用程序之间如何交换数据,涵盖了各种应用协议,如 HTTP、FTP、SMTP,以及你提到的自定义协议。自定义协议是应用层的一部分,通常由应用程序开发者定义,用于特定应用的需求。自定义协议可以定义数据格式、通信规则、如何打包和解包数据等。它通常使用其他协议(如 TCP/IP、UDP)来实现数据的传输。说到自定义协议,我们还要谈到序列化和反序列化这两个概念。序列化与反序列化简介序列化 和 反序列化 是计算机程序在处理数据传输和存储时常用的两个概念。它们用于将数据从一种格式转化为另一种格式,通常在网络通信、文件存储和跨平台数据交换中广泛应用。1. 序列化(Serialization)序列化 是将程序中的对象、数据结构或内存中的数据转换为可存储或传输的格式的过程。常见的格式有 JSON、XML、二进制数据流等。目的:将内存中的复杂数据结构转换为字节流或其他格式,使其能够被存储在文件、数据库中或通过网络传输。应用场景:网络通信:将对象或数据结构转化为字节流或字符串,在客户端与服务器之间传输。数据持久化:将对象序列化为文件或数据库记录,便于后续读取。跨平台通信:将数据转换为跨平台通用格式,支持不同系统之间的通信。2. 反序列化(Deserialization)反序列化 是将序列化后的数据(如字节流或字符串)转换回程序能够理解的对象、数据结构或实体的过程。目的:将传输或存储的字节流转换回原始数据结构,使程序可以对其进行操作。应用场景:网络通信:将接收到的数据字节流还原为原始对象,供程序进一步处理。数据恢复:从存储中恢复对象数据,供程序重新使用。将数据转化为字节流而不是直接传输结构体本身,主要是出于以下几个原因:跨平台兼容性:不同平台的内存布局差异会导致直接传输结构体时的兼容性问题。灵活性:序列化为字节流或标准化格式,可以提高程序的灵活性、可扩展性,并支持版本控制。网络传输效率:通过序列化,你可以优化数据的大小,减少带宽浪费,并更容易适应不同的网络协议。调试与可读性:序列化格式(如 JSON)更加易于调试和查看,对于错误处理和日志记录也更为方便。安全性:序列化后的数据更容易进行验证,保证数据的完整性和安全性。TCP的全双工通信总结全双工通信 是指通信双方可以同时进行数据的发送和接收。在 TCP(传输控制协议)中,全双工通信 是其核心特性之一,支持客户端和服务器之间在同一个连接上同时发送和接收数据。 TCP全双工通信的关键要点:独立的发送与接收缓冲区:每一端(客户端和服务器)都有两个独立的缓冲区:一个用于发送数据(发送缓冲区),另一个用于接收数据(接收缓冲区)。发送和接收操作互不干扰,可以同时进行。同时发送和接收数据:在一个TCP连接上,客户端和服务器可以同时发送和接收数据。例如,客户端可以在发送请求的同时接收服务器的响应,服务器也可以在接收客户端请求时,立即返回数据。数据流双向传输:TCP连接中的数据流是双向的。每个方向上都可以独立进行数据传输,而不会相互阻塞。基于缓冲区的管理:发送数据时,数据从应用层的缓冲区拷贝到 发送缓冲区,然后通过网络传输给接收方。接收方的数据从网络传输到接收缓冲区,应用层可以从接收缓冲区读取数据。这两个缓冲区(发送缓冲区和接收缓冲区)的独立性保证了全双工通信的流畅性。流量控制与拥塞控制:TCP采用 流量控制 和 拥塞控制,确保在通信过程中,双方的发送速率匹配,避免数据丢失或网络拥塞。阻塞I/O操作:TCP的I/O函数(如 read 和 write)通常是阻塞的,确保数据的同步处理。发送和接收操作在每个缓冲区内独立进行,但为了避免数据冲突或丢失,它们通过阻塞机制进行同步。网络版计算器例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端.约定方案一:客户端发送一个形如"1+1"的字符串;这个字符串中有两个操作数, 都是整形;两个数字之间会有一个字符是运算符, 运算符只能是 + ;数字和运算符之间没有空格;约定方案二:定义结构体来表示我们需要交互的信息;发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;这个过程叫做 "序列化" 和“反序列化”。1.Socket.hpp#pragma once #include <iostream>#include <string>#include <stdint.h>#include <unistd.h>#include <sys/types.h> /* See NOTES */#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <string.h>#include "log.hpp" const int backlog =5;extern Log lg; enum{    SOCKERR = 1,    BINDERR = 2,    LISTENERR}; class Sock{public:    Sock()    {    }    ~Sock()    {    } public:    void Socket()    {        sockfd_ = socket(AF_INET, SOCK_STREAM, 0);        if (sockfd_ < 0)        {            lg("fatal", "socket create fail errno:%d strerrno:%s", errno, strerror(errno));            exit(1);        }    }     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;         socklen_t len = sizeof(local);        if (bind(sockfd_, (struct sockaddr *)&local, len) < 0)        {            lg("fatal", "bind fail errno:%d,strerror:%s\n", errno, strerror(errno));            exit(BINDERR);        }    }     void Listen() // 变为监听窗口,错了就别玩了    {        if (listen(sockfd_, backlog) < 0)        {            lg("fatal", "Listen fail errno:%d,strerror:%s\n", errno, strerror(errno));            exit(LISTENERR);        }    }     int Accpect(string*ip,uint16_t* port)//输出型参数,谁链接的我,对方的端口,ip    {         struct sockaddr_in temp;        socklen_t len=sizeof(temp);                int newfd=accept(sockfd_,(struct sockaddr*)&temp,&len);        if(newfd<0)        {            lg("fatal", "accpect fail errno:%d,strerror:%s\n", errno, strerror(errno));            return -1;        }                *port=ntohs(temp.sin_port);                string ipstr=inet_ntoa(temp.sin_addr);        *ip=ipstr;         return newfd;    }     bool Connect(string& ip,uint16_t port)//服务器的端口,ip    {        struct sockaddr_in server;        bzero(&server,0);        server.sin_family=AF_INET;        server.sin_port=htons(port);        server.sin_addr.s_addr=inet_addr(ip.c_str());         int ret=connect(sockfd_,(struct sockaddr*)&server,sizeof(server));        if(ret<0)        {            lg("info", "Connect fail errno:%d,strerror:%s\n", errno, strerror(errno));            return false;        }        return true;    }     void Close()    {        close(sockfd_);    }public:    int sockfd_;};2.Protocal.hpp#pragma once #include <iostream>#include <string>#include <jsoncpp/json/json.h> // #define Myself 1   //编译的时候可以 用-DMyself using namespace std; const string blank_sep = " ";const string protocal_sep = "\n"; //"x op y" ->//"len"/n"x op y"/nstd::string Encode(string &content){    int len = content.size();    string package = to_string(len);    package += protocal_sep;    package += content;    package += protocal_sep;    return package;} //"len"/n"x op y"/n ->  解析可能错误//"x op y"bool Decode(string &package, string *content){    int pos = package.find(protocal_sep);    if (pos == string::npos)        return false;     string len_str = package.substr(0, pos);    int len = stoi(len_str);     int to_len = len_str.size() + 2 + len;    if (package.size() < to_len)        return false;     *content = package.substr(pos + 1, len); // 位置 加 长度     package.erase(0, to_len); // 解析的这一段,从缓冲区里去掉    return true;} class Request{public:    Request(int x, int y, char op) : x_(x), y_(y), op_(op)    {    }     Request()    {    }     ~Request()    {    } public:    bool Serialize(string *content) // 序列化为 字节流    {#ifdef Myself        string s = to_string(x_);        s += blank_sep;        s += op_;        s += blank_sep;        s += to_string(y_);         *content = s;        return true;#else        Json::Value root;         root["x"] = x_;        root["y"] = y_;        root["op"] = op_;         Json::FastWriter w;        *content = w.write(root);        return true;#endif    }     bool Deserialize(string &in) // 反序列化 将字节流解析    {#ifdef Myself        int l = in.find(blank_sep);        if (l == string::npos)            return false;        string x = in.substr(0, l); // 左闭,右开         int r = in.rfind(blank_sep);        if (r == string::npos)            return false;        string y = in.substr(r + 1);         if (l + 2 != r)            return false;         x_ = stoi(x);        y_ = stoi(y);        op_ = in[l + 1];        return true;#else        Json::Reader R;        Json::Value root;         bool ret = R.parse(in.c_str(), root);        if (!ret)            return false;        x_ = root["x"].asInt();        y_ = root["y"].asInt();        op_ = root["op"].asInt();        return true; #endif    }     void Printf()    {        cout << "任务请求" << x_ << op_ << y_ << "=?" << endl;    } public:    int x_;    int y_;    char op_;}; class Respone{public:    Respone(int res, int c) : result_(res), code_(c)    {    }     Respone()    {    }    ~Respone()    {    } public:    bool Serialize(string *out) // 序列化 形成字节流    {#ifdef Myself        string s = to_string(result_);        s += blank_sep;        s += to_string(code_);        *out = s;        return true;#else        Json::Value root;         root["result"] = result_;        root["code"] = code_;         Json::FastWriter w;        *out = w.write(root);        return true;#endif    }     bool Deserialize(string &in) // 反序列化 字节流解析    {#ifdef Myself        int pos = in.find(blank_sep);        if (pos == string::npos)            return false;        string x = in.substr(0, pos);        int result = stoi(x);         string y = in.substr(pos + 1);        int code = stoi(y);         result_ = result;        code_ = code;        return true;#else        Json::Reader R;        Json::Value root;         bool ret = R.parse(in.c_str(), root);        if (!ret)            return false;        result_=root["result"].asInt();        code_=root["code"].asInt();        return true;#endif    }     void Printf()    {        cout << "result " << result_ << "  code " << code_ << endl;    } public:    int result_;    int code_;};3.Server.cal#pragma once #include <iostream>#include <string>#include <jsoncpp/json/json.h> // #define Myself 1   //编译的时候可以 用-DMyself using namespace std; const string blank_sep = " ";const string protocal_sep = "\n"; //"x op y" ->//"len"/n"x op y"/nstd::string Encode(string &content){    int len = content.size();    string package = to_string(len);    package += protocal_sep;    package += content;    package += protocal_sep;    return package;} //"len"/n"x op y"/n ->  解析可能错误//"x op y"bool Decode(string &package, string *content){    int pos = package.find(protocal_sep);    if (pos == string::npos)        return false;     string len_str = package.substr(0, pos);    int len = stoi(len_str);     int to_len = len_str.size() + 2 + len;    if (package.size() < to_len)        return false;     *content = package.substr(pos + 1, len); // 位置 加 长度     package.erase(0, to_len); // 解析的这一段,从缓冲区里去掉    return true;} class Request{public:    Request(int x, int y, char op) : x_(x), y_(y), op_(op)    {    }     Request()    {    }     ~Request()    {    } public:    bool Serialize(string *content) // 序列化为 字节流    {#ifdef Myself        string s = to_string(x_);        s += blank_sep;        s += op_;        s += blank_sep;        s += to_string(y_);         *content = s;        return true;#else        Json::Value root;         root["x"] = x_;        root["y"] = y_;        root["op"] = op_;         Json::FastWriter w;        *content = w.write(root);        return true;#endif    }     bool Deserialize(string &in) // 反序列化 将字节流解析    {#ifdef Myself        int l = in.find(blank_sep);        if (l == string::npos)            return false;        string x = in.substr(0, l); // 左闭,右开         int r = in.rfind(blank_sep);        if (r == string::npos)            return false;        string y = in.substr(r + 1);         if (l + 2 != r)            return false;         x_ = stoi(x);        y_ = stoi(y);        op_ = in[l + 1];        return true;#else        Json::Reader R;        Json::Value root;         bool ret = R.parse(in.c_str(), root);        if (!ret)            return false;        x_ = root["x"].asInt();        y_ = root["y"].asInt();        op_ = root["op"].asInt();        return true; #endif    }     void Printf()    {        cout << "任务请求" << x_ << op_ << y_ << "=?" << endl;    } public:    int x_;    int y_;    char op_;}; class Respone{public:    Respone(int res, int c) : result_(res), code_(c)    {    }     Respone()    {    }    ~Respone()    {    } public:    bool Serialize(string *out) // 序列化 形成字节流    {#ifdef Myself        string s = to_string(result_);        s += blank_sep;        s += to_string(code_);        *out = s;        return true;#else        Json::Value root;         root["result"] = result_;        root["code"] = code_;         Json::FastWriter w;        *out = w.write(root);        return true;#endif    }     bool Deserialize(string &in) // 反序列化 字节流解析    {#ifdef Myself        int pos = in.find(blank_sep);        if (pos == string::npos)            return false;        string x = in.substr(0, pos);        int result = stoi(x);         string y = in.substr(pos + 1);        int code = stoi(y);         result_ = result;        code_ = code;        return true;#else        Json::Reader R;        Json::Value root;         bool ret = R.parse(in.c_str(), root);        if (!ret)            return false;        result_=root["result"].asInt();        code_=root["code"].asInt();        return true;#endif    }     void Printf()    {        cout << "result " << result_ << "  code " << code_ << endl;    } public:    int result_;    int code_;};4.Tcpserver.hpp#pragma once#include <functional>#include <signal.h>#include "log.hpp"#include "Socket.hpp"#include "ServerCal.hpp"#include "Protocal.hpp" using func_t=function<string(string&)>;extern Log lg; class Server{public:    Server(uint16_t port,func_t calback):port_(port) ,calback_(calback)    {}     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)        {            string clientip;            uint16_t clientport;             int sockfd=listensock_.Accpect(&clientip,&clientport);            if(sockfd<0)            {                lg("fatal","accpect fail");                break;            }                        lg("info","accpect success");            if(fork()==0)            {                listensock_.Close();                string inbuffer_stream;                while(true)                {                    char buffer[1024];                    int 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)                        {                            string ret=calback_(inbuffer_stream);                            if(ret.empty())                            break;                             lg("Dedug","debug:\n%s",ret.c_str());                            lg("Dedug","debug:\n%s",inbuffer_stream.c_str());                             write(sockfd,ret.c_str(),ret.size());                        }                    }                    else                     {                        cout<<"错误了"<<endl;                        break;                    }                }                exit(0);            }            close(sockfd);        }    } private:    Sock listensock_;    uint16_t port_;    func_t calback_;};5.Tcpserver.cpp#include "Tcpserver.hpp"#include "ServerCal.hpp"  void Usage(string prco){    cout<<prco<<" [1024++]"<<endl;} int main(int argc,char*argv[]){    if(argc!=2)    {        Usage(argv[0]);        exit(0);    }     uint16_t port=stoi(argv[1]);    ServerCal calculator;     Server* tsrv=new Server(port,bind(&ServerCal::Calculator,&calculator,placeholders::_1));//第一个是this指针    //不用bind的话可以创建一个ServerCal的对象。    tsrv->Init();    tsrv->Start();    return 0;}的bind 是 C++11 引入的一个函数模板,用于创建一个新的可调用对象,将函数的某些参数“绑定”到具体值,然后返回一个可以稍后调用的函数。基本用法:std::bind(function, arg1, arg2, ..., argN);function:要绑定的函数。arg1, arg2, ...:绑定的参数,可以是具体值或占位符(std::placeholders)。#include <iostream>#include <functional> int add(int x, int y) {    return x + y;} int main() {    // 绑定第一个参数为 5,第二个参数用占位符    auto add5 = std::bind(add, 5, std::placeholders::_1);     std::cout << add5(3) << std::endl;  // 输出 8 (5 + 3)    return 0;}简化函数调用:std::bind 可以将函数和参数绑定,生成新函数,便于后续调用。灵活性:支持部分绑定函数参数,允许在调用时提供剩余的参数。与 lambda 比较:// 使用 std::bindauto add5 = std::bind(add, 5, std::placeholders::_1); // 使用 lambdaauto add5 = [](int x) { return add(5, x); };————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/2401_83603768/article/details/151154240
  • [技术干货] Linux网络:网络基础-转载
    1. 协议分层问题一:为什么一般的软件要分层?1.层与层之间的耦合度是降低的,更重要的是一但分层,就相当于一个非常大的项目被肢解成了多个子项目,这样的话,在后期的维护上成本会非常低,所以第一点就是为了方便维护2.凡是划分到同一层的内部,其中的代码和逻辑必须是强相关的—高内聚(层内部),低耦合(层与层之间)3.问题是层状的!!!为了解决相应的问题,所以设计的时候也是按分层来设计的 在现实生活中,我们打电话给别人,看似是人与人之间直接进行交流,但是在实际上,是我们载荷电话沟通,然后电话将信息传输到另一台电话上,然后才让别人给听到,但是在逻辑上,人与人是在同一层的,所以我们会认为是人与人在沟通,同层内部高内聚,层与层之间低耦合。这样的好处就在于,某一层出问题,只需要处理某一层的问题就行了,其它层不用变,即使某一层的协议发生了变化,比如,如图的第一层的语言变成了英语协议,但是其下面的电话协议根本不用变,也无法被影响,依然能够正常交流,同理如果其电话机协议变成了无线电协议,其上层的汉语协议也无需变化,只需要处理无线电这一层的协议就行!!!1.1 OSI七层模型OSI(Open System Interconnection,开放系统互连)七层网络模型称为开放式系统互联参考模型,是一个逻辑上的定义和规范;把网络从逻辑上分为了7层. 每一层都有相关、相对应的物理设备,比如路由器,交换机;OSI 七层模型是一种框架性的设计方法,其最主要的功能使就是帮助不同类型的主机实现数据传输;它的最大优点是将服务、接口和协议这三个概念明确地区分开来,概念清楚,理论也比较完整. 通过七个层次化的结构模型使不同的系统不同的网络之间实现可靠的通讯;但是, 它既复杂又不实用; 所以我们按照TCP/IP四层模型来讲解.1.2 TCP/IP五层(或四层)模型TCP/IP是一组协议的代名词,它还包括许多协议,组成了TCP/IP协议簇.TCP/IP通讯协议采用了5层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求.物理层: 负责光/电信号的传递方式. 比如现在以太网通用的网线(双绞 线)、早期以太网采用的的同轴电缆(现在主要用于有线电视)、光纤, 现在的wifi无线网使用电磁波等都属于物理层的概念。物理层的能力决定了最大传输速率、传输距离、抗干扰性等. 集线器(Hub)工作在物理层.数据链路层: 负责设备之间的数据帧的传送和识别. 例如网卡设备的驱动、帧同步(就是说从网线上检测到什么信号算作新帧的开始)、冲突检测(如果检测到冲突就自动重发)、数据差错校验等工作. 有以太网、令牌环网, 无线LAN等标准. 交换机(Switch)工作在数据链路层.网络层: 负责地址管理和路由选择. 例如在IP协议中, 通过IP地址来标识一台主机, 并通过路由表的方式规划出两台主机之间的数据传输的线路(路由). 路由器(Router)工作在网路层.传输层: 负责两台主机之间的数据传输. 如传输控制协议 (TCP), 能够确保数据可靠的从源主机发送到目标主机.应用层: 负责应用程序间沟通,如简单电子邮件传输(SMTP)、文件传输协议(FTP)、网络远程访问协议(Telnet)等. 我们的网络编程主要就是针对应用层2. 操作系统与网络技术栈问题一:网络协议栈和我们之前学的操作系统有什么关系?数据链路层是软件,是在各种软件内部驱动实现的,其中的传输层和网络层是要在Linux内核中实现的在我们日常的生活中,我们也会刷视频来获取信息,这些信息是怎么来的?就是通过物理层中的网卡获取的!!!所以网络通信的本质也是在访问硬件!!!如果用户想要获取到视频资源,就需要去访问硬件,但是用户并没有资格去直接访问硬件,因为操作系统是硬件的管理者,操作系统不允许用户来访问底层硬件,操作系统并不信任用户,所以用户想要访问到硬件资源就必须要贯穿操作系统!!!用户层、传输层、网络层、数据链路层和物理层,从上往下,依次贯穿!!!网络通信的本质:就是贯穿协议栈的过程此时问题又来了,如果用户层要访问硬件资源,不能直接访问,要依次贯穿网络技术栈,但是操作系统可能让用户去访问、去贯穿网络技术栈吗?这显然不可能,那用户又是如何获取到硬件资源的呢?— ==系统调用接口!!!==通过系统调用接口,用户就可以获取到硬件资源,既然用户无法直接获取,但能让操作系统去获取,并给用户层提供一个接口,让用户层能拿到硬件资源。所以就有了利用系统调用来写出各种协议来供用户层使用!!! 大部分操作系统的内容和系统调用接口都是不一样的,但是网络协议栈是一样的!!!这是因为想要用使用网络,就必须按照网络制定的协议栈,又因为网络通信是要贯穿网络协议栈的(向上贯穿或者向下贯穿),主机1有用户层,主机2也一定会有用户层,同理任意两台主机的网络协议栈一定是是一样的!!!所以同层协议在逻辑上,就是在和对方的同层直接沟通!!!3.网络传输基本流程网络协议栈的层状结构中,由于每一层都有协议,所以每一层都要添加报头(报文 == 报头 + 有效载荷),才会将信息送到下一层。数据的自顶向下的封装的过程:1. 应用层定的协议,要在发送的信息前加一个报头(里面会有版本信息…),消息往下传递给传输层。(将要传输的内容叫做有效载荷)2. 传输层定的协议,再加入一个报头(里面会有信息的序号、源端口号和目的端口号…),消息往下传递给网络层。3. 网络层定的协议,再加入一个报头(里面会有该信息是谁发的(主机),要发生到哪里(主机)…),消息再往下传递给链路层。4. 链路层定的协议,再加入一个报头(里面的信息是从哪里发的(网卡),发送给谁的(网卡)…) 接着,报文就会交给网卡,网卡就会通过以太网,将报文发送给对方的主机的网卡上问题一:为什么远端机器拿到这个数据(报文)的时候一定是网卡先拿到数据?那是因为计算机里面只有一个网络通信的设备就叫做网卡!网卡拿到这个数据(报文)后,肯定要将数据交给操作系统,从硬件外设交到内存里。问题二:为什么网卡拿到数据后一定要将数据(报文)从硬件设备交到内存里?因为这是冯诺依曼体系规定!冯诺依曼体系规定网卡中的数据一定要从硬件设备交给内存里面当对方拿到报头后,是有办法区分报头和有效载荷的,这时候,只需要将报头取出来,就可以将剩下的有效载荷送给上层,同理,由于同层协议既然能给有效载荷添加报头,所以也能获取报头,每一层拿到报文后,都会将报头和有效载荷分离开来,层层传递,直到到了用户层,将信息传递成功。通信的过程:本质就是在不断的封装和解包的过程!除了上述的知识,不同层其实不止一种协议,从上往下其实可以看成是一个倒着的多叉树结构,每一层的协议都要考虑到底是传输给上层的哪种协议封装和解包中的问题(大部分协议的共性,未来学习具体协议的时候,都是这两个问题!!!)1. 几乎任何层的协议,都要提供一种能力,将报头和有效载荷分离的能力2. 几乎任何层的协议,都要在报头中提供,决定将自己的有效载荷交付给上层的哪一个协议只有知道了两个问题,再面对封装和解包的问题就不会困惑,因为这就是大部分协议的共性总结:本文浅谈了OSI七层网络模型和TCP/IP网络模型,主要对操作系统与网络技术栈的关系进行的探讨,并且对网络传输的基本流程进行了深刻的讲解————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/zby2791830200/article/details/151764387
  • [技术干货] Linux 信号-转载
    一. 信号基本概念信号是一种用于进程间通信或系统通知进程发生特定事件的机制。它可以被视为操作系统向进程发送的“消息”,用于告知进程发生了某种异常等需要处理的事情。信号的生命周期分为三个阶段 信号产生 信号保存 信号处理。接下来我将按照这三个阶段进行一一讲解。首先我们来粗略的了解一下信号的基本概念。信号的产生方式有5种,可以由操作系统,其他进程,自身进程,硬件等不同的发送方。当信号产生之后,并不会立即作出处理,内核会先将其暂存,存入一张未决信号表当中,此时要区分对于该信号是否阻塞,待进程有空后再进行处理。当进程成功接收到信号后,此时有三种处理方式,忽略处理,默认动作,信号捕捉。  我们使用 kill -l 指令可以查看到所有的信号,我们把前三十一个称为普通信号,它们都是大写的宏,旁边的数字就是它们的对应值。 使用man 7 signal 可以查看到里边大多数信号的默认动作,以及使用的默认效果。 二. 信号的产生首先我们来认识一个函数 signalsignal:Linux 操作系统用于处理信号的一个系统调用。它允许用户指定一个函数,当接收到特定信号时进行函数调用。这样我们便可以对信号进行自定义处理。 signum:需要处理的信号类型,可以是宏定义的,也可以是对应的数字。handler:函数指针类型,用于接收需要的信号。参数要包含你一个 int 来接收信号1. 通过终端按键产生信号终端按键可以通过终端驱动程序触发特定信号,如我们熟知的 Ctrl + c ,下面我们介绍几个信号同时演示如何通过终端按键产生信号。1-1 操作演示Ctrl + c (SIGINT)会产生 2 号信号,默认会进行终止进程的处理,终止的是前台进程(死循环)。Ctrl + \ (SIGQUIT)会产生3号信号,除了终止进程,还会生成 core dump 文件,主要用于调试使用。9号信号 (SIGKILL)强制终止进程,该信号不可被捕捉,阻塞。19号信号(SIGSTOP)用于暂停进程执行信号,它相当于“冻结”进程而非终止,具有强制性。下面我们就以 2 号信号作为演示:#include <iostream>#include <unistd.h>#include <signal.h>using namespace std; void handler(int signal){    cout<<"我是信号:"<<signal<<endl;} int main(){    cout<<"我是进程:"<<getpid()<<endl;    signal(2,handler);    while(true)    {        cout<<"我是进程:"<<getpid()<<"我正在等待信号"<<endl;        sleep(2);    }    return 0;}AI写代码cpp运行上述代码,将2号信号进行捕捉后特殊处理,我们来看效果。  当我们Ctrl + c 时,进程不会终止,而是执行了我的函数,我们只有使用 9 号信号才能对其进行终止。同理,其他按键也与其类似。1-2 理解OS如何得知键盘数据键盘和其他外部设备都属于硬件,硬件向中断控制器中发送信号,发起中断,这也就是所谓的硬件中断,控制器为其分配一个中断号,确定了唯一标识源。紧接着通知CPU,CPU得知中断获取中断号。CPU 根据中断号,结合中断向量表,执行中断处理方法。 通过上面的过程,我们可以感受到,硬件中断行为和软件中断(信号处理)有着异曲同工之处。其实,信号就是从软件角度,模拟硬件中断的行为。只不过,硬件中断是发送给CPU,软件中断发送给进程。2. 调用系统命令向进程发送信号我们可以通过 kill -信号 进程id 的方法,来对进程发送信号。2-1 操作演示我们还是使用先前的代码#include <iostream>#include <unistd.h>#include <signal.h>using namespace std; void handler(int signal){    cout<<"我是信号:"<<signal<<endl;} int main(){    cout<<"我是进程:"<<getpid()<<endl;    signal(2,handler);    while(true)    {        cout<<"我是进程:"<<getpid()<<"我正在等待信号"<<endl;        sleep(2);    }    return 0;}AI写代码cpp运行我们启动进程,当我们使用 kill 对进程发送2号进程时,屏幕打印出信息,执行了函数。  3. 使用函数产生信号kill 命令是调用 kill 函数实现的,kill 函数可以给一个指定的进程发送指定的信号。 参数:pid:发送信号进程的pidsig:发送的信号返回值:成功返回0,失败返回-1raise:给当前进程发送指定的信号 参数:sig:发送的信号返回值:成功返回0,失败返回 non-zeroabort:使当前进程接收到信号而异常终止 3-1 操作演示首先我们写一个系统调用 kill 函数的程序#include <iostream>#include <unistd.h>#include <signal.h>#include <sys/types.h>using namespace std; int main(int argc,char* argv[]){    if(argc != 3)    {        cerr<<"Usage:"<<argv[0]<<"-signalnumber pid"<<endl;    }     int number = stoi(argv[1]+1);    pid_t id = stoi(argv[2]);     int n = kill(id,number);        return n;} 接着再来一个测试代码#include <iostream>#include <unistd.h>#include <signal.h>using namespace std; int main(){    while(true)    {        cout<<"我是进程:"<<getpid()<<endl;        sleep(3);    }    return 0;}我们来看一下运行结果 当我们调用2号信号后,测试用例终止。4. 软件条件产生信号由软件逻辑或用户行为触发的信号。下面我们来介绍一下一些软件条件下产生的信号。13号信号(SIGPIPE),用于处理管道中的写入错误,例如当读端进程关闭,写端进程仍向管道写入,就会触发 SIGPIPE 。14号信号(SIGALRM),用于通知进程的预设时间已到。我们通常用 alarm 函数来触发该信号。alarm:设置一个定时器,second秒后触发信号 参数:seconds:秒数返回值:若先前设置了定时器,返回剩余秒数,否则返回0。4-1 操作演示下面我们来测试一下闹钟我们来看下面的代码#include <iostream>#include <signal.h>#include <cstdlib>#include <unistd.h>#include <sys/types.h>using namespace std; void handler(int signum){    cout << "接收到信号:" << signum << endl;    exit(1);} int main(){    int cnt = 1;    alarm(1);    signal(14, handler);    while(true)    {        cout<<cnt<<endl;        cnt++;    }    return 0;}运行结果: 当闹钟响起时接收到14号信号,最终返回。4-2 理解闹钟在底层,alarm函数与内核定时器机制相关联,当alarm被调用之后,内核会生成一个软定时器。当定时器到期后,内核会发送SIGALRM信号。当进程中多出用到alarm时候,OS 会借助最小堆,判断要先向谁发送信号。5. 硬件异常产生信号硬件异常被硬件以某种方式通过硬件检测通知内核,内核向进程发送适当信号。例如当前进程执行了除0的指令,CPU 运算单元产生异常,内核会将这个异常解释为 SIGFPE 8号信号发送给进程。或者进程访问了野指针,MMU 会产生异常,会向进程发送 SIGSEGV 11号信号。关于野指针问题这个问题与 CPU 内部的MMU,CR2 ,CR3有关联。MMU和页表用来管理虚拟内存从虚拟地址到物理地址的转换。CR3用于切换不同进程的页表,CR2用于存储当前页表的错误虚拟地址。当MMU无法将虚拟地址与物理地址进行关联时,CR2会存储该虚拟地址,并产生异常信号,向当前进程发送 11号信号。6.term 与 coreterm 和 core 是信号默认动作的表示。1. term 是terminate 的缩写,表示默认终止进程。2. core 动作在终止进程的同时,还会生成一个core dump文件,这个文件用于调试。当进程退出时,core dump为0表示没有异常退出,如果是1表示异常退出。 综上所述,产生信号的方法有5种,但本质其实只有三种,硬件触发如键盘或硬件异常等,软件触发如闹钟管道,用户对系统调用kill命令等。所有的信号产生最终都是由 OS 进行执行,产生的信号不会立即作出处理,而是在合适的时候进行。所以说,这些暂时没有执行的信号就会进行保存,那么接下来我们就来理解一下信号的保存。三. 信号的保存操作系统用三张表来对信号进行管理。Block表(阻塞表):一个位图,用于表示哪些信号被阻塞,阻塞为1,未阻塞为0Pending表(未决表):未决即信号已经产生,但是暂时未被进程处理。当信号处于未决时,位图表为1。Handler表(处理函数表):一个函数指针数组,用于表示信号接收后的处理动作。可以是默认函数,忽略函数,自定义函数。有阻塞不一定有未决,但有未决一定是因为阻塞。进程对该信号进行阻塞,若用户一直不传递该信号,该信号就不会进入未决状态。若该信号进入了未决状态,说明该信号已经被阻塞了,正在等待进程处理。操作系统通过两张位图 + 一张函数指针表 ,就完成了让进程识别不同的信号。 1. 信号集 sigset_tsigset_t 是一种数据类型,用于接收阻塞和未决标志专门设置的数据类型。可以用来表示该数据的有效或者无效。2. 信号操作函数sigset_t 存储在内部,从使用者的角度我们不必关心,我们只需要能调用它的封装函数来对 sigset_t 进行操作。#include <signal.h> int sigemptyset(sigset_t *set);int sigfillset(sigset_t *set);int sigaddset(sigset_t *set, int signo);int sigdelset(sigset_t *set, int signo);int sigismember(const sigset_t *set, int signo);参数:set:指向信号集signo:某种信号返回值:成功返回0,失败返回-1sigemptyset:将指向的信号集所有信号对应的bit清空。sigfillset:将指向的信号集所有的信号bit置为1.sigaddset:向信号集某一个信号的bit置为1.sigdelset:向信号集某一个信号的bit置为0.sigimember:判断一个信号集中是否包含某种信号,包含返回1,不包含返回-1sigprocmask:读取或更改进程的信号屏蔽字#include <signal.h>int sigpromask(int how, const sigset_t *set, sigset_t *oset);参数:how:如何修改当前的信息掩码{SIG_BLOCK:将信号集中的所有信号进行阻塞SIG_UNBLOCK:将信号集中的所有信号解除阻塞SIG_SETMASK:将信号集更新为set,并且返回oldset}set:新的更改过的信号集old:更改前的信号集。返回值:成功返回0,失败返回-1.sigpending:读取当前进程的未决信号集,通过set传出#include <signal.h>int sigpending(sigset_t *set);AI写代码cpp运行参数:set:接收的信号集返回值:成功返回0,失败返回-13. 操作演示我们设计一个程序,对2号信号进行阻塞,然后不断打印未决表观察,在10次轮询后,解除对2号信号阻塞,再对未决表进行观察。#include <iostream>#include <signal.h>#include <unistd.h>#include <cstdio>#include <sys/types.h>#include <sys/wait.h>using namespace std; //先屏蔽2号信号,轮询10次解除屏蔽 void PrintPending(sigset_t &pending){    cout<<"["<<getpid()<<"]:";    for(int i = 31;i>=1;i--)    {        if(sigismember(&pending,i))        {            cout<<"1";        }        else        {            cout<<"0";        }    }    cout<<endl;} void headler(int signo)//打印信号表{    cout<<"接收到信号:"<<signo<<endl;    cout<<"----------------------"<<endl;    sigset_t pending;    sigpending(&pending);    PrintPending(pending);    cout<<"----------------------"<<endl;} int main(){    //初始化    sigset_t new_set,old_set;    signal(2,headler);    sigemptyset(&new_set);    sigemptyset(&old_set);    sigaddset(&new_set,2);        //设置阻塞表    sigprocmask(SIG_BLOCK,&new_set,&old_set);     int cnt = 10;     while(true)    {        sigset_t pending;        sigpending(&pending);        PrintPending(pending);         if(cnt == 0)        {            sigprocmask(SIG_SETMASK,&old_set,&new_set);            cout<<"解除对2号信号屏蔽"<<endl;        }        cnt--;        sleep(1);    }    return 0;}AI写代码cpp运行运行结果: 我们发现当我们发送2号信号时,未决表接收到信号,但是因为阻塞所以进程接收不到。当解除信号屏蔽后,先前发送的2号信号就立马被接受到了,此时再发送信号就不会出现阻塞。四. 信号的处理当我们进程接收到信号后,有三种处理方式1. 忽略信号:忽略该信号,不对其做任何处理2. 捕捉信号:进程可以注册一个对于该信号的一个特殊捕捉函数,当该信号触发时,进程接收信号并做出相应处理。3. 默认处理:若进程没有对该信号进行注册,系统会按照默认的方法进行处理。signal(2, handler);signal(2, SIG_IGN);signal(2, SIG_DFL);AI写代码cpp运行handler:自定义函数SIG_IGN:忽略信号SIG_DFL:默认处理我们从上文得知,信号并不会被立即处理,而是会先储存等待合适的时机再进行处理。那合适的时机是什么时候呢?先说结论,是从内核态切换为用户态的时候。简单讲,当我们执行自己的代码,访问自己的数据就是用户态;进入系统调用,以操作系统的身法运行就是内核态。当程序出现中断,异常或者系统调用的时候,我们会从用户态转为内核态。在内核态中以操作系统的身份完成工作后,将会从内核态切换回用户态,此时操作系统会对未决的信号进行检测处理(通过三张表判断),因为信号的三张表存储在PCB(进程控制块)中,需要调用三张表就必须处在内核态。若信号执行的是默认或者忽略,此时已经完成了工作。若信号执行的是自定义函数,此时返回用户态之后会处理信号函数,并且返回sys_sigreturn()使其进入内核态。最后在内核态和用户态切换的间隙处理函数,最后返回到主流程被中断的地方,这就完成了信号的捕捉流程。也就是说,如果信号执行的是自定义函数,那么就需要4次的用户态和内核态的切换 简化一下: 我们再来认识一个函数sigaction:自定义信号处理方式#include <signal.h>int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); struct sigaction {    void (*sa_handler)(int);  // 信号处理函数(类似 signal() 的回调)    sigset_t sa_mask;         // 信号处理期间临时阻塞的信号集    int sa_flags;             // 信号处理的标志位(如 SA_RESTART、SA_NODEFER 等)    void (*sa_sigaction)(int, siginfo_t *, void *);  // 高级处理函数(需配合 SA_SIGINFO 志)};AI写代码cpp运行参数:signum:信号数字act:结构体定义信号行为,传入的新的行为old:旧的行为返回值:成功返回0,失败返回-1.五. 操作系统是如何运行的1. 硬件中断当我们在键盘上输入命令或数据时,键盘的电路会检测按键的按下和释放,并产生对应的电信号。电信号随后转换为中断信号,通过硬件连线传递到CPU的中断控制器。中断控制器根据信号的优先级和当前CPU的状态,决定是否向CPU发送中断请求。CPU通过中断机制来响应中断请求。在保护下,CPU会维护一个中断描述符表,该表包含中断服务相应的地址信息。当中断发生后,会根据该表来找到对应执行方法的地址。一旦CPU接收到来自键盘的中断请求,就会暂停当前正在执行的程序,然后跳转到特定的中断处理服务地址,处理对应的方法。中断处理操作会执行必要的操作,如读取键盘状态,更新数据,发送响应等。处理完中断后,CPU会恢复之前保存的状态,并继续执行原来的程序。2. 时钟中断进程是在操作系统的指挥下被调度运行的,而操作系统是由时钟中断进行管理的。时钟中断源于硬件定时器,有计算机的主板芯片或处理器芯片提供,通过定时器计数器来实现中断功能。功能:1. 维护系统时间:每当时钟中断发生,内核系统会重新维护一个时间,通过更新系统保证时间的准确性,为用户提供可靠的时间信息。2. 任务调度:在多进程中,时钟会为每一进程分配时间片。当一个时间片用完了,内核就会重新选择下一个要运行的进程,并切换上下文,这样就可以保证公平的进行进程调度。3. 计录进程执行时间:每当进程或线程被抢占或者切换时,时钟会记录下抢占时间,方便我们后序了解系统的运行情况。3. 死循环操作系统在启动之后就会变成一个死循环,但这个循环是可控循环。会进行事件驱动,动态调度,资源管理等操作。通过时钟中断,可以为每一个进程进行时间分配,这样可以确保每个进程都有机会获得处理器资源。4. 软中断软中断是软件主动触发“中断”,从而进入内核态执行特权操作。也被称为“陷阱”,被用作内核态与用户态直接的通信桥梁。软硬中断的具有共性,中断之后需要进行状态切换(从用户态到内核态),上下文保存(保存用户态寄存器,计数器等信息),跳转执行(根据中断号,跳转处理),处理返回(执行完内核逻辑后返回)。在x86下32位机器通常用 int 0x80指令实现调用;在x86下64位机器通常用syscall指令执行调用。通过软中断,我们从用户态到了内核态,这样我们就可以实现系统调用。在内核态中,所有的系统调用其实都是被封装好的一个个函数指针数组,我们通过对应的下标,就可以对其进行访问。 这些被系统调用的系统函数会以数组下标的形式存储在 寄存器 中,通过下标找对应的系统调用表找到对应的处理函数并调用。执行完后再从内核态返回用户态,恢复上下文。这样就完成了系统调用。 紧急着,上层对于这些系统调用接口进行封装,这样我们用户就无法直接调用系统接口,保证了安全性。5. 缺页中断,内存碎片处理,除零野指针错误这些问题会被CPU内部转化为软中断,走中断处理例程进行相应处理。六.对内核虚拟地址空间的理解我们这里只对内核的虚拟地址空间进行讨论。内核虚拟地址空间存储在虚拟地址空间的高地址处,且每一份进程共享这一份内核页表,用于对操作系统进行管理。通常用户态在【0-3G】而内核区在【3-4G】处。CPU通过 int 0x80 或 syscall 指令进行用户态到内核态的切换,将当前状态记录在 CPL 中,其中0代表内核,3代表用户。在内核区中存储了内核页表,存储着内核处理中断异常硬件等各种方法的函数指针。  总结:信号是操作系统中一种轻量级的异步通信机制,通过预定义的信号编号传递事件通知,实现进程对异常、外部请求等事件的响应。其核心是 “事件触发 - 处理” 模型,兼具灵活性(自定义处理)和强制性(关键信号不可忽略),是进程间通信和系统异常处理的基础工具。 ————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/2401_86449430/article/details/150430290
  • [技术干货] KingbaseES 快速安装指南:Windows、Docker 与 ISO 包三种方式详解 -转载
    中电科金仓(北京)科技股份有限公司(以下简称“电科金仓”)成立于1999年,是成立最早的拥有自主知识产权的国产数据库企业,也是中国电子科技集团(CETC)成员企业。电科金仓以“提供卓越的数据库产品助力企业级应用高质量发展”为使命,致力于“成为世界卓越的数据库产品与服务提供商”。    电科金仓自成立起始终坚持自主创新,专注数据库领域二十余载,具备出色的数据库产品研发及服务能力,核心产品金仓数据库管理系统KingbaseES(简称“KES”)是面向全行业、全客户关键应用的企业级大型通用数据库。KES产品V9版本已通过国家权威机构认证,产品核心源代码自主率达到100%。2018年,电科金仓申报的“数据库管理系统核心技术的创新与金仓数据库产业化”项目荣获国家科学技术进步二等奖。金仓数据库管理系统KES于2022年入选国务院国资委发布的十项国有企业数字技术典型成果,彰显数据库领域国家队硬实力。继2023年金仓数据库管理系统V8通过第一批《安全可靠测评》后,2024年金仓数据库管理系统V9、金仓分布式HTAP数据库软件集群V3再度入围,至此电科金仓共计2款产品3个版本通过《安全可靠测评》*。 🥇 点击进入金仓数据库专栏,本专栏聚焦金仓数据库(KingbaseES)这一国产企业级融合数据库,为开发者及技术决策者提供从基础操作到架构设计的系统化学习路径。从多语法兼容(Oracle/MySQL/PostgreSQL)、多模数据存储(关系 / 文档 / 时序 / GIS)等功能展开讲解!🌞 正文开始:KingbaseES是一款功能强大的数据库管理系统,支持多种硬件架构和安装方式。本文将详细介绍通过Docker和ISO包两种方式快速安装KingbaseES的步骤,帮助开发者快速部署和体验该数据库。Docker方式快速安装KingbaseES环境准备硬件环境要求KingbaseES Docker软件包支持通用X86_64、龙芯、飞腾、鲲鹏等国产CPU硬件体系架构,满足不同场景的硬件需求。软件环境要求软件包名称    版本说明Docker    20.10.0及以上版本安装步骤创建安装路径使用root用户在宿主机执行以下命令,创建数据库持久化存储路径:mkdir -p /opt/kingbasemkdir -p /opt/kingbase/datachmod -R 755 /opt/kingbase/data导入镜像首先获取KingbaseES的Docker镜像(如kingbase.tar),可通过电科金仓官网、销售人员、售后支持人员或代理商获取。进入软件安装包目录并查看容器镜像:cd /opt/kingbasels -al使用root用户将镜像导入至Docker镜像库:docker load -i /opt/kingbase/kingbase.tar若执行上述命令报错,可尝试使用docker import kingbase.tar命令重新导包。启动实例最小启动命令(未进行数据持久化):docker run -tid --privileged \-p 54321:54321 \--name kingbase  \kingbase:v1 /usr/sbin/init数据持久化启动命令:docker run -tid --privileged \-p 54321:54321 \--name kingbase  \-v /opt/kingbase/data:/home/kingbase/userdata \kingbase:v1 /usr/sbin/init其中,/home/kingbase/userdata是金仓数据库默认数据文件目录,/opt/kingbase/data是本机存储持久化目录,可根据实际需求修改。安装后检查执行docker ps命令,查看容器运行状态,若能看到创建的kingbase容器信息,则说明安装启动成功。命令行界面访问进入容器并执行bash:docker exec -it kingbase /bin/bash 执行ksql访问命令行界面:ksql -Ukingbase -d testISO包方式快速安装KingbaseES安装步骤概览ISO包安装主要包括:硬件环境检查、创建安装用户、创建安装目录、安装包准备、安装包挂载、安装并启动KingbaseES、安装后检查等步骤。详细步骤硬件环境要求支持架构:通用X86_64、龙芯、飞腾、鲲鹏等国产CPU硬件体系架构。具体要求(标准版/企业版/专业版/开发版):CPU:X86、龙芯、飞腾、鲲鹏内存:512MB以上硬盘:11GB以上空闲空间备注:/tmp目录需要至少10G空间。若安装过程中出现存储空间不足,需先释放足够磁盘空间;硬件配置不满足要求时,需更换硬件设备。创建安装用户使用root用户创建kingbase用户:useradd -u2000  kingbase或若需修改已有用户:kill -9 `lsof -u kingbase`usermod -u 2000 kingbasegroupmod -g 2000 kingbase设置kingbase用户密码(需输入两次且保持一致):passwd kingbase注意:创建安装用户后,后续操作默认使用kingbase用户进行。创建安装目录KingbaseES默认安装目录为/opt/Kingbase/ES/V9,使用root用户创建该目录并设置权限:mkdir -p /opt/Kingbase/ES/V9chmod o+rwx /opt/Kingbase/ES/V9安装包准备获取安装包:通过电科金仓官网、销售人员、售后支持人员或代理商获取对应平台的ISO安装程序。上传安装包至服务器存放路径(示例路径为/opt/software/),并使用root用户创建存放软件路径:mkdir -p /opt/software/ mkdir -p /opt/software/KingbaseESV9 安装包的挂载使用root用户挂载iso文件:cd /opt/software/mount KingbaseES_V009R001C010_Lin64_install.iso ./KingbaseESV9安装KingbaseES使用kingbase用户执行数据库安装命令:cd /opt/software/KingbaseESV9sh setup.sh -i console随后按照安装向导依次进行操作,包括选择安装类型(默认安装新实例)、阅读并接受许可协议、选择安装路径(默认/opt/Kingbase/ES/V9)、选择安装集(默认完全安装)等,直至安装完成。安装后操作安装完成后,可根据提示进行初始化数据库等操作,例如手动初始化数据库命令:/opt/Kingbase/ES/V9/KESRealPro/V009R001/Server/bin/initdb -U "system" -x "12345678ab" -E "UTF-8" --lc-ctype="zh_CN.UTF-8" --lc-collate="zh_CN.UTF-8" -D "/opt/Kingbase/ES/V9/data"AI写代码bash1通过以上两种方式,可快速完成KingbaseES的安装与部署,根据实际应用场景选择合适的安装方式即可。在安装过程中,若遇到问题可参考官方文档或联系技术支持。Windows下载安装下载数据库    金仓官网提供了金仓各产品各版本的安装包、补丁包、对应的工具、接口驱动、授权文件等下载服务。可以通过页面中的筛选栏选择所需产品及版本进行下载,我这里下载的是windows版本的x64完整版金仓数据库。下载地址:https://download.kingbase.com.cn/xzzx/index.htm    这里下载好的是iso镜像文件,直接双击打开,然后把内容复制出来。 安装配置    单击 KINGBASE.EXE 开始安装!    我这里选择默认的中文安装,点击 确定    单击 下一步    单击 下一步    我这里选择的完全安装,点击 下一步    我这里选择试用,点击 下一步    可以自定义安装路径,点击 下一步    点击 安装    默认数据文件就行,点击 下一步     填写相关配置信息,system的密码自定义,字符集编码,选择的是UTF8;数据库兼容模式选择的是MySql;是否大小写敏感选择的是NO,存储块大小选择的是8K,点击 完成    这里数据库就安装完成了,是不是相当简单。 联系博主    xcLeigh 博主,全栈领域优质创作者,博客专家,目前,活跃在CSDN、微信公众号、小红书、知乎、掘金、快手、思否、微博、51CTO、B站、腾讯云开发者社区、阿里云开发者社区等平台,全网拥有几十万的粉丝,全网统一IP为 xcLeigh。希望通过我的分享,让大家能在喜悦的情况下收获到有用的知识。主要分享编程、开发工具、算法、技术学习心得等内容。很多读者评价他的文章简洁易懂,尤其对于一些复杂的技术话题,他能通过通俗的语言来解释,帮助初学者更好地理解。博客通常也会涉及一些实践经验,项目分享以及解决实际开发中遇到的问题。如果你是开发领域的初学者,或者在学习一些新的编程语言或框架,关注他的文章对你有很大帮助。    亲爱的朋友,无论前路如何漫长与崎岖,都请怀揣梦想的火种,因为在生活的广袤星空中,总有一颗属于你的璀璨星辰在熠熠生辉,静候你抵达。     愿你在这纷繁世间,能时常收获微小而确定的幸福,如春日微风轻拂面庞,所有的疲惫与烦恼都能被温柔以待,内心永远充盈着安宁与慰藉。    至此,文章已至尾声,而您的故事仍在续写,不知您对文中所叙有何独特见解?期待您在心中与我对话,开启思想的新交流。     💞 关注博主 🌀 带你实现畅游前后端!     🥇 从零到一学习Python 🌀 带你玩转Python技术流!     🏆 人工智能学习合集 🌀 搭配实例教程与实战案例,帮你构建完整 AI 知识体系     💦 注:本文撰写于CSDN平台,作者:xcLeigh(所有权归作者所有) ,https://xcleigh.blog.csdn.net/,如果相关下载没有跳转,请查看这个地址,相关链接没有跳转,皆是抄袭本文,转载请备注本文原地址。————————————————                                                    原文链接:https://blog.csdn.net/weixin_43151418/article/details/150473138
  • [技术干货] 【Linux】环境变量-转载
    概念        环境变量:一般指的是在操作系统中用来指定操作系统运行环境的一些参数。        就比如说我们编写的C/C++代码在连接时从来不知道我们所连接的动静态库在哪里,但是照样可以连接成功。其原因就是有环境变量帮助编译器进行查找。        环境变量通常具有某些特殊用途,其在系统中还具有全局特性。命令行参数        我们知道中Linux命令中许多命令都是有对应的选项的,不同的选项对应不同的功能。那这个操作是如何实现的呢?main函数参数                 首先,让我们先来讲讲main函数。mian函数可以说是我们接触编程的第一步,但许多人可以对main函数并不了解。        main函数其实是可以有参数的。int main(int argc,char* argv[])AI生成项目         第一个参数,argc:表示命名行参数的数量(包括程序名本身)         第二个参数,argv:是一个字符指针数组,储存了命令行中的每个参数验证:#include<stdio.h> int main(int argc,char* argv[]){  for(int i=0;i<argc;i++)  {      printf("%s\n",argv[i]);                                                                                                                                            }}AI生成项目 实现指令选项#include<stdio.h>#include<string.h> int main(int argc,char* argv[]){    if(argc==2)    {           char* x=argv[1];        if(strcmp(x,"-a")==0)            printf("执行-a功能\n");        if(strcmp(x,"-b")==0)            printf("执行-b功能\n");        if(strcmp(x,"-c")==0)            printf("执行-c功能\n");    }       else        printf("请使用:-a/-b/-c\n");}AI生成项目         进程中有一张表:argv表(命令行参数表),利用此表则可以实现命令的选项的功能。认识环境变量首先我们要执行一个程序,必须先找到它而环境变量可以帮助我们找到其位置使用env指令我们可以查看系统中所有的环境变量,也可以使用echo $名称,打印对应的环境变量环境变量的格式为:名称=内容  PATH        我们先来认识一下PATH环境变量。        PATH环境变量表示查找指令位置的默认路径,比如当我们执行指令:ls、cd等等指令时,bash就会使用PATH环境变量去找到指令对应的程序并执行。        我们知道系统指令的实现也是C语言,但为什么系统自己的指令可以直接执行,而我们自己写的代码却必须要 “./” 作为前缀才可以执行,PATH环境变量就是关键。理解两个问题:1.环境变量是如何存储的呢?        bash下会形成一个环境变量表,存放各种各样的环境变量。(上面我们也讲到了bash中有一个命令行参数表,也是就是bash下有两张表)2.环境变量一开始是从哪里来的?        bash下的环境变量表是重系统配置文件中来的(以下便是配置文件)。 认识更多环境变量HOME        HOME环境变量,记录的是当前用户的家目录路径root@hcss-ecs-4ce7:~# echo $HOME/rootAI生成项目SHELL        表示当前使用的shell版本root@hcss-ecs-4ce7:~# echo $SHELL/bin/bashAI生成项目 USER        表示当前用户是谁root@hcss-ecs-4ce7:~# echo $USERrootAI生成项目 HISTSIZE        表示Linux中记录历史命令的最大数量(既Linux的历史命令最多记录HISTSIZE条)root@hcss-ecs-4ce7:~# echo $HISTSIZE1000AI生成项目HOSTNAME         表示当前主机的主机名root@hcss-ecs-4ce7:~# echo $HOSTNAMEhcss-ecs-4ce7AI生成项目PWD        记录当前所在的工作路径hyc@hcss-ecs-4ce7:~/linux/progress$ echo $PWD/home/hyc/linux/progressAI生成项目OLDPWD        记录上一次的工作路径hyc@hcss-ecs-4ce7:~/linux/progress$ echo $OLDPWD/home/hyc/linuxAI生成项目环境变量的获取方式指令env:查看全部的环境变量echo $xxx:查看某个环境变量expot:添加环境变量unset:删除环境变量 代码调用系统调用:getenv获取环境变量头文件为:#include<stdlib.h>返回值:获取成功返回指针,失败返回NULL#include<stdio.h>#include <stdlib.h> int main(){    char* str=getenv("PATH");                                                                                                                                              if(!str)        printf("获取失败");    else        printf("%s\n",str);}AI生成项目 环境变量特性1.环境变量具有全局性,可以被子进程继承,同一个bash下的进程都可以访问 。2.存在本地变量,只能在bash内部使用。如下:i 就是本地变量。 3.export添加环境变量,是添加在bash中的,再子进程继承达到全局可用。   export命令与其他命令不同:export命令是内键命令:既不需要创建子进程,bash自己调用函数执行(或系统调用完成)————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/huangyuchi/article/details/149715773
  • [技术干货] LM Studio 教程:通过暴露本地端口实现局域网内客户端调用(LM Studio 作为本地 LLM API 服务器)-转载
    links:https://lm-studio.cn/docs/apiLM Studio 可以像 Ollama 一样通过暴露本地端口实现局域网内客户端调用。以下是具体实现方式和技术细节:1. 核心功能支持LM Studio 的 开发者选项卡 提供了完整的本地 API 服务器功能,支持以下配置:端口暴露:默认使用 1234 端口,可自定义修改(需注意端口冲突问题)3局域网访问:勾选 “在局域网内提供服务” 选项后,服务器会监听所有网络接口(0.0.0.0),允许其他设备通过 IP 地址访问34跨域支持:启用 CORS 后,可对接网页应用或其他客户端工具32. 配置步骤实现局域网调用的具体操作流程:启动服务端:在 LM Studio 的 “开发者” 选项卡中选择模型勾选 “在局域网内提供服务” 并设置端口(如 1234)3点击 “启动服务” 按钮,控制台会显示运行日志和访问地址(如 http://<本机IP>:1234)3客户端调用:其他设备可通过以下方式访问:# 示例:使用 curl 调用聊天接口curl http://<本机IP>:1234/v1/chat/completions \  -H "Content-Type: application/json" \  -d '{"messages": [{"role": "user", "content": "你好"}], "model": "<模型名称>"}'AI生成项目bash1234支持 OpenAI 兼容的 API 格式(包括 /v1/chat/completions、/v1/completions 等端点)13防火墙配置(Windows 系统):需在防火墙入站/出站规则中开放对应端口(如 1234)53. 与 Ollama 的对比特性    LM Studio    Ollama协议兼容性    OpenAI API 兼容    自有 API 格式界面操作    图形界面一键配置    需命令行操作多模型支持    需手动加载不同模型    支持 Modelfile 动态切换日志管理    内置日志记录(可记录提示与响应)3    需自行配置日志工具4. 典型应用场景多人协作开发:团队成员通过局域网共享同一模型服务2跨设备调用:手机/平板通过浏览器或 Open WebUI 访问本地模型2自动化流程:对接 VS Code 插件或其他 AI 工具链1注意:如果遇到连接问题,需检查以下三点:LM Studio 服务端是否已勾选局域网选项防火墙是否开放对应端口客户端是否使用正确的 IP 地址(可通过 ipconfig/ifconfig 查看本机局域网 IP)35————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/a772304419/article/details/145800588
  • [技术干货] 【Linux篇章】穿越数据迷雾:HTTPS构筑网络安全的量子级护盾,重塑数字信任帝国-转载
    一· 认识HTTPSHTTPS 也是一个应用层协议. 是在 HTTP 协议的基础上引入了一个加密层,即可以理解成SSL+TLS+HTTP构成!二· 为何要加密所以:因为 http的内容是明文传输的,明文数据会经过路由器、wifi热点、通信服务运营商、代理服务器等多个物理节点,如果信息在传输过程中被劫持,传输的内容就完全暴露了。劫持者还可以篡改传输的信息且不被双方察觉,这就是中间人攻击,所以我们才需要对信息进行加密。总之,早期的http在网络中明文传递,是暴露的,能被修改!!!其中:加密就是把明文(要传输的信息)进行一系列变换,生成密文!解密就是把密文再进行一系列变换,还原成明文!在这个加密和解密的过程中,往往需要一个或者多个中间的数据,辅助进行这个过程,这样的数据称为密钥(正确发音yue 四声,不过大家平时都读作yao四声)!因此:HTTPS 就是在 HTTP 的基础上进行了加密,进一步的来保证用户的信息安全!三. 加密方式3.1 对称加密采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密常见对称加密算法(了解):DES、3DES、AES、TDEA、Blowfish、RC2等特点:算法公开、计算量小、加密速度快、加密效率高!因此:我们只需要记住:加密秘钥和解密秘钥是同一把,这在某种意义上成为了它的缺点!一个简单的对称加密:按位异或:假设明文a= 1234,密钥key = 8888则加密a ^ key得到的密文b 为9834,然后针对密文9834再次进行运算b ^key,得到的就是原来的明文1234.(对于字符串的对称加密也是同理,每一个字符都可以表示成一个数字)当然,按位异或只是最简单的对称加密.HTTPS中并不是使用按位异或.3.2 非对称加密需要两个密钥来进行加密和解密,这两个密钥是公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥)。常见非对称加密算法:RSA,DSA,ECDSA。特点:算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,而使得加密解密速度没有对称加密解密的速度快。最大的缺点就是运算速度非常慢,比对称加密要慢很多。因此:这里只需要记住公钥加密只有私钥解开,私钥加密只有公钥解开!3.3 数据摘要与数据指纹数字指纹(数据摘要),其基本原理是利用单向散列函数(Hash函数)对信息进行运算,生成一串固定长度的数字摘要。数字指纹并不是一种加密机制,但可以用来判断数据有没有被篡改。摘要常见算法:有 MD5、SHA1、SHA256、SHA512等,算法把无限的映射成有限,因此可能会有碰撞(两个不同的信息,算出的摘要相同,但是概率非常低)。摘要特征:和加密算法的区别是,摘要严格意义不是加密,因为没有解密,只不过从摘要很难反推原信息,通常用来进行数据对比,比如数据库管理使用摘要。四. HTTPS加密方式4.1 只使用对称加密也就是我们只使用一把秘钥进行通信:理想:实际:这种理想上被中间人截取的也只能是密文,无利用价值,但是服务端和每个客户端都要有个秘钥来维护,这样就比较麻烦,因此一般采取的是先发送秘钥进行协商,但是这样就被中间人获取秘钥了,因此这种方案是不可行的!因此就导致了这样:4.2 双方使用一个非对称加密服务端发送信息前提是先要和浏览器协商(把公钥发给它)然后用私钥加密,给浏览器,这样信息就暴露了(服务器到浏览器的信息暴露),但是浏览器拿到后,进行发送用公钥加密(只能私钥解密),此时无法暴露,服务器就能正常收到信息!比如:4.3 双方都使用非对称加密交换公钥,公钥加密,私钥解密!它们俩互相交换了公钥(用于加密),有没有可能中间人也用公钥进行加密然后比如从客户端发给服务端,这样服务端也就分不清了或者中间人用自己的公钥给客户端,然后进行信息窃取!总结:因为非对称加密:效率低,而且还有安全问题!4.4 非对称加密+对称加密也就是服务端先把公钥给客户端,客户端用它进行对称密钥加密然后给服务端(中间人无私钥,无法解密),最后他俩就用这个对称公钥进行加密!过程如图所示:这样看似是没有毛病的,但是有没有可能中间人把自己的公钥m给client,然后不就得到了这个被m加密的x了,然后再用s进行加密给服务端这个x,之后s与c用x通信不就被中间人掌握了吗!结局就成了这样:因此:关键问题还是防止中间人攻击也就是如何确保client收到的公钥来自server而不是中间人呢?4.5 两次非对称加密与一次对称加密引入证书这种方式的前两次非对称加密都是为了保证最后一次对称加密的安全性!证书=签名+明文CA认证:服务端在使用 HTTPS 前,需要向 CA 机构申领一份数字证书,数字证书里含有证书申请者信息、公钥信息等。服务器把证书传输给浏览器,浏览器从证书里获取公钥就行了,证书就如身份证,证明服务端公钥的权威性!下面看一下申请证书过程图:这里需要记住:签名只有CA机构能做(因为只有它才有对应私钥)。签名和解签名的秘钥和服务端的秘钥不是同一种。CA的私钥由CA使用进行签名,CA的公钥由浏览器自备进行解签名验证。服务端和客户端通信的时候,客户端会先请求,此时服务端就去CA机构申请一个证书,这个证书就是保证client拿到的一定是server的公钥!证书有没有可能被篡改那么此时浏览器拿到证书如何核对有没有在server与client传播过程中被中间人修改呢?CA机构在形成证书的时候会拿着私钥对明文信息散列后的摘要进行加密作为签名!然后当客户端收到证书后会拿着CA公钥对签名解密拿到的数据和明文信息散列后进行对比;如果相同就证明没有被修改,拿着公钥进行传递对称秘钥!!!被修改了就警告! !! —>保证了client一定拿到server的公钥!!!如图:但是如果中间人修改server发给client的证书的明文/签名/或者整体替换呢(中间人如果要想成功传递自己的秘钥必须修改签名)?修改明文:如果修改了的话,当client拿到证书后会发现不匹配的。修改签名:中间人没有ca的私钥无法对前面修改。整体替换:这里整体替换就必须要ca的严格审查,中间人只能按照要求申请新的证书,但是还有域名等等一些信息使得客户端可以识别到是不是对应服务端!中间人动机:要么把明文里的公钥换成自己的。要么换成自己的后然后改签名让客户能够获取公钥。但是,均行不通!永远记住:中间人没有CA私钥,所以对任何证书都无法进行合法修改,包括自己的。因此总结:中间人无法修改证书和伪造新的证书!非对称加密+对称加密+证书认证因此,它来了!这里可以理解成:首先进证书申请+客户端验证证书(第一次非对称加密以及解密);接着就是client拿到对应的server的公钥,然后进行传递接下来进行非对称加密的秘钥,服务端然后拿着自己的私钥进行解密拿到(第二次非对称加密以及解密);接着就是用这个非对称秘钥进行通信了(非对称加密及解密)!也就是这样:注意:这里的易混点是签名和解签名只能用CA的私钥和公钥;而不是服务器的的私钥公钥(过程中服务器会泄露公钥但私钥不会泄露)!! !下面看张更加详细的图:详细步骤:第一次非对称加密:用于校验证书是否被篡改.服务器持有私钥(私钥在形成CSR文件与申请证书时获得),客户端持有公钥(操作系统包含了可信任的CA认证机构有哪些,同时持有对应的公钥).服务器在客户端请求时,返回携带签名的证书.客户端通过这个公钥进行证书验证,保证证书的合法性,进一步保证证书中携带的服务端公钥权威性。第二次非对称加密:用于协商生成对称加密的密钥.客户端用收到的CA证书中的公钥(是可被信任的)给随机生成的对称加密的密钥加密,传输给服务器,服务器通过私钥解密获取到对称加密密钥。对称加密:客户端和服务器后续传输的数据都通过这个对称密钥加密解密。超详细大白话叙述过程:首先服务器拿着相关信息(域名,csr,明文信息等),然后生产对应的私钥(自己保存),公钥(交给CA)然后向CA申请证书,ca检查严格审核后,拿着自己的专属私钥进行对明文散列后数据签名,接着交给server,最后server把证书给client,client拿着自己内置信任的CA机构的公钥容纳后进行解密签名看是否合法(明文散列是否相同,域名等是否匹配),不合格就发出警告(上报,或者浏览器阻止用户访问等),合格的话就拿到对应服务器的公钥,自己产生对称秘钥,然后发给服务端(中间人稚嫩获得密文,无法解密),然后服务端解密后,他俩就利用对称秘钥进行通信了!总结:这一切的目的就是保证这个对称秘钥通信的时候是安全的 !疑问点为什么要进行签名加密:如果不签名的话就是直接证书上的就是明文,那么在客户端核对前是有可能被中间人修改的,因此是不安全的。为什么明文不直接加密而是先生成摘要呢:缩小签名密文的长度,加快数字签名的验证签名的运算速度。如何成为中间人:ARP欺骗:在局域网中,hacker经过收到ARP Request广播包,能够偷听到其它节点的(IP,MAC)地址。例,黑客收到两个主机A,B的地址,告诉B(受害者),自己是A,使得B在发送给A的数据包都被黑客截取。ICMP攻击: 由于ICMP协议中有重定向的报文类型,那么我们就可以伪造一个ICMP信息然后发送给局域网中的客户端,并伪装自己是一个更好的路由通路。从而导致目标所有的上网流量都会发送到我们指定的接口上,达到和ARP欺骗同样的效果。查看浏览器的受信任证书相关信息首先点击浏览器对应设置:接下来点击安全选项:可以看到有很多信任的证书,点击可以看到公钥等信息:五. 本篇小结通过本篇对HTTPS的基础认识,建立了大致的了解,知道了它背后的原理等,此次学习虽然是大致的,但是也看到了HTTPS背后的基本原理,为以后对它更加深入的学习奠定了基础,冲冲冲!————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/2401_82648291/article/details/147658666
  • [技术干货] 【Linux指南】gcc/g++编译器:从源码到可执行文件的全流程解析 -转载
    一、编译的四个核心阶段从源代码到可执行文件,gcc/g++的工作流程分为四个关键阶段,每个阶段都有明确的输入、输出和任务目标。四个核心阶段输出输出输出输出**预处理 (gcc -E)**▸ 宏替换▸ 删除注释▸ 头文件展开▸ 条件编译处理**编译 (gcc -S)**▸ 语法分析▸ 语义检查▸ 生成汇编指令**汇编 (gcc -c)**▸ 转换汇编为机器码▸ 生成目标文件▸ 未解决外部引用**链接 (gcc *.o)**▸ 合并目标文件▸ 解析函数引用▸ 链接库文件▸ 地址重定位源文件*.c/*.cpp.i文件.s文件.o文件可执行文件1. 预处理阶段:代码的初步加工预处理是编译的第一个环节,主要负责对源代码进行“前期处理”,包括:宏定义的替换(如#define指令)注释的删除(避免注释干扰编译过程)条件编译的处理(如#if、#ifdef等指令)头文件的展开(将#include指令引入的头文件内容插入到当前文件中)预处理阶段的输入是.c(或.cpp)源文件,输出是.i文件。在Linux中,可通过以下命令单独执行预处理:gcc -E code.c -o code.i  # 对C文件进行预处理,生成code.ig++ -E code.cpp -o code.i  # 对C++文件进行预处理其中,-E参数表示“仅执行预处理阶段”,-o用于指定输出文件的名称。预处理后的.i文件仍是文本格式,可通过cat命令查看内容。Linux系统的标准头文件(如stdio.h、stdlib.h)默认存储在/usr/include/目录下,预处理时编译器会自动到该路径搜索头文件。2. 编译阶段:从高级语言到汇编语言编译阶段的任务是将预处理后的.i文件转化为汇编语言代码。编译器会对代码进行语法检查、语义分析,并最终生成与硬件架构相关的汇编指令。该阶段的输入是.i文件,输出是.s汇编文件。执行命令如下:gcc -S code.i -o code.s  # 生成汇编文件code.s-S参数表示“仅执行到编译阶段”,生成的.s文件包含汇编指令,例如mov、add等操作。汇编语言是介于高级语言和机器语言之间的中间形式,仍可被人类阅读。3. 汇编阶段:从汇编到二进制目标文件汇编阶段将汇编语言转化为机器可识别的二进制指令,生成目标文件(.o文件,也称为obj文件)。输入为.s汇编文件,输出为.o二进制文件,命令如下:gcc -c code.s -o code.o  # 汇编生成目标文件-c参数表示“仅执行到汇编阶段”。.o文件是二进制格式,无法直接通过文本编辑器查看,但它已经包含了程序的基本指令。需要注意的是,此时的.o文件尚未解决函数调用的依赖关系(如调用其他文件中的函数),因此还不能直接执行。4. 链接阶段:整合目标文件与库链接是编译的最后阶段,其核心任务是将多个.o目标文件与系统库(或第三方库)整合,生成最终的可执行文件。链接阶段主要解决以下问题:函数调用的地址绑定(确定被调用函数在内存中的具体位置)全局变量的引用解析库文件的关联(如C标准库中的printf、scanf等函数)执行命令如下:gcc code.o -o code  # 将code.o链接为可执行文件code此时生成的code文件即可直接运行(通过./code命令)。如果程序依赖多个目标文件,可同时传入链接命令:gcc a.o b.o c.o -o app  # 链接多个目标文件生成app二、一次性编译与常用扩展选项在实际开发中,我们通常不需要手动执行四个阶段,而是直接通过一条命令完成从源码到可执行文件的全过程:gcc code.c -o code  # 直接编译C文件生成可执行文件codeg++ code.cpp -o app  # 直接编译C++文件生成可执行文件app这条命令会自动依次执行预处理、编译、汇编和链接四个阶段,最终生成指定的可执行文件。除了基础命令,gcc/g++还提供了许多实用选项,用于优化编译过程或控制输出:选项    功能描述-Wall    显示更多警告信息(如未使用的变量、类型不匹配等),帮助排查潜在问题-O(或-O1、-O2、-O3)    开启编译优化,-O3为最高级别优化,可提升程序运行效率(但编译时间更长)-g    生成调试信息,用于gdb调试工具(如gcc -g code.c -o code)-std=c99(或-std=c++11)    指定编译标准(如C99、C++11),确保代码兼容性-I(大写i)    指定头文件搜索路径(如gcc -I./include code.c -o code)-L    指定库文件搜索路径(如gcc code.c -L./lib -o code)-l(小写L)    链接指定的库(如-lm表示链接数学库libm.so)三、多文件编译实践在大型项目中,程序往往被拆分为多个源文件(如main.c、func1.c、func2.c),此时需要通过多文件编译来管理代码。假设项目结构如下:project/├── main.c    # 主函数├── func1.c   # 功能函数1├── func2.c   # 功能函数2└── include/    ├── func1.h  # func1.c的头文件    └── func2.h  # func2.c的头文件编译命令如下:# 方法1:分步编译gcc -c main.c -o main.o -I./include  # 编译main.c,指定头文件路径gcc -c func1.c -o func1.o -I./includegcc -c func2.c -o func2.o -I./includegcc main.o func1.o func2.o -o app  # 链接所有目标文件# 方法2:一次性编译gcc main.c func1.c func2.c -o app -I./include通过多文件编译,不仅可以提高代码的可维护性,还能减少重复编译的时间(修改单个文件后,只需重新编译该文件的.o目标文件,再重新链接即可)。四、总结gcc/g++编译器是Linux开发中不可或缺的工具,其工作流程涵盖预处理、编译、汇编和链接四个阶段,每个阶段都有明确的目标和输出。掌握编译器的使用方法,不仅能帮助我们生成可执行程序,还能通过调试选项、优化参数等提升开发效率和程序性能。在实际开发中,我们可以根据需求选择分步编译(用于调试或分析中间过程)或一次性编译(用于快速生成可执行文件)。同时,合理使用多文件编译和库链接,能更好地管理大型项目的代码结构。理解这些基础原理和操作,是深入Linux开发的重要一步。————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/2302_78391795/article/details/149369002
  • [技术干货] 【Linux】进程信号-转载
    Linux进程信号是一种进程间通信的机制,它允许一个进程通知另一个进程某个事件已经发生。以下是关于Linux进程信号的详细介绍: 1. 信号的概念  信号就像是一个突然的电话铃声,它会打断正在进行的程序并引起其注意。在Linux系统中,信号是一种软件中断,它通常是异步发生的,用来通知进程某个事件已经发生。每个信号都有一个唯一的编号和一个宏定义名称,这些宏定义可以在signal.h中找到。 使用kill -l命令查看信号编号: 查看信号宏定义: 2. 信号的分类  在Linux中,信号被分为标准信号(也称为传统或不可靠信号)和实时信号。它们的主要区别在于编号范围、处理方式以及特性。 1) 标准信号 (Traditional/Standard Signals)这些信号是早期Unix系统定义的,编号通常从1到31(尽管某些系统可能会有所不同)。以下是一些常见的标准信号: SIGHUP (1): 终端挂起或控制进程结束。1号信号,当用户退出终端时,由该终端开启的所有进程都会接收到这个信号,默认动作为终止进程。但也可以捕获这个信号,比如wget能捕获SIGHUP信号并忽略它,以便在退出登录后继续下载。 SIGINT (2): 中断信号,通常是Ctrl+C产生的。2号信号,程序终止(interrupt)信号,在用户键入INTR字符(通常是Ctrl+C)时发出,用于通知前台进程组终止进程。 SIGQUIT (3): 退出信号,产生核心转储。3号信号,和SIGINT类似,但由QUIT字符(通常是Ctrl+\)来控制。进程在因收到SIGQUIT退出时会产生core文件,在这个意义上类似于一个程序错误信号。 SIGILL (4): 非法指令。SIGTRAP (5): 跟踪陷阱(由调试器使用)。SIGABRT (6): 调用abort()函数生成的信号。SIGBUS (7): 总线错误。SIGFPE (8): 浮点异常。SIGKILL (9): 强制终止信号(不可被捕获、阻塞或忽略)。9号信号,用来立即结束程序的运行。本信号不能被阻塞、处理和忽略。 SIGSEGV (11): 段违例。SIGPIPE (13): 管道破裂。SIGALRM (14): 定时器到期。SIGTERM (15): 终止请求。15号信号,程序结束(terminate)信号,与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,允许进程做一些必要的清理工作后退出。 2) 实时信号 (Real-time Signals)实时信号是在POSIX.1b标准中引入的,用于提供更可靠的信号机制。它们的编号范围从SIGRTMIN到SIGRTMAX,具体数值取决于操作系统实现。一般情况下,这个范围是从34开始直到系统的最大信号数。例如,在许多Linux系统上,SIGRTMIN对应的是34,而SIGRTMAX可以达到64或者更高。 实时信号的特点包括但不限于: 不会丢失:如果多个相同的实时信号发送给同一个进程,所有信号都会被接收。支持排队:每个类型的实时信号可以有一个队列来存储未处理的信号实例。有序性:实时信号按照发送顺序处理。可携带数据:可以通过sigqueue()发送附加的数据(一个整数或指针)。请注意,实际的信号编号可能根据不同的系统架构和版本有所变化。此外,对于实时信号,应当使用SIGRTMIN + n和SIGRTMAX - n这样的形式来引用,而不是直接使用具体的数字值,以确保兼容性和正确性。 本章只讨论编号31以下的信号,不讨论实时信号。  3. 信号的处理 在Linux中,信号处理是进程对特定事件响应的一种机制。信号处理有三种方式: ✨方式一:执⾏该信号的默认处理动作 使用命令man 7 signal查看信号在什么条件下产⽣,默认的处理动作是什么:   ✨方式二:忽略此信号 可以通过设置信号处理器(也就是信号处理函数来实现): 信号处理器(Signal Handler)信号处理器是一个函数,它在进程接收到指定信号时被调用。你可以为每个信号设置一个自定义的处理器,函数如下:#include <signal.h> void (*signal(int signum, void (*handler)(int)))(int); 参数signum表示要设置的信号编号,参数handler表示要设置的信号处理函数。signal函数会返回上一个信号处理函数的指针,如果出错则返回SIG_ERR。 忽略信号可以将信号处理器设置为 SIG_IGN 来忽略某些信号。但是,不能忽略像 SIGKILL 和 SIGSTOP 这样的不可捕获信号。代码如下:#include<iostream>#include<unistd.h>#include<signal.h>int main(){    signal(SIGINT,SIG_IGN);//将2号信号忽略    while(true)    {        std::cout<<"PID:"<<getpid()<<"   I am waiting a signal."<<std::endl;        sleep(1);    }    return 0;}   2号信号的默认处理动作是程序终止(interrupt),但是由于我们使用信号处理器忽略了该信号,所以输入Ctrl+c没有终止程序。 ✨情况三:设置自定义处理方式 和忽略信号相同我们也是使用信号处理器来实现,实例代码如下: #include<iostream>#include<unistd.h>#include<signal.h>void Handler(int signo){    std::cout<<"PID:"<<getpid()<<"  Get a signal:"<<signo<<std::endl;//当接受到SIGINT也就是2号信号会执行打印动作,而非终止进程}int main(){    signal(SIGINT,Handler);//对2号信号设置自定义处理动作    while(true)    {        std::cout<<"PID:"<<getpid()<<"   I am waiting a signal."<<std::endl;        sleep(1);    }    return 0;}    对于信号默认处理动作,我们也可以使用信号处理器来实现:signal(SIGINT/*2*/, SIG_DFL);使用SIG_DFL即可选择默认处理动作,结果如下:  注意:signal方法只需要设置一次,那么在这个进程中,只要接受到被设置的信号就会执行Handler方法;此外如果没有产生被设置的信号,Handler方法就永远不会被执行。也就是说signal方法类似的设置了一种机制,当有对应的信号产生时就触发,没有就不触发。  4. 理解信号处理  对于信号的处理我们可以分别通过软件和硬件这两个视角来理解,由于硬件比较麻烦,设计操作系统运行原理、时钟中断、死循环等,所以这里不做解释。 软件我们以发送2号信号——从键盘输入Ctrl+c为例,当键盘将输入的Ctrl+c信息交给操作系统后,操作系统就会将信号发送给对应的进程,然后进程就会在合适的时候处理信号。 注意,进程处理信号不是立即处理,而是在合适的时机,因为当前进程可能在处理自己的事。 当进程接收到信号时,是如何记录是哪个信号从而执行相应的信号处理任务呢? 我们发现本次学习的信号是1 ~ 31,连续的数字,那么只需选择位图即可使用最小的空间记录下完整的1 ~ 31个数字;只需在进程PCB中定义一个无符号整型,使用32个比特位记录31个信号,比特位的位置代表信号的编号,比特位设为1代表收到该位置的信号,为0代表没有。也就是说操作系统给进程发送信号就是将进程PCB中记录信号的位图对应位置的信号比特位由0置1,然后进程在合适的时候发现自己收到了信号,执行对应处理动作。 接下来为了保证条理,将采⽤如下思路来进⾏阐述:   5. 信号产生     在Linux系统中,信号(signal)是一种异步通知机制,用于通知进程发生了某些事件。进程可以接收到多种类型的信号,并且可以对这些信号进行处理或者忽略。以下是几种产生信号的常见方法: 1) 键盘输入Ctrl+C:发送SIGINT给前台进程,通常用于终止一个进程。Ctrl+\:发送SIGQUIT给前台进程,类似于SIGINT但会生成核心转储文件。Ctrl+Z:发送SIGTSTP给前台进程,暂停进程的执行。2) kill命令使用kill命令可以向指定的进程发送信号,默认情况下是发送SIGTERM信号请求进程正常终止。可以通过-l选项列出所有可用的信号,通过-s或直接跟信号编号来指定发送的信号类型。kill -9 <pid>  # 发送SIGKILL信号,强制终止进程kill -15 <pid> # 发送SIGTERM信号,请求进程正常终止AI写代码123) kill函数及系统调用kill()函数: 可以直接从程序内部发送信号给其他进程。#include <sys/types.h>#include <signal.h> int kill(pid_t pid, int sig); 参数说明: pid:要发送信号的进程IDsig:要发送的信号编号该函数的返回值为0表示成功,返回-1表示失败。 所以我们可以根据kill系统调用来封装一个自己的kill命令,代码如下: #include<iostream>#include <sys/types.h>#include <signal.h> int main(int argc,char* argv[]){    if(argc!=3)    {        std::cout<<"you should print: ./mykill -signo -pid"<<std::endl;//提示输入格式        return 1;    }    int signo = std::stoi(argv[1]);//字符串转整型    pid_t pid = std::stoi(argv[2]);     int n = ::kill(pid,signo);    if(n < 0)    {        perror("kill");        return 2;    }    return 0;} 所以其实kill命令本质也是通过系统调用来实现的。 raise函数:用于向调用它的进程自身发送信号。int raise(int sig);AI写代码cpp运行1参数sig为要生成的信号编号。常见的信号编号包括SIGINT(中断信号,通常由终端键盘输入产生)、SIGABRT(终止信号,由abort函数产生)等。 abort函数:abort函数用于异常终止程序。当调用该函数时,程序会立即退出,并生成SIGABRT信号。void abort(void); 4) 软件条件当特定的软件条件发生时,比如子进程结束(SIGCHLD),文件描述符准备就绪(SIGIO),可能会触发信号的产生。例如alarm定时器:用于设置一个定时器,可以在指定的时间后产生一个SIGALRM信号。#include <unistd.h> unsigned int alarm(unsigned int seconds);  参数seconds指定了定时器的时间间隔,单位为秒。该函数返回当前的闹钟定时器的剩余秒数。如果没有闹钟定时器正在运行,则返回0。使用代码如下: #include<iostream>#include<unistd.h>#include<signal.h>int main(){    ::alarm(1);//设置1s的定时器        int count = 0;    while(true)    {        count++;//统计服务器1s可以将计数器累加到多少        printf("count:%d\n",count);    }    return 0;}  我们发现服务器1s也就计算4万多次,有点慢,这其实是因为打印count数值时,进行了IO交互,会影响速度;所以我们可以通过signal信号处理器来优化,代码如下: #include<iostream>#include<unistd.h>#include<signal.h> int gcount = 0; void Handler(int signo){    printf("count:%d\n",gcount);//只在1s后打印总的计算次数    exit(1);//直接退出}int main(){    signal(SIGALRM,Handler);    ::alarm(1);    while(true)    {        gcount++;    }    return 0;}  结果如下:   可以看到服务器1s大概计算了6亿多次  闹钟返回值:返回当前的闹钟定时器的剩余秒数。如果没有闹钟定时器正在运行,则返回0。测试代码如下:#include<iostream>#include<unistd.h>#include<signal.h> int main(){    int n = ::alarm(4);//在此之前没有闹钟在运行,返回0    std::cout<<n<<std::endl;    sleep(1);    int m = ::alarm(0);//0:表示取消闹钟,m表示剩余秒数    std::cout<<m<<std::endl;     return 0;} 结果如下:  闹钟原理:  其实本质是OS必须自身具有定时功能,并能让用户设置这种定时功能,才可能实现闹钟这样的技术。操作系统需要对定时器进行管理,使用结构体对它先描述再组织,其结构体大概包含: struct timer{int who;task_struct *t;//表示哪个进程设置的闹钟uint64_t expired;//过期时间struct timer *next;//定时器可以有多个,通过链表连接起来func_t f;//到期执行的函数//...}   当定时器到期后,操作系统就会根据它的func_t f函数,执行对应的方法,比如给目标进程发送SIGALRM信号。此外我们可以理解定时器在操作系统中连接的数据结构为小堆,每次都是堆顶元素先到期,这样操作系统就不需要每次都遍历定时器查找是否有过期的定时器。 也就是说定时器到期时可以理解为软件条件就绪,操作系统就会给对应进程发送信号,所以软件条件当作信号产生的方式之一。 使用闹钟完成定时器功能:  根据闹钟定时作用,我们可以使用信号处理器使得闹钟响起时进程自动去完成某些任务,代码如下: #include<iostream>#include<unistd.h>#include<signal.h>#include<stdlib.h>#include<vector>#include<functional> using func_t = std::function<void()>;int gcount = 0;std::vector<func_t> gfuncs;//使用vector管理相关任务 void Handler(int signo){    for(auto& f : gfuncs)//遍历任务执行        f();    std::cout<<"gcount:"<<++gcount<<std::endl;//记录执行次数    alarm(1);}int main(){    gfuncs.push_back([](){std::cout<<"我是一个内核刷新操作..."<<std::endl;});    gfuncs.push_back([](){std::cout<<"我是一个检测进程时间片的操作..."<<std::endl;});    gfuncs.push_back([](){std::cout<<"我是一个检测进程时间片的操作..."<<std::endl;});     signal(SIGALRM,Handler);//信号处理器自定义设置闹钟响起时执行的任务     ::alarm(1);//设置1s后执行任务     while(true)//防止进程退出    {        pause();//暂停等待信号到来    }    return 0;}  注意闹钟设置是一次性的,所以在Handler执行方法中要再设置闹钟,不然就会暂停在那。 5) 硬件异常  硬件异常被硬件以某种⽅式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执⾏了除以0的指令, CPU的运算单元会产⽣异常, 内核将这个异常解释为SIGFPE信号发送给进程。再⽐如当前进程访问了⾮法内存地址, MMU会产⽣异常,内核将这个异常解释为SIGSEGV信号发送给进程。 除0异常:#include <stdio.h>#include <signal.h> void handler(int sig){    printf("catch a sig : %d\n", sig);} int main(){    signal(SIGFPE, handler); // 8) SIGFPE    sleep(1);    int a = 10;    a /= 0;    while (1)    ;    return 0;}   野指针异常:#include <stdio.h>#include <signal.h> void handler(int sig){    printf("catch a sig : %d\n", sig);}int main(){    signal(SIGSEGV, handler);    sleep(1);    int *p = NULL;    *p = 100;    while (1)        ;      由此可以确认,我们在C/C++当中除零,内存越界等异常,在系统层⾯上,是被当成信号处理的。 但是我们发现⼀直有8/11号信号产⽣被我们捕获,这是为什么呢?上⾯我们只提到CPU运算异常后,如何处理后续的流程,实际上 OS 会检查应⽤程序的异常情况,其实在CPU中有⼀些控制和状态寄存器,主要⽤于控制处理器的操作,通常由操作系统代码使用。状态寄存器可以简单理解为⼀个位图,对应着⼀些状态标记位、溢出标记位。OS 会检测是否存在异常状态,有异常存在就会调⽤对应的异常处理⽅法。除零异常后,我们并没有清理内存,关闭进程打开的⽂件,切换进程等操作,所以CPU中还保留上下⽂数据以及寄存器内容,除零异常会⼀直存在,就有了我们看到的⼀直发出异常信号的现象。访问⾮法内存其实也是如此,⼤家可以⾃⾏实验。   以上就是信号产生的五种方法,包括键盘输入、命令行产生、kill函数及系统调用、软件条件及硬件异常;但是无论产生信号是何种方式,发送信号的永远是操作系统!!!这是因为操作系统是进程的管理者。 6. 信号保存  6.1 信号其他相关常见概念信号递达(Delivery):实际执⾏信号的处理动作称为信号递达信号未决(Pending):信号从产⽣到递达之间的状态,称为信号未决阻塞:进程可以选择阻塞 (Block )某个信号。被阻塞的信号产⽣时将保持在未决状态,直到进程解除对此信号的阻塞,才执⾏递达的动作注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,⽽忽略是在递达之后可选的⼀种处理动作。 6.2 信号在内核中的表示  每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有⼀个函数指针表示处理动作。如图所示:     信号产⽣时,内核在进程控制块中设置该信号的未决标志,表示接收到信号,直到信号递达(也就是说信号处理完成)才清除该标志。  在上图的例⼦中,SIGHUP信号未产生(pending表SIGHUP信号标志位为0),也未阻塞(block表SIGHUP信号标志位为0)。SIGINT信号产⽣过,但正在被阻塞,所以暂时不能递达。SIGQUIT信号未产⽣过,⼀旦产⽣SIGQUIT信号将被阻塞,无法执行它的处理动作(也就是无法递达)。  如果SIGHUP信号产生时,操作系统会将SIGHUP信号发送给进程,进程pending表SIGHUP标志位就会由0置为1,进程在将自己的事情处理完后查看信号表,发现收到了SIGHUP信号而且没被阻塞,进而执行SIGHUP的handler方法——SIG_DEF表示默认处理方法。  如果SIGINT解除阻塞,那么进程在查看信号时就会执行SIGINT的处理方法——SIG_IGN(忽略)。 如果在进程解除对某信号的阻塞之前这种信号产⽣过多次,将如何处理? POSIX.1允许系统递送该信号⼀次或多次。Linux是这样实现的:常规信号在递达之前产⽣多次只计⼀次,⽽实时信号在递达之前产⽣多次可以依次放在⼀个队列⾥。本章不讨论实时信号。6.3 sigset_t  从上图来看,pending表中每个信号只有⼀个bit的未决标志, ⾮0即1, 不记录该信号产⽣了多少次;阻塞标志(block表)也是这样表示的。因此, 未决和阻塞标志可以⽤相同的数据类型sigset_t来存储, sigset_t称为信号集,这个类型可以表⽰每个信号的“有效”或“⽆效”状态。  在阻塞信号集中“有效”和“⽆效”的含义是该信号是否被阻塞, ⽽在未决信号集中“有 效”和“⽆效”的含义是该信号是否处于未决状态。 sigset_t的底层实现是一个整数类型,使用位操作来设置和获取各个信号的状态。阻塞信号集也叫做当前进程的信号屏蔽字这⾥的“屏蔽”应该理解为阻塞⽽不是忽略。 6.4 信号集操作函数  有了信号的操作类型——信号集sigset_t之后,我们就可以通过它对pending表和block表进行操作,函数如下: 1) 初始化sigset_t信号集函数int sigemptyset(sigset_t *set);int sigfillset(sigset_t *set);int sigaddset(sigset_t *set, int signo);int sigdelset(sigset_t *set, int signo);int sigismember(const sigset_t *set, int signo); 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置为1,表示该信号集的有效信号包括系 统⽀持的所有信号。注意,在使⽤sigset_ t类型的变量之前,⼀定要调⽤sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调⽤sigaddset和sigdelset在该信号集中添加或删除某种有效信号。这四个函数都是成功返回0,出错返回-1。sigismember是⼀个布尔函数,⽤于判断⼀个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。有了上述函数,我们就可以定义完一个信号集之后进行添加或删除等初始化操作。 2) 读取或更改未决信号集(pending)#include <signal.h>int sigpending(sigset_t *set); 读取当前进程的未决信号集(pending),通过set参数传出。调用成功则返回0,出错则返回-13) 读取或更改阻塞信号集函数(block)#include <signal.h>int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 返回值:若成功则为0,若出错则为-1如果oset是非空指针,则读取进程的当前信号屏蔽字(阻塞信号集)通过oset参数传出如果set是非空指针,则 更改进程的信号屏蔽字参数how指⽰如何更改。如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how可选参数及其含义:how参数 参数使用说明SIG_BLOCK set包含了我们希望添加到当前信号屏蔽字的信号,相当于mask=mask|setSIG_UNBLOCK set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&~setSIG_SETMASK 设置当前信号屏蔽字为set所指向的值,相当于mask=set如果调⽤sigprocmask解除了对当前若⼲个未决信号的阻塞,则在sigprocmask返回前,⾄少将其中⼀个信号递达。 下⾯是使用上述函数的示例代码: #include <iostream>#include <unistd.h>#include <cstdio>#include <sys/types.h>#include <sys/wait.h>//打印pending表void PrintPending(sigset_t &pending){    std::cout << "cur process[" << getpid() << "]pending: ";    for (int signo = 31; signo >= 1; signo--)    {        if (sigismember(&pending, signo))//如果pending表中有signo信号返回1            std::cout << 1;        else            std::cout << 0;    }    std::cout << "\n";}  void handler(int signo){    std::cout << signo << " 号信号被递达!!!" << std::endl;    std::cout << "-------------------------------" << std::endl;    sigset_t pending;    sigpending(&pending);//获取当前pending表    PrintPending(pending);//打印pending表    std::cout << "-------------------------------" << std::endl;}int main(){                            signal(2, handler); // 0.⾃定义捕捉2号信号       //  1. 屏蔽2号信号    sigset_t block_set, old_set;    sigemptyset(&block_set);//将自定义block_set设为0    sigemptyset(&old_set);//将自定义old_set设为0    sigaddset(&block_set, SIGINT); // 我们有没有修改当前进⾏的内核block表呢???1 0    // 1.1 设置进⼊进程的Block表中    sigprocmask(SIG_BLOCK, &block_set, &old_set); //SIG_BLOCK->block_set = mask|oldset 真正的修改当前进⾏的内核block表,完成了对2号信号的屏蔽!    int cnt = 10;    while (true)    {        // 2. 获取当前进程的pending信号集        sigset_t pending;        sigpending(&pending);        // 3. 打印pending信号集        PrintPending(pending);        cnt--;        // 4. 解除对2号信号的屏蔽        if (cnt == 0)        {            std::cout << "解除对2号信号的屏蔽!!!" << std::endl;            sigprocmask(SIG_SETMASK, &old_set, &block_set);        }        sleep(1);    }}  没有看懂的同学,可以回顾一下信号未决与阻塞的概念以及相关操作函数。 7. 信号捕捉 如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。 7.1 信号捕捉流程  由于信号处理函数的代码是在用户空间的,处理过程⽐较复杂,举例如下: 用户程序注册了 SIGQUIT 信号的处理函数 sighandler 。当前正在执行main 函数,这时发生中断或异常切换到内核态。在中断处理完毕后要返回用户态的 main 函数之前检查到有信号 SIGQUIT 递达。内核决定返回用户态后不是恢复 main 函数的上下⽂继续执行,而是执行 sighandler 函数, sighandler 和 main 函数使用不同的堆栈空间,它们之间不存在调⽤和被调⽤的关系,是两个独⽴的控制流程。sighandler 函数返回后自动执行特殊的系统调用 sigreturn 再次进⼊内核态。如果没有新的信号要递达,这次再返回用户态就是恢复 main 函数的上下文继续执行了。我们之前说过信号处理不是立即的,而是选择合适的时间,所以进程在进行信号处理的时间段如下图:  也就是说当接收到信号会被存储在pending表中,此时不会立即处理,等到进程从用户态与内核态切换时再检查pending表看是否有信号要处理。 我们可以简单理解: 用户态:执行我自己写的代码内核态:执行操作系统的代码7.2 sigaction函数#include <signal.h>int sigaction(int signo, const struct sigaction *act, struct sigaction *oact); sigaction函数可以读取和修改与指定信号相关联的处理动作。调⽤成功则返回0,出错则返回- 1。 signo:指定要操作的信号编号,例如 SIGINT、SIGTERM 等。 act:指向 sigaction 结构体的指针,根据act修改该信号的处理动作。如果不需要改变当前的信号处理方式,则可以设置为 NULL。 oact:指向 sigaction 结构体的指针,通过oact传出该信号原来的处理动作。如果不需要保存当前的信号处理方式,则可以设置为 NULL。 sigaction 结构体: struct sigaction {    void     (*sa_handler)(int); // 指定信号处理函数或特殊值 SIG_IGN 或 SIG_DFL    void     (*sa_sigaction)(int, siginfo_t *, void *); // 用于替代 sa_handler 的扩展信号处理器    sigset_t   sa_mask; // 额外的信号屏蔽字,在执行信号处理器期间阻止其他信号    int        sa_flags; // 特殊标志,影响信号传递行为    void     (*sa_restorer)(void); // 不再使用,应设为 NULL};  将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号;赋值为常数SIG_DFL表⽰执⾏系统默认动作;赋值为⼀个函数指针表示用自定义函数捕捉信号,或者说向内核注册了⼀个信号处理函数,该函数返回值为void,可以带⼀个int参数——通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是⼀个回调函数,不是被main函数调用,而是被系统所调用。代码示例如下: #include<iostream>#include<signal.h>void handler(int signo)//自定义处理动作{    std::cout<<"get a signal:"<<signo<<std::endl;}int main(){    struct sigaction act,oact;    act.sa_handler=handler;//将sa_handler赋值为一个函数指针,自定义2号信号处理动作    ::sigaction(2,&act,&oact);//oact保存的是2号信号原来的处理动作    while(true)    {        ::pause();//等待    }    return 0;}  其实和信号处理器功能一样,只是sigaction函数提供了对信号处理更精确的控制,相比于 signal 函数来说更为安全和灵活。 8. 结语  我们从信号定义、分类、处理谈到信号产生、信号保存最后到信号捕捉,关键在于信号处理的理解、相关的信号处理函数、信号保存的三张表——pending表、block表和handler表以及信号捕捉的理解与运用。总之,Linux进程信号是一种强大且灵活的进程间通信机制。通过合理地使用信号,可以实现进程间的异步通知、同步和通信等功能。以上就是Linux进程信号有关的内容啦~ 完结撒花~ 🥳🎉🎉————————————————                             版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/Renswc/article/details/144488673
  • [技术干货] 【Linux指南】文件管理高级操作(复制、移动、查找)-转载
    一、文件与目录复制:cp命令1. 基础复制:文件与单目录复制cp(copy的缩写)是Linux中最常用的复制命令 ,支持文件与目录的复制操作:# 复制文件到指定目录cp 源文件 目标目录# 示例:将config.ini复制到backup目录cp config.ini backup/# 复制并重命名文件cp 旧文件名 新文件名# 示例:复制并备份配置文件cp config.ini config.ini.bak# 复制目录(需加-r参数)cp -r 源目录 目标目录# 示例:复制整个项目目录cp -r project/ project_backup/核心参数:-r(recursive):递归复制目录及其内容,是复制目录的必备参数-f(force):强制复制,覆盖已存在的目标文件,不询问-i(interactive):交互式复制,覆盖前询问用户-v(verbose):显示复制过程,输出详细信息2. 高级复制:多文件与属性保留# 复制多个文件到同一目录cp 文件1 文件2 文件3 目标目录# 示例:复制所有脚本到bin目录cp *.sh bin/# 保留文件属性复制(如权限、时间戳)cp -a 源文件 目标文件# 示例:完整备份配置文件cp -a /etc/httpd/conf/httpd.conf /backup/httpd.conf# 仅复制目录结构(不复制文件内容)cp -dR --no-dereference 源目录 目标目录参数详解:-a(archive):相当于-r -p -d的组合,保留所有文件属性-p(preserve):保留文件的权限、所有者、时间戳等属性-d:复制符号链接本身,而非链接指向的文件--no-dereference:不跟随符号链接,复制链接文件本身3. 复制场景实战(1)系统配置备份# 备份整个/etc目录(重要!需root权限)cp -a /etc /backup/etc_bak# 备份单个配置文件并添加时间戳cp -a /etc/nginx/nginx.conf /backup/nginx.conf.$(date +%Y%m%d)(2)项目文件复制# 复制src目录下所有.java文件到build目录cp src/**/*.java build/ -t build/# 排除特定文件复制cp -r project/ project_backup/ --exclude "*.log"(3)跨设备复制# 复制文件到U盘(假设U盘挂载在/mnt/usb)cp important_file.txt /mnt/usb/# 复制目录到移动硬盘cp -r project/ /mnt/hdd/project_backup/4. 复制性能优化(1)大文件复制# 使用pv显示复制进度pv large_file.iso | cp > /backup/large_file.iso# 示例输出:1.2GB 0:02:10 [9.8MB/s] [====>                ] 11% ETA 0:17:20(2)断点续传(需第三方工具)# 安装rsync(系统默认已安装)yum install -y rsync# 断点续传复制rsync -avP source/ destination/(3)压缩复制# 压缩并复制目录tar -czf - source_dir/ | (cd destination_dir/; tar -xzf -)二、移动与重命名:mv命令1. 文件重命名# 重命名文件mv 旧文件名 新文件名# 示例:重命名脚本文件mv install.sh setup.sh# 批量重命名(配合通配符)mv *.txt *.md# 示例:将所有.txt文件重命名为.mdmv *.txt *.md重命名规则:目标文件名不能与当前目录下的已有文件重名支持使用通配符批量重命名重命名操作不改变文件内容,仅修改文件名2. 目录与文件移动# 移动文件到指定目录mv 文件名 目标目录# 示例:将日志文件移动到归档目录mv access.log archive/# 移动目录(无需额外参数)mv 源目录 目标目录# 示例:将项目移动到发布目录mv project/ release/# 跨磁盘分区移动(自动复制后删除源文件)mv large_file.iso /other_disk/移动原理:在同一文件系统内移动:仅修改文件指针,速度极快跨文件系统移动:本质是先复制后删除,速度受磁盘I/O影响移动目录时自动递归处理所有内容,无需-r参数3. 重命名与移动的组合应用(1)目录结构重构# 将src下的java目录重命名为java_src并移动到根目录mv src/java/ /java_src/# 调整项目目录结构mv project/{docs,documentation}/(2)批量文件整理# 按类型移动文件到对应目录mkdir -p images docs scriptsmv *.jpg *.png images/mv *.doc *.pdf docs/mv *.sh scripts/(3)临时文件清理# 将临时文件移动到垃圾桶mv temp_* trash/# 定期清理过期文件find . -name "*.log" -mtime +30 -exec mv {} old_logs/ \;4. 危险操作与安全提示(1)误操作风险场景: 误将目录移动到文件上(目标为已存在文件时,会覆盖该文件)示例错误: mv important_dir/ important_file后果: important_file被删除,important_dir被重命名为important_file(2)安全操作建议移动前先用ls -la确认源和目标对重要文件先备份:cp -a 源文件 源文件.bak && mv 源文件 目标使用-i参数开启交互模式:mv -i 文件名 目标复杂移动操作分步骤执行,避免一次性操作三、文件查找工具1. which:命令路径快速查找which命令用于查找可执行命令的路径,本质是在系统PATH变量指定的目录中搜索:# 查找命令路径which 命令名# 示例:查找ls命令的路径which ls# 输出示例:/bin/ls# 查找多个命令which ls cp mv工作原理:读取系统环境变量PATH的内容(如/usr/bin:/bin:/usr/sbin:/sbin)按顺序在每个目录中查找具有可执行权限的命令文件找到第一个匹配项后立即返回,不再继续搜索应用场景:确认命令是否存在及位置查看系统默认命令与自定义命令的优先级排查命令执行问题(如找不到命令时检查PATH设置)2. whereis:文件全方位定位whereis命令比which更强大,可查找命令、源代码和帮助文档的位置:# 查找文件位置whereis 文件名# 示例:查找ssh相关文件whereis ssh# 输出示例:# ssh: /usr/bin/ssh /usr/libexec/ssh /etc/ssh /usr/share/man/man1/ssh.1.gz /usr/share/man/man8/ssh.8.gz# 仅查找可执行文件whereis -b 文件名# 仅查找帮助文档whereis -m 文件名参数说明:-b:binary,仅查找可执行文件-s:source,仅查找源代码文件-m:manual,仅查找帮助文档-u:unfound,查找没有匹配项的文件3. find:全能型文件搜索工具find命令是Linux中最强大的文件搜索工具,支持按名称、类型、时间、大小等多种条件搜索(如果展开来讲,单独一个find命令都要写一篇文章):# 基本语法find 搜索路径 搜索条件 操作# 示例1:按名称搜索文件find /usr -name "ls"# 示例2:搜索当前目录下所有.txt文件find . -name "*.txt"# 示例3:搜索一天内修改过的文件find /var/log -mtime -1# 示例4:搜索大于100MB的文件find / -size +100M -type f核心搜索条件:名称相关:-name "模式":按文件名匹配(支持通配符)-iname "模式":不区分大小写匹配-regex "正则表达式":按正则表达式匹配时间相关:-mtime n:修改时间为n天前的文件(n为正整数)-mtime -n:n天内修改过的文件-mtime +n:n天前修改过的文件大小相关:-size n:精确大小为n的文件(b=字节,k=KB,M=MB,G=GB)-size +n:大于n的文件-size -n:小于n的文件类型相关:-type f:普通文件-type d:目录-type l:符号链接-type b:块设备-type c:字符设备4. find高级用法与操作(1)组合条件搜索# 搜索同时满足两个条件的文件find /path -条件1 -and -条件2# 示例:搜索/etc目录下大于1MB且修改时间在1周内的文件find /etc -size +1M -and -mtime -7# 搜索满足任意条件的文件find /path -条件1 -or -条件2# 示例:搜索.java或.py文件find src/ -name "*.java" -or -name "*.py"(2)对搜索结果执行操作# 对搜索结果执行命令(谨慎使用!)find 路径 条件 -exec 命令 {} \;# 示例1:删除10天前的临时文件find /tmp -name "temp_*" -mtime +10 -exec rm {} \;# 示例2:为所有.sh文件添加执行权限find . -name "*.sh" -exec chmod +x {} \;# 更安全的替代方案(使用xargs)find . -name "*.log" | xargs ls -la(3)深度限制与排除目录# 限制搜索深度(-maxdepth n)find /usr -maxdepth 2 -name "*.conf"# 排除指定目录(-path "模式" -prune)find /project -path "/project/test" -prune -o -name "*.java"四、通配符与命令路径:底层逻辑与高级技巧1. 通配符:批量操作的效率神器(1)基础通配符*:匹配任意数量的任意字符(包括0个)# 匹配所有以.txt结尾的文件ls *.txt# 匹配所有以a开头、.log结尾的文件rm a*.log?:匹配任意单个字符# 匹配文件名长度为5,且第3个字符为x的文件ls ??x??.txt# 重命名文件,将中间字符替换为amv file? .txt filea.txt[]:匹配括号内的任意一个字符# 匹配文件名以a、b或c开头的文件cp [abc]*.sh scripts/# 匹配扩展名是jpg、png或gif的图片mv image.[jpgpnggif] images/(2)扩展通配符(需启用bash扩展)# 启用扩展通配符shopt -s extglob# 示例1:匹配一个或多个数字ls *+([0-9]).txt# 示例2:匹配不是以.txt结尾的文件rm !(*.txt)# 示例3:匹配以a到f开头的文件ls @(a|b|c|d|e|f)*.sh2. 命令路径的底层逻辑(1)命令执行原理命令本质是可执行程序,存储在特定目录中(比如系统基础指令存放在 /bin路径下)系统通过PATH环境变量指定搜索命令的目录顺序执行命令时,系统按PATH顺序查找第一个匹配的可执行文件(2)查看与修改PATH# 查看当前PATHecho $PATH# 输出示例:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin# 临时添加目录到PATH(仅当前会话有效)PATH=$PATH:/new/directory# 永久添加目录到PATH(修改配置文件)echo 'export PATH=$PATH:/new/directory' >> ~/.bashrcsource ~/.bashrc(3)自定义命令优先级将自定义命令放在PATH靠前的目录(如~/bin)同名命令优先执行PATH中靠前的版本用which确认命令执行的实际路径3. 文件管理实战案例案例1:系统日志归档# 1. 查找一周前的日志文件find /var/log -name "*.log" -mtime +7 -type f# 2. 压缩并移动到归档目录find /var/log -name "*.log" -mtime +7 -type f -exec gzip {} \;find /var/log -name "*.log.gz" -mtime +7 -type f -exec mv {} /archive/logs/ \;案例2:项目文件清理# 1. 查找并删除临时文件find project/ -name "*.tmp" -exec rm {} \;# 2. 备份并删除大文件find project/ -size +100M -type f -exec cp {} backup/ \;find project/ -size +100M -type f -exec rm {} \;案例3:跨服务器文件同步# 使用rsync同步文件(增量更新,仅传输变化部分)rsync -avzP /local/source/ user@remote:/destination/# 选项说明:# -a:归档模式,保留所有属性# -v:详细输出# -z:传输时压缩# -P:显示进度并支持断点续传五、扩展知识:文件管理的高级工具与最佳实践1. rsync:高效的文件同步工具# 基本同步语法rsync [选项] 源路径 目标路径# 示例1:同步目录并显示进度rsync -avhP /data/ /backup/# 示例2:排除指定文件同步rsync -avhP /data/ /backup/ --exclude "*.log"# 示例3:远程同步(通过ssh)rsync -avzP /local/ user@remote:/remote/核心优势:增量同步:仅传输变化的部分,大幅减少数据量支持断点续传:中断后可继续传输保持文件属性:与cp -a效果一致远程同步:内置ssh支持,无需额外配置2. ln:链接文件的创建与管理(1)软链接(符号链接)# 创建软链接ln -s 源文件 链接文件# 示例:创建指向配置文件的软链接ln -s /etc/nginx/nginx.conf /usr/local/nginx/conf/nginx.conf(2)硬链接# 创建硬链接ln 源文件 链接文件# 示例:创建数据文件的硬链接ln data.txt data_hardlink.txt链接区别:软链接:类似Windows快捷方式,可跨文件系统,源文件删除后链接失效硬链接:共享同一个inode,不能跨文件系统,源文件删除后仍可通过硬链接访问3. 文件管理最佳实践(1)目录结构规范项目目录遵循src/(源代码)、docs/(文档)、bin/(可执行文件)的标准结构配置文件统一放在conf/目录,日志文件放在logs/目录临时文件使用tmp/目录,并定期清理(2)备份策略重要数据至少保留3份副本(本地、异地、云端)采用增量备份方式,减少存储占用定期验证备份数据的完整性和可恢复性(3)权限管理遵循"最小权限"原则,文件所有者为创建者,重要文件属主为root定期检查关键目录的权限(如/etc、/usr/bin)使用chmod和chown规范文件权限,避免使用777权限结语通过掌握文件复制、移动与查找的高级操作,读者将能够在Linux系统中高效管理文件资源,从简单的日常操作进阶到复杂的系统维护与数据管理。无论是cp的属性保留复制,mv的智能重命名,还是find的精准搜索,这些工具的灵活运用将成为Linux高手的必备技能。在实际应用中,建议结合rsync、ln等高级工具,建立规范的文件管理流程,确保系统数据的安全性与可维护性。————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/2302_78391795/article/details/148311368
  • [技术干货] grep输出关联的前后10行
    在 grep 中,要输出匹配行及其前后若干行内容,可以使用以下选项:1. 输出匹配行及其后 N 行(-A)grep -A 10 "pattern" file.txt-A 10 表示显示匹配行 及之后 10 行。2. 输出匹配行及其前 N 行(-B)grep -B 10 "pattern" file.txt-B 10 表示显示匹配行 及之前 10 行。3. 输出匹配行及其前后 N 行(-C)grep -C 10 "pattern" file.txt-C 10 表示显示匹配行 及前后各 10 行(相当于 -A 10 -B 10)。示例说明假设 file.txt 内容如下:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 搜索数字 5 并显示前后 2 行:grep -C 2 "5" file.txt输出:3 4 5 6 7 其他实用选项显示文件名(多文件搜索时):grep -C 10 "pattern" *.txt -H忽略大小写:grep -i -C 10 "pattern" file.txt仅显示匹配部分(而非整行):grep -o -C 10 "pattern" file.txt注意事项如果匹配结果相邻,grep 会用 -- 分隔不同匹配块的输出(避免内容混淆)。若需更复杂的上下文控制(如正则匹配上下文),可结合 awk 或 sed 实现。
  • 开发者空间云开发环境使用指导-Linux 案例建议反馈贴
    体验华为开发者空间案例:开发者空间云开发环境使用指导-Linux,反馈改进建议,请直接在评论区反馈即可。体验指导:cid:link_0
  • [技术干货] Linux主机互信配置指南:SSH密钥认证实战教程
    Linux主机互信配置指南:SSH密钥认证实战教程引言在分布式系统和自动化运维场景中,主机间的无密码互信访问是提升效率的关键。通过SSH密钥认证替代传统密码登录,不仅能增强安全性(避免密码泄露风险),还能为Ansible、Jenkins等工具提供无缝集成的基础。本文将详细介绍Linux主机间配置互信的完整流程,涵盖单台配置、批量部署及常见问题排查。一、SSH互信原理1. 非对称加密机制公钥(Public Key):可自由分发,用于加密数据或验证签名私钥(Private Key):必须严格保密,用于解密数据或生成签名认证过程:客户端用私钥对服务器发送的随机数签名服务器用客户端公钥验证签名验证通过即建立信任2. 与密码认证对比特性SSH密钥认证密码认证安全性无需传输密码,抗暴力破解易受字典攻击/中间人攻击便捷性一次配置永久免密每次需输入密码自动化支持完美支持需额外工具(如sshpass)二、单台主机互信配置1. 生成SSH密钥对(若不存在)# 生成RSA密钥(默认2048位) ssh-keygen -t rsa -b 4096 -C "admin@example.com" # 生成Ed25519密钥(推荐,更安全且性能更好) ssh-keygen -t ed25519 -C "admin@example.com" 参数说明:-t:指定算法(rsa/dsa/ecdsa/ed25519)-b:RSA密钥位数(至少2048)-C:添加注释(通常为用户名或用途)生成文件:~/.ssh/id_rsa(私钥)~/.ssh/id_rsa.pub(公钥)2. 配置目标主机授权# 将公钥上传到目标主机(假设用户为deploy) ssh-copy-id -i ~/.ssh/id_rsa.pub deploy@192.168.1.100 # 手动配置方式(若ssh-copy-id不可用) cat ~/.ssh/id_rsa.pub | ssh deploy@192.168.1.100 "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys" 3. 验证互信连接ssh -o PreferredAuthentications=publickey deploy@192.168.1.100 "hostname; uptime" 成功标志:直接进入目标主机命令行,无需输入密码三、批量主机互信配置方案方案1:使用循环脚本(适用于少量主机)#!/bin/bash USER="deploy" KEY_FILE="$HOME/.ssh/id_rsa.pub" HOSTS=("192.168.1.100" "192.168.1.101" "192.168.1.102") for host in "${HOSTS[@]}"; do echo "Configuring trust for $host..." ssh-copy-id -i "$KEY_FILE" "$USER@$host" if [ $? -eq 0 ]; then echo "✅ Success: $host" else echo "❌ Failed: $host" fi done 方案2:使用Ansible(推荐企业级方案)# playbooks/setup_ssh_trust.yml --- - name: Configure SSH Trust Between Hosts hosts: all gather_facts: no vars: ssh_user: deploy ssh_key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" tasks: - name: Ensure .ssh directory exists file: path: "/home/{{ ssh_user }}/.ssh" state: directory mode: 0700 owner: "{{ ssh_user }}" group: "{{ ssh_user }}" - name: Add public key to authorized_keys lineinfile: path: "/home/{{ ssh_user }}/.ssh/authorized_keys" line: "{{ ssh_key }}" create: yes mode: 0600 owner: "{{ ssh_user }}" group: "{{ ssh_user }}" state: present - name: Validate SSH connectivity command: "ssh -o BatchMode=yes -o ConnectTimeout=5 {{ ssh_user }}@{{ inventory_hostname }} echo 'Test successful'" delegate_to: localhost ignore_errors: yes register: ssh_test changed_when: false 执行命令:ansible-playbook -i inventory.ini playbooks/setup_ssh_trust.yml -K方案3:使用Puppet/Chef(适合已有配置管理平台的环境)# Puppet示例 file { '/home/deploy/.ssh/authorized_keys': ensure => file, owner => 'deploy', group => 'deploy', mode => '0600', content => epp('ssh_keys/authorized_keys.epp'), } 四、高级配置与安全加固1. 禁用密码认证(强制密钥登录)编辑/etc/ssh/sshd_config:PasswordAuthentication no ChallengeResponseAuthentication no UsePAM no PubkeyAuthentication yes生效命令:sudo systemctl restart sshd2. 使用SSH代理转发(跳板机场景)# 配置本地SSH配置文件 cat >> ~/.ssh/config <<EOF Host jumpbox HostName 192.168.1.1 User jumpuser Host backend* ProxyJump jumpbox User deploy EOF # 测试连接 ssh backend01 "hostname" 3. 限制特定密钥功能在~/.ssh/authorized_keys中添加选项:from="192.168.1.0/24",command="/usr/bin/rsync --server" ssh-rsa AAAAB3NzaC1... 常用限制选项:from="IP/CIDR":限制来源IPcommand="CMD":强制执行特定命令permitopen="host:port":限制端口转发no-agent-forwarding:禁用代理转发五、常见问题排查1. 连接被拒绝现象:ssh: connect to host port 22: Connection refused原因:SSH服务未运行或防火墙拦截解决方案:sudo systemctl status sshd sudo ufw allow 22/tcp # Ubuntu sudo firewall-cmd --add-service=ssh --permanent # CentOS 2. 权限错误现象:Permissions 0644 for '/home/user/.ssh/id_rsa' are too open原因:SSH对密钥文件权限要求严格解决方案:chmod 600 ~/.ssh/id_rsa chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys3. 密钥认证失败现象:User deploy not allowed because account is locked排查步骤:检查目标用户是否被锁定:sudo passwd -S deploy验证authorized_keys文件格式是否正确(每行一个公钥)查看SSH日志:journalctl -u sshd -n 50 --no-pager4. 已知主机变更问题现象:WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!原因:目标主机重装系统或IP被复用解决方案:ssh-keygen -R 192.168.1.100 # 删除旧记录 # 或手动编辑 ~/.ssh/known_hosts 删除对应行 六、最佳实践建议密钥管理:使用密码保护私钥(ssh-keygen -p可修改密码)考虑使用硬件安全模块(HSM)或云KMS存储高敏感密钥轮换策略:每6-12个月更换密钥对使用ssh-keygen -f old_key -e > new_key.pub生成新密钥审计监控:记录所有SSH登录事件:/var/log/auth.log使用工具如fail2ban防范暴力破解兼容性考虑:旧系统可能需要添加-oKexAlgorithms=diffie-hellman-group-exchange-sha256参数Windows主机需配置Win32-OpenSSH或使用PuTTY格式密钥总结通过SSH密钥认证实现主机互信,是构建安全自动化运维体系的基础步骤。本文从原理到实践,涵盖了从单台配置到批量部署的全流程,并提供了企业级安全加固方案。建议在实际环境中先在小规模测试,再逐步推广。掌握这些技能后,您将能轻松实现:自动化脚本的无密码执行集群节点的免密管理云原生环境的无缝集成立即开始配置您的主机互信环境,体验真正的"一键登录"吧!
  • [技术干货] Ansible 入门教程:从零开始掌握自动化运维利器
    Ansible 入门教程:从零开始掌握自动化运维利器引言在云计算和容器化时代,运维自动化已成为企业IT管理的标配。Ansible凭借其无代理架构、YAML语法和幂等性设计,迅速成为最受欢迎的自动化工具之一。本文将通过实战案例,带您快速掌握Ansible的核心概念与基础操作,实现从“手动运维”到“自动化管理”的跨越。一、Ansible 核心优势与适用场景1. 为什么选择 Ansible?无代理(Agentless):通过SSH或WinRM通信,无需在目标节点安装客户端声明式语言:使用YAML编写Playbook,直观描述系统状态幂等性:重复执行不会导致意外变更模块化设计:内置5000+模块(如yum、file、service)2. 典型应用场景批量服务器配置管理应用部署(如Nginx、Docker)持续交付(CI/CD流水线集成)云资源编排(AWS/Azure/GCP)安全合规检查二、环境准备与安装1. 控制节点要求Python 3.8+(推荐Python 3.10)目标节点需开启SSH服务(Linux)或WinRM(Windows)2. 安装方式(以Ubuntu为例)# 方法1:通过APT安装(推荐) sudo apt update sudo apt install ansible # 方法2:通过pip安装(获取最新版) pip install --user ansible # 验证安装 ansible --version3. 目录结构规划/ansible-demo/ ├── inventory/ # 主机清单目录 │ ├── dev.ini # 开发环境清单 │ └── prod.ini # 生产环境清单 ├── playbooks/ # Playbook目录 │ ├── web_setup.yml # Web服务器配置 │ └── db_backup.yml # 数据库备份 └── roles/ # 角色目录(可选)三、核心概念解析1. Inventory(主机清单)定义管理的主机和分组,支持INI或YAML格式:# inventory/dev.ini [webservers] web1 ansible_host=192.168.1.10 ansible_user=ubuntu web2 ansible_host=192.168.1.11 [dbservers] db1 ansible_host=192.168.1.20 [all:vars] ansible_ssh_private_key_file=~/.ssh/id_rsa关键参数:ansible_host:主机IP或域名ansible_user:SSH用户名ansible_port:SSH端口(默认22)ansible_ssh_pass:SSH密码(不推荐明文存储)2. Ad-Hoc 命令(临时命令)快速执行单条任务,格式:ansible <主机组> -m <模块> -a "<参数>" 实战示例:# 检查主机连通性 ansible all -m ping # 在webservers组安装Nginx ansible webservers -m apt -a "name=nginx state=present" --become # 查看Nginx服务状态 ansible webservers -m service -a "name=nginx state=started" --become3. Playbook(剧本)YAML格式的自动化脚本,定义一系列任务:# playbooks/web_setup.yml --- - name: Configure Web Servers hosts: webservers become: yes # 以root权限执行 vars: nginx_port: 8080 tasks: - name: Install Nginx apt: name: nginx state: present - name: Configure Nginx Port template: src: templates/nginx.conf.j2 dest: /etc/nginx/sites-available/default notify: Restart Nginx - name: Ensure Nginx is running service: name: nginx state: started enabled: yes handlers: - name: Restart Nginx service: name: nginx state: restarted关键元素:name:任务描述(必填)hosts:目标主机组tasks:任务列表handlers:触发器(由notify调用)vars:变量定义四、实战案例:部署WordPress网站1. 环境准备2台Ubuntu 22.04服务器(web1/web2)1台MySQL服务器(db1)2. 完整Playbook# playbooks/wordpress.yml --- - name: Setup Database Server hosts: dbservers become: yes vars: mysql_root_password: "SecurePass123!" mysql_db: wordpress mysql_user: wpuser mysql_password: "WpPass456!" tasks: - name: Install MySQL apt: name: ["mysql-server", "python3-pymysql"] state: present - name: Start MySQL Service service: name: mysql state: started enabled: yes - name: Create WordPress Database mysql_db: name: "{{ mysql_db }}" state: present login_user: root login_password: "{{ mysql_root_password }}" - name: Create MySQL User mysql_user: name: "{{ mysql_user }}" password: "{{ mysql_password }}" priv: "{{ mysql_db }}.*:ALL" state: present login_user: root login_password: "{{ mysql_root_password }}" - name: Setup Web Servers hosts: webservers become: yes vars: wp_version: 6.4.1 wp_checksum: "sha256:12345..." # 实际值需替换 tasks: - name: Install Dependencies apt: name: ["nginx", "php-fpm", "php-mysql", "wget", "unzip"] state: present - name: Download WordPress get_url: url: "wordpress.org/wordpress-{{ wp_version }}.zip" dest: /tmp/wordpress.zip checksum: "{{ wp_checksum }}" - name: Extract WordPress unarchive: src: /tmp/wordpress.zip dest: /var/www/html remote_src: yes owner: www-data group: www-data - name: Configure Nginx template: src: templates/nginx_wp.conf.j2 dest: /etc/nginx/sites-available/wordpress notify: Restart Nginx handlers: - name: Restart Nginx service: name: nginx state: restarted3. 执行Playbookansible-playbook -i inventory/prod.ini playbooks/wordpress.yml五、进阶技巧1. 变量管理Inventory变量:在group_vars/或host_vars/目录中定义Playbook变量:通过vars:或vars_files:引入注册变量:捕获命令输出- name: Get Uptime command: uptime register: uptime_result - debug: var=uptime_result.stdout2. 条件判断- name: Install Apache (if not present) apt: name: apache2 state: present when: ansible_os_family == "Debian"3. 循环结构- name: Install Multiple Packages apt: name: "{{ item }}" state: present loop: - curl - vim - htop4. 角色(Roles)将复杂任务拆分为可复用组件:roles/ ├── nginx/ │ ├── tasks/ │ │ └── main.yml │ ├── templates/ │ └── vars/ └── mysql/ 六、常见问题排查1. 连接失败错误:SSH connection failed检查目标主机SSH服务状态验证ansible_host和ansible_user配置使用-vvvv参数查看详细日志2. 权限问题错误:Failed to change ownership确保控制节点用户有目标目录的sudo权限在Playbook中添加become: yes3. 模块报错错误:module stderr: E: Could not open lock file检查目标主机包管理器是否被占用添加retries和delay参数重试七、总结与学习资源1. 核心收获掌握Inventory配置与分组管理熟练编写Ad-Hoc命令和Playbook理解变量、条件、循环等编程概念在Ansible中的应用2. 推荐学习路径官方文档:[Ansible Documentation]实战项目:使用Ansible部署Kubernetes集群编写自定义模块(Python)认证考试:Red Hat Certified Engineer (RHCE)Ansible Certified Specialist3. 工具生态Ansible Tower:企业级Web控制台(现改名为AWX开源版)Molecule:Playbook测试框架Ansible Lint:代码风格检查工具
  • [专题汇总] 【干货合集】7月份的干货合集来了,全面提示就看这里。
    炎热的天气不能影响学习技术的热情,7月份干货合集来了 ,希望可以帮到大家。1.详解MySQL中乐观锁与悲观锁的实现机制及应用场景https://bbs.huaweicloud.com/forum/thread-0232188895402649026-1-1.html2.C++寻位映射的究极密码:哈希扩展-转载https://bbs.huaweicloud.com/forum/thread-0288188731723925024-1-1.html3.【初阶数据结构】双向链表-转载https://bbs.huaweicloud.com/forum/thread-0278188731637851025-1-1.html4. 通俗易懂->哈希表详解-转载https://bbs.huaweicloud.com/forum/thread-02127188731515830029-1-1.html5.数组去重性能优化:为什么Set和Object哈希表的效率最高 -转载https://bbs.huaweicloud.com/forum/thread-0228188731443765029-1-1.html6.【数据结构】时间复杂度和空间复杂度-转载https://bbs.huaweicloud.com/forum/thread-0232188731388663024-1-1.html7.【HarmonyOS Next之旅】DevEco Studio使用指南(三十五) -> 配置构建(二)-转载https://bbs.huaweicloud.com/forum/thread-0278188730852978024-1-1.html8.在【k8s】中部署Jenkins的实践指南-转载https://bbs.huaweicloud.com/forum/thread-0255188730734591030-1-1.html9.Linux下CUDA安装全攻略 -转载https://bbs.huaweicloud.com/forum/thread-0228188730644737028-1-1.html10.Spring Boot 中的默认异常处理机制及执行流程【转载】https://bbs.huaweicloud.com/forum/thread-0228188730601126027-1-1.html11.MyBatis-Plus 自动赋值实体字段最佳实践指南【转载】https://bbs.huaweicloud.com/forum/thread-0278188730544024023-1-1.html12.【Linux】网络基础-转载https://bbs.huaweicloud.com/forum/thread-02127188730499410027-1-1.html13.一文详解php、jsp、asp和aspx的区别(小科普)【转载】https://bbs.huaweicloud.com/forum/thread-0278188730481700022-1-1.html14.如何使用Ajax完成与后台服务器的数据交互详解【转载】https://bbs.huaweicloud.com/forum/thread-0201188730427989026-1-1.html15.【Linux指南】Linux系统 -权限全面解析 -转载https://bbs.huaweicloud.com/forum/thread-0223188730394113017-1-1.html
总条数:1202 到第 页
上滑加载中