Talk is cheap. Show me the code.手搓一个 Wayland 客户端程序

前几天我写了一篇万字长文《万字长文详解 Wayland 协议、架构》,但光讲协议分析难免有些枯燥。毕竟,程序员更信奉那句名言:Talk is cheap. Show me the code.

所以这篇文章不打算长篇大论,而是通过编写一个简单的 Wayland 客户端程序,带大家实际体验一下 Wayland 的“坑”与门道。

我们要开发的 Wayland 客户端非常简单,只需在窗口中显示一句 “Hello wayland”。其实,写图形界面程序一般推荐用 GTK、QT 这样的 GUI 框架,这样可以自动适配 X11、Wayland 等后端。但为了演示 Wayland 客户端的底层写法,这次我们选择“手搓”一个。当然,具体实现就交给 AI 助手来完成了。

很快,AI 就给出了第一个版本的 Wayland 客户端程序。

第一个版本

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <cairo.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>// 包含我们刚刚生成的头文件
#include "xdg-shell-client-protocol.h"// 用于管理我们所有Wayland对象和状态的结构体
struct state {struct wl_display *display;struct wl_registry *registry;struct wl_compositor *compositor;struct wl_surface *surface;struct wl_shm *shm;struct xdg_wm_base *xdg_wm_base;struct xdg_surface *xdg_surface;struct xdg_toplevel *xdg_toplevel;struct wl_buffer *buffer;void *shm_data;int width, height;_Bool running;
};// 绘制函数
static void draw_frame(struct state *state) {int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, state->width);int size = stride * state->height;// 清空缓冲区内存memset(state->shm_data, 0, size);// 使用Cairo在共享内存上创建表面cairo_surface_t *cairo_surface = cairo_image_surface_create_for_data(state->shm_data, CAIRO_FORMAT_ARGB32, state->width, state->height, stride);cairo_t *cr = cairo_create(cairo_surface);// 绘制背景 (淡蓝色)cairo_set_source_rgba(cr, 0.8, 0.9, 1.0, 1.0);cairo_paint(cr);// 绘制文字cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);cairo_set_font_size(cr, 40);cairo_text_extents_t extents;cairo_text_extents(cr, "Hello World!", &extents);cairo_move_to(cr, state->width/2.0 - extents.width/2.0, state->height/2.0);cairo_show_text(cr, "Hello World!");// 清理Cairo资源cairo_destroy(cr);cairo_surface_destroy(cairo_surface);// 将绘制好的缓冲区附加到表面wl_surface_attach(state->surface, state->buffer, 0, 0);// 告诉合成器表面的哪个区域被更新了 (这里是整个表面)wl_surface_damage_buffer(state->surface, 0, 0, state->width, state->height);// 提交更改,让合成器显示wl_surface_commit(state->surface);
}// --- xdg_toplevel 事件监听器 ---
static void xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *xdg_toplevel,int32_t width, int32_t height, struct wl_array *states) {struct state *state = data;if (width > 0 && height > 0) {state->width = width;state->height = height;}// 注意: 我们不在这里绘图,因为我们会在xdg_surface的configure事件后绘图
}static void xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) {struct state *state = data;// 合成器通知我们用户点击了关闭按钮state->running = 0;
}static const struct xdg_toplevel_listener xdg_toplevel_listener = {.configure = xdg_toplevel_handle_configure,.close = xdg_toplevel_handle_close,
};// --- xdg_surface 事件监听器 ---
static void xdg_surface_handle_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) {struct state *state = data;// 必须确认配置事件xdg_surface_ack_configure(xdg_surface, serial);// 在收到配置后,我们就可以绘图了draw_frame(state);
}static const struct xdg_surface_listener xdg_surface_listener = {.configure = xdg_surface_handle_configure,
};// --- xdg_wm_base 事件监听器 ---
static void xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) {// 客户端必须响应ping事件,否则合成器会认为客户端无响应xdg_wm_base_pong(xdg_wm_base, serial);
}static const struct xdg_wm_base_listener xdg_wm_base_listener = {.ping = xdg_wm_base_handle_ping,
};// --- wl_registry 事件监听器 ---
static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name,const char *interface, uint32_t version) {struct state *state = data;printf("Got a registry event for %s id %u\n", interface, name);if (strcmp(interface, wl_compositor_interface.name) == 0) {state->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4);} elseif (strcmp(interface, wl_shm_interface.name) == 0) {state->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);} elseif (strcmp(interface, xdg_wm_base_interface.name) == 0) {state->xdg_wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);xdg_wm_base_add_listener(state->xdg_wm_base, &xdg_wm_base_listener, state);}
}static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) {// This space is for rent
}static const struct wl_registry_listener registry_listener = {.global = registry_handle_global,.global_remove = registry_handle_global_remove,
};// 创建共享内存缓冲区
static int create_shm_buffer(struct state *state) {// 使用 memfd_create 创建一个匿名的、基于内存的文件char tmp_name[] = "/tmp/wayland-shm-XXXXXX";int fd = mkstemp(tmp_name);if (fd < 0) {fprintf(stderr, "mkstemp failed\n");return -1;}// 立即删除文件名,文件描述符依然有效unlink(tmp_name);int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, state->width);int size = stride * state->height;if (ftruncate(fd, size) < 0) {close(fd);fprintf(stderr, "ftruncate failed\n");return -1;}// 将文件映射到内存state->shm_data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (state->shm_data == MAP_FAILED) {close(fd);fprintf(stderr, "mmap failed\n");return -1;}// 从文件描述符创建Wayland共享内存池struct wl_shm_pool *pool = wl_shm_create_pool(state->shm, fd, size);state->buffer = wl_shm_pool_create_buffer(pool, 0, state->width, state->height, stride, WL_SHM_FORMAT_ARGB8888);wl_shm_pool_destroy(pool);close(fd);return 0;
}int main(int argc, char **argv) {struct state state = {0};state.width = 640;state.height = 480;state.running = 1;// 1. 连接到Wayland displaystate.display = wl_display_connect(NULL);if (state.display == NULL) {fprintf(stderr, "Can't connect to a Wayland display\n");return 1;}// 2. 获取registry,用于发现全局对象state.registry = wl_display_get_registry(state.display);wl_registry_add_listener(state.registry, &registry_listener, &state);// 3. 同步,等待服务器处理我们的请求并发送全局对象事件wl_display_dispatch(state.display);wl_display_roundtrip(state.display);// 检查是否成功绑定了必要的全局对象if (state.compositor == NULL || state.shm == NULL || state.xdg_wm_base == NULL) {fprintf(stderr, "Can't find compositor, shm or xdg_wm_base\n");return 1;}// 4. 创建Wayland表面state.surface = wl_compositor_create_surface(state.compositor);// 5. 通过xdg-shell将表面设置为toplevel窗口state.xdg_surface = xdg_wm_base_get_xdg_surface(state.xdg_wm_base, state.surface);xdg_surface_add_listener(state.xdg_surface, &xdg_surface_listener, &state);state.xdg_toplevel = xdg_surface_get_toplevel(state.xdg_surface);xdg_toplevel_add_listener(state.xdg_toplevel, &xdg_toplevel_listener, &state);// 设置窗口标题xdg_toplevel_set_title(state.xdg_toplevel, "Wayland Hello World");// 提交表面,让xdg-shell知道我们已经配置好了wl_surface_commit(state.surface);// 6. 创建共享内存缓冲区用于绘图if (create_shm_buffer(&state) < 0) {fprintf(stderr, "Failed to create shm buffer\n");return 1;}// 7. 主事件循环while (state.running && wl_display_dispatch(state.display) != -1) {// 事件处理都在监听器回调中完成}// 8. 清理资源printf("Cleaning up...\n");if (state.buffer) wl_buffer_destroy(state.buffer);if (state.xdg_toplevel) xdg_toplevel_destroy(state.xdg_toplevel);if (state.xdg_surface) xdg_surface_destroy(state.xdg_surface);if (state.surface) wl_surface_destroy(state.surface);if (state.xdg_wm_base) xdg_wm_base_destroy(state.xdg_wm_base);if (state.shm) wl_shm_destroy(state.shm);if (state.compositor) wl_compositor_destroy(state.compositor);if (state.registry) wl_registry_destroy(state.registry);if (state.display) wl_display_disconnect(state.display);return 0;
}

