关于RDMA传输的基本流量控制

news2024/11/16 17:28:49

Basic flow control for RDMA transfers | The Geek in the Corner (wordpress.com)

文心一言

已经介绍了使用发送/接收操作和RDMA读写操作,那么现在是一个很好的机会来结合这两种方法的元素,并讨论一般的流量控制。还会稍微谈谈RDMA带有立即数据的写操作(IBV_WR_RDMA_WRITE_WITH_IMM),并且将通过一个示例来说明这些方法,该示例使用RDMA传输命令行中指定的文件。

示例包括一个服务器和一个客户端。服务器等待来自客户端的连接。客户端在连接到服务器后主要执行两个操作:它发送要传输的文件名,然后发送文件的内容。我们不会关注建立连接的细节;这些在之前的帖子中已经讨论过了。相反,我们将专注于同步和流量控制。不过,在本文中的代码结构上做了一些调整,与之前关于RDMA读写的帖子中的结构相反——在那里,将连接管理代码分别放在client.c和server.c中,而将完成处理代码放在common.c中。而在这里,将连接管理代码集中放在common.c中,并将完成处理代码分别放在client.c和server.c中。

回到示例。有许多方法可以从客户端向服务器传输整个文件。例如:

  1. 将整个文件加载到客户端内存中,连接到服务器,等待服务器发布一系列接收操作,然后在客户端端发起一个发送操作(send)以将内容复制到服务器。
  2. 将整个文件加载到客户端内存中,注册内存,将区域详细信息传递给服务器,让服务器发起RDMA读取操作将整个文件复制到其内存中,然后将内容写入磁盘。
  3. 与上述相同,但发起RDMA写入操作以将文件内容复制到服务器内存中,然后通知它写入磁盘。
  4. 在客户端打开文件,读取一个块,等待服务器发布接收操作,然后在客户端端发布一个发送操作,并循环直到整个文件被发送。
  5. 与上述相同,但使用RDMA读取操作。
  6. 与上述相同,但使用RDMA写入操作。

将整个文件加载到内存中对于大文件来说可能不切实际,因此将跳过前三个选项。在剩下的三个选项中,将专注于使用RDMA写入操作,以便可以说明RDMA带有立即数据的写入操作的使用,这是一直想讨论的一个话题。这种操作类似于常规的RDMA写入,但发起者可以将32位值“附加”到写入操作上。与常规RDMA写入不同,RDMA带有立即数据的写入要求在目标的接收队列上发布一个接收操作。当从目标的队列中拉取完成时,该32位值将可用。

12月26日:Roland D. 相当热心地指出,iWARP适配器不支持RDMA带有立即数据的写入。我们可以重写代码以使用RDMA写入(不带立即数据)后跟一个发送操作,但这留作读者的练习。

既然我们已经决定要将文件拆分成块,并将这些块一次一个地写入服务器的内存,我们需要找到一种方法来确保我们不会比服务器能够处理的速度更快地写入块。我们将通过服务器在准备好接收数据时向客户端发送显式消息来实现这一点。另一方面,客户端将使用带有立即数据的写入来向服务器发送信号。这个过程的大致顺序如下:

  1. 服务器开始监听连接。
  2. 客户端发布一个用于流量控制消息的接收操作,并启动到服务器的连接。
  3. 服务器发布一个用于RDMA带有立即数据的写入的接收操作,并接受来自客户端的连接。
  4. 服务器向客户端发送其目标内存区域的详细信息。
  5. 客户端重新发布一个接收操作,然后通过将文件名写入服务器的内存区域来响应。立即数据字段包含文件名的长度。
  6. 服务器打开一个文件描述符,重新发布一个接收操作,然后发送一个消息,指示它已准备好接收数据。
  7. 客户端重新发布一个接收操作,从输入文件中读取一个数据块,然后将该数据块写入服务器的内存区域。立即数据字段包含该数据块的大小(以字节为单位)。
  8. 服务器将数据块写入磁盘,重新发布一个接收操作,然后发送一个消息,指示它已准备好接收数据。
  9. 重复步骤7和8,直到没有数据要发送。
  10. 客户端重新发布一个接收操作,然后向服务器的内存发起一个零字节的写入操作。立即数据字段设置为零。
  11. 服务器发送一个消息,指示已完成操作。
  12. 客户端关闭连接。
  13. 服务器关闭文件描述符。

