Redis總結(jié)

Redis服務(wù)器入口

server.c中的main()

IO多路復(fù)用函數(shù)

ae.c中挡逼,Redis會根據(jù)當(dāng)前系統(tǒng)選擇最佳IO多路復(fù)用函數(shù):

#ifdef HAVE_EPOLL
#include "ae_epoll.c"
#else
    #ifdef HAVE_KQUEUE
    #include "ae_kqueue.c"
    #else
    #include "ae_select.c"
    #endif
#endif

獲取當(dāng)前時(shí)間(微秒)

struct timeval tv;

gettimeofday(&tv, NULL);

獲取當(dāng)前時(shí)間(秒和毫秒)

void aeGetTime(long *seconds, long *milliseconds)
{
    struct timeval tv;

    gettimeofday(&tv, NULL);
    *seconds = tv.tv_sec;
    *milliseconds = tv.tv_usec / 1000;
}

int main()
{
    long now_sec, now_ms;

    aeGetTime(&now_ms, &now_ms);

    return 0;
}

在當(dāng)前時(shí)間上加上一定的時(shí)間

/**
 * aeAddMillisecondsToNow - 在當(dāng)前時(shí)間上加上一定的時(shí)間(毫秒)
 * @milliseconds:加上一定的時(shí)間(毫秒)
 * @sec:在當(dāng)前時(shí)間上加上一定的時(shí)間(毫秒)后的時(shí)間(秒)
 * @ms:在當(dāng)前時(shí)間上加上一定的時(shí)間(毫秒)后的時(shí)間(毫秒)
 * @return:void
 * */
void aeAddMillisecondsToNow(long long milliseconds, long *sec, long *ms)
{
    long cur_sec, cur_ms, when_sec, when_ms;

    aeGetTime(&cur_sec, &cur_ms);
    when_sec = cur_sec + milliseconds / 1000;
    when_ms = cur_ms + milliseconds % 1000;
    if (when_ms >= 1000)
    {
        when_sec ++;
        when_ms -= 1000;
    }
    *sec = when_sec;
    *ms = when_ms;
}

設(shè)置文件描述符為阻塞/非阻塞模式

/**
 * anetSetBlock - 設(shè)置文件描述符為阻塞/非阻塞模式
 * @fd:文件描述符
 * @non_block:0為設(shè)置阻塞模式痢虹,非0為設(shè)置非阻塞模式
 * @return:成功返回ANET_OK,失敗返回ANET_ERR
 * */
int anetSetBlock(int fd, int non_block)
{
    int flags;

    if (-1 == (flags = fcntl(fd, F_GETFL)))
    {
        ERR_EXIT("fcntl(F_GETFL)");
        return ANET_ERR;
    }

    if (non_block)
    {
        flags |= O_NONBLOCK;
    }
    else
    {
        flags &= ~O_NONBLOCK;
    }

    if (-1 == fcntl(fd, F_SETFL, flags))
    {
        ERR_EXIT("fcntl(F_SETFL, O_NONBLOCK)");
        return ANET_ERR;
    }

    return ANET_OK;
}

在循環(huán)的條件中通常會加入循環(huán)退出條件

eventLoop->stop = 0;
while (!eventLoop->stop)
{
    // TODO:添加代碼
}

setsockopt

TCP_NODELAY

static int anetSetTcpNoDelay(char *err, int fd, int val)
{
    if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) == -1)
    {
        perror("setsockopt(TCP_NODELAY)");
        return ANET_ERR;
    }
    return ANET_OK;
}

SO_REUSEADDR

int anetSetReuseAddr(int fd)
{
    int yes = 1;

    if (-1 == setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)))
    {
        perror("setsockopt(SO_REUSEADDR)");
        return ANET_ERR;
    }
    return ANET_OK;
}

SO_REUSEPORT

int anetSetReusePort(int fd)
{
    int yes = 1;

    if (-1 == setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes)))
    {
        perror("setsockopt(SO_REUSEPORT)");
        return ANET_ERR;
    }
    return ANET_OK;
}

SO_SNDTIMEO

