libevent服务端,单线程应用

该博客介绍了如何使用libevent库创建一个TCP服务器,监听特定端口并处理客户端连接。libevent提供了跨平台的事件通知机制,包括监听新的连接、读写事件回调以及信号处理。在接收到新的客户端连接时,服务器会发送欢迎消息并读取客户端数据,然后回应。此外,还涉及了libevent的错误处理和信号中断机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

libevent版本:libevent-2.1.12-stable

#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>

#ifdef _WIN32
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "event.lib")
#pragma comment(lib, "event_extra.lib")
#pragma comment(lib, "event_core.lib")
#else
#include <netinet/in.h>
#include <pthread.h>
# ifdef _XOPEN_SOURCE_EXTENDED
#  include <arpa/inet.h>
# endif
#include <sys/socket.h>
#define GetCurrentThreadId() pthread_self()
#endif

#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/util.h>
#include <event2/event.h>
#include <event2/thread.h>

static const char MESSAGE[] = "Hello, World!\n";

static const int PORT = 9638;

static void listener_cb(struct evconnlistener *, evutil_socket_t,
    struct sockaddr *, int socklen, void *);
static void conn_writecb(struct bufferevent *, void *);
static void conn_readcb(struct bufferevent *, void *);
static void conn_eventcb(struct bufferevent *, short, void *);
static void signal_cb(evutil_socket_t, short, void *);
static void connlistener_errorcb(struct evconnlistener *, void *);

int main1(int argc, char **argv)
{
    printf("the main thread id: %d\n", GetCurrentThreadId());
    struct event_base *base;
    struct evconnlistener *listener;
    struct event *signal_event;

#ifdef WIN32
    WSADATA wsa_data;
    WSAStartup(0x0202, &wsa_data);
#endif

    base = event_base_new();

    if (!base)
    {
        fprintf(stderr, "Could not initialize libevent!\n");
        return 1;
    }

    struct sockaddr_in sin;
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(PORT);

    //创建服务端的socket,并绑定端口监听,当有客户端连接时,则listener_cb发生回调
    listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
        LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1,
        (struct sockaddr *)&sin, sizeof(sin));

    /*//也可以把ipv6添加到监听中去
    struct sockaddr_in6 sin6;
    memset(&sin6, 0, sizeof(struct sockaddr_in6));
    sin6.sin6_family = AF_INET6;
    sin6.sin6_port = htons(2000);
    evconnlistener *listener6 = evconnlistener_new_bind(base, listener_cb, base,
        LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1,
        (struct sockaddr*)&sin6, sizeof(sin6));
    */
    if (!listener)
    {
        fprintf(stderr, "Could not create a listener!\n");
        goto loop_basefree;
        return 1;
    }
    //设置listen监听错误事件,这个其实没什么用
    evconnlistener_set_error_cb(listener, connlistener_errorcb);

    //创建一个新的事件信号
    signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
    if (!signal_event)
    {
        fprintf(stderr, "Could not create/add a signal event!\n");
        goto loop_listener;
        return 1;
    }
    if (event_add(signal_event, NULL) < 0)
    {
        fprintf(stderr, "Could not create/add a signal event!\n");
        goto loop_signal;
        return 1;
    }

    event_base_dispatch(base); //这里面进入循环了

loop_signal:
    event_free(signal_event);
loop_listener:
    evconnlistener_free(listener);
loop_basefree:
    event_base_free(base);

#ifdef WIN32
    WSACleanup();
#endif
    printf("done\n");
    return 0;
}

/**************************************************************************************************************************
libevent的一些问题记录。
1. 读写时,可能存在最大包字节限制,因为默认为16384字节,下面这2个宏是内部定义的。
#define MAX_SINGLE_READ_DEFAULT 16384
#define MAX_SINGLE_WRITE_DEFAULT 16384
可以通过bufferevent_set_max_single_read(...),bufferevent_set_max_single_write(...)去设置最大值。

evutil_socketpair(...)在Windows上使用可能会被防火墙挡住
在上面的应用例子listener_cb()中,之所以采用定时器的方式去获取,是因为可能Windows的防火墙挡住问题,在linux上可以直接使用pipe或者evutil_socketpair
*/
/**********************************************************************************/
//当一个新的连接到来时,触发该回调被调用
//该回调是在 event_base_dispatch() 的内部被执行的,因此这里执行耗时操作可能会影响事件循环。
static void listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
    struct sockaddr *sa, int socklen, void *user_data)
{
    struct event_base *base = (struct event_base *)user_data;
    struct bufferevent *bev;
    /**将fd,也就是socket注册到事件循环中,以后所有的读写都在同一个线程中。
    如果想要读写和释放在别的线程中也能够安全操作,则需要,添加 BEV_OPT_THREADSAFE,同时需要在最开始的时候调用
    evthread_use_windows_threads()、evthread_use_pthreads()告诉平台启用多线程**/
    bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    if (!bev)
    {
        fprintf(stderr, "Error constructing bufferevent!");
        evutil_closesocket(fd);
        //强制结束main里面的event_base_dispatch,这样整个服务端结束
        event_base_loopbreak(base);
        return;
    }

