【Linux】多路IO复用技术①——select详解如何使用select在本地主机实现简易的一对多服务器(附图解与代码实现)

news2024/11/23 12:55:19

这一篇的篇幅可能有点长,但真心希望大家能够静下心来看完,相信一定会有不小的收获。那么话不多说,我们这就开始啦!!!

目录

一对一服务器中的BUG

如何实现简易的一对多服务器

实现简易一对多服务器的大体步骤

每个步骤的具体流程

1.网络初始化

2.启动监听,等待socket相关事件

多路IO复用技术之select模型

3.监听到相关事件 , 辨别是服务器socket还是客户端socket并进行处理

本地主机实现简易一对多服务器的项目实现

项目构成

结果演示


一对一服务器中的BUG

我们先来看一段我之前写的程序——功能:实现一对一的单进程服务器

大家可以直接看该程序的while循环,来看一下该部分有哪些BUG

/*************************************************************************
        > File Name: nan_server.c
        > Author: Nan
        > Mail: **@qq.com
        > Created Time: 2023年10月20日 星期五 13时59分10秒
 ************************************************************************/
 
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<string.h>
 
//定义一个开关,用于决定服务器是否开启,默认为开启状态
#define SERVER_SWITCH 1
 
 
int main()
{
    //1.分别定义服务端与客户端的网络信息结构体
    struct sockaddr_in server_addr , client_addr;
    bzero(&server_addr , sizeof(server_addr));
    bzero(&client_addr , sizeof(client_addr));
    //定义一个读写缓冲区与一个存放客户端IP的缓冲区
    char rw_buffer[1500];
    char client_IP[16];
    bzero(rw_buffer , sizeof(rw_buffer));
    bzero(client_IP , sizeof(client_IP));
    //2.对服务端网络信息结构体进行初始化
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(6060);
    //server_addr.sin_addr.s_addr = inet_addr("192.0.0.1");
    server_addr.sin_addr.s_addr = inet_addr("本地主机IPV4地址");
    //3.创建套接字,该套接字起到监听与传输信息的作用
    int server_sockfd = socket(AF_INET , SOCK_STREAM , 0);
    if(server_sockfd == -1)
    {
        perror("server socket call failed!\n");
        exit(-1);
    }
    //4.将IP地址与端口号绑定到监听套接字上
    int bind_result = bind(server_sockfd , (struct sockaddr*)&server_addr , sizeof(server_addr));
    if(bind_result == -1)
    {
        perror("server bind call failed!\n");
        exit(-1);
    }
    printf("server wait connect!\n");//日志打印,可帮助理解程序执行逻辑
    //5.监听是否有TCP链接
    int backlog = 128;
    listen(server_sockfd , backlog);
    socklen_t addrlen;
    int client_sockfd;
    while(SERVER_SWITCH)
    {
        printf("已进入循环!\n");//日志打印,可帮助理解程序执行逻辑
        addrlen = sizeof(client_addr);
        //6.如果接收成功,返回对应的文件描述符,并执行以下程序
        if( (client_sockfd = accept(server_sockfd , (struct sockaddr*)&client_addr , &addrlen)) > 0)
        {   
            printf("accept call success!\n");
            //将网络信息结构体中的大端序IP转为字符串IP并放到读写缓冲区中
            inet_ntop(AF_INET , &(client_addr.sin_addr.s_addr) , client_IP , sizeof(client_IP));
            printf("client_IP = %s\n" , client_IP);//日志打印,帮助检测是否写入IP地址
            sprintf(rw_buffer , "Hello , %s , welcome connect nan_server\n" , client_IP);
            printf("读写缓冲区中内容为 %s\n" , rw_buffer);//日志打印,帮助检测是否写入要发送的数据
            //将读写缓冲区中的内容发送到服务端的套接字中,由套接字向客户端发送数据
            send(client_sockfd , rw_buffer , sizeof(rw_buffer) , MSG_NOSIGNAL);
            //清空读写缓冲区与存放IP的缓冲区,以供下一次使用
            bzero(rw_buffer , sizeof(rw_buffer));                                      
            bzero(client_IP , sizeof(client_IP));
            //读取客户端发来的数据
            recv(client_sockfd , rw_buffer , sizeof(rw_buffer) , 0);
            printf("client_message : %s\n" , rw_buffer);
            bzero(rw_buffer , sizeof(rw_buffer));
        }
        else if(client_sockfd == -1)
        {
            perror("accept call failed!\n");
            continue;
        }
    }
    close(server_sockfd);
}

