linux【网络编程】TCP协议通信模拟实现、日志函数模拟、守护进程化、TCP协议通信流程、三次握手与四次挥手

news2024/11/25 0:23:09

linux【网络编程】TCP协议通信模拟实现、日志函数模拟、守护进程化、TCP协议通信流程

  • 一、TCP通信简单模拟实现
    • 1.1 服务端实现
      • 1.1.1 接口认识
        • 1.1.1.1 listen:监听socket
        • 1.1.1.2 accept:获取连接
      • 1.1.2 tcpServer.hpp
      • 1.1.3 tcpServer.cc
    • 1.2 客户端实现
      • 1.2.1 接口认识
        • 1.2.1.1 connect:发起连接
      • 1.2.2 tcpClient.hpp
      • 1.2.3 tcpClient.cc
    • 1.3 优化方案
      • 1.3.1 TCP网络通信----多进程版
      • 1.3.2 TCP网络通信----多线程版
  • 二、日志函数编写
  • 三、守护进程
    • 3.1 引入:为什么需要守护进程化
    • 3.2 进程,守护进程化
  • 四、TCP协议通信流程
    • 4.1 三次握手与四次挥手感性认识

一、TCP通信简单模拟实现

Tcp通信模拟实现与Udp通信模拟实现的区别不大,一个是面向字节流,一个是面向数据报;udp协议下拿到的数据可以直接发送,tcp协议下需要创建链接,用文件描述符完成数据的读写

1.1 服务端实现

1.1.1 接口认识

1.1.1.1 listen:监听socket

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
sockfd:创建的套接字
backlog:新连接队列的长度限制

1.1.1.2 accept:获取连接

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd:创健的套接字,仅用于监听新链接
addr:结构体,这里说网络通信,用sockaddr_in
addrlen:结构体大小
返回值:一个新的文件描述符(套接字),这个才是和客户端通信的文件描述符

通信就用accept返回的文件描述符,面向字节流,后续都是文件操作

1.1.2 tcpServer.hpp

#pragma once
#include "logMessage.hpp"
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <unistd.h>
namespace Server
{
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR
    };

    using namespace std;

    static const uint16_t gport = 8080;
    static const uint16_t gbacklog = 5;
    class tcpServer
    {
    public:
        tcpServer(const uint16_t &port = gport)
            : listen_sockfd_(-1), port_(port)
        {
        }

        void InitServer()
        {
            // 1.创建socket
            listen_sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
            if (listen_sockfd_ < 0)
            {
                logMessage(FATAL, "create socket error");
                exit(SOCKET_ERR);
            }
            logMessage(NORMAL, "create socket success");

            // 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(listen_sockfd_,(struct sockaddr*)&local,sizeof(local))<0)
            {
                logMessage(FATAL,"bind socket error");
                exit(BIND_ERR);
            }

            logMessage(NORMAL,"bind socket success");
            
            //3.设置socket为监听状态
            if(listen(listen_sockfd_,gbacklog)<0)
            {
                logMessage(FATAL,"listen socket error");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL,"listen socket success");

        }
        void start()
        {
            for(; ;)
            {
                //4.server获取新链接
                struct sockaddr_in peer;
                socklen_t len=sizeof(peer);
                //sock:和客户端通信的文件描述符
                int sock=accept(listen_sockfd_,(struct sockaddr*)&peer,&len);
                if(sock<0)//没有获取新链接成功就执行下一次循环
                {
                    logMessage(FATAL,"accpect sock error");
                    continue;
                }
                logMessage(NORMAL,"accept sock success");
                std::cout<<"sock"<<sock<<endl;

                //5.通信就用sock文件描述符,面向字节流,后续都是文件操作
                /*version1*/
                serverIO(sock);
                close(sock);
            }
        }
        void serverIO(int sock)
        {
            char buffer[1024];
            while (true)
            {
                ssize_t n=read(sock,buffer,sizeof(buffer)-1);
                if(n>0)
                {
                    buffer[n]=0;
                    std::cout<<"recv message "<<buffer<<endl;
                    string outbuffer=buffer;
                    outbuffer+="server[echo]";
                    write(sock,outbuffer.c_str(),outbuffer.size());
                }
                else if(n==0)
                {
                    //客户端退出
                    logMessage(NORMAL,"client quit ,me too!!");
                    break;
                }
            }
            
        }
        ~tcpServer()
        {
        }

    private:
        int listen_sockfd_;//不负责通信,只负责监听链接,获取新链接
        uint16_t port_;
    };
}

