【Linux】多路IO复用技术③——epoll详解如何使用epoll模型实现简易的一对多服务器(附图解与代码实现)

news2024/11/28 16:43:00

在正式阅读本篇博客之前,建议大家先按顺序把下面这两篇博客看一下,否则直接来看这篇博客的话估计很难搞懂

多路IO复用技术①——select详解&如何使用select模型在本地主机实现简易的一对多服务器icon-default.png?t=N7T8http://t.csdnimg.cn/BiBib多路IO复用技术②——poll详解&如何使用poll模型在本地主机实现简易的一对多服务器icon-default.png?t=N7T8http://t.csdnimg.cn/EEzOf

在了解以上两篇博客讲解的内容后,我们正式开始本篇博客的相关内容讲解,第三种多路套接字监听技术——epoll模型

目录

EPOLL模型优化的第一部分

1.对拷贝开销的优化

2.对挂载开销的优化

3.对查找开销的优化

EPOLL模型的相关接口

相关结构体 struct epoll_event

相关函数

EPOLL模型优化的第二部分

4.监听方面的优化

EPOLL模型的优缺点

本地主机使用epoll模型实现简易一对多服务器的程序实现

程序构成          结果图示


epoll模型是最优秀的套接字监听技术他几乎解决了select和poll模型中的所有缺点,并在其优点上进行了进一步的优化,接下来,我们就来了解一下epoll模型的进行多路套接字监听的原理吧

EPOLL模型优化的第一部分

我们知道,select和poll模型实现的监听原理其实都是按照以下这些步骤:

  1. 系统会将这个用户层的监听集合拷贝到内核层
  2. 系统会将内核层的该监听集合中监听的套接字放入IO设备等待队列,由IO设备等待队列来进行一次又一次的遍历来判断那些套接字中有数据需要处理
  3. 当IO设备等待队列发现有套接字处于就绪状态时,会传出就绪集合到内核层
  4. 系统通过select模型将该就绪集合由内核层拷贝到用户层,供用户使用

想要了解下面的内容,这里需要为大家简单讲解一种数据结构——红黑树  ,并希望大家能够记住红黑树的优点

红黑树的优势就在于非常适合用来存储有序的数据,并且增删查改的时间复杂度很低,只有O(log2n)

1.对拷贝开销的优化

在之前的博客中,我们讲过,随着select/poll模型的持续使用,会产生大量的拷贝开销和挂载开销

这些拷贝开销大致由两部分组成:

  1. 系统从用户层到内核层,内核层到用户层翻来覆去的层级转换
  2. 一旦监听集合更新,就要重新拷贝整个就绪集合,只要服务器不停,就会面临无止尽的拷贝

该如何减少第一部分的拷贝开销呢?

其实很简单粗暴,epoll模型的开发人员直接将监听集合定义在内核层。所以epoll模型的监听集合都是建立在内核层的

PS:这种监听集合实际上是红黑树,每一个叶子节点对应一个监听套接字

至于如何减少第二部分的拷贝开销,就和红黑树树这种数据结构息息相关了。

我们知道,select和poll模型都是采用数组作为作为监听集合,数组这种结构非常不便于查找其中哪些数据产生了变化,就导致当有新的套接字要放入监听集合中时,系统会将整个新的监听集合全部拷贝到内核层。

而红黑树就完美的避开了这一点,每当有新的套接字要放入监听树中时,我们只需要为这棵监听红黑树添加一个叶子节点就够了,而不是重建这棵树,这就导致了这方面的开销大大减少了

2.对挂载开销的优化

在对拷贝开销的优化方面,epoll模型的开发人员依旧采用了这种数据结构,我们来为大家简单的介绍一下原因:

我们知道,在select与poll模型中,数组这种结构非常不便于查找其中哪些数据产生了变化,所以每当有新的套接字放入监听集合中时,系统会将监听的套接字全部重新挂载到IO设备等待队列,这就导致挂载开销方面的开销非常大。

而红黑树则完美的解决了这个问题,由于红黑树的特性,每当有对应新套接字的叶子节点被放入监听树中时,系统能够很轻松的就能找到对应新套接字的叶子节点是哪一个,然后只需要将对应的新套接字放入IO设备等待队列就可以了,在挂载开销方面的消耗大大减少

3.对查找开销的优化

我们知道,虽然数组的下标是有序的,方便我们进行遍历,但是数组中的内容并不一定是有序的,这就导致我们无法根据我们目标内容来直接找到其在数组中的哪个位置,而是需要去通过循环一次次的查找,但红黑树就完美地解决了这个问题

我们知道,当我们调用accept函数让服务器与多个客户端建立TCP链接时,其返回的int类型的套接字文件描述符,其实都是有序递增的,建立一个链接返回值就+1。由于红黑树在有序数据存储方面的优势,使得服务器在对于监听就绪和取消监听方面的效率非常之高。

其实epoll模型不止在这些方面进行了优化,其他部分需要大家了解了EPOLL模型的相关接口后,才能为大家进行讲解,所以别着急,我们先来了解下EPOLL模型的相关接口吧

EPOLL模型的相关接口

以下接口的头文件都是 #include <sys/epoll.h>

相关结构体 struct epoll_event

struct epoll_event 
{
    uint32_t events;  // epoll 事件类型,包括可读,可写等
    epoll_data_t data; // 用户数据,可以是一个指针或文件描述符等
    callback();  //这个函数与用户没有关系,我们是看不见的,只有当发现对应套接字就绪时才会调用该函数进行回调操作
};

typedef union epoll_data 
{
    void *ptr;   //指向任何类型的用户数据
    int fd;   //套接字文件描述符
    uint32_t u32;   //32位的无符号整数
    uint64_t u64;   //64位的无符号整数
} epoll_data_t;

其中,events字段表示要监听的事件类型,可以是以下值之一:

  • EPOLLIN:表示对应的文件描述符上有数据可读
  • EPOLLOUT:表示对应的文件描述符上可以写入数据
  • EPOLLRDHUP:表示对端已经关闭连接,或者关闭了写操作端的写入
  • EPOLLPRI:表示有紧急数据可读
  • EPOLLERR:表示发生错误
  • EPOLLHUP:表示文件描述符被挂起
  • EPOLLET:表示将epoll设置为边缘触发模式
  • EPOLLONESHOT:表示将事件设置为一次性事件

这个回调的原理是什么呢?其实很简单

其实负责监听这些套接字事件的设备就是网卡,所以最先知道套接字就绪的就是网卡设备,这时候开发人员就想了,那我能不能实现一个功能,创建一个结构体队列,直接将该队列与网卡进行绑定,形成一种回调关系。这样的话,当网卡监听到某套接字处于就绪状态时,就通过异步通知的方式来告诉服务器是哪个套接字就绪了,不需要再一次次的遍历来判断哪个套接字就绪

所以epoll模型监听套接字的过程就如下所示:

  1. 建立结构体(ep_item)队列,队列中的每一个结构体和一个套接字对应,将该队列与网卡进行绑定
  2. 建立一个就绪链表,存放对应就绪套接字的结构体epoll_events
  3. 监听到某一套接字就绪时,调用结构体epoll_events中的callback函数,进行拆包,将结构体ep_item中的结构体epoll_events拆分出来,并弹出到就绪链表中(双向链表)
  4. 将这个就绪链表中的内容拷贝到用户在用户层定义的结构体(epoll_event)数组,以便用户使用

不理解的话可以看下面的这个图

相关函数

先来介绍一下一会会用到的参数:

  • int epfd; // 返回的监听红黑树文件描述符
  • int epoll_max; //监听套接字的最大数量
  • int option; //需要执行的操作——添加、修改、删除
  • int sockfd; //需要添加,修改,删除的socket文件描述符
  • struct epoll_event * node; //挂在红黑树上的节点
  • int array_size; //存放对应就绪套接字的结构体的数组大小
  • struct epoll_event ready_array[array_size]; //存放对应就绪套接字的结构体的数组
  • int ready_max; //允许同时监听的最大套接字数量
  • int timeout; //工作模式

PS:一般情况下,epoll_max = ready_max = array_size

函数功能返回值
int epoll_create(epoll_max);建立监听红黑树

1.成功,返回值为epoll监听树对应的文件描述符

2.出错,返回 -1,并设置错误码

int epoll_ctl(epfd , option , sockfd , node);添加/删除节点或修改监听事件

1.成功,返回0

2.出错,返回 -1,并设置错误码

int epoll_wait(epfd , ready_array , ready_max , timeout);监听套接字相关事件

