【libevent】bufferevent的并发访问问题
一、问题
在使用libevent实现websocket服务器时,发生了并发访问的问题。
服务器程序功能主要包括实时响应Websocket客户端的控制请求,同时发送温度到客户端。
现象:
不加上温度发送功能时,程序正常运行

加上温度发送功能后,就会出现段错误,而且检查后发现bufferevent并不为空

二、原因
在我的代码中,temp_cb()用于发送温度,read_cb()用于读取客户端发送来的数据(包括连接请求和led控制等),在没有加上温度发送功能前,程序中bufferevent只用处理错误事件和接收数据事件,加上后还要处理发送温度的事件。如果在发送温度事件时,同时可能有其他事件或回调函数正在修改或访问相同的 bev 变量,可能会导致竞态条件或意外的状态更改,从而引发段错误,导致并发问题。
代码如下:
static void temp_cb(evutil_socket_t fd, short events, void *arg)
{
	 struct bufferevent		*bev = (struct bufferevent *)arg;
	 if (!bev) {
        log_error("Received NULL buffer event in temp_cb\n");
        return;
    }
    send_temperature(bev);
}
static void read_cb (struct bufferevent *bev, void *ctx)
{
    wss_session_t              *session = bev->cbarg;
    if( !session->handshaked )
    {
        do_wss_handshake(session);
        return ;
    }
    do_parser_frames(session);
    return ;
}
static void event_cb (struct bufferevent *bev, short events, void *ctx)
{
    wss_session_t              *session = bev->cbarg;
    if( events&(BEV_EVENT_EOF|BEV_EVENT_ERROR) )
    {
        if( session )
            log_warn("remote client %s closed\n", session->client);
        bufferevent_free(bev);
    }
    return ;
}
static void accept_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int len, void *arg)
{
	struct event_base               *ebase = arg;
	struct bufferevent 				*recv_bev, *send_bev;
	struct event            		*temp_event = NULL;
	struct timeval       			tv={10, 0};
    struct sockaddr_in              *sock = (struct sockaddr_in *)addr;
	wss_session_t                   *session;
	if( !(session = malloc(sizeof(*session))) )
    {
        log_error("malloc for session failure:%s\n", strerror(errno));
        close(fd);
        return ;
    }
	memset(session, 0, sizeof(*session));
    
	snprintf(session->client, sizeof(session->client), "[%d->%s:%d]", fd, inet_ntoa(sock->sin_addr), ntohs(sock->sin_port));
    log_info("accpet new socket client %s\n", session->client);
	bev_accpt = bufferevent_socket_new(ebase, fd, BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS);
	if( !bev_accpt )
    {
        log_error("create bufferevent for client for %s failed\n", session->client);
        return;
    }
	session->bev = bev_accpt;
	bufferevent_setcb(bev_accpt, read_cb, NULL, event_cb, session);
	bufferevent_enable(bev_accpt, EV_READ|EV_WRITE);
	temp_event = event_new(ebase, -1, EV_PERSIST, temp_cb, bev_accpt);
	if (!temp_event)
    {
        log_error("failed to create temp event\n");
        return;
    }
	event_add(temp_event, &tv);
    return;
}
三、分析
libevent是通过I/O多路复用来实现高效的事件处理,事件循环(event loop)会不断调用底层的 I/O 多路复用函数( select、poll 或 epoll),等待事件的发生。
libevent本身确实是一个单线程事件驱动模型。但是也可能出现并发问题:
- 多线程环境:尽管 libevent 的核心是单线程的,但如果程序是多线程的,并且多个线程尝试访问和操作同一个 bufferevent,就会出现并发问题。
- 事件回调重入:如果事件回调函数执行的时间较长,而在此期间另一个事件被触发并尝试访问同一个资源,可能会导致重入问题。
- 非线程安全代码:即使在单线程环境中,某些操作可能会触发不安全的并发访问,例如在回调中操作全局变量或共享资源。
四、解决方法
解决并发访问的问题最常用的方法是加锁,libevent 提供了一些机制来确保 bufferevent 在多线程环境下的安全性。例如,可以使用 bufferevent_lock 和 bufferevent_unlock 函数来显式地对 bufferevent 进行加锁和解锁操作。
 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号