IO 多路复用技术:原理、类型及 Go 实现

news2024/11/26 22:44:57

文章目录

    • 1. 引言
      • IO 多路复用的应用场景与重要性
      • 高并发下的 IO 处理挑战
    • 2. IO 多路复用概述
      • 什么是 IO 多路复用
      • IO 多路复用的优点与适用场景
    • 3. IO 多路复用的三种主要实现
      • 3.1 `select`
      • 3.2 `poll`
      • 3.3 `epoll`
      • 三者对比
    • 4. 深入理解 epoll
      • 4.1 epoll 的三大操作
      • 4.2 epoll 的核心数据结构
      • 4.3 边缘触发与水平触发模式详解
        • 1. 水平触发(LT)
        • 2. 边缘触发(ET)
      • 4.4 水平触发和边缘触发的对比
    • 5. Go 实现一个简单的聊天室服务器
      • 5.1 实现思路
      • 5.2 使用 `epoll` 管理客户端连接
      • 5.3 代码实现
      • 5.4 代码解析
      • 5.5 边缘触发的关键点
      • 5.6 流程图:事件处理流程
    • 6. 总结
    • 结束语


1. 引言

IO 多路复用的应用场景与重要性

在网络编程中,服务器需要同时处理多个客户端的请求,这在高并发环境中尤为突出。举例来说,大型即时通讯应用、HTTP 服务器、数据库服务等场景下,往往要支持成千上万个客户端的连接。如果每个客户端连接都使用一个独立的线程,系统资源消耗会极为庞大,并导致频繁的线程切换,严重影响性能。

IO 多路复用技术则是一种可以在单线程中管理多个 IO 事件的高效机制。通过在单线程中监控多个文件描述符(通常是 socket)上的 IO 操作状态,服务器能够灵活处理多个客户端的请求,避免了线程和资源的大量开销。因此,IO 多路复用广泛应用于高并发服务器编程中,如 Nginx、Redis、Kafka 等项目中。


高并发下的 IO 处理挑战

高并发场景中,服务器面临的主要挑战包括:

  • 资源管理难度:大量线程带来的 CPU 资源开销和内存管理负担。
  • 性能瓶颈:传统阻塞 IO 导致的频繁等待,降低了系统的吞吐量。
  • 复杂的事件处理:如何在不增加复杂度的前提下高效管理多客户端连接。

IO 多路复用技术通过提供非阻塞、集中管理的方式有效解决了以上问题。理解 IO 多路复用技术及其实现原理,尤其是高效的 epoll,对网络编程的开发者非常重要。


2. IO 多路复用概述

什么是 IO 多路复用

IO 多路复用是一种在一个线程中同时监听多个 IO 事件的方法。当任何一个文件描述符上有数据可读或可写时,IO 多路复用会通知应用程序去处理该事件。常见的 IO 多路复用包括 selectpollepoll

IO 多路复用的优点与适用场景

  • 高效资源利用:一个线程管理多个连接,减少了多线程的切换开销。
  • 灵活性:可以动态增加或减少监控的文件描述符,适应高并发需求。
  • 提高吞吐量:通过减少阻塞等待,提高了数据处理的整体吞吐量。

适用场景:IO 多路复用特别适合长连接、大量连接、并发请求频繁的场景,如 Web 服务器、聊天室服务、数据库服务等。


3. IO 多路复用的三种主要实现

在 Linux 系统中,IO 多路复用有三种实现方式:selectpollepoll。这三种方法在原理、性能和适用场景上存在显著差异。

3.1 select

select 是最早的 IO 多路复用实现,广泛支持于多种操作系统。

select 的基本工作方式是通过文件描述符集合来管理多个 IO 通道。应用程序在调用 select 时,需要传入一个文件描述符集合(如读集合、写集合和异常集合),select 会阻塞并等待其中任何一个文件描述符的状态发生变化。如果有 IO 事件发生,select 返回相应的描述符集合,供程序进一步处理。

特点:

  • 文件描述符限制select 受文件描述符数量的限制,通常 FD_SETSIZE 设定为 1024,意味着 select 最多只能同时监控 1024 个文件描述符。
  • 性能问题select 在每次调用时都会遍历整个文件描述符集合,检查是否有事件发生,因此性能较低,尤其在高并发场景中,效率更低。

