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.openatom.tech/explore/journalism/detail/385629933239668736,已经进行了介绍,欢迎读者进行查阅。
写在最后
感谢中国 PostgreSQL 分会的魏波老师与王其达老师,感谢我的本科生导师,袁国铭博士,IvorySQL 社区的任娇老师与牛世继老师,青学会 MOP 社区吴洋老师,他们是我学习 PostgreSQL 的启蒙老师,感谢开放原子开源基金会的张凯老师,他提供了我建设数据库内核专用平台的机会,感谢原 OpenTenBase 社区的符芬菊老师,感谢 PingCAP 的陈小伟老师,KiwiDB 社区的于雨老师与刘月财老师,ohmhong,LongFar 等同志,他们是我在开源存储引擎领域的启蒙老师。
感谢 ShengYu Talk 的 echo 源代码,极好地节约了我的时间。
稻盛和夫的《活法》是一本非常好的书籍,我非常推荐阅读到本文的读者们阅读这本精彩的书籍,它将会将会你一种完全不一样的人生理念,给予你一种新的不断前进的动力!

