百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT知识 > 正文

10. 句柄(Handle)和请求(Request)- 一

liuian 2025-03-04 13:07 7 浏览

句柄和请求在 libuv 中的地位

libuv 是一个跨平台的异步 I/O 库,被广泛应用于 Node.js 等项目中,为开发者提供了高效处理各种 I/O 操作的能力。在 libuv 的架构中,句柄(Handle)和请求(Request)是两个核心概念,它们构成了整个异步操作的基础。句柄代表了一个持久的实体,如网络套接字、定时器等,而请求则表示一个一次性的操作,如文件读取、网络连接等。可以说,句柄和请求是 libuv 实现异步 I/O 功能的关键组件,贯穿了整个库的使用过程。

句柄和请求的核心作用

句柄的核心作用是管理和维护与特定资源的关联,为开发者提供了对这些资源进行操作的接口。通过句柄,开发者可以方便地对网络连接、文件系统、定时器等资源进行创建、启动、停止和关闭等操作。例如,使用 TCP 句柄可以建立和管理 TCP 连接,使用定时器句柄可以实现定时任务。

请求的核心作用是执行具体的异步操作。当需要进行某个特定的操作时,开发者可以创建一个请求对象,并将其提交给 libuv 的事件循环。libuv 会在后台处理这些请求,并在操作完成后通过回调函数通知开发者。这种异步处理方式避免了阻塞主线程,提高了程序的并发性能。例如,使用文件系统请求可以异步地打开、读取和写入文件。

与 libuv 整体架构的关系

libuv 的整体架构基于事件循环机制,事件循环负责监听各种 I/O 事件,并在事件发生时调用相应的回调函数。句柄和请求是与事件循环紧密协作的组件。句柄通常会注册到事件循环中,当与之关联的资源发生事件时,事件循环会触发相应的句柄回调函数。请求则是在事件循环中排队等待处理,当请求完成时,事件循环会调用请求的完成回调函数。因此,句柄和请求是 libuv 事件循环机制的重要组成部分,它们共同实现了 libuv 的异步 I/O 功能。

  1. 句柄(Handle)基础
  1. 句柄的概念

什么是句柄

在编程中,句柄是一个抽象的概念,通常表示对某个资源的引用或标识。在 libuv 中,句柄是一个对象,它封装了与特定资源的交互。这些资源可以是网络套接字、文件描述符、定时器等。通过句柄,开发者可以对这些资源进行操作,而不需要关心底层的具体实现细节。

句柄在 libuv 中的抽象意义

在 libuv 中,句柄的抽象意义在于提供了一种统一的方式来管理和操作不同类型的资源。无论资源是网络、文件系统还是定时器,都可以通过句柄来进行操作。这种抽象使得开发者可以编写更加通用和可移植的代码,同时也提高了代码的可维护性。例如,开发者可以使用相同的接口来启动和停止不同类型的句柄,而不需要考虑具体的资源类型。

  1. uv_handle_t 结构体剖析

结构体的成员变量

uv_handle_t 是 libuv 中所有句柄类型的基类,其定义如下:

struct uv_handle_s {
  UV_HANDLE_FIELDS
};

我们这里把UV_HANDLE_FIELDS全部展开:

struct uv_handle_s {
  /* public */
  void* data;
  /* read-only */
  uv_loop_t* loop;
  uv_handle_type type;
  /* private */
  uv_close_cb close_cb;
  struct uv__queue handle_queue;
  uv_handle_t* next_closing;
  unsigned int flags;
};

其中,UV_HANDLE_FIELDS 是一个宏,展开后包含了一些重要的成员变量,主要包括:

  • uv_loop_t* loop:指向该句柄所属的事件循环。每个句柄都必须与一个事件循环关联,事件循环负责管理句柄的生命周期和事件处理。
  • uv_handle_type type:表示句柄的类型,如 UV_TCP、UV_UDP 等。通过这个字段可以区分不同类型的句柄。
  • void* data:一个通用的指针,开发者可以使用它来存储自定义的数据。例如,可以将一个结构体指针赋值给 data 字段,以便在句柄的回调函数中访问该结构体的数据。
  • uv_close_cb close_cb:关闭句柄时调用的回调函数。当调用 uv_close 函数关闭句柄时,会在句柄关闭完成后调用该回调函数。
  1. 常见句柄类型
  1. TCP 句柄(uv_tcp_t)