适用场景:由于性能瓶颈和文件描述符限制,select 适用于小规模并发的网络应用,通常是一些简单的服务或学习用途。


3.2 poll

poll 是对 select 的改进,消除了文件描述符数量限制,并提高了一定的性能。

基本原理:与 select 类似,poll 通过遍历文件描述符集合来检查 IO 事件,但它使用链表来存储文件描述符,因此文件描述符的数量不再受 FD_SETSIZE 限制,理论上可以监控更多的连接。

特点:

  • 消除了文件描述符上限poll 允许监控大量的文件描述符。
  • 性能问题仍然存在:与 select 类似,poll 每次调用仍需遍历整个文件描述符集合,因此在大并发场景下效率较低。

适用场景:适合中等规模的并发网络应用,但在高并发环境下,性能依然有限。


3.3 epoll

epoll 是 Linux 提供的高效 IO 多路复用方式,专门为大规模并发场景而设计。

epoll 的工作方式与 selectpoll 有显著不同epoll 使用事件通知机制,即在文件描述符有状态变化时,epoll 将只返回变化的文件描述符,而不是遍历整个文件描述符集合。

epoll 提供了以下三种核心操作:

  1. epoll_create:创建一个 epoll 实例,用于管理多个文件描述符。
  2. epoll_ctl:添加、修改或删除 epoll 实例中的文件描述符事件。
  3. epoll_wait:等待事件触发,并返回已经就绪的事件集合。

epoll 的优势在于:

  • O(1) 复杂度:每次事件触发后,epoll 只返回变化的文件描述符,避免了重复遍历整个集合的开销。
  • 红黑树和就绪链表epoll 使用红黑树存储监控的文件描述符,同时将就绪的事件放入就绪链表,以便高效管理和返回事件。

三者对比

特性selectpollepoll
文件描述符限制受限(通常为 1024 个)无限制无限制
实现方式文件描述符集合链表红黑树 + 就绪链表
性能随并发数增加而降低随并发数增加而降低O(1) 性能
适用场景小规模并发中等规模并发大规模并发,适合高性能场景

通过 epoll,高并发服务器能够在单线程中处理数万个并发连接,因此它被广泛用于各类高性能服务器中。接下来我们将深入探讨 epoll 的核心数据结构和触发机制。


4. 深入理解 epoll

在理解了 epoll 的基本原理后,我们需要深入其核心实现,了解数据结构、触发机制等,尤其是边缘触发和水平触发模式。让我们详细分解 epoll 的工作机制及其在高并发场景中的优势。

4.1 epoll 的三大操作

  1. epoll_create:创建 epoll 实例并初始化相关数据结构。这个实例相当于一个事件管理器,用于集中管理各个文件描述符的事件状态。

    epollFD, err := syscall.EpollCreate1(0)
    if err != nil {
        panic(err)
    }
    defer syscall.Close(epollFD)
    
  2. epoll_ctlepoll 控制接口,用于向 epoll 实例中添加、修改或删除文件描述符的事件监听。

    event := syscall.EpollEvent{Events: syscall.EPOLLIN, Fd: int32(fd)}
    err := syscall.EpollCtl(epollFD, syscall.EPOLL_CTL_ADD, fd, &event)
    if err != nil {
        panic(err)
    }
    
  3. epoll_wait:阻塞等待事件触发,并将就绪的文件描述符返回给用户。

    events := make([]syscall.EpollEvent, 10) // 创建事件集合
    nfds, err := syscall.EpollWait(epollFD, events, -1)
    if err != nil {
        panic(err)
    }
    

4.2 epoll 的核心数据结构

epoll 使用两种关键数据结构来管理和维护文件描述符事件的状态:

  • 红黑树(rbtree):所有被监控的文件描述符存储在红黑树中,以便进行快速查找、插入和删除操作。当我们调用 epoll_ctl 添加或删除文件描述符时,epoll 会操作红黑树。

  • 就绪链表:当某个文件描述符的状态发生变化时,epoll 会将它添加到就绪链表中。每次调用 epoll_wait 时,epoll 会将链表中的就绪事件返回,而不再遍历整个红黑树。

4.3 边缘触发与水平触发模式详解

