TCP通信

news2024/11/25 6:48:39

目录

一.服务端

1.1创建套接字与绑定

1.2监听

1.3服务端获取连接

 1.4服务端提供服务

二.客户端

2.1创建套接字

 2.2客户端获取连接

 2.3客户端发送需求

三.填充命令行参数

3.1客户端

3.2服务端

3.3结果测试

 四.线程版本服务端

4.1线程版

4.2线程池版


一.服务端

与上文介绍的UDP网络通信大概类似,部分函数的接口相同。

1.1创建套接字与绑定

class ServerTcp
{
public:
    ServerTcp(uint16_t port, std::string ip = " ")
        : ip_(ip), port_(port), listenSock_(-1)
    {
    }

    void init()
    {
        listenSock_ = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字
        if (listenSock_ < 0)
        {
            std::cerr << "创建套接字失败\n";
            exit(-1);
        }
        std::cout << "创建套接字成功"
                  << " "
                  << "listenSock_="
                  << listenSock_ << std::endl;

        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = PF_INET;
        local.sin_port = htons(port_);
        ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), 
        &local.sin_addr));
        if (bind(listenSock_, (const struct sockaddr *)&local, sizeof local) < 0) // 绑定
        {
            std::cerr << "绑定失败\n";
            exit(-2);
        }
        std::cout << "绑定成功" << std::endl;

private:
    int listenSock_;
    std::string ip_;
    uint16_t port_;
};

补充内容:

1.如果用的是云服务器,在填充服务器的IP地址时,不需要显示绑定IP地址,直接将IP地址设置为INADDR_ANY即可,此时服务器就可以从本地任何一张网卡当中读取数据。

2.创建套接字时所需的服务类型应该是SOCK_STREAM,在编写TCP网络通信时,SOCK_STREAM提供的是一个有序的、可靠的、全双工的、基于连接的流式服务。

1.2监听

TCP服务器是面向连接的,客户端向服务器发送数据时,要确保二者已经建立了关联,将套接字设置为监听状态,然后去监听socket。

函数listen:

int listen(int sockfd, int backlog);

参数解释:

sockfd:需要设置为监听状态的套接字对应的文件描述符。

backlog:全连接队列的最大长度。若有多个客户端同时发来连接请求,此时未被服务器处理的连接就会放入连接队列,该参数代表的就是这个全连接队列的最大长度。

返回值说明:成功返回0,失败返回-1。

  if (listen(listenSock_, 5 ) < 0)
   {
      exit(-2);
   }
  std::cout << "listen success\n";

1.3服务端获取连接

TCP服务器在与客户端进行网络通信之前,服务器需要先获取到客户端发送的连接请求。

accept函数:

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

 参数解释:

返回值说明:其返回值也是一个文件描述符,前面第一次创建的listensock_是用于不断的去获取客户端发来的连接请求,收到连接请求后会再创建一个套接字(也就是其返回值),该套接字(也就是其返回值)用于为本次accept获取到的连接提供服务。

sockfd:特定的监听套接字,表示从该监听套接字中获取连接,进行通信。

addr:对端网络相关的属性信息,包括协议家族、IP地址、端口号等。

