转载自:http://www.cppblog.com/mysileng/archive/2013/02/05/197748.html
libevent的evbuffer实现了为向后面添加数据和从前面移除数据而优化的字节队列。
evbuffer用于处理缓冲网络IO的“缓冲”部分。它不提供调度IO或者当IO就绪时触发IO的功能:这是bufferevent的工作。
除非特别说明,本章描述的函数都在event2/buffer.h中声明。
1 创建和释放evbuffer
接口
struct
evbuffer
*
evbuffer_new(void
);
void
evbuffer_free(struct
evbuffer
*
buf);
这两个函数的功能很简明:evbuffer_new()分配和返回一个新的空evbuffer;而evbuffer_free()释放evbuffer和其内容。
这两个函数从libevent 0.8版就存在了。
void evbuffer_free(struct evbuffer * buf);
这两个函数的功能很简明:evbuffer_new()分配和返回一个新的空evbuffer;而evbuffer_free()释放evbuffer和其内容。
这两个函数从libevent
2 evbuffer与线程安全
接口
int
evbuffer_enable_locking(struct
evbuffer
*
buf, void
*lock
);
void
evbuffer_lock(struct
evbuffer
*
buf);
void
evbuffer_unlock(struct
evbuffer
*
buf);
默认情况下,在多个线程中同时访问evbuffer是不安全的。如果需要这样的访问,可以调用evbuffer_enable_locking()。如果lock参数为NULL,libevent会使用evthread_set_lock_creation_callback提供的锁创建函数创建一个锁。否则,libevent将lock参数用作锁。
evbuffer_lock()和evbuffer_unlock()函数分别请求和释放evbuffer上的锁。可以使用这两个函数让一系列操作是原子的。如果evbuffer没有启用锁,这两个函数不做任何操作。
(注意:对于单个操作,不需要调用evbuffer_lock()和evbuffer_unlock():如果evbuffer启用了锁,单个操作就已经是原子的。只有在需要多个操作连续执行,不让其他线程介入的时候,才需要手动锁定evbuffer)
这些函数都在2.0.1-alpha版本中引入。
void evbuffer_lock(struct evbuffer * buf);
void evbuffer_unlock(struct evbuffer * buf);
默认情况下,在多个线程中同时访问evbuffer是不安全的。如果需要这样的访问,可以调用evbuffer_enable_locking()。如果lock参数为NULL,libevent会使用evthread_set_lock_creation_callback提供的锁创建函数创建一个锁。否则,libevent将lock参数用作锁。
evbuffer_lock()和evbuffer_unlock()函数分别请求和释放evbuffer上的锁。可以使用这两个函数让一系列操作是原子的。如果evbuffer没有启用锁,这两个函数不做任何操作。
(注意:对于单个操作,不需要调用evbuffer_lock()和evbuffer_unlock():如果evbuffer启用了锁,单个操作就已经是原子的。只有在需要多个操作连续执行,不让其他线程介入的时候,才需要手动锁定evbuffer)
这些函数都在2.0.1-alpha版本中引入。
3 检查evbuffer
接口
size_t evbuffer_get_length(const
struct
evbuffer
*
buf);
这个函数返回evbuffer存储的字节数,它在2.0.1-alpha版本中引入。
这个函数返回evbuffer存储的字节数,它在2.0.1-alpha版本中引入。
接口
size_t evbuffer_get_contiguous_space(const
struct
evbuffer
*
buf);
这个函数返回连续地存储在evbuffer前面的字节数。evbuffer中的数据可能存储在多个分隔开的内存块中,这个函数返回当前第一个块中的字节数。
这个函数在2.0.1-alpha版本引入。
这个函数返回连续地存储在evbuffer前面的字节数。evbuffer中的数据可能存储在多个分隔开的内存块中,这个函数返回当前第一个块中的字节数。
这个函数在2.0.1-alpha版本引入。
4 向evbuffer添加数据:基础
接口
int
evbuffer_add(struct
evbuffer
*
buf, const
void
*
data, size_t datlen);
这个函数添加data处的datalen字节到buf的末尾,成功时返回0,失败时返回-1。
这个函数添加data处的datalen字节到buf的末尾,成功时返回0,失败时返回-1。
接口
int
evbuffer_add_printf(struct
evbuffer
*
buf, const
char
*
fmt, )
int
evbuffer_add_vprintf(struct
evbuffer
*
buf, const
char
*
fmt, va_list ap);
这些函数添加格式化的数据到buf末尾。格式参数和其他参数的处理分别与C库函数printf和vprintf相同。函数返回添加的字节数。
int evbuffer_add_vprintf(struct evbuffer * buf, const char * fmt, va_list ap);
这些函数添加格式化的数据到buf末尾。格式参数和其他参数的处理分别与C库函数printf和vprintf相同。函数返回添加的字节数。
接口
int
evbuffer_expand(struct
evbuffer
*
buf, size_t datlen);
这个函数修改缓冲区的最后一块,或者添加一个新的块,使得缓冲区足以容纳datlen字节,而不需要更多的内存分配。
这个函数修改缓冲区的最后一块,或者添加一个新的块,使得缓冲区足以容纳datlen字节,而不需要更多的内存分配。
示例
/*
Here are two ways to add "Hello world 2.0.1" to a buffer.
*/
/*
Directly:
*/
evbuffer_add(buf,
"
Hello world 2.0.1
"
,
17
);
/*
Via printf:
*/
evbuffer_add_printf(buf,
"
Hello %s %d.%d.%d
"
,
"
world
"
,
2
,
0
,
1
);
evbuffer_add()和evbuffer_add_printf()函数在libevent 0.8版本引入;evbuffer_expand()首次出现在0.9版本,而evbuffer_add_printf()首次出现在1.1版本。
/* Directly: */
evbuffer_add(buf, " Hello world 2.0.1 " , 17 );
/* Via printf: */
evbuffer_add_printf(buf, " Hello %s %d.%d.%d " , " world " , 2 , 0 , 1 );
evbuffer_add()和evbuffer_add_printf()函数在libevent
5 将数据从一个evbuffer移动到另一个
为提高效率,libevent具有将数据从一个evbuffer移动到另一个的优化函数。
接口
int
evbuffer_add_buffer(struct
evbuffer
*
dst, struct
evbuffer
*
src);
int
evbuffer_remove_buffer(struct
evbuffer
*
src, struct
evbuffer
*
dst,
size_t datlen);
evbuffer_add_buffer()将src中的所有数据移动到dst末尾,成功时返回0,失败时返回-1。
evbuffer_remove_buffer()函数从src中移动datlen字节到dst末尾,尽量少进行复制。如果字节数小于datlen,所有字节被移动。函数返回移动的字节数。
evbuffer_add_buffer()在0.8版本引入;evbuffer_remove_buffer()是2.0.1-alpha版本新增加的。
int evbuffer_remove_buffer(struct evbuffer * src, struct evbuffer * dst,
size_t datlen);
evbuffer_add_buffer()将src中的所有数据移动到dst末尾,成功时返回0,失败时返回-1。
evbuffer_remove_buffer()函数从src中移动datlen字节到dst末尾,尽量少进行复制。如果字节数小于datlen,所有字节被移动。函数返回移动的字节数。
evbuffer_add_buffer()在0.8版本引入;evbuffer_remove_buffer()是2.0.1-alpha版本新增加的。
6 添加数据到evbuffer前面
接口
int
evbuffer_prepend(struct
evbuffer
*
buf, const
void
*
data, size_t size);
int
evbuffer_prepend_buffer(struct
evbuffer
*
dst, struct
evbuffer
*
src);
除了将数据移动到目标缓冲区前面之外,这两个函数的行为分别与evbuffer_add()和evbuffer_add_buffer()相同。
使用这些函数时要当心,永远不要对与bufferevent共享的evbuffer使用。这些函数是2.0.1-alpha版本新添加的。
int evbuffer_prepend_buffer(struct evbuffer * dst, struct evbuffer * src);
除了将数据移动到目标缓冲区前面之外,这两个函数的行为分别与evbuffer_add()和evbuffer_add_buffer()相同。
使用这些函数时要当心,永远不要对与bufferevent共享的evbuffer使用。这些函数是2.0.1-alpha版本新添加的。
7 重新排列evbuffer的内部布局
有时候需要取出evbuffer前面的N字节,将其看作连续的字节数组。要做到这一点,首先必须确保缓冲区的前面确实是连续的。
接口
unsigned char
*
evbuffer_pullup(struct
evbuffer
*
buf, ev_ssize_t size);
evbuffer_pullup()函数“线性化”buf前面的size字节,必要时将进行复制或者移动,以保证这些字节是连续的,占据相同的内存块。如果size是负的,函数会线性化整个缓冲区。如果size大于缓冲区中的字节数,函数返回NULL。否则,evbuffer_pullup()返回指向buf中首字节的指针。
调用evbuffer_pullup()时使用较大的size参数可能会非常慢,因为这可能需要复制整个缓冲区的内容。
evbuffer_pullup()函数“线性化”buf前面的size字节,必要时将进行复制或者移动,以保证这些字节是连续的,占据相同的内存块。如果size是负的,函数会线性化整个缓冲区。如果size大于缓冲区中的字节数,函数返回NULL。否则,evbuffer_pullup()返回指向buf中首字节的指针。
调用evbuffer_pullup()时使用较大的size参数可能会非常慢,因为这可能需要复制整个缓冲区的内容。
示例
#include
<
event2
/
buffer.h
>
#include
<
event2
/
util.h
>
#include
<string
.h
>
int
parse_socks4(struct
evbuffer
*
buf, ev_uint16_t
*
port, ev_uint32_t
*
addr)
{
/*
Let's parse the start of a SOCKS4 request! The format is easy:
* 1 byte of version, 1 byte of command, 2 bytes destport, 4 bytes of
* destip.
*/
unsigned char
*
mem;
mem
=
evbuffer_pullup(buf,
8
);
if
(mem
==
NULL) {
/*
Not enough data in the buffer
*/
return
0
;
} else
if
(mem[
0
]
!=
4
||
mem[
1
]
!=
1
) {
/*
Unrecognized protocol or command
*/
return
-
1
;
} else
{
memcpy(port, mem
+
2
,
2
);
memcpy(addr, mem
+
4
,
4
);
*
port
=
ntohs(
*
port);
*
addr
=
ntohl(
*
addr);
/*
Actually remove the data from the buffer now that we know we
like it.
*/
evbuffer_drain(buf,
8
);
return
1
;
}
}
#include < event2 / util.h >
#include <string .h >
int parse_socks4(struct evbuffer * buf, ev_uint16_t * port, ev_uint32_t * addr)
{
/* Let's parse the start of a SOCKS4 request! The format is easy:
* 1 byte of version, 1 byte of command, 2 bytes destport, 4 bytes of
* destip. */
unsigned char * mem;
mem = evbuffer_pullup(buf, 8 );
if (mem == NULL) {
/* Not enough data in the buffer */
return 0 ;
} else if (mem[ 0 ] != 4 || mem[ 1 ] != 1 ) {
/* Unrecognized protocol or command */
return - 1 ;
} else {
memcpy(port, mem + 2 , 2 );
memcpy(addr, mem + 4 , 4 );
* port = ntohs( * port);
* addr = ntohl( * addr);
/* Actually remove the data from the buffer now that we know we
like it. */
evbuffer_drain(buf, 8 );
return 1 ;
}
}
提示
使用evbuffer_get_contiguous_space()返回的值作为尺寸值调用evbuffer_pullup()不会导致任何数据复制或者移动。
evbuffer_pullup()函数由2.0.1-alpha版本新增加:先前版本的libevent总是保证evbuffer中的数据是连续的,而不计开销。
使用evbuffer_get_contiguous_space()返回的值作为尺寸值调用evbuffer_pullup()不会导致任何数据复制或者移动。
evbuffer_pullup()函数由2.0.1-alpha版本新增加:先前版本的libevent总是保证evbuffer中的数据是连续的,而不计开销。
8 从evbuffer中移除数据
接口
int
evbuffer_drain(struct
evbuffer
*
buf, size_t len);
int
evbuffer_remove(struct
evbuffer
*
buf, void
*
data, size_t datlen);
evbuffer_remove()函数从buf前面复制和移除datlen字节到data处的内存中。如果可用字节少于datlen,函数复制所有字节。失败时返回-1,否则返回复制了的字节数。
evbuffer_drain()函数的行为与evbuffer_remove()相同,只是它不进行数据复制:而只是将数据从缓冲区前面移除。成功时返回0,失败时返回-1。
evbuffer_drain()由0.8版引入,evbuffer_remove()首次出现在0.9版。
int evbuffer_remove(struct evbuffer * buf, void * data, size_t datlen);
evbuffer_remove()函数从buf前面复制和移除datlen字节到data处的内存中。如果可用字节少于datlen,函数复制所有字节。失败时返回-1,否则返回复制了的字节数。
evbuffer_drain()函数的行为与evbuffer_remove()相同,只是它不进行数据复制:而只是将数据从缓冲区前面移除。成功时返回0,失败时返回-1。
evbuffer_drain()由0.8版引入,evbuffer_remove()首次出现在0.9版。
9 从evbuffer中复制出数据
有时候需要获取缓冲区前面数据的副本,而不清除数据。比如说,可能需要查看某特定类型的记录是否已经完整到达,而不清除任何数据(像evbuffer_remove那样),或者在内部重新排列缓冲区(像evbuffer_pullup那样)。
接口
ev_ssize_t evbuffer_copyout(struct
evbuffer
*
buf, void
*
data, size_t datlen);
ev_ssize_t evbuffer_copyout_from(struct
evbuffer
*
buf,
const
struct
evbuffer_ptr
*
pos,
void
*
data_out, size_t datlen);
evbuffer_copyout()的行为与evbuffer_remove()相同,但是它不从缓冲区移除任何数据。也就是说,它从buf前面复制datlen字节到data处的内存中。如果可用字节少于datlen,函数会复制所有字节。失败时返回-1,否则返回复制的字节数。
如果从缓冲区复制数据太慢,可以使用evbuffer_peek()。
ev_ssize_t evbuffer_copyout_from(struct evbuffer * buf,
const struct evbuffer_ptr * pos,
void * data_out, size_t datlen);
evbuffer_copyout()的行为与evbuffer_remove()相同,但是它不从缓冲区移除任何数据。也就是说,它从buf前面复制datlen字节到data处的内存中。如果可用字节少于datlen,函数会复制所有字节。失败时返回-1,否则返回复制的字节数。
如果从缓冲区复制数据太慢,可以使用evbuffer_peek()。
示例
#include
<
event2
/
buffer.h
>
#include
<
event2
/
util.h
>
#include
<
stdlib.h
>
#include
<
stdlib.h
>
int
get_record(struct
evbuffer
*
buf, size_t
*
size_out, char
**
record_out)
{
/*
Let's assume that we're speaking some protocol where records
contain a 4-byte size field in network order, followed by that
number of bytes. We will return 1 and set the 'out' fields if we
have a whole record, return 0 if the record isn't here yet, and
-1 on error.
*/
size_t buffer_len
=
evbuffer_get_length(buf);
ev_uint32_t record_len;
char
*
record;
if
(buffer_len
<
4
)
return
0
;
/*
The size field hasn't arrived.
*/
/*
We use evbuffer_copyout here so that the size field will stay on
the buffer for now.
*/
evbuffer_copyout(buf,
&
record_len,
4
);
/*
Convert len_buf into host order.
*/
record_len
=
ntohl(record_len);
if
(buffer_len
<
record_len
+
4
)
return
0
;
/*
The record hasn't arrived
*/
/*
Okay, _now_ we can remove the record.
*/
record
=
malloc(record_len);
if
(record
==
NULL)
return
-
1
;
evbuffer_drain(buf,
4
);
evbuffer_remove(buf, record, record_len);
*
record_out
=
record;
*
size_out
=
record_len;
return
1
;
}
#include < event2 / util.h >
#include < stdlib.h >
#include < stdlib.h >
int get_record(struct evbuffer * buf, size_t * size_out, char ** record_out)
{
/* Let's assume that we're speaking some protocol where records
contain a 4-byte size field in network order, followed by that
number of bytes. We will return 1 and set the 'out' fields if we
have a whole record, return 0 if the record isn't here yet, and
-1 on error. */
size_t buffer_len = evbuffer_get_length(buf);
ev_uint32_t record_len;
char * record;
if (buffer_len < 4 )
return 0 ; /* The size field hasn't arrived. */
/* We use evbuffer_copyout here so that the size field will stay on
the buffer for now. */
evbuffer_copyout(buf, & record_len, 4 );
/* Convert len_buf into host order. */
record_len = ntohl(record_len);
if (buffer_len < record_len + 4 )
return 0 ; /* The record hasn't arrived */
/* Okay, _now_ we can remove the record. */
record = malloc(record_len);
if (record == NULL)
return - 1 ;
evbuffer_drain(buf, 4 );
evbuffer_remove(buf, record, record_len);
* record_out = record;
* size_out = record_len;
return 1 ;
}
10 面向行的输入
接口
enum
evbuffer_eol_style {
EVBUFFER_EOL_ANY,
EVBUFFER_EOL_CRLF,
EVBUFFER_EOL_CRLF_STRICT,
EVBUFFER_EOL_LF,
EVBUFFER_EOL_NUL
};
char
*
evbuffer_readln(struct
evbuffer
*
buffer, size_t
*
n_read_out,
enum
evbuffer_eol_style eol_style);
很多互联网协议使用基于行的格式。evbuffer_readln()函数从evbuffer前面取出一行,用一个新分配的空字符结束的字符串返回这一行。如果n_read_out不是NULL,则它被设置为返回的字符串的字节数。如果没有整行供读取,函数返回空。返回的字符串不包括行结束符。
evbuffer_readln()理解4种行结束格式:
l EVBUFFER_EOL_LF
行尾是单个换行符(也就是\n,ASCII值是0x0A)
l EVBUFFER_EOL_CRLF_STRICT
行尾是一个回车符,后随一个换行符(也就是\r\n,ASCII值是0x0D 0x0A)
l EVBUFFER_EOL_CRLF
行尾是一个可选的回车,后随一个换行符(也就是说,可以是\r\n或者\n)。这种格式对于解析基于文本的互联网协议很有用,因为标准通常要求\r\n的行结束符,而不遵循标准的客户端有时候只使用\n。
l EVBUFFER_EOL_ANY
行尾是任意数量、任意次序的回车和换行符。这种格式不是特别有用。它的存在主要是为了向后兼容。
(注意,如果使用event_se_mem_functions()覆盖默认的malloc,则evbuffer_readln返回的字符串将由你指定的malloc替代函数分配)
EVBUFFER_EOL_ANY,
EVBUFFER_EOL_CRLF,
EVBUFFER_EOL_CRLF_STRICT,
EVBUFFER_EOL_LF,
EVBUFFER_EOL_NUL
};
char * evbuffer_readln(struct evbuffer * buffer, size_t * n_read_out,
enum evbuffer_eol_style eol_style);
很多互联网协议使用基于行的格式。evbuffer_readln()函数从evbuffer前面取出一行,用一个新分配的空字符结束的字符串返回这一行。如果n_read_out不是NULL,则它被设置为返回的字符串的字节数。如果没有整行供读取,函数返回空。返回的字符串不包括行结束符。
evbuffer_readln()理解4种行结束格式:
l
行尾是单个换行符(也就是\n,ASCII值是0x0A)
l
行尾是一个回车符,后随一个换行符(也就是\r\n,ASCII值是0x0D
l
行尾是一个可选的回车,后随一个换行符(也就是说,可以是\r\n或者\n)。这种格式对于解析基于文本的互联网协议很有用,因为标准通常要求\r\n的行结束符,而不遵循标准的客户端有时候只使用\n。
l
行尾是任意数量、任意次序的回车和换行符。这种格式不是特别有用。它的存在主要是为了向后兼容。
(注意,如果使用event_se_mem_functions()覆盖默认的malloc,则evbuffer_readln返回的字符串将由你指定的malloc替代函数分配)
示例
char
*
request_line;
size_t len;
request_line
=
evbuffer_readln(buf,
&
len, EVBUFFER_EOL_CRLF);
if
(
!
request_line) {
/*
The first line has not arrived yet.
*/
} else
{
if
(
!
strncmp(request_line,
"
HTTP/1.0
"
,
9
)) {
/*
HTTP 1.0 detected
*/
}
free(request_line);
}
evbuffer_readln()接口在1.4.14-stable及以后版本中可用。
size_t len;
request_line = evbuffer_readln(buf, & len, EVBUFFER_EOL_CRLF);
if ( ! request_line) {
/* The first line has not arrived yet. */
} else {
if ( ! strncmp(request_line, " HTTP/1.0 " , 9 )) {
/* HTTP 1.0 detected */
}
free(request_line);
}
evbuffer_readln()接口在1.4.14-stable及以后版本中可用。
11 在evbuffer中搜索
evbuffer_ptr结构体指示evbuffer中的一个位置,包含可用于在evbuffer中迭代的数据。
接口
struct
evbuffer_ptr {
ev_ssize_t pos;
struct
{
/*
internal fields
*/
} _internal;
};
pos是唯一的公有字段,用户代码不应该使用其他字段。pos指示evbuffer中的一个位置,以到开始处的偏移量表示。
ev_ssize_t pos;
struct {
/* internal fields */
} _internal;
};
pos是唯一的公有字段,用户代码不应该使用其他字段。pos指示evbuffer中的一个位置,以到开始处的偏移量表示。
接口
struct
evbuffer_ptr evbuffer_search(struct
evbuffer
*
buffer,
const
char
*
what, size_t len, const
struct
evbuffer_ptr
*
start);
struct
evbuffer_ptr evbuffer_search_range(struct
evbuffer
*
buffer,
const
char
*
what, size_t len, const
struct
evbuffer_ptr
*
start,
const
struct
evbuffer_ptr
*
end);
struct
evbuffer_ptr evbuffer_search_eol(struct
evbuffer
*
buffer,
struct
evbuffer_ptr
*
start, size_t
*
eol_len_out,
enum
evbuffer_eol_style eol_style);
evbuffer_search()函数在缓冲区中查找含有len个字符的字符串what。函数返回包含字符串位置,或者在没有找到字符串时包含-1的evbuffer_ptr结构体。如果提供了start参数,则从指定的位置开始搜索;否则,从开始处进行搜索。
evbuffer_search_range()函数和evbuffer_search行为相同,只是它只考虑在end之前出现的what。
evbuffer_search_eol()函数像evbuffer_readln()一样检测行结束,但是不复制行,而是返回指向行结束符的evbuffer_ptr。如果eol_len_out非空,则它被设置为EOL字符串长度。
const char * what, size_t len, const struct evbuffer_ptr * start);
struct evbuffer_ptr evbuffer_search_range(struct evbuffer * buffer,
const char * what, size_t len, const struct evbuffer_ptr * start,
const struct evbuffer_ptr * end);
struct evbuffer_ptr evbuffer_search_eol(struct evbuffer * buffer,
struct evbuffer_ptr * start, size_t * eol_len_out,
enum evbuffer_eol_style eol_style);
evbuffer_search()函数在缓冲区中查找含有len个字符的字符串what。函数返回包含字符串位置,或者在没有找到字符串时包含-1的evbuffer_ptr结构体。如果提供了start参数,则从指定的位置开始搜索;否则,从开始处进行搜索。
evbuffer_search_range()函数和evbuffer_search行为相同,只是它只考虑在end之前出现的what。
evbuffer_search_eol()函数像evbuffer_readln()一样检测行结束,但是不复制行,而是返回指向行结束符的evbuffer_ptr。如果eol_len_out非空,则它被设置为EOL字符串长度。
接口
enum
evbuffer_ptr_how {
EVBUFFER_PTR_SET,
EVBUFFER_PTR_ADD
};
int
evbuffer_ptr_set(struct
evbuffer
*
buffer, struct
evbuffer_ptr
*
pos,
size_t position, enum
evbuffer_ptr_how how);
evbuffer_ptr_set函数操作buffer中的位置pos。如果how等于EVBUFFER_PTR_SET,指针被移动到缓冲区中的绝对位置position;如果等于EVBUFFER_PTR_ADD,则向前移动position字节。成功时函数返回0,失败时返回-1。
EVBUFFER_PTR_SET,
EVBUFFER_PTR_ADD
};
int evbuffer_ptr_set(struct evbuffer * buffer, struct evbuffer_ptr * pos,
size_t position, enum evbuffer_ptr_how how);
evbuffer_ptr_set函数操作buffer中的位置pos。如果how等于EVBUFFER_PTR_SET,指针被移动到缓冲区中的绝对位置position;如果等于EVBUFFER_PTR_ADD,则向前移动position字节。成功时函数返回0,失败时返回-1。
示例
#include
<
event2
/
buffer.h
>
#include
<string
.h
>
/*
Count the total occurrences of 'str' in 'buf'.
*/
int
count_instances(struct
evbuffer
*
buf, const
char
*
str)
{
size_t len
=
strlen(str);
int
total
=
0
;
struct
evbuffer_ptr p;
if
(
!
len)
/*
#include <string .h >
/* Count the total occurrences of 'str' in 'buf'. */
int count_instances(struct evbuffer * buf, const char * str)
{
size_t len = strlen(str);
int total = 0 ;
struct evbuffer_ptr p;
if ( ! len)
/*