想没想过,自己写一个redis客户端,是不是很难呢?
  其实,并不是特别难。
  首先,要知道redis服务端用的通信协议,建议直接去官网看,博客啥的其实也是从官网摘抄的,或者从其他博客抄的(忽略)。
  协议说明中文官网地址: http://www.redis.cn/topics/protocol.html
 

  redis通信协议

  

  列出主要的点,便于对于下面程序的理解。
  Redis在TCP端口6379(默认端口,在配置可以修改)上监听到来的连接,在客户端与服务器端之间传输的每个Redis命令或者数据都以\r\n结尾。
 

  回复(服务端可客户端恢复的协议)

  Redis用不同的回复类型回复命令。它可能从服务器发送的第一个字节开始校验回复类型:
  * 用单行回复(状态回复),回复的第一个字节将是“+”
  * 错误消息,回复的第一个字节将是“-”
  * 整型数字,回复的第一个字节将是“:”
  * 批量回复,回复的第一个字节将是“$”
  * 多个批量回复,回复的第一个字节将是“*”
 

  Bulk Strings(批量回复)

  批量回复被服务器用于返回一个单二进制安全字符串。
  C: GET mykey
  S: $6\r\nfoobar\r\n
  服务器发送第一行回复,该行以“$”开始后面跟随实际要发送的字节数,随后是CRLF,然后发送实际数据,随后是2个字节的额外数据用于最后的CRLF。服务器发送的准确序列如下:
  "$6\r\nfoobar\r\n"
  如果请求的值不存在,批量回复将使用特殊的值-1来作为数据长度,例如:
  C: GET nonexistingkey
  S: $-1
  当请求的对象不存在时,客户端库API不会返回空字符串,而会返回空对象。例如:Ruby库返回‘nil’,而C库返回NULL(或者在回复的对象里设置指定的标志)等等。
   

  二进制

  简单说下二进制,就是会包含\0,所以C语言在处理的时候,就不能用str函数,像strlen、strcpy等,因为它们都是以\0来判断字符串结尾的。
 

  redis集群  

  写redis客户端,要考虑到单机和集群,单机知道上面的协议就可以写了,集群还要学习一下。

  超简单搭建redis集群

  

  官网也介绍了怎么搭建redis集群,试过比较麻烦,因为用的centos6.5,如果用较新的centos,可能会好点。
  redis集群超简单搭建方法:https://blog.csdn.net/cjfeii/article/details/47320351

  Redis 集群的数据分片

  

  Redis 集群没有使用一致性hash, 而是引入了 哈希槽的概念.
  Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分hash槽,举个例子,比如当前集群有3个节点,那么:
  * 节点 A 包含 0 到 5500号哈希槽.
  * 节点 B 包含5501 到 11000 号哈希槽.
  * 节点 C 包含11001 到 16384号哈希槽.
  这种结构很容易添加或者删除节点. 比如如果我想新添加个节点D, 我需要从节点 A, B, C中得部分槽到D上. 如果我想移除节点A,需要将A中的槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可.   由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态.
  

  Redis 集群协议中的客户端和服务器端

  在 Redis 集群中,节点负责存储数据、记录集群的状态(包括键值到正确节点的映射)。集群节点同样能自动发现其他节点,检测出没正常工作的节点, 并且在需要的时候在从节点中推选出主节点。
  为了执行这些任务,所有的集群节点都通过TCP连接(TCP bus?)和一个二进制协议(集群连接,cluster bus)建立通信。 每一个节点都通过集群连接(cluster bus)与集群上的其余每个节点连接起来。  节点们使用一个 gossip 协议来传播集群的信息,这样可以:发现新的节点、 发送ping包(用来确保所有节点都在正常工作中)、在特定情况发生时发送集群消息。集群连接也用于在集群中发布或订阅消息。
  由于集群节点不能代理(proxy)请求,所以客户端在接收到重定向错误(redirections errors) -MOVED 和 -ASK 的时候, 将命令重定向到其他节点。理论上来说,客户端是可以自由地向集群中的所有节点发送请求,在需要的时候把请求重定向到其他节点,所以客户端是不需要保存集群状态。 不过客户端可以缓存键值和节点之间的映射关系,这样能明显提高命令执行的效率。

  -MOVED

  

  简单说下返回-MOVED的情况,就是客户端连节点A请求处理key,但其实key其实在节点B,就返回-MOVED,协议如下:-MOVED 3999 127.0.0.1:6381
不用考虑-ASK的情况。

  C语言实现redis客户端

  代码如下:

  

#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <sys/poll.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h> ssize_t sock_write_loop( int fd, const void *vptr, size_t n )
{
size_t nleft = ;
ssize_t nwritten = ;
const char *ptr; ptr = (char *) vptr;
nleft = n; while( nleft > )
{
if( (nwritten = write(fd, ptr, nleft) ) <= )
{
if( errno == EINTR )
{
nwritten = ; //再次调用write
}
else
{
return -;
}
}
nleft = nleft - nwritten;
ptr = ptr + nwritten;
}
return(n);
} int sock_read_wait( int fd, int timeout )
{
struct pollfd pfd; pfd.fd = fd;
pfd.events = POLLIN;
pfd.revents = ; timeout *= ; for (;;)
{
switch( poll(&pfd, , timeout) )
{
case -:
if( errno != EINTR )
{
return (-);
}
continue; case :
errno = ETIMEDOUT;
return (-); default:
if( pfd.revents & POLLIN )
return ();
else
return (-);
}
} } ssize_t sock_read_tmo( int fd, void *vptr, size_t len, int timeout )
{
if( timeout > && sock_read_wait(fd, timeout) < )
return (-);
else
return (read(fd, vptr, len)); } int sock_connect_nore(const char *IPaddr , int port , int timeout)
{
// char temp[4096];
int sock_fd = , n = , errcode = ;
struct sockaddr_in servaddr; if( IPaddr == NULL )
{
return -;
} if( (sock_fd = socket(AF_INET, SOCK_STREAM, ) ) < )
{
return -;
} memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port); //changed by navy 2003.3.3 for support domain addr
//if( (servaddr.sin_addr.s_addr = inet_addr(IPaddr) ) == -1 )
if( (errcode = inet_pton(AF_INET, IPaddr, &servaddr.sin_addr) ) <= )
{
//added by navy 2003.3.31 for support domain addr
struct hostent* pHost = NULL, host;
char sBuf[], sHostIp[];
int h_errnop = ; memset(&host, , sizeof(host));
memset(sBuf, , sizeof(sBuf));
memset(sHostIp, , sizeof(sHostIp));
pHost = &host; #ifdef _SOLARIS_PLAT
//solaris
if( (gethostbyname_r(IPaddr, pHost, sBuf, sizeof(sBuf), &h_errnop) == NULL) ||
#else
//linux
if( (gethostbyname_r(IPaddr, pHost, sBuf, sizeof(sBuf), &pHost, &h_errnop) != ) ||
#endif
(pHost == NULL) )
{
close(sock_fd);
return -;
} if( pHost->h_addrtype != AF_INET && pHost->h_addrtype != AF_INET6 )
{
close(sock_fd);
return -;
} //目前仅取第一个IP地址
if( (inet_ntop(pHost->h_addrtype, *(pHost->h_addr_list), sHostIp, sizeof(sHostIp)) ) == NULL )
{
close(sock_fd);
return -;
} if( (errcode = inet_pton(AF_INET, sHostIp, &servaddr.sin_addr) ) <= )
{
close(sock_fd); return -;
}
//end added by navy 2003.3.31
} if( (errcode = sock_timed_connect(sock_fd, (struct sockaddr *)&servaddr,
sizeof(servaddr), timeout) ) < )
{
close(sock_fd); return -;
} return sock_fd;
} int sock_connect(const char *IPaddr , int port , int timeout)
{
char temp[];
int sock_fd = , n = , errcode = ;
struct sockaddr_in servaddr; if( IPaddr == NULL )
{
return -;
} if( (sock_fd = socket(AF_INET, SOCK_STREAM, ) ) < )
{
return -;
} memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port); //changed by navy 2003.3.3 for support domain addr
//if( (servaddr.sin_addr.s_addr = inet_addr(IPaddr) ) == -1 )
if( (errcode = inet_pton(AF_INET, IPaddr, &servaddr.sin_addr) ) <= )
{
//added by navy 2003.3.31 for support domain addr
struct hostent* pHost = NULL, host;
char sBuf[], sHostIp[];
int h_errnop = ; memset(&host, , sizeof(host));
memset(sBuf, , sizeof(sBuf));
memset(sHostIp, , sizeof(sHostIp));
pHost = &host; #ifdef _SOLARIS_PLAT
//solaris
if( (gethostbyname_r(IPaddr, pHost, sBuf, sizeof(sBuf), &h_errnop) == NULL) ||
#else
//linux
if( (gethostbyname_r(IPaddr, pHost, sBuf, sizeof(sBuf), &pHost, &h_errnop) != ) ||
#endif
(pHost == NULL) )
{
close(sock_fd);
return -;
} if( pHost->h_addrtype != AF_INET && pHost->h_addrtype != AF_INET6 )
{
close(sock_fd);
return -;
} //目前仅取第一个IP地址
if( (inet_ntop(pHost->h_addrtype, *(pHost->h_addr_list), sHostIp, sizeof(sHostIp)) ) == NULL )
{
close(sock_fd);
return -;
} if( (errcode = inet_pton(AF_INET, sHostIp, &servaddr.sin_addr) ) <= )
{
close(sock_fd); return -;
}
//end added by navy 2003.3.31
} if( (errcode = sock_timed_connect(sock_fd, (struct sockaddr *)&servaddr,
sizeof(servaddr), timeout) ) < )
{
close(sock_fd); return -;
} n = sock_read_tmo(sock_fd, temp, , timeout); //一般错误
if( n <= )
{
close(sock_fd); sock_fd = -;
} return sock_fd;
} int sock_non_blocking(int fd, int on)
{
int flags; if ((flags = fcntl(fd, F_GETFL, )) < ){
return -;
}
if (fcntl(fd, F_SETFL, on ? flags | O_NONBLOCK : flags & ~O_NONBLOCK) < ){
return -;
}
return ;
} int sock_write_wait(int fd, int timeout)
{
struct pollfd pfd; pfd.fd = fd;
pfd.events = POLLOUT;
pfd.revents = ; timeout *= ; for (;;)
{
switch( poll(&pfd, , timeout) )
{
case -:
if( errno != EINTR )
{ return (-);
}
continue; case :
errno = ETIMEDOUT;
return (-); default:
if( pfd.revents & POLLOUT )
return ();
else
return (-);
}
} } int sock_timed_connect(int sock, struct sockaddr * sa, int len, int timeout)
{
int error = ;
socklen_t error_len; sock_non_blocking(sock, ); if( connect(sock, sa, len) == )
{
sock_non_blocking(sock, );
return ();
} if( errno != EINPROGRESS )
{
sock_non_blocking(sock, );
return (-);
} /*
* A connection is in progress. Wait for a limited amount of time for
* something to happen. If nothing happens, report an error.
*/
if( sock_write_wait(sock, timeout) != )
{
sock_non_blocking(sock, );
return (-);
} /*
* Something happened. Some Solaris 2 versions have getsockopt() itself
* return the error, instead of returning it via the parameter list.
*/
error = ;
error_len = sizeof(error); if( getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *) &error, &error_len) != )
{
sock_non_blocking(sock, );
return (-);
}
if( error )
{
errno = error;
sock_non_blocking(sock, );
return (-);
} sock_non_blocking(sock, );
/*
* No problems.
*/
return (); } static int check_ip_in_list(const char *ip, char *iplist)
{
char *token = NULL;
char *saveptr = NULL;
token = strtok_r(iplist, ",", &saveptr);
while(token != NULL)
{
char *ptmp = NULL;
char *ip_mask = strtok_r(token, "/", &ptmp);
if(!ip_mask)
return -; char *ip_bit = strtok_r(NULL, "/", &ptmp); if(ip_bit)
{
int mask_bit = atoi(ip_bit);
if(mask_bit < || mask_bit >)
continue; unsigned long addr[] = { };
sscanf( ip_mask, "%lu.%lu.%lu.%lu", addr, addr + , addr + , addr + );
unsigned long vl1 = addr[] << | addr[] << | addr[] << | addr[]; sscanf( ip, "%lu.%lu.%lu.%lu", addr, addr + , addr + , addr + );
unsigned long vl2 = addr[] << | addr[] << | addr[] << | addr[]; vl1 = ( vl1 >> ( - mask_bit ) );
vl2 = ( vl2 >> ( - mask_bit ) ); if( vl1 == vl2 )
return ;
}
else
{
if(strcmp(ip,ip_mask) == )
return ;
} token = strtok_r(NULL, ",", &saveptr);
} return ;
} static int check_ip_in_redis(const char *redis_host, const char *ip,const char *rq_pro)
{
char buf[];
int loops = ; strcpy(buf, redis_host); do
{
loops ++;
char *ptmp = NULL;
char *host = strtok_r(buf, ":", &ptmp);
if(!host) return -;
char *s_port = strtok_r(NULL, ":", &ptmp);
if(!s_port) return -;
int port = atoi(s_port);
char respone[] = {}; int sock_fd = -;
if((sock_fd = sock_connect_nore(host, port, ))<)
return -; if(sock_write_loop(sock_fd, rq_pro, strlen(rq_pro)) != strlen(rq_pro))
{
close(sock_fd);
return -;
} if(sock_read_tmo(sock_fd, respone, sizeof(respone)-, )<=)
{
close(sock_fd);
return -;
} if(strncmp(":0", respone, ) == )
{
close(sock_fd);
return ;
}
else if(strncmp(":1", respone, ) == )
{
close(sock_fd);
return ;
}
else if(strncmp("$", respone, ) == )
{
int data_size = ;
int ret = ; char *data_line = strstr(respone,"\r\n");
if(!data_line)
{
close(sock_fd);
return -;
}
data_line = data_line+; data_size = atoi(respone+);
if(data_size == -)
{
close(sock_fd);
return ;
}
if(strlen(data_line) == data_size+)
{
printf("line = %d, data_line = %s\n",__LINE__,data_line);
ret=check_ip_in_list(ip, data_line);
close(sock_fd);
return ret;
}
char *data = calloc(data_size+,);
if(!data)
{
close(sock_fd);
return -;
}
strcpy(data,data_line);
int read_size = strlen(data);
int left_size = data_size + - read_size;
while(left_size > )
{
int nread = sock_read_tmo(sock_fd, data+read_size, left_size, );
if(nread<=)
{
free(data);
close(sock_fd);
return -;
}
read_size += nread;
left_size -= nread;
}
close(sock_fd);
printf("line = %d, data = %s\n",__LINE__,data);
ret=check_ip_in_list(ip, data);
free(data);
return ret;
}
else if(strncmp("-MOVED", respone, ) == )
{
close(sock_fd);
char *p = strchr(respone, ' ');
if(p == NULL)
return -; p = strchr(p+, ' ');
if(p == NULL)
return -; strcpy(buf, p+);
}
else
{
close(sock_fd);
return -;
} }while(loops < ); return -;
} int main(int argc,char *argv[])
{
if(argc != )
{
printf("please input ip\n");
return -;
}
const char *redis_ip = "127.0.0.1:7002";
const char *domain = "test.com"; char exist_pro[] = {};
char get_pro[] = {};
snprintf(exist_pro,sizeof(exist_pro),"EXISTS test|%s|%s\r\n",domain,"127.0.0.1");
snprintf(get_pro,sizeof(get_pro),"GET test_%s\r\n",domain);
int loops = ;
int ret = ;
do
{
loops ++;
ret = check_ip_in_redis(redis_ip, argv[],exist_pro);
if(ret == )
ret = check_ip_in_redis(redis_ip, argv[],get_pro);
}while(loops < && ret < ); printf("line = %d, ret = %d\n",__LINE__,ret); return ret;
}

c_redis_cli.c

  主要看这个check_ip_in_redis函数就行了,其它都是一些socket的封装。

  

  python实现redis客户端

  

#!/usr/bin/python

import sys
import socket def main(argv):
if(len(argv) != 3):
print "please input domain ip!"
return
host = "192.168.188.47"
port = 7002
while 1:
s = socket.socket()
s.connect((host, port)) cmd = 'set %s_white_ip %s\r\n' % (argv[1],argv[2])
s.send(cmd)
res = s.recv(32)
s.close() if res[0] == "+":
print "set domain white ip suc!"
return
elif res[0:6] == "-MOVED":
list = res.split(" ")
ip_list = list[2].split(":")
host = ip_list[0]
port = int(ip_list[1])
else:
print "set domain white ip error!"
return if __name__ == "__main__":
main(sys.argv)

  总结

  多去学一些,想想redis客户端怎么实现的,对redis的理解会更加深入,写了这部分之后,对redis集群有了更加深入的了解了

  

用C、python手写redis客户端,兼容redis集群 (-MOVED和-ASK),快速搭建redis集群的更多相关文章

  1. python手写bp神经网络实现人脸性别识别1.0

    写在前面:本实验用到的图片均来自google图片,侵删! 实验介绍 用python手写一个简单bp神经网络,实现人脸的性别识别.由于本人的机器配置比较差,所以无法使用网上很红的人脸大数据数据集(如lf ...

  2. 使用java语言基于SMTP协议手写邮件客户端

    使用java语言基于SMTP协议手写邮件客户端 1. 说明 电子邮件是互联网上常见的应用,他是互联网早期的产品,直至今日依然受到广大用户的喜爱(在中国可能因为文化背景不同,电子邮件只在办公的时候常用) ...

  3. [Windows Azure] 使用 Windows Azure 快速搭建 Redis 服务器

    [Windows Azure] 使用 Windows Azure 快速搭建 Redis 服务器   Redis相信玩开源,大数据的朋友们并不陌生,大家最熟悉的使用者就是新浪微博,微博的整体数据缓存都是 ...

  4. centos7 快速搭建redis集群环境

    本文主要是记录一下快速搭建redis集群环境的方式. 环境简介:centos 7  + redis-3.2.4 本次用两个服务6个节点来搭建:192.168.116.120  和  192.168.1 ...

  5. Python 手写数字识别-knn算法应用

    在上一篇博文中,我们对KNN算法思想及流程有了初步的了解,KNN是采用测量不同特征值之间的距离方法进行分类,也就是说对于每个样本数据,需要和训练集中的所有数据进行欧氏距离计算.这里简述KNN算法的特点 ...

  6. python手写神经网络实现识别手写数字

    写在开头:这个实验和matlab手写神经网络实现识别手写数字一样. 实验说明 一直想自己写一个神经网络来实现手写数字的识别,而不是套用别人的框架.恰巧前几天,有幸从同学那拿到5000张已经贴好标签的手 ...

  7. redis客户端修改了key-value对之后有时会报MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist o...错误,不能持久化

    解决方案,连接redis客户端 redis目录下:redis-cli -h 127.0.0.1 -p 6379-h后为redis服务器ip,-p后为端口号进入redis-client之后输入命令 co ...

  8. 快速搭建Redis缓存数据库

    之前一篇随笔——Redis安装及主从配置已经详细的介绍过Redis的安装于配置.本文要讲的是如何在已经安装过Redis的机器上快速的创建出一个新的Redis缓存数据库. 一.环境介绍 1) Linux ...

  9. 快速搭建redis单机版和redis集群版

    单机版 第一步:需要安装redis所需的C语言环境,若虚拟机联网,则执行 yum install gcc-c++ 第二步:redis的源码包上传到linux系统 第三步:解压缩redis   tar ...

随机推荐

  1. BN系列-未完待续

    BN.LN.IN.GN Cross-Iteration Batch Normalization 因为有时候我们的计算能力有限,所以BN设置的比较小,这样BN效果就比较差. 因此我们将最近几次的batc ...

  2. ResNeSt:Split attention

    https://www.cnblogs.com/xiximayou/p/12728644.html 下面是SE和SK这两个网络,兄弟俩很相似 下面是具体的每个cardinal(翻译为枢纽)网络,和SK ...

  3. java final关键字与static关键字

    一  final关键字 1.final修饰类不可以被继承,但是可以继承其他类. 例如: class Yy {} final class Fu extends Yy{} //可以继承Yy类 class ...

  4. 洛谷P1048 采药 二维dp化一维

    题目描述 辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师.为此,他想拜附近最有威望的医师为师.医师为了判断他的资质,给他出了一个难题.医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个 ...

  5. K8s集群verification error" while trying to verify candidate authority certificate "kubernetes"

    问题内容 because of "crypto/rsa: verification error" while trying to verify candidate authorit ...

  6. css中关于:nth-child()和:nth-of-type()的深入理解

    css中关于:nth-child()和:nth-of-type()的深入理解 在css中有这样一类属性,是以:nth-开头的,其中最常见的就是:nth-child() 和 :nth-of-type() ...

  7. Object.prototype.__proto__, [[prototype]] 和 prototype

    Object.prototype.__proto__ , [[prototype]] 和 prototype Object.prototype.__proto__ 是什么? __proto__ 是一个 ...

  8. Petya and Graph/最大权闭合子图、最小割

    原题地址:https://codeforces.com/contest/1082/problem/G G. Petya and Graph time limit per test 2 seconds ...

  9. window下git多账户管理

    前言 一般情况下,我们都是一台电脑配置一个Git账号,使用如下命令: git config --global user.name "your name" git config -- ...

  10. psutil 简单使用!

    psutil.cpu_percent() cpu 百分比 mem = psutil.virtual_memory()mem.total,mem.used mem.free psutil.cpu_cou ...