    printf("the listener_cb thread id: %d, bev=%p\n", GetCurrentThreadId(), bev);

    //客户端连接的ip和端口参数
    char szClientIp[256] = { 0 };
    struct sockaddr_in *addr = (struct sockaddr_in *)sa;
    evutil_inet_ntop(addr->sin_family, &addr->sin_addr, szClientIp, sizeof(szClientIp));
    short iPort = ntohs(addr->sin_port);

    //设置读写和客户端连接状态的回调函数
    bufferevent_setcb(bev, conn_readcb, conn_writecb, conn_eventcb, user_data);

    //设置读写回调是否可用
    bufferevent_enable(bev, EV_WRITE | EV_READ);

    //向客户端写数据
    bufferevent_write(bev, MESSAGE, strlen(MESSAGE));

    printf("a new connect %s:%hu\n", szClientIp, iPort);
}


//向客户端写完数据后发生的一个回调,通常是调用bufferevent_write后发生了回调。
//该回调是在 event_base_dispatch() 的内部被执行的,因此这里执行耗时操作可能会影响事件循环。
static void conn_writecb(struct bufferevent *bev, void *user_data)
{
    printf("the conn_writecb thread id: %d, bev=%p\n", GetCurrentThreadId(), bev);
    //user_data值为bufferevent_setcb的最后一个参数
    struct evbuffer *output = bufferevent_get_output(bev);
    if (evbuffer_get_length(output) == 0)
    {
        //printf("flushed answer\n");
    }
}

//当收到客户端发送过来的数据时,该函数发生了回调
//该回调是在 event_base_dispatch() 的内部被执行的,因此这里执行耗时操作可能会影响事件循环。
static void conn_readcb(struct bufferevent *bev, void *user_data)
{
    printf("the conn_readcb thread id: %d, bev=%p\n", GetCurrentThreadId(), bev);
    struct evbuffer *input = bufferevent_get_input(bev);
    size_t len = evbuffer_get_length(input);
    if (!len)
    {
        puts("接收到的数据个数是0");
        return;
    }
    char data[1025] = "";
    size_t size = 0;
    /*从缓冲区中读取接收到的数据,如果不采用这种方式,那么只有等到下次收到用户发送的数据时才会触发。
     所以循环读取所有数据,不过实际这个缓冲区大小是足够的,默认为 16384=16k
     可以通过 bufferevent_set_max_single_read/write来修改
    */
    while (0 != (size = bufferevent_read(bev, data, 1024)))
    {
        printf("data=%s, len=%d\n", data, size);
    }
    const char *wData = "send to client!";
    bufferevent_write(bev, wData, strlen(wData) + 1);

    //主动断开与客户端的连接
    //bufferevent_free(bev);
    printf("a new conn_readcb %p\n", bev);
}

//该回调是在 event_base_dispatch() 的内部被执行的,因此这里执行耗时操作可能会影响事件循环。
static void conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{
    printf("the conn_eventcb thread id: %d, bev=%p\n", GetCurrentThreadId(), bev);
    if (events & BEV_EVENT_EOF)
    {
        printf("Connection closed.\n");
    }
    else if (events & BEV_EVENT_ERROR)
    {
        printf("Got an error on the connection: %s\n", evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
    }
    // None of the other events can happen here, since we haven't enabled timeouts
    bufferevent_free(bev);
}

static void signal_cb(evutil_socket_t sig, short events, void *user_data)
{
    printf("the signal_cb thread id: %d\n", GetCurrentThreadId());
    struct event_base *base = (struct event_base *)user_data;
    struct timeval delay = { 2, 0 };

    printf("Caught an interrupt signal; exiting cleanly in two seconds.\n");

    event_base_loopexit(base, &delay);
}

static void connlistener_errorcb(struct evconnlistener *listener, void *user_data)
{
    printf("the connlistener_errorcb thread id: %d\n", GetCurrentThreadId());
    struct event_base *base = (struct event_base *)user_data;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值