融合libevent和protobuf

写了一个简单的例子,把libevent中的bufferevent网络收发服务和protobuf里面的序列反序列结合起来。

protobuf文件message.proto:

message PMessage {
        required int32 id = 1;
        optional int32 num = 2;
        optional string str = 3;
}

生成接口命令:

protoc -I=proto --cpp_out=src proto/message.proto

服务器端 lserver.cc:

#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>

#include <stdio.h>
#include <string.h>

#include <event.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/thread.h>

#include "message.pb.h"

using namespace std;

void listener_cb(evconnlistener *listener, evutil_socket_t fd,
                sockaddr *sock, int socklen, void *arg);

void socket_read_cb(bufferevent *bev, void *arg);

void socket_event_cb(bufferevent *bev, short events, void *arg);

int main(int argc, char **argv) {
        sockaddr_in sin;
        memset(&sin, 0, sizeof(sockaddr_in));

        sin.sin_family = AF_INET;
        sin.sin_port = htons(8899);

        event_base *base = event_base_new();
        evconnlistener *listener
                = evconnlistener_new_bind(base,
                        listener_cb, base,
                        LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE,
                        10, (sockaddr*)&sin, sizeof(sockaddr_in));

        event_base_dispatch(base);

        evconnlistener_free(listener);
        event_base_free(base);

}

void listener_cb(evconnlistener *listener, evutil_socket_t fd,
        sockaddr *sock, int socklen, void *arg) {

        printf("accept a client %d\n", fd);
        event_base *base = (event_base *)arg;

        bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);

        bufferevent_setcb(bev, socket_read_cb, NULL, socket_event_cb, NULL);
        bufferevent_enable(bev, EV_READ|EV_PERSIST);

}

void socket_read_cb(bufferevent *bev, void *arg) {
        char msg[4096];
        size_t len = bufferevent_read(bev, msg, sizeof(msg)-1);
        msg[len] = '\0';

        PMessage pmsg;
        pmsg.ParseFromArray((const void*)msg, len);

        printf("Server read the data:%i, %i, %s\n", pmsg.id(), pmsg.num(), pmsg.str().c_str());

        pmsg.set_str("I have read your data.");
        string sendbuf;
        pmsg.SerializeToString(&sendbuf);

        bufferevent_write(bev, sendbuf.c_str(), sendbuf.length());

}

void socket_event_cb(bufferevent *bev, short events, void *arg) {
        if (events & BEV_EVENT_EOF) {
                printf("connection close\n");
        }
        else if (events & BEV_EVENT_ERROR) {
                printf("some other error\n");
        }

        bufferevent_free(bev);
}

客户端lclient.cc

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <event.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/util.h>

#include "message.pb.h"

using namespace std;

void cmd_msg_cb(int fd, short events, void *arg);

void server_msg_cb(bufferevent *bev, void *arg);

void event_cb(bufferevent *bev, short event, void *arg);

static int gid = 1;

int main(int argc, char **argv) {
        if (argc < 3) {
                printf("please input IP and port\n");
                return 1;
        }

        event_base *base = event_base_new();
        bufferevent *bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);

        event *ev_cmd = event_new(base, STDIN_FILENO,
                        EV_READ|EV_PERSIST,
                        cmd_msg_cb, (void *)bev);
        event_add(ev_cmd, NULL);

        sockaddr_in server_addr;
        memset(&server_addr, 0, sizeof(server_addr));

        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(atoi(argv[2]));
        inet_aton(argv[1], &server_addr.sin_addr);

        bufferevent_socket_connect(bev, (sockaddr*)&server_addr, sizeof(server_addr));

        bufferevent_setcb(bev, server_msg_cb, NULL, event_cb, (void*)ev_cmd);
        bufferevent_enable(bev, EV_READ|EV_PERSIST);

        event_base_dispatch(base);

        printf("Finish\n");
        return 0;

}

void cmd_msg_cb(int fd, short events, void *arg) {
        char msg[1024];
        int ret = read(fd, msg, sizeof(msg));
        if (ret < 0) {
                perror("read error.\n");
                exit(1);
        }

        // protobuf
        PMessage pmsg;
        pmsg.set_id(gid++);
        pmsg.set_num(rand());
        pmsg.set_str(msg);

        string sendbuf;
        pmsg.SerializeToString(&sendbuf);

        // processing network transfer
        bufferevent *bev = (bufferevent *)arg;
        bufferevent_write(bev, sendbuf.c_str(), sendbuf.length());
}