1.1.3 tcpServer.cc

#include "tcpServer.hpp"
#include <memory>

using namespace std;
using namespace Server;
static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}


int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t serverport=atoi(argv[1]);
    unique_ptr<tcpServer> tsvr(new tcpServer(serverport));
    tsvr->InitServer();
    tsvr->start();
    return 0;

}

1.2 客户端实现

1.2.1 接口认识

1.2.1.1 connect:发起连接

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数与accept一样,代表的含义也一样
返回值:成功0,失败-1

1.2.2 tcpClient.hpp

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

namespace Client
{
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR
    };
    using namespace std;
    class tcpClient
    {
    public:
        tcpClient(const string &clientip, const uint16_t &clientport)
            : clientip_(clientip), clientport_(clientport), sockfd_(-1)
        {
        }
        void InitCient()
        {
            // 1.创建socket
            sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
            if (sockfd_ < 0)
            {
                std::cerr << "socket create error" << endl;
                exit(2);
            }
        }
        void run()
        {
            struct sockaddr_in server;
            memset(&server, 0, sizeof(server));
            server.sin_family = AF_INET;
            server.sin_port = htons(clientport_);
            server.sin_addr.s_addr = inet_addr(clientip_.c_str());

            // 发起链接
            if (connect(sockfd_, (struct sockaddr *)&server, sizeof(server)) != 0)
            {
                std::cerr << "connect create error" << endl;
            }
            else
            {
                string msg;
                while (true)
                {
                    cout << "Enter# ";
                    getline(cin, msg);
                    write(sockfd_, msg.c_str(), msg.size());

                    char buffer [1024];
                    ssize_t n=read(sockfd_,buffer,sizeof(buffer)-1);
                    if (n>0)
                    {
                        buffer[n]=0;
                        cout<<"Server处理后为# "<<buffer<<endl;

                    }
                    else
                    {
                        break;
                    }
                    

                }
            }
        }
        ~tcpClient() {
            if(sockfd_>=0) close(sockfd_);
        }

    private:
        int sockfd_;
        uint16_t clientport_;
        string clientip_;
    };
}

1.2.3 tcpClient.cc

#include "tcpClient.hpp"
#include <memory>
using namespace std;
using namespace Client;
static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << " server_ip server_port\n\n";
}


int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t clientport=atoi(argv[2]);
    string clientip=argv[1];

    unique_ptr<tcpClient> ucli(new tcpClient(clientip,clientport));
    ucli->InitCient();
    ucli->run();
    return 0;
}

上述代码是一个单进程的版本,一个链接过来会去死循环执行serverIO,也就是说同一时间只能有一个链接过来通信,其他的链接必须阻塞等待上一个链接退出

1.3 优化方案

1.3.1 TCP网络通信----多进程版

更该tcpServer.hpp中的start函数即可,其他文件和单进程版一致

