UEFI 图形编程:直接写 FrameBuffer 与 Blt 接口的对比

在 UEFI 环境下开发图形应用时,我们经常需要在屏幕上绘制图像、显示 Logo 或实现简单 UI。EFI_GRAPHICS_OUTPUT_PROTOCOL(简称 GOP)提供了两种最基本的绘图方式:直接操作 FrameBuffer 内存,或者调用 Blt(Block Transfer)接口。


一、背景:UEFI 的图形输出协议

UEFI(统一可扩展固件接口)中的 Graphics Output Protocol (GOP) 负责管理显示输出。通过该协议,我们可以获取当前显示模式(分辨率、像素格式)、设置新模式,并将像素数据写入屏幕。

GOP 提供了两种绘图途径:

  1. 直接映射 FrameBuffer

    通过 Gop->Mode->FrameBufferBase 获得显存基址,然后像操作普通内存一样向其中写入像素值。这种方式非常直接,但需要开发者自行处理像素格式、行对齐等底层细节。

  2. Blt 接口

    Gop->Blt() 是一个标准的块传输函数,支持填充、拷贝、内存 ↔ 显存传输等多种操作。它接受独立于硬件像素格式的 EFI_GRAPHICS_OUTPUT_BLT_PIXEL 结构体,由驱动完成格式转换。

为了直观对比,我们来看两个完整的 UEFI 应用程序:一个采用直接写 FrameBuffer,另一个使用 Blt 接口。两者功能相同------枚举显示模式、设置合适分辨率、绘制渐变背景和白色十字,最后清屏退出。


二、方式一:直接操作 FrameBuffer

这是最直接的方法,代码中直接向显存地址写入像素数据。

c 复制代码
UINT32 *FrameBuffer = (UINT32*)Gop->Mode->FrameBufferBase;
UINT32 PixelsPerScanLine = Gop->Mode->Info->PixelsPerScanLine;

for (y = 0; y < Vertical; y++) {
    for (x = 0; x < Horizontal; x++) {
        // 计算 RGB 分量
        UINT8 r = (UINT8)(x * 255 / Horizontal);
        UINT8 g = (UINT8)(y * 255 / Vertical);
        UINT8 b = 0x80;
        UINT32 color;

        // 手动处理像素字节序
        if (SwapRedBlue) {
            color = (b << 16) | (g << 8) | r;
        } else {
            color = (r << 16) | (g << 8) | b;
        }
        FrameBuffer[y * PixelsPerScanLine + x] = color;
    }
}

关键点

  • 像素格式依赖 :代码必须根据 Gop->Mode->Info->PixelFormat 自行决定 R、G、B 的排列顺序。示例中通过 SwapRedBlue 布尔值来区分 RGB 和 BGR,并手动组装 32 位颜色值。
  • 行步长PixelsPerScanLine 通常是显卡硬件对齐后的每行像素数,可能大于 Horizontal。直接使用 y * PixelsPerScanLine + x 才能正确定位到每个像素的地址。
  • 绘制十字和清屏 :同样使用 for 循环逐像素修改显存。

优点

  • 零额外内存开销:无需分配 CPU 侧的缓冲区,直接操作显存。
  • 灵活:可以实现任意逐像素算法(如抗锯齿、色彩变换等)。

缺点

  • 兼容性差 :若显卡采用自定义位掩码格式(PixelBitMask)或禁止直接访问(PixelBltOnly),该方式将彻底失效。
  • CPU 负担重:每次写入都跨越 PCIe 总线,对于全屏绘制,大量循环会导致明显的性能下降。
  • 代码复杂:必须谨慎处理字节序和对齐,不同显卡的行为可能存在差异。

三、方式二:使用 Blt 接口

方式二遵循 UEFI 规范推荐的标准化做法,将所有绘图操作封装为对 Gop->Blt() 的调用。

c 复制代码
// 1. 在系统内存中构建 BltBuffer
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer;
gBS->AllocatePool(EfiBootServicesData, 
                  Horizontal * Vertical * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL),
                  (VOID**)&BltBuffer);

