redis在游戏服务器中的使用初探(一) 环境搭建redis在游戏服务器中的使用初探(二) 客户端开源库选择redis在游戏服务器中的使用初探(三) 信息存储redis在游戏服务器中的使用初探(四) redis应用

在学习分布式对象存储的期间,有这么一个需求

"多个接口服务(本文当作客户端Clinet)需要以固定间隔向所有的多个服务器发送心跳,保证服务器确认客户端状态。

服务器在接收到文件读取请求时候,会广播询问所有数据服务器(本文也当作服务器)存储的数据情况"

以上一对多  的询问,是需要消息队列来进行通讯的

但是其实 redis也可以作为轻量级的消息队列来完成这个需求。

结构图

服务器开启一个线程进行redis订阅模式,当有人在指定频道发布消息时,所有订阅该频道的节点都可以接收到消息。

但是订阅操作如果我们不想采取固定时间间隔去获取频道是否有消息这么LOW的方案,其实是需要做成异步模式的。

而windows下 hredis异步模式是需要libevent支持的。 两者都是linux下运行良好的开源库,在windows下却是问题多多。

经过多次尝试,我决定放弃使用这两个开源库而选择cpp-redis。(linux下使用hredis和libevent ,有时间会试试)

流程如下:

一个服务节点 需要开启一个线程 进行客户端消息队列的订阅,每当收到消息就会调用收到消息的回调函数

而最初开启的服务节点的运行线程会定时的在服务器消息队列发布询问数据存储的信息。

客户端节点则相反 开启一个线程 定时向客户端消息队列发布心跳信息。

最初开启的客户端节点进行服务器消息队列的订阅,若收到服务器的数据存储询问,则进行本身是否存储该数据的判断

由于资源有限,最开始我们开启了5个线程 来模拟 2个服务器和 3个客户端

代码如下

 #include <iostream>
#include <Winsock2.h>
#include <thread>
#include <mutex> #include "cpp_redis/cpp_redis"
#include "tacopie/tacopie" using namespace std; const int serverThreadNum = ;
const int clientThreadNum = ;
const int heartBeatTime = ;
const int ServerQueryTime = ;
const std::string clientChanName = "ClientChan";
const std::string serverChanName = "ServerChan";
std::mutex g_mutex; class WinsockGuard {
public:
WinsockGuard() {
WORD version = MAKEWORD(, );
if (WSAStartup(version, &data) != ) {
std::cerr << "WSAStartup() failure!" << std::endl;
return;
}
} ~WinsockGuard() {
WSACleanup();
}
private:
WSADATA data;
}; bool SubcribCommFunc(int threadNum,bool isServer) {
cpp_redis::subscriber sub; try {
sub.connect("127.0.0.1", , [](const std::string& host, std::size_t port, cpp_redis::subscriber::connect_state status) {
if (status == cpp_redis::subscriber::connect_state::dropped) {
{std::lock_guard<std::mutex> l(g_mutex); std::cout << "client disconnected from " << host << ":" << port << std::endl; }
//should_exit.notify_all();
}
}); }
catch (std::exception& e) {
{std::lock_guard<std::mutex> l(g_mutex); std::cerr << "in " << __FUNCTION__ << ".err = " << e.what() << std::endl; }
return false;
}
std::string chanName;
if (isServer) {chanName = clientChanName;}
else {chanName = serverChanName;} sub.subscribe(chanName.c_str(), [threadNum, isServer](const std::string& chan, const std::string& msg) {
string s;
if (isServer)s = "server ";
else s = "client ";
s += to_string(threadNum);s += " recv ";
{std::lock_guard<std::mutex> l(g_mutex); std::cout << s.c_str() << chan << ": " << msg << std::endl; }
//todo Check heatbeat or response
});
sub.commit(); while () {
std::this_thread::sleep_for(std::chrono::seconds());
} return true;
} bool RecvClientInfo(int i) {
return SubcribCommFunc(i,true);
} bool PublishCommFunc(int threadNum, bool isServer, string publishStr) {
cpp_redis::client client;
try {
client.connect("127.0.0.1", , [threadNum, isServer,&publishStr](const std::string& host, std::size_t port, cpp_redis::client::connect_state status) {
if (status == cpp_redis::client::connect_state::dropped) {
{std::lock_guard<std::mutex> l(g_mutex); std::cout << "disconnected from " << host << ":" << port << std::endl; }
}
});
while () {
std::string chanName;
if (isServer) {chanName = serverChanName;}
else { chanName = clientChanName;} client.publish(chanName.c_str(), publishStr.c_str());
client.commit(); int PubliLoopTime = ;
if (isServer) {PubliLoopTime = ServerQueryTime;}
else {PubliLoopTime = heartBeatTime;} std::this_thread::sleep_for(std::chrono::seconds(PubliLoopTime));
}
}
catch (std::exception& e) {
{std::lock_guard<std::mutex> l(g_mutex); std::cerr << "in " << __FUNCTION__ << ".err = " << e.what() << std::endl; }
return false;
} return true;
} void QueryWhoSaveDataLoop(int i) {
string s = "Server thread ";s += to_string(i);s += " query Who save data? ";
PublishCommFunc(i, true, s);
return;
} void ServerFunc(int i) {
{std::lock_guard<std::mutex> l(g_mutex);std::cout << "Enter ServerFunc threadNo = " << i << std::endl;}
//开启一个订阅客户端消息队列的线程 接受客户端的心跳包
thread t = thread(RecvClientInfo, i);
t.detach(); //开启一个定时检测心跳超时的客户端 todo //本线程不定时随机 发送一个询问各个客户端是否保存有数据
QueryWhoSaveDataLoop(i); std::this_thread::sleep_for(std::chrono::seconds());
} void SendHeatBeatOnTime(int threadNum, int sendTime) {
string s = "client thread ";s += to_string(threadNum);s += " send heartbeat";
PublishCommFunc(threadNum, false, s);
} void ClientFunc(int i) {
{std::lock_guard<std::mutex> l(g_mutex);std::cout << "Enter ClientFunc threadNo = " << i << std::endl;} //开启一个线程 定时发送心跳包
int s = heartBeatTime;
std::thread t = thread(SendHeatBeatOnTime, i, s);
t.detach(); SubcribCommFunc(i, false);
} void Start() {
thread serverThread[serverThreadNum];
thread clientThread[clientThreadNum]; for (int i = ; i < serverThreadNum; i++) {
serverThread[i] = thread(ServerFunc, i);
}
for (int i = ; i < clientThreadNum; i++) {
clientThread[i] = thread(ClientFunc, i);
}
//==================================================
for (int i = ; i < serverThreadNum; i++) {
serverThread[i].join();
}
for (int i = ; i < clientThreadNum; i++) {
clientThread[i].join();
}
} int main()
{
WinsockGuard g;
Start();
std::cout << "Finish!\n";
}

开启redis  运行代码如图

番外: 补上我在ubuntu下进行的libevent + hiredis的异步测试

首先是安装源头更新 更新 gcc  g++  make 等工具

sudo apt-get update

sudo apt-get install g++ gcc

安装 redis server

sudo apt-get install redis-server

现在可以通过下面的命令查看到该进程:
ps -ef|grep redis

然后安装 hiredis 和 libevent

sudo apt-get install libhiredis-dev

sudo apt-get install libevent-dev

安装完成验证下是否正确安装

编写libevent 示例代码

 #include <event.h>
#include <stdio.h> struct event ev;
struct timeval tv; void time_cb(int fd, short event, void *argc)
{
printf( "timer wakeup\n");
event_add(&ev, &tv);
} int main()
{
struct event_base *base = event_init(); tv.tv_sec = ;
tv.tv_usec = ;
evtimer_set(&ev, time_cb, NULL);
event_add(&ev, &tv);
event_base_dispatch(base); return ;
}

libeventTest.c

执行编译命令并运行 gcc -o eventexe libeventTest.c  -levent

./eventexe  执行无错误则验证通过

编写hiredis示例代码

 #include <stdio.h>
#include <hiredis/hiredis.h>
int main()
{
redisContext *conn = redisConnect("127.0.0.1",);
if(conn != NULL && conn->err)
{
printf("connection error: %s\n",conn->errstr);
return ;
}
redisReply *reply = (redisReply*)redisCommand(conn,"set foo 1234");
freeReplyObject(reply); reply = redisCommand(conn,"get foo");
printf("%s\n",reply->str);
freeReplyObject(reply); redisFree(conn);
return ;
}

执行编译命令并运行 gcc -o hiredisCli hiredisTest.c  -lhiredis

./hiredisCli  执行无错误则验证通过

libevent和hiredis都确认无误后 开始测试异步代码

编写异步示例代码

 #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h> #include <hiredis/hiredis.h>
#include <hiredis/async.h>
#include <hiredis/adapters/libevent.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h> void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r;
if (reply == NULL) return;
printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */
redisAsyncDisconnect(c);
} void connectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
printf("Connected...\n");
} void disconnectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
printf("Disconnected...\n");
} int main (int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
struct event_base *base = event_base_new(); redisAsyncContext *c = redisAsyncConnect("127.0.0.1", );
if (c->err) {
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
return ;
} redisLibeventAttach(c,base);
redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-], strlen(argv[argc-]));
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
event_base_dispatch(base);
return ;
}

执行编译命令并运行

gcc -o async async.c -lhiredis -levent

./async

测试成功

利用redis制作消息队列的更多相关文章

  1. Java利用Redis实现消息队列

    应用场景 为什么要用redis?二进制存储.java序列化传输.IO连接数高.连接频繁 一.序列化 这里编写了一个java序列化的工具,主要是将对象转化为byte数组,和根据byte数组反序列化成ja ...

  2. Springboot21 整合redis、利用redis实现消息队列

    1 前提准备 1.1 创建一个springboot项目 技巧01:本博文基于springboot2.0创建 1.2 安装redis 1.2.1 linux版本 参考博文 1.2.2 windows版本 ...

  3. PHP中利用redis实现消息队列处理高并发请求

    将请求存入redis 为了模拟多个用户的请求,使用一个for循环替代 //redis数据入队操作 $redis = new Redis(); $redis->connect('127.0.0.1 ...

  4. Redis 做消息队列

    一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式.利用redis这两种场景的消息队列都能够实现.定义: 生产者消费者模式:生产者生产消息放到队列里,多个消费者同时监听队列, ...

  5. Redis除了做缓存--Redis做消息队列/Redis做分布式锁/Redis做接口限流

    1.用Redis实现消息队列 用命令lpush入队,rpop出队 Long size = jedis.lpush("QueueName", message);//返回存放的数据条数 ...

  6. 程序员过关斩将--redis做消息队列,香吗?

    Redis消息队列 在程序员这个圈子打拼了太多年,见过太多的程序员使用redis,其中一部分喜欢把redis做缓存(cache)使用,其中最典型的当属存储用户session,除此之外,把redis作为 ...

  7. Redis作为消息队列服务场景应用案例

    NoSQL初探之人人都爱Redis:(3)使用Redis作为消息队列服务场景应用案例   一.消息队列场景简介 “消息”是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更 ...

  8. redis resque消息队列

    Resque 目前正在学习使用resque .resque-scheduler来发布异步任务和定时任务,为了方便以后查阅,所以记录一下. resque和resque-scheduler其优点在于功能比 ...

  9. 利用System V消息队列实现回射客户/服务器

    一.介绍 在学习UNIX网络编程 卷1时,我们当时可以利用Socket套接字来实现回射客户/服务器程序,但是Socket编程是存在一些不足的,例如: 1. 服务器必须启动之时,客户端才能连上服务端,并 ...

随机推荐

  1. Ubuntu 16.04 LTS 常用快捷键

    在Linux下Win键就是Super键 启动器 Win(长按) 打开启动器,显示快捷键 Win + Tab 通过启动器切换应用程序 Win + 1到9 与点击启动器上的图标效果一样 Win + Shi ...

  2. docker内存监控与压测

    一直运行的docker容器显示内存已经耗尽,并且容器内存耗尽也没出现重启情况,通过后台查看发现进程没有占用多少内存.内存的监控使用的是cadvisor,计算方式也是使用cadvisor的页面计算方式, ...

  3. 廖雪峰Java7处理日期和时间-3java.time的API-2ZonedDateTime

    ZonedDatetime = LocalDateTime + ZoneId ZonedDateTime:带时区的日期和时间 ZoneId:新的API定义的时区对象(取代几句的java.util.Ti ...

  4. Vue 封装的loading组件

    <template> <div class="loadEffect"> <span></span> <span>< ...

  5. Sublime Text 3激活

    Sublime Text 3激活方式: 一.修改hosts文件: 1:windows系统:      找到 C:\Windows\System32\drivers\etc\hosts 这个文件,  用 ...

  6. C#设计模式(3)——工厂方法模式(Factory Method)

    在简单工厂模式中通过工厂Factory获取不同的对象,但是有一个明显的缺点——简单工厂模式系统难以扩展! 一旦添加新产品就不得不修改简单工厂方法,这样就会造成简单工厂的实现逻辑过于复杂, 可以通过工厂 ...

  7. node.js获取本机Ip, hostName, mac

    //获取ip地址 getIPAdress() { let interfaces = require('os').networkInterfaces(); for (var devName in int ...

  8. javascript权威指南第1章Js概述js语言核心

    <html> <head> <script> function Point(x,y){ this.x=x; this.y=y; } ,); console.log( ...

  9. English-商务英文邮件例句100句

    最常用最专业的商务英文邮件例句100句——塞依SAP培训 字体大小:大 | 中 | 小2013-08-27 17:24 阅读(74) 评论(0) 分类:sap职场  1. I am writing t ...

  10. EF 6.x实现dynamic动态查询

    利用SqlQuery实现动态查询 public static IEnumerable<dynamic> SqlQueryDynamic(this DbContext db, string ...