addrlen:调用时传入期望读取的addr结构体的长度,返回时代表实际读取到的addr结构体的长度,这是一个输入输出型参数。

 代码:

  void start()
    {
        threadpool<Task> *p = threadpool<Task>::getInstance();
         p->start();
        while (true)
        {
            struct sockaddr_in peer;   //用于获取对端信息
            socklen_t len = sizeof(peer);
            int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
            if (serviceSock < 0)
            {
                continue;             // 不断的去获取客户端发送来的请求
            }
            std::cout << "获取链接成功"
                      << " "
                      << "servericeSock=" << serviceSock << std::endl;

            // 获取客户端基本信息
            uint16_t peerPort = ntohs(peer.sin_port);
            std::string peerIp = inet_ntoa(peer.sin_addr);
           
          //  startserver(serviceSock, peerPort, peerIp); 下面说明

    }

 1.4服务端提供服务

当服务器与客户端建立关联后,客户端就可以向服务器发送数据了,之后服务器就应该接受这些数据,并作出对应的处理。其实二者建立关联后,该accept函数的返回值就是对应的文件描述符,服务器可以从中读取,发送数据。(上面代码最后一段调用下面函数)

要用到read函数,write函数,这里不在介绍了。

void startserver(int sock, uint16_t peerPort, std::string peerIp)
    {
        char inbuffer[1024];            //将读入的数据放入inbuffer中
        assert(sock > 0 && !peerIp.empty());

        while (true)
        {
            ssize_t s = read(sock, inbuffer, sizeof(inbuffer) - 1);
            if (s > 0)
            {
                inbuffer[s] = '\0';
                std::cout << peerIp << " " << peerPort << " "
                          << "client>>" << inbuffer << std::endl;

                for (int i = 0; i < s; i++)  //将小写字母转为大写,再发送给客户端
                {
                    if (isalpha(inbuffer[i]) && islower(inbuffer[i]))
                        inbuffer[i] = toupper(inbuffer[i]);
                }

                write(sock, inbuffer, strlen(inbuffer));  //将数据发送给客户端
            }
            else if (s == 0)
            {
                std::cout << peerIp << " " << "clinet quit\n";
                break;
            }
            else
            {
                std::cerr << "读取错误\n";
                break;
            }
        }
        close(sock);
    }

 在上面代码中,提供服务的函数正常情况下是一个死循环,若是单进程的话,服务器也就一次只能为一个客户端提供服务,这显然是不合理的。服务器也该可以为许多客户端提供服务的。所以该提供服务的函数应该让创建的子进程或者创建的线程去执行。

创建子进程执行任务代码:

当子进程退出时会给父进程发送SIGCHLD信号,若父进程对该进行捕捉,并将该信号的处理动作设置为忽略,那么父进程可以继续执行自己的代码。

signal(SIGCHLD, SIG_IGN);//这只在LInux中有效
pid_t id = fork();                             
if (id == 0)
{
    close(listenSock_);
    startserver(serviceSock, peerPort, peerIp);//创建的子进程进行服务
    exit(0);
}
close(serviceSock);

也有另外一种写法:当父进程创建子进程后,再让该子进程fork,创建孙子进程,让孙子进程去执行任务代码,后子进程直接退出,父进程可以直接等待子进程。而该孙子进程变成孤儿进程,由操作系统管理,退出后,操作系统会对其进行回收处理。

pid_t id1 = fork();                          
if (id1 == 0)
{
    pid_t id2 = fork();
    if (id2 == 0)
    {
        startserver(serviceSock, peerPort, peerIp);//孙子进程
        exit(0);
    }
    exit(0);//子进程直接退出,孙子进程被bash管理
}

pid_t ret = waitpid(id1, nullptr, 0); //父进程可以直接阻塞式等待
 close(serviceSock);

二.客户端

与上文介绍的UDP客户端类似,同样不需要自己主动去绑定。

2.1创建套接字

class ClientTcp
{
public:
    ClientTcp(std::string ip, uint16_t port)
        : ip_(ip), port_(port), sock_(-1)
    {
    }
    void init()
    {
        sock_ = socket(AF_INET, SOCK_STREAM, 0);
        if (sock_ < 0)
        {
            std::cerr << "创建套接字失败\n";
            exit(-1);
        }
        std::cout << "创建套接字成功" << " " << "sock_=" << sock_ << std::endl;
    }

private:
    uint16_t port_;
    int sock_;
    std::string ip_;
};

 2.2客户端获取连接

TCP服务器在与客户端进行网络通信之前,客户端需要向服务器发送连接请求。

connect函数:

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数解释:

sockfd:特定的套接字,表示通过该套接字发起连接请求。
addr:对端网络相关的属性信息,包括协议家族、IP地址、端口号等。
addrlen:传入的addr结构体的长度。

返回值说明:成功返回0.失败返回-1。
 

void init()
{
    sock_ = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_ < 0)
    {
        std::cerr << "创建套接字失败\n";
        exit(-1);
    }
    std::cout << "创建套接字成功"
        << " "
        << "sock_=" << sock_ << std::endl;

    struct sockaddr_in server;        //填充服务端的信息
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(port_);
    inet_aton(ip_.c_str(), &server.sin_addr);

// 向服务端发起连接请求
    if (connect(sock_, (const struct sockaddr*)&server, sizeof(server)) != 0)
    {
        std::cerr << "connect: " << strerror(errno) << std::endl;
        exit(-1);
    }
    std::cout << "连接成功\n";
}

 2.3客户端发送需求

当客户端发送连接请求,当服务端收到请求后,连接成功后,二者就可以进行通信了。这里模拟客户端向服务器发送数据,后再接受服务器发来的数据。同样是用到read与write函数。