一个图表可能会有所帮助:

查看这个序列,我们可以看到服务器只向客户端发送小消息,并且只从客户端接收RDMA写入操作。客户端只执行RDMA写入操作,并且只从服务器接收小消息。

让我们从服务器开始看起。建立连接的细节现在隐藏在rc_init()函数之后,该函数设置了各种回调函数,以及rc_server_loop()函数,它运行一个事件循环:

int main(int argc, char **argv)
{
  rc_init(
    on_pre_conn,
    on_connection,
    on_completion,
    on_disconnect);
 
  printf("waiting for connections. interrupt (^C) to exit.\n");
 
  rc_server_loop(DEFAULT_PORT);
 
  return 0;
}

回调函数的名称相当直观:on_pre_conn()在接收到连接请求但尚未接受连接时被调用,on_connection()在建立连接时被调用,on_completion()在从完成队列中拉取条目时被调用,而on_disconnect()在断开连接时被调用。

on_pre_conn()中,我们分配一个结构体来包含各种连接上下文字段(一个缓冲区来包含来自客户端的数据,一个缓冲区用于向客户端发送消息等),并发布一个接收工作请求以接收客户端的RDMA写入操作:

static void post_receive(struct rdma_cm_id *id)
{
  struct ibv_recv_wr wr, *bad_wr = NULL;
 
  memset(&wr, 0, sizeof(wr));
 
  wr.wr_id = (uintptr_t)id;
  wr.sg_list = NULL;
  wr.num_sge = 0;
 
  TEST_NZ(ibv_post_recv(id->qp, &wr, &bad_wr));
}

这里有趣的是我们设置了sg_list = NULLnum_sge = 0。传入的RDMA写请求将指定一个目标内存地址,由于这个工作请求只与传入的RDMA写请求匹配,所以我们不需要使用sg_listnum_sge来指定接收的内存位置。在连接建立后,on_connection()将内存区域的详细信息发送给客户端:

static void on_connection(struct rdma_cm_id *id)
{
  struct conn_context *ctx = (struct conn_context *)id->context;
 
  ctx->msg->id = MSG_MR;
  ctx->msg->data.mr.addr = (uintptr_t)ctx->buffer_mr->addr;
  ctx->msg->data.mr.rkey = ctx->buffer_mr->rkey;
 
  send_message(id);
}

这促使客户端开始发出RDMA写入操作,这会触发on_completion()回调函数:

static void on_completion(struct ibv_wc *wc)
{
  struct rdma_cm_id *id = (struct rdma_cm_id *)(uintptr_t)wc->wr_id;
  struct conn_context *ctx = (struct conn_context *)id->context;
 
  if (wc->opcode == IBV_WC_RECV_RDMA_WITH_IMM) {
    uint32_t size = ntohl(wc->imm_data);
 
    if (size == 0) {
      ctx->msg->id = MSG_DONE;
      send_message(id);
 
      // don't need post_receive() since we're done with this connection
 
    } else if (ctx->file_name[0]) {
      ssize_t ret;
 
      printf("received %i bytes.\n", size);
 
      ret = write(ctx->fd, ctx->buffer, size);
 
      if (ret != size)
        rc_die("write() failed");
 
      post_receive(id);
 
      ctx->msg->id = MSG_READY;
      send_message(id);
 
    } else {
      memcpy(ctx->file_name, ctx->buffer, (size > MAX_FILE_NAME) ? MAX_FILE_NAME : size);
      ctx->file_name[size - 1] = '\0';
 
      printf("opening file %s\n", ctx->file_name);
 
      ctx->fd = open(ctx->file_name, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
 
      if (ctx->fd == -1)
        rc_die("open() failed");
 
      post_receive(id);
 
      ctx->msg->id = MSG_READY;
      send_message(id);
    }
  }
}

我们在第7行检索即时数据字段,并将其从网络字节顺序转换为主机字节顺序。然后我们测试三种可能的情况:

如果size == 0,则表示客户端已完成数据写入(第9-14行)。我们用MSG_DONE来确认这一点。

如果ctx->file_name的第一个字节被设置,则表示我们已经有了文件名并且有一个打开的文件描述符(第15-30行)。我们调用write()将客户端的数据追加到我们打开的文件中,然后用MSG_READY回复,表示我们已准备好接收更多数据。

否则,我们尚未收到文件名(第30-45行)。我们从输入缓冲区中复制它,打开一个文件描述符,然后用MSG_READY回复,表示我们已准备好接收数据。

在断开连接时,在on_disconnect()中,我们关闭打开的文件描述符并整理内存注册等。服务器的操作就是这样!

在客户端,main()函数稍微复杂一些,因为我们需要将服务器主机名和端口传递给rc_client_loop()

int main(int argc, char **argv)
{
  struct client_context ctx;
 
  if (argc != 3) {
    fprintf(stderr, "usage: %s <server-address> <file-name>\n", argv[0]);
    return 1;
  }
 
  ctx.file_name = basename(argv[2]);
  ctx.fd = open(argv[2], O_RDONLY);
 
  if (ctx.fd == -1) {
    fprintf(stderr, "unable to open input file \"%s\"\n", ctx.file_name);
    return 1;
  }
 
  rc_init(
    on_pre_conn,
    NULL, // on connect
    on_completion,
    NULL); // on disconnect
 
  rc_client_loop(argv[1], DEFAULT_PORT, &ctx);
 
  close(ctx.fd);
 
  return 0;
}

我们不为连接或断开连接提供回调函数,因为这些事件对客户端来说不是特别相关。on_pre_conn()回调函数与服务器端的相当类似,除了连接上下文结构是预先分配的之外,我们发布的接收工作请求(在post_receive()中)需要一个内存区域:

static void post_receive(struct rdma_cm_id *id)
{
  struct client_context *ctx = (struct client_context *)id->context;
 
  struct ibv_recv_wr wr, *bad_wr = NULL;
  struct ibv_sge sge;
 
  memset(&wr, 0, sizeof(wr));
 
  wr.wr_id = (uintptr_t)id;
  wr.sg_list = &sge;
  wr.num_sge = 1;
 
  sge.addr = (uintptr_t)ctx->msg;
  sge.length = sizeof(*ctx->msg);
  sge.lkey = ctx->msg_mr->lkey;
 
  TEST_NZ(ibv_post_recv(id->qp, &wr, &bad_wr));
}

我们将sg_list指向一个足够大的缓冲区,以容纳一个message结构体。服务器将使用这个缓冲区来传递流控制消息。每个消息都会触发对on_completion()的调用,这是客户端执行大部分工作的地方:

static void on_completion(struct ibv_wc *wc)
{
  struct rdma_cm_id *id = (struct rdma_cm_id *)(uintptr_t)(wc->wr_id);
  struct client_context *ctx = (struct client_context *)id->context;
 
  if (wc->opcode & IBV_WC_RECV) {
    if (ctx->msg->id == MSG_MR) {
      ctx->peer_addr = ctx->msg->data.mr.addr;
      ctx->peer_rkey = ctx->msg->data.mr.rkey;
 
      printf("received MR, sending file name\n");
      send_file_name(id);
    } else if (ctx->msg->id == MSG_READY) {
      printf("received READY, sending chunk\n");
      send_next_chunk(id);
    } else if (ctx->msg->id == MSG_DONE) {
      printf("received DONE, disconnecting\n");
      rc_disconnect(id);
      return;
    }
 
    post_receive(id);
  }
}

这与上面描述的序列相匹配。send_file_name()send_next_chunk()最终都调用了write_remote()

static void write_remote(struct rdma_cm_id *id, uint32_t len)
{
  struct client_context *ctx = (struct client_context *)id->context;
 
  struct ibv_send_wr wr, *bad_wr = NULL;
  struct ibv_sge sge;
 
  memset(&wr, 0, sizeof(wr));
 
  wr.wr_id = (uintptr_t)id;
  wr.opcode = IBV_WR_RDMA_WRITE_WITH_IMM;
  wr.send_flags = IBV_SEND_SIGNALED;
  wr.imm_data = htonl(len);
  wr.wr.rdma.remote_addr = ctx->peer_addr;
  wr.wr.rdma.rkey = ctx->peer_rkey;
 
  if (len) {
    wr.sg_list = &sge;
    wr.num_sge = 1;
 
    sge.addr = (uintptr_t)ctx->buffer;
    sge.length = len;
    sge.lkey = ctx->buffer_mr->lkey;
  }
 
  TEST_NZ(ibv_post_send(id->qp, &wr, &bad_wr));
}

这个RDMA请求与之前帖子中描述的请求在两个方面有所不同:我们将操作码(opcode)设置为IBV_WR_RDMA_WRITE_WITH_IMM,并将imm_data设置为我们缓冲区的长度。

这并不难理解,对吧?如果一切正常,你应该会看到以下情况:

ib-host-1$ ./server 
waiting for connections. interrupt (^C) to exit.
opening file test-file
received 10485760 bytes.
received 10485760 bytes.
received 5242880 bytes.
finished transferring test-file
^C
 
ib-host-1$ md5sum test-file
5815ed31a65c5da9745764c887f5f777  test-file
ib-host-2$ dd if=/dev/urandom of=test-file bs=1048576 count=25
25+0 records in
25+0 records out
26214400 bytes (26 MB) copied, 3.11979 seconds, 8.4 MB/s
 
ib-host-2$ md5sum test-file
5815ed31a65c5da9745764c887f5f777  test-file
 
ib-host-2$ ./client ib-host-1 test-file
received MR, sending file name
received READY, sending chunk
received READY, sending chunk
received READY, sending chunk
received READY, sending chunk
received DONE, disconnecting

如果你在内存注册期间遇到错误,比如以下错误,你可能需要增加你的锁定内存资源限制:

error: ctx->buffer_mr = ibv_reg_mr(rc_get_pd(), ctx->buffer, BUFFER_SIZE, IBV_ACCESS_LOCAL_WRITE) failed (returned zero/null).

OpenMPI的FAQ对如何设置锁定内存限制有很好的解释。

示例代码可以在这里找到。

12月21日:更新了帖子以描述锁定内存限制错误,并更新了示例代码以:检查ibv_reg_mr()的错误;使用文件路径的basename()而不是完整路径;在open()调用中添加缺失的模式参数;在Makefile中添加缺失的库引用。感谢Matt。

10月4日:示例代码现在位于https://github.com/tarickb/the-geek-in-the-corner/tree/master/03_file-transfer。

Kimi

这篇文章讨论了使用RDMA(Remote Direct Memory Access,远程直接内存访问)传输大量数据的基本流程控制。作者Matt最近询问了发送大于或等于2GB数据的方法。作者指出,尽管存在2^31的消息大小限制,但有多种有效的方法可以处理这个问题,考虑到内存资源有限,将跳过将整个文件加载到内存中的前三个选项。作者选择了使用RDMA写入操作来说明RDMA写入即时数据(IBV_WR_RDMA_WRITE_WITH_IMM)操作的使用,这是一种与常规RDMA写入类似的操作,但发起方可以附加一个32位的值。

文章提供了一个示例,包括服务器和客户端的代码,服务器等待客户端连接,客户端在连接后发送要传输的文件名,然后发送文件内容。文章重点讨论了同步和流程控制,而不是连接建立的细节。

以下是文章的中文翻译概要:

  1. 服务器和客户端结构:服务器等待连接,客户端在连接后发送文件名和内容。
  2. 流程控制:使用RDMA写入操作和即时数据来控制数据传输速度,确保不会比服务器处理速度快。
  3. 服务器代码:隐藏了连接建立的细节,通过回调函数处理连接请求、建立、完成和断开连接。
  4. 客户端代码:比服务器复杂,需要传递服务器主机名和端口,处理接收到的消息,并执行RDMA写入操作。
  5. 写入远程数据:客户端使用RDMA写入即时数据操作发送文件名和数据块。
  6. 错误处理:如果遇到内存注册错误,可能需要增加锁定内存资源限制。

文章最后提供了示例代码的链接,并在后续更新中修正了一些错误和遗漏。

请注意,这是文章内容的概要翻译,并非逐字翻译。如果需要更详细的翻译或对特定部分有疑问,请告知。

这是一篇关于使用RDMA(Remote Direct Memory Access,远程直接内存访问)进行大文件传输的讨论。评论者Matt最近询问了如何发送较大的数据量,例如大于或等于2GB的文件。他怀疑存在一种有效的方法来处理这个问题,因为消息大小有一个2^31的限制。他指出,他没有大量的内存可用,因为它被用于其他事情。

文章中提到了多种方法来协调从客户端到服务器的整个文件传输。包括将整个文件加载到客户端内存中,然后发送到服务器;或者使用RDMA读取或写入操作。但是,加载整个文件到内存对于大文件来说可能是不切实际的,所以我们将跳过前三个选项。在剩下的三个选项中,作者选择使用RDMA写入,这样可以展示RDMA-write-with-immediate-data操作的使用,这是他一直想讨论的内容。这种操作与传统的RDMA写入类似,但发起方可以“附加”一个32位的值到写入操作中。与常规的RDMA写入不同,带有立即数据的RDMA写入需要目标在接收队列上发布接收操作。当从目标队列中提取完成时,32位的值将可用。

文章还讨论了如何处理流控制,并提供了一个示例,说明如何使用RDMA传输命令行中指定的文件。示例包括服务器和客户端。服务器等待客户端的连接。客户端在连接到服务器后主要做两件事:发送它正在传输的文件的名称,然后发送文件的内容。文章不会涉及建立连接的细节,而是将重点放在同步和流控制上。

服务器端的代码示例包括初始化、接受连接、处理完成和断开连接的回调函数。客户端代码示例包括发送文件名和文件块,以及接收服务器的流控制消息。

文章最后提供了一个示例,展示了如何使用RDMA写入和立即数据来传输文件,并说明了服务器和客户端如何通过发送和接收消息来协调文件传输的过程。

请注意,由于网络原因,我无法访问提供的GitHub链接以验证示例代码。如果你需要查看示例代码,你可以访问提供的GitHub链接。此外,RDMA write with immediate data在iWARP适配器中可能不受支持,文章建议使用RDMA write(不带立即数据)后跟一个send操作,但这留给读者作为练习。

文章最后更新了一些关于锁定内存限制错误的描述,并更新了示例代码,以检查ibv_reg_mr()的错误,使用文件路径的basename()而不是完整路径,添加了open()调用中缺少的mode参数,以及在Makefile中添加了缺失的库引用。

这篇文章讨论了使用RDMA(Remote Direct Memory Access)传输大量数据的基本流程控制。评论者Matt最近询问了关于发送较大数据量(例如大于或等于2GB的文件)的问题。作者指出,尽管存在2^31的消息大小限制,但有多种有效的方法来处理这种数据传输,尤其是在内存资源有限的情况下。

文章中提到了几种可能的数据传输方式,包括将整个文件加载到客户端内存、使用RDMA读写操作等。但考虑到内存限制,作者选择了使用RDMA写操作,并特别提到了带有立即数据的RDMA写操作(IBV_WR_RDMA_WRITE_WITH_IMM),这种操作允许发送方附加一个32位的值到写操作中,并且需要目标方在接收队列中发布接收操作。

文章详细介绍了客户端和服务器的实现方式,包括它们如何同步和控制流程。服务器等待客户端的连接请求,而客户端在连接后发送要传输的文件名,然后发送文件内容。文章重点介绍了如何使用立即数据信号服务器,以及服务器如何显式地向客户端发送消息以表示它已准备好接收数据。

以下是文章的中文翻译摘要:

Matt最近询问了关于发送较大数据量的问题,例如发送大于或等于2GB的文件。作者提供了一些指针或示例,说明了如何发送大量数据。由于存在2^31的消息大小限制,作者提出了一种结合发送/接收操作和RDMA读写操作的方法来处理流程控制,并特别介绍了带有立即数据的RDMA写操作。

文章中的示例包括一个服务器和一个客户端。服务器等待客户端的连接,客户端在连接后主要做两件事:发送它要传输的文件名,然后发送文件内容。文章没有涉及建立连接的细节,而是专注于同步和流程控制。

作者选择了使用RDMA写操作来分解文件并逐个写入服务器内存,并使用立即数据信号来确保不会比服务器处理速度更快地写入数据。服务器通过发送显式消息来告知客户端何时准备好接收数据。

文章还提供了一个流程图来说明文件传输的顺序,包括服务器和客户端如何交互,以及它们如何使用RDMA写操作和立即数据来控制数据传输。

最后,文章提供了服务器和客户端的示例代码,展示了如何实现上述流程。如果在内存注册过程中遇到错误,可能需要增加锁定内存资源的限制。

文章的示例代码可以在GitHub上找到,链接已在文章中更新。

请注意,这是一个非常技术性的文章,涉及到特定的编程概念和网络协议。如果你需要更详细的翻译或对特定部分有疑问,请告知。

这篇文章讨论了使用RDMA(Remote Direct Memory Access,远程直接内存访问)传输大量数据的基本流程控制。评论者Matt最近询问了发送较大数据量(例如大于或等于2GB的文件)的方法。文章作者指出,尽管存在2^31的消息大小限制,但有多种有效的方法可以处理这种数据传输,尤其是考虑到Matt提到可用内存有限。

文章介绍了几种可能的文件传输方式,包括将整个文件加载到客户端内存中,然后发送到服务器;或者使用RDMA读取或写入操作。作者选择了使用RDMA写入操作,因为它可以展示RDMA-write-with-immediate-data(带有立即数据的RDMA写入)操作的使用,这是一种特殊的RDMA写入操作,允许发送方附加一个32位的值。

文章接着详细描述了客户端和服务器的交互流程,包括服务器监听连接、客户端发送文件名和文件内容、同步和流程控制等。服务器和客户端都使用特定的回调函数来处理连接请求、建立连接、完成队列中的条目以及断开连接。

文章还提供了一个示例代码,展示了如何使用RDMA写入操作和立即数据来传输文件。服务器和客户端的代码都进行了简化,以便专注于流程控制和同步机制。服务器代码隐藏了连接建立的细节,并运行了一个事件循环。客户端代码则需要传递服务器主机名和端口到循环中,并在接收到服务器的内存区域细节后开始发送文件名和文件内容。

最后,文章提到如果遇到内存注册错误,可能需要增加锁定内存资源限制,并提供了OpenMPI FAQ的链接来解释如何操作。文章还提到了示例代码的位置,并感谢了Matt和其他贡献者的帮助。

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

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

相关文章

外部mysql导入

利用这个命令&#xff1a; mysql -u username -p database_name < file.sql 然后就这样。成功导入。

惠海 H5528 升降压芯片 12V24V36V48V60V75V LED恒流驱动IC 调光细腻顺滑无阶梯感

惠海H5528是一款升压、降压、升压降压的LED恒流驱动IC&#xff0c;其具备宽范围调光比且无频闪调光的特性&#xff0c;使得它在智能照明、Dali调光、0~10V调光、摄影灯照明以及补光灯照明等多种应用中具有广泛的应用前景。 这款芯片支持降压、升压和升降压拓扑的应用&#xff0…

torch.cat 与 torch.concat函数

文章目录 区别torch.cat介绍作用参数使用实例关于参数dim为None的使用 区别 先说结论&#xff1a;没有区别在功能、用法以及作用上&#xff0c;concat函数就是cat函数的别名&#xff08;官方就是这样说的&#xff09;。下面截图为证&#xff1a;   因此接下来就主要是介绍 to…

艾体宝方案 | ntopng监测异常流量并通知到企业微信

你是否曾因网络异常而感到困扰&#xff1f;在数字化时代&#xff0c;网络流量异常可能给企业带来巨大损失。但别担心&#xff0c;我们为您准备了一份详尽的解决方案&#xff01;想知道如何利用ntopng及时发现异常流量&#xff0c;并通过企业微信等渠道通知你的团队吗&#xff1…

【Qt秘籍】[007]-LineEdit Pushbutton控件

Qt的中有着各种各样的控件&#xff0c;相较于传统C/C的输出默认只能在控制台实现&#xff0c;Qt中可以有不同的接口实现各种不同的功能&#xff0c;下面我们将实现不同功能的输出 hello world&#xff01; 标签Label 【Qt秘籍】[006]-Label实现Hello World程序-编程第一步-CSD…

C#操作MySQL从入门到精通(13)——对查询结果使用函数

前言 我们有时候需要对查询到的数据使用函数进行处理,比如去掉空格,比如截取一半长度等操作,下面我来详细介绍: 本文使用的测试数据如下: 1、使用文本处理函数 1.1 Left 返回具有指定长度的字符串的左边部分 下面的代码获取email这个列从左边第一个字符开始计算的一共…

使用 ISIC 快速申请 JetBrain 学生免费产品

此篇文章适合急需通过学生优惠使用 JetBrain 产品并且愿意花费 50 &#xff08;申请国际电子学生证 ISIC 需要 50&#xff09;的学生。需要等待时间1-3天&#xff0c;主要是等待 ISIC 的时间&#xff0c;只要 ISIC 发放 ISIC name 和 ISIC ID&#xff0c;将其填写到 JetBrain 的…

Linux学习笔记8

介绍man命令 在Linux中&#xff0c;man命令用于查看系统手册页&#xff08;manual pages&#xff09;。系统手册页是关于各种Linux命令、函数库以及系统调用的详尽文档&#xff0c;能够提供关于命令的使用方法、参数说明、示例以及其他相关信息 可以利用man xxx的命令去查找某…

系统架构设计师【第19章】: 大数据架构设计理论与实践 (核心总结)

文章目录 19.1 传统数据处理系统存在的问题19.2 大数据处理系统架构分析19.2.1 大数据处理系统面临挑战19.2.2 大数据处理系统架构特征 19.3 Lambda架构19.3.1 Lambda架构对大数据处理系统的理解19.3.2 Lambda架构应用场景19.3.3 Lambda架构介绍19.3.4  Lambda架构的实…

MySQL(四) - SQL优化

一、SQL执行流程 MySQL是客户端-服务器的模式。一条SQL的执行流程如下&#xff1a; 在执行过程中&#xff0c;主要有三类角色&#xff1a;客户端、服务器、存储引擎。 大致可以分为三层&#xff1a; 第一层&#xff1a;客户端连接到服务器&#xff0c;构造SQL并发送给服务器…

电源变压器的作用和性能

电源变压器的主要作用是改变输入电压的大小&#xff0c;通常用于降低电压或升高电压&#xff0c;以便适应不同设备的需求。它们还可以提供隔离&#xff0c;使得输出电路与输入电路之间电气隔离&#xff0c;从而提高安全性。性能方面&#xff0c;电源变压器需要具有高效率、低温…

【OpenHarmony】ArkTS 语法基础 ④ ( ArkTS UI 渲染控制 | if else 条件渲染 | ForEach 循环渲染 )

文章目录 一、ArkTS UI 渲染控制1、if else 条件渲染2、ForEach 循环渲染 二、完整代码示例1、自定义组件代码2、主界面代码3、执行结果 参考文档 : <HarmonyOS第一课>ArkTS开发语言介绍 ForEach 渲染控制文档 : https://developer.huawei.com/consumer/cn/doc/harmonyo…

Python中的Paramiko与FTP文件夹及文件检测技巧

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; Python代码的魅力与实用价值 在当今数字化时代&#xff0c;编程已成为一种不可或缺的技能。Python作为一种简洁、易读且功能强大的编程语言&#xff0c;受到了全球开发者的喜爱。它不仅适用于初学者入门&#xff0c…

玩转STM32-通信协议SPI(详细-慢工出细活)

文章目录 一、SPI的基础知识1.1 接口定义1.2 单机和多机通信 二、STM32的SPI工作过程2.1 从选择&#xff08;NSS&#xff09;脚管理2.2 时钟相位与极性2.3 SPI主模式2.4 SPI从模式 三、应用实例 一、SPI的基础知识 1.1 接口定义 SPI系统可直接与各个厂家生产的多种标准外围器…

通俗易懂的解释保护性看跌期权和抛补看涨期权!

今天带你了解通俗易懂的解释保护性看跌期权和抛补看涨期权&#xff01;当涉及期权交易时&#xff0c;保护性看跌期权和抛补看涨期权是两种常见的策略&#xff0c;它们的目的都是为了在特定市场情况下对投资进行保护或增强收益。 保护性看跌期权 保护性看跌期权是一种风险管理策…

如何以非交互方式将参数传递给交互式脚本

文章目录 问题回答1. 使用 Here Document2. 使用 echo 管道传递3. 使用文件描述符4. 使用 expect 工具 参考 问题 我有一个 Bash 脚本&#xff0c;它使用 read 命令以交互方式读取命令参数&#xff0c;例如 yes/no 选项。是否有一种方法可以在非交互式脚本中调用这个脚本&…

粘性代理 vs 轮换代理: 特点、优势与选择指南

在网络领域&#xff0c;代理服务器是一种常见的工具&#xff0c;用于隐藏真实IP地址并提供更安全和匿名的网络体验。 粘性代理和轮换代理是两种常见的代理类型&#xff0c;它们在IP持久性和变更频率等方面有所不同。 本文将介绍粘性代理和轮换代理的区别&#xff0c;并分析在…

1.Linux入门

文章目录 一、介绍1.1 操作系统1.2 Linux1.3 虚拟机1.4 安装 CentOS7 二、远程连接 Linux2.1 FinalShell2.2 远程连接Linux 三、扩展3.1 WSL3.2 虚拟机快照 一、介绍 1.1 操作系统 我们平常所用的电脑是个人桌面操作系统&#xff0c;也就是Windows或者是macOS 目前我们要学的…

npm彻底清理缓存

在使用npm过程中&#xff0c;肯定会遇到清缓存的情况&#xff0c;网上的命令一般为 npm cache clear --force有时笔者在清理缓存之后npm install依然失败&#xff0c;仔细发现&#xff0c;执行该命令之后npm报了一个警告 npm WARN using --force Recommended protections dis…

使用AppJail配置网络并创建tiny jail(未成功)

创建tiny jail成功了&#xff0c;但是网络配置这块&#xff0c;jail里只能ping通外面&#xff0c;而无法pkg更新软件。本文章是这篇文章Jail管理器AppJail的使用FreeBSD-CSDN博客的网络篇。 首先host主机配置pf防火墙 参考这里&#xff1a;Packet Filter - AppJail Handbook …