Linux高性能服务器编程——ch8笔记

news2024/11/14 19:28:31

第8章 高性能服务器程序框架

8.1 服务器模型


服务器启动后,首先创建一个(或多个)监听socket,并调用bind函数将其绑定到服务器感兴趣的端口,然后调用listen函数等待客户连接。服务器稳定运行之后,客户端就可以调用connect函数向服务器发起连接。由于客户连接请求是随机到达的异步事件,版务器需要使用某种I/O模型来监听这一事件。
下图服务器使用的是I/O复用技术之一的select系统调用。当监听到连接请求后,服务器就调用accept函数接受它,并分配一个逻辑单元为新的连接服务。逻辑单元可以是新创建的子进程、子线程或者其他,下图服务器给客户端分配的逻辑单元是fork系统调用创建的子进程。逻辑单元读取客户请求,处理该请求,然后将处理结果返回给客户端,客户端接收到服务器反馈的结果之后,可以继续向服务器发送请求,也可以主动关闭连接。如果客户端主动关团连接,则服务器执行被动关闭连接。至此,双方的通信结束。服务器同时监听多个客户请求是通过select系统调用实现的。

访问量过大时,响应较慢。
P2P(Peer to Peer,点对点)模型使得每台机器在消耗服务的同时也给别人提供服务,每台主机既是客户端,也是服务器,专门的发现服务器用来提供查找服务。

8.2 服务器编程框架


image.pngimage.png

8.3 I/O模型

socket创建时默认是阻塞的。阻塞的概念也能应用于文件描述符,(非)阻塞的文件描述符称为(非)阻塞I/O。
针对阻塞I/O执行的系统调用可能因为无法立即完成而被OS挂起;针对非阻塞I/O执行的系统调用则总是立即返回,而不管事件是否已经发生。
只有在事件已经发生的情况下操作非阻塞I/O,才能提高效率,非阻塞I/O要和其他I/O通知机制(I/O复用和SIGIO信号)一起使用。
同步I/O(阻塞I/O、I/O复用和信号驱动I/O)要求用户代码自行执行I/O 操作(将数据从内核缓冲区读入用户缓冲区,或将数据从用户缓冲区写入内核缓冲区),而异步 I/O机制则由内核来执行I/O操作(数据在内核缓冲区和用户缓冲区之间的移动是用内核在“后台”完成的。
同步I/O向应用程序通知的是I/O就绪事件,由应用程序完成I/O读写;而异步I/O向应用程序通知的是I/O完成事件,由内核完成I/O读写。
image.png

8.4 两种高效的事件处理模式

三类事件:I/O事件、信号事件、定时事件。
Reactor事件处理模式:要求主线程(I/O处理单元)只负责监听文件描述上是否有事件发生。有则立即将事件通知工作线程(逻辑单元),然后读写数据,接受新连接,以及处理客户请求。
同步I/O模型(epoll_wait为例)实现:
1)主线程往epoll内核事件表中注册socket上的读就绪事件;
2)主线程调用epoll_wait等待socket上有数据可读;
3)socket上有数据时,epoll_wait通知主线程将socket可读事件放入请求队列;
4)睡眠在请求队列上的某个工作线程被唤醒,从socket上读取数据,处理客户请求,然后往epoll内核事件表中注册该socket上的写就绪事件。
5)主线程调用epoll_wait等待socket可写。
6)socket可写时,epoll_wait通知主线程,将socket可写事件放入请求队列;
7)睡眠在请求队列上的某个工作线程被唤醒,它往socket上写入服务器处理客户请求的结果。
image.png
Proactor事件处理模式:将所有I/O操作交给主线程和内核处理,工作线程仅负责业务逻辑。
异步I/O模型(aio_read和aio_write为例)实现:
1)主线程调用aio_read函数向内核注册socket上的读完成事件,并告诉内核用户读缓冲区的位置,以及读操作完成时如何通知应用程序(信号为例);
2)主线程继续处理其他逻辑;
3)当socket上的数据被读入用户缓冲区后,内核向应用程序发送信号,以通知应用程序数据已经可用;
4)应用程序预先定义好的信号处理函数选择一个工作线程来处理客户请求。工作线程处理完客户请求之后,调用aio_write函数向内核注册socket上的写完成事件,并告诉内核用户写缓冲区的位置,以及写操作完成时如何通知应用程序(信号为例);
5)主线程继续处理其他逻辑;
6)用户缓冲区的数据被写入socket后,内核将向应用程序发送信号,通知应用程序数据发送完毕;
7)应用程序预先定义好的信号处理函数选择一个工作线程来做善后处理,比如决定是否关闭socket。
image.png
使用同步I/O模型模拟Proactor模式:
1)主线程往epoll内核事件表中注册socket上的读就绪事件;
2)主线程调用epoll_wait等待socket上有数据可读;
3)当socket上有数据可读时,epoll_wait通知主线程。主线程从socket循环读取数据,直到没有更多数据可读,然后将读取到的数据封装成一个请求对象,插入请求队列;
4)睡眠在请求队列上的某个工作线程被唤醒,它获得请求对象并处理客户请求,然后往epoll内核事件表中注册socket上的写就绪事件。
5)主线程调用epoll_wait等待socket可写;
6)当socket可写时,epoll_wait通知主线程。主线程往socket上写入服务器处理客户请求的结果。
image.png

