前言
在上節中,我們介紹了evbuffers的部分知識,在本小節中,我們將對剩餘的部分一起分析總結。
evbuffer_drain
在上節中分析的evbuffer_add_buffer
中,如果添加源緩衝區到目的緩衝區結尾成功,則會調用evbuffer_drain
清除源緩衝區,但是它的功能除了清除整個有效緩衝區以外,還可以清除一部分,接下來我們來看看它的源碼:
void
evbuffer_drain(struct evbuffer *buf, size_t len)
{
size_t oldoff = buf->off;
/* 要清除的緩衝區長度大於了有效緩衝區的長度
* 則將有效緩衝區全部清除
* 否則只清除len長度的有效緩衝區
*/
if (len >= buf->off) {
buf->off = 0;
buf->buffer = buf->orig_buffer;
buf->misalign = 0;
/* 難得一見的goto語句
* 但是這裏好像可以用if-else替代
*/
goto done;
}
/* 縮小有效緩衝區的大小 */
buf->buffer += len;
buf->misalign += len;
buf->off -= len;
done:
/* Tell someone about changes in this buffer */
/* 隨着上面的一頓操作,緩衝區的大小如果已經改變,則回調處理函數 */
if (buf->off != oldoff && buf->cb != NULL)
(*buf->cb)(buf, oldoff, buf->off, buf->cbarg);
}
在當時調用evbuffer_add_buffer
時,傳遞給evbuffer_drain
的len
是等與buf->off
的,所以會清除整個緩衝區的大小,但是其實這個函數的作用是清除指定大小的有效緩衝區。邏輯也很簡單:
- len的長度是否大於等於當前有效緩衝區的長度
- 如果大於等於,則清除整個有效緩衝區
- 否則清除指定大小的有效緩衝區(將buffer後移,調整off)
- 如果滿足緩衝區大小被改變的條件,則調用回調函數
下面介紹一下evbuffer_remove
,它和evbuffer_drain
關係密切(畢竟裏面就調用了evbuffer_drain
),只不過會將該緩衝區指定大小的內容讀出來,然後再消除緩衝區。代碼如下:
/* Reads data from an event buffer and drains the bytes read */
int
evbuffer_remove(struct evbuffer *buf, void *data, size_t datlen)
{
size_t nread = datlen;
//這裏也是處理了一下指定大小大於有效緩衝區大小的情況
if (nread >= buf->off)
nread = buf->off;
/* 將數據拷貝到data
* 然後清除已讀的數據
* 最後返回讀取的大小
*/
memcpy(data, buf->buffer, nread);
evbuffer_drain(buf, nread);
return (nread);
}
添加格式化字符串到緩衝區
除了普通的數據,將格式化的字符串添加到緩衝區需要特殊的處理。這部分主要是由evbuffer_add_printf
以及evbuffer_add_vprintf
完成。
int
evbuffer_add_printf(struct evbuffer *buf, const char *fmt, ...)
{
int res = -1;
va_list ap;
va_start(ap, fmt);
res = evbuffer_add_vprintf(buf, fmt, ap); //調用該函數實現
va_end(ap);
return (res);
}
這個函數涉及到C語言中可變參數的用法,具體過程有時間我再整理出來。
裏面主要調用的了evbuffer_add_vprintf
函數,代碼如下:
int
evbuffer_add_vprintf(struct evbuffer *buf, const char *fmt, va_list ap)
{
char *buffer;
size_t space;
size_t oldoff = buf->off;
int sz;
va_list aq;
/* make sure that at least some space is available */
//檢測有無64字節的有效空間,如果沒有,才分配
evbuffer_expand(buf, 64);
for (;;) {
size_t used = buf->misalign + buf->off;
buffer = (char *)buf->buffer + buf->off;
assert(buf->totallen >= used);
space = buf->totallen - used;
#ifndef va_copy
#define va_copy(dst, src) memcpy(&(dst), &(src), sizeof(va_list))
#endif
va_copy(aq, ap);
//evutil_vsnprintf裏面主要就是調用vsnprintf
sz = evutil_vsnprintf(buffer, space, fmt, aq);
va_end(aq);
/* 將數據寫入緩衝區之後
* 調整緩衝區大小
* 調用相應的回調函數
*/
if (sz < 0)
return (-1);
if ((size_t)sz < space) {
buf->off += sz;
if (buf->cb != NULL)
(*buf->cb)(buf, oldoff, buf->off, buf->cbarg);
return (sz);
}
//如果前面空間不夠,則嘗試擴展
if (evbuffer_expand(buf, sz + 1) == -1)
return (-1);
}
/* NOTREACHED */
}
這部分主要就是需要理解可變參數的用法,其餘的跟evbuffer_add
的操作大同小異。
緩衝區的讀寫操作
接下來我們介紹緩衝區的讀寫操作,從簡到繁,先介紹寫相關的操作。
緩衝區的寫出操作(將緩衝區數據寫出)
緩衝區的寫操作主要由evbuffer_write
實現,代碼如下
int
evbuffer_write(struct evbuffer *buffer, int fd)
{
int n;
//將緩衝區數據寫入fd
#ifndef WIN32
n = write(fd, buffer->buffer, buffer->off);
#else
n = send(fd, buffer->buffer, buffer->off, 0);
#endif
if (n == -1)
return (-1);
if (n == 0)
return (0);
//數據被讀出之後,從緩衝區中清空讀取的n字節
evbuffer_drain(buffer, n);
return (n);
}
寫操作很簡單,就是將數據寫出去,然後將緩衝區對應的數據清除了就行了。
緩衝區的讀入操作(將指定的數據大小讀入緩衝區)
讀操作有幾個函數,首先先介紹evbuffer_read
:
//最多一次性讀取4096個字節
#define EVBUFFER_MAX_READ 4096
int
evbuffer_read(struct evbuffer *buf, int fd, int howmuch)
{
u_char *p;
size_t oldoff = buf->off;
int n = EVBUFFER_MAX_READ;
/* 下面有個條件編譯嵌套,需要注意一下
* #if defined(...)
* #ifdef WIN32
* ...
* #else
* ...
* #endif
* ...
* #endif
*/
/* ioctl(fd, FIONREAD, &n)的作用是得到緩衝區爲有多少字節要被讀取,並將n賦值爲該數據
* 下面的邏輯是:當獲取緩衝區大小失敗時,將大小賦值爲EVBUFFER_MAX_READ;獲取成功時,判斷其大小,如果過於大了,
*/
#if defined(FIONREAD)
#ifdef WIN32
long lng = n;
if (ioctlsocket(fd, FIONREAD, &lng) == -1 || (n=lng) <= 0) {
#else
if (ioctl(fd, FIONREAD, &n) == -1 || n <= 0) {
#endif
//該條語句如果執行是因爲ioctl函數調用失敗
n = EVBUFFER_MAX_READ;
} else if (n > EVBUFFER_MAX_READ && n > howmuch) {
/*
* It's possible that a lot of data is available for
* reading. We do not want to exhaust resources
* before the reader has a chance to do something
* about it. If the reader does not tell us how much
* data we should read, we artifically limit it.
*/
/* 如果可讀緩衝區的大小比我們自己設置的緩衝區的4倍還大,那麼就將其設置爲我們的緩衝區的4倍大小*/
if ((size_t)n > buf->totallen << 2)
n = buf->totallen << 2;
if (n < EVBUFFER_MAX_READ)
n = EVBUFFER_MAX_READ;
}
#endif
//要讀的字節數大於n或者小於0,將其限制爲n
if (howmuch < 0 || howmuch > n)
howmuch = n;
/* If we don't have FIONREAD, we might waste some space here */
if (evbuffer_expand(buf, howmuch) == -1)
return (-1);
/* We can append new data at this point */
p = buf->buffer + buf->off;
/* 將fd中的howmuch的數據讀入p中
* p是有效緩衝區尾部
*/
#ifndef WIN32
n = read(fd, p, howmuch);
#else
n = recv(fd, p, howmuch, 0);
#endif
if (n == -1)
return (-1);
if (n == 0)
return (0);
//收尾工作
buf->off += n;
/* Tell someone about changes in this buffer */
if (buf->off != oldoff && buf->cb != NULL)
(*buf->cb)(buf, oldoff, buf->off, buf->cbarg);
//返回讀入了多少字節
return (n);
}
需要注意,ioctl
加FIONREAD
是用於得知當前有多少數據可讀,和我們自己構建的緩衝區是無關的,而是系統的緩衝區。以及這個讀操作,是將數據往緩衝區裏面讀,而不是讀出去。
緩衝區的讀出一行操作(將一行數據讀出緩衝區)
接下來是讀取一行的函數evbuffer_readline
:
/*
* Reads a line terminated by either '\r\n', '\n\r' or '\r' or '\n'.
* The returned buffer needs to be freed by the called.
*/
//讀取一行直到遇到\r\n,\n\r,\r,\n這個幾個特殊的回車換行符
char *
evbuffer_readline(struct evbuffer *buffer)
{
u_char *data = EVBUFFER_DATA(buffer); //一個簡單的宏函數,即buffer->buffer
size_t len = EVBUFFER_LENGTH(buffer); //一個簡單的宏函數,即buffer->off
char *line;
unsigned int i;
//獲取到一行的末尾位置
for (i = 0; i < len; i++) {
if (data[i] == '\r' || data[i] == '\n')
break;
}
//掃描完了整個緩衝區,該行還沒結束,直接返回NULL
if (i == len)
return (NULL);
//申請內存
if ((line = malloc(i + 1)) == NULL) {
fprintf(stderr, "%s: out of memory\n", __func__);
return (NULL);
}
//將有效緩衝區的i大小的字節拷貝給line
memcpy(line, data, i);
//注意不要忘了結束符
line[i] = '\0';
/*
* Some protocols terminate a line with '\r\n', so check for
* that, too.
*/
/* 如果判度是否是一行的符號是\r\n或\n\r,需要把後面的那個\n或\r讀取了,以防下次讀取數據的時候造成讀空行
* 除非是兩個連續的\n或\r,證明是下一行是空行
*/
if ( i < len - 1 ) {
char fch = data[i], sch = data[i+1];
/* Drain one more character if needed */
if ( (sch == '\r' || sch == '\n') && sch != fch )
i += 1;
}
/* 清除有效緩衝區中i+1字節大小,這裏加了一個1是因爲要把換行字符讀取了
* 但是注意前面寫數據的時候,並未將換行字符一起寫入
* 換行字符相當於被捨棄掉了
*/
evbuffer_drain(buffer, i + 1);
return (line);
}
evbuffer_readline
和evbuffer_read
這兩個函數雖然都是read,但是evbuffer_readline
是將一行數據從緩衝區讀出,而evbuffer_read
是將數據讀入,這點需要特別注意,不要被名字所迷惑了。而將數據從緩衝區讀出的操作其實是文章開頭我們講到的evbuffer_remove
。
evbuffer_readline
函數沒有什麼特別需要注意的地方,唯一的就是需要將換行字符捨棄掉,如果是回車和換行符一起做爲換行字符,則這兩個都要捨棄。如果是連續兩個相同的換行字符,證明下一個是個空行,無需處理。
除了evbuffer_readline
函數,還有一個evbuffer_readln
函數的功能與其類似,其實evbuffer_readline
在現今的libevent版本中已經不推薦使用了,而是使用evbuffer_readln
,不過兩者的思想很類似。
在這裏,我只將你可能不理解的地方做個解釋。
enum evbuffer_eol_style
定義如下:
enum evbuffer_eol_style {
EVBUFFER_EOL_ANY, //任意數量的回車和換行字符中的任何序列
EVBUFFER_EOL_CRLF, // \r\n或\n
EVBUFFER_EOL_CRLF_STRICT, // \r\n
EVBUFFER_EOL_LF // \n
};
這4個變量分別代表了不同的行結束方式。
其他地方應該沒什麼特殊的地方。
evbuffer_readln
代碼如下:
char *
evbuffer_readln(struct evbuffer *buffer, size_t *n_read_out,
enum evbuffer_eol_style eol_style)
{
u_char *data = EVBUFFER_DATA(buffer);
u_char *start_of_eol, *end_of_eol;
size_t len = EVBUFFER_LENGTH(buffer);
char *line;
unsigned int i, n_to_copy, n_to_drain;
if (n_read_out)
*n_read_out = 0;
/* depending on eol_style, set start_of_eol to the first character
* in the newline, and end_of_eol to one after the last character. */
switch (eol_style) {
case EVBUFFER_EOL_ANY:
for (i = 0; i < len; i++) {
if (data[i] == '\r' || data[i] == '\n')
break;
}
if (i == len)
return (NULL);
start_of_eol = data+i;
++i;
for ( ; i < len; i++) {
if (data[i] != '\r' && data[i] != '\n')
break;
}
end_of_eol = data+i;
break;
case EVBUFFER_EOL_CRLF:
end_of_eol = memchr(data, '\n', len);
if (!end_of_eol)
return (NULL);
if (end_of_eol > data && *(end_of_eol-1) == '\r')
start_of_eol = end_of_eol - 1;
else
start_of_eol = end_of_eol;
end_of_eol++; /*point to one after the LF. */
break;
case EVBUFFER_EOL_CRLF_STRICT: {
u_char *cp = data;
while ((cp = memchr(cp, '\r', len-(cp-data)))) {
if (cp < data+len-1 && *(cp+1) == '\n')
break;
if (++cp >= data+len) {
cp = NULL;
break;
}
}
if (!cp)
return (NULL);
start_of_eol = cp;
end_of_eol = cp+2;
break;
}
case EVBUFFER_EOL_LF:
start_of_eol = memchr(data, '\n', len);
if (!start_of_eol)
return (NULL);
end_of_eol = start_of_eol + 1;
break;
default:
return (NULL);
}
n_to_copy = start_of_eol - data;
n_to_drain = end_of_eol - data;
if ((line = malloc(n_to_copy+1)) == NULL) {
event_warn("%s: out of memory\n", __func__);
return (NULL);
}
memcpy(line, data, n_to_copy);
line[n_to_copy] = '\0';
evbuffer_drain(buffer, n_to_drain);
if (n_read_out)
*n_read_out = (size_t)n_to_copy;
return (line);
}
最後還有一個evbuffer_find
函數,它的作用是在緩衝區中搜索字符串的第一個匹配項,並返回一個指向它的指針,主要使用了memechr
函數來查找第一個匹配項,沒什麼好說的。以及還有一個evbuffer_setcb
,就是改變函數指針指向的函數,以及參數。
小結
在本小節中,我們將libevent緩衝區的剩餘部分進行了分析,主要就是緩衝區數據的讀入、讀出一行、寫出等操作。在下一小節中,我們將介紹bufferevent相關的知識,用來自動管理緩衝區。