void start()
{
    logMessage(NORMAL, "Thread init success");
    for (;;)
    {
        // 4.server获取新链接
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        // sock:和客户端通信的文件描述符
        int sock = accept(listen_sockfd_, (struct sockaddr *)&peer, &len);
        if (sock < 0) // 没有获取新链接成功就执行下一次循环
        {
            logMessage(FATAL, "accpect sock error");
            continue;
        }
        logMessage(NORMAL, "accept sock success,get new sock:%d", sock);

        /*******************************************version2多进程版*/

        pid_t id = fork();
        if (id == 0) // 子进程
        {
            // 关闭子进程不需要的文件描述符
            close(listen_sockfd_);

            // 子进程退出,父进程回收资源,孙子进程去执行任务
            // 孙子进程成为孤儿进程,1号进程托管并回收其退出资源
            if (fork() > 0)
                exit(0); 

            // 孙子进程
            serverIO(sock);
            close(sock);//任务完成关闭文件描述符
            exit(0); 
        }
        
        // 细节:父进程必须关闭子进程的sock,避免一直被占用
        // 这里的关闭并不是完全关闭,只是引用计数减一,并不影响孙子进程
        close(sock); // 获取之后立马关闭,多次链接出现sock都一样,也可能不一样

        // 父进程,阻塞等待子进程退出
        pid_t ret = waitpid(id, nullptr, 0);
        if (ret > 0)
        {
            std::cout << "waitsuccess" << ret << endl;
        }
    }
}     
  1. 父进程必须关闭子进程的sock,避免一直被占用
    这里的关闭并不是完全关闭,只是引用计数减一,并不影响孙子进程
  2. waitpid这里不能单纯用非阻塞等待,当有多个连接到来的时候,并且有一个进程退出,父进程非阻塞等待,去执行accept,但是如果后续没有连接来了,就一直阻塞在accept,剩下的子进程就没法回收了

前面【信号】中曾说道子进程退出时会发送SIGCHLD信号,我们可以对其设置捕捉,忽略掉其行为,父进程就不需要阻塞等待了

void start()
{
    logMessage(NORMAL, "Thread init success");
   	signal(SIGCHLD,SIG_IGN);//设置信号忽略行为
    for (;;)
    {
        // 4.server获取新链接
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        // sock:和客户端通信的文件描述符
        int sock = accept(listen_sockfd_, (struct sockaddr *)&peer, &len);
        if (sock < 0) // 没有获取新链接成功就执行下一次循环
        {
            logMessage(FATAL, "accpect sock error");
            continue;
        }
        logMessage(NORMAL, "accept sock success,get new sock:%d", sock);

        /*******************************************version2多进程版*/

        pid_t id = fork();
        if (id == 0) // 子进程
        {
            // 关闭子进程不需要的文件描述符
            close(listen_sockfd_);
            // 子进程
            serverIO(sock);
            close(sock);//任务完成关闭文件描述符
            exit(0); 
        }
        
        // 细节:父进程必须关闭子进程的sock,避免一直被占用
        // 这里的关闭并不是完全关闭,只是引用计数减一,并不影响孙子进程
        close(sock); // 获取之后立马关闭,多次链接出现sock都一样,也可能不一样

       
    }
}     

1.3.2 TCP网络通信----多线程版

在tcpServer类外添加ThreadData类,类内修改start函数,添加threadRoutinue函数其余不变

    class tcpServer;
    class ThreadData
    {
    public:
        ThreadData(tcpServer *self, int sockfd)
            : self_(self), sockfd_(sockfd)
        {
        }

    public:
        tcpServer *self_;
        int sockfd_;
    };
    
void start()
        {
            // 初始化线程池
            ThreadPool<Task>::GetInstance()->run();
            logMessage(NORMAL, "Thread init success");
            // signal(SIGCHLD,SIG_IGN);
            for (;;)
            {
                // 4.server获取新链接
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                // sock:和客户端通信的文件描述符
                int sock = accept(listen_sockfd_, (struct sockaddr *)&peer, &len);
                if (sock < 0) // 没有获取新链接成功就执行下一次循环
                {
                    logMessage(FATAL, "accpect sock error");
                    continue;
                }
                logMessage(NORMAL, "accept sock success,get new sock:%d", sock);

                

                /*******************************************version3:多线程版本*/
                 pthread_t tid;
                 ThreadData* td=new ThreadData(this,sock);
                 pthread_create(&tid,nullptr,threadRoutinue,td);

                
            }
        }
        //类内调用,静态方法
         static void* threadRoutinue(void* args)
         {
             pthread_detach(pthread_self());
             ThreadData* td= static_cast<ThreadData*>(args);
             td->self_->serverIO(td->sockfd_);
             //在一个进程中的所有线程都可以访问到文件描述符表,属于共享资源,
             //一个线程所对应的fd在使用完毕后需要进行关闭。
             close(td->sockfd_);
             delete td;
             return nullptr;
         }