如果大家看不出来的话,再给大家一点提示:这些BUG都和阻塞与socket缓冲区有关

现在来公布一下答案吧,这个服务器的BUG在于——

  1. 如果一直没有客户端向服务器发起TCP链接请求,socket缓冲区中没有表示链接请求的标志数据SYN可读取,服务器的程序就会一直阻塞在accept函数那里,无法执行其他程序,整个服务器一直处于阻塞等待状态
  2. recv函数的调用,由于服务器使用阻塞状态的recv函数,如果客户端迟迟不发送信息,socket缓冲区就会一直为空,整个服务器就会一直阻塞等待该客户端发送数据,无法处理其他客户端的链接请求

那么我们要怎么处理这些BUG呢?

其实看完上面的BUG,大家或多或少都能明白这两个BUG的本质——其实无非就是,由于socket缓冲区中没有数据,可accept函数和recv函数却一直在等待数据的读取,导致服务器一直阻塞等待数据的读取

要想解决这两个BUG,其实也很简单——我们需要一个类似信号的功能,当socket缓冲区中有数据,触发相关事件,需要服务器进行处理时,我们再去进行处理,socket缓冲区中没有数据时,服务器不要一直阻塞等待数据

PS:socket的相关事件共分三种——1.读事件、2.写事件、3.异常事件

如何实现简易的一对多服务器

实现简易一对多服务器的大体步骤

  1. 网络初始化
  2. 启动监听,等待socket相关事件(也就是查看socket缓冲区中是否有数据需要处理)
  3. 监听到相关事件辨别是服务器socket还是客户端socket并进行处理

PS:处理socket相关事件这里要分两类——

  1. 服务器套接字接收到客户端的TCP链接请求,调用accept函数
  2. 服务器中为该客户端创建的套接字接收到客户端发来的信息,调用recv函数

每个步骤的具体流程

1.网络初始化

网络初始化这个步骤就不多做介绍了,就是简单的初始化网络信息结构体、创建服务器套接字、绑定套接字等操作

不会的同学可以去看一下我之前写的这篇博客,相关函数与使用方法都在里面:【Linux】如何在本地主机实现简易的一对一服务器(附图解与代码实现)icon-default.png?t=N7T8http://t.csdnimg.cn/thQS8

2.启动监听,等待socket相关事件

既然是要实现一对多的服务器,就代表着我们要为每个链接的客户端分别创建对应的套接字,同时这也就意味着我们自然也要去监听这些套接字

多路IO复用技术这么名字听起来很高大上,其实本质上就是一个IO事件监听技术,也就是一次性可以监听多个socket,来判断这些socket中是否有数据需要处理并反馈给服务器。所以在这个过程中我们也就要用到该技术中的一种——select

接下来我们来讲解一下select的原理实现与相关函数

多路IO复用技术之select模型

原理实现

select中有一个集合,叫做监听集合,我们可以将需要监听的套接字放入套接字文件描述符表中,由该集合负责帮我们监听该文件描述符表中这些套接字文件描述符对应的这些套接字的缓冲区中是否有数据需要处理

这个监听集合的大小为1024(固定大小,不可改),但需要注意的是,虽然这个集合的大小为1024,但实际能帮我们监听的客户端套接字只有1020个,因为前1-3个分别用于监听标准输入、标准输出和标准出错,第四个用于存放服务器套接字

可能只用文字描述过于抽象了,大家可以看下面的这个图来帮助理解

通过这个监听集合,我们就可以实现对多个socket的同时监听

