目录
代码: https://github.com/WHaoL/study/tree/master/00_06_Linux_SystemCode_and_SocketCode
代码: https://gitee.com/liangwenhao/study/tree/master/00_06_Linux_SystemCode_and_SocketCode
1. Libevent
1.1 libevent特点
Libevent 是一个用C语言编写的、轻量级的开源高性能事件的框架,主要有以下几个亮点:
1. 事件驱动( event-driven),高性能;
- libevent会通过IO转接函数对事件进行检测
- 会通过回调函数进行处理
2.轻量级,专注于网络;
3.源代码相当精炼、易读;
- 现阶段我不建议阅读源码
4.跨平台,支持 Windows、 Linux、 BSD(是Unix的衍生系统) 和 Mac OS;
5.支持多种 I/O 多路复用技术, epoll、 poll、 select 和 kqueue 等;
6.支持 I/O,定时器和信号等事件;
7.支持注册事件优先级。
1.2 libevent安装
# 下载地址: http://libevent.org/
# 源码安装
# 1. 安装目录下有文件:readme或readme.md或install(也有可能大写)
# 这些文件中有相关的安装命令
# 2.从安装目录中找可执行文件: configure
# 2.1:./configure-> 检测安装环境, 生成makefile
# 2.2:make-> 编译源码, 最终生成库(动态/静态)或者可执行程序
# 2.3:sudo make install-> 安装(拷贝头文件/拷贝库/拷贝可执行程序到系统目录)
1.编译libevent程序 -> 需要添加对应的库 libevent.so
$ gcc hello-world.c -o app -l event
2.验证app运行的时候, 是否能加载到这个动态库
$ ldd app # 查看库依赖是否正常
linux-vdso.so.1 => (0x00007ffc00f91000)
`libevent-2.1.so.6 => /usr/local/lib/libevent-2.1.so.6
//能链接到,说明可以正常使用
...
####################################################################
# 链接库,不正常
[root@lwh sample]# gcc hello-world.c -o app -l event
[root@lwh sample]# ldd app
linux-vdso.so.1 => (0x00007ffcbd1dd000)
libevent-2.1.so.7 => not found # 未链接到
libc.so.6 => /lib64/libc.so.6 (0x00007f3a4d414000)
/lib64/ld-linux-x86-64.so.2 (0x00007f3a4d7e2000)
[root@lwh sample]#
# 动态库找不到:
# 首先要 找到 这个库被拷贝到了系统的哪个目录
# find / -name "libevent.so"
/usr/local/lib/libevent.so
# 解决库链接不正常的问题
1.查找动态库所在的系统目录
[root@lwh sample]# find / -name "libevent.so"
/usr/local/lib/libevent.so
2.添加动态库所在的系统目录到配置文件
sudo vim /etc/ld.so.conf
添加刚刚搜索到的路径,例如我添加的为
/usr/local/lib/
3.使配置文件生效
sudo ldconfig
[root@lwh sample]# ldconfig
[root@lwh sample]# ldd app # 此时可以正常链接到库文件了
linux-vdso.so.1 => (0x00007fffa5f64000)
libevent-2.1.so.7 => /usr/local/lib/libevent-2.1.so.7 (0x00007fbd49e0a000)
libc.so.6 => /lib64/libc.so.6 (0x00007fbd49a3c000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fbd49820000)
/lib64/ld-linux-x86-64.so.2 (0x00007fbd4a05f000)
[root@lwh sample]#
####################################################################
2. 事件处理框架 : 创建event_base
使用 libevent 函数之前需要分配一个或者多个 event_base 结构体。
每个 event_base 结构体持有一个事件集合,可以检测以确定哪个事件是激活的。
每个 event_base 都有一种用于检测哪种事件已经就绪的 “方法”,或者说后端(select/poll/epoll)。
2.1 创建和检测event_base的API
// 头文件
#include <event2/event.h>
//1.创建一个事件处理框架/实例
struct event_base * event_base_new(void);// 要掌握的函数
//2.回收一个事件处理框架/实例
void event_base_free(struct event_base * base);
// 检查event_base的后端方法 -> 封装的IO转接函数
// 1.获取libevent事件处理框架 都支持哪些IO转接函数:select poll epoll 等
const char** event_get_supported_methods(void);
// 2.获取libevent在当前操作系统下使用的是哪个IO转接函数:Linux下是epoll
const char *event_base_get_method(const struct event_base *base);
01event_method.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <event2/event.h>
int main()
{
const char** methods = event_get_supported_methods();
printf("支持的IO转接模型/函数: \n");
for(int i=0; methods[i] != NULL; ++i)
{
printf(" %s\n", methods[i]);
}
// 查看当前Linux使用的IO转接模型
// 创建结构体变量
struct event_base* base = event_base_new();
printf("current IO mode: %s\n", event_base_get_method(base));
return 0;
}
[root@lwh testcpp]# gcc 01event_method.c -o method -levent -std=c99
[root@lwh testcpp]# ./method
支持的IO转接模型/函数:
epoll
poll
select
current IO method: epoll
[root@lwh testcpp]#
2.2 event_base和fork
/*
1.如果在父进程中 event_base_new() -> 得到 struct event_base实例
2.子进程: fork() 创建子进程 -> struct event_base实例 被复制
父子进程中都有 struct event_base实例
2.1父进程中的 struct event_base -> 可以继续使用
2.2子进程中的 struct event_base -> 默认不能直接使用
需要重新初始化: event_reinit(base);
*/
int event_reinit(struct event_base* base);
02event_method.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <event2/event.h>
int main()
{
const char **methods = event_get_supported_methods();
printf("支持的IO转接模型/函数: \n");
for (int i = 0; methods[i] != NULL; ++i)
{
printf(" %s\n", methods[i]);
}
// 查看当前Linux使用的IO转接模型
// 创建结构体变量
struct event_base *base = event_base_new();
printf("current IO mode: %s\n", event_base_get_method(base));
pid_t pid = fork();
if (pid > 0)
{
// 父进程
}
else if (pid == 0)
{
// 子进程中 struct event_base 实例不能直接使用
// 需要调用event_reinit进行重新初始化
event_reinit(base);
}
return 0;
}
[root@lwh testcpp]# gcc testMethod.c -o method -levent -std=c99
[root@lwh testcpp]# ./method
3. 事件处理框架: 启动event_base循环
event_base不停的检测'委托的检测'是不是发生了,
如果发生了, event_base会调用对应的回调函数, 这个回调函数是用户委托检测事件的时候给的.
3.1 设置事件循环 : event_base_dispatch()
// 头文件
#include <event2/event.h>
// 调用这个函数之后,event_base 实例才开始工作 -> 开始对事件进行检测
// 这个函数调用之后. 应理解为是一个循环
// 内部维护的是IO转接模型
// 有一些fd的状态需要检测 -> 一直处于循环中
// 没有fd需要检测的时候 -> 循环就结束了
int event_base_dispatch(struct event_base* base); // 一般使用这个函数
参数:
- base: 事件处理框架 -> 通过event_base_new(void)得到的
#define EVLOOP_ONCE 0x01
#define EVLOOP_NONBLOCK 0x02
#define EVLOOP_NO_EXIT_ON_EMPTY 0x04
int event_base_loop(struct event_base *base, int flags);
参数:
- base: 事件处理框架 -> 通过event_base_new(void)得到的
- flag:
EVLOOP_ONCE: fd的事件只检测一次
EVLOOP_NONBLOCK: 设置非阻塞
EVLOOP_NO_EXIT_ON_EMPTY: 当fd没有事件发生的时候, 继续一直检测不退出
3.2 终止事件循环(不常用)
// 头文件
#include <event2/event.h>
struct timeval
{
long tv_sec;
long tv_usec; // 微秒
};
// - 正在进行事件检测 -> 没有事件发生, 因此直接退出
// - 正在进行事件处理 -> 将事件处理完毕之后再退出, 不会直接退出
// - 第二个参数是一个退出的延时
int event_base_loopexit(struct event_base * base, const struct timeval * tv);
// - 正在进行事件检测 -> 直接马上退出
// - 正在进行事件处理 -> 还没处理完毕, 也退出 -> 直接马上退出
int event_base_loopbreak(struct event_base * base);
4. 事件(不带缓冲区的): event
4.1 事件的创建、添加、删除
// 头文件
#include <event2/event.h>
#define EV_TIMEOUT 0x01 // 超时 -> 默认只检测一次
#define EV_READ 0x02 // 读事件 -> 默认只检测一次
#define EV_WRITE 0x04 // 写事件 -> 默认只检测一次
#define EV_SIGNAL 0x08 // 信号事件-> 默认只检测一次
#define EV_PERSIST 0x10 // 设置:上边的事件是否持续的进行检测
#define EV_ET 0x20 // 边沿模式(非阻塞)
typedef void (*event_callback_fn)(evutil_socket_t fd,short what,void *arg);
参数:
- fd: 文件描述符, 对应event_new函数的第2个参数fd
- what: 检测的fd对应的事件 , 对应event_new函数的第3个参数
- arg: 对应event_new函数的参数arg
// 创建事件
// evutil_socket_t -> int
struct event* event_new(struct event_base * base,
evutil_socket_t fd,short what,
event_callback_fn cb,void * arg);
参数:
- base: 事件处理框架 -> 通过event_base_new(void)得到的
- fd: 通过event_new()函数对该fd进行封装 -> 得到 struct event
- 通过open()得到 -> 文件操作( 普通文件/ 伪文件(管道文件) )
- 通过socket()得到 - > 套接字
- 通过pipe()得到 -> 匿名管道
- what: 要检测fd的什么事件, 指定方式参考上边的宏
- cb: 当检测到what参数对应的事件发生了之后, event_base 实例会调用该回调函数进行处理
- arg: 给回调函数cb传参
// 添加事件
// 把当前事件(fd封装成的event),添加到处理框架(event_base / epoll树)上libevent才会帮我们去监测
struct timeval {
long tv_sec;
long tv_usec; // 微秒
};
int event_add(struct event * ev,const struct timeval * tv);
- tv: 超时时长
- 如果检测的事件在指定的时间之内没有发生, 这个事件对应的回调函数也会被执行
// 删除要检测的事件
// 即:从正在监测的fd集合中移除这个fd!!!!
int event_del(struct event * ev);
// 释放事件资源
// 释放 事件对应的资源!!!!
void event_free(struct event * event);
4.2 练习:
1.通过struct event 事件:处理管道通信
2.使用有名管道 -> 没有血缘关系的进程间通信
mkfifo myfifo
03read_fifo.c
// 03read_fifo.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <event2/event.h>
void cb_read(evutil_socket_t fd, short what, void *arg)
{
// 读数据
char buf[1024] = {0};
read(fd, buf, sizeof(buf));
printf("recv data: %s\n", buf);
// 当前是不是监测的读事件
printf("what is read: %s\n", what & EV_READ ? "yes" : "no");
// 当前是否超时
printf("what is timeout: %s\n", what & EV_TIMEOUT ? "yes" : "no");
}
int main()
{
// 1. 打开管道文件
int fd = open("myfifo", O_RDONLY);
if(fd == -1)
{
perror("open");
exit(0);
}
// 2. 将这个fd封装成事件
// 2.1创建事件处理框架
struct event_base* base = event_base_new();
// 2.2创建事件
struct event* r_ev = event_new(base, fd, EV_READ|EV_PERSIST, cb_read, NULL);
// 3.uct timeval val;
val.tv_sec = 2;
val.tv_usec = 0;
// event_add(r_ev, NULL);
event_add(r_ev, &val); // 超时时长2s, 2s之后回调函数会被强制执行
// 4.事件循环
event_base_dispatch(base);
// 结束事件循环后
// 5.资源释放
event_free(r_ev);//释放事件
event_base_free(base);//释放事件框架
return 0;
}
03write_fifo.c
// 03write_fifo.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <event2/event.h>
void cb_write(evutil_socket_t fd, short what, void *arg)
{
// 写数据
static int num = 0;
char buf[1024] = {0};
sprintf(buf, "hello, %d\n", num++);
write(fd, buf, strlen(buf)+1);
// 当前监测的是写事件吗
printf("what is write: %s\n", what & EV_WRITE ? "yes" : "no");
sleep(5);
}
int main()
{
// 1. 打开管道文件
int fd = open("myfifo", O_WRONLY);
if(fd == -1)
{
perror("open");
exit(0);
}
// 2. 将这个fd封装成事件
// 2.1创建事件处理框架
struct event_base* base = event_base_new();
// 2.2创建事件
struct event* w_ev = event_new(base, fd, EV_WRITE|EV_PERSIST, cb_write, NULL);
// 3.将事件添加到处理框架上
event_add(w_ev, NULL);
// 4.启动事件循环
event_base_dispatch(base);
// 5.资源释放
event_free(w_ev);
event_base_free(base);
return 0;
}
4.3 事件的优先级设置
// 头文件
#include <event2/event.h>
// EVENT_MAX_PRIORITIES == 256
// 初始化事件优先级有多少级(用户自己指定有几级)
int event_base_priority_init(struct event_base * base,int n_priorities);
参数:
- base: event_base_new()得到的
- n_priorities: 指定有多少个级别, 假设指定值为5
- 级别: 0, 1, 2, 3, 4, 其中0的级别最高, 4级别最低
// 获取当前可用的等级的个数(会得到用户自己设置的优先级的级别数)
int event_base_get_npriorities(struct event_base * base);
// 给事件设置等级
int event_priority_set(struct event *event, int priority);
参数:
- event: 初始化得到的某一事件实例
- priority: 按照 event_base_priority_init() 指定的等级范围指定就行
5. 事件(带缓冲区的): bufferevent
带缓冲区的事件专注于:套接字通信
5.1概念理解
bufferevent 理解:
- 是libevent为IO缓冲区操作提供的一种通用机制
- bufferevent 由一个底层的传输端口(如套接字 ), 一个读取缓冲区和一个写入缓冲区组成。
- 与通常的事件在底层传输端口已经就绪, 可以读取或者写入的时候执行回调不同的是, bufferevent在读取或者写入了足够量的数据之后调用用户提供的回调。
回调 - 缓冲区对应的操作
每个 bufferevent 有两个数据相关的回调
- 读取回调: 从底层传输端口读取了任意量的数据之后会调用读取回调(默认)
- 当libevent维护的读缓冲区有数据的时候(从内核缓冲区中读过来的,libevent自动从内核读到libevent自己的读缓冲区中), 调用缓冲区的读回调(调用我们自己的读回调函数,把数据从libevent的读缓冲区,读到用户自定义的内存中)
- 数据量到达多少时,用户的读回调被调用, 这是可以设置的
- 写入回调: 输出缓冲区中足够量的数据被清空到底层传输端口后写入回调会被调用(默认)
- 当写缓冲区中的数据被写入到内核缓冲区(数据积累到一定的量)之后 -> libevent调用用户的写缓冲区的回调函数
- 这个回调函没什么用
5.2创建/释放基于套接字的bufferevent
// 主要应用于网络套接字通信 -> socket()
int fd = socket(af_inet, sock_stream, 0);
// 带缓冲区的event事件
struct bufferevent *bufferevent_socket_new(
struct event_base *base,
evutil_socket_t fd,
enum bufferevent_options options
);
参数:
- base: event_base_new()得到的实例 // 事件处理框架
- fd: 通信的套接字 // socket得到的fd
- options: BEV_OPT_CLOSE_ON_FREE // 当释放当前bufferevent时候, 自动关闭封装的fd
// 释放bufferevent的资源
void bufferevent_free(struct bufferevent *bev);
5.3 在bufferevent上启动链接
/*
1. 如果还没有为bufferevent 设置套接字,调用该函数将为其分配一个新的流套接字,并且设置为非阻塞的
bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
2. 如果已经为 bufferevent 设置套接字,调用bufferevent_socket_connect() 将告知 libevent
套接字还未连接,直到连接成功之前不应该对其进行读取操作。
3. 连接完成之前可以向输出缓冲区添加数据。
*/
// 连接服务器函数
int bufferevent_socket_connect(
struct bufferevent *bev,
struct sockaddr *address,
int addrlen
);
参数:
- bev: 通过bufferevent_socket_new()得到的 // 带缓冲区的event事件
- address: 要连接的服务器的IP和端口 //服务器的IP和Port
- addrlen: address参数对应的内存大小
5.3.1bufferevent读写缓冲区的回调操作
//----------------------------------------------------------------------//
// 读写回调函数
// 读回调--自定义
// 检测到有数据从内核缓冲区进入到libevent的读缓冲区中,调用我们的 read_cb()
// read_cb()读回调内部,调用bufferevent_read(),把libevent读缓冲区中的数据,读取到我们的内存中
// bufferevent_read()
// 将数据从用户自定义的内存中,发送到libevent的写缓冲区!!!!!!
// 写回调--自定义
// libevent会自己把数据从自己的写缓冲区发送到内核的写缓冲区中,当检测到fd的内核缓冲区中有数据后,
// 会调用我们的write_cb()写回调函数,(我们这个写回调内部其实不做任何处理逻辑)
typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
参数:
- bev: bufferevent_setcb() 函数的第一个参数, 被传递到该函数中
- ctx: bufferevent_setcb() 函数中的最后一个参数 cbarg, 被传递到该函数中
//----------------------------------------------------------------------//
// 出现其他情况时的回调函数--自定义
typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short events, void *ctx);
参数:
- bev: 从bufferevent_setcb()函数中的第一个参数传入的
- events: 可以检测到的事件, 检测方式: events & BEV_EVENT_CONNECTED
EV_EVENT_READING:读取操作时发生某事件,具体是哪种事件请看其他标志。
- 读操作产生了异常, 举例: 读缓冲区没数据并且做了读操作(读不阻塞)
BEV_EVENT_WRITING:写入操作时发生某事件,具体是哪种事件请看其他标志。
- 写操作产生了异常, 举例: 写缓冲区满了并且做了写操作(写不阻塞的情况下)
BEV_EVENT_ERROR:操作时发生错误。关于错误的更多信息
- 请调用 EVUTIL_SOCKET_ERROR()。
BEV_EVENT_TIMEOUT:发生超时。
BEV_EVENT_EOF:遇到文件结束指示。
- 通信的另外一端已经关闭了连接
BEV_EVENT_CONNECTED:请求的连接过程已经完成
- 这个判定是在客户端做的
- ctx: bufferevent_setcb() 函数中的最后一个参数 cbarg, 被传递到该函数中
//----------------------------------------------------------------------//
// 设置回调函数
void bufferevent_setcb(struct bufferevent *bufev,
bufferevent_data_cb readcb,
bufferevent_data_cb writecb,
bufferevent_event_cb eventcb, void *cbarg
);
参数:
- bufev: 带缓冲的事件的实例, 通过 bufferevent_socket_new() 得到的
- readcb: 读缓冲区对应的回调, 当 缓冲区有数据的时候 这个函数被调用
- writecb: 写缓冲区对应的回调, 当 缓冲区中的数据被发送到内核缓冲区中之后 函数被调用
- 发送到内核, 封装的fd对应的内核的写缓冲区
- eventcb: 某些事件被触发(读写以外的事件), 对应的回调函数
- cbarg: 给以上三个回调函数传参
5.3.2禁用、启用缓冲区
/*
可以启用或者禁用 bufferevent 上的 EV_READ、EV_WRITE 或者 EV_READ | EV_WRITE 事件。
没有启用读取或者写入事件时, bufferevent 将不会试图进行数据读取或者写入。
*/
// 缓冲的读操作默认是禁用的 -> 读回调函数默认不会被调用
// 缓冲的写操作默认是可用的 -> 写回调函数默认会被调用
void bufferevent_enable(struct bufferevent *bufev, short events);
//bufferevent_enable(bev, EV_READ); -> 做这个操作就行
//bufferevent_enable(bev, EV_WRITE);
//bufferevent_enable(bev, EV_WRITE | EV_READ);
// 设置某个事件无效
void bufferevent_disable(struct bufferevent *bufev, short events);
//bufferevent_disable(bev, EV_READ);
//bufferevent_disable(bev, EV_WRITE);
//bufferevent_disable(bev, EV_WRITE | EV_READ);
// 获取缓冲区对应的有效事件
short bufferevent_get_enabled(struct bufferevent *bufev);
5.3.3操作bufferevent缓冲区中的数据
// 向bufferevent的输出缓冲区添加数据
// 将数据从用户自定义的内存中,发送到libevent的写缓冲区!!!!!!
int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);
参数:
- bufev: 带缓冲区的事件实例
- data: 要发送的数据
- size: 要发送的数据的实际长度
// 从bufferevent的输入缓冲区移除数据
// 将数据从libevent的读缓冲区,读到用户自定义的内存中!!!!!!
size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
参数:
- bufev: 带缓冲区的事件实例
- data: 接收数据的一块缓冲区(用户内存)
- size: 接收数据的内存的实际大小(data参数内存大小)
5.4使用bufferevent实现套接字通信(client)
1.客户端-client伪代码
// 实现读回调
void read_cb(struct bufferevent *bev, void *ctx)
{
// 当这个回调被调用 -> libevent管理的读缓冲区中有数据
// 将libevent管理的读缓冲区中的数据 -> 读到用户自己的内存中
char buf[1024];
bufferevent_read(bev, buf, sizeof(buf));
// 数据的处理
// .........
// 回复数据给服务器
char* p = "hello, server";
// 将数据写入到libevent管理的写内存中
// 这些数据会被libevent自动传输到fd对应的内核的写缓冲区中 -> 这步完成之后-> 写回调被调用
bufferevent_write(bev, p, strlen(p)+1);
}
// 实现写回调
// 其实没什么作用
void write_cb(struct bufferevent *bev, void *ctx)
{
printf("数据已经被成功写入到内核中...\n");
}
// 实现事件的回调
void event_cb(struct bufferevent *bev, short events, void *ctx)
{
// 判定是不是成功连接了服务器
if(events & BEV_EVENT_CONNECTED)
{
printf("连接服务器成功!!!\n");
}
else if()
{
}
}
int main()
{
// 0. 创建事件处理框架
struct event_base *base = event_base_new();
// 1. 创建通信的套接字
int fd = socket(af_inet, sock_stream, 0);
// 2. 将fd封装 -> bufferevent
// 带缓冲区的事件直接就添加到了event_base实例上 -> 因此不需要关心
struct bufferevent* bev = bufferevent_socket_new(base, fd,BEV_OPT_CLOSE_ON_FREE);
// 3. 设置缓冲区 bev -> 添加回调
bufferevent_setcb(bev, read_cb, write_cb, event_cb, arg);
// 4. 读缓冲区默认不可用, 设置为可用
bufferevent_enable(bev, EV_READ);
// 4.5 连接服务器
struct sockaddr_in serverAddr;
// init serverAddr
bufferevent_socket_connect(bev, &serverAddr, sizeof(serverAddr));
// 5. 进入到事件循环
event_base_dispatch(base);
// 6. 释放资源
bufferevent_free(bev);
event_base_free(base);
}
6. 链接监听器evconnlistener
6.1创建和释放evconnlistener
#include <event2/listener.h>
// 回调函数--自定义
typedef void (*evconnlistener_cb)(
struct evconnlistener *listener,
evutil_socket_t sock,
struct sockaddr *addr,
int len,
void *ptr
);
参数:
- listener: evconnlistener_new()函数的返回值
- sock: 和客户端连接成功之后, 得到的用于通信的文件描述符
- addr: 存储了客户端的地址信息, ip , port
- len: addr对应的内存大小
- ptr: evconnlistener_new()函数的第三个参数
// 第一种( 相当于listen()+accept() )
// 该函数的fd需要使用者自己创建
// 创建完成之后 -> bind
// 该函数可以帮做我们监听
// 帮我们做了等待并接受客户端连接 accept -> 如果有连接请求进入到回调(第二个参数)
struct evconnlistener* evconnlistener_new(
struct event_base *base,
evconnlistener_cb cb, // 回调函数, 接受了连接请求之后的回调
void *ptr, // 给回调传参
unsigned flags, // 指定一些相关的属性
int backlog, // 监听的时候用的 == listen()的第二个参数
evutil_socket_t fd // 用于监听的并且已经绑定成功了的fd
);
// 第二种------推荐使用 ( 相当于socket()+bind()+listen()+accept() )
// 创建监听的套接字、绑定、设置监听、等待并接受客户端连接 -> 如果有连接调用第二个参数中的回调函数
struct evconnlistener *evconnlistener_new_bind(
struct event_base *base,
evconnlistener_cb cb, // 接受新连接之后的连接函数
void *ptr,
unsigned flags,
int backlog, // listen的第二个参数
const struct sockaddr *sa, // 本地的IP和端口
int socklen // struct sockaddr结构体大小
);
参数:
- flags:
LEV_OPT_CLOSE_ON_FREE: 自动关闭底层套接字
LEV_OPT_REUSEABLE: 设置端口复用
// 释放资源
void evconnlistener_free(struct evconnlistener *lev);
- 启用和禁用 evconnlistener(基本上用不到)
#include <event2/listener.h>
// 设置无效之后, 就不监听连接请求了
int evconnlistener_dis able(struct evconnlistener *lev);
int evconnlistener_enable(struct evconnlistener *lev);
- 调整 evconnlistener 的回调函数(基本上用不到)
#include <event2/listener.h>
// 默认是不需要设置的
// 当某些条件下, 处理逻辑有变化的时候, 就可以替换原来的回调函数
void evconnlistener_set_cb(struct evconnlistener *lev, evconnlistener_cb cb, void *arg);
6.2使用bufferevent实现套接字通信
2.服务器端 -> server伪代码
// 实现读回调
void read_cb(struct bufferevent *bev, void *ctx)
{
// 当这个回调被调用 -> libevent管理的读缓冲区中有数据
// 将libevent管理的读缓冲区中的数据 -> 读到自己的内存中
char buf[1024];
bufferevent_read(bev, buf, sizeof(buf));
// 数据的处理
// .........
// 回复数据给服务器
char* p = "hello, server";
// 将数据写入到libevent管理的写内存中
// 这些数据会被libevent传输到fd对应的内核的写缓冲区中 -> 这步完成之后-> 写回调被调用
bufferevent_write(bev, p, strlen(p)+1);
}
// 实现写回调
void write_cb(struct bufferevent *bev, void *ctx)
{
printf("数据已经被成功写入到内核中...\n");
}
// 实现事件的回调
void event_cb(struct bufferevent *bev, short events, void *ctx)
{
// 判定是不是客户端断开了连接
if(events & BEV_EVENT_EOF)
{
printf("xxxxx!!!\n");
}
else if()
{
}
}
// 接受了新连接, 处理操作
void (*evconnlistener_cb)(
struct evconnlistener *listener,
evutil_socket_t sock,
struct sockaddr *addr,
int len,
void *ptr
)
{
struct event_base* base = (struct event_base*)ptr;
// 有了通信的fd, 通信 -> bufferevent
struct bufferevent* bev =
bufferevent_socket_new(base, sock, BEV_OPT_CLOSE_ON_FREE);
// 设置缓冲区 bev -> 添加回调
bufferevent_setcb(bev, read_cb, write_cb, event_cb, arg);
// 读缓冲区默认不可用, 设置为可用
bufferevent_enable(bev, EV_READ);
}
int main()
{
// 0. 创建事件处理框架
struct event_base *base = event_base_new();
// 1. 创建连接监听器
struct sockaddr_in addr;
// init addr
// 相当于: socket -> bind -> listen -> accept
struct evconnlistener * mylisten = evconnlistener_new_bind(
base, listen_cb, base,
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 128,
(struct sockaddr*)&addr, // 本地的IP和端口
sizeof(addr));
// 5. 进入到事件循环
event_base_dispatch(base);
// 6. 释放资源
evconnlistener_free(mylisten);
event_base_free(base);
}
7. libevent TCP server client
server.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>
// 读缓冲区回调
void read_cb(struct bufferevent *bev, void *arg)
{
char buf[1024] = {0};
bufferevent_read(bev, buf, sizeof(buf));
printf("\nclient say: %s", buf);
// 发数据给客户端
char *p = "我已经收到了你发送的数据!";
bufferevent_write(bev, p, strlen(p) + 1);
}
// 写缓冲区回调
void write_cb(struct bufferevent *bev, void *arg)
{
printf("I am Server's write_cb() ...\n");
}
// 事件
void event_cb(struct bufferevent *bev, short events, void *arg)
{
if (events & BEV_EVENT_EOF)
{
printf("disconnected with client...\n");
}
else if (events & BEV_EVENT_ERROR)
{
printf("some other error\n");
}
bufferevent_free(bev);
printf("free server's communication bufferevent...\n");
}
void cb_listener(
struct evconnlistener *listener,
evutil_socket_t cfd, //accept()的返回值
struct sockaddr *addr,
int len, void *ptr)
{
printf("a new client connected with me!\n");
// 把通信的cfd,通过bufferevent进行封装(即封装为一个事件)
struct event_base *base = (struct event_base *)ptr;
struct bufferevent *bev;
bev = bufferevent_socket_new(base, cfd, BEV_OPT_CLOSE_ON_FREE);
// 给bufferevent缓冲区设置回调
bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);
bufferevent_enable(bev, EV_READ);
}
int main(int argc, const char *argv[])
{
struct event_base *base = event_base_new();
// init serverAddr
struct sockaddr_in serv;
memset(&serv, 0, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(9876);
serv.sin_addr.s_addr = htonl(INADDR_ANY);
//创建监听套接字lfd + 绑定bind + 监听listen + 接收连接请求accept
struct evconnlistener *listener;
listener = evconnlistener_new_bind(base, cb_listener, base,
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
5, (struct sockaddr *)&serv, sizeof(serv));
event_base_dispatch(base);
evconnlistener_free(listener);
event_base_free(base);
return 0;
}
client.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
// 接收数据操作顺序
// 1. 对方发送数据到客户端的内核缓冲区
// 2. libevent自动把数据,从内核缓冲区读取到libevent的读缓冲区
// 3. 调用用户自定义的read_cb()函数,read_cb()内部调用bufferevent_read()
// 把数据从libevent的读缓冲区,读到用户自定义内存中
// 发送数据操作顺序
// 1.不带缓冲区的事件和终端的标准输入STDIN_FILENO,建立连接
// 2.终端发送数据,到,不带缓冲区的事件
// 3.不带缓冲区的事件检测到有数据到达内核
// 4.调用send_cb(),send_cb()中,先把标准输入数据读到用户自己的内存中,
// 然后调用bufferevent_write()
// 把数据写入到带缓冲区的event的,libevent的写缓冲区中
// 5.数据达到一定的量,libevent会把自己写缓冲区的数据,发送到内核写缓冲区中
// 6.检测到有数据到达内核写缓冲区,会调用write_cb()
// (其实该函数没有完成什么又有意义的实际逻辑)
// 7. libevent 负责把数据从内核中发送出去(发送给服务器)
void read_cb(struct bufferevent *bev, void *arg)
{
char buf[1024] = {0};
bufferevent_read(bev, buf, sizeof(buf));
printf("Server say: %s\n", buf);
}
void write_cb(struct bufferevent *bev, void *arg)
{
printf("I am Client's write_cb()\n");
}
void event_cb(struct bufferevent *bev, short events, void *arg)
{
if (events & BEV_EVENT_EOF)
{
printf("disconnected with server...\n");
}
else if (events & BEV_EVENT_ERROR)
{
printf("some other error\n");
}
else if (events & BEV_EVENT_CONNECTED)
{
printf("connected with server !\n");
return;
}
bufferevent_free(bev);
printf("free client's communication bufferevent...\n");
}
void send_cb(evutil_socket_t fd, short what, void *arg)
{
char buf[1024] = {0};
//printf("请输入要发送的数据: \n");
read(fd, buf, sizeof(buf));
struct bufferevent *bev = (struct bufferevent *)arg;
bufferevent_write(bev, buf, strlen(buf) + 1);
}
int main(int argc, const char *argv[])
{
// 事件处理框架
struct event_base *base;
base = event_base_new();
//带缓冲区的事件
struct bufferevent *bev;
bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
//指定为-1时,会帮助我们创建一个用于通信的文件描述符
// 连接服务器
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,
send_cb, bev); //有输入就调用回调函数,把数据发送给server
event_add(ev, NULL);
event_base_dispatch(base);
event_free(ev);
event_base_free(base);
return 0;
}
target:server.c client.c
gcc server.c -o server -levent
gcc client.c -o client -levent
.PHONY:clean
clean:
-rm *.o server client -f