使用bufferevent简单实现服务器与客户端之间的即时聊天通讯
目录
使用bufferevent简单实现服务器与客户端之间的即时聊天通讯
前言
上回我们说到初学libevent框架,使用利用event_new、fifo实现进程间通讯此为无缓冲方式,承上篇所述,本文将讲述使用bufferevent利用数据缓存区实现网络通讯,文章的最后将以此为例实现客户端与服务器之间的聊天通讯。
为libevent造一个房子---架构分析图
- 盖房的套路(从外到内看)
- 装修属于自己的房间(由内而外)
数据缓冲区---bufferevent
是libevent为IO缓冲区操作提供的一种通用机制,bufferevent 由一个底层的传输端口(如套接字 )。
数据缓冲区是由读缓冲区和写缓冲区两部分组成。
创建套接字bufferevent
使用函数bufferevent_socket_new(),创建bufferevent的套接字
// struct bufferevent也是一个 event
struct bufferevent * bufferevent_socket_new(
struct event_base *base,
evutil_socket_t fd,
enum bufferevent_options options
);
// 参数options: 例如:BEV_OPT_CLOSE_ON_FREE 释放 bufferevent 时关闭底层传输端口
// 成功时返回bufferevent,失败则返回NULL
当然我们也忘不了释放函数 bufferevent_free()
void bufferevent_free(struct bufferevent *bev);
接下来就是我们为所欲为的时刻了,把我们的房间装修成想要的样子(为缓冲区添加读写操作,设置读写回调函数)
设置bufferevent回调函数
使用函数bufferevent_setcb(),设置回调函数
//对bufferevent设置回调函数
void bufferevent_setcb(
struct bufferevent *bufev,
bufferevent_data_cb readcb,//使用 bufferevent_read()读取buff中数据信息
bufferevent_data_cb writecb,//写回调只是提示你发生出去数据,没有实质作用
bufferevent_event_cb eventcb,
void *cbarg
);
//读写回调函数typedef
typedef void (*bufferevent_data_cb)(
struct bufferevent *bev,
void *ctx
);
typedef void (*bufferevent_event_cb)(
struct bufferevent *bev,
short events,
void *ctx
);
//events参数:
// EV_EVENT_READING: 读取操作时发生某事件,具体是哪种事件请看其他标志
// BEV_EVENT_WRITING:写入操作时发生某事件,具体是哪种事件请看其他标志
// BEV_EVENT_ERROR: 操作时发生错误
// BEV_EVENT_TIMEOUT:发生超时
// BEV_EVENT_EOF: 遇到文件结束指示。
// BEV_EVENT_CONNECTED:请求的连接过程已经完成实现客户端的时候可以判断
//EVUTIL_SOCKET_ERROR():关于错误的更多信息
EV_EVENT_READING | 读取操作时发生某事件,具体是哪种事件请看其他标志 |
BEV_EVENT_WRITING | 写入操作时发生某事件,具体是哪种事件请看其他标志 |
BEV_EVENT_ERROR | 操作时发生错误 |
BEV_EVENT_TIMEOUT | 发生超时 |
BEV_EVENT_EOF | 遇到文件结束指示 |
BEV_EVENT_CONNECTED | 请求的连接过程已经完成实现客户端的时候可以判断 |
对回调函数的管理--设置回调函数为启动状态或禁用态:
可以启用或者禁用 bufferevent 上的 EV_READ、EV_WRITE 或者 EV_READ | EV_WRITE 事件。没有启用读取或者写入事件时, bufferevent 将不会试图进行数据读取或者写入。
//开启
void bufferevent_enable(
struct bufferevent *bufev,
short events
);
//禁用, 对应的回调就不会被调用
void bufferevent_disable(
struct bufferevent *bufev,
short events
);
short bufferevent_get_enabled(
struct bufferevent *bufev
);
操控bufferevent中的数据
向bufferevent的输出缓冲区添加数据
int bufferevent_write(
struct bufferevent *bufev,
const void *data,
size_t size
);
从bufferevent的输入缓冲区移除数据
size_t bufferevent_read(
struct bufferevent *bufev,
void *data,
size_t size
);
为libevent客户端实现打基础
在实现通讯之前,我们需要拥有connect()作为客户端的启动链接,这时我们需要调用bufferevent_socket_connect()函数---思维导向理解为socket编程中需要connect()
int bufferevent_socket_connect( struct bufferevent *bev,
struct sockaddr *address,
int addrlen
);
- address 和 addrlen 参数跟标准调用 connect() 的参数相同
-
bufferevent 未设置套接字,该函数将为其分配一个新的流套接字, 并且设置状态为非阻塞
-
bufferevent 已设置套接字,调用 bufferevent_socket_connect() 将告知libevent 套接字还未连接,直到连接成功之前不应该对其进行读取或者写入操作。
-
连接完成之前可以向输出缓冲区添加数据
为libevent服务端实现打基础
在实现通讯之前,我们需要拥有bind()绑定与listen()监听,当然libevent为我们提供了更好的选择就是evconnlistener_new_bind()
链接监听器 - evconnlistener
struct evconnlistener *evconnlistener_new_bind(
struct event_base *base, evconnlistener_cb cb, void *ptr,
unsigned flags,
int backlog,
const struct sockaddr *sa, int socklen
);
当然我们也可以使用evconnlistener_new()函数,这两个可以二者取其一使用,推荐使用bind处理起来更简单方便,原因在于其内部为我们实现了绑定操作,感兴趣的小伙伴可查看源码对其深入研究
struct evconnlistener * evconnlistener_new(
struct event_base *base,
evconnlistener_cb cb,
void *ptr,
unsigned flags, int backlog, evutil_socket_t fd
);
回调函数的编写
typedef void (*evconnlistener_cb)(
struct evconnlistener *listener, evutil_socket_t sock,
struct sockaddr *addr,
int len,
void *ptr
);
注:两个 evconnlistener_new*()函数都分配和返回一个新的连接监听器对象。连接监听器使用 event_base 来得知什么时候在给定的监听套接字上有新的 TCP 连接到达时,监听器调用回调函数。
当然我们也忘不了释放函数 vconnlistener_free()
void evconnlistener_free(struct evconnlistener *lev);
对evconnlistener的管理--设置为启动状态或禁用态以及修改回调设置:
以下两个函数暂时禁止或者重新允许监听新连接
int evconnlistener_disable(struct evconnlistener *lev);
int evconnlistener_enable(struct evconnlistener *lev);
函数调整 evconnlistener 的回调函数和其参数
void evconnlistener_set_cb(
struct evconnlistener *lev,
evconnlistener_cb cb,
void *arg
);
demo
举个栗子?
//server.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.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));
char* p = "我已经收到了你发送的数据!";
printf("client say: %s\n", buf);
}
// 写缓冲区回调
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");
}
bufferevent_free(bev);
printf("buffevent 资源已经被释放...\n");
}
void send_cb(evutil_socket_t fd, short what, void *arg);
void cb_listener(
struct evconnlistener *listener,
evutil_socket_t fd,
struct sockaddr *addr,
int len, void *ptr)
{
printf("connect new client\n");
struct event_base* base = (struct event_base*)ptr;
// 通信操作
// 添加新事件
struct bufferevent *bev;
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
// 给bufferevent缓冲区设置回调
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);
event_add(ev, NULL);
}
void send_cb(evutil_socket_t fd, short what, void *arg)
{
char buf[1024] = {0};
struct bufferevent* bev = (struct bufferevent*)arg;
// printf("请输入要发送的数据: \n");
read(fd, buf, sizeof(buf));
bufferevent_write(bev, buf, strlen(buf)+1);
}
int main(int argc, const char* argv[])
{
// init server
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);
struct event_base* base;
base = event_base_new();
// 创建套接字
// 绑定
// 接收连接请求
struct evconnlistener* listener;
listener = evconnlistener_new_bind(base, cb_listener, base,
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
36, (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 <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
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("我是写缓冲区的回调函数...您已发送\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("服务器已连接\n");
return;
}
bufferevent_free(bev);
printf("free bufferevent...\n");
}
void send_cb(evutil_socket_t fd, short what, void *arg)
{
char buf[1024] = {0};
struct bufferevent* bev = (struct bufferevent*)arg;
// printf("请输入要发送的数据: \n");
read(fd, buf, sizeof(buf));
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);
// 连接服务器
struct sockaddr_in serv;
memset(&serv, 0, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(9876);
evutil_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 | EV_PERSIST);
// 创建一个事件
struct event* ev = event_new(base, STDIN_FILENO,
EV_READ | EV_PERSIST,
send_cb, bev);
event_add(ev, NULL);
event_base_dispatch(base);
event_base_free(base);
return 0;
}
更换你想要的服务器地址ip编译后就尽情享受聊天吧!!~
gcc server.c -o server -levent
gcc client.c -o client -levent
文中函数相关参数可翻阅libevent中文手册https://download.csdn.net/download/lemon_tea666/11247645