这时候可能就有同学好奇了,监听?他咋监听啊?是什么高级手段吗?

其实这个监听真的是一种很朴实无华的方法,就是遍历,一次次的遍历,当监听集合完成一遍遍历,发现有套接字处于就绪状态,也就是某些套接字的缓冲区中有数据需要处理时,他就会传出一个就绪码(处于就绪状态的套接字数量)和一个就绪集合(就绪的套接字的位码置1,未就绪的套接字的位码置0)

还是画个图来帮助大家理解

以上就是select模型的相关原理了,接下来我们来讲一讲相关函数

以下函数的头文件都是#include<sys/select.h>

介绍一下一会会用到的变量:

  • fd_set set ; //创建监听集合
  • int sockfd ; //套接字文件描述符
  • int max_fd ; //套接字文件描述符表中的描述符个数
  • struct timeval *timeout ; //时间结构体,在这里表示工作模式——1.阻塞、2.非阻塞、3.定时阻塞(非阻塞与定时阻塞需要设置该结构体)

PS : timeout中有两个成员,一个表示秒(timeout.tv_sec),一个表示微秒(timeout.tv_usec)

struct timeval
{
__time_t  tv_sec;        /* Seconds. */
__suseconds_t  tv_usec;  /* Microseconds. */
};
  • timeout = NULL 就表示阻塞监听
  • timeout.tv_sec = 0 、timeout.tv_sec = 0 就表示非阻塞监听
  • timeout.tv_sec = 4、timeout.tv_usec = 30 就表示阻塞4秒30微秒,之后不阻塞
函数功能返回值
FD_ZERO(&set);初始化监听集合,将所有位的位码都初始化为0因为它们只是对文件描述符集合进行操作,而不是返回任何值,所以他们的返回值都是void
FD_SET(sockfd , &set);将set集合中与sockfd对应位的位码设置为1因为它们只是对文件描述符集合进行操作,而不是返回任何值,所以他们的返回值都是void
FD_CLR(sockfd , &set);将set集合中与sockfd对应位的位码设置为0因为它们只是对文件描述符集合进行操作,而不是返回任何值,所以他们的返回值都是void
FD_ISSET(sockfd , &set);获取set集合中与sockfd对应位的位码0或1
int select(max_fd, 是否监听读事件 , 是否监听写事件 , 是否监听错误事件 , timeout);监听我们要求的文件描述符的状态变化情况,并通过返回值告知(PS:想监听对应时间就传入&set,不想就传NULL)返回处于就绪状态的套接字数量

3.监听到相关事件 , 辨别是服务器socket还是客户端socket并进行处理

上面的就绪集合还需要我们自己去遍历,从而找到哪些套接字需要进行数据处理,并辨别是服务器套接字还是客户端套接字

如果是服务器套接字,就说明是有客户端向服务器发送了TCP链接请求,有以下步骤需要执行:

  1. 调用accept函数进行链接并获取与该客户端对应的套接字文件描述符
  2. 将其放入套接字文件描述符存放数组(由于该服务器为单进程,所以我们需要建立一个数组来存放这些客户端套接字文件描述符)(这个地方不太懂的话别着急,结合代码来看一定会让你豁然开朗)
  3. 将该套接字文件描述符放入套接字文件描述符表中,来让监听集合对该套接字进行监听

如果是客户端套接字,就说明是客户端向服务器发送了数据,有以下步骤需要执行:

  1. 调用recv函数读取套接字缓冲区中的数据
  2. 根据客户端发来的数据进行相应处理

具体过程如下图所示:

本地主机实现简易一对多服务器的项目实现

项目构成

该项目由几个程序共同组成,分别是以下几个:

  • func_2th_parcel.h:定义二次包裹的函数名
  • func_2th_parcel.c:对网络初始化相关的函数进行二次包裹
  • select_server.c:使用select模型的服务器程序
  • 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: select_server.c
        > Author: Nan
        > Mail: **@qq.com
        > Created Time: 2023年10月25日 星期三 18时53分30秒
 ************************************************************************/

