Libevent 学习十:bufferevent 实例

bufferevent 服务端实例

本章使用 bufferevent 实现一个服务端实例:

  • 调用 evconnlistener_new_bind 建立一个 socket 监听;
  • 在监听回调 listen_cb 中使用 bufferevent 建立与客户端的连接,并指定读、写、事件回调,并设置客户端的超时时间;
  • 在读取回调 read_cb 中接受数据,接收一次就向客户端返回一个 OK 字符串,当接受到 quit 时,断开与客户端的连接;
  • 在事件回调 event_cb 中处理超时事件 BEV_EVENT_TIMEOUT,读取缓冲区中的剩余数据,断开与客户端的连接。其他事件直接断开连接。

整个服务端的代码如下:

#include <iostream>
#include <string.h>
#include <errno.h>
#ifndef _WIN32
#include <signal.h>
#endif // !_WIN32

#include "event.h"
#include "event2/listener.h"

#define SERVER_PORT 8000

char recv_buf[10240] = { 0 };
size_t recv_len = 0;

// 读取回调
void read_cb(struct bufferevent *bev, void *ctx) {
    std::cout << "[R]" << std::endl;

    // 读取输入缓冲数据
    char data[1024] = { 0 };
    size_t len = bufferevent_read(bev, data, sizeof(data) - 1);
    std::cout << "read[" << len << "]: " << data << std::endl;
    if (len <= 0) return;

    strncat(recv_buf, data, sizeof(recv_buf) - recv_len - 1);
    recv_len += len;
    
    if (strstr(data, "quit") != NULL) {
        std::cout << "quti" << std::endl;
        // 退出并关闭socket,因为bufferevent设置了BEV_OPT_CLOSE_ON_FREE属性
        bufferevent_free(bev);
    }

    // 发送OK到输出缓冲区
    bufferevent_write(bev, "OK", 3);   
}

// 写入回调
void write_cb(struct bufferevent *bev, void *ctx) {
    std::cout << "[W]" << std::endl;
}

// 事件回调,错误,超时,连接断开会进入
void event_cb(struct bufferevent *bev, short what, void *ctx) {
    std::cout << "[E]:" << what << std::endl;

    if ((what & BEV_EVENT_READING) && (what & BEV_EVENT_TIMEOUT)) {
        // 若是超时事件,读取数据会停止,即EV_READ不可读
        std::cout << "BEV_EVENT_READING & BEV_EVENT_TIMEOUT" << std::endl;

        // 因为设置了最低水位,此时缓存中的数据可能没有读完,需要继续读取
        char data[1024] = { 0 };
        size_t len = bufferevent_read(bev, data, sizeof(data) - 1);
        if (len > 0) {
            std::cout << "read[" << len << "]: " << data << std::endl;
            strncat(recv_buf, data, sizeof(recv_buf) - recv_len - 1);
            recv_len += len;
        }
        std::cout << "recv[" << recv_len << "]: " << recv_buf << std::endl;

        // 可以重新设置可读,一般直接断开连接
        // bufferevent_enable(bev, EV_READ);
        bufferevent_free(bev);
    }
    else if (what & BEV_EVENT_EOF) {
        // 客户端连接断开事件
        std::cout << "BEV_EVENT_EOF" << std::endl;
        bufferevent_free(bev);
    }
    else if (what & BEV_EVENT_ERROR) {
        // 错误,直接断开连接
        std::cout << "BEV_EVENT_ERROR" << std::endl;
        bufferevent_free(bev);
    }
    else {
        std::cout << "OTHERS" << std::endl;
    }
}