多进程版,多线程版,线程池版,可参考我的Gitee

二、日志函数编写

在计算机中,日志文件是记录在操作系统或其他软件运行中发生的事件或在通信软件的不同用户之间的消息的文件。记录是保持日志的行为。在最简单的情况下,消息被写入单个日志文件。

我们借助可变参数列表来模拟实现日志函数
实现格式如:[日志等级][时间][pid][message]

#pragma once
#include <iostream>
#include <string>
#include <stdarg.h>
#include <ctime>
#include <unistd.h>

//把错误信息写到指定文件

#define LOG_NORMAL "log_nrl.txt"
#define LOG_ERR "log_err.txt"



#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4

//typedef char* va_list

const char * to_levelstr(int level)
{
    switch(level)
    {
        case DEBUG : return "DEBUG";
        case NORMAL: return "NORMAL";
        case WARNING: return "WARNING";
        case ERROR: return "ERROR";
        case FATAL: return "FATAL";
        default : return nullptr;
    }
}

//void logMessage(DEBUG,"sss %f %d %c",2.1,6,'h')
void logMessage(int level,const char* format,...)
{
#define NUM 1024
    char logprefix[NUM];

    //前半部分[日志等级][时间][pid]
    snprintf(logprefix,sizeof(logprefix),"[%s][%ld][%d]",to_levelstr(level),(long int)time(nullptr),getpid());

    char logcontent[NUM];
    va_list arg;
    //初始化arg为参数列表中的第一个参数的地址
    va_start(arg,format);

    //后半部分,错误信息
    //vsnprintf()函数的作用是将可变参数列表arg中的数据按照指定的格式format写入缓冲区logcontent中
    vsnprintf(logcontent,sizeof(logcontent),format,arg);  

   

//这里做了简化,实际上是一个等级一个日志文件
    FILE* log=fopen(LOG_NORMAL,"a");
    FILE* err=fopen(LOG_ERR,"a");

    if(log !=nullptr && err!=nullptr)
    {
       
        if(level==DEBUG || level==NORMAL||level==WARNING)
        {
            fprintf(log,"%s%s\n",logprefix,logcontent);
        }

        if(level==ERROR || level==FATAL)
        {
            fprintf(err,"%s%s\n",logprefix,logcontent);
        }

        fclose(log);
        fclose(err);
    }

}

三、守护进程

3.1 引入:为什么需要守护进程化

守护进程又叫精灵进程—本质孤儿进程的一种有了守护进程,上述的服务端才能变成一个真正的服务端

在这里插入图片描述
1.当我们使用xsell链接远端云服务器的时候,打开的页面第一个出现的就是bash命令行,这个时候我们输入sleep 10000 | sleep 20000 |sleep 30000 &就可以添加一个后台任务。

在这里插入图片描述
2.在命令行运行sleep 40000 | sleep 50000 |sleep 60000 &后,查看进程,发现新创建的三个进程PGID一样,属于同一个组,完成一个任务,与之前的任务同属于一个会话(SID都一样)
在这里插入图片描述
3.通过查看SID进程发现,是bash:会话ID是以bash命名的
在这里插入图片描述
4.前后台任务切换
fg + 作业编号切换指定任务到前台
ctrl+z暂停任务:bash自动切换到前台
bg +作业编号指定任务stop->run
在这里插入图片描述
当我们进行网络通信的时候,如果服务器关机或注销了,任务就可能会被清理,导致客户端发送的消息无响应,这显然与真正的服务器不一样,我们需要把服务任务自成会话,自成进程组,不受终端设备影响-----守护进程