int anetSendTimeout(int fd, long long ms)
{
    struct timeval tv;

    tv.tv_sec = ms / 1000;
    tv.tv_usec = (ms % 1000) * 1000;
    if (-1 == setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)))
    {
        perror("setsockopt(SO_SNDTIMEO)");
        return ANET_ERR;
    }
    return ANET_OK;
}

SO_KEEPALIVE

int anetTcpKeepAlive(int fd)
{
    int yes = 1;
    if (-1 == setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)))
    {
        perror("setsockopt(SO_KEEPALIVE)");
        return ANET_ERR;
    }
    return ANET_OK;
}

readn函數(shù)封裝

/**
 * anetRead - 從文件描述符中讀取指定長度數(shù)據(jù)到緩沖區(qū)中
 * @fd:文件描述符
 * @buf:緩沖區(qū)
 * @count:要讀取的數(shù)據(jù)長度
 * @return:-1表示讀取失敗柿顶,==count表示讀取了指定的長度芒珠,<count表示提前遇到了EOF
 * */
int anetRead(int fd, char *buf, int count)
{
    ssize_t nread, totlen = 0;
    while(totlen != count)
    {
        nread = read(fd, buf, count - totlen);
        if (0 == nread)
        {
            return totlen;
        }
        if (-1 == nread)
        {
            return -1;
        }
        totlen += nread;
        buf += nread;
    }
    return totlen;
}

writen函數(shù)的封裝

/**
 * anetWrite - 從緩沖區(qū)中寫指定長度數(shù)據(jù)到文件描述符中
 * @fd:文件描述符
 * @buf:緩沖區(qū)
 * @count:要寫的數(shù)據(jù)長度
 * @return:-1表示寫失敗琅拌,==count表示寫了指定的長度梁呈,<count表示緩沖區(qū)中的內(nèi)容提前被寫完了
 * */
int anetWrite(int fd, char *buf, int count)
{
    ssize_t nwritten, totlen = 0;
    while(totlen != count)
    {
        nwritten = write(fd, buf, count - totlen);
        if (0 == nwritten)
        {
            return totlen;
        }
        if (-1 == nwritten)
        {
            return -1;
        }
        totlen += nwritten;
        buf += nwritten;
    }
    return totlen;
}

gethostbyname()和gethostbyaddr()不可重入的原因

gethostbyname()gethostbyaddr會返回指向靜態(tài)數(shù)據(jù)的指針婚度,而指針?biāo)赶虻撵o態(tài)數(shù)據(jù)可能會被下一次的調(diào)用覆寫。
The functions gethostbyname() and gethostbyaddr() may return pointers to static data, which may be overwritten by later calls.

使用getaddrinfo代替gethostbyname

/**
 * anetGenericResolve - 解析域名為IP地址
 * @host:域名
 * @ipbuf:緩沖區(qū)官卡,用于存放解析域名后得到的點(diǎn)分十進(jìn)制IP地址
 * @ipbuf_len:緩沖區(qū)大小
 * */