代码:

 void start()
{
    std::string outbuffer;
    std::string inbuffer;
    while (true)
    {
        std::cout << "请输入信息>>";
        std::getline(std::cin, outbuffer);

        ssize_t s = write(sock_, outbuffer.c_str(), outbuffer.size());
        if (s > 0)
        {
            inbuffer.resize(1024, 0);
            ssize_t s = read(sock_, (char*)inbuffer.c_str(), 1024);
            if (s > 0)
            {
                inbuffer[s] = '\0';
                std::cout << "server>> " << inbuffer << std::endl;
            }
            else
                break;
        }
    }
    close(sock_);
}

三.填充命令行参数

3.1客户端

int main(int argc, char *argv[])
{

    if (argc != 2 && argc != 3)
    {
        std::cerr << "Usage:\n\t" << argv[0] << " port ip" << std::endl;
        std::cerr << "example:\n\t" << argv[0] << " 8080 127.0.0.1 \n"
                  << std::endl;
        exit(-3);
    }
    std::string ip;
    uint16_t port = atoi(argv[1]);
    if (argc == 3)
        ip = argv[2];
    ServerTcp *T = new ServerTcp(port, ip);
    T->init();
    T->start();
    return 0;
}

3.2服务端

int main(int argc, char *argv[])
{

    if (argc != 3)
    {
        std::cerr << "Usage:\n\t" << argv[0] << " port ip" << std::endl;
        std::cerr << "example:\n\t" << argv[0] << " 127.0.0.1 8080  \n"
                  << std::endl;
        exit(-3);
    }
    std::string ip;
    uint16_t port = atoi(argv[2]);
    ip = argv[1];
    ClientTcp *C = new ClientTcp(ip, port);
    C->init();
    C->start();
    return 0;
}

3.3结果测试

 四.线程版本服务端

4.1线程版

同样是对该执行任务的函数进行处理。在创建线程对应执行任务的函数时,该函数需要有对应客户端的IP,端口号。所以可以再写一个类来保存这些信息,后用一个指向该类的指针当参数,传给该执行任务的函数即可。

对应保存客户端数据的类:

class pthreadStart
{
public:
     pthreadStart(ServerTcp* thi,uint16_t clientPort,std::string clientIP,int sock)
     :this_(thi)
     ,clientPort_(clientPort)
     ,sock_(sock)
     ,clinetIp_(clientIP)
     {

     }
    uint16_t clientPort_;//对应客户
    std::string clinetIp_;
    int sock_;
    ServerTcp* this_;    //通过该指针调用处理任务函数
};

 执行任务函数:该函数在类内,用static修饰,去掉this指针

 static void* Routine(void* args)
{
    pthread_detach(pthread_self());
    pthreadStart* p = static_cast<pthreadStart*>(args);
    p->this_->startserver(p->sock_, p->clientPort_, p->clinetIp_);
}
void start()
{
    while (true)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int serviceSock = accept(listenSock_, (struct sockaddr*)&peer, &len);
        if (serviceSock < 0)
        {
            continue;
        }
        std::cout << "获取链接成功"
            << " "
            << "servericeSock=" << serviceSock << std::endl;

        // 获取客户端基本信息
        uint16_t peerPort = ntohs(peer.sin_port);
        std::string peerIp = inet_ntoa(peer.sin_addr);

        pthread_t tid;                                
        pthreadStart* p = new pthreadStart(this, peerPort, peerIp, serviceSock);
        pthread_create(&tid, nullptr, Routine, (void*)p);

    }
}

4.2线程池版

封装一个线程的类,把创建线程的函数放入该类内,并提供相应接口。

线程池介绍:Linux中线程池的制作_"派派"的博客-CSDN博客

void start()
{
    threadpool<Task>* p = threadpool<Task>::getInstance();//创建一个线程池
    p->start();                                           //线程创建,在等待任务到来
//***************************************************************************
    while (true)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int serviceSock = accept(listenSock_, (struct sockaddr*)&peer, &len);
        if (serviceSock < 0)
        {
            continue;
        }
        std::cout << "获取链接成功"
            << " "
            << "servericeSock=" << serviceSock << std::endl;

        // 获取客户端基本信息
        uint16_t peerPort = ntohs(peer.sin_port);
        std::string peerIp = inet_ntoa(peer.sin_addr);
       
       Task t(peerPort,peerIp,serviceSock); //把对应客户端信息放入任务
       p->push(t);                          //线程池加入任务
    }