void server_msg_cb(bufferevent *bev, void *arg) {
        char msg[1024];

        size_t len = bufferevent_read(bev, msg, sizeof(msg)-1);
        msg[len] = '\0';

        PMessage pmsg;
        pmsg.ParseFromArray((const void*)msg, len);

        printf("Recv %d, %d, %s from server.\n", pmsg.id(), pmsg.num(), pmsg.str().c_str());
}

void event_cb(bufferevent *bev, short eventid, void *arg) {
        if (eventid & BEV_EVENT_EOF) {
                printf("Connection closed.\n");
        }
        else if (eventid & BEV_EVENT_ERROR) {
                printf("Some other error.\n");
        }
        else if (eventid & BEV_EVENT_CONNECTED) {
                printf("Client has successfully connected.\n");
                return;
        }

        bufferevent_free(bev);
        event *ev = (event *)arg;
        event_free(ev);
}

服务器端和客户端共用的Makefile:

CXX=/opt/compiler/gcc-4.8.2/bin/g++

INCPATH= \
        /home/work/.jumbo/include/

DEP_LDFLAGS= \
        -L/home/work/.jumbo/lib/

DEP_LDLIBS= \
        -levent \
        -lprotobuf \
        -lpthread

TARGET= lserver lclient

all : $(TARGET)

lserver : lserver.cc message.pb.cc
        $(CXX) -o $@ $^ -I$(INCPATH) $(DEP_LDFLAGS) $(DEP_LDLIBS)

lclient : lclient.cc message.pb.cc
        $(CXX) -o $@ $^ -I$(INCPATH) $(DEP_LDFLAGS) $(DEP_LDLIBS)

.PHONY : all clean

clean :
        rm -rf $(TARGET)

服务器命令及输出:

src]$ ./lserver 
accept a client 7
Server read the data:1, 1804289383, aaaaaaaaaaaaaaaaaaaaaaaaa

Server read the data:2, 846930886, aa

Server read the data:3, 1681692777, bb

Server read the data:4, 1714636915, abcdefg

Server read the data:5, 1957747793, 

connection close
accept a client 7
Server read the data:1, 1804289383, 2aa

Server read the data:2, 846930886, 2bb

Server read the data:3, 1681692777, 111111111111111111111222222222222222222222223333333333333333333333333

Server read the data:4, 1714636915, 

^C

客户端命令及输出:

src]$ ./lclient localhost 8899
Client has successfully connected.
aaaaaaaaaaaaaaaaaaaaaaaaa
Recv 1, 1804289383, I have read your data. from server.
aa
Recv 2, 846930886, I have read your data. from server.
bb
Recv 3, 1681692777, I have read your data. from server.
abcdefg
Recv 4, 1714636915, I have read your data. from server.

Recv 5, 1957747793, I have read your data. from server.
^C
[src]$ ./lclient localhost 8899
Client has successfully connected.
2aa
Recv 1, 1804289383, I have read your data. from server.
2bb
Recv 2, 846930886, I have read your data. from server.
111111111111111111111222222222222222222222223333333333333333333333333
Recv 3, 1681692777, I have read your data. from server.

Recv 4, 1714636915, I have read your data. from server.
Connection closed.
Finish

注意:

1. 先后开了两个客户端。客户端退出,不影响服务器端。但是服务器端退出会让客户端一起退出,因为客户端在收到网络error信号处理的最后,会free掉从命令行读数据的监听event,这样eventbase就不会再有event需要监听了,所以会退出。

2. 开始在命令行输入的时候,在char数组中没有添加'\0',传输时会造成如下错误。

[libprotobuf ERROR google/protobuf/wire_format.cc:1053] String field contains invalid UTF-8 data when serializing a protocol buffer. Use the 'bytes' type if you intend to send raw bytes.

根据读入函数返回的长度,设置'\0'即可避免这个错误。

posted @ 2016-09-30 14:40  blcblc  阅读(2496)  评论(0编辑  收藏  举报