写一个服务器程序包括以下几个步骤:

  1. socket
  2. bind
  3. listen
  4. accept

在 libevent 中,将上面几个函数都封装在了一起,即 evconnlistener_new_bind 函数。因为做了这么多封装,所以该函数的参数也特别多。
该函数被声明在 listener.h 中。其函数原型如下。

Writing a server program includes the following steps:

  1. socket
  2. bind
  3. listen
  4. accept

In libevent, the functions above are all wrapped together in the evconnlistener_new_bind function. Because of all this wrapping, this function also has a particularly large number of parameters.
This function is declared in listener.h. Its prototype is as follows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
Allocate a new evconnlistener object to listen for incoming TCP connections
on a given address.

@param base The event base to associate the listener with.
@param cb A callback to be invoked when a new connection arrives. If the
callback is NULL, the listener will be treated as disabled until the
callback is set.
@param ptr A user-supplied pointer to give to the callback.
@param flags Any number of LEV_OPT_* flags
@param backlog Passed to the listen() call to determine the length of the
acceptable connection backlog. Set to -1 for a reasonable default.
@param sa The address to listen for connections on.
@param socklen The length of the address.
*/
EVENT2_EXPORT_SYMBOL
struct evconnlistener *evconnlistener_new_bind(struct event_base *base,
evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,
const struct sockaddr *sa, int socklen);

可以看到该函数的第一个参数需要一个事件集合,因此,在调用该函数之前应该先创建一个事件集合。通过 event_base_new 函数。即

It can be seen that the first parameter of this function requires an event base, so before calling this function, an event base should be created first, via the event_base_new function. That is:

1
struct event_base* base = event_base_new();

第二个参数需要一个回调函数,当有一个新的连接到来时会调用该回调函数,如果将该回调函数设置为 NULL, 那么就不会做任何处理。
回调函数的类型是evconnlistener_cb,声明如下:

The second parameter requires a callback function, which is invoked when a new connection arrives. If this callback function is set to NULL, then nothing will be done.
The type of the callback function is evconnlistener_cb, declared as follows:

1
2
3
4
5
6
7
8
9
10
11
/**@file event2/listener.h

@brief A callback that we invoke when a listener has a new connection.

@param listener The evconnlistener
@param fd The new file descriptor
@param addr The source address of the connection
@param socklen The length of addr
@param user_arg the pointer passed to evconnlistener_new()
*/
typedef void (*evconnlistener_cb)(struct evconnlistener *, evutil_socket_t, struct sockaddr *, int socklen, void *);

flags 参数的可供选项也在 listener.h 中进行了介绍,主要包括以下几个。

The available options for the flags parameter are also introduced in listener.h, mainly including the following.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/** Flag: Indicates that we should not make incoming sockets nonblocking
* before passing them to the callback. */
#define LEV_OPT_LEAVE_SOCKETS_BLOCKING (1u<<0)
/** Flag: Indicates that freeing the listener should close the underlying
* socket. */
#define LEV_OPT_CLOSE_ON_FREE (1u<<1)
/** Flag: Indicates that we should set the close-on-exec flag, if possible */
#define LEV_OPT_CLOSE_ON_EXEC (1u<<2)
/** Flag: Indicates that we should disable the timeout (if any) between when
* this socket is closed and when we can listen again on the same port. */
#define LEV_OPT_REUSEABLE (1u<<3)
/** Flag: Indicates that the listener should be locked so it's safe to use
* from multiple threadcs at once. */
#define LEV_OPT_THREADSAFE (1u<<4)
/** Flag: Indicates that the listener should be created in disabled
* state. Use evconnlistener_enable() to enable it later. */
#define LEV_OPT_DISABLED (1u<<5)
/** Flag: Indicates that the listener should defer accept() until data is
* available, if possible. Ignored on platforms that do not support this.
*
* This option can help performance for protocols where the client transmits
* immediately after connecting. Do not use this option if your protocol
* _doesn't_ start out with the client transmitting data, since in that case
* this option will sometimes cause the kernel to never tell you about the
* connection.
*
* This option is only supported by evconnlistener_new_bind(): it can't
* work with evconnlistener_new_fd(), since the listener needs to be told
* to use the option before it is actually bound.
*/
#define LEV_OPT_DEFERRED_ACCEPT (1u<<6)
/** Flag: Indicates that we ask to allow multiple servers (processes or
* threads) to bind to the same port if they each set the option.
*
* SO_REUSEPORT is what most people would expect SO_REUSEADDR to be, however
* SO_REUSEPORT does not imply SO_REUSEADDR.
*
* This is only available on Linux and kernel 3.9+
*/
#define LEV_OPT_REUSEABLE_PORT (1u<<7)
/** Flag: Indicates that the listener wants to work only in IPv6 socket.
*
* According to RFC3493 and most Linux distributions, default value is to
* work in IPv4-mapped mode. If there is a requirement to bind same port
* on same ip addresses but different handlers for both IPv4 and IPv6,
* it is required to set IPV6_V6ONLY socket option to be sure that the
* code works as expected without affected by bindv6only sysctl setting in
* system.
*
* This socket option also supported by Windows.
*/
#define LEV_OPT_BIND_IPV6ONLY (1u<<8)

