网络缓冲区

news2024/11/29 2:30:30

        windows下的体系,我不是特别了解。以下所有的内容都是在Linux下的理解,如果不对的地方,评论区欢迎留言。

Linux收发数据

 接收数据

大体的流程如上图所示,接下来我们对图中一些名词进行解释。

-------------------------------------------------------------------------------------------------------------------------

DMA:

  • 一种计算机系统中的技术,用于在外设和内存之间直接传输数据,而不需要CPU的干预。DMA技术通过在外设和内存之间建立直接的数据传输通路,绕过CPU直接进行数据传输。DMA控制器负责管理数据传输的过程,而CPU可以继续执行其他任务,提高了系统的并发性和效率。

扩展1:

  • 在传统的计算机系统中,数据的传输通常需要通过CPU来完成。

扩展2:

  • DMA技术可以应用于多种外设。例如:硬盘、音频设备等,当外设需要进行数据传输时,DMA控制器会向CPU发送请求,CPU将数据传输的任务交给DMA控制器处理。DMA控制器会直接读取或写入数据而不需要CPU干预。数据传输完成后,DMA控制器会向CPU发送中断信号,通知操作完成。

-------------------------------------------------------------------------------------------------------------------------

硬中断:

  • 向CPU发起硬中断的作用是通知CPU发生了一个重要的事件或需要CPU的处理。硬中断时由硬件设备触发的中断信号,用于引起CPU的注意并中断当前正在执行的程序。
  • 当硬件设备需要CPU的处理时,它会向CPU发送一个中断请求。CPU会暂停当前正在执行的程序,并跳转至一个预定义的中断处理程序来处理中断。中断处理程序是操作系统或设备驱动程序提供的,用于对中断事件进行处理。

-------------------------------------------------------------------------------------------------------------------------

屏蔽硬中断:

  • 处理器屏蔽硬中断是为了确保关键代码的执行不会被中断打断。

-------------------------------------------------------------------------------------------------------------------------

软中断:

  • CPU发起软中断的作用是触发操作系统中的中断处理程序,从而实现与操作系统进行交互和执行特定的系统功能。

-------------------------------------------------------------------------------------------------------------------------

总述:

        网卡是计算机里的一个硬件,专门负责接收和发送网络包,当网卡接收到一个网络包后,会通过DNA技术将网络包写入到指定的内存地址,也就是写入到RingBuffer中,这是一个环形缓冲区。接着网卡向CPU发起硬中断,当CPU收到硬件中断请求后,根据中断注册表,调用已经注册的中断处理函数(中断处理函数:暂时屏蔽硬中断、发起软中断-->通知内核里的ksoftirqd线程进行轮询从ringbuffer中读数据、恢复中断)。ksoftirqd线程从ringbuffer中获取一个数据帧之后保存在sk_buffer中,并且交给网络协议栈进行数据帧处理,经过协议栈处理完后的数据被放到socket读缓冲区中,等待应用程序通过系统调用读取数据。


发送数据

        这个过程没有好说的,跟接收数据的返过程。需要注意的是TCP保证可靠交付,需要超时重传,需要备份。UDP是不需要的,不需要备份。

用户层角度对数据包的处理流程

用户态网络缓冲区

为什么需要用户态网络缓冲区?(为什么需要为每条连接准备一个发送缓冲区和一个接收缓冲区?)

  • 从业务层面来说,我们可以看到read和处理数据包,组成数据包和write写入之间存在处理速度上的差异,也就是说当需要产生数据的能力比处理数据的能力强时,防止数据丢失,需要将数据未处理的数据进行短暂的存储。(生产者的速度大于消费者的速度是)
  • 从posix api接口(read、write)层面(粘包),不能确定一次性接收数据,也不能保证一次性发送数据。

        由于存在这样的问题,我们需要设计一个缓冲区解决这样的问题...

不同的网络模型会不会影响用户态网络缓冲区的设计?

        显然不会,上面两个矛盾发生在读取数据之后和处理数据之间,而不同的网络模型是对IO的不同处理方式。两个过程不是在同一阶段。换句话说就是一个是作用于读数据的方式不同,一个是作用于处理数据和读取快慢的问题上,而不是读取方式的问题上。