1.成功,返回值为就绪的套接字数目

2.如果在请求的超时毫秒内没有套接字准备就绪,返回0

3.出错,返回 -1,并设置错误码

epoll_create()的错误码如下所示:

  • EINVAL:epoll_max大小不为正。
  • EMFILE:遇到了每个用户对/ proc / sys / fs / epoll / max_user_instances施加的epoll实例数量的限制。
  • ENFILE:超过系统设定的进程最大创建的文件描述符数量。
  • ENOMEM:没有足够的内存来创建内核对象。

epoll_ctl()的错误码如下所示:

  • EBADF:epfd或fd不是有效的文件描述符。
  • EEXIST:option为EPOLL_CTL_ADD,并且提供的文件描述符fd已在该epoll实例中注册。
  • EINVAL:epfd不是epoll文件描述符,或者fd与epfd相同,或者此接口不支持请求的操作option。
  • ENOENT:option是EPOLL_CTL_MOD或EPOLL_CTL_DEL,并且fd未在该epoll实例中注册。
  • ENOMEM:没有足够的内存来处理请求的操作控制操作。
  • ENOSPC:尝试在主机上注册(EPOLL_CTL_ADD)新文件描述符时遇到了/ proc / sys / fs / epoll / max_user_watches施加的限制。
  • EPERM:目标文件fd不支持epoll。

epoll_wait()的错误码如下所示:

  • EBADF:epfd不是有效的文件描述符。
  • EFAULT:具有写许可权不能访问事件指向的存储区。
  • EINTR:在任何请求的事件发生或超时到期之前,信号处理程序中断了该调用;参见signal(7)。
  • EINVAL:epfd不是epoll文件描述符,或者epoll_max小于或等于零。

EPOLL模型优化的第二部分

4.监听方面的优化

由于epoll模型的自定义结构体队列、异步通知和回调函数的使用,导致用户不再需要遍历去查找就绪的套接字是哪些,直接对自己在用户层自定义的结构体数组进行遍历处理就可以了,大大减少了时间片方面的消耗,使得服务器的处理能力实现质的飞跃

基本了解了epoll在哪些方面做出了优化后,我们就可以来聊一聊epoll模型的优缺点了

EPOLL模型的优缺点

优点:

  1. 不存在重复的拷贝开销与挂载开销

  2. 自定义实现监听队列,采用异步回调方式,无需轮询,队列体积更小

  3. 不止返回就绪的套接字数量,还返回就绪的套接字是哪些

  4. 监听的事件种类丰富

  5. 可以为不同的套接字设置不同的监听事件,不像select模型只能批量设置监听事件

  6. 可以监听的socket数量不受1024的硬限制

缺点:

  1. 仅linux系统支持
  2. 红黑树的缺点

使用epoll模型实现简易一对多服务器的程序实现

程序构成

该服务器与客户端由以下几个程序共同组成:

  • func_2th_parcel.h:定义二次包裹的函数名
  • func_2th_parcel.c:对网络初始化相关的函数进行二次包裹
  • epoll_server.c:使用poll模型的服务器程序
  • client.c:客户端程序
/*************************************************************************
        > File Name: func_2th_parcel.h
        > Author: Nan
        > Mail: **@qq.com
        > Created Time: 2023年10月18日 星期三 18时32分22秒
 ************************************************************************/
 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include <pthread.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/mman.h>
#include <time.h>
#include <ctype.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <poll.h>
#include <sys/epoll.h>
 
//socket函数的二次包裹
int SOCKET(int domain , int type , int protocol);
 
//bind函数的二次包裹
int BIND(int sockfd , const struct sockaddr* addr , socklen_t  addrlen);
 
//listen函数的二次包裹
int LISTEN(int sockfd , int backlog);
 
//send函数的二次包裹
ssize_t SEND(int sockfd , const void* buf , size_t len , int flags);
 
//recv函数的二次包裹
ssize_t RECV(int sockfd , void* buf , size_t len , int flags);
 
//connect函数的二次包裹
int CONNECT(int sockfd , const struct sockaddr* addr , socklen_t addrlen);
 
//accept函数的二次包裹
int ACCEPT(int sockfd , struct sockaddr* addr , socklen_t addrlen);
 
//网络初始化函数
int SOCKET_NET_CREATE(const char* ip , int port);
 