// 监听回调
void listen_cb(struct evconnlistener *ev, evutil_socket_t fd, struct sockaddr *sin, int slen, void *arg) {
    std::cout << "listen_cb" << std::endl;

    event_base *base = (event_base*)arg;

    // 创建 bufferevent 上下文,BEV_OPT_CLOSE_ON_FREE 清理bufferevent时关闭socket
    bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);

    // 添加监控事件
    bufferevent_enable(bev, EV_READ | EV_WRITE);

    // 设置读取水位
    bufferevent_setwatermark(bev, EV_READ,
        5,      // 低水位,默认0,即无限制,最低读取数量
        10);    // 高水位,默认0,即无限制,最高读取数量

    // 设置写入水位(目前还不太清楚具体应用,以后可能会补上)
    /*bufferevent_setwatermark(bev, EV_WRITE,
        5,      // 低水位,默认0,即无限制,缓冲区低于设置时,回调函数被调用
        0);     // 高水位无效*/

    // 设置超时时间
    timeval tv = { 3, 0 };
    bufferevent_set_timeouts(bev, &tv, NULL);

    // 设置回调函数
    bufferevent_setcb(bev, read_cb, write_cb, event_cb, base);
}

int main(int argc, char* argv[])
{
#ifdef _WIN32
    // 初始化socket库
    WSADATA wsa;
    WSAStartup(MAKEWORD(2, 2), &wsa);
#else
    // 忽略管道信号,因为发送数据给已关闭的socket会生成SIGPIPE信号,导致进程退出
    if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
        return -1;
#endif

    std::cout << "libevent bufferevent test" << std::endl;
    // 创建libevent上下文
    event_base *base = event_base_new();
    if (!base) {
        std::cout << "event_base_new failed" << std::endl;
        return -1;
    }
    std::cout << "event_base_new success" << std::endl;

    // 绑定IP和端口
    struct sockaddr_in sin;
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SERVER_PORT);

    // 创建监听事件
    evconnlistener *ev = evconnlistener_new_bind(base,
        listen_cb,                                  // 回调函数
        base,                                       // 回调函数参数
        LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE,  // 设置地址复用和关闭时释放资源
        10,                                         // listen back
        (sockaddr*)&sin, sizeof(sin));              // 监听地址

    // 进入事件主循环
    event_base_dispatch(base);

    if (ev) evconnlistener_free(ev);
    event_base_free(base);

#ifdef _WIN32
    WSACleanup();
#endif

    return 0;
}

为了测试,在93行设置了读取水位(实际项目中不会这样用)。在104行设置了读取超时,若发生超时事件,而因为有低水位限制,缓冲区中仍会有一部分数据没有读取,此时需要调用 read 函数将缓冲区中的数据读完,见代码52 ~ 59 行。

下图是使用 netassist 工具进行测试的结果,服务端设置了读取水位:满足5个才读取,一次最多读取10个字符数据。从图中可知:

  • 第一次发送 aaa ,此时服务端数据不足5个,没有达到最低水位,此时不会有任何操作,netassist 也没有收到 OK 的回复;
  • 第二次发送 aaa ,此时服务端有6个数据(加上第一次发送的3个字符),已达到最低水位,此时会调用读回调函数,读取到6个字符(aaaaaa),netassist 会收到 OK 的回复;
  • 第三次发送 123456,服务端数据达已到最低水位,会调用读回调函数,读取到6个字符(123456),netassist 会收到 OK 的回复;
  • 第四次发送 1234567890123456,服务端收到16个字符数据,已超过最高水位,会调用两次读回调函数,第一次读取10个字符(1234567890),第二次读取6个字符(123456),netassist 会收到两个 OK 的回复。

在这里插入图片描述
这里使用了网络调试助手充当客户端程序,这个工具很好用,强烈推荐。下面介绍一个客户端实例。


bufferevent 客户端实例

本章使用 bufferevent 实现一个客户端实例:

  • 调用 bufferevent_socket_connect 连接指定的服务端,并指定读、写、事件回调;
  • 在事件回调 client_event_cb 中处理连接事件 BEV_EVENT_CONNECTED,标识连接成功,并触发 write 事件回调;
  • 在写入回调 client_write_cb 中读取指定的文件,并将文件发送给服务端。

整个客户端的代码如下:

#include <iostream>
#include <string.h>
#include <errno.h>
#ifndef _WIN32
#include <signal.h>
#endif // !_WIN32

#include "event.h"

#define SERVER_PORT 8000

size_t send_len = 0;

// 读取回调
void client_read_cb(struct bufferevent *bev, void *ctx)
{
    std::cout << "[CLIENT R]" << std::endl;
}

