Linux网络编程Socket通信6-Libevent移植与使用

目录

  • libevent
    • libevent交叉编译并移植
    • libevent安装
      • 安装步骤
      • 测试代码
      • libevent执行报错解决
    • libevent_base根节点
      • event_base_new
      • event_base_free
      • event_reinit
    • event_loop循环等待事件
      • event_base_loop
      • event_base_dispatch
      • event_base_loopexit
      • event_base_loopbreak
    • event事件
      • event_new
      • event_add
      • event_del
      • event_free
      • levent事件监听流程
      • event事件实现TCP代码
    • bufferevent事件
      • bufferevent_socket_new
      • bufferevent_setcb
      • bufferevent_enable、bufferevent_disable
      • bufferevent_write
      • bufferevent_read
      • evconnlistener_new_bind
      • bufferevent_socket_connect
      • evconnlistener_free
      • bufferevent事件监听流程
    • bufferevent实现TCP服务器
    • bufferevent实现TCP客户端

libevent

libevent是一个事件通知库;封装了reactor。Libevent支持Linux、Unix和Windows。对I/O事件、信号和定时事件提供统一的处理。使用libevent_pthread库来提供线程安全支持。基于Reactor模式实现。事件驱动、高性能、轻量级、专注于网络、跨平台、支持多种I/O多路复用技术、支持I/O,定时器和信号事件、注册事件优先级。

libevent交叉编译并移植

tar zxvf libevent-2.1.8-stable.tar.gz # 解压
cd libevent-2.1.8-stable
./configure --prefix=/xxx/install --host=arm-linux-gnueabihf
make
make install
cd /xxx/install
tar -zcvf libevent-2.1.12-install.tar.gz lib/*.so*

# 将 libevent-2.1.12-install.tar.gz 复制到开发板上
# 新建文件夹:/usr/local/lib/libevent , 然后解压到该文件夹中
sudo mkdir /usr/local/lib/libevent
sudo tar -zxf ./libevent-2.1.12-install.tar.gz --strip-components 1 -C /usr/local/lib/libevent
# 开发板上添加库文件搜索路径
sudo vi /etc/ld.so.conf.d/libc.conf
# 在 /etc/ld.so.conf 文件中添加库的搜索路径
/usr/local/lib/libevent //根据自己的库路径添加
# 然后 ldconfig 生成/etc/ld.so.cache,可用ldconfig -v 查看
ldconfig

libevent安装

安装步骤

  • tar zvxf libevent-2.1.8-stable.tar.gz -C ./
  • cd libevent-2.1.8-stable
  • sudo ./configure
  • sudo make
  • sudo make install
  • ls -al /usr/local/lib | grep libevent
  • 默认如此安装的库路径:/usr/local/lib 头文件目录:/usr/lcoal/include
  • 编译的时候需要指定库名: -levent

测试代码

#include 
#include 
 
int main()
{
    char ** methods = event_get_supported_methods();//获取libevent后端支持的方法
    int i =0;
    for(i = 0; methods[i] != NULL; i++)
    {
        printf("%s\n",methods[i]);
    }
    struct event_base *base = event_base_new();
    printf("----%s\n",event_base_get_methods(base));
    return 0;
}

/**
dongfang@dongfang-virtual-machine:~/NetWorkProgramming$ ./getmethods     
epoll
poll
select
----epoll
*/

编译:gcc getmethods.c -o getmethods -levent

libevent执行报错解决

解决报错:error while loading shared libraries: libevent-2.1.so.6: cannot open shared object file: No such file or directory

ldd getmethods 
linux-vdso.so.1 (0x00007ffdcc7dd000)
libevent-2.1.so.6 => not find
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1f4e200000)
/lib64/ld-linux-x86-64.so.2 (0x00007f1f4e4ea000)

find / -name libevent-2.1.so.6 --> 找到该文件在 /usr/local/lib下面
    
LD_DEBUG=libs ./getmethods -v # 查看程序搜索库时的路径
    可发现他寻找的地址为 /usr/lib/libevent-2.1.so.6 , 所以我们需要添加软链接过去
sudo ln -s /usr/local/lib/libevent-2.1.so.6 /usr/lib/libevent-2.1.so.6

在 .libs 隐藏文件中包含全部libevent已经编译好的so文件。其中core为libevent的核心文件,libevent.so为主链接文件,会关联到其他全部so文件。在sample目录下会有已经编译好的服务器应用程序。在libevent的源码中的sample目录下面提供了很多例子。

libevent_base根节点

event_base_new

使用 libevent 函数之前需要分配一个或者多个 event_base 结构体。每个event_base 结构体持有一个事件集合**,**可以检测以确定哪个事件是激活的。**event_base_new()**函数分配并且返回一个新的具有默认设置的 event_base。函数会检测环境变量,返回一个到 event_base 的指针。如果发生错误,则返回 NULL。选择各种方法时,函数会选择 OS 支持的最快方法。

struct event_base *event_base_new(void);

event_base_free

申请到的 event_base 指针通过 event_base_free 函数进行释放。

void event_base_free(struct event_base *base)

event_reinit

如果fork出子进程,想在子进程继续使用event_base,那么子进程需要对event_base重新初始化,函数如下:

int event_reinit(struct event_base *base)

event_loop循环等待事件

event_base_loop

创建好libevent_base根节点后,需要等待事件的产生,也就是等待想要等待的事件的激活,在libevent中提供了对应的接口,类似 while(1){ epoll_wait() } 的功能函数。

int event_base_loop(struct event_base* base, int flags);
flags的取值:
   #define EVLOOP_ONCE	0x01 只触发一次,如果事件没有被触发,那么就阻塞等待
   #define EVLOOP_NONBLOCK	0x02 非阻塞方式检测事件是否触发

event_base_dispatch

大多时候我们会调用另一个api,它相当于flags=0时候的event_base_loop函数

int event_base_dispatch(struct event_base *base);
该函数相当于没有设置标志位的event_base_loop。程序会一直运行下去,直到没有需要检测的事件了,或者被结束循环的api终止。

event_base_loopexit

等待一定时间后退出循环

int event_base_loopexit(struct event_base *event_base, const struct timeval *tv)
struct timeval{
	long tv_sec;
	long tv_usec;
};

event_base_loopbreak

直接退出循环

int event_base_loopbreak(struct event_base *event_base)

event事件

event_new

生成新事件,使用 event_new()接口创建事件。

struct event *event_new(struct event_base *base, evutil_socket_t fd, short events, 
                       event_callback_fn cb, void *arg);
base: event_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 // 设置边沿触发
cb: void (*event_callback_fn)(evutil_socket_t fd, short events, void *arg);
	回调函数
arg: 传入回调函数的参数

event_add

构造事件之后,在将其添加到 event_base 之前实际上是不能对其做任何操作的。使用event_add()将事件添加到 event_base。

int event_add(struct event *ev, const struct timeval *tv);
ev:上树节点的地址
tv: NULL 永久监听,固定时间 限时等待

event_del

int event_del(struct event *ev);

event_free

void event_free(struct event *ev);

levent事件监听流程

  • 创建套接字 socket -> 绑定 bind -> 监听 listen
  • 创建 event_base(event_base_new) -> 初始化上树节点 event_new
  • 上树 event_add -> 循环监听 event_base_dispatch
  • 其他收尾等 event_base_free、close。

event事件实现TCP代码

#include 
#include 
#include "wrap.h"

#define MAX 1000
// 用于保存accept到的各个客户端的cfd以及上树节点
struct eventfd {
    evutil_socket_t fd;
    struct event *ev;
}event_fd[MAX];

void init_event_fd(void)
{
    int i = 0;
    for(i = 0;i < MAX; i++)
    {
        event_fd[i].fd = -1;
        event_fd[i].ev = NULL;
    }
}

void set_event_fd(evutil_socket_t fd, struct event* ev)
{
    int i = 0;
    for(i = 0; i < MAX; i++)
    {
        if(event_fd[i].fd == -1)
            break;
    }

    if(i == MAX)
        exit(1);

    event_fd[i].fd = fd;
    event_fd[i].ev = ev;
}

int find_event_fd(int fd)
{
    int i = 0;
    for(i = 0; i < MAX; i++)
    {
        if(event_fd[i].fd == fd)
            break;
    }
    if(i == MAX)
    {
        printf("not find fd = %d\n",fd);
        exit(1);
    }
    return i;
}

void cfd_cb(evutil_socket_t cfd, short events, void* arg)
{
    char buf[1500] = "";
    memset(buf, 0, 1500);
    int n = Read(cfd, buf, sizeof(buf));
    if(n <= 0)
    {
        perror("err or close!\n");
        int i = find_event_fd(cfd);
        event_del(event_fd[i].ev);
        event_free(event_fd[i].ev);
        Close(cfd);

        event_fd[i].fd = -1;
        event_fd[i].ev = NULL;
    }else
    {
        printf("%s\n",buf);
        Write(cfd, buf, n);
    }

}

void lfd_cb(evutil_socket_t lfd, short events, void* arg)
{
    struct event_base *base = (struct event_base*)arg;

    int cfd = Accept(lfd, NULL, NULL);
    if(cfd < 0)
    {
        perror("Accept error");
        return;
    }
    struct event *ev = event_new(base, cfd, EV_READ | EV_PERSIST, cfd_cb, NULL);
    event_add(ev, NULL);
    set_event_fd(cfd, ev);
}

int main(int argc, char* argv[])
{
    init_event_fd();
    // 创建套接字并绑定,返回服务器套接字
    int lfd = tcp4bind(8000, NULL);
    // 监听
    Listen(lfd, 128);
    // 创建event_base
    struct event_base* base = event_base_new();
    // 初始化lfd上树节点
    struct event* ev = event_new(base, lfd, EV_READ | EV_PERSIST, lfd_cb, base);
    // 上树
    event_add(ev, NULL);
    // 循环监听
    event_base_dispatch(base);

    Close(lfd);
    event_base_free(base);
	return 0;
}

bufferevent事件

bufferevent 是 libevent 中的一个事件缓冲 IO,内部实现了基本 socket recv/send 操作 ,用户只需要调用 bufferevent 的 API 即可实现数据的读写。libevent的bufferevent事件对应一个文件描述符、两个缓冲区、三个回调函数。在libevent的应用层有读写缓冲区,对应底层的读写缓冲区。底层的读缓冲区数据拷贝到应用层缓冲区会触发读回调,从应用层缓冲区将数据写入底层缓冲区会触发写回调。还有一个事件回调,用于出错、断开连接等触发的事件回调。

bufferevent_socket_new

创建新的节点

struct bufferevent *bufferevent_socket_new(struct event_base *base, 
                                          evutil_socket_t fd, int options);
base : 根节点
fd : 文件描述符
options : 
   enum bufferevent_options {
   // 释放bufferevent自动关闭底层接口fd
	BEV_OPT_CLOSE_ON_FREE = (1<<0),
	//使bufferevent能够在多线程下是安全的
	BEV_OPT_THREADSAFE = (1<<1),
   // 使事件循环中延迟运行回调
	BEV_OPT_DEFER_CALLBACKS = (1<<2),
	// 执行回调时不会在缓冲区事件上保留锁, 要求BEV_OPT_DEFER_CALLBACKS同时也被设置
   // 将来BEV_OPT_UNLOCK_CALLBACKS可能会被libevent作者考虑删除
	BEV_OPT_UNLOCK_CALLBACKS = (1<<3)
};
返回值:初始化新建节点的地址

bufferevent_setcb

设置节点回调

void bufferevent_setcb(struct bufferevent *bufev,
   bufferevent_data_cb readcb, bufferevent_data_cb writecb,
   bufferevent_event_cb eventcb, void *cbarg);
bufev: 新建节点的地址
readcb: 读回调
writecb:写回调
eventcb:事件异常回调
cbarg: 传给回调函数的参数
typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
typedef void (*bufferevent_event_cb)(struct bufferevent *bev, 
                                    short what, void *ctx);
what: 
#define BEV_EVENT_READING	0x01	/**< error encountered while reading */
#define BEV_EVENT_WRITING	0x02	/**< error encountered while writing */
#define BEV_EVENT_EOF		0x10	/**< eof file reached /对方关闭连接 */
#define BEV_EVENT_ERROR		0x20	/**< unrecoverable error encountered */
#define BEV_EVENT_TIMEOUT	0x40	/**< user-specified timeout reached */
#define BEV_EVENT_CONNECTED	0x80	/**< connect operation finished. */

bufferevent_enable、bufferevent_disable

事件回调的使能和不使能函数

int bufferevent_enable(struct bufferevent *bufev, short event);
int bufferevent_disable(struct bufferevent *bufev, short event);
event: EV_READ | EV_WRITE

bufferevent_write

发送数据

int bufferevent_write(struct bufferevent *bufev,const void *data, size_t size);
将data的数据写到buffer_event的写缓冲区中

bufferevent_read

接收数据

size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
将bufferevent的读缓冲区数据读取到data中,
同时将读取到的数据从bufferevent的读缓冲区删除。

evconnlistener_new_bind

连接侦听器,创建套接字、监听并提取

struct evconnlistener *
evconnlistener_new_bind(struct event_base *base, evconnlistener_cb cb,
   void *ptr, unsigned flags, int backlog, const struct sockaddr *sa,
   int socklen)
base: base根节点
cb: 提取套接字cfd后调用的回调
   typedef void (*evconnlistener_cb)(struct evconnlistener *evl, 
        evutil_socket_t fd, struct sockaddr *cliaddr, int socklen, void *ptr);
	evl: 链接侦听器的地址
   fd: cfd
   cliaddr: 客户端的地址信息
   socklen: 地址信息长度
   ptr: 传递给回调函数参数
ptr: 传给回调的参数
flags: 
	LEV_OPT_LEAVE_SOCKETS_BLOCKING 文件描述符为阻塞
   LEV_OPT_CLOSE_ON_FREE *关闭时自动释放
   LEV_OPT_REUSEABLE *端口复用
   LEV_OPT_THREADSAFE 分配锁,线程安全
backlog: -1 自动填充
sa: 绑定的地址信息
socklen: sa的大小
返回值:链接侦听器的地址

bufferevent_socket_connect

封装了底层的socket和connect接口,通过调用此函数将bufferevent事件和通信的socket进行绑定

int bufferevent_socket_connect(struct bufferevent *evl, 
                              const struct sockaddr *serv, int scoklen);
evl: 新建的bufferevent节点
serv: 服务器地址
socklen: 服务器长度
1、创建节点 
base = event_base_new();
int fd = socket(AF_INET, SOCK_STREAM, 0);
struct bufferevent *evl = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
struct bufferevent *evl = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
2、封装
bufferevent_socket_connect(evl, serv, socklen);

evconnlistener_free

释放链接监听器

void evconnlistener_free(struct evconnlistener *lev);

bufferevent事件监听流程

  • 创建套接字 socket -> 绑定 bind -> 监听 listen
  • 创建 event_base(event_base_new) -> 初始化上树节点 bufferevent_socket_new
  • 设置回调函数,设置完后会自动上树 bufferevent_setcb 、 设置事件使能 bufferevent_enable、 设置事件不使能 bufferevent_disable
  • 循环监听 event_base_dispatch
  • 其他收尾等 event_base_free、close。

bufferevent实现TCP服务器

参考hello-world.c更改为TCP收发数据

#include 
#include 
#include 
#include 
#ifndef _WIN32
#include 
# ifdef _XOPEN_SOURCE_EXTENDED
#  include 
# endif
#include 
#endif

#include 
#include 
#include 
#include 
#include 

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

static const int PORT = 9995;

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 *);

int main(int argc, char **argv)
{
	struct event_base *base;
	struct evconnlistener *listener;
	struct event *signal_event;

	struct sockaddr_in sin;
#ifdef _WIN32
	WSADATA wsa_data;
	WSAStartup(0x0201, &wsa_data);
#endif

	base = event_base_new(); // 创建event_base根节点
	if (!base) {
		fprintf(stderr, "Could not initialize libevent!\n");
		return 1;
	}

	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(PORT);
	// 创建链接监听器 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));

	if (!listener) {
		fprintf(stderr, "Could not create a listener!\n");
		return 1;
	}
	// 创建信号触发节点,收到信号SIGINT,调用信号signal_cb
	signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
	// 上树监听
	if (!signal_event || event_add(signal_event, NULL)<0) {
		fprintf(stderr, "Could not create/add a signal event!\n");
		return 1;
	}
	// 循环监听
	event_base_dispatch(base);
	// 释放链接监听器、信号节点、base根节点
	evconnlistener_free(listener);
	event_free(signal_event);
	event_base_free(base);

	printf("done\n");
	return 0;
}

static void
listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
    struct sockaddr *sa, int socklen, void *user_data)
{
	struct event_base *base = user_data;
	struct bufferevent *bev;
	// 创建一个bufferevent节点,释放自动关闭
	bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
	if (!bev) {
		fprintf(stderr, "Error constructing bufferevent!");
		event_base_loopbreak(base);
		return;
	}
    // 设置回调函数并上树,写回调conn_writecb, 异常事件回调conn_eventcb
	bufferevent_setcb(bev, conn_readcb, conn_writecb, conn_eventcb, NULL);
	bufferevent_enable(bev, EV_WRITE | EV_READ); // 设置读写事件使能
	// bufferevent_disable(bev, EV_READ); // 设置读时间非使能
	// 给 cfd 发送消息 MESSAGE
	bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
}

static void
conn_writecb(struct bufferevent *bev, void *user_data)
{
    // 获取缓冲区的类型
	struct evbuffer *output = bufferevent_get_output(bev);
	if (evbuffer_get_length(output) == 0) { // 缓冲区中没有数据了
		// printf("flushed answer\n");
		// bufferevent_free(bev); // 释放节点,自动关闭
	}
}

static void
conn_readcb(struct bufferevent *bev, void *user_data)
{
    char buff[1500] = "";
    int n = bufferevent_read(bev, buff, sizeof(buff)); // 读取数据
    printf("%s\n", buff);
    bufferevent_write(bev, buff, n); // 发送数据
}

static void
conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{
	if (events & BEV_EVENT_EOF) { // 如果连接已经关闭
		printf("Connection closed.\n");
	} else if (events & BEV_EVENT_ERROR) { // 如果连接发生错误
		printf("Got an error on the connection: %s\n",
		    strerror(errno));/*XXX win32*/
	}
	/* None of the other events can happen here, since we haven't enabled
	 * timeouts */
	bufferevent_free(bev); // 释放bev
}

static void
signal_cb(evutil_socket_t sig, short events, void *user_data)
{
	struct event_base *base = user_data; // 接收传入的base根节点
	struct timeval delay = { 2, 0 };

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

	event_base_loopexit(base, &delay); // 2s后退出循环接听
}

bufferevent实现TCP客户端

可以监听 STDIN 、cfd 、服务器的数据等等

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

void read_cb(struct bufferevent *bev, void *arg)
{
    char buf[1024] = {0}; 
    bufferevent_read(bev, buf, sizeof(buf));

    printf("fwq say:%s\n", buf);
    bufferevent_write(bev, buf, strlen(buf)+1);
    sleep(1);
}

void write_cb(struct bufferevent *bev, void *arg)
{
    printf("----------我是客户端的写回调函数\n"); 
}

void event_cb(struct bufferevent *bev, short events, void *arg)
{
    if (events & BEV_EVENT_EOF)
    {
        printf("connection closed\n");  
    }
    else if(events & BEV_EVENT_ERROR)   
    {
        printf("some other error\n");
    }
    else if(events & BEV_EVENT_CONNECTED)
    {
        printf("已经连接服务器...\\(^o^)/...\n");
        return;
    }    
    // 释放资源
    bufferevent_free(bev);
}

// 客户端与用户交互,从终端读取数据写给服务器
void read_terminal(evutil_socket_t fd, short what, void *arg)
{
    // 读数据
    char buf[1024] = {0};
    int len = read(fd, buf, sizeof(buf));

    struct bufferevent* bev = (struct bufferevent*)arg;
    // 发送数据, 此时发送给服务器,服务器下发回复就会触发读回调
    bufferevent_write(bev, buf, len+1);
}

int main(int argc, const char* argv[])
{
    struct event_base* base = NULL;
    base = event_base_new();
    int fd = socket(AF_INET, SOCK_STREAM, 0);

    // 通信的fd放到bufferevent中
    struct bufferevent* bev = NULL;
    bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);

    // init server info
    struct sockaddr_in serv;
    memset(&serv, 0, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(9876);
    inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);

    // 连接服务器
    bufferevent_socket_connect(bev, (struct sockaddr*)&serv, sizeof(serv));
    // 设置回调
    bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);
    // 设置读回调生效
	// bufferevent_enable(bev, EV_READ);
    // 创建事件
    struct event* ev = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST,
                                 read_terminal, bev);
    // 添加事件                     
    event_add(ev, NULL);

    event_base_dispatch(base);
    event_free(ev);    
    event_base_free(base);
    return 0;
}

你可能感兴趣的:(Linux网络编程,linux,linux,网络,单片机)