//服务端与客户端建立连接并返回客户端套接字文件描述符
int SERVER_ACCEPTING(int server_fd);
/*************************************************************************
        > File Name: func_2th_parcel.c
        > Author: Nan
        > Mail: **@qq.com
        > Created Time: 2023年10月18日 星期三 18时32分42秒
 ************************************************************************/
 
#include <func_2th_parcel.h>
 
int SOCKET(int domain , int type , int protocol){
    int return_value;
    if((return_value = socket(domain , type , protocol)) == -1){
        perror("socket call failed!\n");
        return return_value;
    }
    return return_value;
}
 
int BIND(int sockfd , const struct sockaddr* addr , socklen_t addrlen){
    int return_value;   
    if((return_value = bind(sockfd , addr , addrlen)) == -1){
        perror("bind call failed!\n");
        return return_value;
    }                      
    return return_value;   
}
 
int LISTEN(int sockfd , int backlog){
    int return_value;   
    if((return_value = listen(sockfd , backlog)) == -1){
        perror("listen call failed!\n");
        return return_value;
    }                      
    return return_value;   
}
 
ssize_t SEND(int sockfd , const void* buf , size_t len , int flags){
    ssize_t return_value;
    if((return_value = send(sockfd , buf , len , flags)) == -1){
        perror("send call failed!\n");
        return return_value;
    }
    return return_value;
}
 
ssize_t RECV(int sockfd , void* buf , size_t len , int flags){
    ssize_t return_value;   
    if((return_value = recv(sockfd , buf , len , flags)) == -1){
        perror("recv call failed!\n");
        return return_value;
    }                      
    return return_value;   
}
 
int CONNECT(int sockfd , const struct sockaddr* addr , socklen_t addrlen){
    int return_value;   
    if((return_value = connect(sockfd , addr , addrlen)) == -1){
        perror("connect call failed!\n");
        return return_value;
    }                      
    return return_value;   
}
 
int ACCEPT(int sockfd , struct sockaddr* addr , socklen_t addrlen){
    int return_value;   
    if((return_value = accept(sockfd , addr , &addrlen)) == -1){
        perror("accept call failed!\n");
        return return_value;
    }                      
    return return_value;   
}
 
int SOCKET_NET_CREATE(const char* ip , int port){
    int sockfd;
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    inet_pton(AF_INET , ip , &addr.sin_addr.s_addr);
    sockfd = SOCKET(AF_INET , SOCK_STREAM , 0);
    BIND(sockfd , (struct sockaddr*)&addr , sizeof(addr));
    LISTEN(sockfd , 128);
    return sockfd;
}
 
int SERVER_ACCEPTING(int server_fd)
{
    int client_sockfd;
    struct sockaddr_in client_addr;
    char client_ip[16];
    char buffer[1500];
    bzero(buffer , sizeof(buffer));
    bzero(&client_addr , sizeof(client_addr));
    socklen_t addrlen = sizeof(client_addr);
    client_sockfd = ACCEPT(server_fd , (struct sockaddr*)&client_addr , addrlen);
    bzero(client_ip , 16);
    //将客户端的IP地址转成CPU可以识别的序列并存储到client_ip数组中
    inet_ntop(AF_INET , &client_addr.sin_addr.s_addr , client_ip , 16);
    sprintf(buffer , "Hi , %s welcome tcp test server service..\n" , client_ip);
    printf("client %s , %d , connection success , client sockfd is %d\n" , client_ip , ntohs(client_addr.sin_port) , client_sockfd);
    SEND(client_sockfd , buffer , strlen(buffer) , MSG_NOSIGNAL);
    return client_sockfd;
}
/*************************************************************************
        > File Name: epoll_server.c
        > Author: Nan
        > Mail: **@qq.com
        > Created Time: 2023年10月25日 星期三 18时53分30秒
 ************************************************************************/

#include <func_2th_parcel.h>

#define MAX_EPOLL 100000

int main(void)
{
    int server_sockfd;//服务器套接字文件描述符
    int epfd;//监听树文件描述符
    struct epoll_event ready_array[MAX_EPOLL];//由于存放epoll返回的就绪结构体
    struct epoll_event node;//向监听树中放入的节点
    int ready_num;//处于就绪状态的套接字的数量
    int client_sockfd;//客户端套接字文件描述符
    char rw_buffer[1500];//读写缓冲区
    int flag;
    int recv_len = 0;//接受到的数据的长度
    int i;
    bzero(rw_buffer , sizeof(rw_buffer));//清空读写缓冲区
    server_sockfd = SOCKET_NET_CREATE("192.168.79.128" , 6060);//进行网络初始化
    //初始化epoll结构体
    node.data.fd = server_sockfd;
    node.events = EPOLLIN;//监听读事件
    //初始化监听树,并做错误处理
    if((epfd = epoll_create(MAX_EPOLL)) == -1)
    {
    	perror("epoll_create call failed\n");
    	exit(-1);
    }
    //向监听树中添加服务器套接字结构体,并做错误处理
    if((epoll_ctl(epfd , EPOLL_CTL_ADD , server_sockfd , &node)) == -1)
    {
    	perror("epoll_ctl call failed\n");
    	exit(-1);
    }
    printf("epoll_server wait TCP connect\n");
    while(1)
    {
    	//获取处于就绪状态的套接字数量
        if((ready_num = epoll_wait(epfd , ready_array , MAX_EPOLL , -1)) == -1)
        {
            perror("epoll_wait call failed\n");
            exit(0);
        }
        i = 0;
        while(ready_num)
        {
            //辨别就绪,如果是服务端套接字就绪
            if(ready_array[i].data.fd == server_sockfd)
            {
                client_sockfd = SERVER_ACCEPTING(ready_array[i].data.fd);//与客户端建立TCP链接
                node.data.fd = client_sockfd;
                if(epoll_ctl(epfd , EPOLL_CTL_ADD , client_sockfd , &node) == -1)
                {
                	perror("epoll_ctl failed\n");
                	exit(-1);
                }
            }
            //如果是客户端套接字就绪
            else
            {  
                recv_len = RECV(ready_array[i].data.fd , rw_buffer , sizeof(rw_buffer) , 0);
                flag = 0;
                //如果recv_len = 0,就说明与客户端套接字对应的客户端退出了,将对应客户端套接字移出套接字存储数组与监听集合
                if(recv_len == 0)
                {
                    perror("某一客户端与本服务器断开链接\n");
                    printf("客户端%d 与本服务器断开链接,关闭其对应的套接字并停止监听\n" , ready_array[i].data.fd);
                    //关闭该套接字
                    close(ready_array[i].data.fd);
                    //将其对应的节点从监听树上摘下来
                    epoll_ctl(epfd , EPOLL_CTL_DEL , ready_array[i].data.fd , NULL);
                    //注意,这个两个步骤不能搞反
                }
                //进行业务处理:小写字母转大写字母
                printf("服务器已接收到客户端%d 发来的信息 : %s,现在对其进行处理\n" , ready_array[i].data.fd , rw_buffer);
                while(recv_len > flag)
                {
                    rw_buffer[flag] = toupper(rw_buffer[flag]);
                    flag++;
                }
                printf("服务器已对客户端%d 发来的信息完成处理,处理后的数据为 : %s\n" , ready_array[i].data.fd , rw_buffer);
                SEND(ready_array[i].data.fd , rw_buffer , recv_len , MSG_NOSIGNAL);
                bzero(rw_buffer , sizeof(rw_buffer));
                recv_len = 0;
            }
            ready_num--;
            i++;
        }
    }
    close(server_sockfd);
    printf("server shutdown\n");
    return 0;
}
/*************************************************************************
        > File Name: client.c
        > Author: Nan
        > Mail: **@qq.com
        > Created Time: 2023年10月19日 星期四 18时29分12秒
 ************************************************************************/
 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <time.h>
 
//服务器实现大小写转换业务
 
