libev简单使用
libev简单使用
https://zhuanlan.zhihu.com/p/163282654
socket编程最紧迫的需求就是有一个好用的buffer对象可以用,就像libevent的evbuffer类似的东西,现在我们定义一个简单的buffer
buffer.h
#pragma once
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
struct buffer {
unsigned char *head, *data, *tail, *end;
};
void buffer_init(struct buffer *buf);
void buffer_free(struct buffer *buf);
size_t buffer_length(const struct buffer *buf);
size_t buffer_size(const struct buffer *buf);
unsigned char *buffer_data(const struct buffer *buf);
void buffer_append_data(struct buffer *buf, const void *data, size_t len);
void buffer_append_null(struct buffer *buf);
void buffer_append_char(struct buffer *buf, char c);
void buffer_append_printf(struct buffer *buf, const char *fmt, ...);
void buffer_drain(struct buffer *buf, size_t len);
void buffer_reset(struct buffer *buf);
void buffer_hexdump(const struct buffer *buf);
#ifdef __cplusplus
}
#endif
我们对buffer的简单需求,就是可以在末尾添加数据(buffer_append_data),获取一块连续的内存(buffer_data),消费指定长度的数据(buffer_drain),这些操作内部都自动完成内存管理,使用者不需要考虑内存问题。
C语言事件循环库除了大名鼎鼎的libevent,还有libev。后者更加轻量。下面我们已libev为例,实现简单的io_copy功能,也即从一个描述符读数据,透传到另一个描述符。
先看使用示例:
开一个终端运行
$ nc -v -l 9999
Listening on [0.0.0.0] (family 0, port 9999)
运行
$ cc *.c -lev
$ ./a.out 127.0.0.1 9999 < /var/log/syslog
上面的示例程序把标准输入拷贝到127.0.0.1:9999标示的socket上
下面看main.c文件
// main.c
#include "io.h"
#include "xnet.h"
#include <ev.h>
#include <stdio.h>
#include <stdlib.h>
static void die(const char *s)
{
perror(s);
exit(1);
}
static void usage(const char *name)
{
fprintf(stderr, "usage: %s ip port\n", name);
exit(1);
}
static void completion()
{
fprintf(stderr, "done\n");
}
static void error(const char *errmsg)
{
fprintf(stderr, "error: %s\n", errmsg);
}
int main(int argc, char *argv[])
{
struct ev_loop *loop;
int connfd;
struct io io;
if (argc != 3)
usage(argv[0]);
connfd = dial_tcp(argv[1], atoi(argv[2]));
if (connfd == -1)
die("dial_tcp");
loop = ev_default_loop(0);
io_init(&io, loop, completion, error);
io_copy(&io, connfd, 0); // 从描述符0拷贝数据转发给描述符connfd
ev_run(loop, 0);
}
定义struct io
// io.h
#pragma once
#include "buffer.h"
#include <ev.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef void (*completion_cb)();
typedef void (*error_cb)(const char *errmsg);
struct io {
struct ev_io src_watcher;
struct ev_io dst_watcher;
struct buffer buf;
struct ev_loop *loop;
completion_cb completion;
error_cb error;
int src_done;
};
void io_init(struct io *io, struct ev_loop *loop, completion_cb completion, error_cb error);
void io_copy(struct io *io, int dst, int src);
#ifdef __cplusplus
}
#endif
// io.c
#include "io.h"
#include <errno.h>
#include <unistd.h>
#define HIGH_WATER_MARK (1024 * 1024 * 4)
#define LOW_WATER_MARK (1024 * 1024 * 1)
static void io_error(struct io *io)
{
ev_io_stop(io->loop, &io->src_watcher);
ev_io_stop(io->loop, &io->dst_watcher);
if (io->error)
io->error(strerror(errno));
}
static void io_completion(struct io *io)
{
ev_io_stop(io->loop, &io->src_watcher);
ev_io_stop(io->loop, &io->dst_watcher);
if (io->completion)
io->completion();
}
static void src_read_cb(struct ev_loop *loop, ev_io *w, int revents)
{
char buf[4096];
ssize_t nread;
struct io *io;
io = (struct io *)w->data;
nread = read(w->fd, buf, sizeof(buf));
switch (nread) {
case -1:
io_error(io);
return;
case 0:
ev_io_stop(loop, &io->src_watcher);
ev_io_start(loop, &io->dst_watcher);
io->src_done = 1; // 标示读完成了
return;
default:
buffer_append_data(&io->buf, buf, nread); // 把读到的数据添加到buffer末尾
ev_io_start(loop, &io->dst_watcher);
if (buffer_length(&io->buf) > HIGH_WATER_MARK) // buffer过大时停止读
ev_io_stop(loop, &io->src_watcher);
return;
}
}
static void dst_write_cb(struct ev_loop *loop, ev_io *w, int revents)
{
ssize_t nwritten;
struct io *io;
io = (struct io *)w->data;
nwritten = write(w->fd, buffer_data(&io->buf), buffer_length(&io->buf));
if (nwritten == -1) {
io_error(io);
return;
}
buffer_drain(&io->buf, nwritten); // 消费掉nwritten大小的数据
if ((buffer_length(&io->buf) < LOW_WATER_MARK) && !io->src_done) // buffer过小时,开始读
ev_io_start(loop, &io->src_watcher);
if ((buffer_length(&io->buf) == 0) && io->src_done)
io_completion(io);
}
void io_init(struct io *io, struct ev_loop *loop, completion_cb completion, error_cb error)
{
buffer_init(&io->buf);
io->loop = loop;
io->completion = completion;
io->error = error;
io->src_done = 0;
}
void io_copy(struct io *io, int dst, int src)
{
ev_io_init(&io->src_watcher, src_read_cb, src, EV_READ);
ev_io_init(&io->dst_watcher, dst_write_cb, dst, EV_WRITE);
io->src_watcher.data = io;
io->dst_watcher.data = io;
ev_io_start(io->loop, &io->src_watcher);
ev_io_start(io->loop, &io->dst_watcher);
}
buffer.c实现
#include "buffer.h"
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static inline void *xrealloc(void *ptr, size_t size)
{
void *p = realloc(ptr, size);
if (!p) {
fprintf(stderr, "realloc(%p, %zu): out of memory\n", ptr, size);
exit(1);
}
return p;
}
static inline size_t buffer_headroom(const struct buffer *buf)
{
return buf->data - buf->head;
}
static inline size_t buffer_tailroom(const struct buffer *buf)
{
return buf->end - buf->tail;
}
static inline void move_data(struct buffer *buf)
{
size_t data_len;
data_len = buffer_length(buf);
memmove(buf->head, buf->data, data_len);
buf->data = buf->head;
buf->tail = buf->data + data_len;
}
static void buffer_grow(struct buffer *buf, size_t len)
{
size_t size, new_size, data_len;
assert(buf->head == buf->data);
size = buffer_size(buf) + len;
new_size = 1024;
while (new_size < size)
new_size <<= 1;
data_len = buffer_length(buf);
buf->head = xrealloc(buf->head, new_size);
buf->data = buf->head;
buf->tail = buf->data + data_len;
buf->end = buf->head + new_size;
}
static void grow_if_needed(struct buffer *buf, size_t len)
{
if (buffer_tailroom(buf) >= len)
return;
if (buffer_headroom(buf) > 0) {
move_data(buf);
if (buffer_tailroom(buf) >= len)
return;
}
buffer_grow(buf, len);
}
void buffer_init(struct buffer *buf)
{
memset(buf, 0, sizeof(struct buffer));
}
void buffer_free(struct buffer *buf)
{
free(buf->head);
memset(buf, 0, sizeof(struct buffer));
}
size_t buffer_length(const struct buffer *buf)
{
return buf->tail - buf->data;
}
size_t buffer_size(const struct buffer *buf)
{
return buf->end - buf->head;
}
unsigned char *buffer_data(const struct buffer *buf)
{
return buf->data;
}
void buffer_append_data(struct buffer *buf, const void *data, size_t len)
{
grow_if_needed(buf, len);
memcpy(buf->tail, data, len);
buf->tail += len;
}
void buffer_append_null(struct buffer *buf)
{
char null = '\0';
buffer_append_data(buf, &null, 1);
}
void buffer_append_char(struct buffer *buf, char c)
{
buffer_append_data(buf, &c, 1);
}
void buffer_append_printf(struct buffer *buf, const char *fmt, ...)
{
va_list ap;
int npr;
char *str;
va_start(ap, fmt);
npr = vsnprintf(NULL, 0, fmt, ap);
va_end(ap);
str = alloca(npr);
va_start(ap, fmt);
vsprintf(str, fmt, ap);
va_end(ap);
buffer_append_data(buf, str, npr);
}
void buffer_drain(struct buffer *buf, size_t len)
{
buf->data += len;
}
void buffer_reset(struct buffer *buf)
{
buf->data = buf->tail = buf->head;
}
void buffer_hexdump(const struct buffer *buf)
{
size_t len, i;
len = buffer_length(buf);
for (i = 0; i < len; i++)
printf("%02x", buf->data[i]);
printf("\n");
}
xnet.h
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
int dial_tcp(const char *ip, int port);
#ifdef __cplusplus
}
#endif
xnet.c
#include "xnet.h"
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
static int dial_net(int socktype, const char *ip, uint16_t port)
{
int fd, rc;
struct sockaddr_in addr;
fd = socket(AF_INET, socktype, 0);
if (fd == -1)
return -1;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip);
rc = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
if (rc == -1) {
close(fd);
return -1;
}
return fd;
}
int dial_tcp(const char *ip, int port)
{
return dial_net(SOCK_STREAM, ip, port);
}
主要思想就是一边从源描述符读,写到buffer里,一边从buffer里消费数据,写到socket。
==================== End
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
2020-03-25 Qt5 "Clang Code Model" 一些设置
2020-03-25 QTest 单元测试框架
2020-03-25 使用C++/Qt编程的一些技巧
2019-03-25 pfSense用户界面汉化翻译教程
2019-03-25 Pfsense2.3.4中文版
2018-03-25 NetScaler通过DHCP服务器获取IP地址