UDP和TCP协议是否影响用户态网络缓冲区的设计?

        显然不会,很明显可以看出,UDP和TCP已经完成了将数据包写入socket缓冲区,而上面两个矛盾发生在处理数据和读写数据之间。两个不在通过阶段。换句话说就是UDP和TCP协议都存在上面的两个矛盾。

用户态网络缓冲区的设计 

   这里的代码不要求完全自己实现,能看懂就行,知道其原理和缺点和优点就可以了。

  • 定长buffer:

        就是一个固定长度的数组,然后增加一个记录当前空闲的起始位置。

        优点:

        结构简单,易于实现。

        缺点:

        需要频繁的腾挪数据。

        需要实现扩缩容机制。

  • ringBuffer:

        就是一个环形队列,我们可以做到更好一点。就是采用2的能次幂大小的队列,这样可以把取余操作转成位操作,提高处理速度。

        具体的情况这里就不做分析了,为了方便大家理解代码,我把各种会出现的情况图罗列出来,不明白的话,评论区见。

        优点:

        不需要频繁的腾挪数据。

        缺点:

        需要实现扩缩容机制,扩缩容时需要腾挪数据。

        造成不连续的空间,可能引发多次系统调用。

  • chainBuffer:

        就是通过队列的将一块块空间连在一起,说白了就是把数组的连续空间变成了链表的方式,然后记录第一个块位置和最后一个块的位置,错觉上看起来像个连续的空间。

        优点:
        不腾挪数据
        动态扩缩容,不腾挪数据

        缺点:

        造成不连续的空间,可能引发多次系统调用

RingBuffer代码 

#ifndef _ringbuffer_h
#define _ringbuffer_h

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// #include <limits.h>  // for uint_max
#include <stdint.h>
#include <unistd.h>

typedef struct ringbuffer_s buffer_t;

buffer_t * buffer_new(uint32_t sz);

uint32_t buffer_len(buffer_t *r);


void buffer_free(buffer_t *r);

int buffer_add(buffer_t *r, const void *data, uint32_t sz);

int buffer_remove(buffer_t *r, void *data, uint32_t sz);

int buffer_drain(buffer_t *r, uint32_t sz);

int buffer_search(buffer_t *r, const char* sep, const int seplen);

uint8_t * buffer_write_atmost(buffer_t *r);

#endif
#include <stdbool.h>
#include <assert.h>
#include <stdint.h>
#include <stdatomic.h>
#include "buffer.h"

struct ringbuffer_s {
    uint32_t size;
    uint32_t tail;
    uint32_t head;
    uint8_t * buf;
};

#define min(lth, rth) ((lth)<(rth)?(lth):(rth))

static inline int is_power_of_two(uint32_t num) {
    if (num < 2) return 0;
    return (num & (num - 1)) == 0;
}

static inline uint32_t roundup_power_of_two(uint32_t num) {
    if (num == 0) return 2;
    int i = 0;
    for (; num != 0; i++)
        num >>= 1;
    return 1U << i;
}

buffer_t * buffer_new(uint32_t sz) {
    if (!is_power_of_two(sz)) sz = roundup_power_of_two(sz);
    buffer_t * buf = (buffer_t *)malloc(sizeof(buffer_t) + sz);
    if (!buf) {
        return NULL;
    }
    buf->size = sz;
    buf->head = buf->tail = 0;
    buf->buf = (uint8_t *)(buf + 1);
    return buf;
}

void buffer_free(buffer_t *r) {
    free(r);
    r = NULL;
}

static uint32_t
rb_isempty(buffer_t *r) {
    return r->head == r->tail;
}

static uint32_t
rb_isfull(buffer_t *r) {
    return r->size == (r->tail - r->head);
}

static uint32_t
rb_len(buffer_t *r) {
    return r->tail - r->head;
}

static uint32_t
rb_remain(buffer_t *r) {
    return r->size - r->tail + r->head;
}

int buffer_add(buffer_t *r, const void *data, uint32_t sz) {
    if (sz > rb_remain(r)) {
        return -1;
    }

    uint32_t i;
    i = min(sz, r->size - (r->tail & (r->size - 1)));

    memcpy(r->buf + (r->tail & (r->size - 1)), data, i);
    memcpy(r->buf, data+i, sz-i);

    r->tail += sz;
    return 0;
}