任务代码:暂且把处理任务函数放入任务代码中,每个任务还保存有对应客户端的IP,端口号,以及对应套接字。

class Task
{
public:
    Task(uint16_t clientPort, std::string clientIP, int sock)
      : sock_(sock), clinetIp_(clientIP), clientPort_(clientPort)
     {
     }

    void startserver(int sock, uint16_t peerPort, std::string peerIp)
    {
        char inbuffer[1024];
        assert(sock > 0 && !peerIp.empty());

        while (true)
        {
            ssize_t s = read(sock, inbuffer, sizeof(inbuffer) - 1);
            if (s > 0)
            {
                inbuffer[s] = '\0';
                std::cout << peerIp << " " << peerPort << " "
                          << "client>>" << inbuffer << std::endl;

                for (int i = 0; i < s; i++)
                {
                    if (isalpha(inbuffer[i]) && islower(inbuffer[i]))
                        inbuffer[i] = toupper(inbuffer[i]);
                }

                write(sock, inbuffer, strlen(inbuffer));
            }
            else if (s == 0)
            {
                std::cout << peerIp << " "
                          << "clinet quit\n";
                break;
            }
            else
            {
                std::cerr << "读取错误\n";
                break;
            }
        }
        close(sock);
    }
    void run()
    {
     
      std::cout<<"线程:"<<pthread_self()<<"开始处理工作\n";
      startserver(sock_,clientPort_,clinetIp_);
      std::cout<<"线程:"<<pthread_self()<<"结束工作\n";
    }

public:
    uint16_t clientPort_;
    std::string clinetIp_;
    int sock_;
};

线程池代码:当创建的线程去拿到任务后,就去调用任务的处理函数,去处理任务。

template <class T>
class threadpool
{
public:
    threadpool(const threadpool<T> &) = delete;
    void operator=(const threadpool<T> &) = delete;
    static threadpool<T> *getInstance()
    {
        if (nullptr == instance) // 过滤重复的判断
        {
            if (nullptr == instance)
            {
                instance = new threadpool<T>;
            }
        }
        return instance;
    }
    threadpool(int nums = pthnums)
    {
        pthread_num = nums;
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
        is_start = true;
    }

    ~threadpool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    static void *Routine(void *argv) // 类内函数有this指针,将其设置为静态,argv接受this
    {
        pthread_detach(pthread_self());
        threadpool<T> *tp = static_cast<threadpool<T> *>(argv);
        while (true)
        {
            cout << "pthread[" << pthread_self() << "]running" << endl;
            tp->lockQueue();
            while (tp->isempty())
            {
                tp->waitTask();
            }
            T t = tp->pop(); // 线程拿到任务
            tp->unlockQueue();
            t.run();         //线程调用执行任务函数
        }
    }
    void start() // 创建pthread_num个线程
    {
        assert(is_start);
        for (int i = 0; i < pthread_num; i++)
        {
            pthread_t tid;
            pthread_create(&tid, nullptr, Routine, this);
        }
        is_start = false;
    }
    void push(const T x)
    {
        lockQueue();
        _q.push(x);
        unlockQueue();
        SignalTask();
    }

    T pop()
    {
        T x = _q.front();
        _q.pop();
        return x;
    }

private:
    // 封装的接口
    void lockQueue()
    {
        pthread_mutex_lock(&mutex_);
    }
    void unlockQueue()
    {
        pthread_mutex_unlock(&mutex_);
    }
    void waitTask()
    {
        pthread_cond_wait(&cond_, &mutex_);
    }
    void SignalTask()
    {
        pthread_cond_signal(&cond_);
    }
    bool isempty()
    {
        return _q.empty();
    }

private:
    queue<T> _q;
    pthread_mutex_t mutex_; // 互斥锁
    pthread_cond_t cond_;   // 信号量
    int pthread_num;        // 创建线程数量
    bool is_start;
    static threadpool<T> *instance;
};
template <class T>
threadpool<T> *threadpool<T>::instance = nullptr;

结果:

 改进:其实该处理任务的函数可以放在任务函数中,也可以放入线程池中,但为了代码的解耦性,可其放入server端的代码上,后面执行该函数时,可以通过回调的方法。但最好的方法是将处理任务的代码封装成一个类(仿函数),里面包含具体的处理方法,然后将该类的对象放入任务端(Task)。下面就介绍第一种方法。

例如:

server端代码,将任务处理函数的实现放在服务端,后面传给任务端。

 Task t(peerPort,peerIp,serviceSock,startserver);
 p->push(t);