3.2 进程,守护进程化

#include <unistd.h>

pid_t setsid(void);//必须是非组长调用

setsid 用于在一个新的会话中启动一个进程。在运行 setsid 命令时,所启动的进程将会脱离当前的终端会话,并在一个新的会话中运行,这样它就不会受到终端会话关闭或挂起的影响,而可以持续运行。

#pragma once
#include <unistd.h>
#include <signal.h>
#include <cstdlib>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define DEV "/dev/null"  //垃圾站,写进的内容全丢弃

//自建一个会话,组长就是自己,调用函数的不能是组长
//调用完毕之后成为组长
void daemonSelf(char* currPath=nullptr)
{
    //1.让调用进程忽略掉异常的信号
    //客户端已经退出,服务端再写会崩溃
    signal(SIGPIPE,SIG_IGN);

    //2.不是组长,调用setsid
    if(fork()>0) exit(0);
    
    //子进程---守护进程又叫精灵进程---本质孤儿进程
    pid_t n=setsid();
    assert(n!=-1);

    //3.守护进程是脱离终端的,关闭或重定向以前进程默认打开的文件

    //      /dev/null 垃圾站,写进的内容全丢弃
    int fd=open(DEV,O_RDWR);
    if(fd>=0)
    {
        dup2(fd,0);
        dup2(fd,1);
        dup2(fd,2);

        //012已经执行devil/null
        close(fd);
    }
    else
    {
        close(0);
        close(1);
        close(2);
    }

    //4.可选:进程执行路径发生更改
    //chdir:将进程的当前工作目录更改为 currpath 参数指定的目录
    if(!currPath) chdir(currPath);


}

在服务端初始化完毕之后,启动之前执行daemonSelf()函数,再启动服务端,查看进程就可以得到以下信息
在这里插入图片描述
父进程id为1证明这个进程是一个孤儿进程,而且可以发现这个进程的PID,PGID,GID都一样,这就是自成会话,自成进程组的守护进程!!!

这个时候,即便关闭终端,只要不kill掉这个进程,他就会在一直运行响应客户端

四、TCP协议通信流程

在这里插入图片描述
建立连接的过程:

  • 调用socket, 创建文件描述符;
  • 调用connect, 向服务器发起连接请求;
  • connect会发出SYN段并阻塞等待服务器应答; (第一次)
  • 服务器收到客户端的SYN, 会应答一个SYN-ACK段表示"同意建立连接"; (第二次)
  • 客户端收到SYN-ACK后会从connect()返回, 同时应答一个ACK段; (第三次)
    这个建立连接的过程, 通常称为 三次握手

断开连接的过程:

  • 如果客户端没有更多的请求了, 就调用close()关闭连接, 客户端会向服务器发送FIN段(第一次);
  • 此时服务器收到FIN后, 会回应一个ACK, 同时read会返回0 (第二次);
  • read返回之后, 服务器就知道客户端关闭了连接, 也调用close关闭连接, 这个时候服务器会向客户端发送一个FIN; (第三次)
  • 客户端收到FIN, 再返回一个ACK给服务器; (第四次)

4.1 三次握手与四次挥手感性认识

三次握手—建立连接:
女方对男方说想谈恋爱,男方答应并问到什么时候开始,女方说现在

四次挥手—断开连接:
比如女方对男方说离婚,男方回复离婚
同时男方反应一会觉得不行,凭什么你先对我说,我也得休了你,然后对女方说离婚,女方回复离婚

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

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

相关文章

软考知识点---08IP地址与域名地址

一、IP地址 &#xff08;一&#xff09;什么是IP地址&#xff1f; 连入互联网的计算机&#xff0c;每台计算机或者路由器都有一个由授权机构分配的号码&#xff0c;IP地址代表这一台计算机在网络中的地址 在同一个网络中IP地址是唯一的 IP&#xff08;IPV4&#xff09;地…