int anetGenericResolve(char *host, char *ipbuf, size_t ipbuf_len)
{
    struct addrinfo hints, *info;
    int rv;

    memset(&hints,0,sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;  /* specify socktype to avoid dups */

    if (0 != (rv = getaddrinfo(host, NULL, &hints, &info)))
    {
        fprintf(stderr, "%s", gai_strerror(rv));
        return ANET_ERR;
    }
    if (info->ai_family == AF_INET)
    {
        struct sockaddr_in *sa = (struct sockaddr_in *)info->ai_addr;
        inet_ntop(AF_INET, &(sa->sin_addr), ipbuf, ipbuf_len);
    }
    else
    {
        struct sockaddr_in6 *sa = (struct sockaddr_in6 *)info->ai_addr;
        inet_ntop(AF_INET6, &(sa->sin6_addr), ipbuf, ipbuf_len);
    }

    freeaddrinfo(info);
    return ANET_OK;
}

TCP客戶端

/**
 * anetTcpGenericConnect - 創(chuàng)建一個(gè)TCP客戶端蝗茁,并連接到TCP服務(wù)器
 * @addr:要連接的IP地址,既可以是點(diǎn)分十進(jìn)制IP地址寻咒,也可以是域名
 * @port:要連接的端口號
 * @source_addr:要綁定的IP地址哮翘,如果為NULL,則在connect時(shí)由內(nèi)核自動(dòng)綁定一個(gè)可用地址
 * @return:成功返回連接成功的套接字(非阻塞)毛秘,失敗返回-1
 * */
int anetTcpGenericConnect(char *addr, int port, char *source_addr)
{
    int s = ANET_ERR, rv;
    char portstr[6];  /* strlen("65535") + 1; */
    struct addrinfo hints, *servinfo, *bservinfo, *p, *b;

    snprintf(portstr,sizeof(portstr),"%d",port);
    memset(&hints,0,sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if (0 != (rv = getaddrinfo(addr, portstr, &hints, &servinfo)))
    {
        fprintf(stderr, "%s", gai_strerror(rv));
        return ANET_ERR;
    }
    for (p = servinfo; p != NULL; p = p->ai_next)
    {
        /* Try to create the socket and to connect it.
         * If we fail in the socket() call, or on connect(), we retry with
         * the next entry in servinfo. */
        if (-1 == (s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)))
        {
            continue;
        }
        if (anetSetReuseAddr(s) == ANET_ERR)
        {
            goto error;
        }
        if (ANET_OK != anetSetBlock(s, 1))
        {
            goto error;
        }

        if (source_addr)
        {
            int bound = 0;
            /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */
            if (0 != (rv = getaddrinfo(source_addr, NULL, &hints, &bservinfo)))
            {
                fprintf(stderr, "%s", gai_strerror(rv));
                goto error;
            }
            for (b = bservinfo; b != NULL; b = b->ai_next)
            {
                if (-1 == bind(s,b->ai_addr,b->ai_addrlen))
                {
                    bound = 1;
                    break;
                }
            }
            freeaddrinfo(bservinfo);
            if (!bound)
            {
                fprintf(stderr, "bind: %s\n", strerror(errno));
                goto error;
            }
        }
        if (-1 == connect(s,p->ai_addr,p->ai_addrlen))
        {
            /* If the socket is non-blocking, it is ok for connect() to
             * return an EINPROGRESS error here. */
            if (errno == EINPROGRESS)
            {
                goto end;
            }

            close(s);
            s = ANET_ERR;
            continue;
        }

        /* If we ended an iteration of the for loop without errors, we
         * have a connected socket. Let's return to the caller. */
        goto end;
    }
    if (p == NULL)
    {
        fprintf(stderr, "creating socket: %s\n", strerror(errno));
    }

    error:
    if (s != ANET_ERR)
    {
        close(s);
        s = ANET_ERR;
    }

    end:
    freeaddrinfo(servinfo);

    /* Handle best effort binding: if a binding address was used, but it is
     * not possible to create a socket, try again without a binding address. */
    if (s == ANET_ERR && source_addr)
    {
        return anetTcpGenericConnect(addr, port, NULL);
    }
    else
    {
        return s;
    }
}

TCP服務(wù)器

int anetListen(int s, struct sockaddr *sa, socklen_t len, int backlog)
{
    if (-1 == bind(s, sa, len))
    {
        fprintf(stderr, "bind: %s", strerror(errno));
        close(s);
        return ANET_ERR;
    }

    if (-1 == listen(s, backlog))
    {
        fprintf(stderr, "listen: %s", strerror(errno));
        close(s);
        return ANET_ERR;
    }
    return ANET_OK;
}

/**
 * _anetTcpServer - 創(chuàng)建一個(gè)正在監(jiān)聽的TCP服務(wù)器
 * @port:TCP服務(wù)器要綁定的端口號
 * @bindaddr:TCP服務(wù)器要綁定的IP地址饭寺,既可以是點(diǎn)分十進(jìn)制格式阻课,也可以是域名
 * @af:AF_INET/AF_INET6
 * @backlog:listen函數(shù)的第二個(gè)參數(shù)
 * @return:失敗返回-1,成功返回監(jiān)聽套接字
 * */
int _anetTcpServer(int port, char *bindaddr, int af, int backlog)
{
    int s = -1, rv;
    char _port[6];  /* strlen("65535") */
    struct addrinfo hints, *servinfo, *p;

    snprintf(_port, 6, "%d", port);
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = af;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;    /* No effect if bindaddr != NULL */

    if (0 != (rv = getaddrinfo(bindaddr, _port, &hints, &servinfo)))
    {
        fprintf(stderr, "%s", gai_strerror(rv));
        return ANET_ERR;
    }
    for (p = servinfo; p != NULL; p = p->ai_next)
    {
        if (-1 == (s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)))
        {
            continue;
        }

        if (ANET_ERR == anetSetReuseAddr(s))
        {
            goto error;
        }
        if (ANET_ERR == anetListen(s, p->ai_addr, p->ai_addrlen, backlog))
        {
            s = ANET_ERR;
        }
        goto end;
    }
    if (NULL == p)
    {
        fprintf(stderr, "unable to bind socket, errno: %d", errno);
        goto error;
    }

    error:
    if (-1 != s)
    {
        close(s);
    }
    s = ANET_ERR;
    end:
    freeaddrinfo(servinfo);
    return s;
}

