C++ 制作 echo 服务器:为测试 Redis RESP 协议做准备
前言
在我们对 NuRaft 这款优秀的分布式框架进行介绍之前,使用 Echo 服务器对 Redis RESP 建立了解,将会是一个非常好的事情,在本篇文章之中,我们将会通过一个 echo 服务器,管中窥豹地进行研究。
意义何在?在 NuRaft 的示范程序之中(参考下图):
(echo 是 NuRaft 附带的一个示例小程序,用于展现如何使用 NuRaft 搭建分布式集群,如何设计节点间日志的数据格式,如何为程序设计配套指令,并对外交互,且与其它的节点展开信息同步)
EBay 的团队为 echo
设计了 msg
(向集群内存储字符串信息),add
(向集群内增加节点),st
(输出当前节点的日志信息以及自身 ID 与主节点 ID),ls
(罗列集群内的节点信息), help
(打印帮助信息)。
而在实验中,用户通过这些预备指令,同 echo 集群打交道,展开工作。
迁移到实践上,如果我们对 Redis 客户端以及通信协议 RESP 建立理解,并对 NuRaft 的这些示范程序展开改造工作,我们就可以快速地打造出属于我们的分布式类 Redis 存储引擎来(同时,对于其它的分布式存储软件,如 TiKV, Kiwi, 也都可以起到一个辅助理解的作用)。
Echo 服务器的基本原理以及源代码
Echo 服务器做的事情非常简单,就是将外部的输入数据,原样返回,它挂载于一个设定好的端口以及地址之上,等待着来自于外部的连接(在本例中,是 Redis 客户端程序 redis-cli),之后将外部输入的内容传递回去。
而具体的实现源代码,出于简化工作的目的,我直接套用了 ShengYu Talk
的源代码(在这里非常感谢他的无私分享),如下所示:
/*
echo.cpp
A simple C++ echo server
Thanks to ShengYu Talk.
*/
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
const char* host = "0.0.0.0";
int port = 7000;
int main()
{
int sock_fd, new_fd;
socklen_t addrlen;
struct sockaddr_in my_addr, client_addr;
int status;
char indata[512] = {0}, outdata[1024] = {0};
int on = 1;
// create a socket
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd == -1) {
perror("Socket creation error");
exit(1);
}
// for "Address already in use" error message
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int)) == -1) {
perror("Setsockopt error");
exit(1);
}
// server address
my_addr.sin_family = AF_INET;
inet_aton(host, &my_addr.sin_addr);
my_addr.sin_port = htons(port);
status = bind(sock_fd, (struct sockaddr *)&my_addr, sizeof(my_addr));
if (status == -1) {
perror("Binding error");
exit(1);
}
printf("server start at: %s:%d\n", inet_ntoa(my_addr.sin_addr), port);
status = listen(sock_fd, 5);
if (status == -1) {
perror("Listening error");
exit(1);
}
printf("wait for connection...\n");
addrlen = sizeof(client_addr);
while (1) {
new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &addrlen);
printf("connected by %s:%d\n", inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
while (1) {
int nbytes = recv(new_fd, indata, sizeof(indata), 0);
if (nbytes <= 0) {
close(new_fd);
printf("client closed connection.\n");
break;
}
printf("recv: Begin \n");
printf("%s", indata);
printf("recv: End \n");
sprintf(outdata, "echo: %s", indata);
send(new_fd, outdata, strlen(outdata), 0);
}
}
close(sock_fd);
return 0;
}
具体实验流程
保存上述源代码为 echo.cpp
,编译,之后启动挂载,如图所示:
(echo 等待连接)
之后,使用 Redis 客户端连接 echo 服务器,尝试发送一些指令,如图所示:
(可以发现,Redis 客户端在我们输入内容以后就直接结束连接了,因为我们并没有按照 RESP 协议来做工作,但是在研究 echo 服务器的内容以后,还是可以看出其中的学问)
(COMMAND DOCS
在 Redis 指令体系中,代表着 “罗列既有 Redis 服务端所支持的全部指令” 的意思,因此 Redis 客户端的第一件工作,看来应该是了解对面的有关信息,而不是直接闷头执行用户指令,这也确实是我们展开工作的一项基本原理)
这样,我们也就对于 Redis 的客户端,有了了解,而对于 RESP 协议,我的另外一篇文章,参考 https://datapromoto.atomgit.com/explore/journalism/detail/385629933239668736,已经进行了介绍,欢迎读者进行查阅。
写在最后
感谢中国 PostgreSQL 分会的魏波老师与王其达老师,感谢我的本科生导师,袁国铭博士,IvorySQL 社区的任娇老师与牛世继老师,青学会 MOP 社区吴洋老师,他们是我学习 PostgreSQL 的启蒙老师,感谢开放原子开源基金会的张凯老师,他提供了我建设数据库内核专用平台的机会,感谢原 OpenTenBase 社区的符芬菊老师,感谢 PingCAP 的陈小伟老师,KiwiDB 社区的于雨老师与刘月财老师,ohmhong,LongFar 等同志,他们是我在开源存储引擎领域的启蒙老师。
感谢 ShengYu Talk 的 echo 源代码,极好地节约了我的时间。
稻盛和夫的《活法》是一本非常好的书籍,我非常推荐阅读到本文的读者们阅读这本精彩的书籍,它将会将会你一种完全不一样的人生理念,给予你一种新的不断前进的动力!