并且给出了详细的编译步骤。

  1. 安装依赖

    打开终端,输入以下命令安装所有必需的开发包:

    sudo apt update
    sudo apt install libwayland-dev libcairo2-dev cmake build-essential wayland-protocols
  2. 生成 xdg-shell 协议相关文件

    进入项目目录,执行以下命令:

    # 生成头文件
    wayland-scanner client-header /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml xdg-shell-client-protocol.h
    # 生成C源文件
    wayland-scanner private-code /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml xdg-shell-protocol.c

    生成的 xdg-shell-client-protocol.h 和 xdg-shell-protocol.c 文件请放到本项目目录下。

  3. 获取源码

    假设你已经有本项目的源码目录 WaylandClientDemo

  4. 编译程序

    在项目目录下执行:

    cd WaylandClientDemo
    mkdir build && cd build
    cmake ..
    make

    编译成功后会生成 hello-wayland-v1 可执行文件。

  5. 运行程序

    保证你当前在Wayland桌面环境下,执行:

    ./hello-wayland-v1

    程序会弹出一个窗口,显示“Hello Wayland”文字,10秒后自动关闭。

运行后会创建一个Wayland窗口,窗口中间显示“Hello Wayland”文字。


等等,为什么这个窗口程序不是我们常见的窗口样式,没有边框,没有标题,也没有最大化、最小化和关闭按钮?