音频的各项指标

对于下面data和linesize的解释(参考下面3.4中的av_samples_alloc_array_and_samples函数说明)&#xff1a; 1&#xff09;data是通道的意思&#xff0c;例如双通道&#xff0c;data[0]代表左声道&#xff0c;data[1]代表右声道。 2&#xff09;linesize为采样个数的最大大小字…

ChatGPT全栈开发实战:从需求分析到数据可视化,一站式指南助你快速构建全面应用

文章目录 序章&#xff1a;PDF版下载第一章&#xff1a;Java后端开发1.需求分析1.1 项目分析1.2 开发计划1.3 风险评估1.4 需求增强1.5 需求转情景 2.生成代码2.1 解析文件2.2 数据结构2.3 算法策略2.4 异步处理 3.Bug修改3.1 逻辑错误3.2 性能问题3.3 资源泄露3.4 死锁问题3.5…

MySQL之存储过程和存储函数

1. 存储过程概念 能够将完成特定功能的SQL指令进行封装(SQL指令集)&#xff0c;编译之后存储在数据库服务器上&#xff0c;并且为之取一个名字&#xff0c;客户端可以通过名字直接调用这个SQL指令集&#xff0c;获取执行结果。 2. 存储过程优缺点 2.1 优点 &#xff08;1&am…

【SpringCloud】二、服务注册发现Eureka与负载均衡Ribbon

文章目录 一、Eureka1、服务提供者与消费者2、Eureka原理分析3、搭建Eureka4、服务注册5、模拟多服务实例启动6、服务的发现 二、Ribbon1、负载均衡的原理2、源码分析3、负载均衡策略4、饥饿加载 一、Eureka 1、服务提供者与消费者 服务提供者&#xff1a;一次业务中&#xf…

Elastic-Job原理

Elastic-Job作业类型创建任务并执行 &#xff1a;启动流程弹性分布式实现 Elastic-Job elastic-job&#xff08;quartz的扩展&#xff09;使用了quartz的调度机制&#xff0c;内部原理一致&#xff0c;使用注册中心(zookeeper)替换了quartz的jdbc数据存储方式&#xff0c;支持…

ubuntu22.04切换回Xorg使用flameshot截图的问题

在ubuntu20.04时使用flameshot一切正常. 升级到ubuntu22.04之后,发现flameshot不能使用快捷键区域截图了,这个就很不方便. 在网上找了一圈后先是修改文件 /etc/gdm3/custom.conf将里面的 #WaylandEnableflase改成 WaylandEnablefalse即配置为不使用Wayland.然后重启系统,后…

动态通讯录实现(C语言)

目录 前言&#xff1a; 一&#xff1a;单个节点的设计和主逻辑 结点设计 主逻辑 二&#xff1a;接口实现 (1)生成一个新的结点 (2)增加信息 (3)打印信息 (4)查找 (5)删除信息 (6)修改信息 (7)排序 插入排序 快速排序 (8)已有数据读取 (9)更新数据录入 三&…

C语言复习笔记3

1.标识符常量和宏函数&#xff08;宏函数是简单替换所以需要把括号加到位&#xff09; #include<stdio.h>#define MAX 1000//标识符常量 #define num 10 //#define SUM(X,Y) XY //不对 #define SUM(X,Y) ((X)(Y))int max(int a, int b) {return a>b?a:b; }int main(…

系列八、vue配置请求