8.5 两种高效的并发模式

这里讨论模式。并发模式指I/O处理单元和多个逻辑单元之间协调完成任务。
半同步/异步模式:这里的同步异步指程序的执行是按顺序还是由系统事件驱动。
image.png
半同步/半异步模式中,同步线程用于处理客户逻辑,相当于逻辑单元;异步线程用于处理I/O事件,相当于I/O处理单元。异步线程监听到客户请求后,就将其封装成请求对象并插入请求队列中,请求队列将通知某个工作在同步模式的工作线程(选取方法:Round Robin、条件变量、信号量)读取并处理该请求对象。
image.png
其变体:半同步/半反应堆模式。
image.png
半同步/半反应堆模式采用的事件处理模式是Reactor模式:要求工作线程从socket上读取客户请求和往socket写入服务器应答。也可以使用模拟的Proactor事件处理模式。
缺点:
1)主线程和工作线程共享请求队列,添加和取出任务都要对请求队列加锁保护,耗费CPU时间;
2)每个工作线程在同一时间只能处理一个客户请求,客户多,则队列堆积,客户端响应慢,若增加工作线程,则工作线程的切换也会耗费CPU时间。
image.png
主线程只管理监听socket,工作线程管理连接socket。派发方式:往主线程和工作线程之间的管道里写数据,工作线程检测到管道上有数据可读时,就分析是否是一个新的客户连接请求到来。如果是,则把该新soeket上的读写事件注册到自己的epoll内核事件表中。
领导者/追随者模式:多个工作线程轮流获得事件源集合,轮流监听、分发并处理事件。在任意时间点,程序都仅有一个领导者线程,负责监听I/O事件,而其他线程则都是追随者,它们休眠在线程池中等待成为新的领导者,当前的领导者如果检测到 I/O事件,首先要从线程池中推选出新的领导者线程,然后处理I/O事件。此时,新的领导者等得新的I/O事件,而原来的领导者则处理I/O事件,二者实现了并发。
组件:句柄集(HandleSet)、线程集(ThreadSet)、事件处理器(EventHandler)、具体的事件处理器(ConcreteEventHandler)。
image.png
句柄集:句柄表示I/O资源(文件描述符),句柄集用wait_for_event方法监听句柄上的I/O事件,将就绪事件通知领导者线程。领导者则调用绑定到Handle上的事件处理器来处理事件。领导者将Handle和事件处理器绑定是通过调用句柄集中的register_handle方法实现。
线程集:所有工作线程管理者。负责个线程之间的同步,及新领导者推选。线程集中线程的三种状态:Leader(领导者)、Processing(处理事件中)、Follower(追随者)。
image.png
事件处理器和具体的事件处理器:事件处理器通常包含回调函数,用于处理事件对应的业务逻辑,使用前被绑定到句柄上。具体的事件处理器是其派生类,必须重新实现基类的handle_event方法来处理特定任务。
image.png

