Redis连接池

news2024/11/16 12:34:47

        本次实现的Redis连接池是一个单例且多线程安全的连接池。

        主要实现的功能为:读取配置,将配置中对应建立redis连接并加入到连接池中,然后从连接池中取出连接使用。每当配置进行修改,重新往池子中加入连接。

  • 通用类

        实现一些基础都会使用的接口,定义宏等。

        有些类是利用RAII思想,构造时获得对象,初始化锁,加锁等,析构时释放对象,释放锁资源,释放锁等。

#pragma once
#include <iostream>
#include "../../hiredis/hiredis/hiredis.h"
#include <pthread.h>
#include <cstring>
#include <algorithm>
#include <map>

#define SAFE_DELETE(x) {if (x != nullptr){ delete x, (x) = nullptr; }}

class AutoReply
{
public:
  AutoReply(redisReply* reply)
  {
    reply_ = nullptr;
    if(reply)
    {
      reply_ = reply;
    }
  }

  ~AutoReply()
  {
    if(reply_)
    {
      freeReplyObject(reply_);
      reply_ = nullptr;

    }
  }

  redisReply* get()
  {
    return reply_;
  }

  bool isErr()
  {
    if (!reply_)
      return true;
    if (reply_->type == REDIS_REPLY_ERROR)
    {
      std::cout << "reply error " << reply_->str << std::endl;
      return true;
    }

    if (reply_->type == REDIS_REPLY_STATUS)
    {
      std::string tmp_str;
       //大写转小写
      //std::transform(reply_->str, reply_->str + strlen(reply_->str), tmp_str.begin(), tolower);
      for(int i = 0; i < strlen(reply_->str); ++i)
      {
        char c = reply_->str[i];
        if(isupper(reply_->str[i]))
        {
          c = tolower(c);
        }
        tmp_str += tolower(c);
      }
      //std::cout << "iserr str" << tmp_str.c_str() << std::endl;
      if (strcmp(tmp_str.c_str(), "ok") != 0)
      {
        std::cout << "reply statue not ok " << tmp_str << "replystr: " << reply_->str << std::endl;
        return true;
      }
    }
    return false;
  }

    bool isPingErr()
  {
    if (!reply_)
      return true;
    if (reply_->type == REDIS_REPLY_ERROR)
    {
      std::cout << "reply error " << reply_->str << std::endl;
      return true;
    }

    if (reply_->type == REDIS_REPLY_STATUS)
    {
      std::string tmp_str;
    //大写转小写
      std::transform(reply_->str, reply_->str + strlen(reply_->str), tmp_str.begin(), tolower);
      //std::cout << "isPingErr str" << tmp_str.c_str() << std::endl;
      if (strcmp(tmp_str.c_str(), "pong") != 0)
      {
        std::cout << "reply statue not pong " << tmp_str << std::endl;
        return true;
      }
    }
    return false;
  }

  AutoReply(const AutoReply& ar)
  {
    reply_ = ar.reply_;
  }
private:
  AutoReply operator=(const AutoReply& ar);
  redisReply* reply_;
};


class qMutex
{
public:
  qMutex()
  {
    pthread_mutex_init(&mt_, nullptr);
  }

  ~qMutex()
  {
    pthread_mutex_destroy(&mt_);
  }

  void lock()
  {
    pthread_mutex_lock(&mt_);
  }

  void unlock()
  {
    pthread_mutex_unlock(&mt_);
  }


private:
  qMutex(const qMutex& qmt);
  qMutex operator=(const qMutex& qmt);
  pthread_mutex_t mt_;
};

class AutoMutex
{
public:
  AutoMutex(qMutex& mutex):mutex_(mutex)
  {
    mutex_.lock();
  }
  ~AutoMutex()
  {
    mutex_.unlock();
  }
private:
  //AutoMutex(const AutoMutex& am);
  AutoMutex& operator=(const AutoMutex& am);
  qMutex& mutex_;
};
  • 操作redis类

        主要是连接redis,操作redis,有密码需要使用auth命令验证密码。