使用inet_ntop()/inet_pton()代替inet_ntoa()/inet_aton()

inet_ntoa()inet_aton()只能用于IPv4艰匙,而inet_ntop()inet_pton()既可以用于IPv4限煞,又可以用于IPv6。

sockaddr_in addr;
bzero(&addr, sizeof(addr));
addr.sin_addr.s_addr = htonl(INADDR_ANY);

char ipv4[16] = {0};
inet_ntop(AF_INET, &addr.sin_addr, ipv4, sizeof(ipv4));
printf("ipv4 = %s\n", ipv4); // 0.0.0.0

snprintf(ipv4, sizeof(ipv4), "127.0.0.0");
inet_pton(AF_INET, ipv4, &addr.sin_addr);
printf("addr.sin_addr.s_addr = %d\n", addr.sin_addr.s_addr); // 127

點(diǎn)分十進(jìn)制IP地址的本質(zhì)

點(diǎn)分十進(jìn)制IP地址"127.1.2.3"中的"127"對應(yīng)于4字節(jié)整型IP地址的第1個(gè)字節(jié)员凝,"1"對應(yīng)于第2個(gè)字節(jié)署驻,"2"對應(yīng)于第3個(gè)字節(jié),"3"對應(yīng)于第4個(gè)字節(jié)健霹。即旺上,字符串?dāng)?shù)組的低字節(jié)對應(yīng)于整數(shù)的低字節(jié)。

在封裝一個(gè)函數(shù)時(shí)糖埋,通常實(shí)現(xiàn)兩個(gè)抚官,一個(gè)負(fù)責(zé)提供調(diào)用接口,一個(gè)負(fù)責(zé)具體實(shí)現(xiàn)(函數(shù)名以_開頭)

int anetTcpServer(char *err, int port, char *bindaddr, int backlog)
{
    return _anetTcpServer(err, port, bindaddr, AF_INET, backlog);
}

int anetTcp6Server(char *err, int port, char *bindaddr, int backlog)
{
    return _anetTcpServer(err, port, bindaddr, AF_INET6, backlog);
}

strcasecmp與strncasecmp

即忽略大小寫版的strcmp()strncmp()阶捆。

strdup

strdup()函數(shù)返回一個(gè)指針凌节,該指針指向復(fù)制字符串的副本,存放復(fù)制字符串副本的內(nèi)存是由malloc()分配的洒试,使用完畢后需要使用free()釋放倍奢。

以守護(hù)進(jìn)程方式運(yùn)行

void daemonize(void)
{
    int fd;

    if (fork() != 0)
    {
        exit(0); /* parent exits */
    }
    setsid(); /* create a new session */

    /* Every output goes to /dev/null. If Redis is daemonized but
     * the 'logfile' is set to 'stdout' in the configuration file
     * it will not log at all. */
    if (-1 != (fd = open("/dev/null", O_RDWR, 0)))
    {
        dup2(fd, STDIN_FILENO);
        dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);
        if (fd > STDERR_FILENO)
        {
            close(fd);
        }
    }
}

實(shí)現(xiàn)查看應(yīng)用程序的版本號或者幫助