一、vue2配置请求转发 config/index.js proxyTable配置后端的请求地址 proxyTable: {/: {target: "http://localhost:9000", // 后端服务器地址changeOrigin: true,pathRewrite: {^/: }} }, 注意事项&#xff1a;vue2中不像大多数教程里边讲的那样&#xff0c;直接…

Apache NiFi:实时数据流处理的可视化利器【上进小菜猪大数据系列】

上进小菜猪&#xff0c;沈工大软件工程专业&#xff0c;爱好敲代码&#xff0c;持续输出干货。欢迎订阅本专栏&#xff01; Apache NiFi是一个强大的、可扩展的开源数据流处理工具&#xff0c;广泛应用于大数据领域。本文将介绍Apache NiFi的核心概念和架构&#xff0c;并提供…

路由守卫的几种方式-M

vue的路由 Vue-router是Vue.js官方的路由插件。vue的单页面应用是基于路由和组件的&#xff0c;路由用于设定访问路径&#xff0c;并将路径和组件映射起来。传统的页面应用&#xff0c;是用一些超链接来实现页面切换和跳转的。在vue-router单页面应用中&#xff0c;则是路径之…

C# | KMeans聚类算法的实现,轻松将数据点分组成具有相似特征的簇

C# KMeans聚类算法的实现 文章目录 C# KMeans聚类算法的实现前言示例代码实现思路测试结果结束语 前言 本章分享一下如何使用C#实现KMeans算法。在讲解代码前先清晰两个小问题&#xff1a; 什么是聚类? 聚类是将数据点根据其相似性分组的过程&#xff0c;它有很多的应用场景&…

章节1:信息收集

章节1:信息收集 1 信息收集概览 01 为什么要做信息收集&#xff1f; 渗透测试的流程 确定目标 信息收集 漏洞扫描 漏洞利用 形成报告 信息收集包括的内容 域名信息、IP段、开放的端口、网站架构、文件目录结构、软件版本、WAF、旁站、C段… 分类 域名相关信息IP相关…

Redis缓存数据库(四)

目录 一、概述 1、Redis Sentinel 1.1、docker配置Redis Sentinel环境 2、Redis存储方案 2.1、哈希链 2.2、哈希环 3、Redis分区(Partitioning) 4、Redis面试题 一、概述 1、Redis Sentinel Redis Sentinel为Redis提供了高可用解决方案。实际上这意味着使用Sentinel…

Java 与排序算法(1):冒泡排序

一、冒泡排序 冒泡排序&#xff08;Bubble Sort&#xff09;是一种简单的排序算法&#xff0c;它的基本思想是通过不断交换相邻两个元素的位置&#xff0c;使得较大的元素逐渐往后移动&#xff0c;直到最后一个元素为止。冒泡排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2)&…

《Kali渗透基础》02. 基本工具

kali渗透 1&#xff1a;基本工具1.1&#xff1a;NetCat1.1.1&#xff1a;命令参数1.1.2&#xff1a;示例 1.2&#xff1a;NCat1.2.1&#xff1a;命令参数1.2.2&#xff1a;示例 1.3&#xff1a;WireShark1.4&#xff1a;TCPdump1.4.1&#xff1a;命令参数1.4.2&#xff1a;示例…

C语言——函数

目录 1. 函数基本用法1.1 定义和三要素1.2 函数的声明和定义1.2.1 函数声明1.2.2 函数定义格式 1.3 函数调用1.4 函数传参1.4.1 值传递1.4.2 地址传递1.4.3 数组传递 1.5 函数和栈区 2. 开辟堆空间2.1 堆的概念2.2.malloc函数2.2.1 定义2.2.2 用法 2.3 free()函数定义注意&…

随机数发生器设计(一)

1 随机数发生器设计概述 密码行业的随机数发生器总体框架标准为GM/T 0103。随机数发生器可以分为硬件随机数发生器和软件随机数发生器。 硬件随机数发生器一般以组成部件的形式集成在安全芯片的内部&#xff0c;或者随机数发生器本身就是安全芯片。考虑到随机数发生器是密码产…

ChatGPT 能自己跑代码了!

公众号关注 “GitHubDaily” 设为 “星标”&#xff0c;每天带你逛 GitHub&#xff01; time leap, sci-fi, photorealistic, --niji 5 --ar 3:2 --s 1000 自 ChatGPT 发布以来&#xff0c;各行各业对其能力探索的举措一直没有停止。 很多大厂纷纷跟进&#xff0c;竞相推出自研…