Redis源碼學(xué)習(xí):字符串
1.初識(shí)SDS
1.1 SDS定義
Redis定義了一個(gè)叫做sdshdr(SDS or simple dynamic string)的數(shù)據(jù)結(jié)構(gòu)。SDS不僅用于 保存字符串,還用來(lái)當(dāng)做緩沖區(qū),例如AOF緩沖區(qū)或輸入緩沖區(qū)等。如下所示,整數(shù)len和free分別表示buf數(shù)組中已使用的長(zhǎng)度和剩余可用的長(zhǎng)度,buf是一個(gè)原生C字符串,以\0結(jié)尾。
sds就是sdshdr中char buf[]的別名,后面能看到,各種操作函數(shù)的入?yún)⒑头祷刂刀际莝ds而非sdshdr。那sdshdr中的len和free如何訪問(wèn)到?不然不就白定義了嗎?答案就是靈活的指針,各種SDS的函數(shù)都是:struct sdshdr *sh = (void *)(s - (sizeof(struct sdshdr))),通過(guò)指針偏移計(jì)算出sdshdr的首地址。
關(guān)于字符串末尾的“\0”始終要注意:len和free中都沒把“\0”算進(jìn)去,例如為字符串”hello”創(chuàng)建sds,最終buf總長(zhǎng)度是6,len為5,free為0。
/* 類型別名,用于指向 sdshdr 的 buf 屬性 */
typedef char *sds;
/* 保存字符串對(duì)象的結(jié)構(gòu) */
struct sdshdr {
// buf 中已占用空間的長(zhǎng)度
int len;
// buf 中剩余可用空間的長(zhǎng)度
int free;
// 數(shù)據(jù)空間
char buf[];
};
1.2 SDS操作
有了SDS就可以統(tǒng)一接口,所有字符串函數(shù)的操作對(duì)象都是SDS而非原生的C字符串。下面分析一些典型的字符串操作,看看SDS是如何實(shí)現(xiàn)的。代碼注釋來(lái)自于huangz所著Redis 3.0詳細(xì)注釋版。
1.2.1 新建SDS
Redis內(nèi)部使用sdsnew()函數(shù),并在其基礎(chǔ)上提供了sdsnew()和sdsempty()兩個(gè)函數(shù)。在sdsnew()中,若傳入字符串init若為NULL,則創(chuàng)建一個(gè)buf只有1個(gè)字節(jié)的空SDS,否則buf長(zhǎng)度是字符串長(zhǎng)度加1。len置為字符串長(zhǎng)度initlen,free置為0。注意最終返回buf部分,而不是整個(gè)sdshdr,這在前面介紹過(guò)的sdshdr.h中有定義:typedef char *sds。
/* 根據(jù)給定字符串 init ,創(chuàng)建一個(gè)包含同樣字符串的 sds */
sds sdsnew(const char *init) {
size_t initlen = (init == NULL) ? 0 : strlen(init);
return sdsnewlen(init, initlen);
}
/* 創(chuàng)建并返回一個(gè)只保存了空字符串 "" 的 sds */
sds sdsempty(void) {
return sdsnewlen("", 0);
}
/* 根據(jù)給定的初始化字符串 init 和字符串長(zhǎng)度 initlen 創(chuàng)建一個(gè)新的 sds */
sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;
// 根據(jù)是否有初始化內(nèi)容,選擇適當(dāng)?shù)膬?nèi)存分配方式
// T = O(N)
if (init) {
// zmalloc 不初始化所分配的內(nèi)存
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
} else {
// zcalloc 將分配的內(nèi)存全部初始化為 0
sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
}
// 內(nèi)存分配失敗,返回
if (sh == NULL) return NULL;
// 設(shè)置初始化長(zhǎng)度
sh->len = initlen;
// 新 sds 不預(yù)留任何空間
sh->free = 0;
// 如果有指定初始化內(nèi)容,將它們復(fù)制到 sdshdr 的 buf 中
// 以 \0 結(jié)尾,T = O(N)
if (initlen && init)
memcpy(sh->buf, init, initlen);
sh->buf[initlen] = '\0';
// 返回 buf 部分,而不是整個(gè) sdshdr
return (char*)sh->buf;
}
- 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
1.2.2 擴(kuò)展SDS空間
Redis需要的是一個(gè)可以修改的字符串值,所以SDS是可變的。當(dāng)對(duì)SDS中的字符串修改時(shí),長(zhǎng)度難免會(huì)長(zhǎng)了或者短了,這時(shí)就要根據(jù)需要對(duì)SDS進(jìn)行擴(kuò)展。Redis的算法比較簡(jiǎn)單,甚至與Java中ArrayList有些相像。空間不足時(shí)重新分配一塊大小翻倍的內(nèi)存,縮小時(shí)則維持不變。有一點(diǎn)區(qū)別是:在Redis中,如果要擴(kuò)展的長(zhǎng)度超過(guò)1MB,那么只會(huì)額外分配1MB而不會(huì)翻倍。比如修改一個(gè)字符串到10MB了,那最終SDS占用空間只是 10MB + 1MB + 1B(“\0”)。
/* 最大預(yù)分配長(zhǎng)度 */
#define SDS_MAX_PREALLOC (1024*1024)
/* 對(duì) sds 中 buf 的長(zhǎng)度進(jìn)行擴(kuò)展,確保在函數(shù)執(zhí)行之后,
buf 至少會(huì)有 addlen + 1 長(zhǎng)度的空余空間(額外的 1 字節(jié)是為 \0 準(zhǔn)備的)*/
sds sdsMakeRoomFor(sds s, size_t addlen) {
struct sdshdr *sh, *newsh;
// 獲取 s 目前的空余空間長(zhǎng)度
size_t free = sdsavail(s);
size_t len, newlen;
// s 目前的空余空間已經(jīng)足夠,無(wú)須再進(jìn)行擴(kuò)展,直接返回
if (free >= addlen) return s;
// 獲取 s 目前已占用空間的長(zhǎng)度
len = sdslen(s);
sh = (void*) (s-(sizeof(struct sdshdr)));
// s 最少需要的長(zhǎng)度
newlen = (len + addlen);
// 根據(jù)新長(zhǎng)度,為 s 分配新空間所需的大小
if (newlen < SDS_MAX_PREALLOC)
// 如果新長(zhǎng)度小于 SDS_MAX_PREALLOC,那么為它分配兩倍于所需長(zhǎng)度的空間
newlen *= 2;
else
// 否則,分配長(zhǎng)度為目前長(zhǎng)度加上 SDS_MAX_PREALLOC
newlen += SDS_MAX_PREALLOC;
// T = O(N)
newsh = zrealloc(sh, sizeof(struct sdshdr) + newlen + 1);
// 內(nèi)存不足,分配失敗,返回
if (newsh == NULL) return NULL;
// 更新 sds 的空余長(zhǎng)度
newsh->free = newlen - len;
// 返回 sds
return newsh->buf;
}
/* 返回 sds 實(shí)際保存的字符串的長(zhǎng)度,T = O(1) */
static inline size_t sdslen(const sds s) {
struct sdshdr *sh = (void*)(s - (sizeof(struct sdshdr)));
return sh->len;
}
/* 返回 sds 可用空間的長(zhǎng)度,T = O(1) */
static inline size_t sdsavail(const sds s) {
struct sdshdr *sh = (void*)(s - (sizeof(struct sdshdr)));
return sh->free;
}
- 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
1.2.3 收縮SDS空間
類似ArrayList的shrink方法,SDS也能收縮,重新分配一塊僅僅能容納字符串的空間。
/* 回收 sds 中的空閑空間,回收不會(huì)對(duì) sds 中保存的字符串內(nèi)容做任何修改 */
sds sdsRemoveFreeSpace(sds s) {
struct sdshdr *sh;
sh = (void*) (s-(sizeof(struct sdshdr)));
// 進(jìn)行內(nèi)存重分配,讓 buf 的長(zhǎng)度僅僅足夠保存字符串內(nèi)容,T = O(N)
sh = zrealloc(sh, sizeof(struct sdshdr)+sh->len+1);
// 空余空間為 0
sh->free = 0;
return sh->buf;
}
1.2.4 字符串操作
SDS也有類似原生C字符串的拼接、拷貝等函數(shù)。這里就不一一列舉了。
2.SDS設(shè)計(jì)總結(jié)
2.1 O(1)取字符串長(zhǎng)度
C標(biāo)準(zhǔn)庫(kù)的strlen函數(shù)的時(shí)間復(fù)雜度是O(n),因?yàn)樗闅v到字符串末尾的“\0”才會(huì)停止計(jì)數(shù),將當(dāng)前計(jì)數(shù)器的值作為字符串的長(zhǎng)度返回。這在要求高性能的Redis中是無(wú)法容忍的。手頭正好有g(shù)libc-2.15的源碼,打開string文件夾下的strlen.c,發(fā)現(xiàn)前面的說(shuō)法并不準(zhǔn)確,標(biāo)準(zhǔn)庫(kù)還是盡可能做了優(yōu)化,總的思想就是利用數(shù)據(jù)對(duì)齊做快速的“字”查找:
- C標(biāo)準(zhǔn)庫(kù)要求有很好的移植性,在絕大部分系統(tǒng)體系結(jié)構(gòu)下都應(yīng)該能正確運(yùn)行。那么每次拿出4個(gè)字節(jié)比較(unsigned long int),就需要考慮內(nèi)存對(duì)齊問(wèn)題,傳入的字符串的首字符地址可不一定在4對(duì)齊的地址上。如果在內(nèi)存對(duì)齊之前就遇到了’\0’ 則直接return推出。否則到下一步;
- 一次讀入并判斷一個(gè)4字節(jié)(sizeof(unsigned long int))大小的內(nèi)存塊,如果4字節(jié)中沒有為0的字節(jié),則繼續(xù)下一個(gè)4字節(jié),否則到下一步;
- 這里4字節(jié)中至少有一個(gè)字節(jié)為0,最后要做的就是找出這個(gè)字節(jié)的位置并return退出。
/* Return the length of the null-terminated string STR. Scan for
the null terminator quickly by testing four bytes at a time. */
size_t
strlen (str)
const char *str;
{
const char *char_ptr;
const unsigned long int *longword_ptr;
unsigned long int longword, himagic, lomagic;
/* Handle the first few characters by reading one character at a time.
Do this until CHAR_PTR is aligned on a longword boundary. */
for (char_ptr = str;
((unsigned long int) char_ptr & (sizeof (longword) - 1)) != 0;
++char_ptr)
if (*char_ptr == '\0')
return char_ptr - str;
/* All these elucidatory comments refer to 4-byte longwords,
but the theory applies equally well to 8-byte longwords. */
longword_ptr = (unsigned long int *) char_ptr;
/* Bits 31, 24, 16, and 8 of this number are zero. Call these bits
the "holes." Note that there is a hole just to the left of
each byte, with an extra at the end:
bits: 01111110 11111110 11111110 11111111
bytes: AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD
The 1-bits make sure that carries propagate to the next 0-bit.
The 0-bits provide holes for carries to fall into. */
himagic = 0x80808080L;
lomagic = 0x01010101L;
if (sizeof (longword) > 4)
{
/* 64-bit version of the magic. */
/* Do the shift in two steps to avoid a warning if long has 32 bits. */
himagic = ((himagic << 16) << 16) | himagic;
lomagic = ((lomagic << 16) << 16) | lomagic;
}
if (sizeof (longword) > 8)
abort ();
/* Instead of the traditional loop which tests each character,
we will test a longword at a time. The tricky part is testing
if *any of the four* bytes in the longword in question are zero. */
for (;;)
{
longword = *longword_ptr++;
if (((longword - lomagic) & ~longword & himagic) != 0)
{
/* Which of the bytes was the zero? If none of them were, it was
a misfire; continue the search. */
const char *cp = (const char *) (longword_ptr - 1);
if (cp[0] == 0)
return cp - str;
if (cp[1] == 0)
return cp - str + 1;
if (cp[2] == 0)
return cp - str + 2;
if (cp[3] == 0)
return cp - str + 3;
if (sizeof (longword) > 4)
{
if (cp[4] == 0)
return cp - str + 4;
if (cp[5] == 0)
return cp - str + 5;
if (cp[6] == 0)
return cp - str + 6;
if (cp[7] == 0)
return cp - str + 7;
}
}
}
}
- 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
2.2 重用C標(biāo)準(zhǔn)庫(kù)函數(shù)
SDS中的buf并沒有“標(biāo)新立異”,而是老老實(shí)實(shí)地按照原生C字符串的要求保存,末尾放置“\0”。這樣做最大的好處就是標(biāo)準(zhǔn)庫(kù)中許許多多好用的字符串操作函數(shù)又都可以重用了,避免這繁瑣的工作(就算Redis作者有代碼“潔癖”,也不會(huì)到標(biāo)準(zhǔn)庫(kù)都不用的程度吧…)。
2.3 避免緩沖區(qū)溢出
緩沖區(qū)溢出問(wèn)題可是個(gè)安全界的老問(wèn)題了。因?yàn)镃字符串不記錄自身長(zhǎng)度,所以strcat()一旦拷貝過(guò)多的字節(jié)就會(huì)導(dǎo)致原始字符串空間被覆蓋??紤]下面的例子,字符串s1為”Redis”,字符串s2為”MongoDB”,拼接”hello”到s1末尾時(shí)會(huì)發(fā)生什么?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char s1[] = "Redis";
char s2[] = "MongoDB";
int main(int argc, char const *argv[])
{
int s2len, i;
// Save original length of s2
s2len = strlen(s2);
// Cancat to s1
strcat(s1, "hello");
// Print s1 and s2
printf("s1 = [%s]\n", s1);
printf("s2 = [");
for (i = 0; i < s2len; ++i)
printf("%c", s2[i]);
printf("]\n");
return 0;
}
- 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
因?yàn)閟1和s2編譯鏈接后是相鄰的(保存在.data段),所以拼接”hello”到s1末尾時(shí)會(huì)導(dǎo)致s1溢出,覆蓋了s2的部分內(nèi)容。最終打印出的s1和s2就是下面這個(gè)樣子:
[root@vm Temp]# gcc -o bufoverflow bufoverflow.c
[root@vm Temp]# ./bufoverflow
s1 = [Redishello]
s2 = [elloDB]
這里順帶復(fù)習(xí)一個(gè)關(guān)于字符串常量和數(shù)組的基礎(chǔ)知識(shí)。當(dāng)我們將s1從char s1[]改為char *s1再編譯運(yùn)行時(shí),就會(huì)報(bào)錯(cuò)異常終止,這又是為什么?對(duì)比bufoverflow-old(s1是數(shù)組)和bufoverflow(s1是指針),通過(guò)objdump查看.data段,發(fā)現(xiàn)char *的s1并沒有保存在.data里,而是放在了.rodata中,因?yàn)閏har *是字符串常量不允許修改。所以 修改只讀段.rodata導(dǎo)致了段錯(cuò)誤。但是當(dāng)我們對(duì)可讀寫區(qū)域,如各種buf數(shù)據(jù),“搞破壞”時(shí)錯(cuò)誤就悄悄發(fā)生了:
[root@vm Temp]# ./buffoverflow
Segmentation fault (core dumped)
[root@vm Temp]# objdump -j .data -s bufoverflow-old
Contents of section .data:
600a40 00000000 52656469 73004d6f 6e676f44 ....Redis.MongoD
600a50 42000000 B...
[root@vm Temp]# objdump -j .data -s bufoverflow
Contents of section .data:
600a58 00000000 00000000 88074000 00000000 ..........@.....
600a68 4d6f6e67 6f444200 MongoDB.
[root@BC-VM-edce4ac67d304079868c0bb265337bd4 Temp]# objdump -s bufoverflow
...
Contents of section .rodata:
400778 01000200 00000000 00000000 00000000 ................
400788 52656469 73006865 6c6c6f00 7331203d Redis.hello.s1 =
400798 205b2573 5d0a0073 32203d20 5b005d00 [%s]..s2 = [.].
...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
2.4 減少內(nèi)存申請(qǐng)和釋放次數(shù)
前面在[1.2.2 擴(kuò)展SDS空間](###1.2.2 擴(kuò)展SDS空間)中分析過(guò),新建的SDS會(huì)分配能恰好容納字符串的空間。但后續(xù)對(duì)字符串修改導(dǎo)致SDS擴(kuò)容時(shí),Redis會(huì)為其留出一部分的預(yù)留空間。這樣就避免了頻繁調(diào)用zmalloc()和zfree()。
2.5 保證二進(jìn)制安全
C字符串因?yàn)橐阅┪驳摹癨0”空字符作為標(biāo)記,所以C字符串只能保存文本數(shù)據(jù)。若用來(lái)保存圖片、音視頻、壓縮文件等二進(jìn)制數(shù)據(jù)的話,若其中含有哪怕一個(gè)空字符,就會(huì)導(dǎo)致錯(cuò)誤。盡管Redis重用C標(biāo)準(zhǔn)庫(kù)中字符串的基本操作函數(shù),但為了不限制自身的應(yīng)用場(chǎng)景,Redis保證SDS中的字符串是二進(jìn)制安全的,即可以保存各種二進(jìn)制數(shù)據(jù)的。
SDS中已經(jīng)用單獨(dú)的變量len保存了字符串的實(shí)際長(zhǎng)度,所以解決方法就很簡(jiǎn)單了:所有SDS的API都使用len屬性的值而不是“\0”來(lái)判斷字符串是否結(jié)束。也就是說(shuō)不管SDS的buf中保存的是不是普通文本數(shù)據(jù),Redis都會(huì)假設(shè)它是二進(jìn)制數(shù)據(jù),以最安全的方式處理它們。同時(shí),當(dāng)SDS保存的確實(shí)是文本數(shù)據(jù)時(shí),Redis還能享受到C標(biāo)準(zhǔn)庫(kù)帶來(lái)的便捷。
3.SDS應(yīng)用
3.1 AOF緩沖區(qū)
redisServer結(jié)構(gòu)中保存了當(dāng)前Redis實(shí)例的所有全局狀態(tài),其中一項(xiàng)aof_buf就是用作AOF緩沖區(qū)的SDS。
struct redisServer {
/* General */
...
// 數(shù)據(jù)庫(kù)
redisDb *db;
// 事件狀態(tài)
aeEventLoop *el;
...
/* Networking */
...
/* Fields used only for stats */
...
/* slowlog */
...
/* Configuration */
...
/* AOF persistence */
...
// AOF 緩沖區(qū)
sds aof_buf; /* AOF buffer, written before entering the event loop */
...
/* RDB persistence */
...
/* Logging */
...
/* Replication (master) */
...
/* Replication (slave) */
...
/* Replication script cache. */
...
/* Pubsub */
...
/* Cluster */
...
/* Scripting */
...
/* Assert & bug reporting */
...
};
- 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
3.2 客戶端輸入緩沖區(qū)
在表示客戶端連接的redisClient結(jié)構(gòu)中,有一個(gè)叫做queryBuf的SDS屬性,保存的就是來(lái)自遠(yuǎn)程客戶端的命令請(qǐng)求。因?yàn)橐粋€(gè)redisClient反復(fù)使用這一個(gè)緩沖區(qū),所以SDS會(huì)根據(jù)每次請(qǐng)求的內(nèi)容動(dòng)態(tài)地縮小或擴(kuò)大。
/* 因?yàn)?I/O 復(fù)用的緣故,需要為每個(gè)客戶端維持一個(gè)狀態(tài)。
多個(gè)客戶端狀態(tài)被服務(wù)器用鏈表連接起來(lái)。*/
typedef struct redisClient {
// 套接字描述符
int fd;
// 當(dāng)前正在使用的數(shù)據(jù)庫(kù)
redisDb *db;
// 當(dāng)前正在使用的數(shù)據(jù)庫(kù)的 id (號(hào)碼)
int dictid;
// 客戶端的名字
robj *name; /* As set by CLIENT SETNAME */
// 查詢緩沖區(qū)
sds querybuf;
...
} redisClient;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
下面簡(jiǎn)單分析一下Redis是如何使用查詢緩沖區(qū)SDS的。首先,main()函數(shù)中調(diào)用initServer()初始化epoll創(chuàng)建連接的事件函數(shù)(都在redis.c中)acceptTcpHandler()負(fù)責(zé)接收TCP請(qǐng)求,它調(diào)用acceptCommonHandler()->createClient()創(chuàng)建出redisClient(在networking.c中)。因?yàn)椴捎玫氖钱惒降膃poll機(jī)制,所以此時(shí),queryBuf屬性只是初始化為sdsempty(),同時(shí)綁定讀事件處理函數(shù)readQueryFromClient(),這才是真正解析請(qǐng)求的地方!
/* 讀取客戶端的查詢緩沖區(qū)內(nèi)容 */
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
redisClient *c = (redisClient*) privdata;
int nread, readlen;
size_t qblen;
REDIS_NOTUSED(el);
REDIS_NOTUSED(mask);
// 設(shè)置服務(wù)器的當(dāng)前客戶端
server.current_client = c;
// 讀入長(zhǎng)度(默認(rèn)為 16 MB)
readlen = REDIS_IOBUF_LEN;
/* If this is a multi bulk request, and we are processing a bulk reply
* that is large enough, try to maximize the probability that the query
* buffer contains exactly the SDS string representing the object, even
* at the risk of requiring more read(2) calls. This way the function
* processMultiBulkBuffer() can avoid copying buffers to create the
* Redis Object representing the argument. */
if (c->reqtype == REDIS_REQ_MULTIBULK && c->multibulklen && c->bulklen != -1
&& c->bulklen >= REDIS_MBULK_BIG_ARG)
{
int remaining = (unsigned)(c->bulklen+2)-sdslen(c->querybuf);
if (remaining < readlen) readlen = remaining;
}
// 獲取查詢緩沖區(qū)當(dāng)前內(nèi)容的長(zhǎng)度
// 如果讀取出現(xiàn) short read ,那么可能會(huì)有內(nèi)容滯留在讀取緩沖區(qū)里面
// 這些滯留內(nèi)容也許不能完整構(gòu)成一個(gè)符合協(xié)議的命令,
qblen = sdslen(c->querybuf);
// 如果有需要,更新緩沖區(qū)內(nèi)容長(zhǎng)度的峰值(peak)
if (c->querybuf_peak < qblen) c->querybuf_peak = qblen;
// 為查詢緩沖區(qū)分配空間
c->querybuf = sdsMakeRoomFor(c->querybuf, readlen);
// 讀入內(nèi)容到查詢緩存
nread = read(fd, c->querybuf+qblen, readlen);
...
}
- 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
|