用于 TCP 网络通信

在 libuv 中,uv_tcp_t 句柄用于实现基于 TCP 协议的网络通信。TCP(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,广泛应用于需要保证数据准确传输的场景,如 Web 服务、邮件服务等。uv_tcp_t 句柄为开发者提供了一套统一的接口,使得开发者可以方便地在不同操作系统上实现 TCP 客户端和服务器程序,同时利用 libuv 的异步 I/O 特性提高程序的并发性能。

  1. 结构体剖析

uv_tcp_t 结构体继承自 uv_stream_t,而 uv_stream_t 又继承自 uv_handle_t。以下是 uv_tcp_t 结构体的大致关系和关键成员的分析:

#define UV_STREAM_FIELDS                                                      \
  /* number of bytes queued for writing */                                    \
  size_t write_queue_size;                                                    \
  uv_alloc_cb alloc_cb;                                                       \
  uv_read_cb read_cb;                                                         \
  /* private */                                                               \
  UV_STREAM_PRIVATE_FIELDS

typedef struct uv_tcp_s {
    UV_HANDLE_FIELDS
    UV_STREAM_FIELDS
} uv_tcp_t;
  • 继承自 uv_handle_t 的成员
  • uv_loop_t* loop:指向该句柄所属的事件循环。事件循环负责管理句柄的生命周期和处理相关的 I/O 事件。
  • uv_handle_type type:表示句柄的类型,对于 uv_tcp_t 句柄,其类型为 UV_TCP。
  • void* data:一个通用的指针,开发者可以用它来存储自定义的数据,方便在句柄的回调函数中访问。
  • uv_close_cb close_cb:关闭句柄时调用的回调函数,用于执行清理操作。
  • 继承自 uv_stream_t 的成员
  • uv_alloc_cb alloc_cb:分配缓冲区的回调函数,在进行数据读取时,libuv 会调用该函数来分配合适的缓冲区。
  • uv_read_cb read_cb:数据读取完成后的回调函数,当有数据可读时,libuv 会调用该函数将读取到的数据传递给开发者。
  • uv_write_cb write_cb:数据写入完成后的回调函数,当数据写入操作完成后,libuv 会调用该函数通知开发者操作结果。
  • UV_STREAM_PRIVATE_FIELDS 定义的其他字段(unix版本)
  • uv_connect_t *connect_req;
    • 用途:该字段是一个指向 uv_connect_t 结构体的指针。uv_connect_t 结构体用于表示一个连接请求,主要在发起 TCP 或 UDP 连接时使用。当使用 uv_tcp_connect 或 uv_udp_connect 等函数发起连接请求时,会创建一个 uv_connect_t 类型的请求对象,并将其指针赋值给该字段。通过这个指针,可以跟踪连接请求的状态和结果。
    • 示例场景:在编写 TCP 客户端程序时,使用 uv_tcp_connect 发起连接请求,请求对象会与流句柄关联,方便后续处理连接完成后的回调操作。
  • uv_shutdown_t *shutdown_req;
  • 用途:这是一个指向 uv_shutdown_t 结构体的指针。uv_shutdown_t 结构体用于表示一个关闭流的请求,通常用于优雅地关闭 TCP 连接。调用 uv_shutdown 函数时,会创建一个 uv_shutdown_t 类型的请求对象,并将其指针赋值给该字段。该字段允许跟踪关闭请求的状态,确保在关闭操作完成后执行相应的清理工作。
  • 示例场景:在需要关闭 TCP 连接时,使用 uv_shutdown 函数发起关闭请求,通过该指针可以在关闭完成后进行资源释放等操作。
  • uv__io_t io_watcher;
  • 用途:uv__io_t 是 libuv 内部用于处理 I/O 事件的结构体。该字段作为流句柄的 I/O 观察者,负责监听与流相关的 I/O 事件,如可读、可写等。libuv 利用底层的 I/O 多路复用机制(如 epoll、kqueue 或 IOCP)来管理这些事件,io_watcher 结构体将流句柄与这些底层机制关联起来。
  • 示例场景:当流有数据可读时,io_watcher 会检测到可读事件,并触发相应的回调函数,通知应用程序进行数据读取操作。
  • struct uv__queue write_queue;
  • 用途:uv__queue 是 libuv 内部实现的一个队列结构。write_queue 用于存储待写入流的数据请求。当调用 uv_write 函数发送数据时,请求会被添加到这个队列中,libuv 会按照队列的顺序依次处理这些写入请求。
  • 示例场景:在高并发的网络应用中,多个线程或协程可能同时调用 uv_write 函数发送数据,这些请求会被排队到 write_queue 中,确保数据按顺序写入流中。
  • struct uv__queue write_completed_queue;
  • 用途:同样基于 uv__queue 队列结构,write_completed_queue 用于存储已经完成的写入请求。当一个写入请求完成后,它会从 write_queue 中移除,并被添加到 write_completed_queue 中。这样,应用程序可以在适当的时候从这个队列中获取完成的请求,并执行相应的回调函数,如释放缓冲区等。
  • 示例场景:在数据写入完成后,通过遍历 write_completed_queue 队列,调用每个完成请求的回调函数,清理相关资源。
  • uv_connection_cb connection_cb;
  • 用途:uv_connection_cb 是一个回调函数类型的字段。对于服务器端的流句柄(如 TCP 服务器),当有新的连接到来时,libuv 会调用这个回调函数。该回调函数的原型为 void (*uv_connection_cb)(uv_stream_t* server, int status);,其中 server 是服务器的流句柄,status 表示连接的状态。
  • 示例场景:在编写 TCP 服务器程序时,通过设置 connection_cb 回调函数,当有新的客户端连接时,可以在回调函数中接受连接并处理新连接的逻辑。
  • int delayed_error;
  • 用途:该字段用于存储延迟的错误信息。在某些情况下,流操作可能无法立即检测到错误,需要延迟到后续处理。delayed_error 会记录这些延迟的错误码,在适当的时候通知应用程序。
  • 示例场景:在进行异步 I/O 操作时,可能会因为某些原因(如网络拥塞)无法立即确定操作是否成功,此时可以将错误码存储在 delayed_error 中,在后续的回调中处理。
  • int accepted_fd;
  • 用途:对于服务器端的流句柄,当接受一个新的连接时,accepted_fd 会存储新连接的文件描述符(在 Unix 系统中)或套接字描述符(在 Windows 系统中)。这个字段主要用于内部处理,方便 libuv 管理新连接的资源。
  • 示例场景:在 uv_accept 函数接受新连接后,accepted_fd 会保存新连接的描述符,后续可以基于这个描述符进行数据读写等操作。
  • void* queued_fds;
  • 用途:该字段是一个通用指针,用于存储排队的文件描述符或套接字描述符。在某些情况下,可能会有多个连接请求同时到来,这些请求的描述符可以存储在这个指针指向的队列中,以便后续处理。
  • 示例场景:在高并发的服务器应用中,当大量客户端同时发起连接请求时,这些请求的描述符可以被临时存储在 queued_fds 指向的队列中,按顺序进行处理。
  1. 常见操作及示例代码

初始化

在使用 uv_tcp_t 句柄之前,需要对其进行初始化。可以使用 uv_tcp_init 函数来完成初始化操作,该函数将句柄与指定的事件循环关联起来。

#include 
#include 

uv_loop_t* loop;
uv_tcp_t tcp_handle;

int main() {
    loop = uv_default_loop();
    // 初始化 TCP 句柄
    int r = uv_tcp_init(loop, &tcp_handle);
    if (r) {
        fprintf(stderr, "TCP handle initialization error: %s\n", uv_strerror(r));
        return 1;
    }
    // 后续操作...
    return uv_run(loop, UV_RUN_DEFAULT);
}

绑定地址

对于 TCP 服务器,需要将句柄绑定到指定的 IP 地址和端口上。可以使用 uv_tcp_bind 函数来完成绑定操作。

#include 
#include 

uv_loop_t* loop;
uv_tcp_t tcp_handle;

int main() {
    loop = uv_default_loop();
    uv_tcp_init(loop, &tcp_handle);

    struct sockaddr_in addr;
    // 将 IP 地址和端口转换为 sockaddr_in 结构体
    uv_ip4_addr("0.0.0.0", 8080, &addr);

    // 绑定地址
    int r = uv_tcp_bind(&tcp_handle, (const struct sockaddr*)&addr, 0);
    if (r) {
        fprintf(stderr, "TCP bind error: %s\n", uv_strerror(r));
        return 1;
    }
    // 后续操作...
    return uv_run(loop, UV_RUN_DEFAULT);
}

监听连接

绑定地址后,服务器需要开始监听客户端的连接请求。可以使用 uv_listen 函数来启动监听操作,当有新的连接到来时,会调用指定的回调函数。

#include 
#include 

uv_loop_t* loop;
uv_tcp_t tcp_handle;

void on_new_connection(uv_stream_t* server, int status) {
    if (status < 0) {
        fprintf(stderr, "New connection error: %s\n", uv_strerror(status));
        return;
    }
    printf("New connection received\n");
    // 处理新连接的逻辑...
}

int main() {
    loop = uv_default_loop();
    uv_tcp_init(loop, &tcp_handle);

    struct sockaddr_in addr;
    uv_ip4_addr("0.0.0.0", 8080, &addr);

    uv_tcp_bind(&tcp_handle, (const struct sockaddr*)&addr, 0);

    // 开始监听连接,最大连接队列长度为 128
    int r = uv_listen((uv_stream_t*)&tcp_handle, 128, on_new_connection);
    if (r) {
        fprintf(stderr, "TCP listen error: %s\n", uv_strerror(r));
        return 1;
    }
    return uv_run(loop, UV_RUN_DEFAULT);
}

接受连接

在 on_new_connection 回调函数中,需要接受新的连接并创建一个新的 uv_tcp_t 句柄来处理该连接。可以使用 uv_accept 函数来接受连接。

#include 
#include 
#include 

uv_loop_t* loop;
uv_tcp_t tcp_handle;

void on_new_connection(uv_stream_t* server, int status) {
    if (status < 0) {
        fprintf(stderr, "New connection error: %s\n", uv_strerror(status));
        return;
    }

    // 创建一个新的 TCP 句柄来处理新连接
    uv_tcp_t* client = (uv_tcp_t*)malloc(sizeof(uv_tcp_t));
    uv_tcp_init(loop, client);

    // 接受连接
    if (uv_accept(server, (uv_stream_t*)client) == 0) {
        printf("New connection accepted\n");
        // 可以开始读取客户端数据或进行其他操作
    } else {
        // 接受连接失败,关闭客户端句柄
        uv_close((uv_handle_t*)client, NULL);
        free(client);
    }
}

int main() {
    loop = uv_default_loop();
    uv_tcp_init(loop, &tcp_handle);

    struct sockaddr_in addr;
    uv_ip4_addr("0.0.0.0", 8080, &addr);

    uv_tcp_bind(&tcp_handle, (const struct sockaddr*)&addr, 0);

    int r = uv_listen((uv_stream_t*)&tcp_handle, 128, on_new_connection);
    if (r) {
        fprintf(stderr, "TCP listen error: %s\n", uv_strerror(r));
        return 1;
    }
    return uv_run(loop, UV_RUN_DEFAULT);
}

发起连接(客户端)

对于 TCP 客户端,需要发起连接请求。可以使用 uv_tcp_connect 函数来发起连接,连接成功或失败后会调用指定的回调函数。

#include 
#include 

uv_loop_t* loop;
uv_tcp_t tcp_handle;
uv_connect_t connect_req;

void connect_callback(uv_connect_t* req, int status) {
    if (status < 0) {
        fprintf(stderr, "Connect error: %s\n", uv_strerror(status));
    } else {
        printf("Connected to server\n");
        // 连接成功,可以开始发送或接收数据
    }
}

int main() {
    loop = uv_default_loop();
    uv_tcp_init(loop, &tcp_handle);

    struct sockaddr_in addr;
    uv_ip4_addr("127.0.0.1", 8080, &addr);

    // 发起连接请求
    uv_tcp_connect(&connect_req, &tcp_handle, (const struct sockaddr*)&addr, connect_callback);

    return uv_run(loop, UV_RUN_DEFAULT);
}

数据读写

连接建立后,客户端和服务器可以进行数据的读写操作。可以使用 uv_read_start 函数开始读取数据,使用 uv_write 函数发送数据。

读取数据示例

#include 
#include 
#include 

uv_loop_t* loop;
uv_tcp_t tcp_handle;

void alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
    buf->base = (char*)malloc(suggested_size);
    buf->len = suggested_size;
}