这是由于在 Wayland 体系下,窗口的“装饰”(即边框、标题栏、按钮等)有两种方式:

1. 服务端装饰(SSD, Server Side Decoration)

  • 由合成器自动为顶层窗口(xdg_toplevel)添加装饰。

  • 但有些合成器默认不加装饰。

2. 客户端装饰(CSD, Client Side Decoration)

  • 由应用程序自己绘制装饰(如 GTK、Chrome 浏览器等)。

但这就造成一种尴尬的情形,客户端和服务器端都以为对方会绘制窗口装饰,结果都没有绘制。

为了解决这种矛盾,人们就想出了一个新方法,干脆协商一下吧,商量到底谁来绘制这个窗口装饰。于是 Wayland 定义了一个专门的扩展协议,叫 xdg-decoration(全名 xdg-decoration-unstable-v1),用于协商窗口装饰的方式。如果合成器支持该协议,客户端可以请求服务端为窗口添加装饰(SSD),否则需要自己绘制(CSD)。

第二个版本

将诉求丢给 AI 后,很快就出了第二个版本:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <cairo.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>// 包含我们刚刚生成的头文件
#include "xdg-shell-client-protocol.h"
#include "xdg-decoration-client-protocol.h" // <<< 引入新生成的头文件// 用于管理我们所有Wayland对象和状态的结构体
struct state {struct wl_display *display;struct wl_registry *registry;struct wl_compositor *compositor;struct wl_surface *surface;struct wl_shm *shm;struct xdg_wm_base *xdg_wm_base;struct xdg_surface *xdg_surface;struct xdg_toplevel *xdg_toplevel;// <<< 新增:用于xdg-decoration的对象struct zxdg_decoration_manager_v1 *decoration_manager;struct zxdg_toplevel_decoration_v1 *toplevel_decoration;struct wl_buffer *buffer;void *shm_data;int width, height;_Bool running;
};// 绘制函数
static void draw_frame(struct state *state) {int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, state->width);int size = stride * state->height;// 清空缓冲区内存memset(state->shm_data, 0, size);// 使用Cairo在共享内存上创建表面cairo_surface_t *cairo_surface = cairo_image_surface_create_for_data(state->shm_data, CAIRO_FORMAT_ARGB32, state->width, state->height, stride);cairo_t *cr = cairo_create(cairo_surface);// 绘制背景 (淡蓝色)cairo_set_source_rgba(cr, 0.8, 0.9, 1.0, 1.0);cairo_paint(cr);// 绘制文字cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);cairo_set_font_size(cr, 40);cairo_text_extents_t extents;cairo_text_extents(cr, "Hello World!", &extents);cairo_move_to(cr, state->width/2.0 - extents.width/2.0, state->height/2.0);cairo_show_text(cr, "Hello World!");// 清理Cairo资源cairo_destroy(cr);cairo_surface_destroy(cairo_surface);// 将绘制好的缓冲区附加到表面wl_surface_attach(state->surface, state->buffer, 0, 0);// 告诉合成器表面的哪个区域被更新了 (这里是整个表面)wl_surface_damage_buffer(state->surface, 0, 0, state->width, state->height);// 提交更改,让合成器显示wl_surface_commit(state->surface);
}// --- xdg_toplevel 事件监听器 ---
static void xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *xdg_toplevel,int32_t width, int32_t height, struct wl_array *states) {struct state *state = data;if (width > 0 && height > 0) {state->width = width;state->height = height;}// 注意: 我们不在这里绘图,因为我们会在xdg_surface的configure事件后绘图
}static void xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) {struct state *state = data;// 合成器通知我们用户点击了关闭按钮state->running = 0;
}static const struct xdg_toplevel_listener xdg_toplevel_listener = {.configure = xdg_toplevel_handle_configure,.close = xdg_toplevel_handle_close,
};// --- xdg_surface 事件监听器 ---
static void xdg_surface_handle_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) {struct state *state = data;// 必须确认配置事件xdg_surface_ack_configure(xdg_surface, serial);// 在收到配置后,我们就可以绘图了draw_frame(state);
}static const struct xdg_surface_listener xdg_surface_listener = {.configure = xdg_surface_handle_configure,
};// --- xdg_wm_base 事件监听器 ---
static void xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) {// 客户端必须响应ping事件,否则合成器会认为客户端无响应xdg_wm_base_pong(xdg_wm_base, serial);
}static const struct xdg_wm_base_listener xdg_wm_base_listener = {.ping = xdg_wm_base_handle_ping,
};// <<< 新增:xdg_toplevel_decoration 的事件监听器
static void decoration_handle_configure(void *data,struct zxdg_toplevel_decoration_v1 *decoration,uint32_t mode)
{// 这是协商的核心!合成器通过这个事件告诉我们它最终决定的装饰模式。printf("==> Compositor negotiated decoration mode: ");switch (mode) {case ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE:printf("Client-Side (we must draw our own!)\n");break;case ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE:printf("Server-Side (compositor will draw for us!)\n");break;default:printf("Unknown\n");break;}
}static const struct zxdg_toplevel_decoration_v1_listener decoration_listener = {.configure = decoration_handle_configure,
};// --- wl_registry 事件监听器 ---
static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name,const char *interface, uint32_t version) {struct state *state = data;// <<< 我们现在打印所有接口,方便调试printf("Found global: %s (version %u)\n", interface, version);if (strcmp(interface, wl_compositor_interface.name) == 0) {state->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4);} elseif (strcmp(interface, wl_shm_interface.name) == 0) {state->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);} elseif (strcmp(interface, xdg_wm_base_interface.name) == 0) {state->xdg_wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);xdg_wm_base_add_listener(state->xdg_wm_base, &xdg_wm_base_listener, state);}// <<< 新增:检查合成器是否支持 xdg-decoration 协议elseif (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) {state->decoration_manager = wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, 1);}
}static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) {// This space is for rent
}static const struct wl_registry_listener registry_listener = {.global = registry_handle_global,.global_remove = registry_handle_global_remove,
};// 创建共享内存缓冲区
static int create_shm_buffer(struct state *state) {// 使用 memfd_create 创建一个匿名的、基于内存的文件char tmp_name[] = "/tmp/wayland-shm-XXXXXX";int fd = mkstemp(tmp_name);if (fd < 0) {fprintf(stderr, "mkstemp failed\n");return -1;}// 立即删除文件名,文件描述符依然有效unlink(tmp_name);int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, state->width);int size = stride * state->height;if (ftruncate(fd, size) < 0) {close(fd);fprintf(stderr, "ftruncate failed\n");return -1;}// 将文件映射到内存state->shm_data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (state->shm_data == MAP_FAILED) {close(fd);fprintf(stderr, "mmap failed\n");return -1;}// 从文件描述符创建Wayland共享内存池struct wl_shm_pool *pool = wl_shm_create_pool(state->shm, fd, size);state->buffer = wl_shm_pool_create_buffer(pool, 0, state->width, state->height, stride, WL_SHM_FORMAT_ARGB8888);wl_shm_pool_destroy(pool);close(fd);return 0;
}int main(int argc, char **argv) {struct state state = {0};state.width = 640;state.height = 480;state.running = 1;// 1. 连接到Wayland displaystate.display = wl_display_connect(NULL);if (state.display == NULL) {fprintf(stderr, "Can't connect to a Wayland display\n");return 1;}// 2. 获取registry,用于发现全局对象state.registry = wl_display_get_registry(state.display);wl_registry_add_listener(state.registry, &registry_listener, &state);// 3. 同步,等待服务器处理我们的请求并发送全局对象事件wl_display_dispatch(state.display);wl_display_roundtrip(state.display);// 检查是否成功绑定了必要的全局对象if (state.compositor == NULL || state.shm == NULL || state.xdg_wm_base == NULL) {fprintf(stderr, "Can't find compositor, shm or xdg_wm_base\n");return 1;}// 4. 创建Wayland表面state.surface = wl_compositor_create_surface(state.compositor);// 5. 通过xdg-shell将表面设置为toplevel窗口state.xdg_surface = xdg_wm_base_get_xdg_surface(state.xdg_wm_base, state.surface);xdg_surface_add_listener(state.xdg_surface, &xdg_surface_listener, &state);state.xdg_toplevel = xdg_surface_get_toplevel(state.xdg_surface);xdg_toplevel_add_listener(state.xdg_toplevel, &xdg_toplevel_listener, &state);// 设置窗口标题xdg_toplevel_set_title(state.xdg_toplevel, "Wayland Hello World");// <<< 新增:进行装饰协商if (state.decoration_manager) {printf("Decoration manager found. Negotiating...\n");// 为我们的窗口获取一个装饰对象state.toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(state.decoration_manager, state.xdg_toplevel);// 添加监听器以接收合成器的决定zxdg_toplevel_decoration_v1_add_listener(state.toplevel_decoration, &decoration_listener, &state);// *明确地*请求服务器端装饰zxdg_toplevel_decoration_v1_set_mode(state.toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);} else {printf("Decoration manager not found. Cannot negotiate decorations.\n");}// 提交表面,让xdg-shell知道我们已经配置好了wl_surface_commit(state.surface);// 6. 创建共享内存缓冲区用于绘图if (create_shm_buffer(&state) < 0) {fprintf(stderr, "Failed to create shm buffer\n");return 1;}// 7. 主事件循环while (state.running && wl_display_dispatch(state.display) != -1) {// 事件处理都在监听器回调中完成}// 8. 清理资源printf("Cleaning up...\n");// <<< 新增:清理装饰相关的对象if (state.toplevel_decoration) zxdg_toplevel_decoration_v1_destroy(state.toplevel_decoration);if (state.decoration_manager) zxdg_decoration_manager_v1_destroy(state.decoration_manager);if (state.buffer) wl_buffer_destroy(state.buffer);if (state.xdg_toplevel) xdg_toplevel_destroy(state.xdg_toplevel);if (state.xdg_surface) xdg_surface_destroy(state.xdg_surface);if (state.surface) wl_surface_destroy(state.surface);if (state.xdg_wm_base) xdg_wm_base_destroy(state.xdg_wm_base);if (state.shm) wl_shm_destroy(state.shm);if (state.compositor) wl_compositor_destroy(state.compositor);if (state.registry) wl_registry_destroy(state.registry);if (state.display) wl_display_disconnect(state.display);return 0;
}