int buffer_remove(buffer_t *r, void *data, uint32_t sz) {
    assert(!rb_isempty(r));
    uint32_t i;
    sz = min(sz, r->tail - r->head);

    i = min(sz, r->size - (r->head & (r->size - 1)));
    memcpy(data, r->buf+(r->head & (r->size - 1)), i);
    memcpy(data+i, r->buf, sz-i);

    r->head += sz;
    return sz;
}

int buffer_drain(buffer_t *r, uint32_t sz) {
    if (sz > rb_len(r))
        sz = rb_len(r);
    r->head += sz;
    return sz;
}

// 找 buffer 中 是否包含特殊字符串(界定数据包的)
int buffer_search(buffer_t *r, const char* sep, const int seplen) {
    int i;
    for (i = 0; i <= rb_len(r)-seplen; i++) {
        int pos = (r->head + i) & (r->size - 1);
        if (pos + seplen > r->size) {
            if (memcmp(r->buf+pos, sep, r->size-pos))
                return 0;
            if (memcmp(r->buf, sep+r->size-pos, pos+seplen-r->size) == 0) {
                return i+seplen;
            }
        }
        if (memcmp(r->buf+pos, sep, seplen) == 0) {
            return i+seplen;
        }
    }
    return 0;
}

uint32_t buffer_len(buffer_t *r) {
    return rb_len(r);
}

uint8_t * buffer_write_atmost(buffer_t *r) {
    uint32_t rpos = r->head & (r->size - 1);
    uint32_t wpos = r->tail & (r->size - 1);
    if (wpos < rpos) {
        uint8_t* temp = (uint8_t *)malloc(r->size * sizeof(uint8_t));
        memcpy(temp, r->buf+rpos, r->size - rpos);
        memcpy(temp+r->size-rpos, r->buf, wpos);
        free(r->buf);
        r->buf = temp;
        return r->buf;
    }
    return r->buf + rpos;
}

chainBuffer代码 

#include <string.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include "buffer.h"


struct buf_chain_s {
    struct buf_chain_s *next;
    uint32_t buffer_len;
    uint32_t misalign;
    uint32_t off;
    uint8_t *buffer;
};

struct buffer_s {
    buf_chain_t *first;
    buf_chain_t *last;
    buf_chain_t **last_with_datap;
    uint32_t total_len;
    uint32_t last_read_pos; // for sep read
};

#define CHAIN_SPACE_LEN(ch) ((ch)->buffer_len - ((ch)->misalign + (ch)->off))
#define MIN_BUFFER_SIZE 1024
#define MAX_TO_COPY_IN_EXPAND 4096
#define BUFFER_CHAIN_MAX_AUTO_SIZE 4096
#define MAX_TO_REALIGN_IN_EXPAND 2048
#define BUFFER_CHAIN_MAX 16*1024*1024  // 16M
#define BUFFER_CHAIN_EXTRA(t, c) (t *)((buf_chain_t *)(c) + 1)
#define BUFFER_CHAIN_SIZE sizeof(buf_chain_t)

uint32_t
buffer_len(buffer_t *buf) {
    return buf->total_len;
}

buffer_t *
buffer_new(uint32_t sz) {
    (void)sz;
    buffer_t * buf = (buffer_t *) malloc(sizeof(buffer_t));
    if (!buf) {
        return NULL;
    }
    memset(buf, 0, sizeof(*buf));
    buf->last_with_datap = &buf->first;
    return buf;
}

static buf_chain_t *
buf_chain_new(uint32_t size) {
    buf_chain_t *chain;
    uint32_t to_alloc;
    if (size > BUFFER_CHAIN_MAX - BUFFER_CHAIN_SIZE)
        return (NULL);
    size += BUFFER_CHAIN_SIZE;
    
    if (size < BUFFER_CHAIN_MAX / 2) {
        to_alloc = MIN_BUFFER_SIZE;
        while (to_alloc < size) {
            to_alloc <<= 1;
        }
    } else {
        to_alloc = size;
    }
    if ((chain = malloc(to_alloc)) == NULL)
        return (NULL);
    memset(chain, 0, BUFFER_CHAIN_SIZE);
    chain->buffer_len = to_alloc - BUFFER_CHAIN_SIZE;
    chain->buffer = BUFFER_CHAIN_EXTRA(uint8_t, chain);
    return (chain);
}