void read_callback(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
    if (nread > 0) {
        printf("Read %zd bytes: %.*s\n", nread, (int)nread, buf->base);
    } else if (nread < 0) {
        if (nread != UV_EOF) {
            fprintf(stderr, "Read error: %s\n", uv_strerror(nread));
        }
        // 关闭句柄
        uv_close((uv_handle_t*)stream, NULL);
    }
    free(buf->base);
}

int main() {
    loop = uv_default_loop();
    uv_tcp_init(loop, &tcp_handle);

    // 假设已经建立连接...

    // 开始读取数据
    uv_read_start((uv_stream_t*)&tcp_handle, alloc_buffer, read_callback);

    return uv_run(loop, UV_RUN_DEFAULT);
}

发送数据示例

#include 
#include 
#include 

uv_loop_t* loop;
uv_tcp_t tcp_handle;
uv_write_t write_req;

void write_callback(uv_write_t* req, int status) {
    if (status < 0) {
        fprintf(stderr, "Write error: %s\n", uv_strerror(status));
    } else {
        printf("Data written successfully\n");
    }
}

int main() {
    loop = uv_default_loop();
    uv_tcp_init(loop, &tcp_handle);

    // 假设已经建立连接...

    const char* data = "Hello, server!";
    uv_buf_t buf = uv_buf_init((char*)data, strlen(data));

    // 发送数据
    uv_write(&write_req, (uv_stream_t*)&tcp_handle, &buf, 1, write_callback);

    return uv_run(loop, UV_RUN_DEFAULT);
}