// 写入回调
void client_write_cb(struct bufferevent *bev, void *ctx)
{
    std::cout << "[CLIENT W]" << std::endl;

    FILE *fp = (FILE*)ctx;
    char data[1024] = { 0 };
    size_t len = fread(data, 1, sizeof(data) - 1, fp);
    std::cout << "len=" << len << ", data=" << data << std::endl;
    send_len += len;
    if (len <= 0) {
        // 读到结尾或者文件出错
        fclose(fp);
        // 立刻清理,可能会造成缓冲区数据发送没有结束
        // bufferevent_free(bev);
        // 可以将写入禁用,因为当缓冲区可写入的时候是会再次进入
        bufferevent_disable(bev, EV_WRITE);
        std::cout << "send total len=" << send_len << std::endl;
        return;
    }

    // 写入bufferevent
    bufferevent_write(bev, data, len);
}

// 事件回调,错误,超时,连接断开会进入
void client_event_cb(struct bufferevent *bev, short what, void *ctx)
{
    std::cout << "[CLIENT E]:" << what << std::endl;

    if ((what & BEV_EVENT_READING) && (what & BEV_EVENT_TIMEOUT)) {
        // 若是超时事件,读取数据会停止,即EV_READ不可读
        std::cout << "BEV_EVENT_READING & BEV_EVENT_TIMEOUT" << std::endl;
        bufferevent_free(bev);
    }
    else if (what & BEV_EVENT_EOF) {
        // 服务端连接断开事件
        std::cout << "BEV_EVENT_EOF" << std::endl;
        bufferevent_free(bev);
    }
    else if (what & BEV_EVENT_ERROR) {
        // 错误,直接断开连接
        std::cout << "BEV_EVENT_ERROR" << std::endl;
        bufferevent_free(bev);
    }
    else if (what & BEV_EVENT_CONNECTED) {
        // 连接成功
        std::cout << "BEV_EVENT_CONNECTED" << std::endl;
        // 触发 write 事件
        bufferevent_trigger(bev, EV_WRITE, 0);
    }
    else {
        std::cout << "OTHERS" << std::endl;
    }
}

int main(int argc, char* argv[])
{
    if (argc < 2) {
        std::cout << "argument error\n";
        std::cout << "usage:\n\t" << argv[0] << " [file]" << std::endl;
        return -1;
    }

#ifdef _WIN32
    // 初始化socket库
    WSADATA wsa;
    WSAStartup(MAKEWORD(2, 2), &wsa);
#else
    // 忽略管道信号,因为发送数据给已关闭的socket会生成SIGPIPE信号,导致进程退出
    if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
        return -1;
#endif

    std::cout << "libevent bufferevent test" << std::endl;
    // 创建libevent上下文
    event_base *base = event_base_new();
    if (!base) {
        std::cout << "event_base_new failed" << std::endl;
        return -1;
    }
    std::cout << "event_base_new success" << std::endl;

    // 客户端bufferevent,-1表示内部创建socket
    bufferevent *bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
    // 指定服务端的IP和端口
    struct sockaddr_in sin;
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SERVER_PORT);
    evutil_inet_pton(AF_INET, "127.0.0.1", &sin.sin_addr.s_addr);

    FILE *fp = fopen(argv[1], "rb");
    if (!fp) {
        std::cout << "fopen error!" << std::endl;
        return -1;
    }

    // 设置回调函数
    bufferevent_setcb(bev, client_read_cb, client_write_cb, client_event_cb, fp);
    bufferevent_enable(bev, EV_READ | EV_WRITE);
    int ret = bufferevent_socket_connect(bev, (sockaddr*)&sin, sizeof(sin));
    if (ret == 0)

    // 进入事件主循环
    event_base_dispatch(base);
    event_base_free(base);

#ifdef _WIN32
    WSACleanup();
#endif

    return 0;
}

注意:客户端不用主动断开,因为服务端程序检测到客户端超时后,会主动断开与客户端的连接,客户端只需要被动接受服务端的断开事件 BEV_EVENT_EOF ,即代码第 55 ~ 59 行。这是为了演示效果,实际编码时不能这样用,即 实际使用时,客户端处理完后应立刻断开连接

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值