libevent 安装
2015-12-30 10:02
483 查看
1、背景介绍
轻量级,开源高性能网络库。跨平台,支持Windows、Linux、*BSD和Mac Os;
1)支持用户三种类型的事件(事件驱动(event-driven)):支持网络I/O,定时器和信号等事件。定时器的数据结构使用最小堆(Min Heap),以提高效率。网络IO和信号的数据结构采用了双向链表(TAILQ)。在实现上主要有3种链表:EVLIST_INSERTED, EVLIST_ACTIVE, EVLIST_TIMEOUT,一个ev在这3种链表之间被插入或删除,处于EVLIST_ACTIVE链表中的ev最后将会被调度执行。
2)支持多种I/O多路复用技术, epoll、poll、dev/poll、select和kqueue等;
3)注册事件优先级
libevent默认情况下是单线程,每个线程有且仅有一个event_base,这对应一个struct event_base结构体(以及附于其上的事件管理器),用来schedule托管给它的一系列event,可以和操作系统的进程管理类比,当然,要更简单一点。当一个事件发生后,event_base会在合适的时间(不一定是立即)去调用绑定在这个事件上的函数(传入一些预定义的参数,以及在绑定时指定的一个参数),直到这个函数执行完,再返回schedule其他事件。
ls -al /usr/lib |grep libevent
2、安装
tar zxvf libevent-2.0.22-stable.tar.gz
cd libevent-2.0.22-stable./configure -prefix=/usr
make
sudo make install
安装之后,再重启下。
也可以采用以下:
3、linux下用qtcreator进行编程的时候注意点
在安装之后,利用Qtcreator进行项目管理的时候,需要在pro文件中添加如下:
正如在gcc中编译的时候,添加如下:
gcc -o basic basic.c -levent。
否则会出现未定义的情况。
4、使用例子(服务器回显):
1)使用Libevent的基本流程
(1)创建socket,bind,listen,设置为非阻塞模式
(2)首先创建一个event_base对象
[cpp]
view plaincopyprint?
//创建一个event_base struct event_base *base = event_base_new(); assert(base != NULL);
event_base内部有一个循环,循环阻塞在epoll/kqueue等系统调用上,直到有一个或者一些事件发生,然后去处理这些事件。当然,这些事件要被绑定在这个event_base上。每个事件对应一个struct event,可以是监听一个fd或者POSIX信号量之类。struct event使用event_new来创建和绑定,使用event_add来启用:
(3)创建一个event对象,并且将其监听的socket托管给event_base,指定要监听的事件类型,并绑上相应的回调函数
[cpp]
view plaincopyprint?
//创建并绑定一个event struct event *listen_event; //参数:event_base, 监听的fd,事件类型及属性,绑定的回调函数,给回调函数的参数 listen_event = event_new(base, listener, EV_READ|EV_PERSIST, callback_func, (void*)base);
(4)通过event_add方法启动监听事件
[cpp]
view plaincopyprint?
//参数:event,超时时间(struct timeval *类型的,NULL表示无超时设置) event_add(listen_event, NULL);
(5)进入事件循环
需要启动event_base的循环,这样才能开始处理发生的事件。循环的启动使用event_base_dispatch,循环将一直持续,直到不再有需要关注的事件,或者是遇到event_loopbreak()/event_loopexit()函数。
[cpp]
view plaincopyprint?
//启动事件循环 event_base_dispatch(base);
接下来关注下绑定到event的回调函数callback_func:传递给它的是一个socket fd、一个event类型及属性bit_field、以及传递给event_new的最后一个参数(去上面几行回顾一下,把event_base给传进来了,实际上更多地是分配一个结构体,把相关的数据都撂进去,然后丢给event_new,在这里就能取得到了)。其原型是:
[cpp]
view plaincopyprint?
typedef void(* event_callback_fn)(evutil_socket_t sockfd,short event_type,void *arg)
小结下:
对于一个服务器而言,上面的流程大概是这样组合的:
a. listener = socket(),bind(),listen(),设置nonblocking(POSIX系统中可使用fcntl设置,windows不需要设置,实际上libevent提供了统一的包装evutil_make_socket_nonblocking)
b. 创建一个event_base
c. 创建一个event,将该socket托管给event_base,指定要监听的事件类型,并绑定上相应的回调函数(及需要给它的参数)。对于listener socket来说,只需要监听EV_READ|EV_PERSIST
d. 启用该事件
e. 进入事件循环
f. (异步) 当有client发起请求的时候,调用该回调函数,进行处理。
整理后的完整代码如下:
[cpp]
view plaincopyprint?
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <event.h>
#include <sys/types.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#define PORT 25341
#define BACKLOG 5
#define MEM_SIZE 1024
struct event_base* base;
struct sock_ev {
struct event* read_ev;
struct event* write_ev;
char* buffer;
};
void release_sock_event(struct sock_ev* ev)
{
event_del(ev->read_ev);
free(ev->read_ev);
free(ev->write_ev);
free(ev->buffer);
free(ev);
}
void on_accept(int sock,short event,void* arg);
void on_read(int sock,short event,void* arg);
void on_write(int sock,short event,void* arg);
int main(int argc,char* argv[])
{
struct sockaddr_in my_addr;
int sock;
//创建套接字描述符,实质是一个文件描述符
//AF_INET表示使用IP地址,SOCK_STREAM表示使用流式套接字
sock = socket(AF_INET, SOCK_STREAM, 0);
int yes = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes,
sizeof(int));
memset(&my_addr, 0, sizeof(my_addr));
//实例化对象的属性
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(PORT);
my_addr.sin_addr.s_addr = INADDR_ANY;
//将套接字地址和套接字描述符绑定起来
bind(sock, (struct sockaddr*)&my_addr,
sizeof(struct sockaddr));
//监听该套接字,连接的客户端数量最多为BACKLOG
listen(sock, BACKLOG);
//声明事件
struct event listen_ev;
//创建基事件
base = event_base_new();
//设置回调函数.将event对象监听的socket托管给event_base,指定要监听的事件类型,并绑上相应的回调函数
event_set(&listen_ev, sock, EV_READ|EV_PERSIST, on_accept, NULL);//
//上述操作说明在listen_ev这个事件监听sock这个描述字的读操作(EV_READ),当读消息到达则调用on_accept函数,EV_PERSIST参数告诉系统持续的监听sock上的读事件,
//不指定这个属性的话,回调函数被触发后,事件会被删除.所以,如果不加该参数,每次要监听该事件时就要重复的调用event_add函数,从前面的代码可知,
//sock这个描述字是bind到本地的socket端口上,因此其对应的可读事件自然就是来自客户端的连接到达,我们就可以调用accept无阻塞的返回客户的连接了。
//使从属于基事件.将listen_ev注册到base这个事件中,相当于告诉处理IO的管家请留意我的listen_ev上的事件。
event_base_set(base, &listen_ev);
//有时候看到使用<span style="color:#FF0000;">event_new</span>(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base)代替event_set和event_base_set这两个函数
//添加到事件队列当中.相当于告诉处理IO的管家,当有我的事件到达时你发给我(调用on_accept函数),至此对listen_ev的初始化完毕
event_add(&listen_ev, NULL);
//开始循环.正式启动libevent的事件处理机制,使系统运行起来.event_base_dispatch是一个无限循环
event_base_dispatch(base);
return 0;
}
void on_accept(int sock,short event,void* arg)
{
struct sockaddr_in cli_addr;
int newfd;
socklen_t sin_size;
// read_ev must allocate from heap memory, otherwise the program would crash from segmant fault
struct event* read_ev = (struct event*)malloc(sizeof(struct event));
sin_size = sizeof(struct sockaddr_in);
newfd = accept(sock, (struct sockaddr*)&cli_addr, &sin_size);//指定服务端去接受客户端的连接
//客户的描述字newfd上监听可读事件,当有数据到达是调用on_read函数
event_set(read_ev, newfd, EV_READ|EV_PERSIST, on_read, read_ev);
event_base_set(base, read_ev);
event_add(read_ev, NULL);
//这里需要注意两点,一是read_ev需要从堆里malloc出来,如果是在栈上分配,那么当函数返回时变量占用的内存会被释放,
//因此事件主循环event_base_dispatch会访问无效的内存而导致进程崩溃(即crash);第二个要注意的是event_set中,read_ev作为参数传递给了on_read函数
}
void on_read(int sock,short event,void* arg)
{
struct event* write_ev;
int size;
char* buffer = (char*)malloc(MEM_SIZE);
bzero(buffer, MEM_SIZE);
size = recv(sock, buffer, MEM_SIZE, 0);
printf("receive data:%s, size:%d\n", buffer, size);
if (size == 0)//当从socket读返回0标志,对方已经关闭了连接,因此这个时候就没必要继续监听该套接口上的事件
{
event_del((struct event*)arg);
//由于EV_READ在on_accept函数里是用EV_PERSIST参数注册的,因此要显示的调用event_del函数取消对该事件的监听
free((struct event*)arg);
close(sock);
return;
}
write_ev = (struct event*) malloc(sizeof(struct event));
event_set(write_ev, sock, EV_WRITE, on_write, buffer);//写时调用on_write函数,注意将buffer作为参数传递给了on_write
event_base_set(base, write_ev);
event_add(write_ev, NULL);
}
// on_write函数中向客户端回写数据,然后释放on_read函数中malloc出来的buffer。在很多书合编程指导中都很强调资源的所有权,经常要求谁分配资源、就由谁释放资源,
//这样对资源的管理指责就更明确,不容易出问题,但是通过该例子我们发现在异步编程中资源的分配与释放往往是由不同的所有者操作的,因此也是比较容易出问题的地方。
void on_write(int sock,short event,void* arg)
{
char* buffer = (char*)arg;
send(sock, buffer, strlen(buffer), 0);
free(buffer);
}
再来看看前面提到的on_read函数中存在的问题,首先write_ev是动态分配的内存,但是没有释放,因此存在内存泄漏,另外,on_read中进行malloc操作,那么当多次调用该函数的时候就会造成内存的多次泄漏。这里的解决方法是对socket的描述字可以封装一个结构体来保护读、写的事件以及数据缓冲区,
其实在on_read函数中从socket读取数据后程序就可以直接调用write/send接口向客户回写数据了,因为写事件已经满足,不存在异步不异步的问题,这里进行on_write的异步操作仅仅是为了说明异步编程中资源的管理与释放的问题,另外一方面,直接调用write/send函数向客户端写数据可能导致程序较长时间阻塞在IO操作上,比如socket的输出缓冲区已满,则write/send操作阻塞到有可用的缓冲区之后才能进行实际的写操作,而通过向写事件注册on_accept函数,那么libevent会在合适的时间调用我们的callback函数,(比如对于会引起IO阻塞的情况比如socket输出缓冲区满,则由libevent设计算法来处理,如此当回调on_accept函数时我们在调用IO操作就不会发生真正的IO之外的阻塞)。注:前面括号中是我个人认为一个库应该实现的功能,至于libevent是不是实现这样的功能并不清楚也无意深究。
2)Libevent buffer实现异步传输
在Linux下有epoll,BSDS有kqueue,Solaris有evport和/dev/poll等等可以实现异步传输,但是没有哪一个操作系统拥有他们全部,而libevent就是把这些接口都封装起来,并且无论哪一个系统使用它都是最高效的。
Socket的read操作的数据先全部存储到input buffer当中,然后再存储到内存当中
Socket的write操作的数据全部从内存输出到output buffer当中
bufferevent是个神器。struct bufferevent内建了两个event(read/write)和对应的缓冲区【struct evbuffer *input, *output】,并提供相应的函数用来操作缓冲区(或者直接操作bufferevent)。每当有数据被读入input的时候,read_cb函数被调用;每当output被输出完的时候,write_cb被调用;在网络IO操作出现错误的情况(连接中断、超时、其他错误),error_cb被调用。于是上一部分的步骤被简化为:
1. 设置sockfd为nonblocking
2. 使用bufferevent_socket_new创建一个struct bufferevent *bev,关联该sockfd,托管给event_base
3. 使用bufferevent_setcb(bev, read_cb, write_cb, error_cb, (void *)arg)将EV_READ/EV_WRITE对应的函数
4. 使用bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST)来启用read/write事件
------
5. (异步)
在read_cb里面从input读取数据,处理完毕后塞到output里(会被自动写入到sockfd)
在write_cb里面(需要做什么吗?对于一个echo server来说,read_cb就足够了)
在error_cb里面处理遇到的错误
*. 可以使用bufferevent_set_timeouts(bev, struct timeval *READ, struct timeval *WRITE)来设置读写超时, 在error_cb里面处理超时。
*. read_cb和write_cb的原型是
void read_or_write_callback(struct bufferevent *bev, void *arg)
error_cb的原型是
void error_cb(struct bufferevent *bev, short error, void *arg) //这个是event的标准回调函数原型
可以从bev中用libevent的API提取出event_base、sockfd、input/output等相关数据,
[cpp]
view plaincopyprint?
于是代码简化到只需要几行的read_cb和error_cb函数即可:
void read_cb(struct bufferevent *bev,void *arg) {
char line[256];
int n;
evutil_socket_t fd = bufferevent_getfd(bev);
while (n = bufferevent_read(bev, line, 256), n > 0)
bufferevent_write(bev, line, n);
}
void error_cb(struct bufferevent *bev,short event,void *arg) {
bufferevent_free(bev);
}
[cpp]
view plaincopyprint?
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#define LISTEN_PORT 9999
#define LISTEN_BACKLOG 32
void do_accept(evutil_socket_t listener,
short event, void *arg);
void read_cb(struct bufferevent *bev,void *arg);
void error_cb(struct bufferevent *bev,short event,void *arg);
void write_cb(struct bufferevent *bev,void *arg);
int main()
{
//int ret;
evutil_socket_t listener;
listener = socket(AF_INET, SOCK_STREAM, 0);
assert(listener > 0);
evutil_make_listen_socket_reuseable(listener);
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons(LISTEN_PORT);
if (bind(listener, (struct sockaddr *)&sin,sizeof(sin)) < 0) {
perror("bind");
return 1;
}
if (listen(listener, LISTEN_BACKLOG) < 0) {
perror("listen");
return 1;
}
printf ("Listening...\n");
evutil_make_socket_nonblocking(listener);
struct event_base *base = event_base_new();
assert(base != NULL);
struct event *listen_event;
listen_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);
event_add(listen_event, NULL);
event_base_dispatch(base);
printf("The End.");
return 0;
}
void do_accept(evutil_socket_t listener,short event,void *arg)
{
struct event_base *base = (struct event_base *)arg;
evutil_socket_t fd;
struct sockaddr_in sin;
socklen_t slen = sizeof(sin);
fd = accept(listener, (struct sockaddr *)&sin, &slen);
if (fd < 0) {
perror("accept");
return;
}
if (fd > FD_SETSIZE) { //这个if是参考了那个ROT13的例子,貌似是官方的疏漏,从select-based例子里抄过来忘了改
perror("fd > FD_SETSIZE\n");
return;
}
printf("ACCEPT: fd = %u\n", fd);
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);
bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST);
}
void read_cb(struct bufferevent *bev,void *arg)
{
#define MAX_LINE 256
char line[MAX_LINE+1];
int n;
evutil_socket_t fd = bufferevent_getfd(bev);
while (n = bufferevent_read(bev, line, MAX_LINE), n > 0) {
line
= '\0';
printf("fd=%u, read line: %s\n", fd, line);
bufferevent_write(bev, line, n);
}
}
void write_cb(struct bufferevent *bev,void *arg) {}
void error_cb(struct bufferevent *bev,short event,void *arg)
{
evutil_socket_t fd = bufferevent_getfd(bev);
printf("fd = %u, ", fd);
if (event & BEV_EVENT_TIMEOUT) {
printf("Timed out\n"); //if bufferevent_set_timeouts() called
}
else if (event & BEV_EVENT_EOF) {
printf("connection closed\n");
}
else if (event & BEV_EVENT_ERROR) {
printf("some other error\n");
}
bufferevent_free(bev);
}
为完整起见,在此分别附上客户端和服务端的代码。
服务端代码:
[cpp]
view plaincopyprint?
//server端的代码
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#define LISTEN_PORT 9999
#define LISTEN_BACKLOG 32
#define MAX_LINE 256
void do_accept(evutil_socket_t listener,short event,void *arg);
void read_cb(struct bufferevent *bev,void *arg);
void error_cb(struct bufferevent *bev,short event,void *arg);
void write_cb(struct bufferevent *bev,void *arg);
int main()
{
//int ret;
evutil_socket_t listener;//用于跨平台表示socket的ID(在Linux下表示的是其文件描述符)
listener = socket(AF_INET, SOCK_STREAM, 0);
assert(listener > 0);
//用于跨平台将socket设置为可重用(实际上是将端口设为可重用
evutil_make_listen_socket_reuseable(listener);
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons(LISTEN_PORT);
if (bind(listener, (struct sockaddr *)&sin,sizeof(sin)) < 0) {
perror("bind");
return 1;
}
if (listen(listener, LISTEN_BACKLOG) < 0) {
perror("listen");
return 1;
}
printf ("Listening...\n");
/* 用于跨平台将socket设置为非阻塞,使用bufferevent需要 */
evutil_make_socket_nonblocking(listener);
//主要记录事件的相关属性
struct event_base *base = event_base_new();
assert(base != NULL);
/* Register listen event. */
struct event *listen_event;
listen_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);
event_add(listen_event, NULL);
/* Start the event loop. */
event_base_dispatch(base);
printf("The End.");
//close(listener);
return 0;
}
void do_accept(evutil_socket_t listener,
short event, void *arg)
{
struct event_base *base = (struct event_base *)arg;
evutil_socket_t fd;
struct sockaddr_in sin;
socklen_t slen = sizeof(sin);
fd = accept(listener, (struct sockaddr *)&sin, &slen);
if (fd < 0) {
perror("accept");
return;
}
if (fd > FD_SETSIZE) {
//这个if是参考了那个ROT13的例子,貌似是官方的疏漏,从select-based例子里抄过来忘了改
perror("fd > FD_SETSIZE\n");
return;
}
printf("ACCEPT: fd = %u\n", fd);
//关联该sockfd,托管给event_base.
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
//设置读写对应的回调函数
bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);
//启用读写事件,其实是调用了event_add将相应读写事件加入事件监听队列poll.
//如果相应事件不置为true,bufferevent是不会读写数据的
bufferevent_enable(bev, EV_READ|EV_PERSIST);
// 进入bufferevent_setcb回调函数:
// 在readcb里面从input中读取数据,处理完毕后填充到output中;
// writecb对于服务端程序,只需要readcb就可以了,可以置为NULL;
// errorcb用于处理一些错误信息
}
void read_cb(struct bufferevent *bev,void *arg)
{
char line[MAX_LINE+1];
int n;
evutil_socket_t fd = bufferevent_getfd(bev);
while (n = bufferevent_read(bev, line, MAX_LINE), n > 0)
{
line
= '\0';
printf("fd=%u, Server gets the message from client read line: %s\n", fd, line);
//直接将读取的结果,不做任何修改(本文是跳过前两个字符),直接返回给客户端
bufferevent_write(bev, line+2, n);//方案1
}
}
void write_cb(struct bufferevent *bev,void *arg)
{
printf("HelloWorld\n");//直接空代码即可,因为这里并不会被触发调用
}
void error_cb(struct bufferevent *bev,short event,void *arg)
{
evutil_socket_t fd = bufferevent_getfd(bev);
printf("fd = %u, ", fd);
if (event & BEV_EVENT_TIMEOUT) {
printf("Timed out\n");
//if bufferevent_set_timeouts() called
}
else if (event & BEV_EVENT_EOF) {
printf("connection closed\n");
}
else if (event & BEV_EVENT_ERROR) {
printf("some other error\n");
}
bufferevent_free(bev);
}
客户端代码:
[cpp]
view plaincopyprint?
//客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#define SERV_PORT 9999
#define MAX_LINE 1024
void cmd_msg_cb(int fd,short event,void *arg);
void read_cb(struct bufferevent *bev,void *arg);
void error_cb(struct bufferevent *bev,short event,void *arg);
int main(int argc,char *argv[])
{
if(argc < 2)
{
perror("usage: echocli <IPadress>");
return 1;
}
evutil_socket_t sockfd;
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket\n");
return 1;
}
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 1)
{
perror("inet_ntop\n");
return 1;
}
if(connect(sockfd, (struct sockaddr *) &servaddr,sizeof(servaddr)) < 0)
{
perror("connect\n");
return 1;
}
evutil_make_socket_nonblocking(sockfd);
printf("Connect to server sucessfully!\n");
// build event base
struct event_base *base = event_base_new();
if(base == NULL)
{
perror("event_base\n");
return 1;
}
const char *eventMechanism = event_base_get_method(base);
printf("Event mechanism used is %s\n", eventMechanism);
printf("sockfd = %d\n", sockfd);
struct bufferevent *bev = bufferevent_socket_new(base, sockfd, BEV_OPT_CLOSE_ON_FREE);
struct event *ev_cmd;
ev_cmd = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST, cmd_msg_cb, (void *)bev);
event_add(ev_cmd, NULL);
bufferevent_setcb(bev, read_cb, NULL, error_cb, (void *)ev_cmd);
bufferevent_enable(bev, EV_READ | EV_PERSIST);
event_base_dispatch(base);
printf("The End.");
return 0;
}
void cmd_msg_cb(int fd,short event,void *arg)
{
char msg[MAX_LINE];
int nread = read(fd, msg,
sizeof(msg));
if(nread < 0)
{
perror("stdio read fail\n");
return;
}
struct bufferevent *bev = (struct bufferevent *)arg;
bufferevent_write(bev, msg, nread);
}
void read_cb(struct bufferevent *bev,void *arg)
{
char line[MAX_LINE + 1];
int n;
evutil_socket_t fd = bufferevent_getfd(bev);
while((n = bufferevent_read(bev, line, MAX_LINE)) > 0)
{
line
= '\0';
printf("fd = %u, read from server: %s", fd, line);
}
}
void error_cb(struct bufferevent *bev,short event,void *arg)
{
evutil_socket_t fd = bufferevent_getfd(bev);
printf("fd = %u, ", fd);
if(event & BEV_EVENT_TIMEOUT)
printf("Time out.\n"); // if bufferevent_set_timeouts() is called
else if(event & BEV_EVENT_EOF)
printf("Connection closed.\n");
else if(event & BEV_EVENT_ERROR)
printf("Some other error.\n");
bufferevent_free(bev);
struct event *ev = (struct event *)arg;
event_free(ev);
}
编译
[cpp]
view plaincopyprint?
chen@chen-book1:~/libevent20/sample$ gcc echo_client.c -o client chen@chen-book1:~/libevent20/sample$ gcc ser.c -o ser -levent chen@chen-book1:~/libevent20/sample$ ./ser & [1] 6995 chen@chen-book1:~/libevent20/sample$ ./client localhost 25341 receive data:hello , size:6 msg: hello receive data:, size:0 chen@chen-book1:~/libevent20/sample$ ./client localhost 25341 receive data:hello , size:6 msg: hello
运行结果如下所示:本文为了方便区别,将客户端发送到服务端再回显到客户端的过程,在从服务端回显到客户端的时候,去掉前两个字符。如,客户端输入1234567,则从服务端读取到的数据为34567。
客户端的输入和输出结果:
服务端的输入和输出结果:
参考:
http://www.cnblogs.com/cnspace/archive/2011/07/19/2110891.html http://www.felix021.com/blog/read.php?2068 http://www.bkjia.com/ASPjc/886985.html http://blog.csdn.net/tujiaw/article/details/8872943 http://popozhu.github.io/2013/06/26/libevent_r5_bufferevent%E5%9F%BA%E7%A1%80%E5%92%8C%E6%A6%82%E5%BF%B5/ http://blog.csdn.net/bestone0213/article/details/46729091 https://www.ibm.com/developerworks/cn/aix/library/au-libev/
轻量级,开源高性能网络库。跨平台,支持Windows、Linux、*BSD和Mac Os;
1)支持用户三种类型的事件(事件驱动(event-driven)):支持网络I/O,定时器和信号等事件。定时器的数据结构使用最小堆(Min Heap),以提高效率。网络IO和信号的数据结构采用了双向链表(TAILQ)。在实现上主要有3种链表:EVLIST_INSERTED, EVLIST_ACTIVE, EVLIST_TIMEOUT,一个ev在这3种链表之间被插入或删除,处于EVLIST_ACTIVE链表中的ev最后将会被调度执行。
2)支持多种I/O多路复用技术, epoll、poll、dev/poll、select和kqueue等;
3)注册事件优先级
libevent默认情况下是单线程,每个线程有且仅有一个event_base,这对应一个struct event_base结构体(以及附于其上的事件管理器),用来schedule托管给它的一系列event,可以和操作系统的进程管理类比,当然,要更简单一点。当一个事件发生后,event_base会在合适的时间(不一定是立即)去调用绑定在这个事件上的函数(传入一些预定义的参数,以及在绑定时指定的一个参数),直到这个函数执行完,再返回schedule其他事件。
如果想查找libevent 是否已安装 则可以用如下命令
ls -al /usr/lib |grep libevent
2、安装
tar zxvf libevent-2.0.22-stable.tar.gz
cd libevent-2.0.22-stable./configure -prefix=/usr
make
sudo make install
安装之后,再重启下。
也可以采用以下:
<code style="PADDING-BOTTOM: 0px; BORDER-RIGHT-WIDTH: 0px; BACKGROUND-COLOR: rgb(238,238,238); MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; FONT-FAMILY: Consolas,Menlo,Monaco,'Lucida Console','Liberation Mono','DejaVu Sans Mono','Bitstream Vera Sans Mono','Courier New',monospace,sans-serif; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; FONT-SIZE: 13px; BORDER-LEFT-WIDTH: 0px; PADDING-TOP: 0px" sizcache="24" sizset="40"><span style="padding: 0px; border-width: 0px; margin: 0px;" class="pln">apt</span><span style="padding: 0px; border-width: 0px; margin: 0px;" class="pun">-</span><span style="padding: 0px; border-width: 0px; margin: 0px;" class="pln">cache search libevent 和</span></code><code style="PADDING-BOTTOM: 0px; BORDER-RIGHT-WIDTH: 0px; BACKGROUND-COLOR: rgb(238,238,238); MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; FONT-FAMILY: Consolas,Menlo,Monaco,'Lucida Console','Liberation Mono','DejaVu Sans Mono','Bitstream Vera Sans Mono','Courier New',monospace,sans-serif; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; FONT-SIZE: 13px; BORDER-LEFT-WIDTH: 0px; PADDING-TOP: 0px" sizcache="24" sizset="43"><span style="padding: 0px; border-width: 0px; margin: 0px;" class="pln"> apt</span><span style="padding: 0px; border-width: 0px; margin: 0px;" class="pun">-</span><span style="padding: 0px; border-width: 0px; margin: 0px;" class="pln">get install libevent</span><span style="padding: 0px; border-width: 0px; margin: 0px;" class="pun">-</span><span style="padding: 0px; border-width: 0px; margin: 0px;" class="pln">dev</span></code><code style="PADDING-BOTTOM: 0px; BORDER-RIGHT-WIDTH: 0px; BACKGROUND-COLOR: rgb(238,238,238); MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; FONT-FAMILY: Consolas,Menlo,Monaco,'Lucida Console','Liberation Mono','DejaVu Sans Mono','Bitstream Vera Sans Mono','Courier New',monospace,sans-serif; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; FONT-SIZE: 13px; BORDER-LEFT-WIDTH: 0px; PADDING-TOP: 0px" sizcache="24" sizset="48"><span style="padding: 0px; border-width: 0px; margin: 0px;" class="pln"> </span></code>进行安装。
3、linux下用qtcreator进行编程的时候注意点
在安装之后,利用Qtcreator进行项目管理的时候,需要在pro文件中添加如下:
正如在gcc中编译的时候,添加如下:
gcc -o basic basic.c -levent。
否则会出现未定义的情况。
4、使用例子(服务器回显):
1)使用Libevent的基本流程
(1)创建socket,bind,listen,设置为非阻塞模式
(2)首先创建一个event_base对象
[cpp]
view plaincopyprint?
//创建一个event_base struct event_base *base = event_base_new(); assert(base != NULL);
//创建一个event_base struct event_base *base = event_base_new(); assert(base != NULL);struct event_base *base = event_base_new()用以创建一个事件处理的全局变量,可以理解为这是一个负责集中处理各种出入IO事件的总管家,它负责接收和派发所有输入输出IO事件的信息,这里调用的是函数event_base_new(), 很多程序里这里用的是event_init(),区别就是前者是线程安全的、而后者是非线程安全的,后者在其官方说明中已经被标志为过时的函数、且建议用前者代替,libevent中还有很多类似的函数,比如建议用event_base_dispatch代替event_dispatch,用event_assign代替event_set和event_base_set等,关于libevent接口的详细说明见其官方说明libevent_doc。
event_base内部有一个循环,循环阻塞在epoll/kqueue等系统调用上,直到有一个或者一些事件发生,然后去处理这些事件。当然,这些事件要被绑定在这个event_base上。每个事件对应一个struct event,可以是监听一个fd或者POSIX信号量之类。struct event使用event_new来创建和绑定,使用event_add来启用:
(3)创建一个event对象,并且将其监听的socket托管给event_base,指定要监听的事件类型,并绑上相应的回调函数
[cpp]
view plaincopyprint?
//创建并绑定一个event struct event *listen_event; //参数:event_base, 监听的fd,事件类型及属性,绑定的回调函数,给回调函数的参数 listen_event = event_new(base, listener, EV_READ|EV_PERSIST, callback_func, (void*)base);
//创建并绑定一个event struct event *listen_event; //参数:event_base, 监听的fd,事件类型及属性,绑定的回调函数,给回调函数的参数 listen_event = event_new(base, listener, EV_READ|EV_PERSIST, callback_func, (void*)base);
(4)通过event_add方法启动监听事件
[cpp]
view plaincopyprint?
//参数:event,超时时间(struct timeval *类型的,NULL表示无超时设置) event_add(listen_event, NULL);
//参数:event,超时时间(struct timeval *类型的,NULL表示无超时设置) event_add(listen_event, NULL);
(5)进入事件循环
需要启动event_base的循环,这样才能开始处理发生的事件。循环的启动使用event_base_dispatch,循环将一直持续,直到不再有需要关注的事件,或者是遇到event_loopbreak()/event_loopexit()函数。
[cpp]
view plaincopyprint?
//启动事件循环 event_base_dispatch(base);
//启动事件循环 event_base_dispatch(base);
接下来关注下绑定到event的回调函数callback_func:传递给它的是一个socket fd、一个event类型及属性bit_field、以及传递给event_new的最后一个参数(去上面几行回顾一下,把event_base给传进来了,实际上更多地是分配一个结构体,把相关的数据都撂进去,然后丢给event_new,在这里就能取得到了)。其原型是:
[cpp]
view plaincopyprint?
typedef void(* event_callback_fn)(evutil_socket_t sockfd,short event_type,void *arg)
typedef void(* event_callback_fn)(evutil_socket_t sockfd, short event_type, void *arg)
小结下:
对于一个服务器而言,上面的流程大概是这样组合的:
a. listener = socket(),bind(),listen(),设置nonblocking(POSIX系统中可使用fcntl设置,windows不需要设置,实际上libevent提供了统一的包装evutil_make_socket_nonblocking)
b. 创建一个event_base
c. 创建一个event,将该socket托管给event_base,指定要监听的事件类型,并绑定上相应的回调函数(及需要给它的参数)。对于listener socket来说,只需要监听EV_READ|EV_PERSIST
d. 启用该事件
e. 进入事件循环
f. (异步) 当有client发起请求的时候,调用该回调函数,进行处理。
整理后的完整代码如下:
[cpp]
view plaincopyprint?
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <event.h>
#include <sys/types.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#define PORT 25341
#define BACKLOG 5
#define MEM_SIZE 1024
struct event_base* base;
struct sock_ev {
struct event* read_ev;
struct event* write_ev;
char* buffer;
};
void release_sock_event(struct sock_ev* ev)
{
event_del(ev->read_ev);
free(ev->read_ev);
free(ev->write_ev);
free(ev->buffer);
free(ev);
}
void on_accept(int sock,short event,void* arg);
void on_read(int sock,short event,void* arg);
void on_write(int sock,short event,void* arg);
int main(int argc,char* argv[])
{
struct sockaddr_in my_addr;
int sock;
//创建套接字描述符,实质是一个文件描述符
//AF_INET表示使用IP地址,SOCK_STREAM表示使用流式套接字
sock = socket(AF_INET, SOCK_STREAM, 0);
int yes = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes,
sizeof(int));
memset(&my_addr, 0, sizeof(my_addr));
//实例化对象的属性
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(PORT);
my_addr.sin_addr.s_addr = INADDR_ANY;
//将套接字地址和套接字描述符绑定起来
bind(sock, (struct sockaddr*)&my_addr,
sizeof(struct sockaddr));
//监听该套接字,连接的客户端数量最多为BACKLOG
listen(sock, BACKLOG);
//声明事件
struct event listen_ev;
//创建基事件
base = event_base_new();
//设置回调函数.将event对象监听的socket托管给event_base,指定要监听的事件类型,并绑上相应的回调函数
event_set(&listen_ev, sock, EV_READ|EV_PERSIST, on_accept, NULL);//
//上述操作说明在listen_ev这个事件监听sock这个描述字的读操作(EV_READ),当读消息到达则调用on_accept函数,EV_PERSIST参数告诉系统持续的监听sock上的读事件,
//不指定这个属性的话,回调函数被触发后,事件会被删除.所以,如果不加该参数,每次要监听该事件时就要重复的调用event_add函数,从前面的代码可知,
//sock这个描述字是bind到本地的socket端口上,因此其对应的可读事件自然就是来自客户端的连接到达,我们就可以调用accept无阻塞的返回客户的连接了。
//使从属于基事件.将listen_ev注册到base这个事件中,相当于告诉处理IO的管家请留意我的listen_ev上的事件。
event_base_set(base, &listen_ev);
//有时候看到使用<span style="color:#FF0000;">event_new</span>(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base)代替event_set和event_base_set这两个函数
//添加到事件队列当中.相当于告诉处理IO的管家,当有我的事件到达时你发给我(调用on_accept函数),至此对listen_ev的初始化完毕
event_add(&listen_ev, NULL);
//开始循环.正式启动libevent的事件处理机制,使系统运行起来.event_base_dispatch是一个无限循环
event_base_dispatch(base);
return 0;
}
void on_accept(int sock,short event,void* arg)
{
struct sockaddr_in cli_addr;
int newfd;
socklen_t sin_size;
// read_ev must allocate from heap memory, otherwise the program would crash from segmant fault
struct event* read_ev = (struct event*)malloc(sizeof(struct event));
sin_size = sizeof(struct sockaddr_in);
newfd = accept(sock, (struct sockaddr*)&cli_addr, &sin_size);//指定服务端去接受客户端的连接
//客户的描述字newfd上监听可读事件,当有数据到达是调用on_read函数
event_set(read_ev, newfd, EV_READ|EV_PERSIST, on_read, read_ev);
event_base_set(base, read_ev);
event_add(read_ev, NULL);
//这里需要注意两点,一是read_ev需要从堆里malloc出来,如果是在栈上分配,那么当函数返回时变量占用的内存会被释放,
//因此事件主循环event_base_dispatch会访问无效的内存而导致进程崩溃(即crash);第二个要注意的是event_set中,read_ev作为参数传递给了on_read函数
}
void on_read(int sock,short event,void* arg)
{
struct event* write_ev;
int size;
char* buffer = (char*)malloc(MEM_SIZE);
bzero(buffer, MEM_SIZE);
size = recv(sock, buffer, MEM_SIZE, 0);
printf("receive data:%s, size:%d\n", buffer, size);
if (size == 0)//当从socket读返回0标志,对方已经关闭了连接,因此这个时候就没必要继续监听该套接口上的事件
{
event_del((struct event*)arg);
//由于EV_READ在on_accept函数里是用EV_PERSIST参数注册的,因此要显示的调用event_del函数取消对该事件的监听
free((struct event*)arg);
close(sock);
return;
}
write_ev = (struct event*) malloc(sizeof(struct event));
event_set(write_ev, sock, EV_WRITE, on_write, buffer);//写时调用on_write函数,注意将buffer作为参数传递给了on_write
event_base_set(base, write_ev);
event_add(write_ev, NULL);
}
// on_write函数中向客户端回写数据,然后释放on_read函数中malloc出来的buffer。在很多书合编程指导中都很强调资源的所有权,经常要求谁分配资源、就由谁释放资源,
//这样对资源的管理指责就更明确,不容易出问题,但是通过该例子我们发现在异步编程中资源的分配与释放往往是由不同的所有者操作的,因此也是比较容易出问题的地方。
void on_write(int sock,short event,void* arg)
{
char* buffer = (char*)arg;
send(sock, buffer, strlen(buffer), 0);
free(buffer);
}
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <assert.h> #include <string.h> #include <event.h> #include <sys/types.h> #include <unistd.h> #include <netinet/in.h> #include <sys/socket.h> #include <event2/event.h> #include <event2/bufferevent.h> #define PORT 25341 #define BACKLOG 5 #define MEM_SIZE 1024 struct event_base* base; struct sock_ev { struct event* read_ev; struct event* write_ev; char* buffer; }; void release_sock_event(struct sock_ev* ev) { event_del(ev->read_ev); free(ev->read_ev); free(ev->write_ev); free(ev->buffer); free(ev); } void on_accept(int sock, short event, void* arg); void on_read(int sock, short event, void* arg); void on_write(int sock, short event, void* arg); int main(int argc, char* argv[]) { struct sockaddr_in my_addr; int sock; //创建套接字描述符,实质是一个文件描述符 //AF_INET表示使用IP地址,SOCK_STREAM表示使用流式套接字 sock = socket(AF_INET, SOCK_STREAM, 0); int yes = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)); memset(&my_addr, 0, sizeof(my_addr)); //实例化对象的属性 my_addr.sin_family = AF_INET; my_addr.sin_port = htons(PORT); my_addr.sin_addr.s_addr = INADDR_ANY; //将套接字地址和套接字描述符绑定起来 bind(sock, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)); //监听该套接字,连接的客户端数量最多为BACKLOG listen(sock, BACKLOG); //声明事件 struct event listen_ev; //创建基事件 base = event_base_new(); //设置回调函数.将event对象监听的socket托管给event_base,指定要监听的事件类型,并绑上相应的回调函数 event_set(&listen_ev, sock, EV_READ|EV_PERSIST, on_accept, NULL);// //上述操作说明在listen_ev这个事件监听sock这个描述字的读操作(EV_READ),当读消息到达则调用on_accept函数,EV_PERSIST参数告诉系统持续的监听sock上的读事件, //不指定这个属性的话,回调函数被触发后,事件会被删除.所以,如果不加该参数,每次要监听该事件时就要重复的调用event_add函数,从前面的代码可知, //sock这个描述字是bind到本地的socket端口上,因此其对应的可读事件自然就是来自客户端的连接到达,我们就可以调用accept无阻塞的返回客户的连接了。 //使从属于基事件.将listen_ev注册到base这个事件中,相当于告诉处理IO的管家请留意我的listen_ev上的事件。 event_base_set(base, &listen_ev); //有时候看到使用<span style="color:#FF0000;">event_new</span>(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base)代替event_set和event_base_set这两个函数 //添加到事件队列当中.相当于告诉处理IO的管家,当有我的事件到达时你发给我(调用on_accept函数),至此对listen_ev的初始化完毕 event_add(&listen_ev, NULL); //开始循环.正式启动libevent的事件处理机制,使系统运行起来.event_base_dispatch是一个无限循环 event_base_dispatch(base); return 0; } void on_accept(int sock, short event, void* arg) { struct sockaddr_in cli_addr; int newfd; socklen_t sin_size; // read_ev must allocate from heap memory, otherwise the program would crash from segmant fault struct event* read_ev = (struct event*)malloc(sizeof(struct event)); sin_size = sizeof(struct sockaddr_in); newfd = accept(sock, (struct sockaddr*)&cli_addr, &sin_size);//指定服务端去接受客户端的连接 //客户的描述字newfd上监听可读事件,当有数据到达是调用on_read函数 event_set(read_ev, newfd, EV_READ|EV_PERSIST, on_read, read_ev); event_base_set(base, read_ev); event_add(read_ev, NULL); //这里需要注意两点,一是read_ev需要从堆里malloc出来,如果是在栈上分配,那么当函数返回时变量占用的内存会被释放, //因此事件主循环event_base_dispatch会访问无效的内存而导致进程崩溃(即crash);第二个要注意的是event_set中,read_ev作为参数传递给了on_read函数 } void on_read(int sock, short event, void* arg) { struct event* write_ev; int size; char* buffer = (char*)malloc(MEM_SIZE); bzero(buffer, MEM_SIZE); size = recv(sock, buffer, MEM_SIZE, 0); printf("receive data:%s, size:%d\n", buffer, size); if (size == 0)//当从socket读返回0标志,对方已经关闭了连接,因此这个时候就没必要继续监听该套接口上的事件 { event_del((struct event*)arg); //由于EV_READ在on_accept函数里是用EV_PERSIST参数注册的,因此要显示的调用event_del函数取消对该事件的监听 free((struct event*)arg); close(sock); return; } write_ev = (struct event*) malloc(sizeof(struct event)); event_set(write_ev, sock, EV_WRITE, on_write, buffer);//写时调用on_write函数,注意将buffer作为参数传递给了on_write event_base_set(base, write_ev); event_add(write_ev, NULL); } // on_write函数中向客户端回写数据,然后释放on_read函数中malloc出来的buffer。在很多书合编程指导中都很强调资源的所有权,经常要求谁分配资源、就由谁释放资源, //这样对资源的管理指责就更明确,不容易出问题,但是通过该例子我们发现在异步编程中资源的分配与释放往往是由不同的所有者操作的,因此也是比较容易出问题的地方。 void on_write(int sock, short event, void* arg) { char* buffer = (char*)arg; send(sock, buffer, strlen(buffer), 0); free(buffer); }
再来看看前面提到的on_read函数中存在的问题,首先write_ev是动态分配的内存,但是没有释放,因此存在内存泄漏,另外,on_read中进行malloc操作,那么当多次调用该函数的时候就会造成内存的多次泄漏。这里的解决方法是对socket的描述字可以封装一个结构体来保护读、写的事件以及数据缓冲区,
其实在on_read函数中从socket读取数据后程序就可以直接调用write/send接口向客户回写数据了,因为写事件已经满足,不存在异步不异步的问题,这里进行on_write的异步操作仅仅是为了说明异步编程中资源的管理与释放的问题,另外一方面,直接调用write/send函数向客户端写数据可能导致程序较长时间阻塞在IO操作上,比如socket的输出缓冲区已满,则write/send操作阻塞到有可用的缓冲区之后才能进行实际的写操作,而通过向写事件注册on_accept函数,那么libevent会在合适的时间调用我们的callback函数,(比如对于会引起IO阻塞的情况比如socket输出缓冲区满,则由libevent设计算法来处理,如此当回调on_accept函数时我们在调用IO操作就不会发生真正的IO之外的阻塞)。注:前面括号中是我个人认为一个库应该实现的功能,至于libevent是不是实现这样的功能并不清楚也无意深究。
2)Libevent buffer实现异步传输
在Linux下有epoll,BSDS有kqueue,Solaris有evport和/dev/poll等等可以实现异步传输,但是没有哪一个操作系统拥有他们全部,而libevent就是把这些接口都封装起来,并且无论哪一个系统使用它都是最高效的。
Socket的read操作的数据先全部存储到input buffer当中,然后再存储到内存当中
Socket的write操作的数据全部从内存输出到output buffer当中
bufferevent是个神器。struct bufferevent内建了两个event(read/write)和对应的缓冲区【struct evbuffer *input, *output】,并提供相应的函数用来操作缓冲区(或者直接操作bufferevent)。每当有数据被读入input的时候,read_cb函数被调用;每当output被输出完的时候,write_cb被调用;在网络IO操作出现错误的情况(连接中断、超时、其他错误),error_cb被调用。于是上一部分的步骤被简化为:
1. 设置sockfd为nonblocking
2. 使用bufferevent_socket_new创建一个struct bufferevent *bev,关联该sockfd,托管给event_base
3. 使用bufferevent_setcb(bev, read_cb, write_cb, error_cb, (void *)arg)将EV_READ/EV_WRITE对应的函数
4. 使用bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST)来启用read/write事件
------
5. (异步)
在read_cb里面从input读取数据,处理完毕后塞到output里(会被自动写入到sockfd)
在write_cb里面(需要做什么吗?对于一个echo server来说,read_cb就足够了)
在error_cb里面处理遇到的错误
*. 可以使用bufferevent_set_timeouts(bev, struct timeval *READ, struct timeval *WRITE)来设置读写超时, 在error_cb里面处理超时。
*. read_cb和write_cb的原型是
void read_or_write_callback(struct bufferevent *bev, void *arg)
error_cb的原型是
void error_cb(struct bufferevent *bev, short error, void *arg) //这个是event的标准回调函数原型
可以从bev中用libevent的API提取出event_base、sockfd、input/output等相关数据,
[cpp]
view plaincopyprint?
于是代码简化到只需要几行的read_cb和error_cb函数即可:
void read_cb(struct bufferevent *bev,void *arg) {
char line[256];
int n;
evutil_socket_t fd = bufferevent_getfd(bev);
while (n = bufferevent_read(bev, line, 256), n > 0)
bufferevent_write(bev, line, n);
}
void error_cb(struct bufferevent *bev,short event,void *arg) {
bufferevent_free(bev);
}
于是代码简化到只需要几行的read_cb和error_cb函数即可: void read_cb(struct bufferevent *bev, void *arg) { char line[256]; int n; evutil_socket_t fd = bufferevent_getfd(bev); while (n = bufferevent_read(bev, line, 256), n > 0) bufferevent_write(bev, line, n); } void error_cb(struct bufferevent *bev, short event, void *arg) { bufferevent_free(bev); }综合上述,代码如下:
[cpp]
view plaincopyprint?
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#define LISTEN_PORT 9999
#define LISTEN_BACKLOG 32
void do_accept(evutil_socket_t listener,
short event, void *arg);
void read_cb(struct bufferevent *bev,void *arg);
void error_cb(struct bufferevent *bev,short event,void *arg);
void write_cb(struct bufferevent *bev,void *arg);
int main()
{
//int ret;
evutil_socket_t listener;
listener = socket(AF_INET, SOCK_STREAM, 0);
assert(listener > 0);
evutil_make_listen_socket_reuseable(listener);
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons(LISTEN_PORT);
if (bind(listener, (struct sockaddr *)&sin,sizeof(sin)) < 0) {
perror("bind");
return 1;
}
if (listen(listener, LISTEN_BACKLOG) < 0) {
perror("listen");
return 1;
}
printf ("Listening...\n");
evutil_make_socket_nonblocking(listener);
struct event_base *base = event_base_new();
assert(base != NULL);
struct event *listen_event;
listen_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);
event_add(listen_event, NULL);
event_base_dispatch(base);
printf("The End.");
return 0;
}
void do_accept(evutil_socket_t listener,short event,void *arg)
{
struct event_base *base = (struct event_base *)arg;
evutil_socket_t fd;
struct sockaddr_in sin;
socklen_t slen = sizeof(sin);
fd = accept(listener, (struct sockaddr *)&sin, &slen);
if (fd < 0) {
perror("accept");
return;
}
if (fd > FD_SETSIZE) { //这个if是参考了那个ROT13的例子,貌似是官方的疏漏,从select-based例子里抄过来忘了改
perror("fd > FD_SETSIZE\n");
return;
}
printf("ACCEPT: fd = %u\n", fd);
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);
bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST);
}
void read_cb(struct bufferevent *bev,void *arg)
{
#define MAX_LINE 256
char line[MAX_LINE+1];
int n;
evutil_socket_t fd = bufferevent_getfd(bev);
while (n = bufferevent_read(bev, line, MAX_LINE), n > 0) {
line
= '\0';
printf("fd=%u, read line: %s\n", fd, line);
bufferevent_write(bev, line, n);
}
}
void write_cb(struct bufferevent *bev,void *arg) {}
void error_cb(struct bufferevent *bev,short event,void *arg)
{
evutil_socket_t fd = bufferevent_getfd(bev);
printf("fd = %u, ", fd);
if (event & BEV_EVENT_TIMEOUT) {
printf("Timed out\n"); //if bufferevent_set_timeouts() called
}
else if (event & BEV_EVENT_EOF) {
printf("connection closed\n");
}
else if (event & BEV_EVENT_ERROR) {
printf("some other error\n");
}
bufferevent_free(bev);
}
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <assert.h> #include <event2/event.h> #include <event2/bufferevent.h> #define LISTEN_PORT 9999 #define LISTEN_BACKLOG 32 void do_accept(evutil_socket_t listener, short event, void *arg); void read_cb(struct bufferevent *bev, void *arg); void error_cb(struct bufferevent *bev, short event, void *arg); void write_cb(struct bufferevent *bev, void *arg); int main() { //int ret; evutil_socket_t listener; listener = socket(AF_INET, SOCK_STREAM, 0); assert(listener > 0); evutil_make_listen_socket_reuseable(listener); struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_addr.s_addr = 0; sin.sin_port = htons(LISTEN_PORT); if (bind(listener, (struct sockaddr *)&sin, sizeof(sin)) < 0) { perror("bind"); return 1; } if (listen(listener, LISTEN_BACKLOG) < 0) { perror("listen"); return 1; } printf ("Listening...\n"); evutil_make_socket_nonblocking(listener); struct event_base *base = event_base_new(); assert(base != NULL); struct event *listen_event; listen_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base); event_add(listen_event, NULL); event_base_dispatch(base); printf("The End."); return 0; } void do_accept(evutil_socket_t listener, short event, void *arg) { struct event_base *base = (struct event_base *)arg; evutil_socket_t fd; struct sockaddr_in sin; socklen_t slen = sizeof(sin); fd = accept(listener, (struct sockaddr *)&sin, &slen); if (fd < 0) { perror("accept"); return; } if (fd > FD_SETSIZE) { //这个if是参考了那个ROT13的例子,貌似是官方的疏漏,从select-based例子里抄过来忘了改 perror("fd > FD_SETSIZE\n"); return; } printf("ACCEPT: fd = %u\n", fd); struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE); bufferevent_setcb(bev, read_cb, NULL, error_cb, arg); bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST); } void read_cb(struct bufferevent *bev, void *arg) { #define MAX_LINE 256 char line[MAX_LINE+1]; int n; evutil_socket_t fd = bufferevent_getfd(bev); while (n = bufferevent_read(bev, line, MAX_LINE), n > 0) { line = '\0'; printf("fd=%u, read line: %s\n", fd, line); bufferevent_write(bev, line, n); } } void write_cb(struct bufferevent *bev, void *arg) {} void error_cb(struct bufferevent *bev, short event, void *arg) { evutil_socket_t fd = bufferevent_getfd(bev); printf("fd = %u, ", fd); if (event & BEV_EVENT_TIMEOUT) { printf("Timed out\n"); //if bufferevent_set_timeouts() called } else if (event & BEV_EVENT_EOF) { printf("connection closed\n"); } else if (event & BEV_EVENT_ERROR) { printf("some other error\n"); } bufferevent_free(bev); }
为完整起见,在此分别附上客户端和服务端的代码。
服务端代码:
[cpp]
view plaincopyprint?
//server端的代码
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#define LISTEN_PORT 9999
#define LISTEN_BACKLOG 32
#define MAX_LINE 256
void do_accept(evutil_socket_t listener,short event,void *arg);
void read_cb(struct bufferevent *bev,void *arg);
void error_cb(struct bufferevent *bev,short event,void *arg);
void write_cb(struct bufferevent *bev,void *arg);
int main()
{
//int ret;
evutil_socket_t listener;//用于跨平台表示socket的ID(在Linux下表示的是其文件描述符)
listener = socket(AF_INET, SOCK_STREAM, 0);
assert(listener > 0);
//用于跨平台将socket设置为可重用(实际上是将端口设为可重用
evutil_make_listen_socket_reuseable(listener);
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons(LISTEN_PORT);
if (bind(listener, (struct sockaddr *)&sin,sizeof(sin)) < 0) {
perror("bind");
return 1;
}
if (listen(listener, LISTEN_BACKLOG) < 0) {
perror("listen");
return 1;
}
printf ("Listening...\n");
/* 用于跨平台将socket设置为非阻塞,使用bufferevent需要 */
evutil_make_socket_nonblocking(listener);
//主要记录事件的相关属性
struct event_base *base = event_base_new();
assert(base != NULL);
/* Register listen event. */
struct event *listen_event;
listen_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);
event_add(listen_event, NULL);
/* Start the event loop. */
event_base_dispatch(base);
printf("The End.");
//close(listener);
return 0;
}
void do_accept(evutil_socket_t listener,
short event, void *arg)
{
struct event_base *base = (struct event_base *)arg;
evutil_socket_t fd;
struct sockaddr_in sin;
socklen_t slen = sizeof(sin);
fd = accept(listener, (struct sockaddr *)&sin, &slen);
if (fd < 0) {
perror("accept");
return;
}
if (fd > FD_SETSIZE) {
//这个if是参考了那个ROT13的例子,貌似是官方的疏漏,从select-based例子里抄过来忘了改
perror("fd > FD_SETSIZE\n");
return;
}
printf("ACCEPT: fd = %u\n", fd);
//关联该sockfd,托管给event_base.
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
//设置读写对应的回调函数
bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);
//启用读写事件,其实是调用了event_add将相应读写事件加入事件监听队列poll.
//如果相应事件不置为true,bufferevent是不会读写数据的
bufferevent_enable(bev, EV_READ|EV_PERSIST);
// 进入bufferevent_setcb回调函数:
// 在readcb里面从input中读取数据,处理完毕后填充到output中;
// writecb对于服务端程序,只需要readcb就可以了,可以置为NULL;
// errorcb用于处理一些错误信息
}
void read_cb(struct bufferevent *bev,void *arg)
{
char line[MAX_LINE+1];
int n;
evutil_socket_t fd = bufferevent_getfd(bev);
while (n = bufferevent_read(bev, line, MAX_LINE), n > 0)
{
line
= '\0';
printf("fd=%u, Server gets the message from client read line: %s\n", fd, line);
//直接将读取的结果,不做任何修改(本文是跳过前两个字符),直接返回给客户端
bufferevent_write(bev, line+2, n);//方案1
}
}
void write_cb(struct bufferevent *bev,void *arg)
{
printf("HelloWorld\n");//直接空代码即可,因为这里并不会被触发调用
}
void error_cb(struct bufferevent *bev,short event,void *arg)
{
evutil_socket_t fd = bufferevent_getfd(bev);
printf("fd = %u, ", fd);
if (event & BEV_EVENT_TIMEOUT) {
printf("Timed out\n");
//if bufferevent_set_timeouts() called
}
else if (event & BEV_EVENT_EOF) {
printf("connection closed\n");
}
else if (event & BEV_EVENT_ERROR) {
printf("some other error\n");
}
bufferevent_free(bev);
}
//server端的代码 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <assert.h> #include <unistd.h> #include <string.h> #include <event2/event.h> #include <event2/bufferevent.h> #define LISTEN_PORT 9999 #define LISTEN_BACKLOG 32 #define MAX_LINE 256 void do_accept(evutil_socket_t listener, short event, void *arg); void read_cb(struct bufferevent *bev, void *arg); void error_cb(struct bufferevent *bev, short event, void *arg); void write_cb(struct bufferevent *bev, void *arg); int main() { //int ret; evutil_socket_t listener;//用于跨平台表示socket的ID(在Linux下表示的是其文件描述符) listener = socket(AF_INET, SOCK_STREAM, 0); assert(listener > 0); //用于跨平台将socket设置为可重用(实际上是将端口设为可重用 evutil_make_listen_socket_reuseable(listener); struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_addr.s_addr = 0; sin.sin_port = htons(LISTEN_PORT); if (bind(listener, (struct sockaddr *)&sin, sizeof(sin)) < 0) { perror("bind"); return 1; } if (listen(listener, LISTEN_BACKLOG) < 0) { perror("listen"); return 1; } printf ("Listening...\n"); /* 用于跨平台将socket设置为非阻塞,使用bufferevent需要 */ evutil_make_socket_nonblocking(listener); //主要记录事件的相关属性 struct event_base *base = event_base_new(); assert(base != NULL); /* Register listen event. */ struct event *listen_event; listen_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base); event_add(listen_event, NULL); /* Start the event loop. */ event_base_dispatch(base); printf("The End."); //close(listener); return 0; } void do_accept(evutil_socket_t listener, short event, void *arg) { struct event_base *base = (struct event_base *)arg; evutil_socket_t fd; struct sockaddr_in sin; socklen_t slen = sizeof(sin); fd = accept(listener, (struct sockaddr *)&sin, &slen); if (fd < 0) { perror("accept"); return; } if (fd > FD_SETSIZE) { //这个if是参考了那个ROT13的例子,貌似是官方的疏漏,从select-based例子里抄过来忘了改 perror("fd > FD_SETSIZE\n"); return; } printf("ACCEPT: fd = %u\n", fd); //关联该sockfd,托管给event_base. struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE); //设置读写对应的回调函数 bufferevent_setcb(bev, read_cb, NULL, error_cb, arg); //启用读写事件,其实是调用了event_add将相应读写事件加入事件监听队列poll. //如果相应事件不置为true,bufferevent是不会读写数据的 bufferevent_enable(bev, EV_READ|EV_PERSIST); // 进入bufferevent_setcb回调函数: // 在readcb里面从input中读取数据,处理完毕后填充到output中; // writecb对于服务端程序,只需要readcb就可以了,可以置为NULL; // errorcb用于处理一些错误信息 } void read_cb(struct bufferevent *bev, void *arg) { char line[MAX_LINE+1]; int n; evutil_socket_t fd = bufferevent_getfd(bev); while (n = bufferevent_read(bev, line, MAX_LINE), n > 0) { line = '\0'; printf("fd=%u, Server gets the message from client read line: %s\n", fd, line); //直接将读取的结果,不做任何修改(本文是跳过前两个字符),直接返回给客户端 bufferevent_write(bev, line+2, n);//方案1 } } void write_cb(struct bufferevent *bev, void *arg) { printf("HelloWorld\n");//直接空代码即可,因为这里并不会被触发调用 } void error_cb(struct bufferevent *bev, short event, void *arg) { evutil_socket_t fd = bufferevent_getfd(bev); printf("fd = %u, ", fd); if (event & BEV_EVENT_TIMEOUT) { printf("Timed out\n"); //if bufferevent_set_timeouts() called } else if (event & BEV_EVENT_EOF) { printf("connection closed\n"); } else if (event & BEV_EVENT_ERROR) { printf("some other error\n"); } bufferevent_free(bev); }
客户端代码:
[cpp]
view plaincopyprint?
//客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#define SERV_PORT 9999
#define MAX_LINE 1024
void cmd_msg_cb(int fd,short event,void *arg);
void read_cb(struct bufferevent *bev,void *arg);
void error_cb(struct bufferevent *bev,short event,void *arg);
int main(int argc,char *argv[])
{
if(argc < 2)
{
perror("usage: echocli <IPadress>");
return 1;
}
evutil_socket_t sockfd;
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket\n");
return 1;
}
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 1)
{
perror("inet_ntop\n");
return 1;
}
if(connect(sockfd, (struct sockaddr *) &servaddr,sizeof(servaddr)) < 0)
{
perror("connect\n");
return 1;
}
evutil_make_socket_nonblocking(sockfd);
printf("Connect to server sucessfully!\n");
// build event base
struct event_base *base = event_base_new();
if(base == NULL)
{
perror("event_base\n");
return 1;
}
const char *eventMechanism = event_base_get_method(base);
printf("Event mechanism used is %s\n", eventMechanism);
printf("sockfd = %d\n", sockfd);
struct bufferevent *bev = bufferevent_socket_new(base, sockfd, BEV_OPT_CLOSE_ON_FREE);
struct event *ev_cmd;
ev_cmd = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST, cmd_msg_cb, (void *)bev);
event_add(ev_cmd, NULL);
bufferevent_setcb(bev, read_cb, NULL, error_cb, (void *)ev_cmd);
bufferevent_enable(bev, EV_READ | EV_PERSIST);
event_base_dispatch(base);
printf("The End.");
return 0;
}
void cmd_msg_cb(int fd,short event,void *arg)
{
char msg[MAX_LINE];
int nread = read(fd, msg,
sizeof(msg));
if(nread < 0)
{
perror("stdio read fail\n");
return;
}
struct bufferevent *bev = (struct bufferevent *)arg;
bufferevent_write(bev, msg, nread);
}
void read_cb(struct bufferevent *bev,void *arg)
{
char line[MAX_LINE + 1];
int n;
evutil_socket_t fd = bufferevent_getfd(bev);
while((n = bufferevent_read(bev, line, MAX_LINE)) > 0)
{
line
= '\0';
printf("fd = %u, read from server: %s", fd, line);
}
}
void error_cb(struct bufferevent *bev,short event,void *arg)
{
evutil_socket_t fd = bufferevent_getfd(bev);
printf("fd = %u, ", fd);
if(event & BEV_EVENT_TIMEOUT)
printf("Time out.\n"); // if bufferevent_set_timeouts() is called
else if(event & BEV_EVENT_EOF)
printf("Connection closed.\n");
else if(event & BEV_EVENT_ERROR)
printf("Some other error.\n");
bufferevent_free(bev);
struct event *ev = (struct event *)arg;
event_free(ev);
}
//客户端代码 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <string.h> #include <netinet/in.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/types.h> #include <event2/event.h> #include <event2/bufferevent.h> #define SERV_PORT 9999 #define MAX_LINE 1024 void cmd_msg_cb(int fd, short event, void *arg); void read_cb(struct bufferevent *bev, void *arg); void error_cb(struct bufferevent *bev, short event, void *arg); int main(int argc, char *argv[]) { if(argc < 2) { perror("usage: echocli <IPadress>"); return 1; } evutil_socket_t sockfd; if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket\n"); return 1; } struct sockaddr_in servaddr; bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 1) { perror("inet_ntop\n"); return 1; } if(connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) { perror("connect\n"); return 1; } evutil_make_socket_nonblocking(sockfd); printf("Connect to server sucessfully!\n"); // build event base struct event_base *base = event_base_new(); if(base == NULL) { perror("event_base\n"); return 1; } const char *eventMechanism = event_base_get_method(base); printf("Event mechanism used is %s\n", eventMechanism); printf("sockfd = %d\n", sockfd); struct bufferevent *bev = bufferevent_socket_new(base, sockfd, BEV_OPT_CLOSE_ON_FREE); struct event *ev_cmd; ev_cmd = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST, cmd_msg_cb, (void *)bev); event_add(ev_cmd, NULL); bufferevent_setcb(bev, read_cb, NULL, error_cb, (void *)ev_cmd); bufferevent_enable(bev, EV_READ | EV_PERSIST); event_base_dispatch(base); printf("The End."); return 0; } void cmd_msg_cb(int fd, short event, void *arg) { char msg[MAX_LINE]; int nread = read(fd, msg, sizeof(msg)); if(nread < 0) { perror("stdio read fail\n"); return; } struct bufferevent *bev = (struct bufferevent *)arg; bufferevent_write(bev, msg, nread); } void read_cb(struct bufferevent *bev, void *arg) { char line[MAX_LINE + 1]; int n; evutil_socket_t fd = bufferevent_getfd(bev); while((n = bufferevent_read(bev, line, MAX_LINE)) > 0) { line = '\0'; printf("fd = %u, read from server: %s", fd, line); } } void error_cb(struct bufferevent *bev, short event, void *arg) { evutil_socket_t fd = bufferevent_getfd(bev); printf("fd = %u, ", fd); if(event & BEV_EVENT_TIMEOUT) printf("Time out.\n"); // if bufferevent_set_timeouts() is called else if(event & BEV_EVENT_EOF) printf("Connection closed.\n"); else if(event & BEV_EVENT_ERROR) printf("Some other error.\n"); bufferevent_free(bev); struct event *ev = (struct event *)arg; event_free(ev); }
编译
[cpp]
view plaincopyprint?
chen@chen-book1:~/libevent20/sample$ gcc echo_client.c -o client chen@chen-book1:~/libevent20/sample$ gcc ser.c -o ser -levent chen@chen-book1:~/libevent20/sample$ ./ser & [1] 6995 chen@chen-book1:~/libevent20/sample$ ./client localhost 25341 receive data:hello , size:6 msg: hello receive data:, size:0 chen@chen-book1:~/libevent20/sample$ ./client localhost 25341 receive data:hello , size:6 msg: hello
chen@chen-book1:~/libevent20/sample$ gcc echo_client.c -o client chen@chen-book1:~/libevent20/sample$ gcc ser.c -o ser -levent chen@chen-book1:~/libevent20/sample$ ./ser & [1] 6995 chen@chen-book1:~/libevent20/sample$ ./client localhost 25341 receive data:hello , size:6 msg: hello receive data:, size:0 chen@chen-book1:~/libevent20/sample$ ./client localhost 25341 receive data:hello , size:6 msg: hello
运行结果如下所示:本文为了方便区别,将客户端发送到服务端再回显到客户端的过程,在从服务端回显到客户端的时候,去掉前两个字符。如,客户端输入1234567,则从服务端读取到的数据为34567。
客户端的输入和输出结果:
服务端的输入和输出结果:
参考:
http://www.cnblogs.com/cnspace/archive/2011/07/19/2110891.html http://www.felix021.com/blog/read.php?2068 http://www.bkjia.com/ASPjc/886985.html http://blog.csdn.net/tujiaw/article/details/8872943 http://popozhu.github.io/2013/06/26/libevent_r5_bufferevent%E5%9F%BA%E7%A1%80%E5%92%8C%E6%A6%82%E5%BF%B5/ http://blog.csdn.net/bestone0213/article/details/46729091 https://www.ibm.com/developerworks/cn/aix/library/au-libev/
相关文章推荐
- 使用Block在两个界面之间传值(Block高级用法:Block传值)
- mysql 导入excel数据成功,但数据表中80%数据都变成了2147483647解决方案
- HDU 5512
- VC MFC 屏蔽ESC和ENTER键关闭对话框
- 为 TextView 的部分文字设置超链接样式并监听点击事件
- 统一关闭域客户端防火墙服务/功能
- 【tarjan】BZOJ 1051:受欢迎的牛
- Java报表工具简介
- 统一关闭域客户端防火墙服务/功能
- SQL计算两个日期的差(天、小时、分、秒)
- 基于STM32的WAV音频格式播放器
- CBPeripheralManager学习笔记
- (一)keil4 MDK 开发环境下编写裸机程序 (参考杨铸 北航) (开发板只需要连接JLNK 就行了)
- startActivity时报错Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVI
- 二维数组名的本质是数组指针
- unity3d 中动画的帧事件
- Fragment集成cordova框架遇到的问题
- vue.js学习
- 获取Linux命令源代码的方法
- 无法打开物理文件 操作系统错误 5:拒绝访问 SQL Sever (附加数据库出错的解决方案)