注意事项

  • 资源管理:在使用 uv_tcp_t 句柄时,要注意资源的正确管理。例如,在创建新的句柄后,要确保在不再使用时调用 uv_close 函数关闭句柄,并在关闭回调函数中释放相关的资源。对于动态分配的缓冲区,要在使用完后及时释放,避免内存泄漏。
  • 错误处理:在进行 TCP 通信时,可能会出现各种错误,如连接失败、读写错误等。在每个操作的回调函数中,要检查返回的状态码,根据状态码进行相应的错误处理,输出详细的错误信息,方便调试和定位问题。
  • 并发处理:由于 libuv 是基于异步 I/O 的,在处理多个连接时,要考虑并发问题。例如,在多线程环境下,要确保对共享资源的访问是线程安全的,可以使用锁机制来保证数据的一致性。
  1. UDP 句柄(uv_udp_t)

用于 UDP 网络通信

在 libuv 中,uv_udp_t是实现 UDP(User Datagram Protocol)网络通信的关键句柄。UDP 作为一种无连接的传输协议,与 TCP 不同,它不保证数据的可靠传输、顺序到达以及数据完整性 ,但它的优势在于传输速度快、延迟低,常用于对实时性要求高、能容忍少量数据丢失的场景,比如视频会议、实时游戏、直播流等。uv_udp_t句柄提供了跨平台的统一接口,让开发者可以方便地在不同操作系统上进行 UDP 通信,借助 libuv 的异步 I/O 机制,能高效处理大量 UDP 数据报,极大提升了应用程序的性能和响应速度。