剩下的其他参数都是常规操作了。

创建一个服务器需要用到的一些基本函数参考下面的例子。

The remaining parameters are all routine operations.

Some basic functions needed to create a server are shown in the example below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <stdio.h>
#include <stdlib.h>
#include <event.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <event2/listener.h>


void read_cb(struct bufferevent* bev,void* ctx)
{
// 读取数据
char buf[128] = {0};
size_t ret = bufferevent_read(bev,buf,sizeof(buf));
if( ret < 0){
printf("read error\n");
}
else{
printf("read %s\n",buf);
}
}

void event_cb(struct bufferevent* bev,short what,void* ctx)
{
if(what & BEV_EVENT_EOF)
{
printf("客户端下线\n");
//释放 bufferevent 对象
bufferevent_free(bev);
}
else{
printf("未知异常\n");
}
}

void listener_cb(struct evconnlistener * listener, evutil_socket_t fd, struct sockaddr * addr, int socklen, void * arg)
{
printf("接受 %d 的连接\n", fd);
struct event_base *base = arg;

// 针对已经存在的 socket 创建 bufferevent对象
// 第一个参数 事件集合从回调函数的参数中传递进来
// 第二个参数 接受连接的文件描述符
// 选项 BEV_OPT_ 开头的选项
struct bufferevent* bev = bufferevent_socket_new(base,fd,BEV_OPT_CLOSE_ON_FREE);
if( NULL == bev)
{
printf("bufferevent_socket_new error\n");
exit(1);
}

// 给 bufferevent 设置回调函数
// 第一个参数 bufferevent 对象
// 第二个参数 可读时的回调函数
// 第三个参数 可写时的回调函数
// 其他事件 比如异常 时的回调函数
// 第四个参数 给回调函数传参
bufferevent_setcb(bev,read_cb,NULL,event_cb,NULL);

// 使能事件类型
// 第一个参数 bufferevent 对象
// 第二个参数 EV_READ 和 EV_WRITE 的组合
bufferevent_enable(bev,EV_READ);
}

int main()
{
// 创建一个事件集合
struct event_base* base = event_base_new();
if( NULL == base)
{
printf("event_base_new error\n");
exit(1);
}

struct sockaddr_in server_addr;
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = 8000;
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

// 创建 监听对象,在指定的地址上监听接下来的TCP连接
struct evconnlistener* listener = evconnlistener_new_bind(base, listener_cb,
base, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 10 ,(struct sockaddr*)&server_addr,sizeof(server_addr) );

if( NULL == listener )
{
printf("evconnlistener_new_bind error\n");
exit(1);
}


// 监听集合中的事件
event_base_dispatch(base);

// 释放两个对象
evconnlistener_free(listener);
event_base_free(base);

return 0;
}