int main()
{
    //1.定义网络信息结构体与读写缓冲区并初始化
    struct sockaddr_in dest_addr;
    char buffer[1500];
    bzero(&dest_addr , sizeof(dest_addr));
    bzero(buffer , sizeof(buffer));
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(6060);
    //字符串ip转大端序列
    inet_pton(AF_INET , "192.168.79.128" , &dest_addr.sin_addr.s_addr);
    int sockfd = socket(AF_INET , SOCK_STREAM , 0);
    int i;
    //2.判断连接是否成功
    if((connect(sockfd , (struct sockaddr*) &dest_addr , sizeof(dest_addr))) == -1)
    {
        perror("connect failed!\n");
        exit(0);
    }
    recv(sockfd , buffer , sizeof(buffer) , 0);
    printf("%s" , buffer);
    bzero(buffer , sizeof(buffer));
    //3.循环读取终端输入的数据
    while( (fgets(buffer , sizeof(buffer) , stdin) ) != NULL)
    {
        i = strlen(buffer);
        buffer[i-1] = '\0';
        //向服务端发送消息
        send(sockfd , buffer , strlen(buffer) , MSG_NOSIGNAL);
        //接收服务端发来的消息
        recv(sockfd , buffer , sizeof(buffer) , 0);
        //打印服务端发来的信息
        printf("response : %s\n" , buffer);
        //清空读写缓冲区,以便下一次放入数据
        bzero(buffer , sizeof(buffer));
    }
    //4.关闭套接字,断开连接
    close(sockfd);
    return 0;
}

结果图示

以上就是本篇博客的全部内容了,大家有什么地方没有看懂的话,可以在评论区留言给我,咱要力所能及的话就帮大家解答解答

今天的学习记录到此结束啦,咱们下篇文章见,ByeBye!

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

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

相关文章

2.10 CSS BFC

1.简介 BFC是Block Formatting Context(块级格式上下文)&#xff0c;可以理解成元素的一个“特异功能”。该“特异功能”&#xff0c;在默认的情况下处于关闭状态;当元素满足了某些条件后&#xff0c;该"特异功能被激活。所谓激活"特异功能”&#xff0c;专业点说就…

Java线程的基本概念和五种状态

1. 线程 1.1 创建线程 创建线程通常有以下三种方式&#xff1a; 实现 Runnable 接口&#xff0c;并重写其 run 方法&#xff1a; public class J1_Method01 {public static void main(String[] args) {System.out.println("Main线程的ID为&#xff1a;" Thread.curr…

shell综合项目

主菜单 http和Nginx分别的install的菜单&#xff0c;安装过程通过重定向到/dev/null达到看不见的效果 输入非整数或者大于4的数字都会提示错误 代码如下: [rootserver ~]# vim install_menu.sh #!/bin/bash function menu() { cat << EOF …

GraphQL入门与开源的GraphQL引擎Hasura体验

背景 Hasura 是一个开源的 GraphQL 引擎&#xff0c;它可以帮助开发人员快速构建和部署现代应用程序的后端。它提供了一个自动化的 GraphQL API &#xff0c;可以直接连接到现有的数据库&#xff0c;并提供实时数据推送和订阅功能。 Hasura 团队总部位于印度。 下载安装 脚本…

产品经理入门学习(三):产品解决方案

参考引用 黑马-产品经理入门基础课程 1. 需求分析 1.1 需求分析的目的 1.2 需求分析的方法 案例分析 福特公司的创始人亨利福特说&#xff1a;如果我当年去问顾客他们想要什么&#xff0c;他们肯定会告诉我&#xff1a;一匹更快的马 1.3 需求分析的实际应用 人性七宗罪&#…

计网note