与 TCP 句柄的区别

  • 连接方式:TCP 是面向连接的,在进行数据传输之前需要建立连接;而 UDP 是无连接的,不需要建立连接就可以直接发送数据。
  • 可靠性:TCP 提供可靠的数据传输,通过确认机制和重传机制保证数据的完整性和顺序;而 UDP 不保证数据的可靠到达,可能会出现数据丢失、乱序等问题。
  • 性能:由于 TCP 需要建立连接和维护状态,因此开销相对较大;而 UDP 不需要这些操作,开销较小,传输速度较快。
  1. 结构体剖析

uv_udp_t结构体继承自uv_handle_t,其定义包含了一系列成员,用于管理和实现 UDP 通信:


#define UV_UDP_PRIVATE_FIELDS                                                 \
  uv_alloc_cb alloc_cb;                                                       \
  uv_udp_recv_cb recv_cb;                                                     \
  uv__io_t io_watcher;                                                        \
  struct uv__queue write_queue;                                               \
  struct uv__queue write_completed_queue;                                     \

/* uv_udp_t is a subclass of uv_handle_t. */
struct uv_udp_s {
  UV_HANDLE_FIELDS
  /* read-only */
  /*
   * Number of bytes queued for sending. This field strictly shows how much
   * information is currently queued.
   */
  size_t send_queue_size;
  /*
   * Number of send requests currently in the queue awaiting to be processed.
   */
  size_t send_queue_count;
  UV_UDP_PRIVATE_FIELDS
};