for (y = 0; y < Vertical; y++) {
    for (x = 0; x < Horizontal; x++) {
        UINTN idx = (UINTN)y * Horizontal + x;
        BltBuffer[idx].Red   = (UINT8)(x * 255 / Horizontal);
        BltBuffer[idx].Green = (UINT8)(y * 255 / Vertical);
        BltBuffer[idx].Blue  = 0x80;
        BltBuffer[idx].Reserved = 0;
    }
}

// 2. 一次性 Blt 到屏幕
Gop->Blt(Gop, BltBuffer, EfiBltBufferToVideo,
         0, 0,        // SourceX, SourceY
         0, 0,        // DestX, DestY
         Horizontal, Vertical,
         0);          // Delta = 0,表示行宽 = Width * sizeof(PIXEL)

// 3. 用 EfiBltVideoFill 画白色十字
EFI_GRAPHICS_OUTPUT_BLT_PIXEL White = {0xFF, 0xFF, 0xFF, 0x00};
Gop->Blt(Gop, &White, EfiBltVideoFill, 0, 0, 0, cy, Horizontal, 1, 0);
Gop->Blt(Gop, &White, EfiBltVideoFill, 0, 0, cx, 0, 1, Vertical, 0);

关键点

  • 无需关心字节序 :直接填充 EFI_GRAPHICS_OUTPUT_BLT_PIXELRedGreenBlue 字段,驱动会自动转换。
  • Delta 参数 :这里传入 0,表示源缓冲区没有额外填充;如果有偏移(SourceX/Y 非零),则需设置正确的行字节数。
  • 硬件加速指令EfiBltVideoFill 使用单个像素作为填充色,驱动会以最高效的方式(可能借助 GPU)填满指定矩形区域,比软件循环快得多。

优点

  • 高兼容性 :支持所有像素格式,包括 PixelBltOnly
  • 代码简洁:不需要手动处理字节序和对齐,意图清晰。
  • 性能优异:批量传输和硬件填充减少了 CPU 与显存的交互次数。
  • 标准接口:符合 UEFI 规范,移植性更好。

缺点

  • 额外内存开销 :需要分配与全屏大小相当的 BltBuffer(例如 1080p 下约 8 MB),对于内存紧张的早期启动环境可能不友好。

四、全方位对比

对比维度 直接写 FrameBuffer Blt 接口
像素字节序处理 手动,易出错 自动转换,无需关心
行填充(Padding) 需使用 PixelsPerScanLine 手动计算 通过 Delta 参数由驱动处理
额外内存占用 需分配全屏大小的 BltBuffer
绘制效率(全屏) 多次 CPU 写操作,较慢 单次 DMA 传输,较快
硬件加速支持 无法利用 支持 EfiBltVideoFill 等填充操作
兼容 PixelBltOnly ❌ 不支持 ✅ 支持
代码维护性 逻辑混杂底层细节 抽象层次高,易于阅读

五、代码

方式1:

bash 复制代码
/**
 * @brief 显示系统启动时的 LOGO 图 方式1:直接写 FrameBuffer
 * 
 */
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/GraphicsOutput.h>
 
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
    EFI_STATUS                      Status;
    EFI_GRAPHICS_OUTPUT_PROTOCOL    *Gop;
    UINT32                          MaxMode;
    UINT32                          ModeNumber;
    UINT32                          SelectedMode = 0;
    UINTN                           SizeOfInfo;
    UINT32                          Horizontal = 0;
    UINT32                          Vertical = 0;
    UINTN                           Index;
    EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *ModeInfo = NULL;
    BOOLEAN                         SwapRedBlue = FALSE;

    // 1. 获取 GOP Protocol
    Status = gBS->LocateProtocol(
                    &gEfiGraphicsOutputProtocolGuid,
                    NULL,
                    (VOID**)&Gop
                    );
    if (EFI_ERROR(Status)) {
        Print(L"Failed to locate GOP\n");
        return Status;
    }
    Print(L"GOP protocol found successfully!\n");

    // 2. 枚举所有显示模式
    Print(L"=== Enumerating All Display Modes ===\n");
    MaxMode = Gop->Mode->MaxMode;
    Print(L"MaxMode = %d\n\n", MaxMode);
    
    for (ModeNumber = 0; ModeNumber < MaxMode; ModeNumber++) {
        Status = Gop->QueryMode(Gop, ModeNumber, &SizeOfInfo, &ModeInfo);
        if (EFI_ERROR(Status)) {
            Print(L"QueryMode failed for mode %d: %r\n", ModeNumber, Status);
            continue;
        }

        Print(L"Mode %2d: %4d x %4d, PixelFormat = %d\n",
            ModeNumber,
            ModeInfo->HorizontalResolution,
            ModeInfo->VerticalResolution,
            ModeInfo->PixelFormat
        );

        if (ModeInfo != NULL) {
            gBS->FreePool(ModeInfo);
            ModeInfo = NULL;
        }
    }

    // 暂停让用户查看枚举结果
    Print(L"\nPress Enter to continue...");
    SystemTable->ConIn->Reset(SystemTable->ConIn, FALSE);
    while (TRUE) {
        SystemTable->BootServices->WaitForEvent(1, &SystemTable->ConIn->WaitForKey, &Index);
        EFI_INPUT_KEY Key;
        if (!EFI_ERROR(SystemTable->ConIn->ReadKeyStroke(SystemTable->ConIn, &Key))) {
            if (Key.UnicodeChar == L'\r') {
                break;
            }
        }
    }

    // 3. 设置显示模式
    Status = Gop->SetMode(Gop, SelectedMode);
    if (EFI_ERROR(Status)) {
        Print(L"Failed to set mode %d: %r\n", SelectedMode, Status);
        return Status;
    }
    Horizontal = Gop->Mode->Info->HorizontalResolution;
    Vertical = Gop->Mode->Info->VerticalResolution;
    Print(L"Selected mode %d with resolution %d x %d\n", SelectedMode, Horizontal, Vertical);
    Print(L"Mode %d set successfully!\n", SelectedMode);

    // 4. 当前显示模式信息
    Print(L"\n=== Current Mode Information ===\n");
    Print(L"Current Mode Number: %d\n", Gop->Mode->Mode);
    Print(L"FrameBuffer Base:   0x%016lx\n", Gop->Mode->FrameBufferBase);
    {
        UINT32 SizeMB = (UINT32)(Gop->Mode->FrameBufferSize / (1024 * 1024));
        UINT32 SizeKB = (UINT32)((Gop->Mode->FrameBufferSize % (1024 * 1024)) / 1024);
        Print(L"FrameBuffer Size:   %d bytes (%d.%02d MB)\n",
                Gop->Mode->FrameBufferSize,
                SizeMB,
                (SizeKB * 100) / 1024);
    }
    Print(L"Horizontal Resolution: %d\n", Gop->Mode->Info->HorizontalResolution);
    Print(L"Vertical Resolution:   %d\n", Gop->Mode->Info->VerticalResolution);
    Print(L"Pixel Format:          %d\n", Gop->Mode->Info->PixelFormat);

    // 解释像素格式
    switch (Gop->Mode->Info->PixelFormat) {
        case PixelRedGreenBlueReserved8BitPerColor:
            Print(L"  -> Pixel Format: RGB (8:8:8, with reserved byte)\n");
            break;
        case PixelBlueGreenRedReserved8BitPerColor:
            Print(L"  -> Pixel Format: BGR (most common on PC)\n");
            break;
        case PixelBitMask:
            Print(L"  -> Pixel Format: Custom bitmask\n");
            break;
        case PixelBltOnly:
            Print(L"  -> Pixel Format: Blt only (no direct framebuffer access)\n");
            break;
        default:
            Print(L"  -> Pixel Format: Unknown\n");
    }

    // 5. 根据像素格式确定颜色通道排列
    SwapRedBlue = (Gop->Mode->Info->PixelFormat == PixelRedGreenBlueReserved8BitPerColor);

    // 4. 等待 Enter 键
    Print(L"Press Enter to draw...\n");
    SystemTable->ConIn->Reset(SystemTable->ConIn, FALSE);
    while (TRUE) {
        SystemTable->BootServices->WaitForEvent(1, &SystemTable->ConIn->WaitForKey, &Index);
        EFI_INPUT_KEY Key;
        if (!EFI_ERROR(SystemTable->ConIn->ReadKeyStroke(SystemTable->ConIn, &Key))) {
            if (Key.UnicodeChar == L'\r') {
                break;
            }
        }
    }

    // 5. 绘制渐变背景与中心十字
    if (Horizontal > 0 && Vertical > 0) {
        UINT32 *FrameBuffer = (UINT32*)Gop->Mode->FrameBufferBase;
        UINT32 PixelsPerScanLine = Gop->Mode->Info->PixelsPerScanLine;
        UINT32 x, y;
        UINT32 cx = Horizontal / 2;
        UINT32 cy = Vertical / 2;

        for (y = 0; y < Vertical; y++) {
            for (x = 0; x < Horizontal; x++) {
                UINT8 r = (UINT8)(x * 255 / Horizontal);
                UINT8 g = (UINT8)(y * 255 / Vertical);
                UINT8 b = 0x80;
                UINT32 color;

                if (SwapRedBlue) {
                    color = (b << 16) | (g << 8) | r;
                } else {
                    color = (r << 16) | (g << 8) | b;
                }
                FrameBuffer[y * PixelsPerScanLine + x] = color;
            }
        }

        for (x = 0; x < Horizontal; x++) {
            FrameBuffer[cy * PixelsPerScanLine + x] = 0x00FFFFFF;
        }
        for (y = 0; y < Vertical; y++) {
            FrameBuffer[y * PixelsPerScanLine + cx] = 0x00FFFFFF;
        }
    }

    // 6. 等待按键退出
    Print(L"Press any key to exit...\n");
    SystemTable->ConIn->Reset(SystemTable->ConIn, FALSE);
    SystemTable->BootServices->WaitForEvent(1, &SystemTable->ConIn->WaitForKey, &Index);

    // 7. 清屏为黑色
    if (Horizontal > 0 && Vertical > 0) {
        UINT32 *FrameBuffer = (UINT32*)Gop->Mode->FrameBufferBase;
        UINT32 PixelsPerScanLine = Gop->Mode->Info->PixelsPerScanLine;
        UINT32 x, y;
        for (y = 0; y < Vertical; y++) {
            for (x = 0; x < Horizontal; x++) {
                FrameBuffer[y * PixelsPerScanLine + x] = 0x00000000;
            }
        }
    }

    return EFI_SUCCESS;
}