8.6 有限状态机

用于逻辑单元内部。有的应用层协议头部包含数据包类型字段,每种类型可以映射为逻辑单元的一种执行状态,服务器可以根据它来编写相应的处理逻辑,通过内部驱动实现状态转移。
很多网络协议,包括TCP协议和IP协议,都在其头部中提供头部长度字段。程序根据该字段值就可以知道是否接收到一个完整的协议头部。但HTTP协议并未提供这样的头部长度字段,并且其头部长度变化也很大。
判断HTTP头部结束的依据:遇到一个空行仅包含一对回车换行符(<CR><LF>)。如果一次读操作没有读入HTTP请求的整个头部,即没有遇到空行,那么必须等待客户继续写数据并再次读入。不过在寻找空行的过程中,可以同时完成对整个HTTP请求头部的分析(空行前面还有请求行和头部域),以提高解析HTTP请求的效率。
使用主、从两个有限状态机实现最简单的HTTP请求的读取和分析:

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>

#define BUFFER_SIZE 4096    /* 读缓冲区大小 */
/* 主状态机的两种可能状态,分别表示:当前正在分析请求行, 当前正在分析头部字段 */
enum CHECK_STATE{
    CHECK_STATE_REQUESTLINE = 0,
    CHECK_STATE_HEADER
};

/* 从状态机的三种可能状态,即行的读取状态,分别表示:读取到一个完整的行、行出错
 * 和行数据尚且不完整 */
enum LINE_STATUS{
    LINE_OK = 0,
    LINE_BAD,
    LINE_OPEN
};

/* 服务器处理HTTP请求的结果: NO_REQUEST表示请求不完整,需要继续读取客户数据;
 * GET_REQUEST表示获得了一个完整的客户请求;
 * BAD_REQUEST表示客户请求有语法错误;
 * FORBIDDEN_REQUEST表示客户对资源没有足够的访问权限;
 * INTERNAL_ERROR表示服务内部错误;
 * CLOSED_CONNECTION表示客户端已经关闭连接了 */
enum HTTP_CODE{
    NO_REQUEST,
    GET_REQUEST,
    BAD_REQUEST,
    FORBIDDEN_REQUEST,
    INTERNAL_ERROR,
    CLOSED_CONNECTION
};

/* 为了监护问题,我们没有给客户端发送一个完整的HTTP应答报文,
 * 而只是根据服务器的处理结果发送如下成功或失败信息 */
static const char *szret[] = {"I get a correct result\n",
"Somethin wrong\n"};

/* 从状态机,用于解析出一行内容 */
LINE_STATUS parse_line(char *buffer, int &checked_index, int &read_index)
{
    char temp;
    /* checked_index指向buffer(应用程序的读缓冲区)中当前正在分析的字节,
     * read_index指向buffer中客户数据的尾部的下一字节,buffer中第0~checked_index
     * 字节都已经分析完毕,第checked_index~(read_index - 1)字节由下面的循环挨个分析 */
    for (; checked_index < read_index; ++checked_index)
    {
        /* 获得当前要分析的字节 */
        temp = buffer[checked_index];
        /* 如果当前的字节是"\r", 即回车符,则说明可能读取到一个完整的行 */
        if (temp == '\r')
        {
            /* 如果"\r"字符碰巧是目前buffer中的最后一个已经被读入的客户数据,
             * 那么分析没有读取到一个完整的行,返回LINE_OPEN以表示还需要继续
             * 读取客户数据才能进一步分析 */
            if ((checked_index + 1) == read_index)
            {
                return LINE_OPEN;
            }
            /* 如果下一个字符是“\n”,则说明我们成功读取到一个完整的行 */
            else if (buffer[checked_index + 1] == '\n')
            {
                buffer[checked_index++] = '\0';
                buffer[checked_index++] = '\0';
                return LINE_OK;
            }
            /* 否则的话,说明客户范松的HTTP请求存在语法问题 */
            return LINE_BAD;
        }
        /* 如果当前的字节是"\n", 即换行符,则也说明可能读取到一个完整的行 */
        else if (temp == '\n')
        {
            if ((checked_index > 1) && buffer[checked_index - 1] == '\r')
            {
                buffer[checked_index++] = '\0';
                buffer[checked_index++] = '\0';
                return LINE_OK;
            }

            return LINE_BAD;
        }
    }

    /* 如果所有内容都分析完毕也没有遇到"\r"字符,则返回LINE_OPEN,
     * 表示还需要继续读取客户数据才能进一步分析 */
    return LINE_OPEN;
}