继承自uv_handle_t的成员

  • uv_loop_t* loop:指向所属的事件循环,事件循环负责调度和管理句柄的整个生命周期,以及处理与之相关的 I/O 事件,是句柄运作的核心环境。
  • uv_handle_type type:明确句柄类型,对于uv_udp_t,其类型值为UV_UDP ,便于系统在内部进行类型识别和针对性处理。
  • void* data:这是一个通用指针,开发者可以利用它存储自定义数据,在句柄的回调函数执行时,能方便地获取和使用这些数据,增强了代码的灵活性。
  • uv_close_cb close_cb:在关闭句柄时会被调用,主要用于执行资源清理、状态重置等收尾操作,确保系统资源的正确释放和程序的正常结束。

UDP 句柄特有的成员

  • send_queue_size 和 send_queue_count:分别记录等待发送的数据报的总大小和数量,让开发者随时了解发送队列的状态,便于进行流量控制和性能优化。

UV_UDP_PRIVATE_FIELDS定义的其他字段(unix版本):

  • uv_alloc_cb alloc_cb;
  • 用途:uv_alloc_cb 是一个回调函数类型,alloc_cb 字段用于存储一个分配缓冲区的回调函数指针。在 UDP 接收数据时,libuv 需要一个合适的缓冲区来存放接收到的数据,此时会调用该回调函数来动态分配所需的缓冲区。这使得开发者能够根据实际情况灵活管理内存分配,避免固定大小缓冲区可能带来的内存浪费或不足问题。
  • 示例场景:在一个实时视频流传输的应用中,视频数据包大小可能会有所变化。使用 alloc_cb 可以根据不同大小的数据包动态分配合适的缓冲区。例如,当接收到较大的视频关键帧数据包时,分配较大的缓冲区;接收到较小的普通帧数据包时,分配较小的缓冲区,从而提高内存使用效率。
  • uv_udp_recv_cb recv_cb;
  • 用途:uv_udp_recv_cb 是一个回调函数类型,recv_cb 字段存储的是处理接收到的 UDP 数据的回调函数指针。当 UDP 句柄接收到数据时,libuv 会调用该回调函数,将接收到的数据、发送方地址等信息传递给开发者,开发者可以在该回调函数中进行数据解析、业务逻辑处理等操作。
  • 示例场景:在一个基于 UDP 的多人在线游戏中,服务器会不断接收到玩家客户端发送的操作指令(如移动、攻击等)。在 recv_cb 回调函数中,可以解析这些指令,更新游戏中玩家的状态,并将更新后的状态广播给其他玩家。
  • uv__io_t io_watcher;
  • 用途:uv__io_t 是 libuv 内部用于处理 I/O 事件的结构体,io_watcher 作为 UDP 句柄的 I/O 观察者,负责监听 UDP 套接字的 I/O 事件,如可读、可写等。libuv 利用底层操作系统的 I/O 多路复用机制(如 Linux 的 epoll、Windows 的 IOCP 等)来管理这些事件,io_watcher 会将 UDP 句柄与这些底层机制关联起来,当有相关 I/O 事件发生时,会触发相应的处理逻辑。
  • 示例场景:在一个大规模的物联网(IoT)应用中,有大量的传感器设备通过 UDP 协议向服务器发送数据。io_watcher 可以高效地监听所有这些设备对应的 UDP 套接字的可读事件,当有传感器数据到达时,能及时触发数据接收操作,保证系统的实时性和高效性。
  • struct uv__queue write_queue;
  • 用途:uv__queue 是 libuv 内部实现的一个队列结构,write_queue 用于存储待发送的 UDP 数据请求。当调用 uv_udp_send 函数发送数据时,请求会被添加到这个队列中,libuv 会按照队列的顺序依次处理这些发送请求,确保发送请求的有序执行,避免多个发送请求之间的冲突。
  • 示例场景:在一个实时金融数据推送系统中,服务器需要向多个客户端发送实时的股票行情数据。当有新的行情数据更新时,会生成多个发送请求将数据发送给不同的客户端。这些请求会被添加到 write_queue 中,libuv 会依次处理这些请求,保证数据按顺序准确地发送给各个客户端。
  • struct uv__queue write_completed_queue;
  • 用途:同样基于 uv__queue 队列结构,write_completed_queue 用于存储已经完成的 UDP 发送请求。当一个发送请求完成后,它会从 write_queue 中移除,并被添加到 write_completed_queue 中。这样,应用程序可以在适当的时候从这个队列中获取完成的请求,并执行相应的回调函数,如释放相关资源、通知用户发送成功等。
  • 示例场景:在一个文件传输应用中,使用 UDP 协议将大文件分割成多个数据包进行发送。每个数据包的发送请求完成后,会被添加到 write_completed_queue 中。应用程序可以定期检查这个队列,统计已发送的数据包数量,更新文件传输进度,并在所有数据包发送完成后,释放相关的内存资源和通知用户文件传输成功。
  1. 常见操作及示例代码