epoll 中,事件通知有两种模式:水平触发(Level Triggered, LT)边缘触发(Edge Triggered, ET)。两种触发模式决定了 epoll 如何通知应用程序处理 IO 事件。

1. 水平触发(LT)

水平触发是 epoll 的默认模式,也是最常见的触发模式。水平触发模式下,只要文件描述符处于就绪状态(例如,缓冲区中有数据可以读取),epoll_wait 就会不断返回该事件。这意味着应用程序可以多次获取并处理同一个就绪的文件描述符事件,直到事件处理完毕。

流程图:
水平触发

工作机制:

  1. 文件描述符状态变化时触发:每次调用 epoll_wait 时,若文件描述符处于就绪状态,epoll 会将其返回。
  2. 重复通知:如果应用程序没有处理文件描述符的就绪状态(例如,读取完所有数据),epoll_wait 会在下一次调用时再次返回该文件描述符,直到就绪状态被清除(例如,数据被完全读取)。

优缺点:

  • 优点:简单,适合处理大量 IO 事件,因为不会错过任何就绪事件。
  • 缺点:会重复返回同一个事件,增加了系统调用次数,性能可能受影响。

示例:水平触发读取数据

以下是一个使用水平触发读取数据的示例:

func handleEventsLT(epollFD int, events []syscall.EpollEvent, clients map[int]net.Conn) {
    for _, event := range events {
        if event.Events&syscall.EPOLLIN != 0 {
            fd := int(event.Fd)
            buf := make([]byte, 512)
            
            // 循环读取数据直到读完为止
            for {
                n, err := clients[fd].Read(buf)
                if n == 0 || err != nil {
                    // 如果读取完或遇到错误,关闭连接
                    fmt.Printf("Closing connection %d\n", fd)
                    syscall.EpollCtl(epollFD, syscall.EPOLL_CTL_DEL, fd, nil)
                    clients[fd].Close()
                    delete(clients, fd)
                    break
                }
                
                fmt.Printf("Read %d bytes: %s\n", n, string(buf[:n]))
                
                // 这里可以处理读取到的数据,比如广播到其他客户端
            }
        }
    }
}

在水平触发模式中,上述代码会在文件描述符仍有未处理的数据时不断被触发,确保我们可以持续读取到数据,直到全部读完。

2. 边缘触发(ET)

边缘触发是一种高效的触发模式,适合性能要求高的场景。与水平触发不同,边缘触发仅在文件描述符状态发生边缘变化时(例如,从不可读到可读)通知一次。也就是说,如果缓冲区有新数据到达,epoll 会通知一次,而不会持续通知。

边缘触发时序图:
边缘触发

工作机制:

  1. 状态变化时触发:边缘触发只在文件描述符的状态从不可读到可读、或不可写到可写时通知一次。
  2. 不重复通知:如果应用程序在收到通知后没有将数据读完,那么在数据再次变化前不会收到新的通知。这意味着应用程序必须一次性将数据全部读取,否则会错过后续的事件通知。

优缺点:

  • 优点:减少了重复通知,性能更高,适合高并发场景。
  • 缺点:开发难度更高。应用程序必须一次性将数据读完,否则可能错过事件通知,导致数据读取不完整。

示例:边缘触发读取数据:

为了在边缘触发模式下保证数据不遗漏,我们通常会使用非阻塞模式,并在单次事件触发中循环读取数据直到缓冲区为空。

func handleEventsET(epollFD int, events []syscall.EpollEvent, clients map[int]net.Conn) {
    for _, event := range events {
        if event.Events&syscall.EPOLLIN != 0 {
            fd := int(event.Fd)
            buf := make([]byte, 512)
            
            // 边缘触发模式下,必须一次性读完所有数据
            for {
                n, err := clients[fd].Read(buf)
                if n == 0 || err != nil {
                    if err != nil && err != syscall.EAGAIN {
                        // 出现非阻塞错误或读到 EOF,关闭连接
                        fmt.Printf("Closing connection %d\n", fd)
                        syscall.EpollCtl(epollFD, syscall.EPOLL_CTL_DEL, fd, nil)
                        clients[fd].Close()
                        delete(clients, fd)
                    }
                    break
                }
                
                fmt.Printf("Read %d bytes: %s\n", n, string(buf[:n]))
                
                // 处理读取到的数据
            }
        }
    }
}