任务端代码:

class Task
{
public:
     //typedef std::function<void (int, uint16_t,std::string)> callback_t;//类型的声明
     using callback_t = std::function<void(int, std::string, uint16_t)>;

     Task(uint16_t clientPort, std::string clientIP, int sock, callback_t func)
       : sock_(sock), clinetIp_(clientIP), clientPort_(clientPort), func_(func)
     {
     }

    void run()
    {
     
       std::cout<<"线程:"<<pthread_self()<<"开始处理工作\n";
       func_(sock_,clientPort_,clinetIp_);   //进行回调
      std::cout<<"线程:"<<pthread_self()<<"结束工作\n";

    }

public:
    uint16_t clientPort_;
    std::string clinetIp_;
    int sock_;

     callback_t func_; // 回调方法
                       // void(*p_)(int,uint16_t,std::string);
};

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

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

相关文章

程序员过圣诞 | 用HTML写出绽放的烟花

文章目录一、前言二、创意名三、效果展示四、烟花代码五、总结一、前言 2022年圣诞节到来啦&#xff0c;圣诞节是基督教纪念耶稣诞生的重要节日。亦称耶稣圣诞节、主降生节&#xff0c;天主教亦称耶稣圣诞瞻礼。耶稣诞生的日期&#xff0c;《圣经》并无记载。公元336年罗马教会…

【博客567】http/2 goaway frame 与 grpc graceful restart

http2 goway frame 与 grpc graceful restart 1、http/2 HTTP/2新增特性 二进制分帧(HTTP Frames)多路复用头部压缩服务端推送(server push)等等新特性 二进制分帧(HTTP Frames)是实现一流多用的基础 HTTP/2最革命性的原因就在于这个二进制分帧了&#xff0c;要了解二进制…

Win10 21H2 19044+vs2019 WDK驱动开发,错误 MSB8040缓解Spectre 漏洞的库以及输出SXS.DLL的垃圾信息

错误 MSB8040缓解Spectre 漏洞的库以及输出SXS.DLL的垃圾信息&#xff0c;win7关闭驱动签名、进入驱动测试模式缓解Spectre 漏洞错误的解决windbg狂刷输出SXS.DLL的垃圾信息的解决缓解Spectre 漏洞错误的解决 在工程配置属性&#xff0c;常规&#xff0c;输出目录&#xff0c;…

SSD_学习笔记记录

one-steage VGG16模型的修改 VGG16的模型输出&#xff0c;其中224.。。为特征图的大小。 SSD模型中对VGG网络的修改&#xff1a; SSD模型是对VGG中的第四个卷积块中的最后一个Conv2d进行第一个输出&#xff0c;在这里简称为Conv4-3。以及第五个卷积块中的最后一个Conv2d进行…

TCP 的可靠传输(计算机网络-运输层)

TCP的可靠传输 因特网的网络层服务是不可靠的 TCP在IP的不可靠的&#xff1a;尽最大努力服务的基础上实现了一种可靠的数据传输服务 TCP采用的可靠传输机制&#xff1a;差错检测、序号、确认、超时重传、滑动窗口等 互联网环境中端到端的时延往往是比较大的&#xff1a;采用…

(机器学习深度学习常用库、框架|Pytorch篇)第二节:Pytorch中数据加载方法(DataLoader、DataSet和Sampler)

文章目录一&#xff1a;DataLoader,、DataSet、Sampler三者的关系二&#xff1a;DataLoader,、DataSet、Sampler详解&#xff08;1&#xff09;DatasetA&#xff1a;基本介绍C&#xff1a;Pytroch内置数据集&#xff08;2&#xff09;SamplerA&#xff1a;SequentialSampler&am…

【算法与数据结构】排序详解(C语言)

目录 前言 插入排序 希尔排序 选择排序 堆排序 冒泡排序 快速排序 hoare版本 ​编辑 挖坑法 前后指针版本 优化 非递归实现 归并排序 非递归实现 前言 &#x1f384;在生活中我们必不可少的就是对一组数据进行排序&#xff0c;所谓排序&#xff0c;就是使一串…

ByteBuffer常用方法与分析

目录 目标 常用API 工具方法 演示案例 allocate(int capacity)和allocateDirect(int capacity) put()和get() flip()和hasRemaining() clear() compact() wrap() 总结 目标 掌握ByteBuffer常用方法&#xff0c;分析ByteBuffer对象在切换读写模式的情况下基本属性的变…