初始化

在使用uv_udp_t句柄之前,需要通过uv_udp_init函数将其与事件循环关联并初始化:

#include 
#include 

uv_loop_t* loop;
uv_udp_t udp_handle;

int main() {
    loop = uv_default_loop();
    // 初始化UDP句柄
    int r = uv_udp_init(loop, &udp_handle);
    if (r) {
        fprintf(stderr, "UDP handle initialization error: %s\n", uv_strerror(r));
        return 1;
    }
    // 后续操作...
    return uv_run(loop, UV_RUN_DEFAULT);
}

在上述代码中,首先获取默认的事件循环loop,然后调用uv_udp_init对udp_handle进行初始化。如果初始化失败,会打印错误信息并返回。

绑定地址

为了接收 UDP 数据,需要将句柄绑定到指定的 IP 地址和端口,使用uv_udp_bind函数完成该操作:

#include 
#include 

uv_loop_t* loop;
uv_udp_t udp_handle;

int main() {
    loop = uv_default_loop();
    uv_udp_init(loop, &udp_handle);

    struct sockaddr_in addr;
    uv_ip4_addr("0.0.0.0", 8080, &addr);

    // 绑定地址
    int r = uv_udp_bind(&udp_handle, (const struct sockaddr*)&addr, 0);
    if (r) {
        fprintf(stderr, "UDP bind error: %s\n", uv_strerror(r));
        return 1;
    }
    // 后续操作...
    return uv_run(loop, UV_RUN_DEFAULT);
}

这里先初始化 UDP 句柄,然后创建一个sockaddr_in结构体addr,设置好 IP 地址和端口,最后调用uv_udp_bind将句柄绑定到该地址。若绑定失败,打印错误信息并返回。

开始接收数据

绑定地址后,使用uv_udp_recv_start函数开启数据接收,同时需要提供缓冲区分配和数据接收的回调函数:

#include 
#include 
#include 

uv_loop_t* loop;
uv_udp_t udp_handle;

void alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
    buf->base = (char*)malloc(suggested_size);
    buf->len = suggested_size;
}

void recv_callback(uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags) {
    if (nread > 0) {
        printf("Received %zd bytes: %.*s\n", nread, (int)nread, buf->base);
    } else if (nread < 0) {
        fprintf(stderr, "Receive error: %s\n", uv_strerror(nread));
    }
    free(buf->base);
}

int main() {
    loop = uv_default_loop();
    uv_udp_init(loop, &udp_handle);

    struct sockaddr_in addr;
    uv_ip4_addr("0.0.0.0", 8080, &addr);

    uv_udp_bind(&udp_handle, (const struct sockaddr*)&addr, 0);

    // 开始接收数据
    uv_udp_recv_start(&udp_handle, alloc_buffer, recv_callback);

    return uv_run(loop, UV_RUN_DEFAULT);
}

在这个示例中,alloc_buffer函数负责分配接收缓冲区,recv_callback函数处理接收到的数据。当有数据到达时,recv_callback会被调用,根据接收数据的情况进行相应处理,若接收成功则打印接收到的数据,若失败则打印错误信息。最后通过uv_udp_recv_start开启数据接收。

相关推荐

【常识】如何优化Windows 7