/* 分析请求行 */
HTTP_CODE parse_requestline(char *temp, CHECK_STATE &checkstate)
{
    char *url = strpbrk(temp, " \t");
    /* 如果请求航中没有空白字符或“\t”字符,则HTTP请求必有问题 */
    if (!url)
    {
        return BAD_REQUEST;
    }
    *url++ = '\0';

    char *method = temp;
    if (strcasecmp(method, "GET") == 0) /* 仅支持GET方法 */
    {
        printf("The request method is GET\n");
    }
    else
    {
        return BAD_REQUEST;
    }

    url += strspn(url, " \t");
    char *version = strpbrk(url, " \t");
    if (!version)
    {
        return BAD_REQUEST;
    }

    *version++ = '\0';
    version += strspn(version, " \t");
    /* 仅支持HTTP/1.1 */
    if (strcasecmp(version, "HTTP/1.1") != 0)
    {
        return BAD_REQUEST;
    }

    /* 检查URL是否合法 */
    if (strncasecmp(url, "http://", 7) == 0)
    {
        url += 7;
        url = strchr(url, '/');
    }

    if (!url || url[0] != '/')
    {
        return BAD_REQUEST;
    }

    printf("The request URL is %s\n", url);
    /* HTTP 请求行处理完毕,状态转移到头部字段的分析 */
    checkstate = CHECK_STATE_HEADER;
    return NO_REQUEST;
}

/* 分析头部字段 */
HTTP_CODE parse_headers(char *temp)
{
    /* 遇到一个空行,说明我们得到了一个正确的HTTP请求 */
    if (temp[0] == '\0')
    {
        return GET_REQUEST;
    }
    else if (strncasecmp(temp, "Host:", 5) == 0) /* 处理"HOST"头部字段 */
    {
        temp += 5;
        temp += strspn(temp, " \t");
        printf("the request host is: %s\n", temp);
    }
    else /* 其他头部字段暂不处理 */
    {
        printf("I can not handle this header\n");
    }

    return NO_REQUEST;
}

/* 分析HTTP请求的入口函数 */
HTTP_CODE parse_content(char *buffer, int &checked_index, CHECK_STATE &checkstate,
        int &read_index, int &start_line)
{
    LINE_STATUS linestatus = LINE_OK;   /* 记录当前行的读取状态 */
    HTTP_CODE retcode = NO_REQUEST;     /* 记录HTTP请求的处理结果 */
    /* 主状态机,用于从buffer中取出所有完整的行 */
    while ((linestatus == parse_line(buffer, checked_index, read_index)) == LINE_OK)
    {
        char *temp = buffer + start_line;   /* start_line是行在buffer中的起始位置 */
        start_line = checked_index;     /* 记录下一行的起始位置 */
        /* checkstate 记录主状态机当前的状态 */
        switch(checkstate)
        {
            case CHECK_STATE_REQUESTLINE:   /* 第一个状态,分析请求行 */
                {
                    retcode = parse_requestline(temp, checkstate);
                    if (retcode == BAD_REQUEST)
                    {
                        return BAD_REQUEST;
                    }
                    break;
                }
            case CHECK_STATE_HEADER:    /* 第二个状态,分析头部字段 */
                {
                    retcode = parse_headers(temp);
                    if (retcode == BAD_REQUEST)
                    {
                        return BAD_REQUEST;
                    }
                    else if (retcode == GET_REQUEST)
                    {
                        return GET_REQUEST;
                    }
                    break;
                }
            default:
                {
                    return INTERNAL_ERROR;
                }
        }
    }

    /* 若没有读取到一个完整的行,则表示还需要继续读取客户数据才能进一步分析 */
    if (linestatus == LINE_OPEN)
    {
        return NO_REQUEST;
    }
    else
    {
        return BAD_REQUEST;
    }
}