典型流程

  1. 客户端通过 wl_registry 获取 zxdg_decoration_manager_v1

  2. 用 zxdg_decoration_manager_v1 为 xdg_toplevel 创建 decoration 对象。

  3. 调用 zxdg_toplevel_decoration_v1_request_mode() 请求 SSD。

  4. 合成器响应,决定是否提供装饰。

运行效果图如下:

是一个正常的窗口,和我们的预期相符。然而,这个程序在 Ubuntu 24.04 下运行的效果图:


仍然没有窗口装饰。其实从 xdg-decoration 扩展的文件名 xdg-decoration-unstable-v1.xml 就可以看出,该扩展协议还处在 unstable 状态,然后 Ubuntu 使用的合成器 Mutter 没实现该扩展协议。这种情况下,就需要 Wayland 客户端来绘制。

小结

本文通过手搓 Wayland 客户端的实践,带你从零体验了 Wayland 协议下窗口程序的开发流程。我们首先实现了一个最基础的“Hello Wayland”窗口,随后又引入了 xdg-decoration 协议,尝试与合成器协商窗口装饰的绘制方式。通过实际运行和对比不同环境下的效果,你可以直观感受到 Wayland 生态中窗口装饰的多样性与复杂性。

Wayland 的窗口装饰机制分为服务端装饰(SSD)和客户端装饰(CSD),而 xdg-decoration 协议则是二者协商的桥梁。但由于协议本身还处于 unstable 阶段,不同合成器的支持情况并不一致,导致实际效果会有差异。比如在 Ubuntu 24.04 的 Mutter 合成器下,仍需客户端自行绘制装饰。