优化Windows7可以让这个经典系统运行更流畅,特别是在老旧硬件上。以下是经过整理的实用优化方案,分为基础优化和进阶优化两部分:一、基础优化(适合所有用户)1.关闭不必要的视觉效果右键计算机...

系统优化!Windows 11/10 必做的十个优化配置

以下是为Windows10/11用户整理的10个必做优化配置,涵盖性能提升、隐私保护和系统精简等方面,操作安全且无需第三方工具:1.禁用不必要的开机启动项操作路径:`Ctrl+S...

最好用音频剪辑的软件,使用方法?

QVE音频剪辑是一款简单实用的软件,功能丰富,可编辑全格式音频。支持音频转换、合并、淡入淡出、变速、音量调节等,无时长限制,用户可自由剪辑。剪辑后文件音质无损,支持多格式转换,便于存储与跨设备播放,满...

Vue2 开发总踩坑?这 8 个实战技巧让代码秒变丝滑

前端开发的小伙伴们,在和Vue2打交道的日子里,是不是总被各种奇奇怪怪的问题搞得头大?数据不响应、组件传值混乱、页面加载慢……别慌!今天带来8个超实用的Vue2实战技巧,每一个都能直击痛...

Motion for Vue:为Vue量身定制的强大动画库

在前端开发中,动画效果是提升用户体验的重要手段。Vue生态系统中虽然有许多动画库,但真正能做到高性能、易用且功能丰富的并不多。今天,我们要介绍的是MotionforVue(motion-v),...

CSS view():JavaScript 滚动动画的终结

前言CSSview()方法可能会标志着JavaScript在制作滚动动画方面的衰落。如何用5行CSS代码取代50多行繁琐的JavaScript,彻底改变网页动画每次和UI/U...

「大数据」 hive入门

前言最近会介入数据中台项目,所以会推出一系列的跟大数据相关的组件博客与文档。Hive这个大数据组件自从Hadoop诞生之日起,便作为Hadoop生态体系(HDFS、MR/YARN、HIVE、HBASE...

青铜时代的终结:对奖牌架构的反思

作者|AdamBellemare译者|王强策划|Tina要点运维和分析用例无法可靠地访问相关、完整和可信赖的数据。需要一种新的数据处理方法。虽然多跳架构已经存在了几十年,并且可以对...

解析IBM SQL-on-Hadoop的优化思路

对于BigSQL的优化,您需要注意以下六个方面:1.平衡的物理设计在进行集群的物理设计需要考虑数据节点的配置要一致,避免某个数据节点性能短板而影响整体性能。而对于管理节点,它虽然不保存业务数据,但作...

交易型数据湖 - Apache Iceberg、Apache Hudi和Delta Lake的比较

图片由作者提供简介构建数据湖最重要的决定之一是选择数据的存储格式,因为它可以大大影响系统的性能、可用性和兼容性。通过仔细考虑数据存储的格式,我们可以增强数据湖的功能和性能。有几种不同的选择,每一种都有...

深入解析全新 AWS S3 Tables:重塑数据湖仓架构

在AWSre:Invent2024大会中,AWS发布了AmazonS3Tables:一项专为可扩展存储和管理结构化数据而设计的解决方案,基于ApacheIceberg开放表格...

Apache DataFusion查询引擎简介

简介DataFusion是一个查询引擎,其本身不具备存储数据的能力。正因为不依赖底层存储的格式,使其成为了一个灵活可扩展的查询引擎。它原生支持了查询CSV,Parquet,Avro,Json等存储格式...

大数据Hadoop之——Flink Table API 和 SQL(单机Kafka)

一、TableAPI和FlinkSQL是什么TableAPI和SQL集成在同一套API中。这套API的核心概念是Table,用作查询的输入和输出,这套API都是批处理和...

比较前 3 名Schema管理工具

关注留言点赞,带你了解最流行的软件开发知识与最新科技行业趋势。在本文中,读者将了解三种顶级schema管理工具,如AWSGlue、ConfluentSchemaRegistry和Memph...

大数据技术之Flume

第1章概述1.1Flume定义Flume是Cloudera提供的一个高可用的,高可靠的,分布式的海量日志采集、聚合和传输的系统。Flume基于流式架构,灵活简单。1.2Flume的优点1.可以和...