#pragma once

#include "common.h"

enum ConnectState {
  CONNECTSTATE_NONE,
  CONNECTSTATE_CONN,//已连接
  CONNECTSTATE_UNCONN,//断开连接
};

class redisConn{
public:
  redisConn(size_t id, std::string ip = "127.0.0.1", size_t port = 6379, std::string passwd = "123456");
  ~redisConn();

  bool connect();
  bool connectUnblock();

  bool disConnect();

  bool isRun();
  
  bool auth();

  bool ping();
  bool reconnect();
  //断线重连
  void resetReconnectTime() \
  {
    AutoMutex mt(locker_); 
    reconnect_times_ = 0; 
  }
  bool keepAlive();

  bool set(const char* key, const char* val);
  std::string get(const char* key);
private:
  size_t id_;

  std::string ip_;
  size_t port_;
  std::string passwd_;
  ConnectState state_;//连接状态
  size_t reconnect_times_;//连接次数

  redisContext* context_;
  redisReply* reply_;

  qMutex locker_;//为了保证线程安全
};
#include "redisConn.h"

redisConn::redisConn(size_t id, std::string ip, size_t port, std::string passwd)
{
  id_ = id;
  ip_ = ip;
  port_ = port;
  passwd_ = passwd;
  state_ = CONNECTSTATE_NONE;
  context_ = nullptr;
}
redisConn::~redisConn()
{
  disConnect();
}

bool redisConn::connect()
{
  if (context_ != nullptr)
  {
    redisFree(context_);
    context_ = nullptr;      
  }
  context_ = redisConnect(ip_.c_str(), port_);
  if (!context_)
  {
    std::cerr << "connect fail" << std::endl;
    return false;
  }
  if(context_->err)
  {
    std::cerr << "connect err " << context_->errstr << std::endl;
    redisFree(context_);
    context_ = nullptr;
    return false;
  }
  state_ = CONNECTSTATE_CONN;
  std::cout << "connect succ" << std::endl;

  //验证密码
  auth();
  return true;
}

bool redisConn::connectUnblock()
{
  if (context_)
  {
    redisFree(context_);
    context_ = nullptr;
  }
  
  context_ = redisConnectNonBlock(ip_.c_str(), port_);
  if(!context_)
  {
    std::cout << "connect fail" << std::endl;
    return false;
  }

  if(context_->err)
  {
    std::cout << "connect err " << context_->errstr << std::endl;
    redisFree(context_);
    context_ = nullptr;
    return false; 
  }
  state_ = CONNECTSTATE_CONN;
  std::cout << "conn no block succ" << std::endl;

  //验证密码
  //auth();
  return true;
}

bool redisConn::disConnect()
{
  AutoMutex mt(locker_);
  if (context_)
  {
    redisFree(context_);
    context_ = nullptr;
    //SAFE_DELETE(context_);
  }
  state_ = CONNECTSTATE_UNCONN;
  std::cout << "disConnect succ" << std::endl;
  return true;
}

bool redisConn::isRun()
{
  AutoMutex mt(locker_);
  return state_ == CONNECTSTATE_CONN;
}

bool redisConn::auth()
{
  AutoMutex mt(locker_);
  AutoReply reply = (redisReply*)redisCommand(context_, "auth %s", passwd_.c_str());
  if (reply.isErr())
    return false;
  std::cout << "auth succ" << std::endl;
  return true;
}

bool redisConn::ping()
{
  //检查服务器是否在运行
  AutoMutex mt(locker_);
  AutoReply reply = (redisReply*)redisCommand(context_, "ping");
  if(reply.isPingErr())
    return false;
  return true;
}

bool redisConn::reconnect()
{
  if(!connect() && !auth())
  {
    std::cout << "connect un block fail" << std::endl;
    return false;
  }
  state_ = CONNECTSTATE_CONN;
  std::cout << "reconnect succ" << std::endl;
  return true;
}