总之,Wayland 生态仍在不断发展,协议和实现也在持续完善。对于开发者来说,理解其原理和流程,有助于更好地适配和优化自己的应用。希望本文的代码和讲解,能为你打开 Wayland 世界的一扇窗。

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

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

相关文章

Golang JSON 标准库用法详解

JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式&#xff0c;Go语言的标准库encoding/json提供了强大的JSON处理能力。下面我将详细介绍各种用法并提供示例代码。 1. 基本编码&#xff08;Marshal&#xff09; 将Go数据结构转换为JSON字符串。 package maini…

Day.42

hook函数&#xff1a; import torch import torch.nn as nn import numpy as np import matplotlib.pyplot as plt torch.manual_seed(42) np.random.seed(42) 张量钩子&#xff1a; x torch.tensor([2.0], requires_gradTrue) y x ** 2 z y ** 3 def tensor_hook…

【.net core】【sqlsugar】在where条件查询时使用原生SQL

//初始化查询 var query repository.IQueryable();//添加原生SQL WHERE条件 query query.Where(" fieldA < 123"); 对应调用ISugarQueryable接口类中&#xff1a; ISugarQueryable<T> Where(string whereString, object parameters null);

网络 : 传输层【TCP协议】

网络 : 传输层【TCP协议】 一、TCP协议段格式1.1 32位序号与确认号1.1.1 32位序号1.1.2 确认号 1.2 4位首部长度1.3 6位标志位1.4 16位窗口大小 二、确认应答(ACK)机制三、超时重传机制四、连接管理机制4.1 三次握手(连接)listen的第二个参数 4.2 四次挥手(断开连接)**TIME_WAI…