static void 
buf_chain_free_all(buf_chain_t *chain) {
    buf_chain_t *next;
    for (; chain; chain = next) {
        next = chain->next;
        free(chain);
    }
}

void
buffer_free(buffer_t *buf) {
    buf_chain_free_all(buf->first);
}


static buf_chain_t **
free_empty_chains(buffer_t *buf) {
    buf_chain_t **ch = buf->last_with_datap;
    while ((*ch) && (*ch)->off != 0)
        ch = &(*ch)->next;
    if (*ch) {
        buf_chain_free_all(*ch);
        *ch = NULL;
    }
    return ch;
}

static void
buf_chain_insert(buffer_t *buf, buf_chain_t *chain) {
    if (*buf->last_with_datap == NULL) {
        buf->first = buf->last = chain;
    } else {
        buf_chain_t **chp;
        chp = free_empty_chains(buf);
        *chp = chain;
        if (chain->off)
            buf->last_with_datap = chp;
        buf->last = chain;
    }
    buf->total_len += chain->off;
}

static inline buf_chain_t *
buf_chain_insert_new(buffer_t *buf, uint32_t datlen) {
    buf_chain_t *chain;
    if ((chain = buf_chain_new(datlen)) == NULL)
        return NULL;
    buf_chain_insert(buf, chain);
    return chain;
}

static int
buf_chain_should_realign(buf_chain_t *chain, uint32_t datlen)
{
    return chain->buffer_len - chain->off >= datlen &&
        (chain->off < chain->buffer_len / 2) &&
        (chain->off <= MAX_TO_REALIGN_IN_EXPAND);
}

static void
buf_chain_align(buf_chain_t *chain) {
    memmove(chain->buffer, chain->buffer + chain->misalign, chain->off);
    chain->misalign = 0;
}

int buffer_add(buffer_t *buf, const void *data_in, uint32_t datlen) {
    buf_chain_t *chain, *tmp;
    const uint8_t *data = data_in;
    uint32_t remain, to_alloc;
    int result = -1;
    if (datlen > BUFFER_CHAIN_MAX - buf->total_len) {
        goto done;
    }

    if (*buf->last_with_datap == NULL) {
        chain = buf->last;
    } else {
        chain = *buf->last_with_datap;
    }

    if (chain == NULL) {
        chain = buf_chain_insert_new(buf, datlen);
        if (!chain)
            goto done;
    }

    remain = chain->buffer_len - chain->misalign - chain->off;
    if (remain >= datlen) {
        memcpy(chain->buffer + chain->misalign + chain->off, data, datlen);
        chain->off += datlen;
        buf->total_len += datlen;
        // buf->n_add_for_cb += datlen;
        goto out;
    } else if (buf_chain_should_realign(chain, datlen)) {
        buf_chain_align(chain);

        memcpy(chain->buffer + chain->off, data, datlen);
        chain->off += datlen;
        buf->total_len += datlen;
        // buf->n_add_for_cb += datlen;
        goto out;
    }
    to_alloc = chain->buffer_len;
    if (to_alloc <= BUFFER_CHAIN_MAX_AUTO_SIZE/2)
        to_alloc <<= 1;
    if (datlen > to_alloc)
        to_alloc = datlen;
    tmp = buf_chain_new(to_alloc);
    if (tmp == NULL)
        goto done;
    if (remain) {
        memcpy(chain->buffer + chain->misalign + chain->off, data, remain);
        chain->off += remain;
        buf->total_len += remain;
        // buf->n_add_for_cb += remain;
    }

    data += remain;
    datlen -= remain;

    memcpy(tmp->buffer, data, datlen);
    tmp->off = datlen;
    buf_chain_insert(buf, tmp);
    // buf->n_add_for_cb += datlen;
out:
    result = 0;
done:
    return result;
}

static uint32_t
buf_copyout(buffer_t *buf, void *data_out, uint32_t datlen) {
    buf_chain_t *chain;
    char *data = data_out;
    uint32_t nread;
    chain = buf->first;
    if (datlen > buf->total_len)
        datlen = buf->total_len;
    if (datlen == 0)
        return 0;
    nread = datlen;

    while (datlen && datlen >= chain->off) {
        uint32_t copylen = chain->off;
        memcpy(data,
            chain->buffer + chain->misalign,
            copylen);
        data += copylen;
        datlen -= copylen;

        chain = chain->next;
    }
    if (datlen) {
        memcpy(data, chain->buffer + chain->misalign, datlen);
    }

    return nread;
}