int main(int argc, char *argv[])
{
    if (argc < 2)
    {
        printf("usage: %s ip_address port_number \n", basename(argv[0]));
        return 1;
    }

    const char *ip = argv[1];
    int port = atoi(argv[2]);

    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);

    int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(listenfd >= 0);
    int ret = bind(listenfd, (struct sockaddr *)&address, sizeof(address));
    assert(ret != -1);
    ret = listen(listenfd, 5);
    assert(ret != -1);

    struct sockaddr_in client_address;
    socklen_t client_addrlength = sizeof(client_address);

    int fd = accept(listenfd, (struct sockaddr *)&client_address, &client_addrlength);
    if (fd < 0)
    {
        printf("errno is : %d\n", errno);
    }
    else
    {
        char buffer[BUFFER_SIZE];   /* 读缓冲区 */
        memset(buffer, '\0', BUFFER_SIZE);
        int data_read = 0;
        int read_index = 0;     /* 当前已经读取了多少字节的客户数据 */
        int checked_index = 0;  /* 当前已经分析完了多少字节的客户数据 */
        int start_line = 0;      /* 行在buffer中的起始位置 */
        /* 设置主状态机的初始状态 */
        CHECK_STATE checkstate = CHECK_STATE_REQUESTLINE;
        while (1)   /* 循环读取客户数据并分析之 */
        {
            data_read = recv(fd, buffer + read_index, BUFFER_SIZE - read_index, 0);
            if (data_read == -1)
            {
                printf("reading failed\n");
                break;
            }
            else if (data_read == 0)
            {
                printf("remote client has closed the connection\n");
                break;
            }

            read_index += data_read;
            /* 分析目前已经获得的所有客户数据 */
            HTTP_CODE result = parse_content(buffer, checked_index, checkstate,
                    read_index, start_line);    /* 尚未得到一个完整的HTTP请求 */
            if (result == NO_REQUEST)
            {
                continue;
            }
            else if (result == GET_REQUEST)     /* 得到一个完整的、正确的HTTP请求 */
            {
                send(fd, szret[0], strlen(szret[0]), 0);
                break;
            }
            else    /* 其他情况表示发生错误 */
            {
                send(fd, szret[1], strlen(szret[1]), 0);
                break;
            }
        }
        close(fd);
    }

    close(listenfd);

    return 0;
}

image.png
image.png

8.7 提高服务器性能的其他建议

软件上:系统软件资源(OS允许用户打开的最大文件描述符数量)、服务器程序本身。
:用服务器硬件资源换取运行效率。池是一组资源的集合,在服务器启动时就被完全创建好并初始化(静态资源分配)。直接从池中获取资源,也可在处理完客户连接后放回池中。池相当于服务器管理系统资源的应用层设施,避免对内核的频繁访问。内存池:用于socket的接收缓存和发送缓存。比如HTTP请求,预先分配足够大的接收缓存区。
进程池、线程池:需要工作进程或线程处理客户请求时,从池中取得执行实体,无须动态调用fork或pthread_create。
连接池:用于服务器或服务器机群的内部永久连接。如服务器预先和DB建立连接。
数据复制:内核直接处理从socket或者文件读入的数据,如ftp服务器使用”零拷贝“函数sendfile;使用共享内存,而不是管道或消息队列;指针。
上下文切换和锁:进程切换或线程切换导致的的系统开销,半同步/半异步模式(一个线程同时处理多个客户连接)、多线程服务器的一个优点是不同的线程可以同时运行在不同的CPU上,当线程的数量不大于CPU的数目时,上下文的切换就不是问题;共享资源的加锁保护,高效的半同步/半异步模式、减小锁的粒度,使用读写锁,读一块共享内存不增加开销,写内存才会锁。**

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

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