人大金仓Kingbase数据库 Ksql: 未找到命令

人大金仓Kingbase数据库 Ksql: 未找到命令 1. 定位 Kingbase 安装目录 Kingbase 数据库通常安装在 /kingbase/ES/V8/Server 目录下。可以通过以下命令定位&#xff1a; cd /kingbase/ES/V8/Server2. 验证 ksql 工具是否安装成功 执行以下命令检查 ksql 客户端工具的版本信息…

Flask(四) 模板渲染render_template

文章目录 &#x1f4e6; 过程详解&#xff08;路由 <-> HTML 模板&#xff09;&#x1f9e0; 数据是怎么传过去的&#xff1f;多变量示例 ✅ Jinja2 支持条件判断、循环、模板继承&#xff1a;✅ 安全性&#x1f512; Flask 默认也会对变量进行 HTML 转义&#xff1a;&am…

[附源码+数据库+毕业论文+开题报告]基于Spring+MyBatis+MySQL+Maven+jsp实现的宠物领养管理系统,推荐!

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对宠物领养信息管理混乱&#xff0c;出错率高&#xff0c;信息安全性差…

【ArcGIS】水资源单项评价

【ArcGIS】水资源单项评价 一、水资源单项评价1、评价思路 二、操作步骤1、处理环境设置2、数据处理3、要素转栅格4、水资源评价 一、水资源单项评价 1、评价思路 &#xff08;1&#xff09;省级层面宜选用四级/五级水资源分区或县级行政区为评价单元&#xff0c;按照水资源总…

Windows环境下C语言汇编语言编辑器及环境安装

安装MinGw&#xff1a; 1.下载安装文件 MinGW - 适用于 Windows 的极简主义 GNU 下载 |SourceForge.net 点击下载 下载之后就是如下图的安装文件 2.安装 双击安装文件进行安装&#xff0c;点击Install下一步 选择安装位置&#xff0c;默认是安装在C盘&#xff0c;点击Change…