其他 未分类文档 CDMA是码分多路复用技术 和CMSA不是一个东西 UPD是只确保发送 但是接收端收到之后(使用检验和校验 除了检验的部分相加 对比检验和是否相等。如果不相同就丢弃。 复用和分用是发生在上层和下层的问题。通过比如时分多路复用 频分多路复用等。TCP IP 应用层的…

css 图片好玩的一个属性,添加滤镜

鼠标经过效果对比&#xff1a; 上图是改变了图片的饱和度&#xff0c;代码如下&#xff1a; .img-box .v-image:hover {filter: saturate(1.75); }其他滤镜说明如下图&#xff1a;

2023全新小程序广告流量主奖励发放系统源码 流量变现系统 带安装教程

2023全新小程序广告流量主奖励发放系统源码 流量变现系统 分享软件&#xff0c;吃瓜视频&#xff0c;或其他资源内容&#xff0c;通过用户付费买会员来变现&#xff0c;用户需要付费&#xff0c;有些人喜欢白嫖&#xff0c;所以会流失一部分用户&#xff0c;所以就写了这个系统…

46基于matlab的模拟退火算法(SA)优化车辆路径问题(VRP)

基于matlab的模拟退火算法&#xff08;SA&#xff09;优化车辆路径问题&#xff08;VRP&#xff09;&#xff0c;在位置已知的条件下&#xff0c;确定车辆到各个指定位置的行程路线图&#xff0c;使得路径最短&#xff0c;运输成本最低。一个位置由一台车服务&#xff0c;且始于…

内存池设计实现

1.设计原理 1.内存池实际就是预先分配不同大小的内存块, 然如果需要调用的时候, 直接把这个块的指针返回. 图中, 就是内存池划分. 2.通过一个链表, 将这些分配的内存块串联起来, 每一块最头部都记录这这个块的信息 3.分配的时候, 会遍历一遍链表, 找到is_used未被置1, pool…

【C语言进阶】之动态内存管理笔试题及柔性数组

【C语言进阶】之动态内存管理笔试题 1.动态内存管理笔试题汇总1.1第一道题1.2第二道题1.3第三道题1.4第四道题 2.C/C内存管理3.柔性数组3.1什么是柔性数组3.2柔性数组的使用3.2柔性数组的优点 &#x1f4c3;博客主页&#xff1a; 小镇敲码人 &#x1f680; 欢迎关注&#xff1a…

Kubernetes Dashboard 用户名密码方式登录

Author&#xff1a;rab 前言 为了 K8s 集群安全&#xff0c;默认情况下 Dashboard 以 Token 的形式登录的&#xff0c;那如果我们想以用户名/密码的方式登录该怎么操作呢&#xff1f;其实只需要我们创建用户并进行 ClusterRoleBinding 绑定即可&#xff0c;接下来是具体的操作…

Cygwin一个在 Windows 操作系统上提供类似于Unix、Linux 环境的兼容层项目

一、简介 Cygwin 是一个在 Windows 操作系统上提供类似于 Unix/Linux 环境的兼容层的开源项目。它为 Windows 用户提供了一种在 Windows 平台上运行类 Unix 程序的方式。 Cygwin 提供了一组工具和库&#xff0c;包括一个动态链接库&#xff08;cygwin1.dll&#xff09;和一个用…

图解系列--防火墙

05.01 防火墙是怎样的网络硬件 构建安全网络体系而需要遵循的 CIA 基本理念。CIA 是机密性 (Confidentiality) 、 完整性(Integrity) 、 可用性(Availability)。 防火墙硬件作为防范装置能够同时实现CIA 中3个条目的相应对策。在20世纪90年代中期&#xff0c;普通企业一般都…

Kibana使用Timelion根据时间序列展示数据

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

【服务器】Java连接redis及使用Java操作redis、使用场景

一、Java连接redis-No-SQL 1、导入依赖 在你的项目里面导入redis的pom依赖 <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.9.0</version> </dependency> 2、连接redis 连接redis //…

【MongoDB】MongoExport如何过滤数据导出

问题 使用MongoDB处理导出数据时&#xff0c;想增加数据过滤操作。 例如&#xff1a;导出所有isGirl为true的所有数据。 分析 在mongoexport说明文档中找到了query字段和queryFile字段&#xff0c;用来进行数据查询匹配导出。 query字段 后面直接跟 json格式数据。 queryF…

【C++语法讲解】 | 运算符重构 | 三种运算符的重构方式 |代码演示

文章目录 1&#xff0c;简述2&#xff0c;结构体的定义1&#xff0c;结构体的声明2&#xff0c;结构体的申请 3.1 &#xff0c;在结构体中重构3.2 在结构体外进行重构 1&#xff0c;简述 通常情况下&#xff0c;我们会创建一些简单的数据结构以应对日常的算法使用&#xff0c;…

layui form 中input输入框长度的统一设置

Layui.form中使用class"layui-input-inline"就可轻松将元素都放到一行&#xff0c;但如果元素过多&#xff0c;就会自动换行。那就需要手动设置input框的长度。 像这种情况&#xff1a; 其实只需要添加css样式就可修改了 .layui-form-item .layui-input-inline {wid…

海康Visionmaster通讯管理:通讯管理的心跳管理功能 的使用方法

当外部设备与视觉保持连接过程中&#xff0c;由于各种不可控的原因&#xff08;例如网线被意外拔 出&#xff0c;网口松动&#xff0c;视觉程序意外退出&#xff09;&#xff0c;如何让外部设备的程序可以知道&#xff1a;与视觉的通讯已 经中断。 可以通过通讯管理模块中的心跳…