相关文章

网盘限速问题解析:哪家网盘真的不限速?

天下苦网盘限速久矣。市面上一些网盘工具要不然是收费限流&#xff0c;要不然是需要额外购买下载券。哪家网盘真的不限速&#xff1f; Zoho Workdrive 企业网盘是真正的不限速网盘&#xff0c;上传和下载文件都不限速&#xff0c;真正做到用户的网速有多快&#xff0c;下载就有…

玩转ChatGPT:批量下载Alphafold的蛋白pdb文件

一、写在前面 突发奇想&#xff0c;想批量下载Alphafold网站的蛋白pdb文件&#xff0c;后续再做个分子对接用。又不想手动下载&#xff0c;来求助CSDN和GPT。 二、CSDN白嫖基础代码 CSDN大神多&#xff0c;这不&#xff0c;找到一个&#xff1a;Alphafold批量下载蛋白的pdb文…

Unity Shader当用户靠近的时候会出现吃鸡一样的光墙

效果图片 靠近墙壁 远离墙壁 材质球的设置 两张图片 使用方式 把这个脚本放到墙上&#xff0c;将player赋值给"_player"&#xff0c;然后运行&#xff0c;用户靠近就会根据距离显示光墙。 using UnityEngine;public class NewBehaviourScript : MonoBehaviour {pr…

【算法|贪心算法系列No.5】leetcode409. 最长回文串

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【手撕算法系列专栏】【LeetCode】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&#xff0c;希望…

win10 + VS2017 编译libjpeg(jpeg-9b)

需要用到的文件&#xff1a; jpeg-9b.zip win32.mak 下载链接链接&#xff1a;https://pan.baidu.com/s/1Z0fwbi74-ZSMjSej-0dV2A 提取码&#xff1a;huhu 步骤1&#xff1a;下载并解压jpeg-9b。 这里把jpeg-9b解压到文件夹"D:\build-libs\jpeg\build\jpeg-9b" …

创纪录的1亿RPS DDoS攻击利用HTTP/2快速重置漏洞

导语&#xff1a;最近&#xff0c;一项创纪录的DDoS攻击引起了广泛关注。攻击者利用了HTTP/2协议中的一个快速重置漏洞&#xff0c;发起了一系列超大规模的攻击。本文将为大家详细介绍这次攻击的背景、影响以及应对措施。 攻击背景 最近&#xff0c;全球范围内遭受了一系列规模…

计算机操作系统重点概念整理-第六章 输入输出I/O管理【期末复习|考研复习】

第六章 输入输出I/O管理【期末复习|考研复习】 计算机操作系统系列文章传送门&#xff1a; 第一章 计算机系统概述 第二章 进程管理 第三章 进程同步 第四章 内存管理 第五章 文件管理 第六章 输出输出I/O管理 文章目录 第六章 输入输出I/O管理【期末复习|考研复习】前言六、输…

c++的4中类型转换操作符(static_cast,reinterpret_cast,dynamic_cast,const_cast),RTTI

目录 引入 介绍 static_cast 介绍 使用 reinterpret_cast 介绍 使用 const_cast 介绍 使用 dynamic_cast 介绍 使用 RTTI(运行时确定类型) 介绍 typeid运算符 dynamic_cast运算符 type_info类 引入 原本在c中,我们就已经接触到了很多类型转换 -- 隐式类型转…

论文阅读——GPT3