在此代码中,循环读取数据直到返回 EAGAIN 错误或数据读完。通过这种方式,确保我们在一次事件触发中尽可能多地读取数据,避免漏掉数据。


4.4 水平触发和边缘触发的对比

特点水平触发(LT)边缘触发(ET)
触发条件只要文件描述符处于就绪状态,持续触发状态从不可用变为可用时触发一次
重复通知会持续返回相同的事件,直到状态改变不会重复通知
性能较低较高
实现复杂度简单较高
适用场景适合一般场景,特别是低并发或对性能要求不高的场景适合高并发场景,性能要求高
  1. 一般场景(水平触发):水平触发模式适合大部分 IO 操作,因为它简单可靠。尤其在低并发场景下,水平触发模式便于实现,不容易遗漏事件。
  2. 高性能场景(边缘触发):在高并发场景下,边缘触发模式具有更高的性能,但要求程序确保数据一次性读取完毕,代码实现较复杂。适用于高性能服务器的设计,比如 Nginx 和 Redis 服务器。

总结来说,水平触发模式适合简单易用的场景边缘触发模式则适用于追求性能的高并发系统。在实际开发中,选择触发模式时应综合考虑系统的并发量、对性能的要求以及代码的复杂度。


5. Go 实现一个简单的聊天室服务器

5.1 实现思路

聊天室服务器的核心功能是管理多个客户端的连接,并支持消息广播。具体而言,这个服务器需要具备以下功能:

  1. 监听客户端连接:通过 epoll 监听客户端的连接请求,并将连接加入 epoll 实例的监听列表中。
  2. 处理客户端消息:在收到某个客户端的消息时,服务器将消息广播给其他所有客户端。
  3. 边缘触发模式下的高效读写:在边缘触发(ET)模式下实现非阻塞读写,保证在一次触发中尽量将数据处理完。

5.2 使用 epoll 管理客户端连接

在实现中,我们将使用 Go 的 syscall 包直接调用 epoll 系统接口。每个新连接或就绪的客户端 socket 会通过 epoll_wait 触发事件,从而被服务器捕获并处理。边缘触发模式要求我们在处理每个 socket 时确保数据被一次性读取完毕,避免遗漏数据。

5.3 代码实现

以下是完整的聊天室服务器代码,实现了客户端连接管理、消息广播和边缘触发模式的高效事件处理。代码详细注释了各个步骤,便于理解 epoll 的具体应用。

package main

import (
    "fmt"
    "net"
    "syscall"
)

const (
    MaxEvents = 10
)

func main() {
    // 1. 创建监听 socket
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        panic(err)
    }
    defer listener.Close()
    fmt.Println("Chat server started on :8080")

    // 2. 创建 epoll 实例
    epollFD, err := syscall.EpollCreate1(0)
    if err != nil {
        panic(err)
    }
    defer syscall.Close(epollFD)

    // 3. 将监听 socket 的文件描述符加入 epoll 实例
    listenerFD := int(listener.(*net.TCPListener).Fd())
    addToEpoll(epollFD, listenerFD, syscall.EPOLLIN)

    // 创建事件列表和客户端连接映射
    events := make([]syscall.EpollEvent, MaxEvents)
    clients := make(map[int]net.Conn) // 存储客户端连接,键为文件描述符

    for {
        // 4. 等待事件触发
        n, err := syscall.EpollWait(epollFD, events, -1)
        if err != nil {
            panic(err)
        }

        // 5. 遍历每个就绪事件
        for i := 0; i < n; i++ {
            fd := int(events[i].Fd)
            if fd == listenerFD {
                // 处理新的客户端连接
                conn, err := listener.Accept()
                if err != nil {
                    fmt.Println("Error accepting connection:", err)
                    continue
                }
                clientFD := int(conn.(*net.TCPConn).Fd())
                addToEpoll(epollFD, clientFD, syscall.EPOLLIN|syscall.EPOLLET) // 使用边缘触发
                clients[clientFD] = conn
                fmt.Println("New client connected:", clientFD)
            } else {
                // 处理来自客户端的数据
                handleClientMessage(fd, epollFD, clients)
            }
        }
    }
}