bool redisConn::keepAlive()
{
  if (state_ == CONNECTSTATE_CONN)
  {
    if(!ping())
    {
      if(!reconnect())
      {
        std::cout << "断线" << ip_ << ":" << port_ << std::endl;
        state_ = CONNECTSTATE_UNCONN;
        return false;

      }
      return true;
    }
    else if(state_ == CONNECTSTATE_UNCONN)
    {
      //断线重连
      if (reconnect_times_ < 10)
      {
        reconnect_times_++;
        if(reconnect())
        {
          reconnect_times_ = 0;
          state_ = CONNECTSTATE_CONN;
          return true;
        }
      }
    }
  }

  return false;
}


bool redisConn::set(const char* key, const char* val)
{
  AutoMutex mt(locker_);
  if(!context_)
    return false;
  AutoReply reply = (redisReply*)redisCommand(context_, "set %s %s", key, val);
  if(reply.isErr())
  {
    return false;
  }
  return true;
}

std::string redisConn::get(const char* key)
{
  AutoMutex mt(locker_);
  if(!context_)
    return "";
  AutoReply reply = (redisReply*)redisCommand(context_, "get %s", key);
  if(reply.isErr())
  {
    return "";
  }

  return reply.get()->str ? reply.get()->str : "";

}
  • 单例泛型类

        对应的连接池和读取配置只需要一个对象,可以实现为单例。

         使用这个单例:

  1. 继承该单例类(继承单例接口),并且设为友元(由于构造,拷贝构造,赋值重载运算符设为私有,单例类需要使用)。
  2. 设为友元。

        两种方式使用方法不同,看下面例子。

#pragma once

#include "common.h"

template<class T>
class Singleton
{
public:
    static T* getMe()
    {
        if(single_ == nullptr)
        {
            mt_.lock();
            if(single_ == nullptr)
            {
                single_ = new T;
            }
            mt_.unlock();
        }
        return single_;
    }

    static void delMe()
    {
        if(single_)
        {
            SAFE_DELETE(single_);
        }
    }
protected:
    //需要写出protected,不然继承的构造无法调用
    Singleton()
    {
    }
    ~Singleton()
    {
        delMe();
    }
private:
    //外部不允许赋值喝拷贝构造
    Singleton(const Singleton& );
    const Singleton& operator=(const Singleton& );
    static T* single_;
    static qMutex mt_;
};

template<class T>
T* Singleton<T>::single_ = nullptr;

template<class T>
qMutex Singleton<T>::mt_;

        使用实例:

 

  •  连接池类和读配置类

        连接池主要实现了将连接加入到连接池中,和从连接池中拿出连接。

        读取配置,配置主要为读取唯一标识——服务器ip——redis端口。如果需要和可以读取密码等。

        连接池中的每一个连接有一个唯一标识,通过标识来获取对一个连接,标识可以是对应表冈功能(比如:如果是游戏中的排行榜,可以是不同的排行榜,也可以是不同玩家,每个玩家对应一个redis)。

#pragma once 

#include "qSingleton.h"
#include "redisConn.h"
#include <fstream>
#include <cstring>


struct ConnectInfo
{
  ConnectInfo()
  {
    state_ = 0;
  }

  redisConn* con_;
  int state_;
};

class redisPool : public Singleton<redisPool>
{
  friend class Singleton<redisPool>;
public:

  bool addClient(size_t id, std::string ip, size_t port);
  bool delClient();
  bool keepAlive();

  bool updateCfgConn();
  redisConn* getClientByID(size_t id);
private:
  redisPool();
  ~redisPool();
  redisPool(const redisPool& );
  const redisPool& operator=(const redisPool&);

  ConnectInfo* getConnectInfo(size_t id);
  typedef std::map<size_t, ConnectInfo> ConnectInfoT;
  ConnectInfoT connect_;

};

