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();