// addToEpoll 将文件描述符添加到 epoll 实例中,监听指定事件
func addToEpoll(epollFD int, fd int, events uint32) {
    event := syscall.EpollEvent{Events: events, Fd: int32(fd)}
    if err := syscall.EpollCtl(epollFD, syscall.EPOLL_CTL_ADD, fd, &event); err != nil {
        panic(err)
    }
}

// handleClientMessage 读取客户端消息并广播给其他客户端
func handleClientMessage(clientFD int, epollFD int, clients map[int]net.Conn) {
    buf := make([]byte, 512)
    conn := clients[clientFD]

    // 使用边缘触发,循环读取数据直到读取完毕
    for {
        n, err := conn.Read(buf)
        if n == 0 || err != nil {
            // 客户端断开连接
            fmt.Printf("Client %d disconnected\n", clientFD)
            syscall.EpollCtl(epollFD, syscall.EPOLL_CTL_DEL, clientFD, nil)
            conn.Close()
            delete(clients, clientFD)
            break
        }

        // 打印并广播消息
        message := fmt.Sprintf("Client %d: %s", clientFD, string(buf[:n]))
        fmt.Print(message)
        broadcastMessage(clientFD, message, clients)
    }
}

// broadcastMessage 将消息广播给其他所有客户端
func broadcastMessage(senderFD int, message string, clients map[int]net.Conn) {
    for fd, conn := range clients {
        if fd != senderFD { // 不发送给自己
            conn.Write([]byte(message))
        }
    }
}

5.4 代码解析

  1. 创建监听 socket:使用 net.Listen 启动一个 TCP 监听 socket 以接受客户端连接。
  2. 创建 epoll 实例:通过 syscall.EpollCreate1 创建一个 epoll 实例,返回 epollFD 文件描述符,用于管理多个客户端连接。
  3. 将监听 socket 添加到 epoll 实例:将监听 socket 的文件描述符添加到 epoll,并设置监听 EPOLLIN 事件,表示有新的客户端连接时会触发事件。
  4. 等待事件触发syscall.EpollWait 阻塞等待事件触发,并返回已经就绪的事件列表。
  5. 处理新客户端连接:当监听 socket 的事件触发时,表示有新客户端连接。使用 Accept 接受连接,并将新连接的文件描述符添加到 epoll 中,设置为 EPOLLET 模式(边缘触发)。
  6. 读取客户端消息并广播:当客户端 socket 的事件触发时,表示有消息可读。在 handleClientMessage 中,我们使用非阻塞方式循环读取数据,直到所有数据读取完毕,随后广播消息给其他客户端。
  7. 广播消息broadcastMessage 函数将来自某个客户端的消息广播给所有其他客户端,实现聊天室功能。

5.5 边缘触发的关键点

在边缘触发模式(EPOLLET)下,epoll_wait 只会在文件描述符状态发生变化时触发一次。为确保在一次触发中处理完所有数据,我们在 handleClientMessage 函数中使用非阻塞读取,循环读取直到所有数据读完。这避免了数据遗漏,同时利用边缘触发的高性能。


5.6 流程图:事件处理流程

以下流程图展示了聊天室服务器的事件处理流程,帮助我们直观理解每一步骤:
完成触发流程


6. 总结

在本篇文章中,我们系统深入地讲解了 IO 多路复用技术,从基础概念到具体实现,帮助读者理解其在高并发网络编程中的重要性。以下是我们文章中的关键要点总结:

  1. IO 多路复用的意义与应用场景:我们首先介绍了 IO 多路复用的重要性。通过允许单线程管理多个 IO 通道,IO 多路复用可以极大地提升服务器的并发能力,广泛应用于高性能服务器、实时通讯系统和数据库服务等场景。

  2. 三种 IO 多路复用实现方式:selectpollepoll:我们分别介绍了 selectpollepoll 的基本原理、优缺点以及适用场景。虽然 selectpoll 提供了基本的 IO 多路复用功能,但它们的性能在高并发下存在瓶颈。epoll 则是专为高并发设计的高效 IO 多路复用机制,具备 O(1) 的性能特征,是大型 Linux 服务器应用的主流选择。

  3. 深入理解 epoll 的实现细节:通过讲解 epoll 的三大操作(epoll_createepoll_ctlepoll_wait)、核心数据结构(红黑树和就绪链表)、以及事件触发模式(边缘触发和水平触发),我们详细剖析了 epoll 的高效实现原理。特别是边缘触发模式下的非阻塞处理,帮助我们了解了如何在高并发场景下充分利用 epoll 的性能优势。

  4. 基于 Go 实现的聊天室服务器示例:我们提供了一个简单的聊天室服务器实现,展示了 epoll 的实际应用。通过 epoll 的边缘触发模式,服务器能够高效管理多个客户端连接并进行消息广播。具体代码实现帮助我们理解如何使用 epoll 的非阻塞读写来确保数据处理的完整性。