// 注意:執(zhí)行完version()函數(shù)后,應(yīng)該退出進(jìn)程垒棋,因?yàn)榭蛻糁皇遣榭磻?yīng)用程序版本卒煞,而不是要執(zhí)行應(yīng)用程序
void version()
{
    printf("Redis server version %s (%s:%d)\n", REDIS_VERSION,
           redisGitSHA1(), atoi(redisGitDirty()) > 0);
    exit(0);
}

// 注意:在usage()函數(shù)中,打印信息應(yīng)該輸出到標(biāo)準(zhǔn)錯(cuò)誤中叼架,且該函數(shù)執(zhí)行完畢后畔裕,應(yīng)該退出進(jìn)程,且返回1乖订,表示發(fā)生了某種錯(cuò)誤
void usage()
{
    fprintf(stderr,"Usage: ./redis-server [/path/to/redis.conf]\n");
    fprintf(stderr,"       ./redis-server - (read config from stdin)\n");
    exit(1);
}

int main(int argc, char **argv) {
    // 如果是2個(gè)參數(shù)扮饶,且第2個(gè)參數(shù)為-v、--version或者--help乍构,則打印對應(yīng)的信息
    if (2 == argc)
    {
        if (strcmp(argv[1], "-v") == 0 ||
            strcmp(argv[1], "--version") == 0)
        {
            version();
        }
        if (strcmp(argv[1], "--help") == 0)
        {
            usage();
        }
    }
    // 如果大于2個(gè)參數(shù)甜无,則打印使用方法
    else if ((argc > 2))
    {
        usage();
    }
    // 如果只有1個(gè)參數(shù),則打印警告信息
    else
    {
        redisLog(REDIS_WARNING,"Warning: no config file specified, using the default config. In order to specify a config file use 'redis-server /path/to/redis.conf'");
    }
}

測試函數(shù)

int __failed_tests = 0;
int __test_num = 0;
#define test_cond(descr,_c) do { \
    __test_num++; printf("%d - %s: ", __test_num, descr); \
    if(_c) printf("PASSED\n"); else {printf("FAILED\n"); __failed_tests++;} \
} while(0)

#define test_report() do { \
    printf("%d tests, %d passed, %d failed\n", __test_num, \
                    __test_num-__failed_tests, __failed_tests); \
    if (__failed_tests) { \
        printf("=== WARNING === We have failed tests here...\n"); \
    } \
} while(0)

test_cond("Create a string and obtain the length",
            sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0);
test_report();
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末哥遮,一起剝皮案震驚了整個(gè)濱河市岂丘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌眠饮,老刑警劉巖奥帘,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異仪召,居然都是意外死亡寨蹋,警方通過查閱死者的電腦和手機(jī)牲距,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钥庇,“玉大人牍鞠,你說我怎么就攤上這事∑酪蹋” “怎么了难述?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吐句。 經(jīng)常有香客問我胁后,道長,這世上最難降的妖魔是什么嗦枢? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任攀芯,我火速辦了婚禮,結(jié)果婚禮上文虏,老公的妹妹穿的比我還像新娘侣诺。我一直安慰自己,他們只是感情好氧秘,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布年鸳。 她就那樣靜靜地躺著,像睡著了一般丸相。 火紅的嫁衣襯著肌膚如雪搔确。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天灭忠,我揣著相機(jī)與錄音膳算,去河邊找鬼。 笑死弛作,一個(gè)胖子當(dāng)著我的面吹牛涕蜂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缆蝉,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼宇葱,長吁一口氣:“原來是場噩夢啊……” “哼瘦真!你這毒婦竟也來了刊头?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤诸尽,失蹤者是張志新(化名)和其女友劉穎原杂,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體您机,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡穿肄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年年局,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咸产。...
    茶點(diǎn)故事閱讀 38,646評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡矢否,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出脑溢,到底是詐尸還是另有隱情僵朗,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布屑彻,位于F島的核電站验庙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏社牲。R本人自食惡果不足惜粪薛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望搏恤。 院中可真熱鬧违寿,春花似錦、人聲如沸熟空。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽痛阻。三九已至菌瘪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間阱当,已是汗流浹背俏扩。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弊添,地道東北人录淡。 一個(gè)月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像油坝,于是被迫代替她去往敵國和親嫉戚。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評論 2 348