static inline void
ZERO_CHAIN(buffer_t *dst) {
    dst->first = NULL;
    dst->last = NULL;
    dst->last_with_datap = &(dst)->first;
    dst->total_len = 0;
}

int buffer_drain(buffer_t *buf, uint32_t len) {
    buf_chain_t *chain, *next;
    uint32_t remaining, old_len;
    old_len = buf->total_len;
    if (old_len == 0)
        return 0;

    if (len >= old_len) {
        len = old_len;
        for (chain = buf->first; chain != NULL; chain = next) {
            next = chain->next;
            free(chain);
        }
        ZERO_CHAIN(buf);
    } else {
        buf->total_len -= len;
        remaining = len;
        for (chain = buf->first; remaining >= chain->off; chain = next) {
            next = chain->next;
            remaining -= chain->off;

            if (chain == *buf->last_with_datap) {
                buf->last_with_datap = &buf->first;
            }
            if (&chain->next == buf->last_with_datap)
                buf->last_with_datap = &buf->first;

            free(chain);
        }

        buf->first = chain;
        chain->misalign += remaining;
        chain->off -= remaining;
    }
    
    // buf->n_del_for_cb += len;
    return len;
}

int buffer_remove(buffer_t *buf, void *data_out, uint32_t datlen) {
    uint32_t n = buf_copyout(buf, data_out, datlen);
    if (n > 0) {
        if (buffer_drain(buf, n) < 0)
            n = -1;
    }
    return (int)n;
}

static bool
check_sep(buf_chain_t * chain, int from, const char *sep, int seplen) {
    for (;;) {
        int sz = chain->off - from;
        if (sz >= seplen) {
            return memcmp(chain->buffer + chain->misalign + from, sep, seplen) == 0;
        }
        if (sz > 0) {
            if (memcmp(chain->buffer + chain->misalign + from, sep, sz)) {
                return false;
            }
        }
        chain = chain->next;
        sep += sz;
        seplen -= sz;
        from = 0;
    }
}

int buffer_search(buffer_t *buf, const char* sep, const int seplen) {
    buf_chain_t *chain;
    int i;
    chain = buf->first;
    if (chain == NULL)
        return 0;
    int bytes = chain->off;
    while (bytes <= buf->last_read_pos) {
        chain = chain->next;
        if (chain == NULL)
            return 0;
        bytes += chain->off;
    }
    bytes -= buf->last_read_pos;
    int from = chain->off - bytes;
    for (i = buf->last_read_pos; i <= buf->total_len - seplen; i++) {
        if (check_sep(chain, from, sep, seplen)) {
            buf->last_read_pos = 0;
            return i+seplen;
        }
        ++from;
        --bytes;
        if (bytes == 0) {
            chain = chain->next;
            from = 0;
            if (chain == NULL)
                break;
            bytes = chain->off;
        }
    }
    buf->last_read_pos = i;
    return 0;
}

uint8_t * buffer_write_atmost(buffer_t *p) {
    buf_chain_t *chain, *next, *tmp, *last_with_data;
    uint8_t *buffer;
    uint32_t remaining;
    int removed_last_with_data = 0;
    int removed_last_with_datap = 0;

    chain = p->first;
    uint32_t size = p->total_len;

    if (chain->off >= size) {
        return chain->buffer + chain->misalign;
    }

    remaining = size - chain->off;
    for (tmp=chain->next; tmp; tmp=tmp->next) {
        if (tmp->off >= (size_t)remaining)
            break;
        remaining -= tmp->off;
    }
    if (chain->buffer_len - chain->misalign >= (size_t)size) {
        /* already have enough space in the first chain */
        size_t old_off = chain->off;
        buffer = chain->buffer + chain->misalign + chain->off;
        tmp = chain;
        tmp->off = size;
        size -= old_off;
        chain = chain->next;
    } else {
        if ((tmp = buf_chain_new(size)) == NULL) {
            return NULL;
        }
        buffer = tmp->buffer;
        tmp->off = size;
        p->first = tmp;
    }

    last_with_data = *p->last_with_datap;
    for (; chain != NULL && (size_t)size >= chain->off; chain = next) {
        next = chain->next;

        if (chain->buffer) {
            memcpy(buffer, chain->buffer + chain->misalign, chain->off);
            size -= chain->off;
            buffer += chain->off;
        }
        if (chain == last_with_data)
            removed_last_with_data = 1;
        if (&chain->next == p->last_with_datap)
            removed_last_with_datap = 1;

        free(chain);
    }

    if (chain != NULL) {
        memcpy(buffer, chain->buffer + chain->misalign, size);
        chain->misalign += size;
        chain->off -= size;
    } else {
        p->last = tmp;
    }

    tmp->next = chain;

    if (removed_last_with_data) {
        p->last_with_datap = &p->first;
    } else if (removed_last_with_datap) {
        if (p->first->next && p->first->next->off)
            p->last_with_datap = &p->first->next;
        else
            p->last_with_datap = &p->first;
    }
    return tmp->buffer + tmp->misalign;
}

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

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

