使用libevent實現一個簡單的tcp服務端


一、概述

  1.特點:

     1.事件驅動、高性能、輕量級、專注於網絡

     2.源代碼精煉、易讀

     3.跨平台

     4.支持多種I/O多路復用技術,如epoll 、poll 、select等

     5.支持I/O和信號等事件

  2.使用libevent 函數之前需要分配一個或者多個 event_base 結構體, 每個event_base結構體持有一個事件集合, 可以檢測以確定哪個事件是激活的, event_base結構相當於epoll紅黑樹的樹根節點, 每個event_base都有一種用於檢測某種事件已經就緒的 “方法”(回調函數)

通常情況下可以通過event_base_new函數獲得event_base結構。

  3.相關函數

1 struct event_base *event_base_new(void);    
  函數說明: 獲得event_base結構
  參數說明: 無
  返回值: 
      成功返回event_base結構體指針;
      失敗返回NULL;

2 void event_base_free(struct event_base *);   
    函數說明: 釋放event_base指針

3 int event_reinit(struct event_base *base);  
    函數說明: 如果有子進程, 且子進程也要使用base, 則子進程需要對event_base重新初始化, 此時需要調用event_reinit函數.
  函數參數: 由event_base_new返回的執行event_base結構的指針
  返回值: 成功返回0, 失敗返回-1

對於不同系統而言, event_base就是調用不同的多路IO接口去判斷事件是否已經被激活, 對於linux系統而言, 核心調用的就是epoll, 同時支持poll和select.

查看libevent支持的后端的方法有哪些:
const char **event_get_supported_methods(void);
函數說明: 獲得當前系統(或者稱為平台)支持的方法有哪些
參數: 無
返回值: 返回二維數組, 類似與main函數的第二個參數**argv.

const char * event_base_get_method(const struct event_base *base);
函數說明: 獲得當前base節點使用的多路io方法
函數參數: event_base結構的base指針.
返回值: 獲得當前base節點使用的多路io方法的指針

libevent在地基打好之后, 需要等待事件的產生, 也就是等待事件被激活, 所以程序不能退出, 對於epoll來說, 
我們需要自己控制循環, 而在libevent中也給我們提供了API接口, 類似while(1)的功能. 
int event_base_dispatch(struct event_base *base);   //
函數說明: 進入循環等待事件
參數說明:由event_base_new函數返回的指向event_base結構的指針
調用該函數, 相當於沒有設置標志位的event_base_loop。程序將會一直運行, 直到沒有需要檢測的事件了, 或者被結束循環的API終止。
int event_base_loopexit(struct event_base *base, const struct timeval *tv);
int event_base_loopbreak(struct event_base *base);
struct timeval {
  long    tv_sec;                    
  long    tv_usec;            
};

兩個函數的區別是如果正在執行激活事件的回調函數, 那么event_base_loopexit將在事件回調執行結束后終止循環(如果tv時間非NULL, 那么將等待tv設置的時間后立即結束循環), 而event_base_loopbreak會立即終止循環。

typedef void (*event_callback_fn)(evutil_socket_t fd, short events, void *arg);
struct event *event_new(struct event_base *base, evutil_socket_t fd, short events, event_callback_fn cb, void *arg);
函數說明: event_new負責創建event結構指針, 同時指定對應的地基base,      還有對應的文件描述符, 事件, 以及回調函數和回調函數的參數。
參數說明:
base: 對應的根節點--地基
fd: 要監聽的文件描述符
events:要監聽的事件
      #define  EV_TIMEOUT    0x01   //超時事件
      #define  EV_READ       0x02    //讀事件
      #define  EV_WRITE      0x04    //寫事件
      #define  EV_SIGNAL     0x08    //信號事件
      #define  EV_PERSIST     0x10    //周期性觸發
      #define  EV_ET         0x20    //邊緣觸發, 如果底層模型支持設置                    則有效, 若不支持則無效.
      
若要想設置持續的讀事件則: EV_READ | EV_PERSIST
cb 回調函數, 原型如下:
typedef void (*event_callback_fn)(evutil_socket_t fd, short events, void *arg);
注意: 回調函數的參數就對應於event_new函數的fd, event和arg

#define evsignal_new(b, x, cb, arg)             \                                                                    
      event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))

int event_add(struct event *ev, const struct timeval *timeout);
函數說明: 將非未決態事件轉為未決態, 相當於調用epoll_ctl函數(EPOLL_CTL_ADD), 開始監聽事件是否產生, 相當於epoll的上樹操作.
參數說明:
  ev: 調用event_new創建的事件
timeout: 限時等待事件的產生, 也可以設置為NULL, 沒有限時。

int event_del(struct event *ev);
函數說明: 將事件從未決態變為非未決態, 相當於epoll的下樹(epoll_ctl調用      EPOLL_CTL_DEL操作)操作。
參數說明: ev指的是由event_new創建的事件.

void event_free(struct event *ev);
函數說明: 釋放由event_new申請的event節點。

 

二、示例代碼

//編寫libevent服務端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <event2/event.h>

struct event *connev = NULL;
void readcb(evutil_socket_t fd,short events,void *arg){
    int n;
    char buf[1024];
    memset(buf,0x00,sizeof(buf));
    n = read(fd,buf,sizeof(buf));
    if(n<=0){
        close(fd);
        //將通訊文件描述符對應的事件從base地基上刪除
        event_del(connev);
    }else{
        write(fd,buf,n);
    }
}


//創建連接回調事件
void conncb(evutil_socket_t fd,short events ,void *arg){
    struct event_base *base = (struct event_base*)arg;

    //接收新的客戶端連接
    int cfd = accept(fd,NULL,NULL);
    if(cfd>0){
        //創建通信文件描述對應的事件並設置回調函數為readcb
        connev = event_new(base,cfd,EV_READ|EV_PERSIST,readcb,NULL);
        if(connev==NULL){
            //退出循環
            event_base_loopexit(base,NULL);
        }
        //將通信文件描述符對應的事件上event_base地基
        event_add(connev,NULL);

    }
}


int main(int argc, char const *argv[])
{
    
    //1.創建socket
    int lfd = socket(AF_INET,SOCK_STREAM,0);

    //2.設置端口復用
    int opt;
    setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

    //3.綁定
    struct sockaddr_in serv;
    bzero(&serv,sizeof(serv));
    serv.sin_addr.s_addr = htonl(INADDR_ANY);
    serv.sin_port = htons(8888);
    serv.sin_family = AF_INET;
    bind(lfd,(struct sockaddr *)&serv,sizeof(serv));

    //4.監聽
    listen(lfd,128);

    //5.創建地基
    struct event_base *base = event_base_new();
    if(base==NULL){
        perror("event_base_new error");
        return -1;
    }

    //6.創建文件描述符對應的事件
    struct event *ev = event_new(base,lfd,EV_READ|EV_PERSIST,conncb,base);
    if(ev==NULL){
        perror("event_new error");
        return -1;
    }

    //7.將新的事件節點上base地基
    event_add(ev,NULL);
    //8.進行事件分發(進入事件循環等待)
    event_base_dispatch(base);

    //9.釋放資源

    event_base_free(base);
    event_free(ev);

    close(lfd);
    return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM