C++ 制作 echo 服务器:为测试 Redis RESP 协议做准备

C++ 制作 echo 服务器:为测试 Redis RESP 协议做准备

文一

2024-12-01 发布64 浏览 · 0 点赞 · 0 收藏

前言

在我们对 NuRaft 这款优秀的分布式框架进行介绍之前,使用 Echo 服务器对 Redis RESP 建立了解,将会是一个非常好的事情,在本篇文章之中,我们将会通过一个 echo 服务器,管中窥豹地进行研究。

意义何在?在 NuRaft 的示范程序之中(参考下图):

NuRaft-Example

(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,编译,之后启动挂载,如图所示:

compile-execute

(echo 等待连接)

之后,使用 Redis 客户端连接 echo 服务器,尝试发送一些指令,如图所示:

redis-cli

(可以发现,Redis 客户端在我们输入内容以后就直接结束连接了,因为我们并没有按照 RESP 协议来做工作,但是在研究 echo 服务器的内容以后,还是可以看出其中的学问)

echo-show

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 源代码,极好地节约了我的时间。

稻盛和夫的《活法》是一本非常好的书籍,我非常推荐阅读到本文的读者们阅读这本精彩的书籍,它将会将会你一种完全不一样的人生理念,给予你一种新的不断前进的动力!