结束语

IO 多路复用是一项强大的技术,epoll 的高效实现为 Linux 系统中的高并发网络编程提供了有力支持。在本篇文章中,我们通过详细讲解和示例实现,让大家更加深入地理解了 IO 多路复用技术的原理和应用。

祝大家在 IO 多路复用和高并发编程的学习之旅中一帆风顺!如有任何问题或讨论,欢迎留言,我们共同交流。


在这里插入图片描述

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

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

相关文章

HarmonyOS-消息推送

一. 服务简述 Push Kit&#xff08;推送服务&#xff09;是华为提供的消息推送平台&#xff0c;建立了从云端到终端的消息推送通道。所有HarmonyOS 应用可通过集成 Push Kit&#xff0c;实现向应用实时推送消息&#xff0c;使消息易见&#xff0c;构筑良好的用户关系&#xff0…

ubuntu安装与配置Nginx(1)

在 Ubuntu 上安装和配置 Nginx 是相对简单的。以下是一个逐步指南&#xff1a; 1. 更新系统包 首先&#xff0c;确保你的系统是最新的。打开终端并运行&#xff1a; sudo apt update sudo apt upgrade2. 安装 Nginx 使用以下命令安装 Nginx&#xff1a; sudo apt install …

FastAdmin动态创建一个富文本编辑器(summernote)

话多说直接看效果&#xff1a; <!-- 动态创建的一个富文本&#xff0c;请注意本人是为了方便所以把js放在了这里&#xff0c;使用者可以结合自身需求修改 --><div class"form-group"><!-- 这里博主使用临时路径&#xff0c;需要自行修改 --><…

网络层5——IPV6

目录 一、IPv6 vs IPv4 1、对IPv6主要变化 2、IPv4 vs IPv6 二、IPv6基本首部 1、版本——4位 2、通信量类——8位 3、流标号——20位 4、有效载荷长度——16位 5、下一个首部——8位 6、跳数限制——8位 7、源 、 目的地址——128位 8、扩展首部 三、IPv6地址 1…

C++STL——list

C教学总目录 list 1、list简介2、构造函数3、迭代器4、访问和容量函数5、修改类函数6、操作类函数 1、list简介 list是带头双向循环链表&#xff0c;也是模板类&#xff0c;使用时要指明类型&#xff0c;包含于头文件<list> 由于list是双向循环链表&#xff0c;在任意位置…

DMRl-Former用于工业过程预测建模和关键样本分析的数据模式相关可解释Transformer网络

DMRl-Former用于工业过程预测建模和关键样本分析的数据模式相关可解释Transformer网络 Liu, Diju, et al. “Data mode related interpretable transformer network for predictive modeling and key sample analysis in industrial processes.” IEEE Transactions on Indust…

JS渗透(安全)

JS逆向 基本了解 作用域&#xff1a; 相关数据值 调用堆栈&#xff1a; 由下到上就是代码的执行顺序 常见分析调试流程&#xff1a; 1、代码全局搜索 2、文件流程断点 3、代码标签断点 4、XHR提交断点 某通js逆向结合burp插件jsEncrypter 申通快递会员中心-登录 查看登录包…

Redis为什么用跳表实现有序集合

Redis为什么用跳表实现有序集合 手写一个跳表 为了更好的回答上述问题以及更好的理解和掌握跳表&#xff0c;这里可以通过手写一个简单的跳表的形式来帮助读者理解跳表这个数据结构。 我们都知道有序链表在添加、查询、删除的平均时间复杂都都是 O(n) 即线性增长&#xff0c…

影刀RPA实战:嵌入python,如虎添翼