【REDIS】安装配置 可视化工具

Redis作为一个高性能&#xff0c;内存可存储化的no SQL数据库&#xff0c;近两年来发展迅猛&#xff0c;然而并没有比较成熟的管理工具来使用&#xff0c;或者是我不知道 下载redis 并安装&#xff1a; 双击安装&#xff0c;可以安装到d: 盘 配置文件是 .conf Redis作为一个…

排序——快排(递归/非递归)

目录 定义 递归 三种方法 1.hoare法 2.挖坑法 3.双指针法 整体 优化1 优化2 非递归 定义 快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法&#xff0c;其基本思想为&#xff1a;任取待排序元素序列中 的某元素作为基准值&#xff0c;按照该排序码将待排序集…

快速掌握e语言,以js语言对比,快速了解掌握。

易语言&#xff0c;怎么调试输出&#xff0c;查看内容 在js语言里&#xff0c;弹窗是 alert()在易语言里&#xff0c;弹窗是 信息框 (“弹出显示内容”, 0, “标题”, ); 在js语言里&#xff0c;调试输出是 console.log()在易语言里&#xff0c;调试输出是 调试输出 (“输出内…

开发过程中使用,可以早点下班的coding小技巧

前言 在实际开发过程中,通过时间的沉淀,一些老人常常能写出一些让小白大吃一惊“骚操作”,那些“骚操作”通常简单的离谱,却能做很多事,属实是让很多新人摸不着头脑。 做一件事时间长了,技巧也就有了。 下面来个情景小剧场: 初入职场小鱼仔:这傻逼是不是写错了,~~ s…

基于凸几何和K均值的高光谱端元提取算法(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

Sentinel统一异常处理

五.统一异常处理—BlockException 在上述规则测试中&#xff0c;当违反规则时&#xff0c;出来的异常信息页面不够友好和统一&#xff0c;我们可以通过设置统一的异常处理类&#xff0c;针对不同规则显示不同异常信息。 创建一个配置类&#xff0c;实现BlockExceptionHandler…

numpy数组,numpy索引,numpy中nan和常用方法

一&#xff1a;【numpy数组】 1.1为什么要学习numpy 1.快速 2.方便 3.科学计算的基础库 1.2什么是numpy 一个python中做科学计算的基础库&#xff0c;重在数值计算&#xff0c;也是大部分python科学计算库的基础库&#xff0c;多用于在大型&#xff0c;多维数组上执行数组运…

常用的键盘事件

1、键盘事件 键盘事件触发条件onkeyup某个键盘按键被松开时触发onkeydown某个键盘按键被按下时触发onkeypress某个键盘按键被按下时触发&#xff08;但它不识别功能键&#xff0c;比如ctrl、shift等&#xff09; 注意&#xff1a; 如果使用addEventListener不需要加ononkeypr…

Go 堆数据结构使用

说到 container/heap 下的堆数据结构&#xff0c;让我们不需要从零开始实现这个数据结构。如果只是日常工作&#xff0c;其实还挺难用到堆的&#xff0c;更多的还是在写算法题的时候会用到。 基本概念 堆分为大顶堆和小顶堆&#xff0c;区分这两种类型方便我们处理问题。大顶…

Docker安装Zookeeper教程(超详细)

生命无罪&#xff0c;健康万岁&#xff0c;我是laity。 我曾七次鄙视自己的灵魂&#xff1a; 第一次&#xff0c;当它本可进取时&#xff0c;却故作谦卑&#xff1b; 第二次&#xff0c;当它在空虚时&#xff0c;用爱欲来填充&#xff1b; 第三次&#xff0c;在困难和容易之…

第六章:关系数据理论

一、问题的提出、范式 1、【多选题】下列说法中正确的是&#xff1a; 正确答案&#xff1a; ABCD 2、【多选题】关系模式R&#xff08;项目序号&#xff0c;项目代码&#xff0c;项目名称&#xff09;&#xff0c;项目序号是码。一个项目代码只有一个项目名称。下列说法不正确…

文献检索报告

文献检索第一篇检索作业总结第一章检索任务1.1检索课题1.2确定选题所属学科1.3中英文检索词第二章检索策略与结果2.1检索中文期刊文献2.1.1 CNKI中国期刊全文数据库2.1.2 维普期刊全文数据库2.1.3 万方期刊数据库2.1.4 超星期刊全文2.2检索中文学位论文2.2.1 CNKI博硕学位论文数…