方式2

bash 复制代码
/**
 * @brief 显示系统启动时的 LOGO 图 方式2:使用 Blt接口绘制
 * 
 */
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/GraphicsOutput.h>
 
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
    EFI_STATUS                      Status;
    EFI_GRAPHICS_OUTPUT_PROTOCOL    *Gop;
    UINT32                          MaxMode;
    UINT32                          ModeNumber;
    UINT32                          SelectedMode = 0;
    UINTN                           SizeOfInfo;
    UINT32                          Horizontal = 0;
    UINT32                          Vertical = 0;
    UINTN                           Index;
    EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *ModeInfo = NULL;
    EFI_GRAPHICS_OUTPUT_BLT_PIXEL   *BltBuffer = NULL;   // Blt 像素缓冲(B/G/R/A 结构体,无需管字节序)

    // 1. 获取 GOP Protocol
    Status = gBS->LocateProtocol(
                    &gEfiGraphicsOutputProtocolGuid,
                    NULL,
                    (VOID**)&Gop
                    );
    if (EFI_ERROR(Status)) {
        Print(L"Failed to locate GOP\n");
        return Status;
    }
    Print(L"GOP protocol found successfully!\n");

    // 2. 枚举所有显示模式
    Print(L"=== Enumerating All Display Modes ===\n");
    MaxMode = Gop->Mode->MaxMode;
    Print(L"MaxMode = %d\n\n", MaxMode);
    
    for (ModeNumber = 0; ModeNumber < MaxMode; ModeNumber++) {
        Status = Gop->QueryMode(Gop, ModeNumber, &SizeOfInfo, &ModeInfo);
        if (EFI_ERROR(Status)) {
            Print(L"QueryMode failed for mode %d: %r\n", ModeNumber, Status);
            continue;
        }

        Print(L"Mode %2d: %4d x %4d, PixelFormat = %d\n",
            ModeNumber,
            ModeInfo->HorizontalResolution,
            ModeInfo->VerticalResolution,
            ModeInfo->PixelFormat
        );

        if (ModeInfo != NULL) {
            gBS->FreePool(ModeInfo);
            ModeInfo = NULL;
        }
    }

    // 暂停让用户查看枚举结果
    Print(L"\nPress Enter to continue...");
    SystemTable->ConIn->Reset(SystemTable->ConIn, FALSE);
    while (TRUE) {
        SystemTable->BootServices->WaitForEvent(1, &SystemTable->ConIn->WaitForKey, &Index);
        EFI_INPUT_KEY Key;
        if (!EFI_ERROR(SystemTable->ConIn->ReadKeyStroke(SystemTable->ConIn, &Key))) {
            if (Key.UnicodeChar == L'\r') {
                break;
            }
        }
    }

    // 3. 设置显示模式
    Status = Gop->SetMode(Gop, SelectedMode);
    if (EFI_ERROR(Status)) {
        Print(L"Failed to set mode %d: %r\n", SelectedMode, Status);
        return Status;
    }
    Horizontal = Gop->Mode->Info->HorizontalResolution;
    Vertical = Gop->Mode->Info->VerticalResolution;
    Print(L"Selected mode %d with resolution %d x %d\n", SelectedMode, Horizontal, Vertical);
    Print(L"Mode %d set successfully!\n", SelectedMode);

    // 4. 当前显示模式信息
    Print(L"\n=== Current Mode Information ===\n");
    Print(L"Current Mode Number: %d\n", Gop->Mode->Mode);
    Print(L"FrameBuffer Base:   0x%016lx\n", Gop->Mode->FrameBufferBase);
    {
        UINT32 SizeMB = (UINT32)(Gop->Mode->FrameBufferSize / (1024 * 1024));
        UINT32 SizeKB = (UINT32)((Gop->Mode->FrameBufferSize % (1024 * 1024)) / 1024);
        Print(L"FrameBuffer Size:   %d bytes (%d.%02d MB)\n",
                Gop->Mode->FrameBufferSize,
                SizeMB,
                (SizeKB * 100) / 1024);
    }
    Print(L"Horizontal Resolution: %d\n", Gop->Mode->Info->HorizontalResolution);
    Print(L"Vertical Resolution:   %d\n", Gop->Mode->Info->VerticalResolution);
    Print(L"Pixel Format:          %d\n", Gop->Mode->Info->PixelFormat);

    // 解释像素格式
    switch (Gop->Mode->Info->PixelFormat) {
        case PixelRedGreenBlueReserved8BitPerColor:
            Print(L"  -> Pixel Format: RGB (8:8:8, with reserved byte)\n");
            break;
        case PixelBlueGreenRedReserved8BitPerColor:
            Print(L"  -> Pixel Format: BGR (most common on PC)\n");
            break;
        case PixelBitMask:
            Print(L"  -> Pixel Format: Custom bitmask\n");
            break;
        case PixelBltOnly:
            Print(L"  -> Pixel Format: Blt only (no direct framebuffer access)\n");
            break;
        default:
            Print(L"  -> Pixel Format: Unknown\n");
    }

    // 等待 Enter 键后开始绘制
    Print(L"Press Enter to draw...\n");
    SystemTable->ConIn->Reset(SystemTable->ConIn, FALSE);
    while (TRUE) {
        SystemTable->BootServices->WaitForEvent(1, &SystemTable->ConIn->WaitForKey, &Index);
        EFI_INPUT_KEY Key;
        if (!EFI_ERROR(SystemTable->ConIn->ReadKeyStroke(SystemTable->ConIn, &Key))) {
            if (Key.UnicodeChar == L'\r') {
                break;
            }
        }
    }

    // 5. 使用 Blt 接口绘制渐变背景与中心十字
    if (Horizontal > 0 && Vertical > 0) {
        UINTN  BltBufSize = (UINTN)Horizontal * Vertical * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL);
        UINT32 x, y;
        UINT32 cx = Horizontal / 2;
        UINT32 cy = Vertical / 2;
        EFI_GRAPHICS_OUTPUT_BLT_PIXEL White = {0xFF, 0xFF, 0xFF, 0x00};

        // 5.1 在 CPU 内存中构建渐变 BltBuffer(结构体直接写 B/G/R,不关心硬件字节序)
        Status = gBS->AllocatePool(EfiBootServicesData, BltBufSize, (VOID**)&BltBuffer);
        if (EFI_ERROR(Status)) {
            Print(L"AllocatePool for BltBuffer failed: %r\n", Status);
        } else {
            for (y = 0; y < Vertical; y++) {
                for (x = 0; x < Horizontal; x++) {
                    UINTN idx = (UINTN)y * Horizontal + x;
                    BltBuffer[idx].Red      = (UINT8)(x * 255 / Horizontal);
                    BltBuffer[idx].Green    = (UINT8)(y * 255 / Vertical);
                    BltBuffer[idx].Blue     = 0x80;
                    BltBuffer[idx].Reserved = 0x00;
                }
            }
            // 5.2 把 BltBuffer 一次性 Blt 到屏幕 (0,0)
            Status = Gop->Blt(Gop, BltBuffer, EfiBltBufferToVideo,
                              0, 0,              // SourceX, SourceY
                              0, 0,              // DestX, DestY
                              Horizontal, Vertical,
                              0);                // Delta=0 -> 使用 Width*sizeof(PIXEL)
            if (EFI_ERROR(Status)) {
                Print(L"Blt EfiBltBufferToVideo failed: %r\n", Status);
            }
            gBS->FreePool(BltBuffer);
            BltBuffer = NULL;
        }

        // 5.3 用 EfiBltVideoFill 画白色中心十字(硬件加速,不需要构建缓冲)
        //    横线:从 (0, cy) 开始,宽=Horizontal,高=1
        Gop->Blt(Gop, &White, EfiBltVideoFill,
                 0, 0, 0, cy, Horizontal, 1, 0);
        //    竖线:从 (cx, 0) 开始,宽=1,高=Vertical
        Gop->Blt(Gop, &White, EfiBltVideoFill,
                 0, 0, cx, 0, 1, Vertical, 0);
    }

    // 6. 等待按键退出
    Print(L"Press any key to exit...\n");
    SystemTable->ConIn->Reset(SystemTable->ConIn, FALSE);
    SystemTable->BootServices->WaitForEvent(1, &SystemTable->ConIn->WaitForKey, &Index);

    // 7. 用 EfiBltVideoFill 清屏为黑色(1 次调用 = 整屏填充)
    if (Horizontal > 0 && Vertical > 0) {
        EFI_GRAPHICS_OUTPUT_BLT_PIXEL Black = {0x00, 0x00, 0x00, 0x00};
        Gop->Blt(Gop, &Black, EfiBltVideoFill,
                 0, 0, 0, 0, Horizontal, Vertical, 0);
    }

    return EFI_SUCCESS;
}
.inf:
bash 复制代码
[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = ShowLogo
  FILE_GUID                      = 5B1A8D6E-7C3F-4E2A-9D01-3F1B2A4C5D6E
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = UefiMain

[Sources]
  ShowLogo.c

[Packages]
  MdePkg/MdePkg.dec
  MdeModulePkg/MdeModulePkg.dec

[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib
  UefiBootServicesTableLib

[Protocols]
  gEfiGraphicsOutputProtocolGuid
相关推荐
格林威24 分钟前
C# 相机图像配合频闪光源:实现高速稳定拍摄的几个方法
开发语言·网络·人工智能·数码相机·计算机视觉·c#·视觉检测
爱研究的小梁1 小时前
公网地基与卫星天基如何平衡成本与可靠性
网络·信息与通信
anxiao_m1 小时前
2026企业大数据传输解决方案测评,不同场景选型指南
网络
啦啦啦啦啦zzzz1 小时前
ET和LT详解
linux·运维·服务器·网络·c++·网络编程
深圳市宝华视联2 小时前
移动示教推车功能介绍
网络·嵌入式硬件·音视频·视频编解码·嵌入式实时数据库
梦難2 小时前
linux的系统命令
linux·服务器·网络
Java小白笔记2 小时前
Java中PDF文件导出,生成链路与实现
服务器·网络·oracle
跨境技工小黎2 小时前
YouTube联盟营销如何变现?如何利用IP代理提高流量变现效果?
服务器·网络·tcp/ip
瓦学妹2 小时前
HTTP代理性能分析:如何利用 IPFoxy 快速灵活切换协议?
网络·网络协议·http