struct RedisConnCfg
{
  RedisConnCfg()
  {
    id_ = 0;
    port_ = 0;
  }

  void print()
  {
      std::cout << id_ << ":" << ip_ << ":" << port_ << std::endl;
  }
  size_t id_;
  std::string ip_;
  size_t port_;
};

class RedisConnCfgMgr : public Singleton<RedisConnCfgMgr>
{
  friend class Singleton<RedisConnCfgMgr>;
public:
  bool loadCfg();
  const std::vector<RedisConnCfg>& getRedisConnCfg(){ return cfg_; }
private:
  RedisConnCfgMgr();
  ~RedisConnCfgMgr();
  RedisConnCfgMgr(const RedisConnCfgMgr&);
  const RedisConnCfgMgr& operator=(const RedisConnCfgMgr& c);

  std::vector<RedisConnCfg> cfg_;
};
#include "redisPool.h"

redisPool::redisPool()
{

}

redisPool::~redisPool()
{
  delClient();
}

bool redisPool::addClient(size_t id, std::string ip, size_t port)
{
  ConnectInfo* pInfo = getConnectInfo(id);
  if(!pInfo)
  {
    pInfo = &connect_[id];
    pInfo->con_ = new redisConn(id, ip.c_str(), port, "123456");
    pInfo->state_ = 1;
  }

  if(!pInfo->con_->isRun())
  {
    if(pInfo->con_->connect() && pInfo->con_->auth())
    {
      return true;
    }
    else 
    {
      pInfo->con_->disConnect();
      return false;
    }
  }

  return true;
}

bool redisPool::delClient()
{
  ConnectInfoT::iterator it = connect_.begin();
  for(; it != connect_.end(); ++it)
  {
    it->second.con_->disConnect();
    it->second.state_ = 0;
    SAFE_DELETE(it->second.con_);
    it->second.con_ = nullptr;
  }  
  connect_.clear();
}

redisConn* redisPool::getClientByID(size_t id)
{
  ConnectInfoT::iterator it = connect_.find(id);
  if(it != connect_.end())
  {
    return it->second.con_;
  }
  return nullptr;
}

ConnectInfo* redisPool::getConnectInfo(size_t id)
{
  ConnectInfoT::iterator it = connect_.find(id);
  if(it != connect_.end())
  {
    return &it->second;
  }
  return nullptr;
}

bool redisPool::keepAlive()
{
  ConnectInfoT::iterator it = connect_.begin();
  for(; it != connect_.end(); ++it)
  {
    it->second.con_->keepAlive();
  }
}

bool redisPool::updateCfgConn()
{
  //重置连接状态
  ConnectInfoT::iterator it = connect_.begin();
  for(; it != connect_.end(); ++it)
  {
    it->second.state_ = 0;
  }
  //更新配置中的redis到redisPool中
  const std::vector<RedisConnCfg>& cfg_vec = RedisConnCfgMgr::getMe()->getRedisConnCfg();
  for(int i = 0; i < cfg_vec.size(); ++i)
  {
    //在连接池中进行重连
    const RedisConnCfg& tmp = cfg_vec[i];
    it = connect_.find(tmp.id_);
    if(it != connect_.end())
    {
      it->second.con_->resetReconnectTime();
      it->second.con_->keepAlive();
      it->second.state_ = 1;
    }
    else
    {
      //不在进行添加并连接redis
      addClient(tmp.id_, tmp.ip_, tmp.port_);
    }
  }

  //删除掉未连接的redis
  it = connect_.begin();
  for(; it != connect_.end(); )
  {
    if(!it->second.state_)
    {
      it->second.con_->disConnect();
      SAFE_DELETE(it->second.con_);
      connect_.erase(it++);
    }
    else
    {
      it++;
    }
  }

  return true;
}

RedisConnCfgMgr::RedisConnCfgMgr()
{

}

RedisConnCfgMgr::~RedisConnCfgMgr()
{

}

bool RedisConnCfgMgr::loadCfg()
{
  std::ifstream infile("redisCfg.txt");
  if(!infile.is_open())
  {
    std::cout << "file open fail" << std::endl;
    return false;
  }

  std::vector<std::string> cfg_vec;
  char buf[1024];
  while(infile.getline(buf, sizeof(buf)))
  {
    //std::cout << buf << std::endl;
    char* p = strtok(buf, "-");
    while(p)
    {
      cfg_vec.push_back(p);
      p = strtok(NULL, "-");
    }
    if(cfg_vec.size() >= 3)
    {
      RedisConnCfg tmp;
      tmp.id_ = atoi(cfg_vec[0].c_str());
      tmp.ip_ = cfg_vec[1];
      tmp.port_ = atoi(cfg_vec[2].c_str());
      tmp.print();
      cfg_.push_back(tmp);
    }
    cfg_vec.clear();
  }
  infile.close();
  return true;
}

        测试:

#include "redisPool.h"


void* setHandleFunc(void* arg)
{
  //std::cout << pthread_self() << std::endl;
  RedisConnCfg* tmp = (RedisConnCfg*)arg;
  redisConn* conn =  redisPool::getMe()->getClientByID(tmp->id_);
  if(conn)
  {
    char kbuf[64];
    char vbuf[64];
    snprintf(kbuf, sizeof(kbuf), "k%d", tmp->id_);
    snprintf(vbuf, sizeof(vbuf), "v%ld", pthread_self());
    //std::cout << kbuf << "---" << vbuf << std::endl;
    conn->set(kbuf, vbuf);
  }
  return nullptr;
}

void* getHandleFunc(void* arg)
{
  //std::cout << pthread_self() << std::endl;
  RedisConnCfg* tmp = (RedisConnCfg*)arg;
  redisConn* conn =  redisPool::getMe()->getClientByID(tmp->id_);
  if(conn)
  {
    char kbuf[64];
    snprintf(kbuf, sizeof(kbuf), "k%d", tmp->id_);
    std::string str = conn->get(kbuf);
    std::cout << kbuf << ":" << str << std::endl;
  }
  return nullptr;
}

int main()
{
  //加载配置
  RedisConnCfgMgr::getMe()->loadCfg();
  redisPool::getMe()->updateCfgConn();

  std::vector<pthread_t> pt_vec;
  const std::vector<RedisConnCfg>& cfg_vec = RedisConnCfgMgr::getMe()->getRedisConnCfg();
  for(int i = 0; i < cfg_vec.size(); ++i)
  {
    //创建线程
    pthread_t pid = 0;
    pthread_create(&pid, nullptr, setHandleFunc, (void*)&cfg_vec[i]);
    pt_vec.push_back(pid);
  }

  for(int i = 0; i < pt_vec.size(); ++i)
  {
    pthread_detach(pt_vec[i]);
  }

  pt_vec.clear();
  for(int i = 0; i < cfg_vec.size(); ++i)
  {
    //创建线程
    pthread_t pid = 0;
    pthread_create(&pid, nullptr, getHandleFunc, (void*)&cfg_vec[i]);
    pt_vec.push_back(pid);
  }

  for(int i = 0; i < pt_vec.size(); ++i)
  {
    pthread_detach(pt_vec[i]);
  }

  while(1)
  {}

  return 0;
}

int main()
{
  RedisConnCfgMgr rm;
  rm.loadCfg();

  return 0;
}

         Makefile编译文件:

        主要需要下载hiredis库。

main:main.cpp redisPool.cpp redisConn.cpp
	g++ -g $^ -o $@ -std=c++11 -lhiredis  -lpthread


.PHONY:clean
clean:
	rm -rf main

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1712807.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

将文件批量重命名001到100?怎么批量修改文件夹名字?这四款工具不要错过!