相关文章

【博士每天一篇文献-算法】Learning without forgetting

阅读时间&#xff1a;2023-10-29 1 介绍 年份&#xff1a;2016 作者&#xff1a;李志忠; 德里克霍伊姆&#xff0c;伊利诺伊大学 期刊&#xff1a;IEEE transactions on pattern analysis and machine intelligence 引用量&#xff1a;3353 提出一种名为"无忘记学习&quo…

Linux下的环境变量【详解】

Linux下的环境变量 一&#xff0c;环境变量的概念1 概述2 环境变量的分类3 常见的环境变量4 查看环境变量4.1 shell变量4.2 查看环境变量 5 添加和删除环境变量5.1 添加环境变量5.2 删除环境变量 6. 通过代码如何获取环境变量6.1 命令行的第三个参数6.2 通过第三方变量environ获…

手把手教你如何扩展(破解)mybatisplus的sql生成 | 京东云技术团队

mybatisplus 的常用CRUD方法 众所周知&#xff0c;mybatisplus提供了强大的代码生成能力&#xff0c;他默认生成的常用的CRUD方法&#xff08;例如插入、更新、删除、查询等&#xff09;的定义&#xff0c;能够帮助我们节省很多体力劳动。 他的BaseMapper中定义了这些常用的C…

3线硬件SPI+DMA驱动 HX8347 TFT屏

3线硬件SPIDMA驱动 HX8347 TFT屏&#xff0c;实现用DMA清屏。 参考&#xff1a;基于stm32 标准库spi驱动st7789彩屏TFT(使用DMA)-技术天地-深圳市修德电子有限公司 一、源码 HX8347.h #ifndef USER_HX8347_H_ #define USER_HX8347_H_#define SPI_hardware #define SPI_hardw…

详解JS的四种异步解决方案:回调函数、Promise、Generator、async/await

同步&异步的概念 在讲这四种异步方案之前&#xff0c;我们先来明确一下同步和异步的概念&#xff1a; 所谓同步(synchronization)&#xff0c;简单来说&#xff0c;就是顺序执行&#xff0c;指的是同一时间只能做一件事情&#xff0c;只有目前正在执行的事情做完之后&am…

AI:80-基于深度学习的医学图像分割与病变识别

🚀 本文选自专栏:人工智能领域200例教程专栏 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的代码,详细讲解供大家学习,希望可以帮到大家。欢迎订阅支持,正在不断更新中,…

强大好用的shell:shell的工作原理是什么

Shell的工作原理可以简要概括为以下几个步骤&#xff1a; 1.命令行输入&#xff1a;用户在命令行界面输入命令。 2.命令解析&#xff1a;Shell接收用户的输入&#xff0c;并对命令进行解析。这个过程包括解析命令名、参数、选项等&#xff0c;将其转换成计算机可以理解的形式。…

【正点原子STM32连载】 第四十九章 SD卡实验 摘自【正点原子】APM32F407最小系统板使用指南

1&#xff09;实验平台&#xff1a;正点原子stm32f103战舰开发板V4 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id609294757420 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html## 第四…

Power Automate-创建和运行

网站&#xff1a;Microsoft Power Automate 根据自己需求选择创建 选择需要的触发方式 点击添加新步骤 可以选择多种微软应用或者自定义应用连接 此处以向SharePoint列表追加项为例&#xff0c;要提前创建好SharePoint列表&#xff0c;并写好表结构 搜索SharePoint&#xff0…

Docsify 顶部的导航是如何配置