来自论文&#xff1a;Language Models are Few-Shot Learners Arxiv&#xff1a;https://arxiv.org/abs/2005.14165v2 记录下一些概念等。&#xff0c;没有太多细节。 预训练LM尽管任务无关&#xff0c;但是要达到好的效果仍然需要在特定数据集或任务上微调。因此需要消除这个…

YOLOv5配置文件之 - yaml

在YOLOv5的目录中&#xff0c;models文件夹里存储了YOLO的模型配置。 ./models/yolov5.yaml 定义了YOLOv5s网络结构的定义文件 yaml的主要内容 参数配置 nc: 80 类别数量 depth_multiple: 0.33 模型深度缩放因子 width_multiple: 0.50 控制卷积特征图的通道个数 anchors配…

JSON(详解)

目录 什么是JSON&#xff1f; 哪里会用到JSON&#xff1f; JSON的特点 JSON的优点 JSON的缺点 JSON和cJSON的关系 什么是JSON&#xff1f; JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式。它以易于阅读和编写的文本格式来存储和表示…

Linux mkdir命令:创建目录(文件夹)

mkdir 命令&#xff0c;是 make directories 的缩写&#xff0c;用于创建新目录&#xff0c;此命令所有用户都可以使用。mkdir 命令的基本格式为&#xff1a; [rootlocalhost ~]# mkdir [-mp] 目录名 -m 选项用于手动配置所创建目录的权限&#xff0c;而不再使用默认权限。 -p…

shell算数运算指令、

1.shell算数运算的指令 (( )) $[ ] let expr expr的字符串运算 例子&#xff1a; 2.shell的if分支结构

1624. 两个相同字符之间的最长子字符串

1624. 两个相同字符之间的最长子字符串 java代码&#xff1a; class Solution {public int maxLengthBetweenEqualCharacters(String s) {int[] hash new int[26];Arrays.fill(hash, -1); // fill是Arrays静态方法int max -1;for (int i 0; i < s.length(); i) { // 对…

计算机毕业设计 基于SpringBoot大学生创新创业项目管理系统的设计与实现 Javaweb项目 Java实战项目 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

ResNet简单解释

什么是Resnet&#xff1f; resnet是一种残差网络&#xff0c;简单看一下resnet的结构 上面是ResNet&#xff0c;下面是传统的 ResNet里的一小块就是这样的 为什么要引入ResNet&#xff1f; 理论上讲&#xff0c;随着网络加深&#xff0c;我们获取的信息越来越多多&#xff0…

C语言 每日一题 PTA 10.27 day5

1.高速公路超速处罚 按照规定&#xff0c;在高速公路上行使的机动车&#xff0c;达到或超出本车道限速的10 % 则处200元罚款&#xff1b; 若达到或超出50 % &#xff0c;就要吊销驾驶证。请编写程序根据车速和限速自动判别对该机动车的处理。 输入格式 : 输入在一行中给出2个正…

构造类型详解及热门题型结构体大小的计算

在编写程序时&#xff0c;简单的变量类型已经不能满足程序中各种复杂数据的需求&#xff0c;因此c语言还提供了构造类型的数据&#xff0c;构造数据是有基本数据按照一定的规则组成的。 目录 结构体类型的概念 结构体变量的定义 结构体变量的初始化 结构体变量的引用 结构…

老年少女测试媛入职感想

作为一枚从事通信行业测试的老年少女测试媛&#xff0c;入职离职也有两三次了。现在又在一家企业入职了。虽然心里也清楚离职和入职&#xff0c;无非也就是从一个公司的坑里跳出来&#xff0c;再跳到另外一个公司的坑里罢了&#xff0c;明明知道老东家的坑是填不完的了&#xf…

【Java 进阶篇】Java Request 获取请求头数据详解

在Java Web开发中&#xff0c;获取HTTP请求的请求头数据是一项常见任务。HTTP请求的请求头包含了客户端发送给服务器的额外信息&#xff0c;这些信息对于服务器来说很重要&#xff0c;因为它们可以包含用户代理、授权信息、Cookies等内容。在Java中&#xff0c;可以使用HttpSer…