你们有没有遇到过需要批量修改文件&#xff08;文件夹&#xff09;名的情况&#xff1f;从网上下载一些文件都会带有一些后缀名字。大量的文件&#xff0c;一个一个修改重命名的话&#xff0c;这简直是个头疼的事情。市面上虽然有很多批量文件重命名工具&#xff0c;但要么收费…

勒索软件统计数据揭示了网络勒索的惊人速度

本文通过各种报告摘录&#xff0c;提供了有关当前勒索软件形势的统计数据和见解。 全球勒索病毒危机加剧 NTT安全控股《2024全球威胁情报报告》&#xff08;2024年5月&#xff09; 据NTT安全控股公司的《2024年全球威胁情报报告》显示&#xff0c;勒索软件和勒索事件在2023年激…

静态测试---基于WorkList的活跃变量分析

本文主要用于记录在活跃变量分析实验中的报错及解决&#xff0c;涉及静态测试的详细原理内容较少&#xff0c;编译运行底层逻辑偏多。 一、实验要求 1&#xff09;使用llvm基于框架实现一个基于WorkList的活跃变量分析demo。变量在某个程序点有两种状态&#xff0c;live 或 dea…

在PyCharm中,不希望新建Python文件自动打开Python控制台

很久没更新水一下 第一步编辑配置 第二步编辑配置模板 第三步取消勾选 第四步确定

MySQL 解决登录报错 - 错误1130- Host xxx is not allowed to connect to this server

1、原因 没有给远程连接权限 2、解决 2.1 打开命令行提示符界面输入命令cd C:\Program Files\MySQL\MySQL Server 8.0\bin\ 2.2 连接 MySQL 数据库 输入命令 mysql -u root -p &#xff0c;然后输入密码 回车登录 2.3 查看当前表中的数据库 show databases;查看当前使用的数…

国内外专业权威最厉害的易经姓名学大师颜廷利:从零售与批发到生活智慧

国内外最专业最权威最厉害的易经姓名学大师颜廷利&#xff1a;从零售与批发到生活智慧 在经济的快速发展中&#xff0c;"零售"与"批发"作为商业运作的两大支柱&#xff0c;早已融入我们的日常语言。然而&#xff0c;当我们以中文的韵味倒读"零售"…

动手学深度学习24 AlexNet

动手学深度学习24 AlexNet 1. AlexNet传统机器学习AlexNet 2. 代码3. QA 1. AlexNet 传统机器学习 AlexNet AlexNet & LeNet对比 加了三层隐藏层&#xff0c;通道数和全连接层单元数更多 计算需要的浮点数&#xff0c;10亿次浮点数计算。 2. 代码 import torch fro…

大气污染溯源算法及其技术实现

污染溯源基础概念知识 大气污染溯源是指识别并追踪污染物的来源及其传输过程&#xff0c;以确定造成大气污染的根本原因和污染物传播路径的技术和方法。这对于制定有效的控制和减轻污染策略至关重要。大气污染的溯源主要涉及以下几个方面&#xff1a; 污染源识别&#xff1a;…

【信息学奥赛】字典的键和值对换

【信息学奥赛】字典的键和值对换 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 输入一个字典&#xff0c;要求将该字典的键和值对换。&#xff08;注意&#xff0c;字典中有键的值是重复的&#xff09; 输入&#xff1a; 一行&#xff0…

详解 HTML5 服务器发送事件(Server-Sent Events)

HTML5 服务器发送事件&#xff08;server-sent event&#xff09;允许网页获得来自服务器的更新。 EventSource 是单向通信的&#xff08;是服务器向客户端的单向通信&#xff0c;客户端接收来自服务器的事件流&#xff09;、基于 HTTP 协议&#xff08;EventSource 是基于标准…

提升B端图表设计技能:教程分享