如下图&#xff0c;我们在 Docsify 的文档中配置了一个顶部导航。 下面的步骤对顶部导航的配置进行简要介绍。 配置 有 2 个地方需要这个地方进行配置。 首先需要在 index.html 文件中的 loadNavbar: true, 配置上。 然后再在项目中添加一个 _navbar.md 文件。 在这个文件中…

Linux系统编程——文件的写入及读取

写入(write) 使用write函数需要包含以下头文件&#xff1a; #include <unistd.h> write的函数定义格式 ssize_t write(int fd, const void *buf, size_t count); 附加&#xff1a;一般将数据写入文件中后需关闭文件&#xff0c;这里需要调用关闭(close)函数&#xf…

Kibana使用Watcher监控服务日志并发送飞书报警(Markdown)

Watcher是什么 Kibana Watcher 是 Elasticsearch 的监控和告警工具&#xff0c;它允许你设置和管理告警规则以监控 Elasticsearch 数据和集群的状态。Kibana Watcher 可以监测各种指标和数据&#xff0c;然后在满足特定条件时触发警报。它提供了一种强大的方式来实时监控 Elas…

频谱分析仪 如何选择 TFN RMT系列给您答案

TFN RMT手持式频谱分析仪是TFN新推出的一款高性能、全功能版测试仪&#xff0c;集高性能信号分析模块、多制式解析算法软件于一体的手持式测试仪表&#xff0c;满足军工、高校科研、通信运营商、电力、铁路等对移动通信的测试、无线排查干扰等应用而全新推出的平台&#xff0c;…

地区 IP 库

地区 & IP 库 yudao-spring-boot-starter-biz-ip (opens new window)业务组件&#xff0c;提供地区 & IP 库的封装。 #1. 地区 AreaUtils (opens new window)是地区工具类&#xff0c;可以查询中国的省、市、区县&#xff0c;也可以查询国外的国家。 它的数据来自 …

电脑恢复出厂设置在哪里?我来告诉你

在电脑使用中&#xff0c;有时候出现了一些问题&#xff0c;比如系统崩溃、性能下降、病毒感染或者想要将电脑彻底清空以出售或赠予他人。这时&#xff0c;恢复出厂设置成为一个有效的解决方案。可是恢复出厂设置在哪里呢&#xff1f;本文将介绍三种不同的方法来恢复电脑出厂设…

FCOS难点记录

FCOS 中有计算 特征图&#xff08;Feature map中的每个特征点到gt_box的左、上、右、下的距离&#xff09; 1、特征点到gt_box框的 左、上、右、下距离计算 x coords[:, 0] # h*w&#xff0c;2 即 第一列y coords[:, 1] l_off x[None, :, None] - gt_boxes[..., 0][:, No…

禅道研发项⽬管理系统未授权RCE漏洞复现

1、产品介绍 Zendao禅道是第一款国产的开源项目管理软件&#xff0c;他的核心管理思想基于敏捷方法scrum&#xff0c;内置了产品管理和项目管理&#xff0c;同时又根据国内研发现状补充了测试管理、计划管理、发布管理、文档管理、事务管理等功能。 2、漏洞描述 该系统在202…

opencv创建图片,绘制图片,画框,划线,改变像素点颜色

文章目录 创建空白图片创建一张渐变色彩色绘制多边形绘制多线改变像素点颜色 创建空白图片 bool tool_class::creatEmpty(int width, int height, std::string image_p) {// 创建一个空白图像cv::Mat blankImage(height, width, CV_8UC3, cv::Scalar(255, 255, 255));// 保存图…

html与django实现多级数据联动

html与django实现多级数据联动 1、流程 1、进入页面后先获取年级数据 2、选择年级后获取院级数据 3、选择院级后获取层次数据 4、选择层次数据后获取专业数据 2、html代码 <p style"margin-top: 10px;"><label>年级</label><select id"…

MCU平台使用SPI-DirectC实现FPGA在线升级

本文介绍在MCU平台上使用SPI-DirectC实现FPGA的在线升级功能。 对于使用Microchip FPGA若想使用离线方式对FPGA进行Bitstream的烧写,就不得不使用官方提供的DirectC组件(开源,包含JTAG-DirectC和SPI-DirectC),本文是在MCU(32bit)上实现的,采用的是SPI-DirectC组件。 …