1. 影刀RPA与Python的关系 影刀RPA与Python的关系可以从以下几个方面来理解&#xff1a; 技术互补&#xff1a;影刀RPA是一种自动化工具&#xff0c;它允许用户通过图形化界面创建自动化流程&#xff0c;而Python是一种编程语言&#xff0c;常用于编写自动化脚本。影刀RPA可以…

GR2——在大规模视频数据集上预训练且机器人数据上微调,随后预测动作轨迹和视频(含GR1详解)

前言 上个月的24年10.9日&#xff0c;我在朋友圈看到字节发了个机器人大模型GR2&#xff0c;立马去看了下其论文(当然了&#xff0c;本质是个技术报告) 那天之后&#xff0c;我就一直想解读这个GR2来着 然&#xff0c;意外来了&#xff0c;如此文《OmniH2O——通用灵巧且可全…

HarmonyOS NEXT应用元服务开发组合场景

在一些场景中&#xff0c;一个功能上完整的UI对象可能是由若干个更小的UI组件组合而成的。若每一个小的UI组件都可以获焦并朗读&#xff0c;则会造成信息冗余和效率降低。同时由于可聚焦的组件过多过细&#xff0c;也会影响触摸浏览时走焦的性能体验。在这种情况下&#xff0c;…

2024双11高端家用投影仪哪个牌子好?当贝因何力压极米坚果

随着生活水平的日益提升与科技的飞速进步&#xff0c;人们不只通过外出游玩来获得身心的愉悦&#xff0c;也通过提升家庭娱乐生活的品质&#xff0c;来获得足不出户的快乐。在2024年双11购物狂欢节之际&#xff0c;很多家庭都纷纷将高端家用投影仪加入购物清单&#xff0c;但各…

SpringBoot day 1104

ok了家人们这周学习SpringBoot的使用&#xff0c;和深入了解&#xff0c;letgo 一.SpringBoot简介 1.1 设计初衷 目前我们开发的过程当中&#xff0c;一般采用一个单体应用的开发采用 SSM 等框架进行开发&#xff0c;并在 开发的过程当中使用了大量的 xml 等配置文件&#x…

HCIP(7)-边界网关协议BGP基本配置(对等体peer,宣告network,引入import)

边界网关协议&#xff08;Border Gateway Protocol&#xff0c;BGP&#xff09;是一种用来在路由选择域之间交换网络层可达性信息&#xff08;Network Layer Reachability Information&#xff0c;NLRI&#xff09;的路由选择协议。由于不同的管理机构分别控制着他们各自的路由…

算法: 链表题目练习

文章目录 链表题目练习两数相加两两交换链表中的节点重排链表合并 K 个升序链表K 个一组翻转链表 总结 链表题目练习 两数相加 坑: 两个链表都遍历完后,可能需要进位. class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {ListNode cur1 l1;ListNode…

HTML 基础标签——元数据标签 <meta>

文章目录 1. `<meta>` 标签概述2. 属性详解2.1 `charset` 属性2.2 `name` 属性2.3 `content` 属性2.4 `http-equiv` 属性3. 其他常见属性小结在 HTML 文档中,元数据标签 <meta> 是一种重要的标签,用于提供关于文档的信息,这些信息不直接显示在网页内容中,但对于…

新闻稿件管理系统:SpringBoot框架深度解析

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

【网络】自定义协议——序列化和反序列化

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;了解什么是序列化和分序列&#xff0c;并且自己能手撕网络版的计算器。 > 毒鸡汤&#xff1a;有些事情&#xff0c;总是不明白&#xff0c;所以我不…

CTFshow之信息收集第1关到10关。详细讲解

得而不惜就该死&#xff01; --章总 开始新的篇章&#xff01; 零、目录 一、实验准备 1、ctf网站&#xff1a;ctf.show 2、工具&#xff1a;chrome浏览器、hackbar插件 3、burpsuite抓包工具 二、实验技巧 &#xff08;一&#xff09;F12摸奖 源码泄露 &#xff08;二…

Redis ——发布订阅

问题引入&#xff1a; 服务器A、B、C、D&#xff0c;客户端&#xff1a;C1&#xff0c;C2&#xff0c;C3&#xff0c;C4&#xff1b; 客户端基于集群聊天服务器的负载均衡分配&#xff1b; C1 被分配到A&#xff0c;C2 被分配到B&#xff0c;C3 被分配到C&#xff0c;C4 被分…