2025-11-29 · libevent / c/c++
Libevent 深度剖析与实战指南
一套深度与广度兼备的 Libevent 技术专栏。从源码层面剖析 Reactor 模式、IO 多路复用、内存管理等核心机制,结合生产级实战项目,助你掌握高性能网络编程。
在网络编程中,TCP 占据了主导地位,但 UDP 在实时音视频、DNS 解析、QUIC 等场景中依然不可或缺。
很多初学者习惯了 Libevent 的 bufferevent
抽象,但在处理 UDP 时会发现无从下手。本文将讲解如何在
Libevent 中正确地处理 UDP。
Libevent 的 bufferevent 是为 流式
(Stream) 协议设计的: * 它假设数据是连续的字节流。
* 它自动处理缓冲区的拼接和移动。 *
它处理“连接”状态(Connected, Disconnected)。
而 UDP 是 数据报 (Datagram) 协议: *
数据是独立的包,有明确的边界。 * 没有“连接”的概念(除非使用
connect() 绑定目标,但这只是内核状态)。 * 一个
UDP Socket 通常用于与多个客户端通信(多对一)。
虽然 Libevent 历史上曾尝试过 UDP bufferevent
的支持,但在实际开发中,直接使用原始的
struct event 是处理 UDP
的标准做法。
实现一个 UDP Server 的核心步骤非常简单: 1. 创建 UDP
Socket。 2. 绑定端口 (bind)。 3. 创建一个
EV_READ | EV_PERSIST 的事件监听该 Socket。 4.
在回调函数中使用 recvfrom 读取数据,用
sendto 回复数据。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <event2/event.h>
#define PORT 9999
#define BUF_SIZE 1024
void on_read(evutil_socket_t fd, short events, void *arg) {
char buf[BUF_SIZE];
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
ssize_t len;
// 1. 读取数据包
// 注意:UDP 是保留消息边界的,一次 recvfrom 对应一个包
len = recvfrom(fd, buf, BUF_SIZE - 1, 0,
(struct sockaddr *)&client_addr, &client_len);
if (len < 0) {
perror("recvfrom");
return;
}
buf[len] = '\0';
printf("Received %zd bytes from %s:%d: %s\n",
len,
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port),
buf);
// 2. 回复数据 (Echo)
sendto(fd, buf, len, 0, (struct sockaddr *)&client_addr, client_len);
}
int main() {
struct event_base *base;
struct event *udp_event;
evutil_socket_t fd;
struct sockaddr_in sin;
// 1. 初始化 Libevent
base = event_base_new();
if (!base) return 1;
// 2. 创建 UDP Socket
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
perror("socket");
return 1;
}
// 设置为非阻塞 (这是 Libevent 的要求)
evutil_make_socket_nonblocking(fd);
// 3. 绑定端口
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons(PORT);
if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
perror("bind");
return 1;
}
// 4. 创建并添加事件
// 监听 EV_READ,并设置 EV_PERSIST 保持持续监听
udp_event = event_new(base, fd, EV_READ | EV_PERSIST, on_read, NULL);
event_add(udp_event, NULL);
printf("UDP Server listening on port %d\n", PORT);
// 5. 进入事件循环
event_base_dispatch(base);
// 清理
event_free(udp_event);
evutil_closesocket(fd);
event_base_free(base);
return 0;
}UDP
接收缓冲区大小有限。如果你的处理速度跟不上发包速度,内核会丢弃数据包。
* 可以使用
setsockopt(fd, SOL_SOCKET, SO_RCVBUF, ...)
增大内核缓冲区。 *
在回调中,尽量快速处理或将数据拷贝到队列中异步处理,避免阻塞
Event Loop。
recvfrom 提供的 buffer
必须足够大以容纳最大的预期数据包(通常是 MTU
限制,互联网上建议控制在 512 或 1400 字节以内)。如果包超过
buffer 大小,多余部分会被丢弃(且设置 MSG_TRUNC
标志)。
在高并发场景下,单个线程处理 UDP 可能会成为瓶颈。 Linux
3.9+ 支持
SO_REUSEPORT。你可以启动多个进程/线程,每个都创建自己的
event_base,绑定到同一个端口。内核会自动进行负载均衡,将数据包分发给不同的线程。
int opt = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
// 多个线程都执行这段 bind 代码
bind(fd, ...);event_new +
recvfrom。evutil_make_socket_nonblocking。完整代码: 04-udp-echo.c
把当前热点继续串成多页阅读,而不是停在单篇消费。
2025-11-29 · libevent / c/c++
一套深度与广度兼备的 Libevent 技术专栏。从源码层面剖析 Reactor 模式、IO 多路复用、内存管理等核心机制,结合生产级实战项目,助你掌握高性能网络编程。
2025-11-27 · libevent
告别阻塞的 getaddrinfo,使用 Libevent 内置的 evdns 实现高性能异步域名解析。
2025-11-27 · libevent
快速上手 Libevent 内置的 evhttp 模块,构建轻量级 HTTP 服务,并了解其局限性。
2025-11-27 · libevent
Libevent 原生不支持 HTTP/2 和 QUIC,但这并不妨碍我们集成 nghttp2 和 ngtcp2。本文探讨如何基于 Libevent 构建下一代 Web 服务。
在网络编程中,TCP 占据了主导地位,但 UDP 在实时音视频、DNS 解析、QUIC 等场景中依然不可或缺。
很多初学者习惯了 Libevent 的 bufferevent
抽象,但在处理 UDP 时会发现无从下手。本文将讲解如何在
Libevent 中正确地处理 UDP。
Libevent 的 bufferevent 是为 流式
(Stream) 协议设计的: * 它假设数据是连续的字节流。
* 它自动处理缓冲区的拼接和移动。 *
它处理“连接”状态(Connected, Disconnected)。
而 UDP 是 数据报 (Datagram) 协议: *
数据是独立的包,有明确的边界。 * 没有“连接”的概念(除非使用
connect() 绑定目标,但这只是内核状态)。 * 一个
UDP Socket 通常用于与多个客户端通信(多对一)。
虽然 Libevent 历史上曾尝试过 UDP bufferevent
的支持,但在实际开发中,直接使用原始的
struct event 是处理 UDP
的标准做法。
实现一个 UDP Server 的核心步骤非常简单: 1. 创建 UDP
Socket。 2. 绑定端口 (bind)。 3. 创建一个
EV_READ | EV_PERSIST 的事件监听该 Socket。 4.
在回调函数中使用 recvfrom 读取数据,用
sendto 回复数据。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <event2/event.h>
#define PORT 9999
#define BUF_SIZE 1024
void on_read(evutil_socket_t fd, short events, void *arg) {
char buf[BUF_SIZE];
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
ssize_t len;
// 1. 读取数据包
// 注意:UDP 是保留消息边界的,一次 recvfrom 对应一个包
len = recvfrom(fd, buf, BUF_SIZE - 1, 0,
(struct sockaddr *)&client_addr, &client_len);
if (len < 0) {
perror("recvfrom");
return;
}
buf[len] = '\0';
printf("Received %zd bytes from %s:%d: %s\n",
len,
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port),
buf);
// 2. 回复数据 (Echo)
sendto(fd, buf, len, 0, (struct sockaddr *)&client_addr, client_len);
}
int main() {
struct event_base *base;
struct event *udp_event;
evutil_socket_t fd;
struct sockaddr_in sin;
// 1. 初始化 Libevent
base = event_base_new();
if (!base) return 1;
// 2. 创建 UDP Socket
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
perror("socket");
return 1;
}
// 设置为非阻塞 (这是 Libevent 的要求)
evutil_make_socket_nonblocking(fd);
// 3. 绑定端口
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons(PORT);
if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
perror("bind");
return 1;
}
// 4. 创建并添加事件
// 监听 EV_READ,并设置 EV_PERSIST 保持持续监听
udp_event = event_new(base, fd, EV_READ | EV_PERSIST, on_read, NULL);
event_add(udp_event, NULL);
printf("UDP Server listening on port %d\n", PORT);
// 5. 进入事件循环
event_base_dispatch(base);
// 清理
event_free(udp_event);
evutil_closesocket(fd);
event_base_free(base);
return 0;
}UDP
接收缓冲区大小有限。如果你的处理速度跟不上发包速度,内核会丢弃数据包。
* 可以使用
setsockopt(fd, SOL_SOCKET, SO_RCVBUF, ...)
增大内核缓冲区。 *
在回调中,尽量快速处理或将数据拷贝到队列中异步处理,避免阻塞
Event Loop。
recvfrom 提供的 buffer
必须足够大以容纳最大的预期数据包(通常是 MTU
限制,互联网上建议控制在 512 或 1400 字节以内)。如果包超过
buffer 大小,多余部分会被丢弃(且设置 MSG_TRUNC
标志)。
在高并发场景下,单个线程处理 UDP 可能会成为瓶颈。 Linux
3.9+ 支持
SO_REUSEPORT。你可以启动多个进程/线程,每个都创建自己的
event_base,绑定到同一个端口。内核会自动进行负载均衡,将数据包分发给不同的线程。
int opt = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
// 多个线程都执行这段 bind 代码
bind(fd, ...);event_new +
recvfrom。evutil_make_socket_nonblocking。完整代码: 04-udp-echo.c
把当前热点继续串成多页阅读,而不是停在单篇消费。
2025-11-29 · libevent / c/c++
一套深度与广度兼备的 Libevent 技术专栏。从源码层面剖析 Reactor 模式、IO 多路复用、内存管理等核心机制,结合生产级实战项目,助你掌握高性能网络编程。
2025-11-27 · libevent
告别阻塞的 getaddrinfo,使用 Libevent 内置的 evdns 实现高性能异步域名解析。
2025-11-27 · libevent
快速上手 Libevent 内置的 evhttp 模块,构建轻量级 HTTP 服务,并了解其局限性。
2025-11-27 · libevent
Libevent 原生不支持 HTTP/2 和 QUIC,但这并不妨碍我们集成 nghttp2 和 ngtcp2。本文探讨如何基于 Libevent 构建下一代 Web 服务。