图表是数据可视化的常用表现形式&#xff0c;是对数据的二次加工&#xff0c;可以帮助我们理解数据、洞悉数据背后的真相&#xff0c;让我们更好地适应这个数据驱动的世界。本期就来带大家学习图表的设计及构成&#xff0c;帮助大家更好的理解图表设计。 设计教程源文件http:/…

【vscode篇】1-VScode设置语言为中文,2-解决中文注释乱码问题。

设置语言为中文 在前端开发中&#xff0c;Visual Studio Code(简称vscode)是一个非常好用的工具&#xff0c;但第一次打开vscode会发现界面为英文&#xff0c;这对很多开发者来说会很不友好&#xff08;比如我&#xff09;&#xff0c;把界面设置成中文只需要安装一个插件即可&…

博途S7-1200/1500PLC区域长度错误

S7-1200/1500PLC故障有时提示PLC区域长度错误&#xff0c;如下图所示 1、区域长度错误 未完...

08.tomcat多实例

在加两个tomcat实例 [rootweb01 ~]# ll apache-tomcat-8.0.27.tar.gz -rw-r--r-- 1 root root 9128610 10月 5 2015 apache-tomcat-8.0.27.tar.gz [rootweb01 ~]# tar xf apache-tomcat-8.0.27.tar.gz [rootweb01 ~]# cp -a apache-tomcat-8.0.27 tomcat_8081 [rootweb01 ~…

性能测试(一)—— 性能测试理论+jmeter的使用

1.性能测试介绍 定义&#xff1a;软件的性能是软件的一种非功能特性&#xff0c;它关注的不是软件是否能够完成特定的功能&#xff0c;而是在完成该功能时展示出来的及时性。 由定义可知性能关注的是软件的非功能特性&#xff0c;所以一般来说性能测试介入的时机是在功能测试完…

便民社区信息小程序源码系统 功能强大 带生活电商+求职招聘功能 带完整的安装代码包以及搭建教程

系统概述 便民社区信息小程序源码系统是一款集多种功能于一身的综合性平台。它旨在为用户提供便捷的生活服务&#xff0c;满足社区居民的各种需求。无论是购物、求职还是获取社区信息&#xff0c;都能在这个平台上得到满足。该系统采用先进的技术架构&#xff0c;确保系统的稳…

sendmail发送邮件配置详解?如何正确设置?

sendmail发送邮件如何保障安全&#xff1f;AokSend有何安全措施&#xff1f; 为了确保sendmail发送邮件的高效性和安全性&#xff0c;正确配置是至关重要的。本文将详细介绍sendmail发送邮件的配置步骤&#xff0c;并探讨如何保障sendmail发送邮件的安全性。同时&#xff0c;我…

排序进阶----插入排序,希尔排序

各位看官们好&#xff0c;接下来鄙人想与大家分享的实现被称为六大排序之一的插入排序。其实关于这六大排序在我们最开始就已经接触过了。我们在最开始学习c语言的时候&#xff0c;我们要学习到其中之一的冒泡排序。虽然现在看起来冒泡排序确实是没有太大的实际效果&#xff0c…

ROS无人机追踪小车项目开发实战 | 第四届中国智能汽车创新大会圆满结束

2024年5月26日&#xff0c;阿木实验室在深圳第四届中国智能汽车创新大会上&#xff0c;开展的《Prometheus开源平台-ROS无人机追踪小车项目开发实战课》圆满结束。 该实战课从初学者的角度出发&#xff0c;通过实践性讲解和开发&#xff0c;使开发者们系统地学习了硬件系统架构…

无需开孔,安全美观:低功耗微波雷达模块开启宠物喂食器新未来

在快节奏的现代生活中&#xff0c;宠物已成为许多家庭的重要成员。然而&#xff0c;忙碌的主人常常为如何确保宠物按时进食而困扰。近年来&#xff0c;智能家居技术飞速发展&#xff0c;宠物喂食器也逐渐智能化&#xff0c;极大地方便了宠物主人。今天&#xff0c;我们要介绍的…