#include <func_2th_parcel.h>

int main(void)
{
    //一、进行网络初始化
    int server_sockfd;//服务器套接字文件描述符
    int max_fd;//套接字文件描述符表中的描述符个数
    int client_sockfd_array[1020];//存放客户端套接字文件描述符的数组  
    int client_sockfd;//客户端套接字文件描述符
    int ready_num = 0;//获取处于就绪状态的套接字数目
    char rw_buffer[1500];//读写缓冲区
    int flag;
    int recv_len = 0;//客户端发来的数据长度
    memset(client_sockfd_array , -1 , sizeof(client_sockfd_array));//将套接字数组每一位都置为-1,方便后面查找就绪套接字
    bzero(rw_buffer , sizeof(rw_buffer));
    fd_set listen_set , ready_set;//监听集合,就绪集合
    server_sockfd = SOCKET_NET_CREATE("192.168.79.128" , 6060);//初始化服务器套接字
    max_fd = server_sockfd;//初始化最大套接字数目
    //初始化监听集合,将server_sockfd设置为监听套接字
    FD_ZERO(&listen_set);
    FD_SET(server_sockfd , &listen_set);
    printf("select_server wait TCP connect\n");
    //二、启动监听,等待socket相关事件
    while(1)
    {
        ready_set = listen_set;
        //阻塞等待socket相关事件
        if((ready_num = select(max_fd + 1 , &ready_set , NULL , NULL , NULL)) == -1)
        {
            perror("select call failed\n");
            exit(0);
        }

        //三、监听到相关事件 , 辨别是服务器socket还是客户端socket并进行处理
        while(ready_num)
        {
            //辨别就绪,如果是服务端套接字就绪
            if(FD_ISSET(server_sockfd , &ready_set))
            {
                client_sockfd = SERVER_ACCEPTING(server_sockfd);//与客户端建立TCP链接
                FD_SET(client_sockfd , &listen_set);//将该套接字放入监听集合中
                //如果max_fd小于客户端套接字返回的描述符,说明这个新的客户端套接字放到了最后一位,max_fd需要加1
                if(max_fd < client_sockfd)
                {
                    max_fd = max_fd + 1;
                }
                for(int i = 0 ; i < 1020 ; i++)
                {
                    //将该客户端套接字,放到数组中有空缺的地方
                    if(client_sockfd_array[i] == -1)
                    {
                        client_sockfd_array[i] = client_sockfd;
                        break;
                    }
                }
                //将就绪集合中服务器套接字这一位的位码置为0,因为如果ready_num > 1,不做该处理服务器会一直认为是客户端发送了TCP链接请求,从而导致错误处理
                FD_CLR(server_sockfd , &ready_set);
            }
            //如果是客户端套接字就绪
            else
            {
                for(int i = 0 ; i < 1020 ; i++)
                {
                    //检测存放的客户端套接字是否处于就绪状态
                    if(client_sockfd_array[i] != -1)
                    {
                        //如果该套接字处于就绪状态
                        if(FD_ISSET(client_sockfd_array[i] , &ready_set))
                        {
                            recv_len = RECV(client_sockfd_array[i] , rw_buffer , sizeof(rw_buffer) , 0);//获取数据长度
                            printf("客户端%d 发来数据 : %s , 现在进行处理\n" , client_sockfd_array[i] , rw_buffer);
                            flag = 0;
                        }
                        //如果recv_len = 0,就说明与客户端套接字对应的客户端退出了,将对应客户端套接字移出套接字存储数组与监听集合
                        if(recv_len == 0)
                        {
                            printf("客户端%d 已下线\n" , client_sockfd_array[i]); 
                            FD_CLR(client_sockfd_array[i] , &ready_set);
                            client_sockfd_array[i] = -1;
                            break;
                        }
                        //进行业务处理:小写字母转大写字母
                        while(recv_len > flag)
                        {
                            rw_buffer[flag] = toupper(rw_buffer[flag]);
                            flag++;
                        }
                        SEND(client_sockfd_array[i] , rw_buffer , recv_len , MSG_NOSIGNAL);
                        printf("已向客户端%d 发送处理后的数据 : %s\n" , client_sockfd_array[i] , rw_buffer);
                        bzero(rw_buffer , sizeof(rw_buffer));
                        recv_len = 0;
                        FD_CLR(client_sockfd_array[i] , &ready_set);
                        break;
                    }
                }
            }
            ready_num--;
        }
    }
    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/1147853.html

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

相关文章

Python超入门(7)__迅速上手操作掌握Python

# 31.类 class Point:# 构造函数def __init__(self, x, y, z):self.x xself.y yself.z z# 自定义函数def move(self):print("move")def draw(self):print("draw")# 定义一个Point类的实例point1 # 注意&#xff1a;新建实例的参数要与构造函数一致 poi…

一天下来一个微信号能添加多少个微信好友?

在即时通讯领域&#xff0c;微信的用户量处于领先的地位。据了解微信及WeChat合并的月活跃账户数已超13亿。远远超越QQ的移动端5.71亿的月活跃用户数量。 那么&#xff0c;微信的用户数量这么多&#xff0c;一天可以加多少好友呢&#xff1f; 新号和不活跃的号 01 微信新号是…

【计算机网络】分层模型和应用协议

网络分层模型和应用协议 1. 分层模型 1.1 五层网络模型 网络要解决的问题是&#xff1a;两个程序之间如何交换数据。 四层&#xff1f;五层&#xff1f;七层&#xff1f; 2. 应用层协议 2.1 URL URL&#xff08;uniform resource locator&#xff0c;统一资源定位符&#…

ZYNQ连载06-EasyLogger日志组件

ZYNQ连载06-EasyLogger日志组件 1. EasyLogger介绍 Easylogger仓库 2. EasyLogger移植 EasyLogger移植比较简单&#xff0c;在Vitis中移植时主要注意路径问题&#xff0c;然后适配下接口即可&#xff1a; void elog_port_output(const char *log, size_t size) {printf(&…

密码学基础

密码学总览 信息安全面临的危险与应对这些威胁的密码技术&#xff1a; 关于上图中的威胁&#xff0c;这里在简单的说明&#xff1a; 窃听&#xff1a;指的是需要保密的消息被第三方获取。篡改&#xff1a;指的是消息的内容被第三方修改&#xff0c;达到欺骗的效果。伪装&…

k8s命令式对象管理、命令式对象配置、声明式对象配置管理资源介绍

目录 一.kubernetes资源管理简介 二.三种资源管理方式优缺点比较 三.命令式对象管理介绍 1.kubectl命令语法格式 2.资源类型 &#xff08;1&#xff09;通过“kubectl api-resources”来查看所有的资源 &#xff08;2&#xff09;每列含义 &#xff08;3&#xff09;常…

RabbitMQ学习05

文章目录 交换机1.Exchanges1.1 概念1.2 类型1.3 无名exchange 2. 临时队列3. 绑定&#xff08;bings&#xff09;4. Fanout4.1 介绍 5.Direct exchange5.1 介绍5.2 多重绑定5.3 实战: 6. Topics6.1 规则6.2 实战 交换机 1.Exchanges 1.1 概念 RabbitMQ 消息传递模型的核心思…

C++初阶2

目录 一&#xff0c;auto关键字 1-1&#xff0c;auto的使用 1-2&#xff0c;基于范围auto的for循环 二&#xff0c;nullptr的运用 三&#xff0c;C类的初步学习 3-1&#xff0c;类的引用 3-2&#xff0c;类的访问权限 3-3&#xff0c;类的使用 1&#xff0c;类中函数的…

SA实战 ·《SpringCloud Alibaba实战》第12章-服务网关:网关概述与核心架构

作者:冰河 星球:http://m6z.cn/6aeFbs 博客:https://binghe.gitcode.host 文章汇总:https://binghe.gitcode.host/md/all/all.html 大家好,我是冰河~~ 一不小心《SpringCloud Alibaba实战》专栏都更新到第12章了,再不上车就跟不上了,小伙伴们快跟上啊! 在《SpringClou…

基于AliO Things和阿里云的智能环境监控系统。

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、实习内容二、实习方法2.1搭建开发环境并完成编译2.1.1 正常完成编译的标志2.1.2 编写实例烧录程序&#xff0c;并完成烧录 2.2按键实现流水灯2.2.1 HaaS ED…

General Expression In Oral English

1. With all Due Respect 恕我直言&#xff0c; 那些和你混在一起的混蛋们只关心政治 With all due respect , these sons of bitches that youre mingling with(与.. 交际) only care about politics

A. Doremy‘s Paint 3(规律)

Problem - A - Codeforces 解析&#xff1a; 首先最多只能存在两个值&#xff0c;因为间隔必须相同。并且两个值的数量相差小于等于1 #include<bits/stdc.h> using namespace std; #define int long long const int N2e55; int t,n,a[N]; map<int,int>mp; signed…

【SpringSecurity】快速入门—通俗易懂

目录 1.导入依赖 2.继承WebSecurityConfigurerAdapter 3.实现UserDetailsService 4.记住我 5.用户注销 6.CSRF理解 7.注解功能 7.1Secured 7.2PreAuthorized 7.3PostAuthorized 7.4PostFilter 7.5ZPreFilter 8.原理解析 1.导入依赖 首先&#xff0c;在pom.xml文…

第五章 I/O管理 二、I/O控制器

目录 一、电子部件 1、I/O控制器 1.功能&#xff1a; &#xff08;1&#xff09;接受和识别CPU发出的命令&#xff1a; &#xff08;2&#xff09;向CPU报告设备的状态 &#xff08;3&#xff09;数据交换 &#xff08;4&#xff09;地址识别 2.组成 二、内存映像和寄…

磁盘调度算法之先来先服务(FCFS),最短寻找时间优先(SSTF),扫描算法(SCAN,电梯算法),LOOK调度算法

目录 1.一次磁盘读/写操作需要的时间1.寻找时间2.延迟时间3.传输时间4.影响读写操作的因素 2.磁盘调度算法1.先来先服务(FCFS)1.例题2.优缺点 2.最短寻找时间优先(SSTF)1.例题2.优缺点3.饥饿的原因 3.扫描算法(SCAN)1.例题2.优缺点 4.LOOK调度算法1.例题2.优点 5.循环扫描算法(…

81 分割回文串

分割回文串 题解1 回溯题解2 回溯dp利用dp相当于先判断哪段是回文(省掉了每次都需要调用的isValid)【预处理】 给你一个字符串 s&#xff0c;请你将 s 分割成一些子串&#xff0c;使 每个子串都是 回文串 。返回 s 所有可能的分割方案。 回文串 是正着读和反着读都一样的字…

【idea】新建Maven文件都会默认在C盘下载的解决方法

Customize——>All settings 完成配置 同理&#xff0c;其他需要全局配置的设置步骤也是一样的。

第9天:字符编码

字符编码 字符串类型、文本文件的内容都是由字符组成的&#xff0c;但凡涉及到字符的存取&#xff0c;都需要考虑字符编码的问题。 存储原理&#xff1a; 1、软件运行前&#xff0c;软件的代码及其相关数据都是存放于硬盘中的 2、任何软件的启动都是将数据从硬盘中读入内存&…

JAVA-JVM 之Class字节码文件的组成 【上篇】

字节码 前言概述基本结构魔数头版本号常量池访问标志 主页传送门&#xff1a;&#x1f4c0; 传送 前言 java的特点是跨平台性&#xff0c;而跨平台的运行标准是Class字节码文件&#xff0c;Class字节码是提供平台无关性的基础模型&#xff0c;使我们无须考虑如何兼容异构系统&…

车载电子电器架构 —— 基于AP定义车载HPC

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…