【数据分析】分段逻辑回归示例分析(模拟数据)

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包步骤 1:模拟数据步骤 2:构建逻辑回归和分段模型步骤 3:计算预测值和置信区间步骤 4:提取 OR 和统计值步骤 5:绘图展示结步骤 6:输出图片原始代码总结系统信息参考介绍…

Webpack 构建过程详解

Webpack 是一个功能强大的模块打包工具,它能够将项目中的各种资源(如 JavaScript、CSS、图片等)打包成一个或多个文件,以便于在浏览器中运行。本文将以 Webpack 5 为例介绍它的构建过程: 1. 初始化阶段 在这个阶段,Webpack 从配置文件和命令行参数中读取并解析配置。然…

Flutter基础(UI监听)

文本按钮&#xff08;TextButton&#xff09; 文本按钮是没有边框的按钮&#xff0c;当点击时会有涟漪效果。 TextButton(onPressed: () {// 点击按钮后要执行的代码print(文本按钮被点击了);},child: Text(点击我), ) 手势检测器&#xff08;GestureDetector&#xff09; …

Linux 下的 regulator 子系统

1、简介 regulator 框架是 Linux 内核中用于管理电压和电流调节器&#xff08;如 LDO、DCDC 转换器等&#xff09;的一个子系统。它提供了一个抽象层&#xff0c;使得驱动程序和内核的其他部分可以以一致的方式与调节器进行交互&#xff0c;而无需了解底层硬件的细节。 主要功能…

12345政务热线系统:接诉即办,赋能智慧城市治理

一、12345热线&#xff1a;民情直通车&#xff0c;治理新引擎 “12345”政务热线是党委政府了解社情民意、解决群众合理诉求、倾听批评建议、改进工作作风的重要渠道。当前&#xff0c;全国各城市已基本建成12345政务服务热线体系&#xff0c;形成“接诉即办”的高效响应机制。…

【SpringBoot核心】Spring Boot + MyBatis 深度整合与最佳实践

目录 引言Spring Boot 基础回顾MyBatis 核心概念解析Spring Boot 整合 MyBatisMyBatis 高级特性Spring Boot + MyBatis 最佳实践性能优化与扩展实战案例:电商系统开发常见问题与解决方案总结与展望1. 引言 1.1 技术背景与现状 在现代企业级应用开发中,数据持久化是一个核心…

力扣第77题-组合-力扣第78题-子集

力扣链接:77. 组合 - 力扣&#xff08;LeetCode&#xff09; 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;n 4, k 2 输出&#xff1a; [[2,4],[3,4],[2,3],[1,2],[1,3…

嵌入式MTD设备与Flash管理解析

理解MTD是嵌入式系统中处理Flash存储的关键一步&#xff01;我来帮你梳理清楚&#xff1a; MTD 是什么&#xff1f; MTD 是 Memory Technology Device 的缩写&#xff0c;中文常译为内存技术设备。它是 Linux 内核及其衍生系统&#xff08;如嵌入式 Linux&#xff09;中用于管…

基于 GEE 利用 Sentinel-2 数据计算并下载植被指数数据

目录 1 植被指数 2 完整代码 3 运行结果 1 植被指数 植被指数全名NDVI归一化差值植被指数GNDVI绿色归一化差值植被指数EVI增强植被指数EVI2双波段增强植被指数DVI差值植被指数GDVI绿色差植被值指数RVI比值植被指数SAVI土壤调整植被指数OSAVI优化土壤调整植被指数MSAVI修改…

python基础23(2025.6.29)分布式爬虫(增量式爬虫去重)redis应用_(未完成!)

本次写一个爬取网易新闻的案例。因为redis能处理高并发&#xff0c;存储数据也可以&#xff0c;故不用mysql。而且新闻网站容易更新很多&#xff0c;而mysql只能持久化存储。 import scrapy import re import json import redis # 用它来去除重复, 记录访问过的urlclass Wang…

Springboot 集成 SpringState 状态机

Springboot 集成 SpringState 状态机 1.SpringState 简介2.状态机示例2.1 项目结构和依赖包2.2 定义事件类和状态类2.3 Spring 事件监听器2.4 状态机持久化类2.4.1 Redis 状态机持久化容器2.4.2 Redis 配置2.4.3 状态机监听器 2.5 装机器容器2.6 状态机事件发送器2.7 状态机配置…