1. 创建event_base_new();
2. 使用 evconnlistener_new_bind 创建监听服务器,设置其回调函数listner_cb(),当有客户端成功连接时回调函数被调用。
3. 封装 listner_cb() ,在函数内部,完成:
4. 创建bufferevent事件对象,bufferevent_socket_new();
5. 使用bufferevent_setcb() 函数给bufferevent的 read、write、event 设置回调函数。
6. 设置读缓冲、写缓冲的使能状态
7. 启动循环 event_base_dispath();
8. 当监听的事件满足时,read_cb会被调用。
9. 释放连接 evconnlistener_free(listener); event_base_free(base);
// server.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
void read_cb(struct bufferevent *bev, void *arg){
// 每次读之前,给缓冲区清0
char buf[1024] = {0};
int read_size = 0;
// 从 bev 绑定的 cfd 中读数据
read_size = bufferevent_read(bev, buf, sizeof(buf)); // read(cfd, buf, sizeof(buf));没有文件描述符cfd,不能用read(),只能用包含文件描述符cfd 的结构体对象bev。
// 向屏幕打印客户端发来的数据
printf("client say: %s\n", buf);
write(STDOUT_FILENO, buf, read_size);
// // 发数据给客户端
// char* msg = "我是服务器,我已经收到了你发送的数据!";
// bufferevent_write(bev, msg, strlen(msg)+1); // 写缓冲一有数据,就会自动把数据刷给对端; 然后调用 write_cb 回调函数。
// sleep(2);
}
// 写缓冲区回调
void write_cb(struct bufferevent *bev, void *arg)
{
printf("I'm服务器, 成功写数据给客户端,写缓冲区回调函数被回调...\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(int fd, short events, void *arg){
// fd = STDIN_FILENO
// events = EV_READ | EV_PERSIST;
struct bufferevent* bev = (struct bufferevent*)arg;
// 从键盘读之前,清空缓冲区
char buf[1024] = {0};
read(fd, buf, sizeof(buf));
// 将从键盘读到的数据,传给对端
bufferevent_write(bev, buf, strlen(buf)+1);
}
// 一旦被回调,说明在其内部应该与客户端建立了连接, 并且已经有cfd,可以直接用。
void listener_cb(
struct evconnlistener *listener,
evutil_socket_t cfd, // 与客户端通信的socket的文件描述符cfd。
struct sockaddr *addr, int len, // 客户端的地址结构
void *ptr)
{
printf("connect new client\n");
struct event_base * base = (struct event_base *)ptr;
// 创建套接字bufferevent
struct bufferevent * bev = NULL;
bev = bufferevent_socket_new(base, cfd, BEV_OPT_CLOSE_ON_FREE); // 把 cfd 封装到 struct bufferevent *bev 中。
// 将默认关闭的读事件开启
bufferevent_enable(bev, EV_READ);
// 对bufferevent读写缓冲区 设置回调函数
bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL); // NULL 是传给三个回调的参数。
// 创建一个普通事件
struct event * ev = NULL;
ev = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST, // 从键盘持续 读,
send_cb, bev); // 一旦从键盘读到数据,就触发 ev 的 send_cb 回调函数。
event_add(ev, NULL);
}
int main(int argc, char* argv[])
{
// 服务器的地址结构
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(9999);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 创建底座
struct event_base *base = NULL;
base = event_base_new();
// 创建事件连接监听器
struct evconnlistener *listener;
// 创建套接字 --- lfd
// 绑定服务器的地址结构 ---serv, bind(lfd, serv, sizeof(serv))
// 设置监听上限 --- 36, listen(lfd, 36)
// 接收连接请求,等着客户端建立连接请求。一旦与客户端成功建立了连接,就走 cb_listener 回调函数。 accept(lfd, ...)
listener = evconnlistener_new_bind(
base,
listener_cb, base, // 回调函数与其参数
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, // 释放连接监听器会关闭底层套接字,设置端口复用
5,
(struct sockaddr *)&serv_addr, sizeof(serv_addr)
);
// 循环监听事件
event_base_dispatch(base);
evconnlistener_free(listener);
event_base_free(base);
printf("server over!\n");
return 0;
}
不能先创建bufferevent,因为需要封装进去的是cfd,而不是lfd。
lfd创建的过程应该是直接封装在evconnlister_new_bind函数中的,外部不可见,因为根本不需要我们操作lfd。
创建buffrtevent事件对象时,需要有一个客户端对应的文件描述符,而这个文件描述符只有在先监听了后才会有。
// client.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
void read_cb(struct bufferevent *bev, void *arg){
// 每次读之前,给缓冲区清0
char buf[1024] = {0};
// 从 bev 绑定的 cfd 中读数据
bufferevent_read(bev, buf, sizeof(buf)); // read(cfd, buf, sizeof(buf));没有文件描述符cfd,不能用read(),只能用包含文件描述符cfd 的结构体对象bev。
// 向屏幕打印客户端发来的数据
printf("server say: %s\n", buf);
}
// 写缓冲区回调
void write_cb(struct bufferevent *bev, void *arg)
{
printf("I'm服务器, 成功写数据给客户端,写缓冲区回调函数被回调...\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(int fd, short events, void *arg){
// fd = STDIN_FILENO
// events = EV_READ | EV_PERSIST;
struct bufferevent* bev = (struct bufferevent*)arg;
// 从键盘读之前,清空缓冲区
char buf[1024] = {0};
read(fd, buf, sizeof(buf));
// 将从键盘读到的数据,传给对端
bufferevent_write(bev, buf, strlen(buf)+1);
}
int main(int argc, char* argv[])
{
// 创建底座
struct event_base *base = NULL;
base = event_base_new();
// 创建套接字
int cfd = socket(AF_INET, SOCK_STREAM, 0);
// 创建套接字bufferevent
struct bufferevent * bev;
bev = bufferevent_socket_new(base, cfd, BEV_OPT_CLOSE_ON_FREE); // BEV_OPT_CLOSE_ON_FREE 释放 bufferevent 时关闭底层传输端口
// 服务器的地址结构
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(9999);
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
// 连接服务器
bufferevent_socket_connect(bev, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
// 给bev设置回调函数
bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);
bufferevent_enable(bev, EV_READ | EV_PERSIST);
// 创建一个普通事件, 客户端与用户交互,从终端读取数据写给服务器
struct event * ev = NULL;
ev = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST, // 从键盘持续 读,
read_terminal, bev); // 一旦从键盘读到数据,就触发 ev 的 send_cb 回调函数。
event_add(ev, NULL);
// 循环监听事件
event_base_dispatch(base);
event_free(ev);
event_base_free(base);
printf("client over!\n");
return 0;
}
客户端发送 "asas",
服务器回:
client say: asas
asas
然后,服务器发送 “aaaaaaaaaaaaaaaaaaaaaa”
客户端接收:“
server say: aaaaaaaaaaaaaaaaaaaaaa
”
正常结果图:
错误写法,如果,将客户端的下列代码注释掉:
else if(events & BEV_EVENT_CONNECTED)
{
printf("已经连接服务器...\\(^o^)/...\n");
return;
}
那么,客户端刚连上服务器,就断开连接。
错误结果图:
参考链接:
详解libevent网络库(二)---即时聊天通讯