本文实现了基于libevent事件驱动的网络库和zlib压缩库的服务端和客户端进行文件传输。
实现细节
在libevent上建立过滤器,使得客户端和服务端的数据I/O每次经过过滤器,在客户端的写过滤器中将原始数据进行压缩为二进制,然后发送。在服务端的读过滤器中将网络传输过来的二进制数据进行解压缩而后进行写文件。
在客户端发送完文件数据时,需要将文件关闭并释放某些资源,如果在写过滤器中仅仅判断src中无数据读入就关闭文件指针的话,此时真正的数据可能还没有发送完(CPU处理数据的速度远远大于内存的I/O速度),所以我们必须判断真正发送数据的缓冲中到底还有没有数据,如果有数据则等待数据发送发后再关闭文件。
客户端
#include <event2/event.h>
#include <iostream>
#include <cstring>
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <unistd.h>
#include <zlib.h>
#define IP "127.0.0.1"
#define PORT 8888
#define FILEPATH "001.txt"
using namespace std;
static int sendCnt = 0;
struct ClientStatus {
FILE *fp = nullptr;
bool startSend = false;
z_stream *z_output = nullptr;
bool isfpEnd = false;
~ClientStatus() {
if (z_output)
deflateEnd(z_output);
delete z_output;
z_output = nullptr;
if (fp)
fclose(fp);
fp = nullptr;
}
};
bufferevent_filter_result
filter_in(evbuffer *src, evbuffer *dest, ev_ssize_t limit, bufferevent_flush_mode mode, void *arg) {
return BEV_OK;
}
bufferevent_filter_result
filter_out(evbuffer *src, evbuffer *dest, ev_ssize_t limit, bufferevent_flush_mode mode, void *arg) {
ClientStatus *clientstatus = static_cast<ClientStatus *>(arg);
cout << "filter_out" << endl;
//还没开始,接收OK后开始
//开始压缩文件
//取出buffer中数据的引用
evbuffer_iovec v_in[1];
int buffer_len = evbuffer_peek(src, -1, nullptr, v_in, 1);
if (buffer_len <= 0) {
//没有数据
//调用write回调,清理空间
if (clientstatus->isfpEnd) {
return BEV_OK;
}
return BEV_NEED_MORE;
}
z_stream *p = clientstatus->z_output;
if (!p)return BEV_ERROR;
//zlib输入数据大小
p->avail_in = v_in[0].iov_len;
//zlib输入数据地址
p->next_in = (Byte *) v_in[0].iov_base;
//申请空间大小
evbuffer_iovec v_out[1];
evbuffer_reserve_space(dest, BUFSIZ, v_out, 1);
//zlib输出空间大小
//zlib输出空间地址
p->avail_out = v_out[0].iov_len;
p->next_out = (Byte *) v_out[0].iov_base;
int result = deflate(p, Z_SYNC_FLUSH);
if (result != Z_OK) {
cerr << "deflate failed" << endl;
}
//压缩用了多少数据,从source evbuffer中移除
//压缩后数据传入dest evbuffer
//p->avail_in未处理数据的大小 zlibedLen:压缩了多少
size_t zlibedLen = v_in[0].iov_len - p->avail_in;
//p->avail_out剩余空间大小 zliboutLen:压缩后大小
size_t zliboutLen = v_out[0].iov_len - p->avail_out;
//remove src data
evbuffer_drain(src, zlibedLen);
cout << "deflate data:" << zlibedLen << ",deflated data : " << zliboutLen << endl;
v_out[0].iov_len = zliboutLen;
evbuffer_commit_space(dest, v_out, 1);
sendCnt += zliboutLen;
return BEV_OK;
}
void read_cb(bufferevent *bev, void *arg) {
auto *clientstatus = static_cast<ClientStatus *>(arg);
// 002接收服务端发送的OK
char data[BUFSIZ] = {0};
bufferevent_read(bev, data, sizeof(data) - 1);
if (strcmp(data, "OK") == 0) {
clientstatus->startSend = true;
cout << data << endl;
//开始发送文件,触发写入回调
bufferevent_trigger(bev, EV_WRITE, 0);
}
}
void write_cb(bufferevent *bev, void *arg) {
cout << "write_cb" << endl;
auto *clientstatus = static_cast<ClientStatus *>(arg);
FILE *fp = clientstatus->fp;
if (!fp)return;
//读取文件
char data[BUFSIZ] = {0};
size_t len = fread(data, 1, sizeof(data), fp);
if (len <= 0) {
clientstatus->isfpEnd = true;
//判断缓冲是否有数据,如果有就刷新
//获取过滤器绑定的buffer
bufferevent *be = bufferevent_get_underlying(bev);
//获取输出缓冲及其大小
evbuffer *out_evb = bufferevent_get_output(be);
size_t buflen = evbuffer_get_length(out_evb);
if (buflen > 0) {
//有数据,刷新过滤器缓冲,不刷新缓冲,不会再次进入该函数
//将过滤器输出缓冲区必须要刷出,不然文件无法关闭
// 刷出的过程就是socket数据发送的过程,耗时
bufferevent_flush(bev, EV_WRITE, BEV_FINISHED);
return;
}
if (clientstatus->isfpEnd) {
delete clientstatus;
bufferevent_free(bev);
cout << "send cnt = " << sendCnt << endl;
return;
}
}
bufferevent_write(bev, data, len);
}
void event_cb(bufferevent *bev, short events, void *arg) {
}
void client_event_cb(bufferevent *bev, short events, void *arg) {
cout << "client_event_cb" << events << endl;
if (events & BEV_EVENT_CONNECTED) {
//001发送文件名
cout << "BEV_EVENT_CONNECTED" << endl;
bufferevent_write(bev, FILEPATH, strlen(FILEPATH));
//设置读取写入和事件回调
FILE *fp = fopen(FILEPATH, "rb");
//初始化zlib上下文,默认压缩方式
auto *clientstatus = new ClientStatus();
clientstatus->z_output = new z_stream();
deflateInit(clientstatus->z_output, Z_DEFAULT_COMPRESSION);
if (!fp) {
cout << "open failed" << endl;
}
clientstatus->fp = fp;
//创建输出过滤
bufferevent *bevfilter = bufferevent_filter_new(bev,
nullptr, //输入过滤
filter_out, //输出过滤
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS,
nullptr,//请理回调
clientstatus);//参数
bufferevent_enable(bevfilter, EV_READ | EV_WRITE);
bufferevent_setcb(bevfilter, read_cb, write_cb, event_cb, clientstatus);
} else {
cout << "other events" << endl;
}
}
int main(int argc, char *argv[]) {
//忽略管道信号,发送数据给已关闭的socket会使程序崩溃
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
return 1;
//创建libevent的上下文
event_base *base = event_base_new();
if (!base) {
cout << "base create failed!" << endl;
}
sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
evutil_inet_pton(AF_INET, IP, &sin.sin_addr.s_addr);
bufferevent *bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
//只绑定事件回调,用来确认连接成功
bufferevent_enable(bev, EV_READ | EV_WRITE);
bufferevent_setcb(bev, nullptr, nullptr, client_event_cb, nullptr);
//建立连接
bufferevent_socket_connect(bev, (sockaddr *) &sin, sizeof(sin));
//事件分发处理
if (base) event_base_dispatch(base);
//销毁资源
if (base) event_base_free(base);
return 0;
}
服务端
#include <event2/event.h>
#include <iostream>
#include <cstring>
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <zlib.h>
#define PORT 8888
using namespace std;
struct Status {
bool start = false;
FILE *fp = nullptr;
z_stream *z_input = nullptr;
~Status() {
if (fp)
fclose(fp);
delete z_input;
}
};
static int recvCnt = 0;
bufferevent_filter_result
filter_in(evbuffer *src, evbuffer *dest, ev_ssize_t limit, bufferevent_flush_mode mode, void *arg) {
auto *status = (Status *) arg;
//接收客户端发送的文件名,回复OK
if (!status->start) {
char data[BUFSIZ] = {0};
int len = evbuffer_remove(src, data, sizeof(data) - 1);
if (len > 0)
cout << "server receive: filename " << data << endl;
evbuffer_add(dest, data, len);
return BEV_OK;
}
z_stream *z_p = status->z_input;
if (!z_p)return BEV_ERROR;
//取出buffer中数据的引用
evbuffer_iovec v_in[1];
int buffer_len = evbuffer_peek(src, -1, 0, v_in, 1);
if (buffer_len <= 0) {
return BEV_NEED_MORE;
}
//zlib输入数据大小
z_p->avail_in = v_in[0].iov_len;
//zlib输入数据地址
z_p->next_in = (Byte *) v_in[0].iov_base;
//申请空间大小
evbuffer_iovec v_out[1];
evbuffer_reserve_space(dest, BUFSIZ, v_out, 1);
//zlib输出空间大小
//zlib输出空间地址
z_p->avail_out = v_out[0].iov_len;
z_p->next_out = (Byte *) v_out[0].iov_base;
int result = inflate(z_p, Z_SYNC_FLUSH);
if (result != Z_OK) {
cerr << "inflate failed" << endl;
}
//p->avail_in未处理数据的大小 zlibedLen:解压了多少
size_t zlibedLen = v_in[0].iov_len - z_p->avail_in;
//p->avail_out剩余空间大小 zliboutLen:解压后大小
size_t zliboutLen = v_out[0].iov_len - z_p->avail_out;
//remove evbuffer src data
evbuffer_drain(src, zlibedLen);
cout << "inflate data size:" << zlibedLen << " , inflated data size " << zliboutLen << endl;
v_out[0].iov_len = zliboutLen;
evbuffer_commit_space(dest, v_out, 1);
return BEV_OK;
}
bufferevent_filter_result
filter_out(evbuffer *src, evbuffer *dest, ev_ssize_t limit, bufferevent_flush_mode mode, void *arg) {
return BEV_OK;
}
void read_cb(bufferevent *bev, void *arg) {
//接收客户端发送的文件名filter后,回复OK
auto *status = (Status *) arg;
cout << "status->start = " << status->start << endl;
if (!status->start) {
//001 收到文件名
char data[BUFSIZ] = {0};
bufferevent_read(bev, data, sizeof(data) - 1);
string out = "out/";
out += data;
//打开写入文件
status->fp = fopen(out.c_str(), "wb");
if (!status->fp)
cout << "server open file failed" << endl;
//002回复OK
string str = "OK";
bufferevent_write(bev, str.c_str(), str.length());
status->start = true;
cout << "status->start = true" << endl;
return;
}
cout << "---------" << endl;
//写入文件, 可能没读完。
do {
char data[BUFSIZ] = {0};
size_t len = bufferevent_read(bev, data, sizeof(data));
if (len >= 0) {
recvCnt += len;
}
fwrite(data, 1, len, status->fp);
} while (evbuffer_get_length(bufferevent_get_input(bev)) > 0);
cout << "read_cb" << endl;
}
void write_cb(bufferevent *bev, void *arg) {
cout << "write_cb" << endl;
}
void event_cb(bufferevent *bev, short events, void *arg) {
auto *status = (Status *) arg;
cout << "server event_cb" << endl;
if (events & BEV_EVENT_EOF) {
fclose(status->fp);
status->fp = nullptr;
delete status;
bufferevent_free(bev);
cout << "recv cnt = " << recvCnt << endl;
cout << "BEV_EVENT_EOF" << endl;
}
}
void listen_cb(struct evconnlistener *evl, evutil_socket_t est, struct sockaddr *addr, int socklen, void *arg) {
cout << "listen_cb" << endl;
//创建bufferevent
auto *base = static_cast<event_base *>(arg);
bufferevent *bev = bufferevent_socket_new(base, est, BEV_OPT_CLOSE_ON_FREE);
//添加过滤
auto *status = new Status();
status->z_input = new z_stream();
inflateInit(status->z_input);
bufferevent *bev_filter = bufferevent_filter_new(bev,
filter_in,//输入过滤
nullptr,//输出过滤
BEV_OPT_CLOSE_ON_FREE, //关闭filter同时关闭bufferevent
nullptr,//清理回调
status//传递参数
);
//设置回调
bufferevent_setcb(bev_filter, read_cb, write_cb, event_cb,
status);//回调的参数
bufferevent_enable(bev_filter, EV_READ | EV_WRITE);
}
int main() {
//忽略管道信号,发送数据给已关闭的socket会使程序崩溃
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
return 1;
//创建libevent的上下文
event_base *base = event_base_new();
if (base) {
cout << "start server" << endl;
}
sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
evconnlistener *ev = evconnlistener_new_bind(base,
listen_cb, //接收到连接的回调函数
base, //回调函数获取的参数
LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, //地址重用,listen关闭同时关闭socket
10, //listen函数中, 连接队列大小
(sockaddr *) &sin, //绑定的地址和端口
sizeof(sin));
//事件分发处理
if (base) event_base_dispatch(base);
if (ev) evconnlistener_free(ev);
//销毁资源
if (base) event_base_free(base);
return 0;
}
测试环境使的是CLion,CMakeList如下
cmake_minimum_required(VERSION 3.8)
project(learn_libevent)
set(CMAKE_CXX_STANDARD 11)
# add extra include directories
include_directories(/usr/local/libevent/include)
include_directories(/usr/local/zlib/include)
# add extra lib directories
link_directories(/usr/local/libevent/lib)
link_directories(/usr/local/zlib/lib)
link_libraries(event)
link_libraries(z)
set(SRC_DIR ./server/zlib_server.cpp )
set(CLIENT_SRC_DIR ./client/zlib_client.cpp)
add_executable(zlib_server ${SRC_DIR})
add_executable(zlib_client ${CLIENT_SRC_DIR})
target_link_libraries(zlib_server event)
target_link_libraries(zlib_client event)
target_link_libraries(zlib_client z)
target_link_libraries(zlib_server z)